polkamarkets-js 1.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/.openzeppelin/unknown-1337.json +2056 -0
  2. package/CONTRIBUTING.md +36 -0
  3. package/README.md +24 -25
  4. package/_book/README.md +590 -0
  5. package/_book/core.md +50 -0
  6. package/_book/gitbook/fonts/fontawesome/FontAwesome.otf +0 -0
  7. package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot +0 -0
  8. package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.svg +685 -0
  9. package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf +0 -0
  10. package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff +0 -0
  11. package/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 +0 -0
  12. package/_book/gitbook/gitbook-plugin-fontsettings/fontsettings.js +240 -0
  13. package/_book/gitbook/gitbook-plugin-fontsettings/website.css +291 -0
  14. package/_book/gitbook/gitbook-plugin-highlight/ebook.css +135 -0
  15. package/_book/gitbook/gitbook-plugin-highlight/website.css +434 -0
  16. package/_book/gitbook/gitbook-plugin-lunr/lunr.min.js +7 -0
  17. package/_book/gitbook/gitbook-plugin-lunr/search-lunr.js +59 -0
  18. package/_book/gitbook/gitbook-plugin-search/lunr.min.js +7 -0
  19. package/_book/gitbook/gitbook-plugin-search/search-engine.js +50 -0
  20. package/_book/gitbook/gitbook-plugin-search/search.css +35 -0
  21. package/_book/gitbook/gitbook-plugin-search/search.js +213 -0
  22. package/_book/gitbook/gitbook-plugin-sharing/buttons.js +90 -0
  23. package/_book/gitbook/gitbook.js +4 -0
  24. package/_book/gitbook/images/apple-touch-icon-precomposed-152.png +0 -0
  25. package/_book/gitbook/images/favicon.ico +0 -0
  26. package/_book/gitbook/style.css +9 -0
  27. package/_book/gitbook/theme.js +4 -0
  28. package/_book/index.html +705 -0
  29. package/_book/intro.md +32 -0
  30. package/_book/search_index.json +1 -0
  31. package/book.json +8 -0
  32. package/build/contracts/AccessControl.json +1 -0
  33. package/build/contracts/AccessControlEnumerable.json +1 -0
  34. package/build/contracts/Achievements.json +1 -0
  35. package/build/contracts/Address.json +1 -0
  36. package/build/contracts/BalanceHolder.json +1 -0
  37. package/build/contracts/BalanceHolder_ERC20.json +1 -0
  38. package/build/contracts/CeilDiv.json +1 -0
  39. package/build/contracts/Clones.json +1 -0
  40. package/build/contracts/Context.json +1 -0
  41. package/build/contracts/Counters.json +1 -0
  42. package/build/contracts/ERC165.json +1 -0
  43. package/build/contracts/ERC20.json +1 -0
  44. package/build/contracts/ERC20Burnable.json +1 -0
  45. package/build/contracts/ERC20Pausable.json +1 -0
  46. package/build/contracts/ERC20PresetMinterPauser.json +1 -0
  47. package/build/contracts/ERC721.json +1 -0
  48. package/build/contracts/EnumerableMap.json +1 -0
  49. package/build/contracts/EnumerableSet.json +1 -0
  50. package/build/contracts/FantasyERC20.json +1 -0
  51. package/build/contracts/IAccessControl.json +1 -0
  52. package/build/contracts/IAccessControlEnumerable.json +1 -0
  53. package/build/contracts/IBalanceHolder_ERC20.json +1 -0
  54. package/build/contracts/IERC165.json +1 -0
  55. package/build/contracts/IERC20.json +1 -0
  56. package/build/contracts/IERC20Metadata.json +1 -0
  57. package/build/contracts/IERC20Permit.json +1 -0
  58. package/build/contracts/IERC721.json +1 -0
  59. package/build/contracts/IERC721Enumerable.json +1 -0
  60. package/build/contracts/IERC721Metadata.json +1 -0
  61. package/build/contracts/IERC721Receiver.json +1 -0
  62. package/build/contracts/IFantasyERC20.json +1 -0
  63. package/build/contracts/IPredictionMarketV3.json +1 -0
  64. package/build/contracts/IPredictionMarketV3Factory.json +1 -0
  65. package/build/contracts/IPredictionMarketV3Manager.json +1 -0
  66. package/build/contracts/IRealityETH_ERC20.json +1 -0
  67. package/build/contracts/IRealityETH_IERC20.json +1 -0
  68. package/build/contracts/IWETH.json +1 -0
  69. package/build/contracts/LandFactory.json +1 -0
  70. package/build/contracts/Math.json +1 -0
  71. package/build/contracts/Migrations.json +1 -0
  72. package/build/contracts/Ownable.json +1 -0
  73. package/build/contracts/Pausable.json +1 -0
  74. package/build/contracts/PredictionMarket.json +1 -0
  75. package/build/contracts/PredictionMarketV2.json +1 -0
  76. package/build/contracts/PredictionMarketV3.json +1 -0
  77. package/build/contracts/PredictionMarketV3Controller.json +1 -0
  78. package/build/contracts/PredictionMarketV3Factory.json +1 -0
  79. package/build/contracts/PredictionMarketV3Manager.json +1 -0
  80. package/build/contracts/PredictionMarketV3Querier.json +1 -0
  81. package/build/contracts/RealitioERC20.json +1 -0
  82. package/build/contracts/RealitioForeignArbitrationProxyWithAppeals.json +1 -0
  83. package/build/contracts/RealitioHomeArbitrationProxy.json +1 -0
  84. package/build/contracts/RealitioSafeMath256.json +1 -0
  85. package/build/contracts/RealitioSafeMath32.json +1 -0
  86. package/build/contracts/RealityETH_ERC20_Factory.json +1 -0
  87. package/build/contracts/RealityETH_ERC20_v3_0.json +1 -0
  88. package/build/contracts/ReentrancyGuard.json +1 -0
  89. package/build/contracts/SafeERC20.json +1 -0
  90. package/build/contracts/SafeMath.json +1 -0
  91. package/build/contracts/Strings.json +1 -0
  92. package/build/contracts/Voting.json +1 -0
  93. package/build/contracts/WETH9.json +1 -0
  94. package/build/contracts/test.json +1 -0
  95. package/cleanContracts.js +22 -0
  96. package/contracts/FantasyERC20.sol +81 -0
  97. package/contracts/IFantasyERC20.sol +20 -0
  98. package/contracts/IPredictionMarketV3.sol +207 -0
  99. package/contracts/IPredictionMarketV3Factory.sol +10 -0
  100. package/contracts/IPredictionMarketV3Manager.sol +12 -0
  101. package/contracts/IRealityETH_ERC20.sol +64 -0
  102. package/contracts/LandFactory.sol +248 -0
  103. package/contracts/Migrations.sol +24 -0
  104. package/contracts/PredictionMarketV3.sol +1332 -0
  105. package/contracts/PredictionMarketV3Controller.sol +87 -0
  106. package/contracts/PredictionMarketV3Factory.sol +205 -0
  107. package/contracts/PredictionMarketV3Manager.sol +45 -0
  108. package/contracts/PredictionMarketV3Querier.sol +79 -0
  109. package/contracts/RealityETH_ERC20_Factory.sol +54 -0
  110. package/contracts/Voting.sol +153 -0
  111. package/contracts/WETH9.sol +62 -0
  112. package/hardhat.config.js +4 -0
  113. package/help.txt +8 -0
  114. package/index.js +3 -0
  115. package/migrations/10_deploy_weth.js +5 -0
  116. package/migrations/11_deploy_full_flow.js +99 -0
  117. package/migrations/12_deploy_pm_v3_querier.js +7 -0
  118. package/migrations/13_deploy_pm_v3_factory.js +14 -0
  119. package/migrations/1_initial_migration.js +5 -0
  120. package/migrations/2_deploy_erc20.js +10 -0
  121. package/migrations/3_deploy_realitio.js +11 -0
  122. package/migrations/4_deploy_pm.js +20 -0
  123. package/migrations/5_seed_markets.js +51 -0
  124. package/migrations/6_deploy_achievements.js +5 -0
  125. package/migrations/7_deploy_voting.js +14 -0
  126. package/migrations/8_deploy_pm_v2.js +20 -0
  127. package/migrations/9_seed_markets_v2.js +68 -0
  128. package/package.json +106 -13
  129. package/src/Application.js +421 -0
  130. package/src/interfaces/index.js +19 -0
  131. package/src/models/AchievementsContract.js +217 -0
  132. package/src/models/ArbitrationContract.js +69 -0
  133. package/src/models/ArbitrationProxyContract.js +32 -0
  134. package/src/models/ERC20Contract.js +156 -0
  135. package/src/models/FantasyERC20Contract.js +92 -0
  136. package/src/models/IContract.js +1002 -0
  137. package/src/models/PolkamarketsSmartAccount.js +100 -0
  138. package/src/models/PredictionMarketContract.js +562 -0
  139. package/src/models/PredictionMarketV2Contract.js +830 -0
  140. package/src/models/PredictionMarketV3Contract.js +256 -0
  141. package/src/models/PredictionMarketV3ControllerContract.js +102 -0
  142. package/src/models/PredictionMarketV3FactoryContract.js +96 -0
  143. package/src/models/PredictionMarketV3ManagerContract.js +111 -0
  144. package/src/models/PredictionMarketV3QuerierContract.js +24 -0
  145. package/src/models/RealitioERC20Contract.js +286 -0
  146. package/src/models/VotingContract.js +182 -0
  147. package/src/models/WETH9Contract.js +92 -0
  148. package/src/models/index.js +33 -0
  149. package/src/utils/Account.js +40 -0
  150. package/src/utils/Contract.js +120 -0
  151. package/src/utils/Numbers.js +94 -0
  152. package/tests/fantasyERC20Contract.js +225 -0
  153. package/tests/index.js +10 -0
  154. package/tests/predictionMarketContract.js +466 -0
  155. package/tests/predictionMarketV2Contract.js +1042 -0
  156. package/tests/predictionMarketV3Contract.js +1079 -0
  157. package/tests/predictionMarketV3ControllerContract.js +613 -0
  158. package/tests/predictionMarketV3FactoryContract.js +469 -0
  159. package/tests/predictionMarketV3ManagerContract.js +610 -0
  160. package/tests/utils.js +16 -0
  161. package/tests/votingContract.js +490 -0
  162. package/tooling/docs/jsdoc.json +6 -0
  163. package/truffle-config.js +134 -0
  164. package/polkamarkets.js +0 -436
