UNPKG

@gnosis.pm/pm-contracts

Version:

Collection of smart contracts for the Gnosis prediction market platform

240 lines (199 loc) 11.9 kB
const _ = require('lodash') const BigNumber = require('bignumber.js') const utils = require('./utils') const { ONE, isClose, lmsrMarginalPrice, getParamFromTxEvent } = utils const EventFactory = artifacts.require('EventFactory') const CentralizedOracleFactory = artifacts.require('CentralizedOracleFactory') const StandardMarketFactory = artifacts.require('StandardMarketFactory') const LMSRMarketMaker = artifacts.require('LMSRMarketMaker') const WETH9 = artifacts.require('WETH9') const OutcomeToken = artifacts.require('OutcomeToken') const StandardMarket = artifacts.require('StandardMarket') const CategoricalEvent = artifacts.require('CategoricalEvent') contract('MarketMaker', function(accounts) { let eventFactory let centralizedOracleFactory let standardMarketFactory let lmsrMarketMaker let etherToken beforeEach(async () => { eventFactory = await EventFactory.deployed() centralizedOracleFactory = await CentralizedOracleFactory.deployed() standardMarketFactory = await StandardMarketFactory.deployed() lmsrMarketMaker = await LMSRMarketMaker.deployed.call() etherToken = await WETH9.deployed() }) it.skip('should move price of an outcome to 0 after participants sell lots of that outcome to market maker', async () => { // Create event const numOutcomes = 2 const ipfsHash = web3.utils.utf8ToHex('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG') const oracleAddress = await getParamFromTxEvent( await centralizedOracleFactory.createCentralizedOracle(ipfsHash), 'centralizedOracle') const event = await getParamFromTxEvent( await eventFactory.createCategoricalEvent(etherToken.address, oracleAddress, numOutcomes), 'categoricalEvent', CategoricalEvent) // Create market const investor = 0 const feeFactor = 0 // 0% const market = await getParamFromTxEvent( await standardMarketFactory.createMarket(event.address, lmsrMarketMaker.address, feeFactor, { from: accounts[investor] }), 'market', StandardMarket) // Fund market const funding = 1e17 await etherToken.deposit({ value: funding, from: accounts[investor] }) assert.equal(await etherToken.balanceOf.call(accounts[investor]), funding.toString()) await etherToken.approve(market.address, funding.toString(), { from: accounts[investor] }) await market.fund(funding.toString(), { from: accounts[investor] }) assert.equal(await etherToken.balanceOf.call(accounts[investor]), 0) // User buys all outcomes const trader = 1 const outcome = 1 const outcomeToken = await OutcomeToken.at(await event.outcomeTokens.call(outcome)) const tokenCount = 1e18 const loopCount = 10 await etherToken.deposit({ value: tokenCount * loopCount, from: accounts[trader] }) await etherToken.approve(event.address, (tokenCount * loopCount).toString(), { from: accounts[trader] }) await event.buyAllOutcomes((tokenCount * loopCount).toString(), { from: accounts[trader] }) // User sells tokens const buyerBalance = await etherToken.balanceOf.call(accounts[trader]) let profit, outcomeTokenAmounts for(let i of _.range(loopCount)) { // Calculate profit for selling tokens outcomeTokenAmounts = Array.from({length: numOutcomes}, (v, i) => i === outcome ? (-tokenCount).toString() : 0) profit = (await lmsrMarketMaker.calcNetCost.call(market.address, outcomeTokenAmounts)).neg() if(profit == 0) break // Selling tokens await outcomeToken.approve(market.address, tokenCount.toString(), { from: accounts[trader] }) assert.equal((await getParamFromTxEvent( await market.trade(outcomeTokenAmounts, profit.neg(), { from: accounts[trader] }), 'outcomeTokenNetCost' )).neg().valueOf(), profit.toString()) let netOutcomeTokensSold = await Promise.all(_.range(numOutcomes).map((j) => market.netOutcomeTokensSold(j))) let expected = lmsrMarginalPrice(funding, netOutcomeTokensSold, outcome) let actual = new BigNumber((await lmsrMarketMaker.calcMarginalPrice.call(market.address, outcome.toString()))) .div(ONE.toString()) assert( isClose(actual, expected, 1e-3, 1e-9), `Marginal price calculation is off for iteration ${i}:\n` + ` funding: ${funding}\n` + ` net outcome tokens sold: ${netOutcomeTokensSold}\n` + ` actual: ${actual}\n` + ` expected: ${expected}` ) } // Selling of tokens is worth less than 1 Wei assert.equal(profit, 0) // User's Ether balance increased assert(BigNumber(await etherToken.balanceOf.call(accounts[trader]).valueOf()).gte(buyerBalance)) }) it.skip('should move price of an outcome to 1 after participants buy lots of that outcome from market maker', async () => { for(let [investor, funding, tokenCount] of [ [2, 1e17, 1e18], [3, 1, 10], [4, 1, 1e18], ]) { // Create event const numOutcomes = 2 const ipfsHash = web3.utils.utf8ToHex('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG') const oracleAddress = await getParamFromTxEvent( await centralizedOracleFactory.createCentralizedOracle(ipfsHash), 'centralizedOracle') const event = await getParamFromTxEvent( await eventFactory.createCategoricalEvent(etherToken.address, oracleAddress, numOutcomes), 'categoricalEvent', CategoricalEvent) // Create market const feeFactor = 0 // 0% const market = await getParamFromTxEvent( await standardMarketFactory.createMarket(event.address, lmsrMarketMaker.address, feeFactor, { from: accounts[investor] }), 'market', StandardMarket) // Fund market await etherToken.deposit({ value: funding, from: accounts[investor] }) assert.equal(await etherToken.balanceOf.call(accounts[investor]), funding.toString()) await etherToken.approve(market.address, funding.toString(), { from: accounts[investor] }) await market.fund(funding.toString(), { from: accounts[investor] }) assert.equal(await etherToken.balanceOf.call(accounts[investor]), 0) // User buys ether tokens const trader = 1 const outcome = 1 const loopCount = 10 await etherToken.deposit({ value: tokenCount * loopCount, from: accounts[trader] }) // User buys outcome tokens from market maker let cost, outcomeTokenAmounts for(let i of _.range(loopCount)) { // Calculate cost of buying tokens outcomeTokenAmounts = Array.from({length: numOutcomes}, (v, i) => i === outcome.toString() ? tokenCount.toString() : 0) cost = await lmsrMarketMaker.calcNetCost.call(market.address, outcomeTokenAmounts) // Buying tokens await etherToken.approve(market.address, tokenCount.toString(), { from: accounts[trader] }) assert.equal(await getParamFromTxEvent( await market.trade(outcomeTokenAmounts, cost.toString(), { from: accounts[trader] }), 'outcomeTokenNetCost' ).valueOf(), cost.toString()) let netOutcomeTokensSold = await Promise.all(_.range(numOutcomes).map((j) => market.netOutcomeTokensSold(j))) let expected = lmsrMarginalPrice(funding, netOutcomeTokensSold, outcome) let actual = new BigNumber(await lmsrMarketMaker.calcMarginalPrice.call(market.address, outcome.toString())).div(ONE.toString()) assert( isClose(actual, expected) || expected.toString() == 'NaN', `Marginal price calculation is off for iteration ${i}:\n` + ` funding: ${funding}\n` + ` net outcome tokens sold: ${netOutcomeTokensSold}\n` + ` actual: ${actual}\n` + ` expected: ${expected}` ) } // Price is equal to 1 assert.equal(cost.toString(), 1) } }) it('should allow buying and selling outcome tokens in the same transaction', async () => { // Create event const numOutcomes = 4 const ipfsHash = web3.utils.utf8ToHex('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG') const oracleAddress = await getParamFromTxEvent( await centralizedOracleFactory.createCentralizedOracle(ipfsHash), 'centralizedOracle') const event = await getParamFromTxEvent( await eventFactory.createCategoricalEvent(etherToken.address, oracleAddress, numOutcomes), 'categoricalEvent', CategoricalEvent) // Create market const investor = 5 const feeFactor = 0 // 0% const market = await getParamFromTxEvent( await standardMarketFactory.createMarket(event.address, lmsrMarketMaker.address, feeFactor, { from: accounts[investor] }), 'market', StandardMarket) // Fund market const funding = 1e18.toString() await etherToken.deposit({ value: funding, from: accounts[investor] }) assert.equal(await etherToken.balanceOf.call(accounts[investor]), funding) await etherToken.approve(market.address, funding, { from: accounts[investor] }) await market.fund(funding, { from: accounts[investor] }) assert.equal(await etherToken.balanceOf.call(accounts[investor]), 0) const trader = 6 const initialOutcomeTokenCount = 1e18 const initialWETH9Count = 10e18 // User buys all outcomes await etherToken.deposit({ value: (initialOutcomeTokenCount + initialWETH9Count).toString(), from: accounts[trader] }) await etherToken.approve(event.address, initialOutcomeTokenCount.toString(), { from: accounts[trader] }) await event.buyAllOutcomes(initialOutcomeTokenCount.toString(), { from: accounts[trader] }) // User trades with the market const tradeValues = [5e17, -1e18, -1e17, 2e18] const tradeValuesAsStrings = [5e17.toString(), (-1e18).toString(), (-1e17).toString(), 2e18.toString()] const cost = await lmsrMarketMaker.calcNetCost.call(market.address, tradeValuesAsStrings) if(cost.gt(0)) await etherToken.approve(market.address, cost, { from: accounts[trader] }) const outcomeTokens = await Promise.all(_.range(numOutcomes).map(i => event.outcomeTokens.call(i).then(tokenAddr => OutcomeToken.at(tokenAddr)))) await Promise.all(tradeValues.map((v, i) => [v, i]).filter(([v]) => v < 0).map(([v, i]) => outcomeTokens[i].approve(market.address, (-v).toString(), { from: accounts[trader] }))) assert.equal(await getParamFromTxEvent( await market.trade(tradeValuesAsStrings, cost, { from: accounts[trader] }), 'outcomeTokenNetCost' ), cost.toString()) // All state transitions associated with trade have been performed for(let [tradeValue, i] of tradeValues.map((v, i) => [v, i])) { assert.equal(await outcomeTokens[i].balanceOf.call(accounts[trader]), (initialOutcomeTokenCount + tradeValue).toString()) } assert.equal(await etherToken.balanceOf.call(accounts[trader]), new BigNumber(initialWETH9Count).minus(cost).toString() ) }) })