From e8ee341897bbaca9adc6182bd52cffbea9c421e1 Mon Sep 17 00:00:00 2001 From: Alan Lu Date: Thu, 9 Nov 2017 14:23:18 -0600 Subject: [PATCH 1/2] Made error message for malformed logs more informative --- src/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.js b/src/utils.js index c451ce3..e476911 100644 --- a/src/utils.js +++ b/src/utils.js @@ -265,9 +265,9 @@ export function requireEventFromTXResult (result, eventName) { let matchingLogs = _.filter(result.logs, (l) => l.event === eventName) if (matchingLogs.length < 1) { - throw new Error(`could not find any logs in result ${result} corresponding to event ${eventName}`) + throw new Error(`could not find any logs in transaction ${result.tx} corresponding to event ${eventName}`) } else if (matchingLogs.length > 1) { - throw new Error(`found too many logs in result ${result} corresponding to event ${eventName}`) + throw new Error(`found too many logs in transaction ${result.tx} corresponding to event ${eventName}`) } return matchingLogs[0] From 76726fff8a2775ea110e55b36a7257534a768c89 Mon Sep 17 00:00:00 2001 From: Alan Lu Date: Thu, 9 Nov 2017 15:22:24 -0600 Subject: [PATCH 2/2] Implemented buying and selling with a limit margin --- src/markets.js | 22 ++++++++++++++++++---- test/test_gnosis.js | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/markets.js b/src/markets.js index 45f16f4..796778b 100644 --- a/src/markets.js +++ b/src/markets.js @@ -41,6 +41,7 @@ export const createMarket = wrapWeb3Function((self, opts) => ({ * @param {(Contract|string)} opts.market - The market to buy tokens from * @param {(number|string|BigNumber)} opts.outcomeTokenIndex - The index of the outcome * @param {(number|string|BigNumber)} opts.outcomeTokenCount - Number of outcome tokens to buy + * @param {(number|string|BigNumber)} [opts.limitMargin=0] - Because transactions change prices, there is a chance that the cost limit for the buy, which is set to the cost according to the latest mined block, will prevent the buy transaction from succeeding. This parameter can be used to increase the cost limit by a fixed proportion. For example, specifying `limitMargin: 0.05` will make the cost limit increase by 5%. * @param {(number|string|BigNumber)} [opts.approvalAmount] - Amount of collateral to allow market to spend. If unsupplied or null, allowance will be reset to the `approvalResetAmount` only if necessary. If set to 0, the approval transaction will be skipped. * @param {(number|string|BigNumber)} [opts.approvalResetAmount] - Set to this amount when resetting market collateral allowance. If unsupplied or null, will be the cost of this transaction. * @returns {BigNumber} How much collateral tokens caller paid @@ -59,7 +60,7 @@ export async function buyOutcomeTokens() { const txOpts = _.pick(opts, ['from', 'to', 'value', 'gas', 'gasPrice']) - let { approvalAmount, approvalResetAmount } = opts || {} + let { approvalAmount, approvalResetAmount, limitMargin } = opts || {} const market = await this.contracts.Market.at(marketAddress) const collateralToken = await this.contracts.Token.at( @@ -67,8 +68,14 @@ export async function buyOutcomeTokens() { await market.eventContract(txOpts) ).collateralToken() ) + + if(limitMargin == null) { + limitMargin = 0 + } + const baseCost = await this.lmsrMarketMaker.calcCost(marketAddress, outcomeTokenIndex, outcomeTokenCount, txOpts) - const cost = baseCost.add(await market.calcMarketFee(baseCost, txOpts)) + const baseCostWithFee = baseCost.add(await market.calcMarketFee(baseCost, txOpts)) + const cost = baseCostWithFee.mul(this.web3.toBigNumber(1).add(limitMargin)).round() if(approvalResetAmount == null) { approvalResetAmount = cost @@ -119,6 +126,7 @@ buyOutcomeTokens.estimateGas = async function({ using }) { * @param {(Contract|string)} opts.market - The market to sell tokens to * @param {(number|string|BigNumber)} opts.outcomeTokenIndex - The index of the outcome * @param {(number|string|BigNumber)} opts.outcomeTokenCount - Number of outcome tokens to sell + * @param {(number|string|BigNumber)} [opts.limitMargin=0] - Because transactions change profits, there is a chance that the profit limit for the sell, which is set to the profit according to the latest mined block, will prevent the sell transaction from succeeding. This parameter can be used to decrease the profit limit by a fixed proportion. For example, specifying `limitMargin: 0.05` will make the profit limit decrease by 5%. * @param {(number|string|BigNumber)} [opts.approvalAmount] - Amount of outcome tokens to allow market to handle. If unsupplied or null, allowance will be reset to the `approvalResetAmount` only if necessary. If set to 0, the approval transaction will be skipped. * @param {(number|string|BigNumber)} [opts.approvalResetAmount] - Set to this amount when resetting market outcome token allowance. If unsupplied or null, will be the sale amount specified by `outcomeTokenCount`. * @returns {BigNumber} How much collateral tokens caller received from sale @@ -137,7 +145,7 @@ export async function sellOutcomeTokens() { const txOpts = _.pick(opts, ['from', 'to', 'value', 'gas', 'gasPrice']) - let { approvalAmount, approvalResetAmount } = opts || {} + let { approvalAmount, approvalResetAmount, limitMargin } = opts || {} const market = await this.contracts.Market.at(marketAddress) const outcomeToken = await this.contracts.Token.at( @@ -145,8 +153,14 @@ export async function sellOutcomeTokens() { await market.eventContract(txOpts) ).outcomeTokens(outcomeTokenIndex) ) + + if(limitMargin == null) { + limitMargin = 0 + } + const baseProfit = await this.lmsrMarketMaker.calcProfit(marketAddress, outcomeTokenIndex, outcomeTokenCount, txOpts) - const minProfit = baseProfit.sub(await market.calcMarketFee(baseProfit, txOpts)) + const baseProfitWithFee = baseProfit.sub(await market.calcMarketFee(baseProfit, txOpts)) + const minProfit = baseProfitWithFee.mul(this.web3.toBigNumber(1).sub(limitMargin)).round() if(approvalResetAmount == null) { approvalResetAmount = outcomeTokenCount diff --git a/test/test_gnosis.js b/test/test_gnosis.js index 9ceb17b..19ccc01 100644 --- a/test/test_gnosis.js +++ b/test/test_gnosis.js @@ -266,13 +266,14 @@ describe('Gnosis', function () { }) describe('.lmsr', () => { - let gnosis, oracle, event, ipfsHash, market, netOutcomeTokensSold, funding, feeFactor + let gnosis, oracle, event, ipfsHash, market, netOutcomeTokensSold, funding, feeFactor, participants beforeEach(async () => { netOutcomeTokensSold = [0, 0] feeFactor = 5000 // 0.5% gnosis = await Gnosis.create(options) + participants = gnosis.web3.eth.accounts.slice(0, 4) ipfsHash = await gnosis.publishEventDescription(description) oracle = await gnosis.createCentralizedOracle(ipfsHash) event = await gnosis.createCategoricalEvent({ @@ -401,7 +402,6 @@ describe('Gnosis', function () { actualProfit = await gnosis.sellOutcomeTokens({ market, outcomeTokenIndex, outcomeTokenCount: numOutcomeTokensToSell, - feeFactor, }) assert(isClose(localCalculatedProfit.valueOf(), actualProfit.valueOf())) assert(localCalculatedProfit.lte(actualProfit.valueOf())) @@ -569,6 +569,37 @@ describe('Gnosis', function () { assert.equal(outcomeAllowanceAfter2.valueOf(), approvalResetAmount - 2 * amountToSell) }) + it('can buy and sell with a limit margin', async () => { + const outcomeTokenIndex = 0 + const outcomeTokenCount = 4e16 + const amountToStart = 1e18 + const limitMargin = 0.05; + + (await Promise.all(participants.map( + (p) => gnosis.etherToken.deposit({ value: amountToStart, from: p }) + ))).forEach((res) => requireEventFromTXResult(res, 'Deposit')); + + (await Promise.all(participants.map( + (p) => gnosis.etherToken.balanceOf(p) + ))).forEach((b) => assert(b.gte(amountToStart))); + + (await Promise.all(participants.map( + (p) => gnosis.buyOutcomeTokens({ + market, outcomeTokenIndex, outcomeTokenCount, + limitMargin, + from: p, + }) + ))); + + (await Promise.all(participants.map( + (p) => gnosis.sellOutcomeTokens({ + market, outcomeTokenIndex, outcomeTokenCount, + limitMargin, + from: p, + }) + ))) + }) + it('accepts strings for outcome token index', async () => { let outcomeTokenIndex = 0 let outcomeTokenCount = 1000000000