@@ -0,0 +1,1042 @@
1
+
2
+
3
+ import { expect } from 'chai';
4
+ import moment from 'moment';
5
+
6
+ import { mochaAsync } from './utils';
7
+ import { Application } from '..';
8
+
9
+ context('Prediction Market Contract V2', async () => {
10
+ require('dotenv').config();
11
+
12
+ let app;
13
+ let accountAddress;
14
+ let predictionMarketContract;
15
+ let realitioERC20Contract
16
+ let requiredBalanceERC20Contract;
17
+ let tokenERC20Contract;
18
+ let WETH9Contract;
19
+
20
+ // market / outcome ids we'll make unit tests with
21
+ let outcomeIds = [0, 1];
22
+ const value = 0.01;
23
+
24
+ context('Contract Deployment', async () => {
25
+ it('should start the Application', mochaAsync(async () => {
26
+ app = new Application({
27
+ web3Provider: process.env.WEB3_PROVIDER,
28
+ web3PrivateKey: process.env.WEB3_PRIVATE_KEY
29
+ });
30
+ expect(app).to.not.equal(null);
31
+ }));
32
+
33
+ it('should deploy Prediction Market Contract', mochaAsync(async () => {
34
+ // Create Contract
35
+ predictionMarketContract = app.getPredictionMarketV2Contract({});
36
+ realitioERC20Contract = app.getRealitioERC20Contract({});
37
+ requiredBalanceERC20Contract = app.getERC20Contract({});
38
+ tokenERC20Contract = app.getERC20Contract({});
39
+ WETH9Contract = app.getWETH9Contract({});
40
+
41
+ // Deploy
42
+ await realitioERC20Contract.deploy({});
43
+ await requiredBalanceERC20Contract.deploy({params: ['Polkamarkets', 'POLK']});
44
+ await WETH9Contract.deploy({});
45
+
46
+ const realitioContractAddress = realitioERC20Contract.getAddress();
47
+ const requiredBalanceERC20ContractAddress = requiredBalanceERC20Contract.getAddress();
48
+ accountAddress = await predictionMarketContract.getMyAccount();
49
+ const WETH9ContractAddress = WETH9Contract.getAddress();
50
+
51
+ await predictionMarketContract.deploy({
52
+ params: [
53
+ requiredBalanceERC20ContractAddress,
54
+ 0,
55
+ realitioContractAddress,
56
+ 86400,
57
+ WETH9ContractAddress
58
+ ]
59
+ });
60
+ const predictionMarketContractAddress = predictionMarketContract.getAddress();
61
+
62
+ expect(predictionMarketContractAddress).to.not.equal(null);
63
+ expect(realitioContractAddress).to.not.equal(null);
64
+
65
+ await tokenERC20Contract.deploy({params: ['Test Token', 'TEST']});
66
+
67
+ // minting tokens for market interaction
68
+ await tokenERC20Contract.getContract().methods.mint(accountAddress, '100000000000000000000000000').send({ from: accountAddress });
69
+
70
+ const balance = await tokenERC20Contract.getContract().methods.balanceOf(accountAddress).call();
71
+ const supply = await tokenERC20Contract.getContract().methods.totalSupply().call();
72
+
73
+ expect(balance).to.equal('100000000000000000000000000');
74
+ expect(balance).to.equal(supply);
75
+
76
+ // approve tokens for market interaction
77
+ await tokenERC20Contract.getContract().methods.approve(predictionMarketContractAddress, '100000000000000000000000000').send({ from: accountAddress });
78
+
79
+ // setting predictionMarket ownable vars
80
+ // await predictionMarketContract.getContract().methods.initialize().send({ from: accountAddress });
81
+ // // setting realitioERC20 governance vars
82
+ // await predictionMarketContract.getContract().methods.setRealitioERC20(realitioContractAddress).send({ from: accountAddress });
83
+ // await predictionMarketContract.getContract().methods.setRealitioTimeout(86400).send({ from: accountAddress });
84
+ // await predictionMarketContract.getContract().methods.setToken(ERC20ContractAddress).send({ from: accountAddress });
85
+ // await predictionMarketContract.getContract().methods.setRequiredBalance(0).send({ from: accountAddress });
86
+ }));
87
+ });
88
+
89
+ context('Market Creation', async () => {
90
+ let marketId;
91
+
92
+ it('should create a Market', mochaAsync(async () => {
93
+ try {
94
+ const res = await predictionMarketContract.createMarket({
95
+ value,
96
+ name: 'Will BTC price close above 100k$ on May 1st 2024',
97
+ description: 'This is a description',
98
+ image: 'foo-bar',
99
+ category: 'Foo;Bar',
100
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
101
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
102
+ outcomes: ['Yes', 'No'],
103
+ token: tokenERC20Contract.getAddress(),
104
+ });
105
+ expect(res.status).to.equal(true);
106
+ } catch(e) {
107
+ console.log(e);
108
+ }
109
+
110
+ const marketIds = await predictionMarketContract.getMarkets();
111
+ marketId = marketIds[marketIds.length - 1];
112
+ expect(marketIds.length).to.equal(1);
113
+ expect(marketIds[marketIds.length - 1]).to.equal(marketId);
114
+ }));
115
+
116
+ it('should create another Market', mochaAsync(async () => {
117
+ const res = await predictionMarketContract.createMarket({
118
+ name: 'Will ETH price close above 10k$ on May 1st 2024',
119
+ image: 'foo-bar',
120
+ category: 'Foo;Bar',
121
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
122
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
123
+ outcomes: ['Yes', 'No'],
124
+ value: 0.001,
125
+ token: tokenERC20Contract.getAddress(),
126
+ });
127
+ expect(res.status).to.equal(true);
128
+
129
+ const marketIds = await predictionMarketContract.getMarkets();
130
+ expect(marketIds.length).to.equal(2);
131
+ }));
132
+ });
133
+
134
+ context('Market Data', async () => {
135
+ let marketId = 0;
136
+ it('should get Market data', mochaAsync(async () => {
137
+ const res = await predictionMarketContract.getMarketData({marketId});
138
+ expect(res).to.eql({
139
+ closeDateTime: moment().add(1, 'month').format('YYYY-MM-DD 00:00'),
140
+ state: 0,
141
+ oracleAddress: '0x0000000000000000000000000000000000000000',
142
+ liquidity: 0.01,
143
+ outcomeIds: [0, 1],
144
+ resolvedOutcomeId: -1,
145
+ voided: false
146
+ });
147
+ }));
148
+
149
+ it('should get Market details', mochaAsync(async () => {
150
+ const res = await predictionMarketContract.getMarketDetails({marketId});
151
+ expect(res).to.eql({
152
+ name: 'Will BTC price close above 100k$ on May 1st 2024',
153
+ description: 'This is a description',
154
+ category: 'Foo',
155
+ subcategory: 'Bar',
156
+ outcomes: ['Yes', 'No'],
157
+ image: 'foo-bar'
158
+ });
159
+ }));
160
+
161
+ it('should get Market Outcomes data', mochaAsync(async () => {
162
+ const outcome1Data = await predictionMarketContract.getOutcomeData({marketId, outcomeId: outcomeIds[0]});
163
+ expect(outcome1Data).to.include({
164
+ price: 0.5,
165
+ shares: 0.01
166
+ });
167
+
168
+ const outcome2Data = await predictionMarketContract.getOutcomeData({marketId, outcomeId: outcomeIds[1]});
169
+ expect(outcome2Data).to.include({
170
+ price: 0.5,
171
+ shares: 0.01
172
+ });
173
+
174
+ // outcomes share prices should sum to 1
175
+ expect(outcome1Data.price + outcome2Data.price).to.equal(1);
176
+ // outcomes number of shares should dum to value * 2
177
+ expect(outcome1Data.shares + outcome2Data.shares).to.equal(value * 2);
178
+ }));
179
+ });
180
+
181
+ context('Market Interaction - WETH Market', async () => {
182
+ let marketId;
183
+ const wrapped = true;
184
+
185
+ before(mochaAsync(async () => {
186
+ try {
187
+ const res = await predictionMarketContract.createMarketWithETH({
188
+ value,
189
+ name: 'WETH Market',
190
+ image: 'foo-bar',
191
+ category: 'Foo;Bar',
192
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
193
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
194
+ outcomes: ['A', 'B']
195
+ });
196
+ expect(res.status).to.equal(true);
197
+ } catch(e) {
198
+ console.log(e);
199
+ }
200
+
201
+ const marketIds = await predictionMarketContract.getMarkets();
202
+ marketId = marketIds[marketIds.length - 1];
203
+ }));
204
+
205
+ it('should match WETH supply', mochaAsync(async () => {
206
+ const ETHBalance = Number(await WETH9Contract.getBalance());
207
+ expect(Number(ETHBalance)).to.equal(value);
208
+
209
+ const WETHSupply = await WETH9Contract.totalSupply();
210
+ expect(Number(WETHSupply)).to.equal(value);
211
+ }));
212
+
213
+ it('should match market WETH balance', mochaAsync(async () => {
214
+ const contractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
215
+ expect(contractBalance).to.equal(value);
216
+ }));
217
+
218
+ it('should add liquidity', mochaAsync(async () => {
219
+ const contractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
220
+ const WETHBalance = Number(await WETH9Contract.getBalance());
221
+
222
+ try {
223
+ const res = await predictionMarketContract.addLiquidity({marketId, value, wrapped})
224
+ expect(res.status).to.equal(true);
225
+ } catch(e) {
226
+ console.log(e);
227
+ }
228
+
229
+ const newContractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
230
+ const NewWETHBalance = Number(await WETH9Contract.getBalance());
231
+ const amountTransferred = Number((newContractBalance - contractBalance).toFixed(5));
232
+
233
+ expect(amountTransferred).to.equal(value);
234
+ expect(NewWETHBalance).to.equal(WETHBalance + value);
235
+ }));
236
+
237
+ it('should remove liquidity', mochaAsync(async () => {
238
+ const contractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
239
+ const WETHBalance = Number(await WETH9Contract.getBalance());
240
+
241
+ try {
242
+ const res = await predictionMarketContract.removeLiquidity({marketId, shares: value, wrapped})
243
+ expect(res.status).to.equal(true);
244
+ } catch(e) {
245
+ console.log(e);
246
+ }
247
+
248
+ const newContractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
249
+ const NewWETHBalance = Number(await WETH9Contract.getBalance());
250
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
251
+
252
+ expect(amountTransferred).to.equal(value);
253
+ expect(NewWETHBalance).to.equal(WETHBalance - value);
254
+ }));
255
+
256
+ it('should buy outcome shares', mochaAsync(async () => {
257
+ const outcomeId = 0;
258
+ const minOutcomeSharesToBuy = 0.015;
259
+
260
+ const contractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
261
+ const WETHBalance = Number(await WETH9Contract.getBalance());
262
+
263
+ try {
264
+ const res = await predictionMarketContract.buy({marketId, outcomeId, value, minOutcomeSharesToBuy, wrapped});
265
+ expect(res.status).to.equal(true);
266
+ } catch(e) {
267
+ console.log(e);
268
+ }
269
+
270
+ const newContractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
271
+ const NewWETHBalance = Number(await WETH9Contract.getBalance());
272
+ const amountTransferred = Number((newContractBalance - contractBalance).toFixed(5));
273
+
274
+ expect(amountTransferred).to.equal(value);
275
+ expect(NewWETHBalance).to.equal(WETHBalance + value);
276
+ }));
277
+
278
+ it('should sell outcome shares', mochaAsync(async () => {
279
+ const outcomeId = 0;
280
+ const maxOutcomeSharesToSell = 0.015;
281
+
282
+ const contractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
283
+ const WETHBalance = Number(await WETH9Contract.getBalance());
284
+
285
+ try {
286
+ const res = await predictionMarketContract.sell({marketId, outcomeId, value, maxOutcomeSharesToSell, wrapped});
287
+ expect(res.status).to.equal(true);
288
+ } catch(e) {
289
+ console.log(e);
290
+ }
291
+
292
+ const newContractBalance = await WETH9Contract.balanceOf({address: predictionMarketContract.getAddress()});
293
+ const NewWETHBalance = Number(await WETH9Contract.getBalance());
294
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
295
+
296
+ expect(amountTransferred).to.equal(value);
297
+ expect(NewWETHBalance).to.equal(WETHBalance - value);
298
+ }));
299
+ });
300
+
301
+ context('Market Interaction - Balanced Market (Same Outcome Odds)', async () => {
302
+ let marketId = 0;
303
+ it('should add liquidity without changing shares balance', mochaAsync(async () => {
304
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
305
+ const marketData = await predictionMarketContract.getMarketData({marketId});
306
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
307
+
308
+ // balanced market - same price in all outcomoes
309
+ expect(outcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[1]);
310
+
311
+ try {
312
+ const res = await predictionMarketContract.addLiquidity({marketId, value})
313
+ expect(res.status).to.equal(true);
314
+ } catch(e) {
315
+ console.log(e);
316
+ }
317
+
318
+ const myNewShares = await predictionMarketContract.getMyMarketShares({marketId});
319
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
320
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
321
+
322
+ expect(newMarketData.liquidity).to.above(marketData.liquidity);
323
+ expect(newMarketData.liquidity).to.equal(marketData.liquidity + value);
324
+
325
+ // Outcome prices shoud remain the same after providing liquidity
326
+ expect(newOutcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[0]);
327
+ expect(newOutcomePrices.outcomes[1]).to.equal(outcomePrices.outcomes[1]);
328
+
329
+ // Price balances are 0.5-0.5, liquidity will be added solely through liquidity shares
330
+ expect(myNewShares.liquidityShares).to.above(myShares.liquidityShares);
331
+ expect(myNewShares.liquidityShares).to.equal(myShares.liquidityShares + value);
332
+ // shares balance remains the same
333
+ expect(myNewShares.outcomeShares[0]).to.equal(myShares.outcomeShares[0]);
334
+ expect(myNewShares.outcomeShares[1]).to.equal(myShares.outcomeShares[1]);
335
+ }));
336
+
337
+ it('should remove liquidity without changing shares balance', mochaAsync(async () => {
338
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
339
+ const marketData = await predictionMarketContract.getMarketData({marketId});
340
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
341
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
342
+
343
+ // balanced market - same price in all outcomoes
344
+ expect(outcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[1]);
345
+
346
+ try {
347
+ const res = await predictionMarketContract.removeLiquidity({marketId, shares: value})
348
+ expect(res.status).to.equal(true);
349
+ } catch(e) {
350
+ console.log(e);
351
+ }
352
+
353
+ const myNewShares = await predictionMarketContract.getMyMarketShares({marketId});
354
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
355
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
356
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
357
+
358
+ expect(newMarketData.liquidity).to.below(marketData.liquidity);
359
+ expect(newMarketData.liquidity).to.equal(marketData.liquidity - value);
360
+
361
+ // Outcome prices shoud remain the same after providing liquidity
362
+ expect(newOutcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[0]);
363
+ expect(newOutcomePrices.outcomes[1]).to.equal(outcomePrices.outcomes[1]);
364
+
365
+ // Price balances are 0.5-0.5, liquidity will be added solely through liquidity shares
366
+ expect(myNewShares.liquidityShares).to.below(myShares.liquidityShares);
367
+ expect(myNewShares.liquidityShares).to.equal(myShares.liquidityShares - value);
368
+ // shares balance remains the same
369
+ expect(myNewShares.outcomeShares[0]).to.equal(myShares.outcomeShares[0]);
370
+ expect(myNewShares.outcomeShares[1]).to.equal(myShares.outcomeShares[1]);
371
+
372
+ // User gets liquidity tokens back in ETH
373
+ expect(newContractBalance).to.below(contractBalance);
374
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
375
+ expect(amountTransferred).to.equal(value);
376
+ }));
377
+ });
378
+
379
+ context('Market Interaction - Unbalanced Market (Different Outcome Odds)', async () => {
380
+ let marketId = 0;
381
+ it('should display my shares', mochaAsync(async () => {
382
+ const res = await predictionMarketContract.getMyMarketShares({marketId});
383
+ // currently holding liquidity tokens from market creation
384
+ expect(res).to.eql({
385
+ liquidityShares: 0.01,
386
+ outcomeShares: {
387
+ 0: 0.00,
388
+ 1: 0.00,
389
+ }
390
+ });
391
+ }));
392
+
393
+ it('should buy outcome shares', mochaAsync(async () => {
394
+ const outcomeId = 0;
395
+ const minOutcomeSharesToBuy = 0.015;
396
+
397
+ const marketData = await predictionMarketContract.getMarketData({marketId});
398
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
399
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
400
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
401
+
402
+ try {
403
+ const res = await predictionMarketContract.buy({marketId, outcomeId, value, minOutcomeSharesToBuy});
404
+ expect(res.status).to.equal(true);
405
+ } catch(e) {
406
+ console.log(e);
407
+ }
408
+
409
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
410
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
411
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
412
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
413
+
414
+ // outcome price should increase
415
+ expect(newOutcomePrices.outcomes[0]).to.above(outcomePrices.outcomes[0]);
416
+ expect(newOutcomePrices.outcomes[0]).to.equal(0.8);
417
+ // opposite outcome price should decrease
418
+ expect(newOutcomePrices.outcomes[1]).to.below(outcomePrices.outcomes[1]);
419
+ expect(newOutcomePrices.outcomes[1]).to.equal(0.2);
420
+ // Prices sum = 1
421
+ // 0.5 + 0.5 = 1
422
+ expect(newOutcomePrices.outcomes[0] + newOutcomePrices.outcomes[1]).to.equal(1);
423
+
424
+ // Liquidity value remains the same
425
+ expect(newMarketData.liquidity).to.equal(marketData.liquidity);
426
+
427
+ // outcome shares should decrease
428
+ expect(newOutcomeShares.outcomes[0]).to.below(outcomeShares.outcomes[0]);
429
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.005);
430
+ // opposite outcome shares should increase
431
+ expect(newOutcomeShares.outcomes[1]).to.above(outcomeShares.outcomes[1]);
432
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.02);
433
+ // # Shares Product = Liquidity^2
434
+ // 0.005 * 0.02 = 0.01^2
435
+ expect(outcomeShares.outcomes[0] * outcomeShares.outcomes[1]).to.equal(newMarketData.liquidity**2);
436
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1]).to.equal(newMarketData.liquidity**2);
437
+
438
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
439
+ expect(myShares).to.eql({
440
+ liquidityShares: 0.01,
441
+ outcomeShares: {
442
+ 0: 0.015,
443
+ 1: 0.00,
444
+ }
445
+ });
446
+
447
+ // Contract adds value to balance
448
+ expect(newContractBalance).to.above(contractBalance);
449
+ // TODO: check amountReceived from internal transactions
450
+ const amountReceived = Number((newContractBalance - contractBalance).toFixed(5));
451
+ expect(amountReceived).to.equal(value);
452
+ }));
453
+
454
+ it('should add liquidity', mochaAsync(async () => {
455
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
456
+ const marketData = await predictionMarketContract.getMarketData({marketId});
457
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
458
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
459
+
460
+ try {
461
+ const res = await predictionMarketContract.addLiquidity({marketId, value})
462
+ expect(res.status).to.equal(true);
463
+ } catch(e) {
464
+ console.log(e);
465
+ }
466
+
467
+ const myNewShares = await predictionMarketContract.getMyMarketShares({marketId});
468
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
469
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
470
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
471
+
472
+ // Outcome prices shoud remain the same after providing liquidity
473
+ expect(newOutcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[0]);
474
+ expect(newOutcomePrices.outcomes[1]).to.equal(outcomePrices.outcomes[1]);
475
+
476
+ // # Shares Product = Liquidity^2
477
+ // 0.0075 * 0.03 = 0.015^2
478
+ expect(newMarketData.liquidity).to.above(marketData.liquidity);
479
+ expect(newMarketData.liquidity).to.equal(0.015);
480
+ expect(newOutcomeShares.outcomes[0]).to.above(outcomeShares.outcomes[0]);
481
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.0075);
482
+ expect(newOutcomeShares.outcomes[1]).to.above(outcomeShares.outcomes[1]);
483
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.03);
484
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1]).to.equal(newMarketData.liquidity**2);
485
+
486
+ // Price balances are not 0.5-0.5, liquidity will be added through shares + liquidity
487
+ expect(myNewShares.liquidityShares).to.above(myShares.liquidityShares);
488
+ expect(myNewShares.liquidityShares).to.equal(0.015);
489
+ // shares balance of higher odd outcome increases
490
+ expect(myNewShares.outcomeShares[0]).to.above(myShares.outcomeShares[0]);
491
+ expect(myNewShares.outcomeShares[0]).to.equal(0.0225);
492
+ // shares balance of lower odd outcome remains
493
+ expect(myNewShares.outcomeShares[1]).to.equal(myShares.outcomeShares[1]);
494
+ expect(myNewShares.outcomeShares[1]).to.equal(0);
495
+ }));
496
+
497
+ it('should remove liquidity', mochaAsync(async () => {
498
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
499
+ const marketData = await predictionMarketContract.getMarketData({marketId});
500
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
501
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
502
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
503
+ const liquiditySharesToRemove = 0.005;
504
+
505
+ try {
506
+ const res = await predictionMarketContract.removeLiquidity({marketId, shares: liquiditySharesToRemove});
507
+ expect(res.status).to.equal(true);
508
+ } catch(e) {
509
+ console.log(e);
510
+ }
511
+
512
+ const myNewShares = await predictionMarketContract.getMyMarketShares({marketId});
513
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
514
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
515
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
516
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
517
+
518
+ // Outcome prices shoud remain the same after removing liquidity
519
+ expect(newOutcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[0]);
520
+ expect(newOutcomePrices.outcomes[1]).to.equal(outcomePrices.outcomes[1]);
521
+
522
+ // # Shares Product = Liquidity^2
523
+ // 0.005 * 0.02 = 0.01^2
524
+ expect(newMarketData.liquidity).to.below(marketData.liquidity);
525
+ expect(newMarketData.liquidity).to.equal(0.01);
526
+ expect(newOutcomeShares.outcomes[0]).to.below(outcomeShares.outcomes[0]);
527
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.005);
528
+ expect(newOutcomeShares.outcomes[1]).to.below(outcomeShares.outcomes[1]);
529
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.02);
530
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1]).to.equal(newMarketData.liquidity**2);
531
+
532
+ // Price balances are not 0.5-0.5, liquidity will be added through shares + liquidity
533
+ expect(myNewShares.liquidityShares).to.below(myShares.liquidityShares);
534
+ expect(myNewShares.liquidityShares).to.equal(0.01);
535
+ // shares balance of higher odd outcome remains
536
+ expect(myNewShares.outcomeShares[0]).to.equal(myShares.outcomeShares[0]);
537
+ expect(myNewShares.outcomeShares[0]).to.equal(0.0225);
538
+ // shares balance of lower odd outcome increases
539
+ expect(myNewShares.outcomeShares[1]).to.above(myShares.outcomeShares[1]);
540
+ expect(myNewShares.outcomeShares[1]).to.equal(0.0075);
541
+
542
+ // User gets part of the liquidity tokens back in ETH
543
+ expect(newContractBalance).to.below(contractBalance);
544
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
545
+ expect(amountTransferred).to.equal(0.0025);
546
+ }));
547
+
548
+ it('should sell outcome shares', mochaAsync(async () => {
549
+ const outcomeId = 0;
550
+ const maxOutcomeSharesToSell = 0.015;
551
+
552
+ const marketData = await predictionMarketContract.getMarketData({marketId});
553
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
554
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
555
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
556
+
557
+ try {
558
+ const res = await predictionMarketContract.sell({marketId, outcomeId, value, maxOutcomeSharesToSell});
559
+ expect(res.status).to.equal(true);
560
+ } catch(e) {
561
+ console.log(e);
562
+ }
563
+
564
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
565
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
566
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
567
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
568
+
569
+ // outcome price should decrease
570
+ expect(newOutcomePrices.outcomes[0]).to.below(outcomePrices.outcomes[0]);
571
+ expect(newOutcomePrices.outcomes[0]).to.equal(0.5);
572
+ // opposite outcome price should increase
573
+ expect(newOutcomePrices.outcomes[1]).to.above(outcomePrices.outcomes[1]);
574
+ expect(newOutcomePrices.outcomes[1]).to.equal(0.5);
575
+ // Prices sum = 1
576
+ // 0.5 + 0.5 = 1
577
+ expect(newOutcomePrices.outcomes[0] + newOutcomePrices.outcomes[1]).to.equal(1);
578
+
579
+ // Liquidity value remains the same
580
+ expect(newMarketData.liquidity).to.equal(marketData.liquidity);
581
+
582
+ // outcome shares should increase
583
+ expect(newOutcomeShares.outcomes[0]).to.above(outcomeShares.outcomes[0]);
584
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.01);
585
+ // opposite outcome shares should increase
586
+ expect(newOutcomeShares.outcomes[1]).to.below(outcomeShares.outcomes[1]);
587
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.01);
588
+ // # Shares Product = Liquidity^2
589
+ // 0.01 * 0.01 = 0.01^2
590
+ expect(outcomeShares.outcomes[0] * outcomeShares.outcomes[1]).to.equal(newMarketData.liquidity**2);
591
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1]).to.equal(newMarketData.liquidity**2);
592
+
593
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
594
+ expect(myShares).to.eql({
595
+ liquidityShares: 0.01,
596
+ outcomeShares: {
597
+ 0: 0.0075,
598
+ 1: 0.0075,
599
+ }
600
+ });
601
+
602
+ // User gets shares value back in ETH
603
+ expect(newContractBalance).to.below(contractBalance);
604
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
605
+ expect(amountTransferred).to.equal(0.01);
606
+ }));
607
+ });
608
+
609
+ context('Multiple Outcomes', async () => {
610
+ let marketId = 0;
611
+ context('Market Creation', async () => {
612
+ it('should create a Market with 3 outcomes', mochaAsync(async () => {
613
+ try {
614
+ const res = await predictionMarketContract.createMarket({
615
+ value,
616
+ name: 'Market with 3 outcomes',
617
+ image: 'foo-bar',
618
+ category: 'Foo;Bar',
619
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
620
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
621
+ outcomes: ['A', 'B', 'C'],
622
+ token: tokenERC20Contract.getAddress(),
623
+ });
624
+ expect(res.status).to.equal(true);
625
+ } catch(e) {
626
+ console.log(e);
627
+ }
628
+
629
+ const marketIds = await predictionMarketContract.getMarkets();
630
+ marketId = marketIds[marketIds.length - 1];
631
+
632
+ const res = await predictionMarketContract.getMarketData({marketId});
633
+ expect(res.outcomeIds.length).to.eql(3);
634
+ expect(res.outcomeIds).to.eql([0, 1, 2]);
635
+ }));
636
+
637
+ it('should create a Market with 32 outcomes', mochaAsync(async () => {
638
+ // Array with outcomes with outcome id as name
639
+ // TODO: improve gas optimization for markets with 10+ outcomes
640
+ const outcomeCount = 32;
641
+ const outcomes = Array.from(Array(outcomeCount).keys());
642
+
643
+ try {
644
+ const res = await predictionMarketContract.createMarket({
645
+ value,
646
+ name: `Market with ${outcomeCount} outcomes`,
647
+ image: 'foo-bar',
648
+ category: 'Foo;Bar',
649
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
650
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
651
+ outcomes,
652
+ token: tokenERC20Contract.getAddress(),
653
+ });
654
+ expect(res.status).to.equal(true);
655
+ } catch(e) {
656
+ console.log(e);
657
+ }
658
+
659
+ const marketIds = await predictionMarketContract.getMarkets();
660
+ marketId = marketIds[marketIds.length - 1];
661
+
662
+ const res = await predictionMarketContract.getMarketData({marketId});
663
+ expect(res.outcomeIds.length).to.eql(outcomeCount);
664
+ expect(res.outcomeIds).to.eql(outcomes);
665
+ }));
666
+
667
+ it('should not create a Market with more than 32 outcomes', mochaAsync(async () => {
668
+ const oldMarketIds = await predictionMarketContract.getMarkets();
669
+ try {
670
+ const outcomes = Array.from(Array(33).keys());
671
+
672
+ const res = await predictionMarketContract.createMarket({
673
+ value,
674
+ name: 'Market with 257 outcomes',
675
+ image: 'foo-bar',
676
+ category: 'Foo;Bar',
677
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
678
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
679
+ outcomes,
680
+ token: tokenERC20Contract.getAddress(),
681
+ });
682
+ expect(res.status).to.equal(true);
683
+ } catch(e) {
684
+ // not logging error, as tx is expected to fail
685
+ // console.log(e);
686
+ }
687
+
688
+ const currentMarketIds = await predictionMarketContract.getMarkets();
689
+
690
+ expect(currentMarketIds.length).to.eql(oldMarketIds.length);
691
+ }));
692
+ });
693
+
694
+ context('Market Interaction', async () => {
695
+ let marketId;
696
+ let outcomeIds = [0, 1, 2];
697
+
698
+ before(mochaAsync(async () => {
699
+ try {
700
+ const res = await predictionMarketContract.createMarket({
701
+ value,
702
+ name: 'Market with 3 outcomes',
703
+ image: 'foo-bar',
704
+ category: 'Foo;Bar',
705
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
706
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
707
+ outcomes: ['D', 'E', 'F'],
708
+ token: tokenERC20Contract.getAddress(),
709
+ });
710
+ expect(res.status).to.equal(true);
711
+ } catch(e) {
712
+ console.log(e);
713
+ }
714
+
715
+ const marketIds = await predictionMarketContract.getMarkets();
716
+ marketId = marketIds[marketIds.length - 1];
717
+ }));
718
+
719
+ it('should buy outcome shares', mochaAsync(async () => {
720
+ const outcomeId = 0;
721
+ const minOutcomeSharesToBuy = 0.015;
722
+
723
+ const marketData = await predictionMarketContract.getMarketData({marketId});
724
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
725
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
726
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
727
+
728
+ try {
729
+ const res = await predictionMarketContract.buy({marketId, outcomeId, value, minOutcomeSharesToBuy});
730
+ expect(res.status).to.equal(true);
731
+ } catch(e) {
732
+ console.log(e);
733
+ }
734
+
735
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
736
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
737
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
738
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
739
+
740
+ // outcome price should increase
741
+ expect(newOutcomePrices.outcomes[0]).to.above(outcomePrices.outcomes[0]);
742
+ expect(newOutcomePrices.outcomes[0]).to.equal(0.8);
743
+ // opposite outcome price should decrease
744
+ expect(newOutcomePrices.outcomes[1]).to.below(outcomePrices.outcomes[1]);
745
+ expect(newOutcomePrices.outcomes[1]).to.equal(0.1);
746
+ expect(newOutcomePrices.outcomes[2]).to.below(outcomePrices.outcomes[2]);
747
+ expect(newOutcomePrices.outcomes[2]).to.equal(0.1);
748
+ // Prices sum = 1
749
+ // 0.1 + 0.1 + 0.8 = 1
750
+ expect(newOutcomePrices.outcomes[0] + newOutcomePrices.outcomes[1] + newOutcomePrices.outcomes[2]).to.equal(1);
751
+
752
+ // Liquidity value remains the same
753
+ expect(newMarketData.liquidity).to.equal(marketData.liquidity);
754
+
755
+ // outcome shares should decrease
756
+ expect(newOutcomeShares.outcomes[0]).to.below(outcomeShares.outcomes[0]);
757
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.0025);
758
+ // opposite outcome shares should increase
759
+ expect(newOutcomeShares.outcomes[1]).to.above(outcomeShares.outcomes[1]);
760
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.02);
761
+ expect(newOutcomeShares.outcomes[2]).to.above(outcomeShares.outcomes[2]);
762
+ expect(newOutcomeShares.outcomes[2]).to.equal(0.02);
763
+ // # Shares Product = Liquidity^2
764
+ // 0.0025 * 0.02 * 0.02 = 0.01^3
765
+ expect(outcomeShares.outcomes[0] * outcomeShares.outcomes[1] * outcomeShares.outcomes[2]).to.equal(newMarketData.liquidity ** 3);
766
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1] * newOutcomeShares.outcomes[2]).to.equal(newMarketData.liquidity ** 3);
767
+
768
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
769
+ expect(myShares).to.eql({
770
+ liquidityShares: 0.01,
771
+ outcomeShares: {
772
+ 0: 0.0175,
773
+ 1: 0.00,
774
+ 2: 0.00,
775
+ }
776
+ });
777
+
778
+ // Contract adds value to balance
779
+ expect(newContractBalance).to.above(contractBalance);
780
+ // TODO: check amountReceived from internal transactions
781
+ const amountReceived = Number((newContractBalance - contractBalance).toFixed(5));
782
+ expect(amountReceived).to.equal(value);
783
+ }));
784
+
785
+ it('should add liquidity', mochaAsync(async () => {
786
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
787
+ const marketData = await predictionMarketContract.getMarketData({marketId});
788
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
789
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
790
+
791
+ try {
792
+ const res = await predictionMarketContract.addLiquidity({marketId, value})
793
+ expect(res.status).to.equal(true);
794
+ } catch(e) {
795
+ console.log(e);
796
+ }
797
+
798
+ const myNewShares = await predictionMarketContract.getMyMarketShares({marketId});
799
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
800
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
801
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
802
+
803
+ // Outcome prices shoud remain the same after providing liquidity
804
+ expect(newOutcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[0]);
805
+ expect(newOutcomePrices.outcomes[1]).to.equal(outcomePrices.outcomes[1]);
806
+ expect(newOutcomePrices.outcomes[2]).to.equal(outcomePrices.outcomes[2]);
807
+
808
+ // # Shares Product = Liquidity^2
809
+ // 0.00375 * 0.03 * 0.03 = 0.015^3
810
+ expect(newMarketData.liquidity).to.above(marketData.liquidity);
811
+ expect(newMarketData.liquidity).to.equal(0.015);
812
+ expect(newOutcomeShares.outcomes[0]).to.above(outcomeShares.outcomes[0]);
813
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.00375);
814
+ expect(newOutcomeShares.outcomes[1]).to.above(outcomeShares.outcomes[1]);
815
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.03);
816
+ expect(newOutcomeShares.outcomes[2]).to.above(outcomeShares.outcomes[2]);
817
+ expect(newOutcomeShares.outcomes[2]).to.equal(0.03);
818
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1] * newOutcomeShares.outcomes[2]).to.be.closeTo(newMarketData.liquidity ** 3, 0.00000001);
819
+
820
+ // Price balances are not 0.5-0.5, liquidity will be added through shares + liquidity
821
+ expect(myNewShares.liquidityShares).to.above(myShares.liquidityShares);
822
+ expect(myNewShares.liquidityShares).to.equal(0.015);
823
+ // shares balance of higher odd outcome increases
824
+ expect(myNewShares.outcomeShares[0]).to.above(myShares.outcomeShares[0]);
825
+ expect(myNewShares.outcomeShares[0]).to.equal(0.02625);
826
+ // shares balance of lower odd outcome remains
827
+ expect(myNewShares.outcomeShares[1]).to.equal(myShares.outcomeShares[1]);
828
+ expect(myNewShares.outcomeShares[1]).to.equal(0);
829
+ expect(myNewShares.outcomeShares[2]).to.equal(myShares.outcomeShares[2]);
830
+ expect(myNewShares.outcomeShares[2]).to.equal(0);
831
+ }));
832
+
833
+ it('should remove liquidity', mochaAsync(async () => {
834
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
835
+ const marketData = await predictionMarketContract.getMarketData({marketId});
836
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
837
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
838
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
839
+ const liquiditySharesToRemove = 0.005;
840
+
841
+ try {
842
+ const res = await predictionMarketContract.removeLiquidity({marketId, shares: liquiditySharesToRemove});
843
+ expect(res.status).to.equal(true);
844
+ } catch(e) {
845
+ console.log(e);
846
+ }
847
+
848
+ const myNewShares = await predictionMarketContract.getMyMarketShares({marketId});
849
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
850
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
851
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
852
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
853
+
854
+ // Outcome prices shoud remain the same after removing liquidity
855
+ expect(newOutcomePrices.outcomes[0]).to.equal(outcomePrices.outcomes[0]);
856
+ expect(newOutcomePrices.outcomes[1]).to.equal(outcomePrices.outcomes[1]);
857
+ expect(newOutcomePrices.outcomes[2]).to.equal(outcomePrices.outcomes[2]);
858
+
859
+ // # Shares Product = Liquidity^3
860
+ // 0.0025 * 0.02 * 0.02 = 0.01^3
861
+ expect(newMarketData.liquidity).to.below(marketData.liquidity);
862
+ expect(newMarketData.liquidity).to.equal(0.01);
863
+ expect(newOutcomeShares.outcomes[0]).to.below(outcomeShares.outcomes[0]);
864
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.0025);
865
+ expect(newOutcomeShares.outcomes[1]).to.below(outcomeShares.outcomes[1]);
866
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.02);
867
+ expect(newOutcomeShares.outcomes[2]).to.below(outcomeShares.outcomes[2]);
868
+ expect(newOutcomeShares.outcomes[2]).to.equal(0.02);
869
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1] * newOutcomeShares.outcomes[2]).to.equal(newMarketData.liquidity ** 3);
870
+
871
+ // Price balances are not 0.5-0.5, liquidity will be added through shares + liquidity
872
+ expect(myNewShares.liquidityShares).to.below(myShares.liquidityShares);
873
+ expect(myNewShares.liquidityShares).to.equal(0.01);
874
+ // shares balance of higher odd outcome remains
875
+ expect(myNewShares.outcomeShares[0]).to.equal(myShares.outcomeShares[0]);
876
+ expect(myNewShares.outcomeShares[0]).to.equal(0.02625);
877
+ // shares balance of lower odd outcome increases
878
+ expect(myNewShares.outcomeShares[1]).to.above(myShares.outcomeShares[1]);
879
+ expect(myNewShares.outcomeShares[1]).to.equal(0.00875);
880
+ expect(myNewShares.outcomeShares[2]).to.above(myShares.outcomeShares[2]);
881
+ expect(myNewShares.outcomeShares[2]).to.equal(0.00875);
882
+
883
+ // User gets part of the liquidity tokens back in ETH
884
+ expect(newContractBalance).to.below(contractBalance);
885
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
886
+ expect(amountTransferred).to.equal(0.00125);
887
+ }));
888
+
889
+ it('should sell outcome shares', mochaAsync(async () => {
890
+ const outcomeId = 0;
891
+ const maxOutcomeSharesToSell = 0.0175;
892
+
893
+ const marketData = await predictionMarketContract.getMarketData({marketId});
894
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
895
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
896
+ const contractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
897
+
898
+ try {
899
+ const res = await predictionMarketContract.sell({marketId, outcomeId, value, maxOutcomeSharesToSell});
900
+ expect(res.status).to.equal(true);
901
+ } catch(e) {
902
+ console.log(e);
903
+ }
904
+
905
+ const newMarketData = await predictionMarketContract.getMarketData({marketId});
906
+ const newOutcomePrices = await predictionMarketContract.getMarketPrices({marketId});
907
+ const newOutcomeShares = await predictionMarketContract.getMarketShares({marketId});
908
+ const newContractBalance = await tokenERC20Contract.balanceOf({address: predictionMarketContract.getAddress()});
909
+
910
+ // outcome price should decrease
911
+ expect(newOutcomePrices.outcomes[0]).to.below(outcomePrices.outcomes[0]);
912
+ expect(newOutcomePrices.outcomes[0]).to.equal(1/3);
913
+ // opposite outcome price should increase
914
+ expect(newOutcomePrices.outcomes[1]).to.above(outcomePrices.outcomes[1]);
915
+ expect(newOutcomePrices.outcomes[1]).to.equal(1/3);
916
+ expect(newOutcomePrices.outcomes[2]).to.above(outcomePrices.outcomes[2]);
917
+ expect(newOutcomePrices.outcomes[2]).to.equal(1/3);
918
+ // Prices sum = 1
919
+ // 0.333 + 0.333 + 0.333 = 1
920
+ expect(newOutcomePrices.outcomes[0] + newOutcomePrices.outcomes[1] + newOutcomePrices.outcomes[2]).to.equal(1);
921
+
922
+ // Liquidity value remains the same
923
+ expect(newMarketData.liquidity).to.equal(marketData.liquidity);
924
+
925
+ // outcome shares should increase
926
+ expect(newOutcomeShares.outcomes[0]).to.above(outcomeShares.outcomes[0]);
927
+ expect(newOutcomeShares.outcomes[0]).to.equal(0.01);
928
+ // opposite outcome shares should increase
929
+ expect(newOutcomeShares.outcomes[1]).to.below(outcomeShares.outcomes[1]);
930
+ expect(newOutcomeShares.outcomes[1]).to.equal(0.01);
931
+ expect(newOutcomeShares.outcomes[2]).to.below(outcomeShares.outcomes[2]);
932
+ expect(newOutcomeShares.outcomes[2]).to.equal(0.01);
933
+ // # Shares Product = Liquidity^2
934
+ // 0.01 * 0.01 = 0.01^2
935
+ expect(outcomeShares.outcomes[0] * outcomeShares.outcomes[1] * outcomeShares.outcomes[2]).to.equal(newMarketData.liquidity ** 3);
936
+ expect(newOutcomeShares.outcomes[0] * newOutcomeShares.outcomes[1] * newOutcomeShares.outcomes[2]).to.equal(newMarketData.liquidity ** 3);
937
+
938
+ const myShares = await predictionMarketContract.getMyMarketShares({marketId});
939
+ expect(myShares).to.eql({
940
+ liquidityShares: 0.01,
941
+ outcomeShares: {
942
+ 0: 0.00875,
943
+ 1: 0.00875,
944
+ 2: 0.00875,
945
+ }
946
+ });
947
+
948
+ // User gets shares value back in ETH
949
+ expect(newContractBalance).to.below(contractBalance);
950
+ const amountTransferred = Number((contractBalance - newContractBalance).toFixed(5));
951
+ expect(amountTransferred).to.equal(0.01);
952
+ }));
953
+ });
954
+ context('Distributed probabilites', async () => {
955
+ let marketId;
956
+ let outcomeIds = [0, 1, 2];
957
+
958
+ const initialOdds = [
959
+ [
960
+ [50, 50],
961
+ ['50000000', '50000000']
962
+ ],
963
+ [
964
+ [40, 60],
965
+ ['60000000', '40000000']
966
+ ],
967
+ [
968
+ [15, 20, 65],
969
+ ['1300000000', '975000000', '300000000']
970
+ ],
971
+ [
972
+ [65, 15, 20],
973
+ ['300000000', '1300000000', '975000000']
974
+ ],
975
+ [
976
+ [10, 20, 30, 40],
977
+ ['24000000000', '12000000000', '8000000000', '6000000000']
978
+ ]
979
+ ];
980
+
981
+ for (const [odds, expectedDistribution] of initialOdds) {
982
+ context(`Odds: ${odds}`, async () => {
983
+ before(mochaAsync(async () => {
984
+ const distribution = await predictionMarketContract.calcDistribution({odds});
985
+
986
+ expect(distribution).to.eql(expectedDistribution);
987
+
988
+ try {
989
+ const res = await predictionMarketContract.createMarket({
990
+ value,
991
+ name: `Market with ${odds} odds`,
992
+ image: 'foo-bar',
993
+ category: 'Foo;Bar',
994
+ oracleAddress: '0x0000000000000000000000000000000000000001', // TODO
995
+ duration: moment(moment().add(1, 'month').format('YYYY-MM-DD')).unix(),
996
+ outcomes: ['A', 'B', 'C', 'D', 'E'].slice(0, odds.length),
997
+ token: tokenERC20Contract.getAddress(),
998
+ odds,
999
+ });
1000
+ expect(res.status).to.equal(true);
1001
+ } catch(e) {
1002
+ console.log(e);
1003
+ }
1004
+
1005
+ const marketIds = await predictionMarketContract.getMarkets();
1006
+ marketId = marketIds[marketIds.length - 1];
1007
+ }));
1008
+
1009
+ it('market prices should match odds', mochaAsync(async () => {
1010
+ const outcomePrices = await predictionMarketContract.getMarketPrices({marketId});
1011
+
1012
+ for (let i = 0; i < odds.length; i++) {
1013
+ const price = outcomePrices.outcomes[i];
1014
+ const expectedPrice = odds[i] / 100;
1015
+
1016
+ expect(price).to.closeTo(expectedPrice, 0.00000001);
1017
+ }
1018
+ }));
1019
+
1020
+ it('market shares should match shares', mochaAsync(async () => {
1021
+ const outcomeShares = await predictionMarketContract.getMarketShares({marketId});
1022
+
1023
+ for (let i = 0; i < odds.length; i++) {
1024
+ const shares = outcomeShares.outcomes[i];
1025
+ const minOdds = Math.min(...odds);
1026
+ const expectedShares = value * minOdds / odds[i];
1027
+
1028
+ expect(shares).to.closeTo(expectedShares, 0.00000001);
1029
+ }
1030
+ }));
1031
+
1032
+ it('market liquidity should match liquidity', mochaAsync(async () => {
1033
+ const marketData = await predictionMarketContract.getMarketData({marketId});
1034
+ const expectedLiquidity = value;
1035
+
1036
+ expect(marketData.liquidity).to.equal(expectedLiquidity);
1037
+ }));
1038
+ });
1039
+ }
1040
+ });
1041
+ });
1042
+ });