├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmignore
├── .readthedocs.yml
├── .soliumignore
├── .soliumrc.json
├── .travis.yml
├── LICENSE
├── README.rst
├── codechecks.yml
├── contracts
├── CTHelpers.sol
├── ConditionalTokens.sol
├── ERC1155
│ ├── ERC1155.sol
│ ├── ERC1155TokenReceiver.sol
│ ├── IERC1155.sol
│ ├── IERC1155TokenReceiver.sol
│ └── README.md
└── Migrations.sol
├── docs
├── Makefile
├── _static
│ ├── all-positions-from-two-conditions.png
│ ├── merge-positions.png
│ ├── redemption.png
│ ├── v1-cond-market-abc-hilo.png
│ ├── v1-cond-market-hilo-abc.png
│ ├── v1-cond-market-ot-compare.png
│ ├── v2-cond-market-ot-compare.png
│ ├── v2-cond-market-slots-only.png
│ └── valid-vs-invalid-splits.png
├── audit
│ ├── 2020-01-20_accumulator_audit.pdf
│ ├── AuditReport-ConditionalTokens.md
│ └── Initial Review of Elliptic Curve Multiset Hashing Proposal.docx
├── conf.py
├── contributing.rst
├── developer-guide.rst
├── glossary.rst
├── index.rst
├── make.bat
├── motivation.rst
└── requirements.txt
├── migrations
├── 01_initial_migration.js
└── 02_deploy_conditional_tokens.js
├── networks.json
├── package.json
├── test
├── DefaultCallbackHandler.sol
├── ERC1155Mock.sol
├── Forwarder.sol
├── GnosisSafe.sol
├── MockCoin.sol
└── test-conditional-tokens.js
├── truffle-config.js
├── utils
└── id-helpers.js
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | docs/
2 | !.eslintrc.js
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["eslint:recommended", "plugin:prettier/recommended"],
3 | env: {
4 | node: true,
5 | mocha: true
6 | },
7 | globals: {
8 | artifacts: true,
9 | web3: true,
10 | contract: true,
11 | assert: true
12 | },
13 | parserOptions: { ecmaVersion: 8 }
14 | };
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | # Byte-compiled / optimized / DLL files
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | env/
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # dotenv
84 | .env
85 |
86 | # virtualenv
87 | .venv
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # Pycharm
98 | .idea
99 |
100 | # Vagrant
101 | .vagrant
102 |
103 | # Vim
104 |
105 | *~
106 | *.swp
107 | *.swo
108 |
109 | # npm
110 | node_modules/
111 |
112 | # Compass
113 | .sass-cache
114 |
115 | # sqlite
116 | *.sqlite
117 |
118 | */CACHE/*
119 |
120 | # pip
121 | src/
122 |
123 | # OS generated files #
124 | ######################
125 | .DS_Store
126 | .DS_Store?
127 | ._*
128 | .Spotlight-V100
129 | .Trashes
130 | ehthumbs.db
131 | Thumbs.db
132 |
133 | # Coverage stuff #
134 | ##################
135 |
136 | allFiredEvents
137 | coverage.json
138 | coverage/
139 | coverageEnv/
140 | scTopics
141 |
142 | # Project stuff #
143 | #################
144 |
145 | truffle-local.js
146 |
147 | Gemfile
148 | Gemfile.lock
149 | _site/
150 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test/python/
2 | requirements.txt
3 | .travis.yml
4 |
5 | # Documentation build #
6 | #######################
7 | docs/_build/
8 |
9 | # Coverage stuff #
10 | ##################
11 |
12 | allFiredEvents
13 | coverage.json
14 | coverage/
15 | coverageEnv/
16 | scTopics
17 |
18 | # Project stuff #
19 | #################
20 |
21 | truffle-local.js
22 |
23 | Gemfile
24 | Gemfile.lock
25 | _site/
26 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | build:
2 | image: latest
3 |
4 | python:
5 | version: 3
6 | requirements_file: docs/requirements.txt
7 |
--------------------------------------------------------------------------------
/.soliumignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | contracts/ERC1820Registry.sol
3 |
--------------------------------------------------------------------------------
/.soliumrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solium:recommended",
3 | "plugins": [
4 | "security"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - lts/*
4 | script:
5 | - npm run lint
6 | - npm test
7 | - npx codechecks
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Conditional Tokens Contracts
2 | ============================
3 |
4 | .. image:: https://travis-ci.org/gnosis/conditional-tokens-contracts.svg?branch=master
5 | :target: https://travis-ci.org/gnosis/conditional-tokens-contracts
6 | :alt: Build Status
7 |
8 | .. image:: https://badges.greenkeeper.io/gnosis/conditional-tokens-contracts.svg
9 | :target: https://greenkeeper.io/
10 | :alt: Greenkeeper badge
11 |
12 | Smart contracts for conditional tokens.
13 |
14 | `→ Online Documentation`_
15 |
16 | .. _→ Online Documentation: https://docs.gnosis.io/conditionaltokens/
17 |
18 | The conditional tokens contracts are deployed at the following addresses:
19 |
20 | * Mainnet: ```0xC59b0e4De5F1248C1140964E0fF287B192407E0C```
21 | * xDai: ```0xCeAfDD6bc0bEF976fdCd1112955828E00543c0Ce```
22 | * Rinkeby: ```0x36bede640D19981A82090519bC1626249984c908```
23 |
24 |
25 | License
26 | -------
27 |
28 | All smart contracts are released under the `LGPL 3.0`_ license.
29 |
30 | Security and Liability
31 | ~~~~~~~~~~~~~~~~~~~~~~
32 |
33 | All contracts are **WITHOUT ANY WARRANTY**; *without even* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
34 |
35 | .. _LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.en.html
36 |
--------------------------------------------------------------------------------
/codechecks.yml:
--------------------------------------------------------------------------------
1 | checks:
2 | - name: eth-gas-reporter/codechecks
3 |
--------------------------------------------------------------------------------
/contracts/CTHelpers.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.1;
2 |
3 | import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
4 |
5 | library CTHelpers {
6 | /// @dev Constructs a condition ID from an oracle, a question ID, and the outcome slot count for the question.
7 | /// @param oracle The account assigned to report the result for the prepared condition.
8 | /// @param questionId An identifier for the question to be answered by the oracle.
9 | /// @param outcomeSlotCount The number of outcome slots which should be used for this condition. Must not exceed 256.
10 | function getConditionId(address oracle, bytes32 questionId, uint outcomeSlotCount) internal pure returns (bytes32) {
11 | return keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount));
12 | }
13 |
14 | uint constant P = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
15 | uint constant B = 3;
16 |
17 | function sqrt(uint x) private pure returns (uint y) {
18 | uint p = P;
19 | // solium-disable-next-line security/no-inline-assembly
20 | assembly {
21 | // add chain generated via https://crypto.stackexchange.com/q/27179/71252
22 | // and transformed to the following program:
23 |
24 | // x=1; y=x+x; z=y+y; z=z+z; y=y+z; x=x+y; y=y+x; z=y+y; t=z+z; t=z+t; t=t+t;
25 | // t=t+t; z=z+t; x=x+z; z=x+x; z=z+z; y=y+z; z=y+y; z=z+z; z=z+z; z=y+z; x=x+z;
26 | // z=x+x; z=z+z; z=z+z; z=x+z; y=y+z; x=x+y; z=x+x; z=z+z; y=y+z; z=y+y; t=z+z;
27 | // t=t+t; t=t+t; z=z+t; x=x+z; y=y+x; z=y+y; z=z+z; z=z+z; x=x+z; z=x+x; z=z+z;
28 | // z=x+z; z=z+z; z=z+z; z=x+z; y=y+z; z=y+y; t=z+z; t=t+t; t=z+t; t=y+t; t=t+t;
29 | // t=t+t; t=t+t; t=t+t; z=z+t; x=x+z; z=x+x; z=x+z; y=y+z; z=y+y; z=y+z; z=z+z;
30 | // t=z+z; t=z+t; w=t+t; w=w+w; w=w+w; w=w+w; w=w+w; t=t+w; z=z+t; x=x+z; y=y+x;
31 | // z=y+y; x=x+z; y=y+x; x=x+y; y=y+x; x=x+y; z=x+x; z=x+z; z=z+z; y=y+z; z=y+y;
32 | // z=z+z; x=x+z; y=y+x; z=y+y; z=y+z; x=x+z; y=y+x; x=x+y; y=y+x; z=y+y; z=z+z;
33 | // z=y+z; x=x+z; z=x+x; z=x+z; y=y+z; x=x+y; y=y+x; x=x+y; y=y+x; z=y+y; z=y+z;
34 | // z=z+z; x=x+z; y=y+x; z=y+y; z=y+z; z=z+z; x=x+z; z=x+x; t=z+z; t=t+t; t=z+t;
35 | // t=x+t; t=t+t; t=t+t; t=t+t; t=t+t; z=z+t; y=y+z; x=x+y; y=y+x; x=x+y; z=x+x;
36 | // z=x+z; z=z+z; z=z+z; z=z+z; z=x+z; y=y+z; z=y+y; z=y+z; z=z+z; x=x+z; z=x+x;
37 | // z=x+z; y=y+z; x=x+y; z=x+x; z=z+z; y=y+z; x=x+y; z=x+x; y=y+z; x=x+y; y=y+x;
38 | // z=y+y; z=y+z; x=x+z; y=y+x; z=y+y; z=y+z; z=z+z; z=z+z; x=x+z; z=x+x; z=z+z;
39 | // z=z+z; z=x+z; y=y+z; x=x+y; z=x+x; t=x+z; t=t+t; t=t+t; z=z+t; y=y+z; z=y+y;
40 | // x=x+z; y=y+x; x=x+y; y=y+x; x=x+y; y=y+x; z=y+y; t=y+z; z=y+t; z=z+z; z=z+z;
41 | // z=t+z; x=x+z; y=y+x; x=x+y; y=y+x; x=x+y; z=x+x; z=x+z; y=y+z; x=x+y; x=x+x;
42 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
43 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
44 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
45 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
46 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
47 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
48 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
49 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
50 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
51 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
52 | // x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x; x=x+x;
53 | // x=x+x; x=x+x; x=x+x; x=x+x; res=y+x
54 | // res == (P + 1) // 4
55 |
56 | y := mulmod(x, x, p)
57 | {
58 | let z := mulmod(y, y, p)
59 | z := mulmod(z, z, p)
60 | y := mulmod(y, z, p)
61 | x := mulmod(x, y, p)
62 | y := mulmod(y, x, p)
63 | z := mulmod(y, y, p)
64 | {
65 | let t := mulmod(z, z, p)
66 | t := mulmod(z, t, p)
67 | t := mulmod(t, t, p)
68 | t := mulmod(t, t, p)
69 | z := mulmod(z, t, p)
70 | x := mulmod(x, z, p)
71 | z := mulmod(x, x, p)
72 | z := mulmod(z, z, p)
73 | y := mulmod(y, z, p)
74 | z := mulmod(y, y, p)
75 | z := mulmod(z, z, p)
76 | z := mulmod(z, z, p)
77 | z := mulmod(y, z, p)
78 | x := mulmod(x, z, p)
79 | z := mulmod(x, x, p)
80 | z := mulmod(z, z, p)
81 | z := mulmod(z, z, p)
82 | z := mulmod(x, z, p)
83 | y := mulmod(y, z, p)
84 | x := mulmod(x, y, p)
85 | z := mulmod(x, x, p)
86 | z := mulmod(z, z, p)
87 | y := mulmod(y, z, p)
88 | z := mulmod(y, y, p)
89 | t := mulmod(z, z, p)
90 | t := mulmod(t, t, p)
91 | t := mulmod(t, t, p)
92 | z := mulmod(z, t, p)
93 | x := mulmod(x, z, p)
94 | y := mulmod(y, x, p)
95 | z := mulmod(y, y, p)
96 | z := mulmod(z, z, p)
97 | z := mulmod(z, z, p)
98 | x := mulmod(x, z, p)
99 | z := mulmod(x, x, p)
100 | z := mulmod(z, z, p)
101 | z := mulmod(x, z, p)
102 | z := mulmod(z, z, p)
103 | z := mulmod(z, z, p)
104 | z := mulmod(x, z, p)
105 | y := mulmod(y, z, p)
106 | z := mulmod(y, y, p)
107 | t := mulmod(z, z, p)
108 | t := mulmod(t, t, p)
109 | t := mulmod(z, t, p)
110 | t := mulmod(y, t, p)
111 | t := mulmod(t, t, p)
112 | t := mulmod(t, t, p)
113 | t := mulmod(t, t, p)
114 | t := mulmod(t, t, p)
115 | z := mulmod(z, t, p)
116 | x := mulmod(x, z, p)
117 | z := mulmod(x, x, p)
118 | z := mulmod(x, z, p)
119 | y := mulmod(y, z, p)
120 | z := mulmod(y, y, p)
121 | z := mulmod(y, z, p)
122 | z := mulmod(z, z, p)
123 | t := mulmod(z, z, p)
124 | t := mulmod(z, t, p)
125 | {
126 | let w := mulmod(t, t, p)
127 | w := mulmod(w, w, p)
128 | w := mulmod(w, w, p)
129 | w := mulmod(w, w, p)
130 | w := mulmod(w, w, p)
131 | t := mulmod(t, w, p)
132 | }
133 | z := mulmod(z, t, p)
134 | x := mulmod(x, z, p)
135 | y := mulmod(y, x, p)
136 | z := mulmod(y, y, p)
137 | x := mulmod(x, z, p)
138 | y := mulmod(y, x, p)
139 | x := mulmod(x, y, p)
140 | y := mulmod(y, x, p)
141 | x := mulmod(x, y, p)
142 | z := mulmod(x, x, p)
143 | z := mulmod(x, z, p)
144 | z := mulmod(z, z, p)
145 | y := mulmod(y, z, p)
146 | z := mulmod(y, y, p)
147 | z := mulmod(z, z, p)
148 | x := mulmod(x, z, p)
149 | y := mulmod(y, x, p)
150 | z := mulmod(y, y, p)
151 | z := mulmod(y, z, p)
152 | x := mulmod(x, z, p)
153 | y := mulmod(y, x, p)
154 | x := mulmod(x, y, p)
155 | y := mulmod(y, x, p)
156 | z := mulmod(y, y, p)
157 | z := mulmod(z, z, p)
158 | z := mulmod(y, z, p)
159 | x := mulmod(x, z, p)
160 | z := mulmod(x, x, p)
161 | z := mulmod(x, z, p)
162 | y := mulmod(y, z, p)
163 | x := mulmod(x, y, p)
164 | y := mulmod(y, x, p)
165 | x := mulmod(x, y, p)
166 | y := mulmod(y, x, p)
167 | z := mulmod(y, y, p)
168 | z := mulmod(y, z, p)
169 | z := mulmod(z, z, p)
170 | x := mulmod(x, z, p)
171 | y := mulmod(y, x, p)
172 | z := mulmod(y, y, p)
173 | z := mulmod(y, z, p)
174 | z := mulmod(z, z, p)
175 | x := mulmod(x, z, p)
176 | z := mulmod(x, x, p)
177 | t := mulmod(z, z, p)
178 | t := mulmod(t, t, p)
179 | t := mulmod(z, t, p)
180 | t := mulmod(x, t, p)
181 | t := mulmod(t, t, p)
182 | t := mulmod(t, t, p)
183 | t := mulmod(t, t, p)
184 | t := mulmod(t, t, p)
185 | z := mulmod(z, t, p)
186 | y := mulmod(y, z, p)
187 | x := mulmod(x, y, p)
188 | y := mulmod(y, x, p)
189 | x := mulmod(x, y, p)
190 | z := mulmod(x, x, p)
191 | z := mulmod(x, z, p)
192 | z := mulmod(z, z, p)
193 | z := mulmod(z, z, p)
194 | z := mulmod(z, z, p)
195 | z := mulmod(x, z, p)
196 | y := mulmod(y, z, p)
197 | z := mulmod(y, y, p)
198 | z := mulmod(y, z, p)
199 | z := mulmod(z, z, p)
200 | x := mulmod(x, z, p)
201 | z := mulmod(x, x, p)
202 | z := mulmod(x, z, p)
203 | y := mulmod(y, z, p)
204 | x := mulmod(x, y, p)
205 | z := mulmod(x, x, p)
206 | z := mulmod(z, z, p)
207 | y := mulmod(y, z, p)
208 | x := mulmod(x, y, p)
209 | z := mulmod(x, x, p)
210 | y := mulmod(y, z, p)
211 | x := mulmod(x, y, p)
212 | y := mulmod(y, x, p)
213 | z := mulmod(y, y, p)
214 | z := mulmod(y, z, p)
215 | x := mulmod(x, z, p)
216 | y := mulmod(y, x, p)
217 | z := mulmod(y, y, p)
218 | z := mulmod(y, z, p)
219 | z := mulmod(z, z, p)
220 | z := mulmod(z, z, p)
221 | x := mulmod(x, z, p)
222 | z := mulmod(x, x, p)
223 | z := mulmod(z, z, p)
224 | z := mulmod(z, z, p)
225 | z := mulmod(x, z, p)
226 | y := mulmod(y, z, p)
227 | x := mulmod(x, y, p)
228 | z := mulmod(x, x, p)
229 | t := mulmod(x, z, p)
230 | t := mulmod(t, t, p)
231 | t := mulmod(t, t, p)
232 | z := mulmod(z, t, p)
233 | y := mulmod(y, z, p)
234 | z := mulmod(y, y, p)
235 | x := mulmod(x, z, p)
236 | y := mulmod(y, x, p)
237 | x := mulmod(x, y, p)
238 | y := mulmod(y, x, p)
239 | x := mulmod(x, y, p)
240 | y := mulmod(y, x, p)
241 | z := mulmod(y, y, p)
242 | t := mulmod(y, z, p)
243 | z := mulmod(y, t, p)
244 | z := mulmod(z, z, p)
245 | z := mulmod(z, z, p)
246 | z := mulmod(t, z, p)
247 | }
248 | x := mulmod(x, z, p)
249 | y := mulmod(y, x, p)
250 | x := mulmod(x, y, p)
251 | y := mulmod(y, x, p)
252 | x := mulmod(x, y, p)
253 | z := mulmod(x, x, p)
254 | z := mulmod(x, z, p)
255 | y := mulmod(y, z, p)
256 | }
257 | x := mulmod(x, y, p)
258 | x := mulmod(x, x, p)
259 | x := mulmod(x, x, p)
260 | x := mulmod(x, x, p)
261 | x := mulmod(x, x, p)
262 | x := mulmod(x, x, p)
263 | x := mulmod(x, x, p)
264 | x := mulmod(x, x, p)
265 | x := mulmod(x, x, p)
266 | x := mulmod(x, x, p)
267 | x := mulmod(x, x, p)
268 | x := mulmod(x, x, p)
269 | x := mulmod(x, x, p)
270 | x := mulmod(x, x, p)
271 | x := mulmod(x, x, p)
272 | x := mulmod(x, x, p)
273 | x := mulmod(x, x, p)
274 | x := mulmod(x, x, p)
275 | x := mulmod(x, x, p)
276 | x := mulmod(x, x, p)
277 | x := mulmod(x, x, p)
278 | x := mulmod(x, x, p)
279 | x := mulmod(x, x, p)
280 | x := mulmod(x, x, p)
281 | x := mulmod(x, x, p)
282 | x := mulmod(x, x, p)
283 | x := mulmod(x, x, p)
284 | x := mulmod(x, x, p)
285 | x := mulmod(x, x, p)
286 | x := mulmod(x, x, p)
287 | x := mulmod(x, x, p)
288 | x := mulmod(x, x, p)
289 | x := mulmod(x, x, p)
290 | x := mulmod(x, x, p)
291 | x := mulmod(x, x, p)
292 | x := mulmod(x, x, p)
293 | x := mulmod(x, x, p)
294 | x := mulmod(x, x, p)
295 | x := mulmod(x, x, p)
296 | x := mulmod(x, x, p)
297 | x := mulmod(x, x, p)
298 | x := mulmod(x, x, p)
299 | x := mulmod(x, x, p)
300 | x := mulmod(x, x, p)
301 | x := mulmod(x, x, p)
302 | x := mulmod(x, x, p)
303 | x := mulmod(x, x, p)
304 | x := mulmod(x, x, p)
305 | x := mulmod(x, x, p)
306 | x := mulmod(x, x, p)
307 | x := mulmod(x, x, p)
308 | x := mulmod(x, x, p)
309 | x := mulmod(x, x, p)
310 | x := mulmod(x, x, p)
311 | x := mulmod(x, x, p)
312 | x := mulmod(x, x, p)
313 | x := mulmod(x, x, p)
314 | x := mulmod(x, x, p)
315 | x := mulmod(x, x, p)
316 | x := mulmod(x, x, p)
317 | x := mulmod(x, x, p)
318 | x := mulmod(x, x, p)
319 | x := mulmod(x, x, p)
320 | x := mulmod(x, x, p)
321 | x := mulmod(x, x, p)
322 | x := mulmod(x, x, p)
323 | x := mulmod(x, x, p)
324 | x := mulmod(x, x, p)
325 | x := mulmod(x, x, p)
326 | x := mulmod(x, x, p)
327 | x := mulmod(x, x, p)
328 | x := mulmod(x, x, p)
329 | x := mulmod(x, x, p)
330 | x := mulmod(x, x, p)
331 | x := mulmod(x, x, p)
332 | x := mulmod(x, x, p)
333 | x := mulmod(x, x, p)
334 | x := mulmod(x, x, p)
335 | x := mulmod(x, x, p)
336 | x := mulmod(x, x, p)
337 | x := mulmod(x, x, p)
338 | x := mulmod(x, x, p)
339 | x := mulmod(x, x, p)
340 | x := mulmod(x, x, p)
341 | x := mulmod(x, x, p)
342 | x := mulmod(x, x, p)
343 | x := mulmod(x, x, p)
344 | x := mulmod(x, x, p)
345 | x := mulmod(x, x, p)
346 | x := mulmod(x, x, p)
347 | x := mulmod(x, x, p)
348 | x := mulmod(x, x, p)
349 | x := mulmod(x, x, p)
350 | x := mulmod(x, x, p)
351 | x := mulmod(x, x, p)
352 | x := mulmod(x, x, p)
353 | x := mulmod(x, x, p)
354 | x := mulmod(x, x, p)
355 | x := mulmod(x, x, p)
356 | x := mulmod(x, x, p)
357 | x := mulmod(x, x, p)
358 | x := mulmod(x, x, p)
359 | x := mulmod(x, x, p)
360 | x := mulmod(x, x, p)
361 | x := mulmod(x, x, p)
362 | x := mulmod(x, x, p)
363 | x := mulmod(x, x, p)
364 | x := mulmod(x, x, p)
365 | x := mulmod(x, x, p)
366 | x := mulmod(x, x, p)
367 | x := mulmod(x, x, p)
368 | x := mulmod(x, x, p)
369 | x := mulmod(x, x, p)
370 | x := mulmod(x, x, p)
371 | x := mulmod(x, x, p)
372 | x := mulmod(x, x, p)
373 | x := mulmod(x, x, p)
374 | x := mulmod(x, x, p)
375 | x := mulmod(x, x, p)
376 | x := mulmod(x, x, p)
377 | x := mulmod(x, x, p)
378 | x := mulmod(x, x, p)
379 | x := mulmod(x, x, p)
380 | x := mulmod(x, x, p)
381 | x := mulmod(x, x, p)
382 | x := mulmod(x, x, p)
383 | x := mulmod(x, x, p)
384 | y := mulmod(y, x, p)
385 | }
386 | }
387 |
388 | /// @dev Constructs an outcome collection ID from a parent collection and an outcome collection.
389 | /// @param parentCollectionId Collection ID of the parent outcome collection, or bytes32(0) if there's no parent.
390 | /// @param conditionId Condition ID of the outcome collection to combine with the parent outcome collection.
391 | /// @param indexSet Index set of the outcome collection to combine with the parent outcome collection.
392 | function getCollectionId(bytes32 parentCollectionId, bytes32 conditionId, uint indexSet) internal view returns (bytes32) {
393 | uint x1 = uint(keccak256(abi.encodePacked(conditionId, indexSet)));
394 | bool odd = x1 >> 255 != 0;
395 | uint y1;
396 | uint yy;
397 | do {
398 | x1 = addmod(x1, 1, P);
399 | yy = addmod(mulmod(x1, mulmod(x1, x1, P), P), B, P);
400 | y1 = sqrt(yy);
401 | } while(mulmod(y1, y1, P) != yy);
402 | if(odd && y1 % 2 == 0 || !odd && y1 % 2 == 1)
403 | y1 = P - y1;
404 |
405 | uint x2 = uint(parentCollectionId);
406 | if(x2 != 0) {
407 | odd = x2 >> 254 != 0;
408 | x2 = (x2 << 2) >> 2;
409 | yy = addmod(mulmod(x2, mulmod(x2, x2, P), P), B, P);
410 | uint y2 = sqrt(yy);
411 | if(odd && y2 % 2 == 0 || !odd && y2 % 2 == 1)
412 | y2 = P - y2;
413 | require(mulmod(y2, y2, P) == yy, "invalid parent collection ID");
414 |
415 | (bool success, bytes memory ret) = address(6).staticcall(abi.encode(x1, y1, x2, y2));
416 | require(success, "ecadd failed");
417 | (x1, y1) = abi.decode(ret, (uint, uint));
418 | }
419 |
420 | if(y1 % 2 == 1)
421 | x1 ^= 1 << 254;
422 |
423 | return bytes32(x1);
424 | }
425 |
426 | /// @dev Constructs a position ID from a collateral token and an outcome collection. These IDs are used as the ERC-1155 ID for this contract.
427 | /// @param collateralToken Collateral token which backs the position.
428 | /// @param collectionId ID of the outcome collection associated with this position.
429 | function getPositionId(IERC20 collateralToken, bytes32 collectionId) internal pure returns (uint) {
430 | return uint(keccak256(abi.encodePacked(collateralToken, collectionId)));
431 | }
432 | }
--------------------------------------------------------------------------------
/contracts/ConditionalTokens.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.1;
2 | import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
3 | import { ERC1155 } from "./ERC1155/ERC1155.sol";
4 | import { CTHelpers } from "./CTHelpers.sol";
5 |
6 | contract ConditionalTokens is ERC1155 {
7 |
8 | /// @dev Emitted upon the successful preparation of a condition.
9 | /// @param conditionId The condition's ID. This ID may be derived from the other three parameters via ``keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount))``.
10 | /// @param oracle The account assigned to report the result for the prepared condition.
11 | /// @param questionId An identifier for the question to be answered by the oracle.
12 | /// @param outcomeSlotCount The number of outcome slots which should be used for this condition. Must not exceed 256.
13 | event ConditionPreparation(
14 | bytes32 indexed conditionId,
15 | address indexed oracle,
16 | bytes32 indexed questionId,
17 | uint outcomeSlotCount
18 | );
19 |
20 | event ConditionResolution(
21 | bytes32 indexed conditionId,
22 | address indexed oracle,
23 | bytes32 indexed questionId,
24 | uint outcomeSlotCount,
25 | uint[] payoutNumerators
26 | );
27 |
28 | /// @dev Emitted when a position is successfully split.
29 | event PositionSplit(
30 | address indexed stakeholder,
31 | IERC20 collateralToken,
32 | bytes32 indexed parentCollectionId,
33 | bytes32 indexed conditionId,
34 | uint[] partition,
35 | uint amount
36 | );
37 | /// @dev Emitted when positions are successfully merged.
38 | event PositionsMerge(
39 | address indexed stakeholder,
40 | IERC20 collateralToken,
41 | bytes32 indexed parentCollectionId,
42 | bytes32 indexed conditionId,
43 | uint[] partition,
44 | uint amount
45 | );
46 | event PayoutRedemption(
47 | address indexed redeemer,
48 | IERC20 indexed collateralToken,
49 | bytes32 indexed parentCollectionId,
50 | bytes32 conditionId,
51 | uint[] indexSets,
52 | uint payout
53 | );
54 |
55 |
56 | /// Mapping key is an condition ID. Value represents numerators of the payout vector associated with the condition. This array is initialized with a length equal to the outcome slot count. E.g. Condition with 3 outcomes [A, B, C] and two of those correct [0.5, 0.5, 0]. In Ethereum there are no decimal values, so here, 0.5 is represented by fractions like 1/2 == 0.5. That's why we need numerator and denominator values. Payout numerators are also used as a check of initialization. If the numerators array is empty (has length zero), the condition was not created/prepared. See getOutcomeSlotCount.
57 | mapping(bytes32 => uint[]) public payoutNumerators;
58 | /// Denominator is also used for checking if the condition has been resolved. If the denominator is non-zero, then the condition has been resolved.
59 | mapping(bytes32 => uint) public payoutDenominator;
60 |
61 | /// @dev This function prepares a condition by initializing a payout vector associated with the condition.
62 | /// @param oracle The account assigned to report the result for the prepared condition.
63 | /// @param questionId An identifier for the question to be answered by the oracle.
64 | /// @param outcomeSlotCount The number of outcome slots which should be used for this condition. Must not exceed 256.
65 | function prepareCondition(address oracle, bytes32 questionId, uint outcomeSlotCount) external {
66 | // Limit of 256 because we use a partition array that is a number of 256 bits.
67 | require(outcomeSlotCount <= 256, "too many outcome slots");
68 | require(outcomeSlotCount > 1, "there should be more than one outcome slot");
69 | bytes32 conditionId = CTHelpers.getConditionId(oracle, questionId, outcomeSlotCount);
70 | require(payoutNumerators[conditionId].length == 0, "condition already prepared");
71 | payoutNumerators[conditionId] = new uint[](outcomeSlotCount);
72 | emit ConditionPreparation(conditionId, oracle, questionId, outcomeSlotCount);
73 | }
74 |
75 | /// @dev Called by the oracle for reporting results of conditions. Will set the payout vector for the condition with the ID ``keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount))``, where oracle is the message sender, questionId is one of the parameters of this function, and outcomeSlotCount is the length of the payouts parameter, which contains the payoutNumerators for each outcome slot of the condition.
76 | /// @param questionId The question ID the oracle is answering for
77 | /// @param payouts The oracle's answer
78 | function reportPayouts(bytes32 questionId, uint[] calldata payouts) external {
79 | uint outcomeSlotCount = payouts.length;
80 | require(outcomeSlotCount > 1, "there should be more than one outcome slot");
81 | // IMPORTANT, the oracle is enforced to be the sender because it's part of the hash.
82 | bytes32 conditionId = CTHelpers.getConditionId(msg.sender, questionId, outcomeSlotCount);
83 | require(payoutNumerators[conditionId].length == outcomeSlotCount, "condition not prepared or found");
84 | require(payoutDenominator[conditionId] == 0, "payout denominator already set");
85 |
86 | uint den = 0;
87 | for (uint i = 0; i < outcomeSlotCount; i++) {
88 | uint num = payouts[i];
89 | den = den.add(num);
90 |
91 | require(payoutNumerators[conditionId][i] == 0, "payout numerator already set");
92 | payoutNumerators[conditionId][i] = num;
93 | }
94 | require(den > 0, "payout is all zeroes");
95 | payoutDenominator[conditionId] = den;
96 | emit ConditionResolution(conditionId, msg.sender, questionId, outcomeSlotCount, payoutNumerators[conditionId]);
97 | }
98 |
99 | /// @dev This function splits a position. If splitting from the collateral, this contract will attempt to transfer `amount` collateral from the message sender to itself. Otherwise, this contract will burn `amount` stake held by the message sender in the position being split worth of EIP 1155 tokens. Regardless, if successful, `amount` stake will be minted in the split target positions. If any of the transfers, mints, or burns fail, the transaction will revert. The transaction will also revert if the given partition is trivial, invalid, or refers to more slots than the condition is prepared with.
100 | /// @param collateralToken The address of the positions' backing collateral token.
101 | /// @param parentCollectionId The ID of the outcome collections common to the position being split and the split target positions. May be null, in which only the collateral is shared.
102 | /// @param conditionId The ID of the condition to split on.
103 | /// @param partition An array of disjoint index sets representing a nontrivial partition of the outcome slots of the given condition. E.g. A|B and C but not A|B and B|C (is not disjoint). Each element's a number which, together with the condition, represents the outcome collection. E.g. 0b110 is A|B, 0b010 is B, etc.
104 | /// @param amount The amount of collateral or stake to split.
105 | function splitPosition(
106 | IERC20 collateralToken,
107 | bytes32 parentCollectionId,
108 | bytes32 conditionId,
109 | uint[] calldata partition,
110 | uint amount
111 | ) external {
112 | require(partition.length > 1, "got empty or singleton partition");
113 | uint outcomeSlotCount = payoutNumerators[conditionId].length;
114 | require(outcomeSlotCount > 0, "condition not prepared yet");
115 |
116 | // For a condition with 4 outcomes fullIndexSet's 0b1111; for 5 it's 0b11111...
117 | uint fullIndexSet = (1 << outcomeSlotCount) - 1;
118 | // freeIndexSet starts as the full collection
119 | uint freeIndexSet = fullIndexSet;
120 | // This loop checks that all condition sets are disjoint (the same outcome is not part of more than 1 set)
121 | uint[] memory positionIds = new uint[](partition.length);
122 | uint[] memory amounts = new uint[](partition.length);
123 | for (uint i = 0; i < partition.length; i++) {
124 | uint indexSet = partition[i];
125 | require(indexSet > 0 && indexSet < fullIndexSet, "got invalid index set");
126 | require((indexSet & freeIndexSet) == indexSet, "partition not disjoint");
127 | freeIndexSet ^= indexSet;
128 | positionIds[i] = CTHelpers.getPositionId(collateralToken, CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet));
129 | amounts[i] = amount;
130 | }
131 |
132 | if (freeIndexSet == 0) {
133 | // Partitioning the full set of outcomes for the condition in this branch
134 | if (parentCollectionId == bytes32(0)) {
135 | require(collateralToken.transferFrom(msg.sender, address(this), amount), "could not receive collateral tokens");
136 | } else {
137 | _burn(
138 | msg.sender,
139 | CTHelpers.getPositionId(collateralToken, parentCollectionId),
140 | amount
141 | );
142 | }
143 | } else {
144 | // Partitioning a subset of outcomes for the condition in this branch.
145 | // For example, for a condition with three outcomes A, B, and C, this branch
146 | // allows the splitting of a position $:(A|C) to positions $:(A) and $:(C).
147 | _burn(
148 | msg.sender,
149 | CTHelpers.getPositionId(collateralToken,
150 | CTHelpers.getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)),
151 | amount
152 | );
153 | }
154 |
155 | _batchMint(
156 | msg.sender,
157 | // position ID is the ERC 1155 token ID
158 | positionIds,
159 | amounts,
160 | ""
161 | );
162 | emit PositionSplit(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount);
163 | }
164 |
165 | function mergePositions(
166 | IERC20 collateralToken,
167 | bytes32 parentCollectionId,
168 | bytes32 conditionId,
169 | uint[] calldata partition,
170 | uint amount
171 | ) external {
172 | require(partition.length > 1, "got empty or singleton partition");
173 | uint outcomeSlotCount = payoutNumerators[conditionId].length;
174 | require(outcomeSlotCount > 0, "condition not prepared yet");
175 |
176 | uint fullIndexSet = (1 << outcomeSlotCount) - 1;
177 | uint freeIndexSet = fullIndexSet;
178 | uint[] memory positionIds = new uint[](partition.length);
179 | uint[] memory amounts = new uint[](partition.length);
180 | for (uint i = 0; i < partition.length; i++) {
181 | uint indexSet = partition[i];
182 | require(indexSet > 0 && indexSet < fullIndexSet, "got invalid index set");
183 | require((indexSet & freeIndexSet) == indexSet, "partition not disjoint");
184 | freeIndexSet ^= indexSet;
185 | positionIds[i] = CTHelpers.getPositionId(collateralToken, CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet));
186 | amounts[i] = amount;
187 | }
188 | _batchBurn(
189 | msg.sender,
190 | positionIds,
191 | amounts
192 | );
193 |
194 | if (freeIndexSet == 0) {
195 | if (parentCollectionId == bytes32(0)) {
196 | require(collateralToken.transfer(msg.sender, amount), "could not send collateral tokens");
197 | } else {
198 | _mint(
199 | msg.sender,
200 | CTHelpers.getPositionId(collateralToken, parentCollectionId),
201 | amount,
202 | ""
203 | );
204 | }
205 | } else {
206 | _mint(
207 | msg.sender,
208 | CTHelpers.getPositionId(collateralToken,
209 | CTHelpers.getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)),
210 | amount,
211 | ""
212 | );
213 | }
214 |
215 | emit PositionsMerge(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount);
216 | }
217 |
218 | function redeemPositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] calldata indexSets) external {
219 | uint den = payoutDenominator[conditionId];
220 | require(den > 0, "result for condition not received yet");
221 | uint outcomeSlotCount = payoutNumerators[conditionId].length;
222 | require(outcomeSlotCount > 0, "condition not prepared yet");
223 |
224 | uint totalPayout = 0;
225 |
226 | uint fullIndexSet = (1 << outcomeSlotCount) - 1;
227 | for (uint i = 0; i < indexSets.length; i++) {
228 | uint indexSet = indexSets[i];
229 | require(indexSet > 0 && indexSet < fullIndexSet, "got invalid index set");
230 | uint positionId = CTHelpers.getPositionId(collateralToken,
231 | CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet));
232 |
233 | uint payoutNumerator = 0;
234 | for (uint j = 0; j < outcomeSlotCount; j++) {
235 | if (indexSet & (1 << j) != 0) {
236 | payoutNumerator = payoutNumerator.add(payoutNumerators[conditionId][j]);
237 | }
238 | }
239 |
240 | uint payoutStake = balanceOf(msg.sender, positionId);
241 | if (payoutStake > 0) {
242 | totalPayout = totalPayout.add(payoutStake.mul(payoutNumerator).div(den));
243 | _burn(msg.sender, positionId, payoutStake);
244 | }
245 | }
246 |
247 | if (totalPayout > 0) {
248 | if (parentCollectionId == bytes32(0)) {
249 | require(collateralToken.transfer(msg.sender, totalPayout), "could not transfer payout to message sender");
250 | } else {
251 | _mint(msg.sender, CTHelpers.getPositionId(collateralToken, parentCollectionId), totalPayout, "");
252 | }
253 | }
254 | emit PayoutRedemption(msg.sender, collateralToken, parentCollectionId, conditionId, indexSets, totalPayout);
255 | }
256 |
257 | /// @dev Gets the outcome slot count of a condition.
258 | /// @param conditionId ID of the condition.
259 | /// @return Number of outcome slots associated with a condition, or zero if condition has not been prepared yet.
260 | function getOutcomeSlotCount(bytes32 conditionId) external view returns (uint) {
261 | return payoutNumerators[conditionId].length;
262 | }
263 |
264 | /// @dev Constructs a condition ID from an oracle, a question ID, and the outcome slot count for the question.
265 | /// @param oracle The account assigned to report the result for the prepared condition.
266 | /// @param questionId An identifier for the question to be answered by the oracle.
267 | /// @param outcomeSlotCount The number of outcome slots which should be used for this condition. Must not exceed 256.
268 | function getConditionId(address oracle, bytes32 questionId, uint outcomeSlotCount) external pure returns (bytes32) {
269 | return CTHelpers.getConditionId(oracle, questionId, outcomeSlotCount);
270 | }
271 |
272 | /// @dev Constructs an outcome collection ID from a parent collection and an outcome collection.
273 | /// @param parentCollectionId Collection ID of the parent outcome collection, or bytes32(0) if there's no parent.
274 | /// @param conditionId Condition ID of the outcome collection to combine with the parent outcome collection.
275 | /// @param indexSet Index set of the outcome collection to combine with the parent outcome collection.
276 | function getCollectionId(bytes32 parentCollectionId, bytes32 conditionId, uint indexSet) external view returns (bytes32) {
277 | return CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet);
278 | }
279 |
280 | /// @dev Constructs a position ID from a collateral token and an outcome collection. These IDs are used as the ERC-1155 ID for this contract.
281 | /// @param collateralToken Collateral token which backs the position.
282 | /// @param collectionId ID of the outcome collection associated with this position.
283 | function getPositionId(IERC20 collateralToken, bytes32 collectionId) external pure returns (uint) {
284 | return CTHelpers.getPositionId(collateralToken, collectionId);
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/contracts/ERC1155/ERC1155.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import "./IERC1155.sol";
4 | import "./IERC1155TokenReceiver.sol";
5 | import "openzeppelin-solidity/contracts/math/SafeMath.sol";
6 | import "openzeppelin-solidity/contracts/utils/Address.sol";
7 | import "openzeppelin-solidity/contracts/introspection/ERC165.sol";
8 |
9 | /**
10 | * @title Standard ERC1155 token
11 | *
12 | * @dev Implementation of the basic standard multi-token.
13 | * See https://eips.ethereum.org/EIPS/eip-1155
14 | * Originally based on code by Enjin: https://github.com/enjin/erc-1155
15 | */
16 | contract ERC1155 is ERC165, IERC1155
17 | {
18 | using SafeMath for uint256;
19 | using Address for address;
20 |
21 | // Mapping from token ID to owner balances
22 | mapping (uint256 => mapping(address => uint256)) private _balances;
23 |
24 | // Mapping from owner to operator approvals
25 | mapping (address => mapping(address => bool)) private _operatorApprovals;
26 |
27 | constructor()
28 | public
29 | {
30 | _registerInterface(
31 | ERC1155(0).safeTransferFrom.selector ^
32 | ERC1155(0).safeBatchTransferFrom.selector ^
33 | ERC1155(0).balanceOf.selector ^
34 | ERC1155(0).balanceOfBatch.selector ^
35 | ERC1155(0).setApprovalForAll.selector ^
36 | ERC1155(0).isApprovedForAll.selector
37 | );
38 | }
39 |
40 | /**
41 | @dev Get the specified address' balance for token with specified ID.
42 | @param owner The address of the token holder
43 | @param id ID of the token
44 | @return The owner's balance of the token type requested
45 | */
46 | function balanceOf(address owner, uint256 id) public view returns (uint256) {
47 | require(owner != address(0), "ERC1155: balance query for the zero address");
48 | return _balances[id][owner];
49 | }
50 |
51 | /**
52 | @dev Get the balance of multiple account/token pairs
53 | @param owners The addresses of the token holders
54 | @param ids IDs of the tokens
55 | @return Balances for each owner and token id pair
56 | */
57 | function balanceOfBatch(
58 | address[] memory owners,
59 | uint256[] memory ids
60 | )
61 | public
62 | view
63 | returns (uint256[] memory)
64 | {
65 | require(owners.length == ids.length, "ERC1155: owners and IDs must have same lengths");
66 |
67 | uint256[] memory batchBalances = new uint256[](owners.length);
68 |
69 | for (uint256 i = 0; i < owners.length; ++i) {
70 | require(owners[i] != address(0), "ERC1155: some address in batch balance query is zero");
71 | batchBalances[i] = _balances[ids[i]][owners[i]];
72 | }
73 |
74 | return batchBalances;
75 | }
76 |
77 | /**
78 | * @dev Sets or unsets the approval of a given operator
79 | * An operator is allowed to transfer all tokens of the sender on their behalf
80 | * @param operator address to set the approval
81 | * @param approved representing the status of the approval to be set
82 | */
83 | function setApprovalForAll(address operator, bool approved) external {
84 | _operatorApprovals[msg.sender][operator] = approved;
85 | emit ApprovalForAll(msg.sender, operator, approved);
86 | }
87 |
88 | /**
89 | @notice Queries the approval status of an operator for a given owner.
90 | @param owner The owner of the Tokens
91 | @param operator Address of authorized operator
92 | @return True if the operator is approved, false if not
93 | */
94 | function isApprovedForAll(address owner, address operator) external view returns (bool) {
95 | return _operatorApprovals[owner][operator];
96 | }
97 |
98 | /**
99 | @dev Transfers `value` amount of an `id` from the `from` address to the `to` address specified.
100 | Caller must be approved to manage the tokens being transferred out of the `from` account.
101 | If `to` is a smart contract, will call `onERC1155Received` on `to` and act appropriately.
102 | @param from Source address
103 | @param to Target address
104 | @param id ID of the token type
105 | @param value Transfer amount
106 | @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver
107 | */
108 | function safeTransferFrom(
109 | address from,
110 | address to,
111 | uint256 id,
112 | uint256 value,
113 | bytes calldata data
114 | )
115 | external
116 | {
117 | require(to != address(0), "ERC1155: target address must be non-zero");
118 | require(
119 | from == msg.sender || _operatorApprovals[from][msg.sender] == true,
120 | "ERC1155: need operator approval for 3rd party transfers."
121 | );
122 |
123 | _balances[id][from] = _balances[id][from].sub(value);
124 | _balances[id][to] = value.add(_balances[id][to]);
125 |
126 | emit TransferSingle(msg.sender, from, to, id, value);
127 |
128 | _doSafeTransferAcceptanceCheck(msg.sender, from, to, id, value, data);
129 | }
130 |
131 | /**
132 | @dev Transfers `values` amount(s) of `ids` from the `from` address to the
133 | `to` address specified. Caller must be approved to manage the tokens being
134 | transferred out of the `from` account. If `to` is a smart contract, will
135 | call `onERC1155BatchReceived` on `to` and act appropriately.
136 | @param from Source address
137 | @param to Target address
138 | @param ids IDs of each token type
139 | @param values Transfer amounts per token type
140 | @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver
141 | */
142 | function safeBatchTransferFrom(
143 | address from,
144 | address to,
145 | uint256[] calldata ids,
146 | uint256[] calldata values,
147 | bytes calldata data
148 | )
149 | external
150 | {
151 | require(ids.length == values.length, "ERC1155: IDs and values must have same lengths");
152 | require(to != address(0), "ERC1155: target address must be non-zero");
153 | require(
154 | from == msg.sender || _operatorApprovals[from][msg.sender] == true,
155 | "ERC1155: need operator approval for 3rd party transfers."
156 | );
157 |
158 | for (uint256 i = 0; i < ids.length; ++i) {
159 | uint256 id = ids[i];
160 | uint256 value = values[i];
161 |
162 | _balances[id][from] = _balances[id][from].sub(value);
163 | _balances[id][to] = value.add(_balances[id][to]);
164 | }
165 |
166 | emit TransferBatch(msg.sender, from, to, ids, values);
167 |
168 | _doSafeBatchTransferAcceptanceCheck(msg.sender, from, to, ids, values, data);
169 | }
170 |
171 | /**
172 | * @dev Internal function to mint an amount of a token with the given ID
173 | * @param to The address that will own the minted token
174 | * @param id ID of the token to be minted
175 | * @param value Amount of the token to be minted
176 | * @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver
177 | */
178 | function _mint(address to, uint256 id, uint256 value, bytes memory data) internal {
179 | require(to != address(0), "ERC1155: mint to the zero address");
180 |
181 | _balances[id][to] = value.add(_balances[id][to]);
182 | emit TransferSingle(msg.sender, address(0), to, id, value);
183 |
184 | _doSafeTransferAcceptanceCheck(msg.sender, address(0), to, id, value, data);
185 | }
186 |
187 | /**
188 | * @dev Internal function to batch mint amounts of tokens with the given IDs
189 | * @param to The address that will own the minted token
190 | * @param ids IDs of the tokens to be minted
191 | * @param values Amounts of the tokens to be minted
192 | * @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver
193 | */
194 | function _batchMint(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal {
195 | require(to != address(0), "ERC1155: batch mint to the zero address");
196 | require(ids.length == values.length, "ERC1155: IDs and values must have same lengths");
197 |
198 | for(uint i = 0; i < ids.length; i++) {
199 | _balances[ids[i]][to] = values[i].add(_balances[ids[i]][to]);
200 | }
201 |
202 | emit TransferBatch(msg.sender, address(0), to, ids, values);
203 |
204 | _doSafeBatchTransferAcceptanceCheck(msg.sender, address(0), to, ids, values, data);
205 | }
206 |
207 | /**
208 | * @dev Internal function to burn an amount of a token with the given ID
209 | * @param owner Account which owns the token to be burnt
210 | * @param id ID of the token to be burnt
211 | * @param value Amount of the token to be burnt
212 | */
213 | function _burn(address owner, uint256 id, uint256 value) internal {
214 | _balances[id][owner] = _balances[id][owner].sub(value);
215 | emit TransferSingle(msg.sender, owner, address(0), id, value);
216 | }
217 |
218 | /**
219 | * @dev Internal function to batch burn an amounts of tokens with the given IDs
220 | * @param owner Account which owns the token to be burnt
221 | * @param ids IDs of the tokens to be burnt
222 | * @param values Amounts of the tokens to be burnt
223 | */
224 | function _batchBurn(address owner, uint256[] memory ids, uint256[] memory values) internal {
225 | require(ids.length == values.length, "ERC1155: IDs and values must have same lengths");
226 |
227 | for(uint i = 0; i < ids.length; i++) {
228 | _balances[ids[i]][owner] = _balances[ids[i]][owner].sub(values[i]);
229 | }
230 |
231 | emit TransferBatch(msg.sender, owner, address(0), ids, values);
232 | }
233 |
234 | function _doSafeTransferAcceptanceCheck(
235 | address operator,
236 | address from,
237 | address to,
238 | uint256 id,
239 | uint256 value,
240 | bytes memory data
241 | )
242 | internal
243 | {
244 | if(to.isContract()) {
245 | require(
246 | IERC1155TokenReceiver(to).onERC1155Received(operator, from, id, value, data) ==
247 | IERC1155TokenReceiver(to).onERC1155Received.selector,
248 | "ERC1155: got unknown value from onERC1155Received"
249 | );
250 | }
251 | }
252 |
253 | function _doSafeBatchTransferAcceptanceCheck(
254 | address operator,
255 | address from,
256 | address to,
257 | uint256[] memory ids,
258 | uint256[] memory values,
259 | bytes memory data
260 | )
261 | internal
262 | {
263 | if(to.isContract()) {
264 | require(
265 | IERC1155TokenReceiver(to).onERC1155BatchReceived(operator, from, ids, values, data) == IERC1155TokenReceiver(to).onERC1155BatchReceived.selector,
266 | "ERC1155: got unknown value from onERC1155BatchReceived"
267 | );
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/contracts/ERC1155/ERC1155TokenReceiver.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import "./IERC1155TokenReceiver.sol";
4 | import "openzeppelin-solidity/contracts/introspection/ERC165.sol";
5 |
6 | contract ERC1155TokenReceiver is ERC165, IERC1155TokenReceiver {
7 | constructor() public {
8 | _registerInterface(
9 | ERC1155TokenReceiver(0).onERC1155Received.selector ^
10 | ERC1155TokenReceiver(0).onERC1155BatchReceived.selector
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/contracts/ERC1155/IERC1155.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import "openzeppelin-solidity/contracts/introspection/IERC165.sol";
4 |
5 | /**
6 | @title ERC-1155 Multi Token Standard basic interface
7 | @dev See https://eips.ethereum.org/EIPS/eip-1155
8 | */
9 | contract IERC1155 is IERC165 {
10 | event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
11 |
12 | event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
13 |
14 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
15 |
16 | event URI(string value, uint256 indexed id);
17 |
18 | function balanceOf(address owner, uint256 id) public view returns (uint256);
19 |
20 | function balanceOfBatch(address[] memory owners, uint256[] memory ids) public view returns (uint256[] memory);
21 |
22 | function setApprovalForAll(address operator, bool approved) external;
23 |
24 | function isApprovedForAll(address owner, address operator) external view returns (bool);
25 |
26 | function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
27 |
28 | function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external;
29 | }
30 |
--------------------------------------------------------------------------------
/contracts/ERC1155/IERC1155TokenReceiver.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import "openzeppelin-solidity/contracts/introspection/IERC165.sol";
4 |
5 | /**
6 | @title ERC-1155 Multi Token Receiver Interface
7 | @dev See https://eips.ethereum.org/EIPS/eip-1155
8 | */
9 | contract IERC1155TokenReceiver is IERC165 {
10 |
11 | /**
12 | @dev Handles the receipt of a single ERC1155 token type. This function is
13 | called at the end of a `safeTransferFrom` after the balance has been updated.
14 | To accept the transfer, this must return
15 | `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
16 | (i.e. 0xf23a6e61, or its own function selector).
17 | @param operator The address which initiated the transfer (i.e. msg.sender)
18 | @param from The address which previously owned the token
19 | @param id The ID of the token being transferred
20 | @param value The amount of tokens being transferred
21 | @param data Additional data with no specified format
22 | @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
23 | */
24 | function onERC1155Received(
25 | address operator,
26 | address from,
27 | uint256 id,
28 | uint256 value,
29 | bytes calldata data
30 | )
31 | external
32 | returns(bytes4);
33 |
34 | /**
35 | @dev Handles the receipt of a multiple ERC1155 token types. This function
36 | is called at the end of a `safeBatchTransferFrom` after the balances have
37 | been updated. To accept the transfer(s), this must return
38 | `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
39 | (i.e. 0xbc197c81, or its own function selector).
40 | @param operator The address which initiated the batch transfer (i.e. msg.sender)
41 | @param from The address which previously owned the token
42 | @param ids An array containing ids of each token being transferred (order and length must match values array)
43 | @param values An array containing amounts of each token being transferred (order and length must match ids array)
44 | @param data Additional data with no specified format
45 | @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
46 | */
47 | function onERC1155BatchReceived(
48 | address operator,
49 | address from,
50 | uint256[] calldata ids,
51 | uint256[] calldata values,
52 | bytes calldata data
53 | )
54 | external
55 | returns(bytes4);
56 | }
57 |
--------------------------------------------------------------------------------
/contracts/ERC1155/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | sections:
3 | - title: Core
4 | contracts:
5 | - IERC1155
6 | - ERC1155
7 | - IERC1155TokenReceiver
8 | ---
9 |
10 | This set of interfaces and contracts are all related to the [ERC1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155).
11 |
12 | The EIP consists of two interfaces which fulfill different roles, found here as `IERC1155` and `IERC1155TokenReceiver`. Only `IERC1155` is required for a contract to be ERC1155 compliant. The basic functionality is implemented in `ERC1155`.
13 |
--------------------------------------------------------------------------------
/contracts/Migrations.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.1;
2 |
3 |
4 | contract Migrations {
5 | address public owner;
6 | uint public lastCompletedMigration;
7 |
8 | modifier restricted() {
9 | if (msg.sender == owner) _;
10 | }
11 |
12 | constructor() public {
13 | owner = msg.sender;
14 | }
15 |
16 | function setCompleted(uint completed) public restricted {
17 | lastCompletedMigration = completed;
18 | }
19 |
20 | function upgrade(address newAddress) public restricted {
21 | Migrations upgraded = Migrations(newAddress);
22 | upgraded.setCompleted(lastCompletedMigration);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SPHINXAUTOB = sphinx-autobuild
8 | SPHINXPROJ = ConditionalTokens
9 | SOURCEDIR = .
10 | BUILDDIR = _build
11 |
12 | # Put it first so that "make" without argument is like "make help".
13 | help:
14 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
15 |
16 | watch:
17 | @$(SPHINXAUTOB) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O)
18 |
19 | .PHONY: help Makefile
20 |
21 | # Catch-all target: route all unknown targets to Sphinx using the new
22 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
23 | %: Makefile
24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/docs/_static/all-positions-from-two-conditions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/all-positions-from-two-conditions.png
--------------------------------------------------------------------------------
/docs/_static/merge-positions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/merge-positions.png
--------------------------------------------------------------------------------
/docs/_static/redemption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/redemption.png
--------------------------------------------------------------------------------
/docs/_static/v1-cond-market-abc-hilo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/v1-cond-market-abc-hilo.png
--------------------------------------------------------------------------------
/docs/_static/v1-cond-market-hilo-abc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/v1-cond-market-hilo-abc.png
--------------------------------------------------------------------------------
/docs/_static/v1-cond-market-ot-compare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/v1-cond-market-ot-compare.png
--------------------------------------------------------------------------------
/docs/_static/v2-cond-market-ot-compare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/v2-cond-market-ot-compare.png
--------------------------------------------------------------------------------
/docs/_static/v2-cond-market-slots-only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/v2-cond-market-slots-only.png
--------------------------------------------------------------------------------
/docs/_static/valid-vs-invalid-splits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/_static/valid-vs-invalid-splits.png
--------------------------------------------------------------------------------
/docs/audit/2020-01-20_accumulator_audit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/audit/2020-01-20_accumulator_audit.pdf
--------------------------------------------------------------------------------
/docs/audit/AuditReport-ConditionalTokens.md:
--------------------------------------------------------------------------------
1 | # Audit report
2 | ## Gnosis Conditional Tokens
3 |
4 | ## Files
5 |
6 | All solidity files in the following repository:
7 |
8 | https://github.com/gnosis/conditional-tokens-contracts/tree/a050b6c16aba8e3bfd6697e9a68bd23aeba307b4
9 |
10 | ## Issues
11 |
12 | ## 1. By splitting non-existent collections it's possible to forge other collections and ultimately steal all collateral tokens from the contract
13 |
14 | ### type: security / severity: critical
15 |
16 | It's possible to split non-existent position tokens so that some of the resulting tokens will share the same `collectionId` as a different position, this is possible for three reasons:
17 |
18 | ### a)
19 |
20 | When splitting tokens, these tokens are destroyed only after the tokens resulting from the split have been created
21 |
22 | ### b)
23 | When new tokens are created and transferred to the recipient, `onERC1155Received` function on the recipient's address is called allowing re-entracny of the `ConditionalTokens` contract
24 |
25 | ### c)
26 |
27 | Complex `collectionId`s are derived in a predictable way from the `collectionId`s of included positions. `collectionId` of a complex position is a simple sum of all contained positions. This allows attacker to craft a position that upon splitting will result in tokens that have a `collectionId` that collides with a different position.
28 | For example collection with `collectionId`: `bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01))) - uint(keccak256(abi.encodePacked(conditionId, 0b10))))` when split will result in collections with ids:
29 |
30 | `bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01)))` (if `0b01` is the winning outcome, this position can be directly redeemed as collateral)
31 | and
32 | `bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01))) + uint(keccak256(abi.encodePacked(conditionId, 0b01))) - uint(keccak256(abi.encodePacked(conditionId, 0b10))))` which if `0b01` is the winning outcome can be be redeemed back into `bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01))) - uint(keccak256(abi.encodePacked(conditionId, 0b10))))` in full making sure the original split terminates correctly.
33 |
34 | ### Replication
35 |
36 | We assume that `0b01` is the winning position of condition with id `conditionId`
37 |
38 | ```
39 | splitPosition(
40 | collateralToken,
41 | bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01))) - uint(keccak256(abi.encodePacked(conditionId, 0b10)))),
42 | conditionId,
43 | [0b10, 0b01],
44 | amount
45 | )
46 |
47 | ConditionalTokens._mint(..) -> msg.sender.onERC1155Received(..) ->
48 |
49 | redeemPositions(
50 | collateralToken,
51 | bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01))) - uint(keccak256(abi.encodePacked(conditionId, 0b10)))),
52 | conditionId,
53 | [0b01]
54 | ) //redeems `amount` of position of collection with id `bytes32(uint(keccak256(abi.encodePacked(conditionId, 0b01))) - uint(keccak256(abi.encodePacked(conditionId, 0b10))))` so that splitPosition can burn it and successfully terminate
55 |
56 | redeemPositions(
57 | collateralToken,
58 | bytes32(0),
59 | conditionId,
60 | [0b01]
61 | ) //redeems collateral from the winning position that has been forged
62 | ```
63 |
64 |
65 | ### fixed
66 |
67 | The issue was addressed by burning tokens before minting new ones and is no longer present in: https://github.com/gnosis/conditional-tokens-contracts/tree/4afa2fed1dfa62d8f413e126f238811f1d40bbfc
68 |
69 |
70 | ## 2. Used multihash algorithm vulnerable to generalised birthday attack
71 |
72 | ### type: security / severity: critical
73 |
74 | Ids of complex collections are sums of hashes of data describing simple collections (algorithm that was introduced as AdHash in: http://cseweb.ucsd.edu/~mihir/papers/inchash.pdf). Unfortunately there are known practical techniques that allow finding sets of different hashes that sum to the same number, opening the indexing system to fatal collision attacks (for details of such attack see: http://www.cs.berkeley.edu/~daw/papers/genbday-long.ps).
75 |
76 | ### addressed
77 |
78 | The issue was addressed by replacing the AdHash algorithm with Elliptic Curve Multiset Hash (see: https://arxiv.org/abs/1601.06502), this seems like a promising solution but the implementation (present in https://github.com/gnosis/conditional-tokens-contracts/tree/4afa2fed1dfa62d8f413e126f238811f1d40bbfc) is still pending evaluation.
79 |
80 |
81 | ## 3. Possible efficiency gains by adding a batch mint nethod to ERC1155 contract
82 |
83 | ### type: efficiency / severity: minor
84 |
85 | Adding a batch mint in ERC1155 that invokes `_doSafeBatchTransferAcceptanceCheck` instead of `_doSafeTransferAcceptanceCheck` might be useful because `ConditionalTokens.sol:L126` can be executed a lot of times and generate a lot of external calls through `_doSafeTransferAcceptanceCheck`.
86 |
87 | ### fixed
88 |
89 | Batch mint has been added https://github.com/gnosis/conditional-tokens-contracts/tree/4afa2fed1dfa62d8f413e126f238811f1d40bbfc
--------------------------------------------------------------------------------
/docs/audit/Initial Review of Elliptic Curve Multiset Hashing Proposal.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnosis/conditional-tokens-contracts/eeefca66eb46c800a9aaab88db2064a99026fde5/docs/audit/Initial Review of Elliptic Curve Multiset Hashing Proposal.docx
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Configuration file for the Sphinx documentation builder.
4 | #
5 | # This file does only contain a selection of the most common options. For a
6 | # full list see the documentation:
7 | # http://www.sphinx-doc.org/en/stable/config
8 |
9 | # -- Path setup --------------------------------------------------------------
10 |
11 | # If extensions (or modules to document with autodoc) are in another directory,
12 | # add these directories to sys.path here. If the directory is relative to the
13 | # documentation root, use os.path.abspath to make it absolute, like shown here.
14 | #
15 | import os
16 | # import sys
17 | # sys.path.insert(0, os.path.abspath('.'))
18 |
19 |
20 | # -- Project information -----------------------------------------------------
21 |
22 | project = 'Conditional Tokens'
23 | copyright = '2018, Gnosis Ltd'
24 | author = 'Gnosis'
25 |
26 | # The full version, including alpha/beta/rc tags
27 | import json
28 | with open(os.path.join(os.path.dirname(__file__), '..', 'package.json')) as package_json_file:
29 | release = json.load(package_json_file)['version']
30 | # The short X.Y version
31 | (major, _, rest_of_version) = release.partition('.')
32 | (minor, _, _) = rest_of_version.partition('.')
33 | version = major + '.' + minor
34 |
35 | # -- General configuration ---------------------------------------------------
36 |
37 | # If your documentation needs a minimal Sphinx version, state it here.
38 | #
39 | # needs_sphinx = '1.0'
40 |
41 | # Add any Sphinx extension module names here, as strings. They can be
42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
43 | # ones.
44 | extensions = [
45 | 'sphinx.ext.autodoc',
46 | 'sphinxcontrib.soliditydomain',
47 | ]
48 |
49 | # Add any paths that contain templates here, relative to this directory.
50 | templates_path = ['_templates']
51 |
52 | # The suffix(es) of source filenames.
53 | # You can specify multiple suffix as a list of string:
54 | #
55 | # source_suffix = ['.rst', '.md']
56 | source_suffix = '.rst'
57 |
58 | # The master toctree document.
59 | master_doc = 'index'
60 |
61 | # The language for content autogenerated by Sphinx. Refer to documentation
62 | # for a list of supported languages.
63 | #
64 | # This is also used if you do content translation via gettext catalogs.
65 | # Usually you set "language" from the command line for these cases.
66 | language = None
67 |
68 | # List of patterns, relative to source directory, that match files and
69 | # directories to ignore when looking for source files.
70 | # This pattern also affects html_static_path and html_extra_path .
71 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
72 |
73 | # The name of the Pygments (syntax highlighting) style to use.
74 | pygments_style = 'sphinx'
75 |
76 |
77 | # -- Options for HTML output -------------------------------------------------
78 |
79 | # The theme to use for HTML and HTML Help pages. See the documentation for
80 | # a list of builtin themes.
81 | #
82 | html_theme = 'sphinx_rtd_theme'
83 |
84 | # Theme options are theme-specific and customize the look and feel of a theme
85 | # further. For a list of options available for each theme, see the
86 | # documentation.
87 | #
88 | # html_theme_options = {}
89 |
90 | # Add any paths that contain custom static files (such as style sheets) here,
91 | # relative to this directory. They are copied after the builtin static files,
92 | # so a file named "default.css" will overwrite the builtin "default.css".
93 | html_static_path = ['_static']
94 |
95 | # Custom sidebar templates, must be a dictionary that maps document names
96 | # to template names.
97 | #
98 | # The default sidebars (for documents that don't match any pattern) are
99 | # defined by theme itself. Builtin themes are using these templates by
100 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
101 | # 'searchbox.html']``.
102 | #
103 | # html_sidebars = {}
104 |
105 |
106 | # -- Options for HTMLHelp output ---------------------------------------------
107 |
108 | # Output file base name for HTML help builder.
109 | htmlhelp_basename = 'ConditionalTokensdoc'
110 |
111 |
112 | # -- Options for LaTeX output ------------------------------------------------
113 |
114 | latex_elements = {
115 | # The paper size ('letterpaper' or 'a4paper').
116 | #
117 | # 'papersize': 'letterpaper',
118 |
119 | # The font size ('10pt', '11pt' or '12pt').
120 | #
121 | # 'pointsize': '10pt',
122 |
123 | # Additional stuff for the LaTeX preamble.
124 | #
125 | # 'preamble': '',
126 |
127 | # Latex figure (float) alignment
128 | #
129 | # 'figure_align': 'htbp',
130 | }
131 |
132 | # Grouping the document tree into LaTeX files. List of tuples
133 | # (source start file, target name, title,
134 | # author, documentclass [howto, manual, or own class]).
135 | latex_documents = [
136 | (master_doc,
137 | 'ConditionalTokens.tex',
138 | 'Conditional Tokens Documentation',
139 | 'Gnosis', 'manual'),
140 | ]
141 |
142 |
143 | # -- Options for manual page output ------------------------------------------
144 |
145 | # One entry per manual page. List of tuples
146 | # (source start file, name, description, authors, manual section).
147 | man_pages = [
148 | (master_doc, 'conditionaltokens',
149 | 'Conditional Tokens Documentation',
150 | [author], 1)
151 | ]
152 |
153 |
154 | # -- Options for Texinfo output ----------------------------------------------
155 |
156 | # Grouping the document tree into Texinfo files. List of tuples
157 | # (source start file, target name, title, author,
158 | # dir menu entry, description, category)
159 | texinfo_documents = [
160 | (master_doc, 'ConditionalTokens',
161 | 'Conditional Tokens Documentation',
162 | author, 'ConditionalTokens',
163 | 'One line description of project.',
164 | 'Miscellaneous'),
165 | ]
166 |
167 |
168 | # -- Options for Epub output -------------------------------------------------
169 |
170 | # Bibliographic Dublin Core info.
171 | epub_title = project
172 | epub_author = author
173 | epub_publisher = author
174 | epub_copyright = copyright
175 |
176 | # The unique identifier of the text. This can be a ISBN number
177 | # or the project homepage.
178 | #
179 | # epub_identifier = ''
180 |
181 | # A unique identification for the text.
182 | #
183 | # epub_uid = ''
184 |
185 | # A list of files that should not be packed into the epub file.
186 | epub_exclude_files = ['search.html']
187 |
188 |
189 | # -- Extension configuration -------------------------------------------------
190 |
--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | The source for the contracts can be found on `Github`_.
5 |
6 | .. _Github: https://github.com/gnosis/conditional-tokens-contracts
7 |
8 | .. highlight:: bash
9 |
10 | To set up for contributing, install requirements with NPM::
11 |
12 | npm install
13 |
14 | .. tip:: Many of the following commands simply wrap corresponding `Truffle commands `_.
15 |
16 |
17 | Testing and Linting
18 | -------------------
19 |
20 | The test suite may be run using::
21 |
22 | npm test
23 |
24 | In order to run a subset of test cases which match a regular expression, the ``TEST_GREP`` environment variable may be used::
25 |
26 | TEST_GREP='obtainable conditionIds' npm test
27 |
28 | The JS test files may be linted via::
29 |
30 | npm run lint
31 |
32 | Contracts may also be linted via::
33 |
34 | npm run lint-contracts
35 |
36 | .. _Truffle: https://truffleframework.com
37 |
38 |
39 | Development commands
40 | --------------------
41 |
42 | To compile all the contracts, obtaining build artifacts containing each containing their respective contract's ABI and bytecode, use the following command::
43 |
44 | npm run compile
45 |
46 | Running the migrations, deploying the contracts onto a chain and recording the contract's deployed location in the build artifact can also be done::
47 |
48 | npm run migrate
49 |
50 | Dropping into a Truffle develop session can be done via::
51 |
52 | npm run develop
53 |
54 |
55 | Network Information
56 | -------------------
57 |
58 | Showing the deployed addresses of all contracts on all networks can be done via::
59 |
60 | npm run networks
61 |
62 | Extra command line options for the underlying Truffle command can be passed down through NPM by preceding the options list with ``--``. For example, in order to purge the build artifacts of any unnamed network information, you can run::
63 |
64 | npm run networks -- --clean
65 |
66 | To take network info from ``networks.json`` and inject it into the build artifacts, you can run::
67 |
68 | npm run injectnetinfo
69 |
70 | If you instead wish to extract all network information from the build artifacts into ``networks.json``, run::
71 |
72 | npm run extractnetinfo
73 |
74 | .. warning:: Extracting network info will overwrite ``networks.json``.
75 |
76 |
77 | Building the Documentation
78 | --------------------------
79 |
80 | (Will install `Sphinx `_ and `Solidity Domain for Sphinx `_):
81 |
82 | .. code-block:: bash
83 |
84 | cd docs
85 | pip install -r requirements.txt
86 | make html
87 |
88 |
89 | Contributors
90 | ------------
91 |
92 | * Stefan George (`Georgi87 `_)
93 | * Martin Koeppelmann (`koeppelmann `_)
94 | * Alan Lu (`cag `_)
95 | * Roland Kofler (`rolandkofler `_)
96 | * Collin Chin (`collinc97 `_)
97 | * Christopher Gewecke (`cgewecke `_)
98 | * Anton V Shtylman (`InfiniteStyles `_)
99 | * Billy Rennekamp (`okwme `_)
100 | * Denis Granha (`denisgranha `_)
101 | * Alex Beregszaszi (`axic `_)
102 |
--------------------------------------------------------------------------------
/docs/developer-guide.rst:
--------------------------------------------------------------------------------
1 | Developer Guide
2 | ===============
3 |
4 | Prerequisites
5 | -------------
6 |
7 | Usage of the ``ConditionalTokens`` smart contract requires some proficiency in `Solidity`_.
8 |
9 | Additionally, this guide will assume a `Truffle`_ based setup. Client-side code samples will be written in JavaScript assuming the presence of a `web3.js`_ instance and various `TruffleContract`_ wrappers.
10 |
11 | The current state of this smart contract may be found on `Github`_.
12 |
13 | .. _Solidity: https://solidity.readthedocs.io
14 | .. _Truffle: https://truffleframework.com
15 | .. _web3.js: https://web3js.readthedocs.io/en/1.0/
16 | .. _TruffleContract: https://github.com/trufflesuite/truffle/tree/next/packages/truffle-contract#truffle-contract
17 | .. _Github: https://github.com/gnosis/conditional-tokens-contracts
18 |
19 | Installation
20 | ------------
21 |
22 | Via NPM
23 | ~~~~~~~
24 |
25 | This developmental framework may be installed from Github through NPM by running the following::
26 |
27 | npm i '@gnosis.pm/conditional-tokens-contracts'
28 |
29 |
30 | Preparing a Condition
31 | ---------------------
32 |
33 | Before conditional tokens can exist, a *condition* must be prepared. A condition is a question to be answered in the future by a specific oracle in a particular manner. The following function may be used to prepare a condition:
34 |
35 | .. autosolfunction:: ConditionalTokens.prepareCondition
36 |
37 | .. note:: It is up to the consumer of the contract to interpret the question ID correctly. For example, a client may interpret the question ID as an IPFS hash which can be used to retrieve a document specifying the question more fully. The meaning of the question ID is left up to clients.
38 |
39 | If the function succeeds, the following event will be emitted, signifying the preparation of a condition:
40 |
41 | .. autosolevent:: ConditionalTokens.ConditionPreparation
42 |
43 | .. note:: The condition ID is different from the question ID, and their distinction is important.
44 |
45 | The successful preparation of a condition also initializes the following state variable:
46 |
47 | .. autosolstatevar:: ConditionalTokens.payoutNumerators
48 |
49 | To determine if, given a condition's ID, a condition has been prepared, or to find out a condition's outcome slot count, use the following accessor:
50 |
51 | .. autosolfunction:: ConditionalTokens.getOutcomeSlotCount
52 |
53 | The resultant payout vector of a condition contains a predetermined number of *outcome slots*. The entries of this vector are reported by the oracle, and their values sum up to one. This payout vector may be interpreted as the oracle's answer to the question posed in the condition.
54 |
55 | A Categorical Example
56 | ~~~~~~~~~~~~~~~~~~~~~
57 |
58 | Let's consider a question where only one out of multiple choices may be chosen:
59 |
60 | Who out of the following will be chosen?
61 |
62 | * Alice
63 | * Bob
64 | * Carol
65 |
66 | Through some commonly agreed upon mechanism, the detailed description for this question becomes strongly associated with a 32 byte question ID: ``0xabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc1234``
67 |
68 | Let's also suppose we trust the oracle with address ``0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF`` to deliver the answer for this question.
69 |
70 | To prepare this condition, the following code gets run:
71 |
72 | .. code-block:: js
73 |
74 | await conditionalTokens.prepareCondition(
75 | '0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF',
76 | '0xabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc1234',
77 | 3
78 | )
79 |
80 | The condition ID may be determined off-chain from the parameters via ``web3``:
81 |
82 | .. code-block:: js
83 |
84 | web3.utils.soliditySha3({
85 | t: 'address',
86 | v: '0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF'
87 | }, {
88 | t: 'bytes32',
89 | v: '0xabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc1234'
90 | }, {
91 | t: 'uint',
92 | v: 3
93 | })
94 |
95 | A helper function for determining the condition ID also exists on both the contract and the ``CTHelpers`` library:
96 |
97 | .. autosolfunction:: ConditionalTokens.getConditionId
98 |
99 | This yields a condition ID of ``0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63``.
100 |
101 | Later, if the oracle ``0x1337aBcdef1337abCdEf1337ABcDeF1337AbcDeF`` makes a report that the payout vector for the condition is ``[0, 1, 0]``, the oracle essentially states that Bob was chosen, as the outcome slot associated with Bob would receive all of the payout.
102 |
103 | A Scalar Example
104 | ~~~~~~~~~~~~~~~~
105 |
106 | Let us now consider a question where the answer may lie in a range:
107 |
108 | What will the score be? [0, 1000]
109 |
110 | Let's say the question ID for this question is ``0x777def777def777def777def777def777def777def777def777def777def7890``, and that we trust the oracle ``0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe`` to deliver the results for this question.
111 |
112 | To prepare this condition, the following code gets run:
113 |
114 | .. code-block:: js
115 |
116 | await conditionalTokens.prepareCondition(
117 | '0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe',
118 | '0x777def777def777def777def777def777def777def777def777def777def7890',
119 | 2
120 | )
121 |
122 | The condition ID for this condition can be calculated as ``0x3bdb7de3d0860745c0cac9c1dcc8e0d9cb7d33e6a899c2c298343ccedf1d66cf``.
123 |
124 | In this case, the condition was created with two slots: one which represents the low end of the range (0) and another which represents the high end (1000). The slots' reported payout values should indicate how close the answer was to these endpoints. For example, if the oracle ``0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe`` makes a report that the payout vector is ``[9/10, 1/10]``, then the oracle essentially states that the score was 100, as the slot corresponding to the low end is worth nine times what the slot corresponding with the high end is worth, meaning the score should be nine times closer to 0 than it is close to 1000. Likewise, if the payout vector is reported to be ``[0, 1]``, then the oracle is saying that the score was *at least* 1000.
125 |
126 |
127 | Outcome Collections
128 | -------------------
129 |
130 | The main concept for understanding the mechanics of this system is that of a *position*. We will build to this concept from conditions and outcome slots, and then demonstrate the use of this concept.
131 |
132 | However, before we can talk about positions, we first have to talk about *outcome collections*, which may be defined like so:
133 |
134 | A nonempty proper subset of a condition’s outcome slots which represents the sum total of all the contained slots’ payout values.
135 |
136 | Categorical Example Featuring Alice, Bob, and Carol
137 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138 |
139 | We'll denote the outcome slots for Alice, Bob, and Carol as ``A``, ``B``, and ``C`` respectively.
140 |
141 | A valid outcome collection may be ``(A|B)``. In this example, this outcome collection represents the eventuality in which either Alice or Bob is chosen. Note that for a categorical condition, the payout vector which the oracle reports will eventually contain a one in exactly one of the three slots, so the sum of the values in Alice's and Bob's slots is one precisely when either Alice or Bob is chosen, and zero otherwise.
142 |
143 | ``(C)`` by itself is also a valid outcome collection, and this simply represents the case where Carol is chosen.
144 |
145 | ``()`` is an invalid outcome collection, as it is empty. Empty outcome collections do not make sense, as they would essentially represent no eventuality and have no value no matter what happens.
146 |
147 | Conversely, ``(A|B|C)`` is also an invalid outcome collection, as it is not a proper subset. Outcome collections consisting of all the outcome slots for a condition also do not make sense, as they would simply represent any eventuality, and should be equivalent to whatever was used to collateralize these outcome collections.
148 |
149 | Finally, outcome slots from different conditions (e.g. ``(A|X)``) cannot be composed in a single outcome collection.
150 |
151 | Index Set Representation and Identifier Derivation
152 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
153 |
154 | A outcome collection may be represented by an a condition and an *index set*. This is a 256 bit array which denotes which outcome slots are present in a outcome collection. For example, the value ``3 == 0b011`` corresponds to the outcome collection ``(A|B)``, whereas the value ``4 == 0b100`` corresponds to ``(C)``. Note that the indices start at the lowest bit in a ``uint``.
155 |
156 | A outcome collection may be identified with a 32 byte value called a *collection identifier*. Calculating the collection ID for an outcome collection involves hashing its condition ID and index set into a point on the `alt_bn128`_ elliptic curve.
157 |
158 | .. note::
159 |
160 | In order to calculate the collection ID for ``(A|B)``, the following steps must be performed.
161 |
162 | 1. An initial value for the point x-coordinate is set by hashing the condition ID and the index set of the outcome collection, and interpreting the resulting hash as a big-endian integer.
163 |
164 | .. code-block:: js
165 |
166 | web3.utils.soliditySha3({
167 | // See section "A Categorical Example" for derivation of this condition ID
168 | t: 'bytes32',
169 | v: '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63'
170 | }, {
171 | t: 'uint',
172 | v: 0b011 // Binary Number literals supported in newer versions of JavaScript
173 | })
174 |
175 | This results in an initial x-coordinate of ``0x52ff54f0f5616e34a2d4f56fb68ab4cc636bf0d92111de74d1ec99040a8da118``, or ``37540785828268254412066351790903087640191294994197155621611396915481249947928``.
176 |
177 | An ``odd`` flag is set according to whether the highest bit of the hash result is set. In this case, because the highest bit of the hash result is not set,``odd = false``.
178 |
179 | 2. The x-coordinate gets incremented by one modulo the order of the `alt_bn128`_ base field, which is ``21888242871839275222246405745257275088696311157297823662689037894645226208583``.
180 |
181 | The first time, this results in an updated x-coordinate ``x = 15652542956428979189819946045645812551494983836899331958922359020836023739346``.
182 |
183 | 3. The x-coordinate is checked to see if it is the x-coordinate of points on the elliptic curve. Specifically, ``x**3 + 3`` gets computed in the base field, and if the result is a quadratic residue, the x-coordinate belongs to a pair of points on the elliptic curve. If the result is a non-residue however, return to step 2.
184 |
185 | When ``x = 15652542956428979189819946045645812551494983836899331958922359020836023739346``, ``x**3 + 3 == 7181824697751204416624405172148440000524665091599802536460745194285959874882`` is not a quadratic residue in the base field, so go back to step 2.
186 |
187 | When ``x = 15652542956428979189819946045645812551494983836899331958922359020836023739347``, ``x**3 + 3 == 19234863727839675005817902755221636205208068129817953505352549927470359854418`` is also not a quadratic residue in the base field, so go back to step 2.
188 |
189 | When ``x = 15652542956428979189819946045645812551494983836899331958922359020836023739348``, ``x**3 + 3 == 15761946137305644622699047885883332275379818402942977914333319312444771227121`` is still not a quadratic residue in the base field, so go back to step 2.
190 |
191 | When ``x = 15652542956428979189819946045645812551494983836899331958922359020836023739349``, ``x**3 + 3 == 18651314797988388489514246309390803299736227068272699426092091243854420201580`` is a quadratic residue in the base field, so we have found a pair of points on the curve, and we may continue.
192 |
193 | 4. Note that the base field occupies 254 bits of space, meaning the x-coordinate we found also occupies 254 bits of space, and has two free bits in an EVM word (256 bits). Leave the highest bit unset, and set the next highest bit if ``odd == true``. In our example, ``odd`` is unset, so we're done, and the collection ID for ``(A|B)`` is ``15652542956428979189819946045645812551494983836899331958922359020836023739349``, or ``0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5``.
194 |
195 | We may also combine collection IDs for outcome collections for different conditions by performing elliptic curve point addition on them.
196 |
197 | .. note::
198 |
199 | Let's denote the slots for range ends 0 and 1000 from our scalar condition example as ``LO`` and ``HI``. We can find the collection ID for ``(LO)`` to be ``0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2`` using the procedure illustrated in the previous note.
200 |
201 | The combined collection ID for ``(A|B)&(LO)`` can be calculated in the following manner:
202 |
203 | 1. Decompress the constituent collection IDs into elliptic curve point coordinates. Take the low 254 bits as the x-coordinate, and pick the y-coordinate which is even or odd depending on the value of the second highest bit.
204 |
205 | * ``(A|B)``, which has a collection ID of ``0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5``, gets decompressed to the point::
206 |
207 | (15652542956428979189819946045645812551494983836899331958922359020836023739349,
208 | 11459896044816691076313215195950563425899182565928550352639564868174527712586)
209 |
210 | Note the even y-coordinate is chosen here.
211 |
212 | * ``(LO)``, which has a collection ID of ``0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2``, gets decompressed to the point::
213 |
214 | (9970120961273109372766525305441055537695652051815636823675568206550524069826,
215 | 5871835597783351455285190273403665696556137392019654883787357811704360229175)
216 |
217 | The odd y-coordinate indication bit was chopped off the compressed form before its use as the decompressed form's x-coordinate, and the odd y-coordinate is chosen here.
218 |
219 | 2. Perform point addition on the `alt_bn128`_ curve with these points. The sum of these points is the point::
220 |
221 | (21460418698095194776649446887647175906168566678584695492252634897075584178441,
222 | 4596536621806896659272941037410436605631447622293229168614769592376282983323)
223 |
224 | 3. Compress the result by taking the x-coordinate, and setting the second highest bit, which should be just outside the x-coordinate, depending on whether the y-coordinate was odd. The combined collection ID for ``(A|B)&(LO)`` is ``0x6f722aa250221af2eba9868fc9d7d43994794177dd6fa7766e3e72ba3c111909``.
225 |
226 | .. warning:: Both bitwise XOR and truncated addition is not used in this scenario because these operations are vulnerable to collisions via `a generalized birthday attack`_.
227 |
228 | Similar to with conditions, the contract and the ``CTHelpers`` library also provide helper functions for calculating outcome collection IDs:
229 |
230 | .. autosolfunction:: ConditionalTokens.getCollectionId
231 |
232 | .. _alt_bn128: https://eips.ethereum.org/EIPS/eip-196
233 | .. _a generalized birthday attack: https://link.springer.com/chapter/10.1007/3-540-45708-9_19
234 |
235 |
236 | Defining Positions
237 | ------------------
238 |
239 | In order to define a position, we first need to designate a collateral token. This token must be an `ERC20`_ token which exists on the same chain as the ConditionalTokens instance.
240 |
241 | Then we need at least one condition with a outcome collection, though a position may refer to multiple conditions each with an associated outcome collection. Positions become valuable precisely when *all* of its constituent outcome collections are valuable. More explicitly, the value of a position is a *product* of the values of those outcome collections composing the position.
242 |
243 | With these ingredients, position identifiers can also be calculated by hashing the address of the collateral token and the combined collection ID of all the outcome collections in the position. We say positions are *deeper* if they contain more conditions and outcome collections, and *shallower* if they contain less.
244 |
245 | As an example, let's suppose that there is an ERC20 token called DollaCoin which exists at the address ``0xD011ad011ad011AD011ad011Ad011Ad011Ad011A``, and it is used as collateral for some positions. We will denote this token with ``$``.
246 |
247 | We may calculate the position ID for the position ``$:(A|B)`` via:
248 |
249 | .. code-block:: js
250 |
251 | web3.utils.soliditySha3({
252 | t: 'address',
253 | v: '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A'
254 | }, {
255 | t: 'bytes32',
256 | v: '0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5'
257 | })
258 |
259 | The ID for ``$:(A|B)`` turns out to be ``0x5355fd8106a08b14aedf99935210b2c22a7f92abaf8bb00b60fcece1032436b7``.
260 |
261 | Similarly, the ID for ``$:(LO)`` can be found to be ``0x1958e759291b2bde460cdf2158dea8d0f5c4e22c77ecd09d3ca6a36f01616e02``, and ``$:(A|B)&(LO)`` has an ID of ``0x994b964b94eb15148726de8caa08cac559ec51a90fcbc9cc19aadfdc809f34c9``.
262 |
263 | Helper functions for calculating positions also exist:
264 |
265 | .. autosolfunction:: ConditionalTokens.getPositionId
266 |
267 | .. _ERC20: https://theethereum.wiki/w/index.php/ERC20_Token_Standard
268 |
269 | All the positions backed by DollaCoin which depend on the example categorical condition and the example scalar condition form a DAG (directed acyclic graph):
270 |
271 | .. figure:: /_static/all-positions-from-two-conditions.png
272 | :alt: DAG of every position which can be made from DollaCoin and the two example conditions, where the nodes are positions, edges are colored by condition, and directionality is implied with vertical spacing.
273 | :align: center
274 |
275 | Graph of all positions backed by ``$`` which are contingent on either or both of the example conditions.
276 |
277 |
278 | Splitting and Merging Positions
279 | -------------------------------
280 |
281 | Once conditions have been prepared, stake in positions contingent on these conditions may be obtained. Furthermore, this stake must be backed by collateral held by the contract. In order to ensure this is the case, stake in shallow positions may only be minted by sending collateral to the contract for the contract to hold, and stake in deeper positions may only be created by burning stake in shallower positions. Any of these is referred to as *splitting a position*, and is done through the following function:
282 |
283 | .. autosolfunction:: ConditionalTokens.splitPosition
284 |
285 | If this transaction does not revert, the following event will be emitted:
286 |
287 | .. autosolevent:: ConditionalTokens.PositionSplit
288 |
289 | To decipher this function, let's consider what would be considered a valid split, and what would be invalid:
290 |
291 | .. figure:: /_static/valid-vs-invalid-splits.png
292 | :alt: Various valid and invalid splits of positions.
293 | :align: center
294 |
295 | Details for some of these scenarios will follow
296 |
297 | Basic Splits
298 | ~~~~~~~~~~~~
299 |
300 | Collateral ``$`` can be split into conditional tokens in positions ``$:(A)``, ``$:(B)``, and ``$:(C)``. To do so, use the following code:
301 |
302 | .. code-block:: js
303 |
304 | const amount = 1e18 // could be any amount
305 |
306 | // user must allow conditionalTokens to
307 | // spend amount of DollaCoin, e.g. through
308 | // await dollaCoin.approve(conditionalTokens.address, amount)
309 |
310 | await conditionalTokens.splitPosition(
311 | // This is just DollaCoin's address
312 | '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
313 | // For splitting from collateral, pass bytes32(0)
314 | '0x0000000000000000000000000000000000000000000000000000000000000000',
315 | // "Choice" condition ID:
316 | // see A Categorical Example for derivation
317 | '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
318 | // Each element of this partition is an index set:
319 | // see Outcome Collections for explanation
320 | [0b001, 0b010, 0b100],
321 | // Amount of collateral token to submit for holding
322 | // in exchange for minting the same amount of
323 | // conditional token in each of the target positions
324 | amount,
325 | )
326 |
327 | The effect of this transaction is to transfer ``amount`` DollaCoin from the message sender to the ``conditionalTokens`` to hold, and to mint ``amount`` of conditional token for the following positions:
328 |
329 | * ``$:(A)``
330 | * ``$:(B)``
331 | * ``$:(C)``
332 |
333 | .. note:: The previous example, where collateral was split into shallow positions containing collections with one slot each, is similar to ``Event.buyAllOutcomes`` from Gnosis' first prediction market contracts.
334 |
335 | The set of ``(A)``, ``(B)``, and ``(C)`` is not the only nontrivial partition of outcome slots for the example categorical condition. For example, the set ``(B)`` (with index set ``0b010``) and ``(A|C)`` (with index set ``0b101``) also partitions these outcome slots, and consequently, splitting from ``$`` to ``$:(B)`` and ``$:(A|C)`` is also valid and can be done with the following code:
336 |
337 | .. code-block:: js
338 |
339 | await conditionalTokens.splitPosition(
340 | '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
341 | '0x0000000000000000000000000000000000000000000000000000000000000000',
342 | '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
343 | // This partition differs from the previous example
344 | [0b010, 0b101],
345 | amount,
346 | )
347 |
348 | This transaction also transfers ``amount`` DollaCoin from the message sender to the ``conditionalTokens`` to hold, but it mints ``amount`` of conditional token for the following positions instead:
349 |
350 | * ``$:(B)``
351 | * ``$:(A|C)``
352 |
353 | .. warning:: If non-disjoint index sets are supplied to ``splitPosition``, the transaction will revert.
354 |
355 | Partitions must be valid partitions. For example, you can't split ``$`` to ``$:(A|B)`` and ``$:(B|C)`` because ``(A|B)`` (``0b011``) and ``(B|C)`` (``0b110``) share outcome slot ``B`` (``0b010``).
356 |
357 | Splits to Deeper Positions
358 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
359 |
360 | It's also possible to split from a position, burning conditional tokens in that position in order to acquire conditional tokens in deeper positions. For example, you can split ``$:(A|B)`` to target ``$:(A|B)&(LO)`` and ``$:(A|B)&(HI)``:
361 |
362 | .. code-block:: js
363 |
364 | await conditionalTokens.splitPosition(
365 | // Note that we're still supplying the same collateral token
366 | // even though we're going two levels deep.
367 | '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
368 | // Here, instead of just supplying 32 zero bytes, we supply
369 | // the collection ID for (A|B).
370 | // This is NOT the position ID for $:(A|B)!
371 | '0x229b067e142fce0aea84afb935095c6ecbea8647b8a013e795cc0ced3210a3d5',
372 | // This is the condition ID for the example scalar condition
373 | '0x3bdb7de3d0860745c0cac9c1dcc8e0d9cb7d33e6a899c2c298343ccedf1d66cf',
374 | // This is the only partition that makes sense
375 | // for conditions with only two outcome slots
376 | [0b01, 0b10],
377 | amount,
378 | )
379 |
380 | This transaction burns ``amount`` of conditional token in position ``$:(A|B)`` (position ID ``0x5355fd8106a08b14aedf99935210b2c22a7f92abaf8bb00b60fcece1032436b7``) in order to mint ``amount`` of conditional token in the following positions:
381 |
382 | * ``$:(A|B)&(LO)``
383 | * ``$:(A|B)&(HI)``
384 |
385 | Because the collection ID for ``(A|B)&(LO)`` is just the sum of the collection IDs for ``(A|B)`` and ``(LO)``, we could have split from ``(LO)`` to get ``(A|B)&(LO)`` and ``(C)&(LO)``:
386 |
387 | .. code-block:: js
388 |
389 | await conditionalTokens.splitPosition(
390 | '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
391 | // The collection ID for (LO).
392 | // This collection contains an outcome collection from the example scalar condition
393 | // instead of from the example categorical condition.
394 | '0x560ae373ed304932b6f424c8a243842092c117645533390a3c1c95ff481587c2',
395 | // This is the condition ID for the example categorical condition
396 | // as opposed to the example scalar condition.
397 | '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
398 | // This partitions { A, B, C } into [{ A, B }, { C }]
399 | [0b011, 0b100],
400 | amount,
401 | )
402 |
403 | The ``$:(A|B)&(LO)`` position reached is the same both ways.
404 |
405 | .. figure:: /_static/v2-cond-market-ot-compare.png
406 | :alt: There is a single class of conditional tokens which resolves to collateral if Alice gets chosen and the score is high.
407 | :align: center
408 |
409 | There are many ways to split to a deep position.
410 |
411 | Splits on Partial Partitions
412 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
413 |
414 | Supplying a partition which does not cover the set of all outcome slots for a condition, but instead some outcome collection, is also possible. For example, it is possible to split ``$:(B|C)`` (position ID ``0x5d06cd85e2ff915efab0e7881432b1c93b3e543c5538d952591197b3893f5ce3``) to ``$:(B)`` and ``$:(C)``:
415 |
416 | .. code-block:: js
417 |
418 | await conditionalTokens.splitPosition(
419 | '0xD011ad011ad011AD011ad011Ad011Ad011Ad011A',
420 | // Note that we also supply zeroes here, as the only aspect shared
421 | // between $:(B|C), $:(B) and $:(C) is the collateral token
422 | '0x0000000000000000000000000000000000000000000000000000000000000000',
423 | '0x67eb23e8932765c1d7a094838c928476df8c50d1d3898f278ef1fb2a62afab63',
424 | // This partition does not cover the first outcome slot
425 | [0b010, 0b100],
426 | amount,
427 | )
428 |
429 | Merging Positions
430 | ~~~~~~~~~~~~~~~~~
431 |
432 | Merging positions does precisely the opposite of what splitting a position does. It burns conditional tokens in the deeper positions to either mint conditional tokens in a shallower position or send collateral to the message sender:
433 |
434 | .. figure:: /_static/merge-positions.png
435 | :alt: A couple examples of merging positions.
436 | :align: center
437 |
438 | Splitting positions, except with the arrows turned around.
439 |
440 | To merge positions, use the following function:
441 |
442 | .. autosolfunction:: ConditionalTokens.mergePositions
443 |
444 | If successful, the function will emit this event:
445 |
446 | .. autosolevent:: ConditionalTokens.PositionsMerge
447 |
448 | .. note:: This generalizes ``sellAllOutcomes`` from Gnosis' first prediction market contracts like ``splitPosition`` generalizes ``buyAllOutcomes``.
449 |
450 |
451 | Querying and Transferring Stake
452 | -------------------------------
453 |
454 | The ConditionalTokens contract implements the `ERC1155 multitoken`_ interface. In addition to a holder address, each token is indexed by an ID in this standard. In particular, position IDs are used to index conditional tokens. This is reflected in the balance querying function:
455 |
456 | .. sol:function:: balanceOf(address owner, uint256 positionId) external view returns (uint256)
457 |
458 | To transfer conditional tokens, the following functions may be used, as per ERC1155:
459 |
460 | .. sol:function::
461 | safeTransferFrom(address from, address to, uint256 positionId, uint256 value, bytes data) external
462 | safeBatchTransferFrom(address from, address to, uint256[] positionIds, uint256[] values, bytes data) external
463 |
464 | These transfer functions ignore the ``data`` parameter.
465 |
466 | .. note:: When sending to contract accounts, transfers will be rejected unless the recipient implements the ``ERC1155TokenReceiver`` interface and returns the expected magic values. See the `ERC1155 multitoken`_ spec for more information.
467 |
468 | Approving an operator account to transfer conditional tokens on your behalf may also be done via:
469 |
470 | .. sol:function:: setApprovalForAll(address operator, bool approved) external
471 |
472 | Querying the status of approval can be done with:
473 |
474 | .. sol:function:: isApprovedForAll(address owner, address operator) external view returns (bool)
475 |
476 | .. _ERC1155 multitoken: https://eips.ethereum.org/EIPS/eip-1155
477 |
478 |
479 | Redeeming Positions
480 | -------------------
481 |
482 | Before this is possible, the payout vector must be set by the oracle:
483 |
484 | .. autosolfunction:: ConditionalTokens.reportPayouts
485 |
486 | This will emit the following event:
487 |
488 | .. autosolevent:: ConditionalTokens.ConditionResolution
489 |
490 | Then positions containing this condition can be redeemed via:
491 |
492 | .. autosolfunction:: ConditionalTokens.redeemPositions
493 |
494 | This will trigger the following event:
495 |
496 | .. autosolevent:: ConditionalTokens.PayoutRedemption
497 |
498 | Also look at this chart:
499 |
500 | .. figure:: /_static/redemption.png
501 | :alt: Oracle reporting and corresponding redemption rates.
502 | :align: center
503 |
--------------------------------------------------------------------------------
/docs/glossary.rst:
--------------------------------------------------------------------------------
1 | Glossary
2 | ========
3 |
4 | *********
5 | Condition
6 | *********
7 | A question that a specific oracle reports on with a preset number of outcome slots. Analogous to events from Gnosis' first prediction market contracts.
8 |
9 | For example, a condition with a categorical outcome, that is, one of N outcomes, may have N outcome slots, where the resolution of the condition sets one of the outcome slots to receive the full payout.
10 |
11 | Another example: a condition with a scalar outcome, that is an outcome X in some range [A, B], may have two outcome slots which correspond to the ends of the range A and B. Both slots are set to receive a proportion of the payout according to how close the outcome X is to A or B.
12 |
13 | *Identified by keccak256(oracle . questionId . outcomeSlotCount)*
14 |
15 | **Outcome Slot**
16 | Defines the redemption rate of Conditional Tokens. Conditional Tokens convert to a proportion of collateral depending on the outcome resolution of a set of conditions.
17 |
18 | Outcome Slots can either be unresolved (when the condition hasn’t been reported on) or resolved (after condition resolution).
19 |
20 | **Index Set**
21 | A bit array that represents a subset of Outcome Slots in one condition.
22 |
23 | Example: Condition1 has Outcome Slots: A,B,C. It would have 7 possible indexSets: A, B, C, A|B, A|C, B|C, A|B,C
24 |
25 | **Partition**
26 | A specific way to separate the subsets of the Outcome Slots in a condition, using a combination of indexSets.
27 |
28 | **Oracle**
29 | The account which can report the results on the condition.
30 |
31 | **Outcome Resolution**
32 | The process in which an oracle reports results for the Outcome Slots in a condition, setting the Outcome Slot value for each of the condition’s Outcome Slots.
33 |
34 | *********
35 | Position
36 | *********
37 | A set of conditions, along with a non-empty proper subset of Outcome Slots for each condition (represents a combination of one or many Outcome Slots from multiple conditions) represented as a DAG (Directed Acyclic Graph) and tied to a specific stakeholder, Collateral Token, and amount of Conditional Tokens.
38 |
39 | Representing a specific stakeholders stake in a certain condition(s) Outcome Slots as an ERC1155 token.
40 |
41 | A position is made up of:
42 | 1. Stakeholder
43 | Collection Identifier
44 | Condition(s)
45 | IndexSet(s)
46 | CollateralToken
47 | Conditional Tokens
48 |
49 | *Identified by the hash of a H(Collateral Token, Collection Identifier)*
50 |
51 | **Collection Identifier**
52 | An identifier used by positions to target Condition(s) and indexSet(s).
53 |
54 | Rather than target individual Conditions and IndexSets. The Condition Identifier can identify a DAG (Directed Acyclic Graph) of dependant Condition(s) and indexSet(s).
55 |
56 | It is the abstract structure that identifies what Conditions and IndexSets, a position is representing, along with their heirarchy. Without being tied to any specific stakeholder or Collateral Token.
57 |
58 | If the parentCollectionId is equal to 0, then it is a Root Position.
59 |
60 | Identified by a sum( parentCollectionIdentifier, hash(ConditionIdentifier . indexSet)
61 |
62 | **Collateral Token**
63 | An ERC20 token used to create stake in positions.
64 |
65 | **Conditional Tokens**
66 | For a given Collection Identifier, a stakeholder may express a belief in what that Collection Identifier of Outcome Slots represents by using a collateral token to create a position from the Collection Identifier and holding Conditional Tokens in that slot.
67 |
68 | For non-root positions, redemption will convert Conditional Tokens into stake in a shallower position. For root positions, redemption will convert Conditional Tokens into Collateral Tokens.
69 |
70 | **Position Depth**
71 | The number of conditions a position is based off of. Terminology is chosen because positions form a DAG which is very tree-like. Shallow positions have few conditions, and deep positions have many conditions.
72 |
73 | **Root Position**
74 | A position based off of only a single condition. Pays out depending on the outcome of the condition. Pays out directly to the Collateral Token
75 |
76 | **Non-Root Position**
77 | A position based off of multiple conditions. Pays out depending on all of the outcomes of the multiple conditions. Pays out to a shallower Position.
78 |
79 | **Atomic Position**
80 | A position is atomic with respect to a set of conditions if it is contingent on all of the conditions in that set. Pays out to a shallower Position.
81 |
82 | **Splitting a Position**
83 | Stakeholders can split a position on an optional collection identifier and a condition.
84 |
85 | For Root Positions, a collection identifier is not given (instead it is 0), and the stakeholder transfers an input amount of collateral tokens in order to get an equal amount of conditional tokens in each of the condition’s outcome slots.
86 |
87 | For Non-Root Positions, a parent Collection Identifier is provided, and the stakeholder transfers an input amount of Conditional Tokens from the Position corresponding to the parent Collection Identifier down to a set of new Non-Root Position(s).
88 |
89 | Results in conditional tokens being transferred from the position being split to the positions resulting from the split.
90 |
91 | **Merging a Position**
92 | Basically the opposite of splitting a position. Stakeholders can merge a position on an optional Outcome Slot and a Collection Identifier for non-root positions.
93 |
94 | For Root Positions, if an Outcome Slot is not given, the stakeholder inputs an equal amount of Conditional Tokens in each of the condition’s root Outcome Slots to receive an equal amount of Collateral Tokens.
95 |
96 | For Non-Root Positions, a parent Collection Identifier is provided, and the stakeholder transfers an input amount of Conditional Tokens from all the Outcome Slots input in the partition[] either up to a position identified by the parent Collection Identifier or merged into a single Position.
97 |
98 | Results in conditional tokens being transferred from the positions being merged to the position resulting from the merge.
99 |
100 | **Redeeming Positions**
101 | Redeems (1 - all Index Sets) of Positions that are predicated on a single Condition and collection identifier.
102 |
103 | Resulting in either more Conditional Tokens in a shallower position, or a conversion of Conditional Tokens into the Collateral Token, depending on whether it’s a Root Position or Non-Root Position.
104 |
105 | To redeem a position, you need:
106 | 1. The Collateral Token that position is tied to.
107 | It’s parent positions Collection Identifier (if it has one), otherwise it would be a Root Position, and you would input 0 to receive back Collateral Tokens.
108 | The condition you want to redeem.
109 | The Index Sets[] you want to redeem.
110 |
111 | This will redeem all of the Index Sets[] slots listed in the given condition, for only positions with a parent position that has a Collection Idenfier equal to parentCollectionId.
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Conditional Tokens Contracts
2 | ============================
3 |
4 | .. image:: https://travis-ci.org/gnosis/conditional-tokens-contracts.svg?branch=master
5 | :target: https://travis-ci.org/gnosis/conditional-tokens-contracts
6 | :alt: Build Status
7 |
8 | .. image:: https://badges.greenkeeper.io/gnosis/conditional-tokens-contracts.svg
9 | :target: https://greenkeeper.io/
10 | :alt: Greenkeeper badge
11 |
12 | Smart contracts for conditional tokens.
13 |
14 | `→ Github source repository`_
15 |
16 | .. _→ Github source repository: https://github.com/gnosis/conditional-tokens-contracts
17 |
18 |
19 | License
20 | -------
21 |
22 | All smart contracts are released under the `LGPL 3.0`_ license.
23 |
24 | Security and Liability
25 | ~~~~~~~~~~~~~~~~~~~~~~
26 |
27 | All contracts are **WITHOUT ANY WARRANTY**; *without even* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
28 |
29 | .. _LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.en.html
30 |
31 | .. toctree::
32 | :maxdepth: 2
33 | :caption: Contents:
34 |
35 | motivation
36 | developer-guide
37 | contributing
38 | glossary
39 |
40 | Indices and tables
41 | ==================
42 |
43 | * :ref:`genindex`
44 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 | set SPHINXPROJ=ConditionalTokens
13 |
14 | if "%1" == "" goto help
15 |
16 | %SPHINXBUILD% >NUL 2>NUL
17 | if errorlevel 9009 (
18 | echo.
19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20 | echo.installed, then set the SPHINXBUILD environment variable to point
21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
22 | echo.may add the Sphinx directory to PATH.
23 | echo.
24 | echo.If you don't have Sphinx installed, grab it from
25 | echo.http://sphinx-doc.org/
26 | exit /b 1
27 | )
28 |
29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30 | goto end
31 |
32 | :help
33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34 |
35 | :end
36 | popd
37 |
--------------------------------------------------------------------------------
/docs/motivation.rst:
--------------------------------------------------------------------------------
1 | Motivation
2 | ==========
3 |
4 | Conditional tokens were originally designed to enable combinatorial prediction markets more fully. These sorts of markets enable deeper information discovery with respect to conditional probabilities of events and conditional expected values. Prediction markets like this may be represented by nesting traditional prediction markets in systems like Augur or the first version of Gnosis prediction market contracts. However, they weren't designed to maximize fungibility in deeper combinatorial markets.
5 |
6 | Existing Approach to Combinatorial Markets
7 | ------------------------------------------
8 |
9 | For example, let's suppose there are two oracles which report on the following questions:
10 |
11 | 1. Which **choice** out of Alice, Bob, and Carol will be made?
12 | 2. Will the **score** be high or low?
13 |
14 | There are two ways to create conditional tokens backed by a collateral token denoted as ``$``, where the value of these conditional tokens depend on *both* of the reports of these oracles on their respective assigned questions:
15 |
16 | .. figure:: /_static/v1-cond-market-abc-hilo.png
17 | :alt: Markets where events depending on the outcome of the "score" question use outcome tokens from an event depending on the "choice" question as collateral
18 | :align: center
19 |
20 | **Choice**, then **Score**
21 |
22 | .. figure:: /_static/v1-cond-market-hilo-abc.png
23 | :alt: Another setup where instead events depending on the outcome of the "choice" question use outcome tokens from an event depending on the "score" question as collateral
24 | :align: center
25 |
26 | **Score**, then **Choice**
27 |
28 | Although the outcome tokens in the second layer should represent value in collateral under the same conditions irrespective of the order in which the conditions are specified, they are in reality separate entities. Users may hold separate balances of each even though that balance should theoretically be redeemable under the same conditions.
29 |
30 | .. figure:: /_static/v1-cond-market-ot-compare.png
31 | :alt: The two different conditional tokens which resolves to collateral if Alice gets chosen and the score is high.
32 | :align: center
33 |
34 | These tokens should be the same, but aren't.
35 |
36 | The order in which operations are done on these "deeper" tokens matters as well. For example, partial redemptions to the first layer are only possible if that specific outcome token's second layer condition has been resolved.
37 |
38 | Combinatorial Markets with Conditional Tokens
39 | ---------------------------------------------
40 |
41 | For conditional tokens, because all conditions are held in a single contract and are not tied to a specific collateral token, fungibility in deeper layers may be preserved. Referring to the example, the situation using conditional tokens looks more like this:
42 |
43 | .. figure:: /_static/v2-cond-market-slots-only.png
44 | :alt: The second layer of conditional tokens may resolve to conditional tokens of either condition.
45 | :align: center
46 |
47 | It can be seen that the deeper outcome tokens which were different tokens in older systems are now the same token:
48 |
49 | .. figure:: /_static/v2-cond-market-ot-compare.png
50 | :alt: There is a single class of conditional tokens which resolves to collateral if Alice gets chosen and the score is high.
51 | :align: center
52 |
53 | Contrast this with the older approach.
54 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx
2 | sphinx-autobuild
3 | sphinx_rtd_theme
4 | sphinxcontrib-soliditydomain
5 |
--------------------------------------------------------------------------------
/migrations/01_initial_migration.js:
--------------------------------------------------------------------------------
1 | const Migrations = artifacts.require("Migrations");
2 |
3 | module.exports = function(deployer) {
4 | deployer.deploy(Migrations);
5 | };
6 |
--------------------------------------------------------------------------------
/migrations/02_deploy_conditional_tokens.js:
--------------------------------------------------------------------------------
1 | module.exports = function(deployer) {
2 | deployer.deploy(artifacts.require("ConditionalTokens"));
3 | };
4 |
--------------------------------------------------------------------------------
/networks.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConditionalTokens": {
3 | "1": {
4 | "events": {
5 | "0xab3760c3bd2bb38b5bcf54dc79802ed67338b4cf29f3054ded67ed24661e4177": {
6 | "anonymous": false,
7 | "inputs": [
8 | {
9 | "indexed": true,
10 | "name": "conditionId",
11 | "type": "bytes32"
12 | },
13 | {
14 | "indexed": true,
15 | "name": "oracle",
16 | "type": "address"
17 | },
18 | {
19 | "indexed": true,
20 | "name": "questionId",
21 | "type": "bytes32"
22 | },
23 | {
24 | "indexed": false,
25 | "name": "outcomeSlotCount",
26 | "type": "uint256"
27 | }
28 | ],
29 | "name": "ConditionPreparation",
30 | "type": "event",
31 | "signature": "0xab3760c3bd2bb38b5bcf54dc79802ed67338b4cf29f3054ded67ed24661e4177"
32 | },
33 | "0xb44d84d3289691f71497564b85d4233648d9dbae8cbdbb4329f301c3a0185894": {
34 | "anonymous": false,
35 | "inputs": [
36 | {
37 | "indexed": true,
38 | "name": "conditionId",
39 | "type": "bytes32"
40 | },
41 | {
42 | "indexed": true,
43 | "name": "oracle",
44 | "type": "address"
45 | },
46 | {
47 | "indexed": true,
48 | "name": "questionId",
49 | "type": "bytes32"
50 | },
51 | {
52 | "indexed": false,
53 | "name": "outcomeSlotCount",
54 | "type": "uint256"
55 | },
56 | {
57 | "indexed": false,
58 | "name": "payoutNumerators",
59 | "type": "uint256[]"
60 | }
61 | ],
62 | "name": "ConditionResolution",
63 | "type": "event",
64 | "signature": "0xb44d84d3289691f71497564b85d4233648d9dbae8cbdbb4329f301c3a0185894"
65 | },
66 | "0x2e6bb91f8cbcda0c93623c54d0403a43514fabc40084ec96b6d5379a74786298": {
67 | "anonymous": false,
68 | "inputs": [
69 | {
70 | "indexed": true,
71 | "name": "stakeholder",
72 | "type": "address"
73 | },
74 | {
75 | "indexed": false,
76 | "name": "collateralToken",
77 | "type": "address"
78 | },
79 | {
80 | "indexed": true,
81 | "name": "parentCollectionId",
82 | "type": "bytes32"
83 | },
84 | {
85 | "indexed": true,
86 | "name": "conditionId",
87 | "type": "bytes32"
88 | },
89 | {
90 | "indexed": false,
91 | "name": "partition",
92 | "type": "uint256[]"
93 | },
94 | {
95 | "indexed": false,
96 | "name": "amount",
97 | "type": "uint256"
98 | }
99 | ],
100 | "name": "PositionSplit",
101 | "type": "event",
102 | "signature": "0x2e6bb91f8cbcda0c93623c54d0403a43514fabc40084ec96b6d5379a74786298"
103 | },
104 | "0x6f13ca62553fcc2bcd2372180a43949c1e4cebba603901ede2f4e14f36b282ca": {
105 | "anonymous": false,
106 | "inputs": [
107 | {
108 | "indexed": true,
109 | "name": "stakeholder",
110 | "type": "address"
111 | },
112 | {
113 | "indexed": false,
114 | "name": "collateralToken",
115 | "type": "address"
116 | },
117 | {
118 | "indexed": true,
119 | "name": "parentCollectionId",
120 | "type": "bytes32"
121 | },
122 | {
123 | "indexed": true,
124 | "name": "conditionId",
125 | "type": "bytes32"
126 | },
127 | {
128 | "indexed": false,
129 | "name": "partition",
130 | "type": "uint256[]"
131 | },
132 | {
133 | "indexed": false,
134 | "name": "amount",
135 | "type": "uint256"
136 | }
137 | ],
138 | "name": "PositionsMerge",
139 | "type": "event",
140 | "signature": "0x6f13ca62553fcc2bcd2372180a43949c1e4cebba603901ede2f4e14f36b282ca"
141 | },
142 | "0x2682012a4a4f1973119f1c9b90745d1bd91fa2bab387344f044cb3586864d18d": {
143 | "anonymous": false,
144 | "inputs": [
145 | {
146 | "indexed": true,
147 | "name": "redeemer",
148 | "type": "address"
149 | },
150 | {
151 | "indexed": true,
152 | "name": "collateralToken",
153 | "type": "address"
154 | },
155 | {
156 | "indexed": true,
157 | "name": "parentCollectionId",
158 | "type": "bytes32"
159 | },
160 | {
161 | "indexed": false,
162 | "name": "conditionId",
163 | "type": "bytes32"
164 | },
165 | {
166 | "indexed": false,
167 | "name": "indexSets",
168 | "type": "uint256[]"
169 | },
170 | {
171 | "indexed": false,
172 | "name": "payout",
173 | "type": "uint256"
174 | }
175 | ],
176 | "name": "PayoutRedemption",
177 | "type": "event",
178 | "signature": "0x2682012a4a4f1973119f1c9b90745d1bd91fa2bab387344f044cb3586864d18d"
179 | },
180 | "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": {
181 | "anonymous": false,
182 | "inputs": [
183 | {
184 | "indexed": true,
185 | "name": "operator",
186 | "type": "address"
187 | },
188 | {
189 | "indexed": true,
190 | "name": "from",
191 | "type": "address"
192 | },
193 | {
194 | "indexed": true,
195 | "name": "to",
196 | "type": "address"
197 | },
198 | {
199 | "indexed": false,
200 | "name": "id",
201 | "type": "uint256"
202 | },
203 | {
204 | "indexed": false,
205 | "name": "value",
206 | "type": "uint256"
207 | }
208 | ],
209 | "name": "TransferSingle",
210 | "type": "event",
211 | "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"
212 | },
213 | "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb": {
214 | "anonymous": false,
215 | "inputs": [
216 | {
217 | "indexed": true,
218 | "name": "operator",
219 | "type": "address"
220 | },
221 | {
222 | "indexed": true,
223 | "name": "from",
224 | "type": "address"
225 | },
226 | {
227 | "indexed": true,
228 | "name": "to",
229 | "type": "address"
230 | },
231 | {
232 | "indexed": false,
233 | "name": "ids",
234 | "type": "uint256[]"
235 | },
236 | {
237 | "indexed": false,
238 | "name": "values",
239 | "type": "uint256[]"
240 | }
241 | ],
242 | "name": "TransferBatch",
243 | "type": "event",
244 | "signature": "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb"
245 | },
246 | "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": {
247 | "anonymous": false,
248 | "inputs": [
249 | {
250 | "indexed": true,
251 | "name": "owner",
252 | "type": "address"
253 | },
254 | {
255 | "indexed": true,
256 | "name": "operator",
257 | "type": "address"
258 | },
259 | {
260 | "indexed": false,
261 | "name": "approved",
262 | "type": "bool"
263 | }
264 | ],
265 | "name": "ApprovalForAll",
266 | "type": "event",
267 | "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31"
268 | },
269 | "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": {
270 | "anonymous": false,
271 | "inputs": [
272 | {
273 | "indexed": false,
274 | "name": "value",
275 | "type": "string"
276 | },
277 | {
278 | "indexed": true,
279 | "name": "id",
280 | "type": "uint256"
281 | }
282 | ],
283 | "name": "URI",
284 | "type": "event",
285 | "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b"
286 | }
287 | },
288 | "links": {},
289 | "address": "0xC59b0e4De5F1248C1140964E0fF287B192407E0C",
290 | "transactionHash": "0x7e10ef2a2116a46153d52ad50d6428fa9f4b19c36c803ba2f707cd70b7434348"
291 | },
292 | "4": {
293 | "events": {
294 | "0xab3760c3bd2bb38b5bcf54dc79802ed67338b4cf29f3054ded67ed24661e4177": {
295 | "anonymous": false,
296 | "inputs": [
297 | {
298 | "indexed": true,
299 | "name": "conditionId",
300 | "type": "bytes32"
301 | },
302 | {
303 | "indexed": true,
304 | "name": "oracle",
305 | "type": "address"
306 | },
307 | {
308 | "indexed": true,
309 | "name": "questionId",
310 | "type": "bytes32"
311 | },
312 | {
313 | "indexed": false,
314 | "name": "outcomeSlotCount",
315 | "type": "uint256"
316 | }
317 | ],
318 | "name": "ConditionPreparation",
319 | "type": "event",
320 | "signature": "0xab3760c3bd2bb38b5bcf54dc79802ed67338b4cf29f3054ded67ed24661e4177"
321 | },
322 | "0xb44d84d3289691f71497564b85d4233648d9dbae8cbdbb4329f301c3a0185894": {
323 | "anonymous": false,
324 | "inputs": [
325 | {
326 | "indexed": true,
327 | "name": "conditionId",
328 | "type": "bytes32"
329 | },
330 | {
331 | "indexed": true,
332 | "name": "oracle",
333 | "type": "address"
334 | },
335 | {
336 | "indexed": true,
337 | "name": "questionId",
338 | "type": "bytes32"
339 | },
340 | {
341 | "indexed": false,
342 | "name": "outcomeSlotCount",
343 | "type": "uint256"
344 | },
345 | {
346 | "indexed": false,
347 | "name": "payoutNumerators",
348 | "type": "uint256[]"
349 | }
350 | ],
351 | "name": "ConditionResolution",
352 | "type": "event",
353 | "signature": "0xb44d84d3289691f71497564b85d4233648d9dbae8cbdbb4329f301c3a0185894"
354 | },
355 | "0x2e6bb91f8cbcda0c93623c54d0403a43514fabc40084ec96b6d5379a74786298": {
356 | "anonymous": false,
357 | "inputs": [
358 | {
359 | "indexed": true,
360 | "name": "stakeholder",
361 | "type": "address"
362 | },
363 | {
364 | "indexed": false,
365 | "name": "collateralToken",
366 | "type": "address"
367 | },
368 | {
369 | "indexed": true,
370 | "name": "parentCollectionId",
371 | "type": "bytes32"
372 | },
373 | {
374 | "indexed": true,
375 | "name": "conditionId",
376 | "type": "bytes32"
377 | },
378 | {
379 | "indexed": false,
380 | "name": "partition",
381 | "type": "uint256[]"
382 | },
383 | {
384 | "indexed": false,
385 | "name": "amount",
386 | "type": "uint256"
387 | }
388 | ],
389 | "name": "PositionSplit",
390 | "type": "event",
391 | "signature": "0x2e6bb91f8cbcda0c93623c54d0403a43514fabc40084ec96b6d5379a74786298"
392 | },
393 | "0x6f13ca62553fcc2bcd2372180a43949c1e4cebba603901ede2f4e14f36b282ca": {
394 | "anonymous": false,
395 | "inputs": [
396 | {
397 | "indexed": true,
398 | "name": "stakeholder",
399 | "type": "address"
400 | },
401 | {
402 | "indexed": false,
403 | "name": "collateralToken",
404 | "type": "address"
405 | },
406 | {
407 | "indexed": true,
408 | "name": "parentCollectionId",
409 | "type": "bytes32"
410 | },
411 | {
412 | "indexed": true,
413 | "name": "conditionId",
414 | "type": "bytes32"
415 | },
416 | {
417 | "indexed": false,
418 | "name": "partition",
419 | "type": "uint256[]"
420 | },
421 | {
422 | "indexed": false,
423 | "name": "amount",
424 | "type": "uint256"
425 | }
426 | ],
427 | "name": "PositionsMerge",
428 | "type": "event",
429 | "signature": "0x6f13ca62553fcc2bcd2372180a43949c1e4cebba603901ede2f4e14f36b282ca"
430 | },
431 | "0x2682012a4a4f1973119f1c9b90745d1bd91fa2bab387344f044cb3586864d18d": {
432 | "anonymous": false,
433 | "inputs": [
434 | {
435 | "indexed": true,
436 | "name": "redeemer",
437 | "type": "address"
438 | },
439 | {
440 | "indexed": true,
441 | "name": "collateralToken",
442 | "type": "address"
443 | },
444 | {
445 | "indexed": true,
446 | "name": "parentCollectionId",
447 | "type": "bytes32"
448 | },
449 | {
450 | "indexed": false,
451 | "name": "conditionId",
452 | "type": "bytes32"
453 | },
454 | {
455 | "indexed": false,
456 | "name": "indexSets",
457 | "type": "uint256[]"
458 | },
459 | {
460 | "indexed": false,
461 | "name": "payout",
462 | "type": "uint256"
463 | }
464 | ],
465 | "name": "PayoutRedemption",
466 | "type": "event",
467 | "signature": "0x2682012a4a4f1973119f1c9b90745d1bd91fa2bab387344f044cb3586864d18d"
468 | },
469 | "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": {
470 | "anonymous": false,
471 | "inputs": [
472 | {
473 | "indexed": true,
474 | "name": "operator",
475 | "type": "address"
476 | },
477 | {
478 | "indexed": true,
479 | "name": "from",
480 | "type": "address"
481 | },
482 | {
483 | "indexed": true,
484 | "name": "to",
485 | "type": "address"
486 | },
487 | {
488 | "indexed": false,
489 | "name": "id",
490 | "type": "uint256"
491 | },
492 | {
493 | "indexed": false,
494 | "name": "value",
495 | "type": "uint256"
496 | }
497 | ],
498 | "name": "TransferSingle",
499 | "type": "event",
500 | "signature": "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"
501 | },
502 | "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb": {
503 | "anonymous": false,
504 | "inputs": [
505 | {
506 | "indexed": true,
507 | "name": "operator",
508 | "type": "address"
509 | },
510 | {
511 | "indexed": true,
512 | "name": "from",
513 | "type": "address"
514 | },
515 | {
516 | "indexed": true,
517 | "name": "to",
518 | "type": "address"
519 | },
520 | {
521 | "indexed": false,
522 | "name": "ids",
523 | "type": "uint256[]"
524 | },
525 | {
526 | "indexed": false,
527 | "name": "values",
528 | "type": "uint256[]"
529 | }
530 | ],
531 | "name": "TransferBatch",
532 | "type": "event",
533 | "signature": "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb"
534 | },
535 | "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": {
536 | "anonymous": false,
537 | "inputs": [
538 | {
539 | "indexed": true,
540 | "name": "owner",
541 | "type": "address"
542 | },
543 | {
544 | "indexed": true,
545 | "name": "operator",
546 | "type": "address"
547 | },
548 | {
549 | "indexed": false,
550 | "name": "approved",
551 | "type": "bool"
552 | }
553 | ],
554 | "name": "ApprovalForAll",
555 | "type": "event",
556 | "signature": "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31"
557 | },
558 | "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": {
559 | "anonymous": false,
560 | "inputs": [
561 | {
562 | "indexed": false,
563 | "name": "value",
564 | "type": "string"
565 | },
566 | {
567 | "indexed": true,
568 | "name": "id",
569 | "type": "uint256"
570 | }
571 | ],
572 | "name": "URI",
573 | "type": "event",
574 | "signature": "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b"
575 | }
576 | },
577 | "links": {},
578 | "address": "0x36bede640D19981A82090519bC1626249984c908",
579 | "transactionHash": "0x7db87634594160ae8acc745d38329f6e8902ae8cd98a9f1b10afb606b41c436b"
580 | },
581 | "100": {
582 | "events": {
583 | "0xab3760c3bd2bb38b5bcf54dc79802ed67338b4cf29f3054ded67ed24661e4177": {
584 | "anonymous": false,
585 | "inputs": [
586 | {
587 | "indexed": true,
588 | "name": "conditionId",
589 | "type": "bytes32"
590 | },
591 | {
592 | "indexed": true,
593 | "name": "oracle",
594 | "type": "address"
595 | },
596 | {
597 | "indexed": true,
598 | "name": "questionId",
599 | "type": "bytes32"
600 | },
601 | {
602 | "indexed": false,
603 | "name": "outcomeSlotCount",
604 | "type": "uint256"
605 | }
606 | ],
607 | "name": "ConditionPreparation",
608 | "type": "event"
609 | },
610 | "0xb44d84d3289691f71497564b85d4233648d9dbae8cbdbb4329f301c3a0185894": {
611 | "anonymous": false,
612 | "inputs": [
613 | {
614 | "indexed": true,
615 | "name": "conditionId",
616 | "type": "bytes32"
617 | },
618 | {
619 | "indexed": true,
620 | "name": "oracle",
621 | "type": "address"
622 | },
623 | {
624 | "indexed": true,
625 | "name": "questionId",
626 | "type": "bytes32"
627 | },
628 | {
629 | "indexed": false,
630 | "name": "outcomeSlotCount",
631 | "type": "uint256"
632 | },
633 | {
634 | "indexed": false,
635 | "name": "payoutNumerators",
636 | "type": "uint256[]"
637 | }
638 | ],
639 | "name": "ConditionResolution",
640 | "type": "event"
641 | },
642 | "0x2e6bb91f8cbcda0c93623c54d0403a43514fabc40084ec96b6d5379a74786298": {
643 | "anonymous": false,
644 | "inputs": [
645 | {
646 | "indexed": true,
647 | "name": "stakeholder",
648 | "type": "address"
649 | },
650 | {
651 | "indexed": false,
652 | "name": "collateralToken",
653 | "type": "address"
654 | },
655 | {
656 | "indexed": true,
657 | "name": "parentCollectionId",
658 | "type": "bytes32"
659 | },
660 | {
661 | "indexed": true,
662 | "name": "conditionId",
663 | "type": "bytes32"
664 | },
665 | {
666 | "indexed": false,
667 | "name": "partition",
668 | "type": "uint256[]"
669 | },
670 | {
671 | "indexed": false,
672 | "name": "amount",
673 | "type": "uint256"
674 | }
675 | ],
676 | "name": "PositionSplit",
677 | "type": "event"
678 | },
679 | "0x6f13ca62553fcc2bcd2372180a43949c1e4cebba603901ede2f4e14f36b282ca": {
680 | "anonymous": false,
681 | "inputs": [
682 | {
683 | "indexed": true,
684 | "name": "stakeholder",
685 | "type": "address"
686 | },
687 | {
688 | "indexed": false,
689 | "name": "collateralToken",
690 | "type": "address"
691 | },
692 | {
693 | "indexed": true,
694 | "name": "parentCollectionId",
695 | "type": "bytes32"
696 | },
697 | {
698 | "indexed": true,
699 | "name": "conditionId",
700 | "type": "bytes32"
701 | },
702 | {
703 | "indexed": false,
704 | "name": "partition",
705 | "type": "uint256[]"
706 | },
707 | {
708 | "indexed": false,
709 | "name": "amount",
710 | "type": "uint256"
711 | }
712 | ],
713 | "name": "PositionsMerge",
714 | "type": "event"
715 | },
716 | "0x2682012a4a4f1973119f1c9b90745d1bd91fa2bab387344f044cb3586864d18d": {
717 | "anonymous": false,
718 | "inputs": [
719 | {
720 | "indexed": true,
721 | "name": "redeemer",
722 | "type": "address"
723 | },
724 | {
725 | "indexed": true,
726 | "name": "collateralToken",
727 | "type": "address"
728 | },
729 | {
730 | "indexed": true,
731 | "name": "parentCollectionId",
732 | "type": "bytes32"
733 | },
734 | {
735 | "indexed": false,
736 | "name": "conditionId",
737 | "type": "bytes32"
738 | },
739 | {
740 | "indexed": false,
741 | "name": "indexSets",
742 | "type": "uint256[]"
743 | },
744 | {
745 | "indexed": false,
746 | "name": "payout",
747 | "type": "uint256"
748 | }
749 | ],
750 | "name": "PayoutRedemption",
751 | "type": "event"
752 | },
753 | "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": {
754 | "anonymous": false,
755 | "inputs": [
756 | {
757 | "indexed": true,
758 | "name": "operator",
759 | "type": "address"
760 | },
761 | {
762 | "indexed": true,
763 | "name": "from",
764 | "type": "address"
765 | },
766 | {
767 | "indexed": true,
768 | "name": "to",
769 | "type": "address"
770 | },
771 | {
772 | "indexed": false,
773 | "name": "id",
774 | "type": "uint256"
775 | },
776 | {
777 | "indexed": false,
778 | "name": "value",
779 | "type": "uint256"
780 | }
781 | ],
782 | "name": "TransferSingle",
783 | "type": "event"
784 | },
785 | "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb": {
786 | "anonymous": false,
787 | "inputs": [
788 | {
789 | "indexed": true,
790 | "name": "operator",
791 | "type": "address"
792 | },
793 | {
794 | "indexed": true,
795 | "name": "from",
796 | "type": "address"
797 | },
798 | {
799 | "indexed": true,
800 | "name": "to",
801 | "type": "address"
802 | },
803 | {
804 | "indexed": false,
805 | "name": "ids",
806 | "type": "uint256[]"
807 | },
808 | {
809 | "indexed": false,
810 | "name": "values",
811 | "type": "uint256[]"
812 | }
813 | ],
814 | "name": "TransferBatch",
815 | "type": "event"
816 | },
817 | "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": {
818 | "anonymous": false,
819 | "inputs": [
820 | {
821 | "indexed": true,
822 | "name": "owner",
823 | "type": "address"
824 | },
825 | {
826 | "indexed": true,
827 | "name": "operator",
828 | "type": "address"
829 | },
830 | {
831 | "indexed": false,
832 | "name": "approved",
833 | "type": "bool"
834 | }
835 | ],
836 | "name": "ApprovalForAll",
837 | "type": "event"
838 | },
839 | "0x6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b": {
840 | "anonymous": false,
841 | "inputs": [
842 | {
843 | "indexed": false,
844 | "name": "value",
845 | "type": "string"
846 | },
847 | {
848 | "indexed": true,
849 | "name": "id",
850 | "type": "uint256"
851 | }
852 | ],
853 | "name": "URI",
854 | "type": "event"
855 | }
856 | },
857 | "links": {},
858 | "address": "0xCeAfDD6bc0bEF976fdCd1112955828E00543c0Ce",
859 | "transactionHash": "0x8135a49a80eb20e8932bf8b39a55e34229f992c298c4cdfbb62feffa088001c3"
860 | }
861 | },
862 | "Migrations": {
863 | "1": {
864 | "events": {},
865 | "links": {},
866 | "address": "0x03Ce050DAEB28021086Bf8e9B7843d6212c05F7B",
867 | "transactionHash": "0x0ac0453238d2b4bb9ba0e393bb446c6453588fff9f259125c9a6cec4b0465336"
868 | },
869 | "4": {
870 | "events": {},
871 | "links": {},
872 | "address": "0x06Fe100857e0cFE8B818E7476013C0203292D2A4",
873 | "transactionHash": "0xa424fff196b8c0521bd902036a894fd9b6a3b9d10cc63533ae8bbfec4a393134"
874 | },
875 | "100": {
876 | "events": {},
877 | "links": {},
878 | "address": "0x099efD8CD80b344702fbFFFA81dcAeaeE9B91FF4",
879 | "transactionHash": "0x0b76248d36f0e1e7d8b1cf0ead1c57e86c5d026ddeb016ced4da5c8977577901"
880 | }
881 | }
882 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gnosis.pm/conditional-tokens-contracts",
3 | "version": "1.0.3",
4 | "description": "Collection of smart contracts for the conditional tokens",
5 | "scripts": {
6 | "lint": "eslint .",
7 | "develop": "truffle develop",
8 | "compile": "truffle compile",
9 | "migrate": "truffle migrate",
10 | "networks": "truffle networks",
11 | "test": "truffle test",
12 | "lint-contracts": "solium -d contracts/",
13 | "injectnetinfo": "tnt iN",
14 | "extractnetinfo": "tnt eN",
15 | "resetnetinfo": "truffle networks --clean && tnt iN",
16 | "prepack": "eslint . && npm run lint-contracts && truffle compile && truffle networks --clean && tnt iN && npx tnt cleanBuild"
17 | },
18 | "keywords": [
19 | "Ethereum",
20 | "Gnosis",
21 | "Prediction-Market",
22 | "Solidity",
23 | "Truffle"
24 | ],
25 | "author": "Gnosis (https://gnosis.io)",
26 | "license": "LGPL-3.0",
27 | "dependencies": {
28 | "openzeppelin-solidity": "2.3.0"
29 | },
30 | "devDependencies": {
31 | "@codechecks/client": "^0.1.9",
32 | "@gnosis.pm/safe-contracts": "github:gnosis/safe-contracts",
33 | "@gnosis.pm/truffle-nice-tools": "^1.1.3",
34 | "chai": "^4.2.0",
35 | "chai-as-promised": "^7.1.1",
36 | "eslint": "^6.3.0",
37 | "eslint-config-prettier": "^6.3.0",
38 | "eslint-plugin-prettier": "^3.1.0",
39 | "eth-gas-reporter": "^0.2.11",
40 | "eth-sig-util": "^2.4.4",
41 | "ethlint": "^1.2.4",
42 | "husky": "^4.0.2",
43 | "lint-staged": "^9.2.5",
44 | "lodash": "^4.17.15",
45 | "npm-prepublish": "^1.2.3",
46 | "openzeppelin-test-helpers": "^0.5.0",
47 | "prettier": "1.19.1",
48 | "run-with-testrpc": "^0.3.1",
49 | "truffle": "^5.0.36",
50 | "truffle-flattener": "^1.4.4",
51 | "truffle-hdwallet-provider": "^1.0.17",
52 | "web3": "^2.0.0-alpha.1"
53 | },
54 | "repository": {
55 | "type": "git",
56 | "url": "git+https://github.com/gnosis/conditional-tokens-contracts.git"
57 | },
58 | "bugs": {
59 | "url": "https://github.com/gnosis/conditional-tokens-contracts/issues"
60 | },
61 | "homepage": "https://github.com/gnosis/conditional-tokens-contracts#readme",
62 | "husky": {
63 | "hooks": {
64 | "pre-commit": "lint-staged"
65 | }
66 | },
67 | "lint-staged": {
68 | "*.js": [
69 | "eslint --fix",
70 | "git add"
71 | ]
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/test/DefaultCallbackHandler.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import { DefaultCallbackHandler } from "@gnosis.pm/safe-contracts/contracts/handler/DefaultCallbackHandler.sol";
4 |
--------------------------------------------------------------------------------
/test/ERC1155Mock.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import { ERC1155 } from "../contracts/ERC1155/ERC1155.sol";
4 |
5 | /**
6 | * @title ERC1155Mock
7 | * This mock just allows minting for testing purposes
8 | */
9 | contract ERC1155Mock is ERC1155 {
10 | function mint(address to, uint256 id, uint256 value, bytes memory data) public {
11 | _mint(to, id, value, data);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/Forwarder.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import { ERC1155TokenReceiver } from "../contracts/ERC1155/ERC1155TokenReceiver.sol";
4 |
5 | contract Forwarder is ERC1155TokenReceiver {
6 | function call(address to, bytes calldata data) external {
7 | (bool success, bytes memory retData) = to.call(data);
8 | require(success, string(retData));
9 | }
10 |
11 | function onERC1155Received(
12 | address /* operator */,
13 | address /* from */,
14 | uint256 /* id */,
15 | uint256 /* value */,
16 | bytes calldata /* data */
17 | )
18 | external
19 | returns(bytes4)
20 | {
21 | return this.onERC1155Received.selector;
22 | }
23 |
24 | function onERC1155BatchReceived(
25 | address /* operator */,
26 | address /* from */,
27 | uint256[] calldata /* ids */,
28 | uint256[] calldata /* values */,
29 | bytes calldata /* data */
30 | )
31 | external
32 | returns(bytes4)
33 | {
34 | return this.onERC1155BatchReceived.selector;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/GnosisSafe.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.0;
2 |
3 | import { GnosisSafe } from "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
4 |
--------------------------------------------------------------------------------
/test/MockCoin.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.5.1;
2 | import { ERC20Mintable } from "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol";
3 |
4 | contract MockCoin is ERC20Mintable {}
5 |
--------------------------------------------------------------------------------
/test/test-conditional-tokens.js:
--------------------------------------------------------------------------------
1 | const ethSigUtil = require("eth-sig-util");
2 |
3 | const { expectEvent, expectRevert } = require("openzeppelin-test-helpers");
4 | const { toBN, randomHex } = web3.utils;
5 | const {
6 | getConditionId,
7 | getCollectionId,
8 | combineCollectionIds,
9 | getPositionId
10 | } = require("../utils/id-helpers")(web3.utils);
11 |
12 | const ConditionalTokens = artifacts.require("ConditionalTokens");
13 | const ERC20Mintable = artifacts.require("MockCoin");
14 | const Forwarder = artifacts.require("Forwarder");
15 | const DefaultCallbackHandler = artifacts.require("DefaultCallbackHandler.sol");
16 | const GnosisSafe = artifacts.require("GnosisSafe");
17 |
18 | const NULL_BYTES32 = `0x${"0".repeat(64)}`;
19 |
20 | contract("ConditionalTokens", function(accounts) {
21 | const [
22 | minter,
23 | oracle,
24 | notOracle,
25 | eoaTrader,
26 | fwdExecutor,
27 | safeExecutor,
28 | counterparty
29 | ] = accounts;
30 |
31 | beforeEach("deploy ConditionalTokens", async function() {
32 | this.conditionalTokens = await ConditionalTokens.new();
33 | });
34 |
35 | describe("prepareCondition", function() {
36 | it("should not be able to prepare a condition with no outcome slots", async function() {
37 | const questionId = randomHex(32);
38 | const outcomeSlotCount = 0;
39 |
40 | await expectRevert(
41 | this.conditionalTokens.prepareCondition(
42 | oracle,
43 | questionId,
44 | outcomeSlotCount
45 | ),
46 | "there should be more than one outcome slot"
47 | );
48 | });
49 |
50 | it("should not be able to prepare a condition with just one outcome slots", async function() {
51 | const questionId = randomHex(32);
52 | const outcomeSlotCount = 1;
53 |
54 | await expectRevert(
55 | this.conditionalTokens.prepareCondition(
56 | oracle,
57 | questionId,
58 | outcomeSlotCount
59 | ),
60 | "there should be more than one outcome slot"
61 | );
62 | });
63 |
64 | context("with valid parameters", function() {
65 | const questionId = randomHex(32);
66 | const outcomeSlotCount = toBN(256);
67 |
68 | const conditionId = getConditionId(oracle, questionId, outcomeSlotCount);
69 |
70 | beforeEach(async function() {
71 | ({ logs: this.logs } = await this.conditionalTokens.prepareCondition(
72 | oracle,
73 | questionId,
74 | outcomeSlotCount
75 | ));
76 | });
77 |
78 | it("should emit an ConditionPreparation event", async function() {
79 | expectEvent.inLogs(this.logs, "ConditionPreparation", {
80 | conditionId,
81 | oracle,
82 | questionId,
83 | outcomeSlotCount
84 | });
85 | });
86 |
87 | it("should make outcome slot count available via getOutcomeSlotCount", async function() {
88 | (
89 | await this.conditionalTokens.getOutcomeSlotCount(conditionId)
90 | ).should.be.bignumber.equal(outcomeSlotCount);
91 | });
92 |
93 | it("should leave payout denominator unset", async function() {
94 | (
95 | await this.conditionalTokens.payoutDenominator(conditionId)
96 | ).should.be.bignumber.equal("0");
97 | });
98 |
99 | it("should not be able to prepare the same condition more than once", async function() {
100 | await expectRevert(
101 | this.conditionalTokens.prepareCondition(
102 | oracle,
103 | questionId,
104 | outcomeSlotCount
105 | ),
106 | "condition already prepared"
107 | );
108 | });
109 | });
110 | });
111 |
112 | describe("splitting and merging", function() {
113 | function shouldSplitAndMergePositions(trader) {
114 | const questionId = randomHex(32);
115 | const outcomeSlotCount = toBN(2);
116 |
117 | const conditionId = getConditionId(oracle, questionId, outcomeSlotCount);
118 |
119 | const collateralTokenCount = toBN(1e19);
120 | const splitAmount = toBN(4e18);
121 | const mergeAmount = toBN(3e18);
122 |
123 | function shouldWorkWithSplittingAndMerging({
124 | prepareTokens,
125 | doSplit,
126 | doMerge,
127 | doRedeem,
128 | collateralBalanceOf,
129 | getPositionForCollection,
130 | getExpectedEventCollateralProperties,
131 | deeperTests
132 | }) {
133 | beforeEach(prepareTokens);
134 |
135 | it("should not split on unprepared conditions", async function() {
136 | await doSplit.call(
137 | this,
138 | conditionId,
139 | [0b01, 0b10],
140 | splitAmount
141 | ).should.be.rejected;
142 | });
143 |
144 | context("with a condition prepared", async function() {
145 | beforeEach(async function() {
146 | await this.conditionalTokens.prepareCondition(
147 | oracle,
148 | questionId,
149 | outcomeSlotCount
150 | );
151 | });
152 |
153 | it("should not split if given index sets aren't disjoint", async function() {
154 | await doSplit.call(
155 | this,
156 | conditionId,
157 | [0b11, 0b10],
158 | splitAmount
159 | ).should.be.rejected;
160 | });
161 |
162 | it("should not split if partitioning more than condition's outcome slots", async function() {
163 | await doSplit.call(
164 | this,
165 | conditionId,
166 | [0b001, 0b010, 0b100],
167 | splitAmount
168 | ).should.be.rejected;
169 | });
170 |
171 | it("should not split if given a singleton partition", async function() {
172 | await doSplit.call(
173 | this,
174 | conditionId,
175 | [0b11],
176 | splitAmount
177 | ).should.be.rejected;
178 | });
179 |
180 | it("should not split if given an incomplete singleton partition", async function() {
181 | await doSplit.call(
182 | this,
183 | conditionId,
184 | [0b01],
185 | splitAmount
186 | ).should.be.rejected;
187 | });
188 |
189 | context("with valid split", function() {
190 | const partition = [0b01, 0b10];
191 |
192 | beforeEach(async function() {
193 | ({ tx: this.splitTx } = await doSplit.call(
194 | this,
195 | conditionId,
196 | partition,
197 | splitAmount
198 | ));
199 | });
200 |
201 | it("should emit a PositionSplit event", async function() {
202 | await expectEvent.inTransaction(
203 | this.splitTx,
204 | ConditionalTokens,
205 | "PositionSplit",
206 | Object.assign(
207 | {
208 | stakeholder: trader.address,
209 | parentCollectionId: NULL_BYTES32,
210 | conditionId,
211 | // partition,
212 | amount: splitAmount
213 | },
214 | getExpectedEventCollateralProperties.call(this)
215 | )
216 | );
217 | });
218 |
219 | it("should transfer split collateral from trader", async function() {
220 | (
221 | await collateralBalanceOf.call(this, trader.address)
222 | ).should.be.bignumber.equal(
223 | collateralTokenCount.sub(splitAmount)
224 | );
225 | (
226 | await collateralBalanceOf.call(
227 | this,
228 | this.conditionalTokens.address
229 | )
230 | ).should.be.bignumber.equal(splitAmount);
231 | });
232 |
233 | it("should mint amounts in positions associated with partition", async function() {
234 | for (const indexSet of partition) {
235 | const positionId = getPositionForCollection.call(
236 | this,
237 | getCollectionId(conditionId, indexSet)
238 | );
239 |
240 | (
241 | await this.conditionalTokens.balanceOf(
242 | trader.address,
243 | positionId
244 | )
245 | ).should.be.bignumber.equal(splitAmount);
246 | }
247 | });
248 |
249 | it("should not merge if amount exceeds balances in to-be-merged positions", async function() {
250 | await doMerge.call(
251 | this,
252 | conditionId,
253 | partition,
254 | splitAmount.addn(1)
255 | ).should.be.rejected;
256 | });
257 |
258 | context("with valid merge", function() {
259 | beforeEach(async function() {
260 | ({ tx: this.mergeTx } = await doMerge.call(
261 | this,
262 | conditionId,
263 | partition,
264 | mergeAmount
265 | ));
266 | });
267 |
268 | it("should emit a PositionsMerge event", async function() {
269 | await expectEvent.inTransaction(
270 | this.mergeTx,
271 | ConditionalTokens,
272 | "PositionsMerge",
273 | Object.assign(
274 | {
275 | stakeholder: trader.address,
276 | parentCollectionId: NULL_BYTES32,
277 | conditionId,
278 | // partition,
279 | amount: mergeAmount
280 | },
281 | getExpectedEventCollateralProperties.call(this)
282 | )
283 | );
284 | });
285 |
286 | it("should transfer split collateral back to trader", async function() {
287 | (
288 | await collateralBalanceOf.call(this, trader.address)
289 | ).should.be.bignumber.equal(
290 | collateralTokenCount.sub(splitAmount).add(mergeAmount)
291 | );
292 | (
293 | await collateralBalanceOf.call(
294 | this,
295 | this.conditionalTokens.address
296 | )
297 | ).should.be.bignumber.equal(splitAmount.sub(mergeAmount));
298 | });
299 |
300 | it("should burn amounts in positions associated with partition", async function() {
301 | for (const indexSet of partition) {
302 | const positionId = getPositionForCollection.call(
303 | this,
304 | getCollectionId(conditionId, indexSet)
305 | );
306 |
307 | (
308 | await this.conditionalTokens.balanceOf(
309 | trader.address,
310 | positionId
311 | )
312 | ).should.be.bignumber.equal(splitAmount.sub(mergeAmount));
313 | }
314 | });
315 | });
316 |
317 | describe("transferring, reporting, and redeeming", function() {
318 | const transferAmount = toBN(1e18);
319 | const payoutNumerators = [toBN(3), toBN(7)];
320 |
321 | it("should not allow transferring more than split balance", async function() {
322 | const positionId = getPositionForCollection.call(
323 | this,
324 | getCollectionId(conditionId, partition[0])
325 | );
326 |
327 | await trader.execCall(
328 | this.conditionalTokens,
329 | "safeTransferFrom",
330 | trader.address,
331 | counterparty,
332 | positionId,
333 | splitAmount.addn(1),
334 | "0x"
335 | ).should.be.rejected;
336 | });
337 |
338 | it("should not allow reporting by incorrect oracle", async function() {
339 | await expectRevert(
340 | this.conditionalTokens.reportPayouts(
341 | questionId,
342 | payoutNumerators,
343 | { from: notOracle }
344 | ),
345 | "condition not prepared or found"
346 | );
347 | });
348 |
349 | it("should not allow report with wrong questionId", async function() {
350 | const wrongQuestionId = randomHex(32);
351 | await expectRevert(
352 | this.conditionalTokens.reportPayouts(
353 | wrongQuestionId,
354 | payoutNumerators,
355 | { from: oracle }
356 | ),
357 | "condition not prepared or found"
358 | );
359 | });
360 |
361 | it("should not allow report with no slots", async function() {
362 | await expectRevert(
363 | this.conditionalTokens.reportPayouts(questionId, [], {
364 | from: oracle
365 | }),
366 | "there should be more than one outcome slot"
367 | );
368 | });
369 |
370 | it("should not allow report with wrong number of slots", async function() {
371 | await expectRevert(
372 | this.conditionalTokens.reportPayouts(questionId, [2, 3, 5], {
373 | from: oracle
374 | }),
375 | "condition not prepared or found"
376 | );
377 | });
378 |
379 | it("should not allow report with zero payouts in all slots", async function() {
380 | await expectRevert(
381 | this.conditionalTokens.reportPayouts(questionId, [0, 0], {
382 | from: oracle
383 | }),
384 | "payout is all zeroes"
385 | );
386 | });
387 |
388 | context("with valid transfer and oracle report", function() {
389 | beforeEach(async function() {
390 | const positionId = getPositionForCollection.call(
391 | this,
392 | getCollectionId(conditionId, partition[0])
393 | );
394 |
395 | ({ tx: this.transferTx } = await trader.execCall(
396 | this.conditionalTokens,
397 | "safeTransferFrom",
398 | trader.address,
399 | counterparty,
400 | positionId,
401 | transferAmount,
402 | "0x"
403 | ));
404 | ({
405 | logs: this.reportLogs
406 | } = await this.conditionalTokens.reportPayouts(
407 | questionId,
408 | payoutNumerators,
409 | { from: oracle }
410 | ));
411 | });
412 |
413 | it("should not merge if any amount is short", async function() {
414 | await doMerge.call(
415 | this,
416 | conditionId,
417 | partition,
418 | splitAmount
419 | ).should.be.rejected;
420 | });
421 |
422 | it("should emit ConditionResolution event", async function() {
423 | expectEvent.inLogs(this.reportLogs, "ConditionResolution", {
424 | conditionId,
425 | oracle,
426 | questionId,
427 | outcomeSlotCount
428 | });
429 | });
430 |
431 | it("should make reported payout numerators available", async function() {
432 | for (let i = 0; i < payoutNumerators.length; i++) {
433 | (
434 | await this.conditionalTokens.payoutNumerators(
435 | conditionId,
436 | i
437 | )
438 | ).should.be.bignumber.equal(payoutNumerators[i]);
439 | }
440 | });
441 |
442 | describe("redeeming", function() {
443 | const payoutDenominator = payoutNumerators.reduce(
444 | (a, b) => a.add(b),
445 | toBN(0)
446 | );
447 | const payout = [
448 | splitAmount.sub(transferAmount),
449 | splitAmount
450 | ].reduce(
451 | (acc, amount, i) =>
452 | acc.add(
453 | amount.mul(payoutNumerators[i]).div(payoutDenominator)
454 | ),
455 | toBN(0)
456 | );
457 |
458 | beforeEach(async function() {
459 | ({ tx: this.redeemTx } = await doRedeem.call(
460 | this,
461 | conditionId,
462 | partition
463 | ));
464 | });
465 |
466 | it("should emit PayoutRedemption event", async function() {
467 | await expectEvent.inTransaction(
468 | this.redeemTx,
469 | ConditionalTokens,
470 | "PayoutRedemption",
471 | Object.assign(
472 | {
473 | redeemer: trader.address,
474 | parentCollectionId: NULL_BYTES32,
475 | conditionId,
476 | // indexSets: partition,
477 | payout
478 | },
479 | getExpectedEventCollateralProperties.call(this)
480 | )
481 | );
482 | });
483 |
484 | it("should zero out redeemed positions", async function() {
485 | for (const indexSet of partition) {
486 | const positionId = getPositionForCollection.call(
487 | this,
488 | getCollectionId(conditionId, indexSet)
489 | );
490 | (
491 | await this.conditionalTokens.balanceOf(
492 | trader.address,
493 | positionId
494 | )
495 | ).should.be.bignumber.equal("0");
496 | }
497 | });
498 |
499 | it("should not affect other's positions", async function() {
500 | const positionId = getPositionForCollection.call(
501 | this,
502 | getCollectionId(conditionId, partition[0])
503 | );
504 | (
505 | await this.conditionalTokens.balanceOf(
506 | counterparty,
507 | positionId
508 | )
509 | ).should.be.bignumber.equal(transferAmount);
510 | });
511 |
512 | it("should credit payout as collateral", async function() {
513 | (
514 | await collateralBalanceOf.call(this, trader.address)
515 | ).should.be.bignumber.equal(
516 | collateralTokenCount.sub(splitAmount).add(payout)
517 | );
518 | });
519 | });
520 | });
521 | });
522 | });
523 | });
524 |
525 | if (deeperTests)
526 | context("with many conditions prepared", async function() {
527 | const conditions = Array.from({ length: 3 }, () => ({
528 | oracle,
529 | questionId: randomHex(32),
530 | outcomeSlotCount: toBN(4)
531 | }));
532 |
533 | conditions.forEach(condition => {
534 | condition.id = getConditionId(
535 | condition.oracle,
536 | condition.questionId,
537 | condition.outcomeSlotCount
538 | );
539 | });
540 |
541 | beforeEach(async function() {
542 | for (const {
543 | oracle,
544 | questionId,
545 | outcomeSlotCount
546 | } of conditions) {
547 | await this.conditionalTokens.prepareCondition(
548 | oracle,
549 | questionId,
550 | outcomeSlotCount
551 | );
552 | }
553 | });
554 |
555 | context("when trader has collateralized a condition", function() {
556 | const condition = conditions[0];
557 | const { oracle, questionId, outcomeSlotCount } = condition;
558 | const conditionId = condition.id;
559 | const finalReport = [0, 33, 289, 678].map(toBN);
560 | const payoutDenominator = finalReport.reduce((a, b) => a.add(b));
561 | const partition = [0b0111, 0b1000];
562 | const positionIndexSet = partition[0];
563 |
564 | beforeEach(async function() {
565 | await doSplit.call(
566 | this,
567 | conditionId,
568 | partition,
569 | collateralTokenCount
570 | );
571 | await trader.execCall(
572 | this.conditionalTokens,
573 | "safeTransferFrom",
574 | trader.address,
575 | counterparty,
576 | getPositionForCollection.call(
577 | this,
578 | getCollectionId(conditionId, partition[1])
579 | ),
580 | collateralTokenCount,
581 | "0x"
582 | );
583 | });
584 |
585 | context(
586 | "when trader splits to a deeper position with another condition",
587 | function() {
588 | const conditionId2 = conditions[1].id;
589 | const partition2 = [0b0001, 0b0010, 0b1100];
590 | const deepSplitAmount = toBN(4e18);
591 | const parentCollectionId = getCollectionId(
592 | conditionId,
593 | positionIndexSet
594 | );
595 |
596 | beforeEach(async function() {
597 | ({ tx: this.deepSplitTx } = await doSplit.call(
598 | this,
599 | conditionId2,
600 | partition2,
601 | deepSplitAmount,
602 | parentCollectionId
603 | ));
604 | });
605 |
606 | it("combines collection IDs", async function() {
607 | for (const indexSet of partition2) {
608 | (
609 | await this.conditionalTokens.getCollectionId(
610 | parentCollectionId,
611 | conditionId2,
612 | indexSet
613 | )
614 | ).should.be.equal(
615 | combineCollectionIds([
616 | parentCollectionId,
617 | getCollectionId(conditionId2, indexSet)
618 | ])
619 | );
620 | }
621 | });
622 |
623 | it("emits PositionSplit event", async function() {
624 | await expectEvent.inTransaction(
625 | this.deepSplitTx,
626 | ConditionalTokens,
627 | "PositionSplit",
628 | Object.assign(
629 | {
630 | stakeholder: trader.address,
631 | parentCollectionId,
632 | conditionId: conditionId2,
633 | // partition: partition2,
634 | amount: deepSplitAmount
635 | },
636 | getExpectedEventCollateralProperties.call(this)
637 | )
638 | );
639 | });
640 |
641 | it("burns value in the parent position", async function() {
642 | (
643 | await this.conditionalTokens.balanceOf(
644 | trader.address,
645 | getPositionForCollection.call(this, parentCollectionId)
646 | )
647 | ).should.be.bignumber.equal(
648 | collateralTokenCount.sub(deepSplitAmount)
649 | );
650 | });
651 |
652 | it("mints values in the child positions", async function() {
653 | for (const indexSet of partition2) {
654 | const positionId = getPositionForCollection.call(
655 | this,
656 | combineCollectionIds([
657 | parentCollectionId,
658 | getCollectionId(conditionId2, indexSet)
659 | ])
660 | );
661 |
662 | (
663 | await this.conditionalTokens.balanceOf(
664 | trader.address,
665 | positionId
666 | )
667 | ).should.be.bignumber.equal(deepSplitAmount);
668 | }
669 | });
670 | }
671 | );
672 |
673 | context("with valid report", function() {
674 | beforeEach(async function() {
675 | ({
676 | logs: this.reportLogs
677 | } = await this.conditionalTokens.reportPayouts(
678 | questionId,
679 | finalReport,
680 | { from: oracle }
681 | ));
682 | });
683 |
684 | it("should emit ConditionResolution event", function() {
685 | expectEvent.inLogs(this.reportLogs, "ConditionResolution", {
686 | conditionId,
687 | oracle,
688 | questionId,
689 | outcomeSlotCount
690 | });
691 | });
692 |
693 | it("should reflect report via payoutNumerators", async function() {
694 | for (let i = 0; i < finalReport.length; i++) {
695 | (
696 | await this.conditionalTokens.payoutNumerators(
697 | conditionId,
698 | i
699 | )
700 | ).should.be.bignumber.equal(finalReport[i]);
701 | }
702 | });
703 |
704 | it("should not allow an update to the report", async function() {
705 | const badUpdateReport = finalReport.map((x, i) =>
706 | i === 1 ? x : toBN(0)
707 | );
708 | await expectRevert(
709 | this.conditionalTokens.reportPayouts(
710 | questionId,
711 | badUpdateReport,
712 | { from: oracle }
713 | ),
714 | "payout denominator already set"
715 | );
716 | });
717 |
718 | context("with valid redemption", async function() {
719 | const payout = collateralTokenCount
720 | .mul(
721 | finalReport.reduce(
722 | (acc, term, i) =>
723 | positionIndexSet & (1 << i) ? acc.add(term) : acc,
724 | toBN(0)
725 | )
726 | )
727 | .div(payoutDenominator);
728 |
729 | beforeEach(async function() {
730 | ({ tx: this.redeemTx } = await doRedeem.call(
731 | this,
732 | conditionId,
733 | [positionIndexSet]
734 | ));
735 | });
736 |
737 | it("should emit PayoutRedemption event", async function() {
738 | await expectEvent.inTransaction(
739 | this.redeemTx,
740 | ConditionalTokens,
741 | "PayoutRedemption",
742 | Object.assign(
743 | {
744 | redeemer: trader.address,
745 | parentCollectionId: NULL_BYTES32,
746 | conditionId,
747 | // indexSets: partition,
748 | payout
749 | },
750 | getExpectedEventCollateralProperties.call(this)
751 | )
752 | );
753 | });
754 | });
755 | });
756 | });
757 | });
758 | }
759 |
760 | context("with an ERC-20 collateral allowance", function() {
761 | shouldWorkWithSplittingAndMerging({
762 | async prepareTokens() {
763 | this.collateralToken = await ERC20Mintable.new({ from: minter });
764 | await this.collateralToken.mint(
765 | trader.address,
766 | collateralTokenCount,
767 | { from: minter }
768 | );
769 | await trader.execCall(
770 | this.collateralToken,
771 | "approve",
772 | this.conditionalTokens.address,
773 | collateralTokenCount
774 | );
775 | },
776 | async doSplit(conditionId, partition, amount, parentCollectionId) {
777 | return await trader.execCall(
778 | this.conditionalTokens,
779 | "splitPosition",
780 | this.collateralToken.address,
781 | parentCollectionId || NULL_BYTES32,
782 | conditionId,
783 | partition,
784 | amount
785 | );
786 | },
787 | async doMerge(conditionId, partition, amount, parentCollectionId) {
788 | return await trader.execCall(
789 | this.conditionalTokens,
790 | "mergePositions",
791 | this.collateralToken.address,
792 | parentCollectionId || NULL_BYTES32,
793 | conditionId,
794 | partition,
795 | amount
796 | );
797 | },
798 | async doRedeem(conditionId, indexSets, parentCollectionId) {
799 | return await trader.execCall(
800 | this.conditionalTokens,
801 | "redeemPositions",
802 | this.collateralToken.address,
803 | parentCollectionId || NULL_BYTES32,
804 | conditionId,
805 | indexSets
806 | );
807 | },
808 | async collateralBalanceOf(address) {
809 | return await this.collateralToken.balanceOf(address);
810 | },
811 | getPositionForCollection(collectionId) {
812 | return getPositionId(this.collateralToken.address, collectionId);
813 | },
814 | getExpectedEventCollateralProperties() {
815 | return { collateralToken: this.collateralToken.address };
816 | },
817 | deeperTests: true
818 | });
819 | });
820 | }
821 |
822 | context("with an EOA", function() {
823 | shouldSplitAndMergePositions({
824 | address: eoaTrader,
825 | async execCall(contract, method, ...args) {
826 | return await contract[method](...args, { from: eoaTrader });
827 | }
828 | });
829 | });
830 |
831 | context.skip("with a Forwarder", function() {
832 | let trader = {};
833 | before(async function() {
834 | const forwarder = await Forwarder.new();
835 | async function forwardCall(contract, method, ...args) {
836 | // ???: why is reformatting the args necessary here?
837 | args = args.map(arg =>
838 | Array.isArray(arg) ? arg.map(a => a.toString()) : arg.toString()
839 | );
840 |
841 | return await forwarder.call(
842 | contract.address,
843 | contract.contract.methods[method](...args).encodeABI(),
844 | { from: fwdExecutor }
845 | );
846 | }
847 |
848 | trader.address = forwarder.address;
849 | trader.execCall = forwardCall;
850 | });
851 |
852 | shouldSplitAndMergePositions(trader);
853 | });
854 |
855 | context.skip("with a Gnosis Safe", function() {
856 | let trader = {};
857 | before(async function() {
858 | const zeroAccount = `0x${"0".repeat(40)}`;
859 | const safeOwners = Array.from({ length: 2 }, () =>
860 | web3.eth.accounts.create()
861 | );
862 | safeOwners.sort(({ address: a }, { address: b }) =>
863 | a.toLowerCase() < b.toLowerCase() ? -1 : a === b ? 0 : 1
864 | );
865 | const callbackHandler = await DefaultCallbackHandler.new();
866 | const gnosisSafe = await GnosisSafe.new();
867 | await gnosisSafe.setup(
868 | safeOwners.map(({ address }) => address),
869 | safeOwners.length,
870 | zeroAccount,
871 | "0x",
872 | callbackHandler.address,
873 | zeroAccount,
874 | 0,
875 | zeroAccount
876 | );
877 | const gnosisSafeTypedDataCommon = {
878 | types: {
879 | EIP712Domain: [{ name: "verifyingContract", type: "address" }],
880 | SafeTx: [
881 | { name: "to", type: "address" },
882 | { name: "value", type: "uint256" },
883 | { name: "data", type: "bytes" },
884 | { name: "operation", type: "uint8" },
885 | { name: "safeTxGas", type: "uint256" },
886 | { name: "baseGas", type: "uint256" },
887 | { name: "gasPrice", type: "uint256" },
888 | { name: "gasToken", type: "address" },
889 | { name: "refundReceiver", type: "address" },
890 | { name: "nonce", type: "uint256" }
891 | ],
892 | SafeMessage: [{ name: "message", type: "bytes" }]
893 | },
894 | domain: {
895 | verifyingContract: gnosisSafe.address
896 | }
897 | };
898 |
899 | async function gnosisSafeCall(contract, method, ...args) {
900 | const safeOperations = {
901 | CALL: 0,
902 | DELEGATECALL: 1,
903 | CREATE: 2
904 | };
905 | const nonce = await gnosisSafe.nonce();
906 |
907 | // ???: why is reformatting the args necessary here?
908 | args = args.map(arg =>
909 | Array.isArray(arg) ? arg.map(a => a.toString()) : arg.toString()
910 | );
911 |
912 | const txData = contract.contract.methods[method](...args).encodeABI();
913 | const signatures = safeOwners.map(safeOwner =>
914 | ethSigUtil.signTypedData(
915 | Buffer.from(safeOwner.privateKey.replace("0x", ""), "hex"),
916 | {
917 | data: Object.assign(
918 | {
919 | primaryType: "SafeTx",
920 | message: {
921 | to: contract.address,
922 | value: 0,
923 | data: txData,
924 | operation: safeOperations.CALL,
925 | safeTxGas: 0,
926 | baseGas: 0,
927 | gasPrice: 0,
928 | gasToken: zeroAccount,
929 | refundReceiver: zeroAccount,
930 | nonce
931 | }
932 | },
933 | gnosisSafeTypedDataCommon
934 | )
935 | }
936 | )
937 | );
938 | const tx = await gnosisSafe.execTransaction(
939 | contract.address,
940 | 0,
941 | txData,
942 | safeOperations.CALL,
943 | 0,
944 | 0,
945 | 0,
946 | zeroAccount,
947 | zeroAccount,
948 | `0x${signatures.map(s => s.replace("0x", "")).join("")}`,
949 | { from: safeExecutor }
950 | );
951 | if (tx.logs[0] && tx.logs[0].event === "ExecutionFailed")
952 | throw new Error(`Safe transaction ${method}(${args}) failed`);
953 | return tx;
954 | }
955 |
956 | trader.address = gnosisSafe.address;
957 | trader.execCall = gnosisSafeCall;
958 | });
959 |
960 | shouldSplitAndMergePositions(trader);
961 | });
962 | });
963 | });
964 |
--------------------------------------------------------------------------------
/truffle-config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | networks: {
3 | mainnet: {
4 | host: "localhost",
5 | port: 8545,
6 | network_id: "1"
7 | },
8 | ropsten: {
9 | host: "localhost",
10 | port: 8545,
11 | network_id: "3"
12 | },
13 | kovan: {
14 | host: "localhost",
15 | port: 8545,
16 | network_id: "42"
17 | },
18 | rinkeby: {
19 | host: "localhost",
20 | port: 8545,
21 | network_id: "4"
22 | },
23 | xdai: {
24 | host: "localhost",
25 | port: 8545,
26 | network_id: "100"
27 | },
28 | local: {
29 | host: "localhost",
30 | port: 8545,
31 | network_id: "*"
32 | }
33 | },
34 | mocha: {
35 | enableTimeouts: false,
36 | grep: process.env.TEST_GREP,
37 | reporter: "eth-gas-reporter",
38 | reporterOptions: {
39 | currency: "USD",
40 | excludeContracts: ["Migrations"]
41 | }
42 | },
43 | compilers: {
44 | solc: {
45 | version: "0.5.10",
46 | settings: {
47 | optimizer: {
48 | enabled: true
49 | }
50 | }
51 | }
52 | }
53 | };
54 |
55 | try {
56 | require("chai/register-should");
57 | require("chai").use(require("chai-as-promised"));
58 | } catch (e) {
59 | // eslint-disable-next-line no-console
60 | console.log("Skip setting up testing utilities");
61 | }
62 |
63 | try {
64 | const _ = require("lodash");
65 | _.merge(config, require("./truffle-local"));
66 | } catch (e) {
67 | if (e.code === "MODULE_NOT_FOUND" && e.message.includes("truffle-local")) {
68 | // eslint-disable-next-line no-console
69 | console.log("No local truffle config found. Using all defaults...");
70 | } else {
71 | // eslint-disable-next-line no-console
72 | console.warn("Tried processing local config but got error:", e);
73 | }
74 | }
75 |
76 | module.exports = config;
77 |
--------------------------------------------------------------------------------
/utils/id-helpers.js:
--------------------------------------------------------------------------------
1 | // To use this, just import this file and supply it with some web3 utils:
2 | // require("@gnosis.pm/conditional-tokens-contracts/utils/id-helpers")(web3.utils)
3 |
4 | module.exports = function({ BN, toBN, soliditySha3 }) {
5 | function getConditionId(oracle, questionId, outcomeSlotCount) {
6 | return soliditySha3(
7 | { t: "address", v: oracle },
8 | { t: "bytes32", v: questionId },
9 | { t: "uint", v: outcomeSlotCount }
10 | );
11 | }
12 |
13 | const altBN128P = toBN(
14 | "21888242871839275222246405745257275088696311157297823662689037894645226208583"
15 | );
16 | const altBN128PRed = BN.red(altBN128P);
17 | const altBN128B = toBN(3).toRed(altBN128PRed);
18 | const zeroPRed = toBN(0).toRed(altBN128PRed);
19 | const onePRed = toBN(1).toRed(altBN128PRed);
20 | const twoPRed = toBN(2).toRed(altBN128PRed);
21 | const fourPRed = toBN(4).toRed(altBN128PRed);
22 | const oddToggle = toBN(1).ushln(254);
23 |
24 | function getCollectionId(conditionId, indexSet) {
25 | const initHash = soliditySha3(
26 | { t: "bytes32", v: conditionId },
27 | { t: "uint", v: indexSet }
28 | );
29 | const odd = "89abcdef".includes(initHash[2]);
30 |
31 | const x = toBN(initHash).toRed(altBN128PRed);
32 |
33 | let y, yy;
34 | do {
35 | x.redIAdd(onePRed);
36 | yy = x.redSqr();
37 | yy.redIMul(x);
38 | yy = yy.mod(altBN128P);
39 | yy.redIAdd(altBN128B);
40 | y = yy.redSqrt();
41 | } while (!y.redSqr().eq(yy));
42 |
43 | const ecHash = x.fromRed();
44 | if (odd) ecHash.ixor(oddToggle);
45 | return `0x${ecHash.toString(16, 64)}`;
46 | }
47 |
48 | function combineCollectionIds(collectionIds) {
49 | if (Array.isArray(collectionIds) && collectionIds.length === 0) {
50 | return `0x${"0".repeat(64)}`;
51 | }
52 |
53 | const points = collectionIds.map(id => {
54 | let x = toBN(id);
55 | if (x.eqn(0)) {
56 | // a zero collection ID represents EC group identity
57 | // which is the point at infinity
58 | // satisfying projective equation
59 | // Y^2 = X^3 + 3*Z^6, Z=0
60 | return [onePRed, onePRed, zeroPRed];
61 | }
62 | const odd = x.and(oddToggle).eq(oddToggle);
63 | if (odd) x.ixor(oddToggle);
64 | x = x.toRed(altBN128PRed);
65 | let y, yy;
66 | yy = x.redSqr();
67 | yy = yy.redMul(x); // this might be a BN.js bug workaround
68 | yy.redIAdd(altBN128B);
69 | y = yy.redSqrt();
70 | if (!y.redSqr().eq(yy))
71 | throw new Error(`got invalid collection ID ${id}`);
72 | if (odd !== y.isOdd()) y = y.redNeg();
73 | return [x, y];
74 | });
75 |
76 | const [X, Y, Z] = points.reduce(([X1, Y1, Z1], [x2, y2]) => {
77 | // https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl
78 | if (Z1 == null) {
79 | Z1 = onePRed;
80 | }
81 |
82 | if (Z1.eqn(0)) {
83 | return [x2, y2];
84 | }
85 |
86 | // source 2007 Bernstein--Lange
87 | // assume Z2=1
88 |
89 | // compute Z1Z1 = Z1^2
90 | const Z1Z1 = Z1.redSqr();
91 | // compute U2 = X2 Z1Z1
92 | const U2 = x2.redMul(Z1Z1);
93 | // compute S2 = Y2 Z1 Z1Z1
94 | const S2 = y2.redMul(Z1).redMul(Z1Z1);
95 | // compute H = U2-X1
96 | const H = U2.redSub(X1);
97 | // compute HH = H^2
98 | const HH = H.redSqr();
99 | // compute I = 4 HH
100 | const I = HH.redMul(fourPRed);
101 | // compute J = H I
102 | const J = H.redMul(I);
103 | // compute r = 2 (S2-Y1)
104 | const r = twoPRed.redMul(S2.redSub(Y1));
105 | // compute V = X1 I
106 | const V = X1.redMul(I);
107 | // compute X3 = r^2-J-2 V
108 | const X3 = r
109 | .redSqr()
110 | .redSub(J)
111 | .redSub(twoPRed.redMul(V));
112 | // compute Y3 = r (V-X3)-2 Y1 J
113 | const Y3 = r.redMul(V.redSub(X3)).redSub(twoPRed.redMul(Y1).redMul(J));
114 | // compute Z3 = (Z1+H)^2-Z1Z1-HH
115 | const Z3 = Z1.redAdd(H)
116 | .redSqr()
117 | .redSub(Z1Z1)
118 | .redSub(HH);
119 |
120 | return [X3, Y3, Z3];
121 | });
122 |
123 | let x, y;
124 | if (Z) {
125 | if (Z.eqn(0)) {
126 | return `0x${"0".repeat(64)}`;
127 | } else {
128 | const invZ = Z.redInvm();
129 | const invZZ = invZ.redSqr();
130 | const invZZZ = invZZ.redMul(invZ);
131 | x = X.redMul(invZZ);
132 | y = Y.redMul(invZZZ);
133 | }
134 | } else {
135 | x = X;
136 | y = Y;
137 | }
138 |
139 | const ecHash = x.fromRed();
140 | if (y.isOdd()) ecHash.ixor(oddToggle);
141 | return `0x${ecHash.toString(16, 64)}`;
142 | }
143 |
144 | function getPositionId(collateralToken, collectionId) {
145 | return soliditySha3(
146 | { t: "address", v: collateralToken },
147 | { t: "uint", v: collectionId }
148 | );
149 | }
150 |
151 | return {
152 | getConditionId,
153 | getCollectionId,
154 | combineCollectionIds,
155 | getPositionId
156 | };
157 | };
158 |
--------------------------------------------------------------------------------