From ac5bd0c34164f793397fd7a4dca3e27797b9ddbe Mon Sep 17 00:00:00 2001 From: Jens Ivar Jordre Date: Mon, 30 Apr 2018 13:04:27 +0200 Subject: [PATCH] fixup! Add function challengeFraudulentDealByTrade (#14) --- contracts/Exchange.sol | 52 +++++++-------- test/scenarios/Exchange.js | 127 +++++++++++++++++++++++++++++++++++-- 2 files changed, 143 insertions(+), 36 deletions(-) diff --git a/contracts/Exchange.sol b/contracts/Exchange.sol index a6595367..0a231d90 100644 --- a/contracts/Exchange.sol +++ b/contracts/Exchange.sol @@ -8,7 +8,7 @@ pragma solidity ^0.4.21; pragma experimental ABIEncoderV2 ; -import ". / SafeMath . sol "; +import "./SafeMath.sol"; import "./Configuration.sol"; @@ -148,12 +148,6 @@ contract Exchange { } } - // TODO Remove - function getUint256Values(Trade trade) public view returns (uint256, uint256) { - uint256 feePartsPer = configuration.partsPer(); - return (trade.singleFees.intended, trade.amount.mul(configuration.getTradeMakerFee(0, 0)).div(feePartsPer)); - } - /// @notice Submit a trade candidate in continuous Fraudulent Deal Challenge (FDC) /// @dev The seizure of client funds remains to be enabled once implemented in ClientFund contract /// @param trade Fraudulent trade candidate @@ -161,39 +155,42 @@ contract Exchange { uint256 feePartsPer = configuration.partsPer(); uint256 amountConjugate = trade.amount.div(trade.rate); - Trader memory maker; - Trader memory taker; - if (LiquidityRole.Maker == trade.buyer.liquidityRole) { - maker = trade.buyer; - taker = trade.seller; - } else { - maker = trade.seller; - taker = trade.buyer; - } - - bool genuine = (getHashOfTrade(trade) == trade.seal.hash) - && (trade.singleFees.intended <= trade.amount.mul(configuration.getTradeMakerFee(0, 0)).div(feePartsPer)) - && (trade.singleFees.intended == trade.amount.mul(configuration.getTradeMakerFee(0, maker.rollingVolume)).div(feePartsPer)) - && (trade.singleFees.intended >= trade.amount.mul(configuration.getTradeMakerMinimumFee(0)).div(feePartsPer)) - && (trade.singleFees.conjugate <= amountConjugate.mul(configuration.getTradeTakerFee(0, 0)).div(feePartsPer)) - && (trade.singleFees.conjugate == amountConjugate.mul(configuration.getTradeTakerFee(0, taker.rollingVolume)).div(feePartsPer)) + // Gauge the genuineness of maker fee. Depending on whether maker is buyer or seller + // this result is baked into genuineness by buyer and seller below. + uint256 makerRollingVolume = (LiquidityRole.Maker == trade.buyer.liquidityRole ? trade.buyer.rollingVolume : trade.seller.rollingVolume); + bool genuineMakerFee = (trade.singleFees.intended <= trade.amount.mul(configuration.getTradeMakerFee(0, 0)).div(feePartsPer)) + && (trade.singleFees.intended == trade.amount.mul(configuration.getTradeMakerFee(0, makerRollingVolume)).div(feePartsPer)) + && (trade.singleFees.intended >= trade.amount.mul(configuration.getTradeMakerMinimumFee(0)).div(feePartsPer)); + + // Gauge the genuineness of taker fee. Depending on whether maker is buyer or seller + // this result is baked into genuineness by buyer and seller below. + uint256 takerRollingVolume = (LiquidityRole.Taker == trade.buyer.liquidityRole ? trade.buyer.rollingVolume : trade.seller.rollingVolume); + bool genuineTakerFee = (trade.singleFees.conjugate <= amountConjugate.mul(configuration.getTradeTakerFee(0, 0)).div(feePartsPer)) + && (trade.singleFees.conjugate == amountConjugate.mul(configuration.getTradeTakerFee(0, takerRollingVolume)).div(feePartsPer)) && (trade.singleFees.conjugate >= amountConjugate.mul(configuration.getTradeTakerMinimumFee(0)).div(feePartsPer)); + // Genuineness that does not related to buyer or seller + bool genuine = (getHashOfTrade(trade) == trade.seal.hash); + + // Genuineness affected by buyer bool genuineByBuyer = (trade.buyer._address != trade.seller._address) && (trade.buyer._address != owner) && (trade.buyer.balances.intended.current == trade.buyer.balances.intended.previous.add(trade.transfers.intended.single).sub(trade.singleFees.intended)) && (trade.buyer.balances.conjugate.current == trade.buyer.balances.conjugate.previous.sub(trade.transfers.conjugate.single)) && (trade.buyer.order.amount >= trade.buyer.order.residuals.current) && (trade.buyer.order.amount >= trade.buyer.order.residuals.previous) - && (trade.buyer.order.residuals.previous >= trade.buyer.order.residuals.current); + && (trade.buyer.order.residuals.previous >= trade.buyer.order.residuals.current) + && (LiquidityRole.Maker == trade.buyer.liquidityRole ? genuineMakerFee : genuineTakerFee); + // Genuineness affected by seller bool genuineBySeller = (trade.buyer._address != trade.seller._address) && (trade.seller._address != owner) && (trade.seller.balances.intended.current == trade.seller.balances.intended.previous.sub(trade.transfers.intended.single)) && (trade.seller.balances.conjugate.current == trade.seller.balances.conjugate.previous.add(trade.transfers.conjugate.single).sub(trade.singleFees.conjugate)) && (trade.seller.order.amount >= trade.seller.order.residuals.current) && (trade.seller.order.amount >= trade.seller.order.residuals.previous) - && (trade.seller.order.residuals.previous >= trade.seller.order.residuals.current); + && (trade.seller.order.residuals.previous >= trade.seller.order.residuals.current) + && (LiquidityRole.Maker == trade.seller.liquidityRole ? genuineMakerFee : genuineTakerFee); require(!genuine || !genuineByBuyer || !genuineBySeller); @@ -220,11 +217,6 @@ contract Exchange { // TODO Implement fully function getHashOfTrade(Trade trade) private pure returns (bytes32) { return keccak256(bytes32(trade.nonce)); - - // bytes memory prefix = "\x19Ethereum Signed Message:\n32"; - // bytes32 hash = keccak256(trade.nonce/*trade.buyOrderHash, trade.sellOrderHash, trade.buyerOrderNonce, trade.sellerOrderNonce, trade.buyer, trade.seller, trade.tokenAmount, trade.etherAmount, trade.token, trade.immediateSettlement*/); - // hash = keccak256(prefix, hash); - // return hash; } function changeConfiguration(Configuration _configuration) public onlyOwner { diff --git a/test/scenarios/Exchange.js b/test/scenarios/Exchange.js index ed11c2c4..54f195a5 100644 --- a/test/scenarios/Exchange.js +++ b/test/scenarios/Exchange.js @@ -122,11 +122,6 @@ module.exports = (glob) => { overrideOptions = {gasLimit: 1e6}; await ethersConfiguration.setTradeMakerFee(utils.bigNumberify(0), utils.parseUnits('0.001', 18), [], [], overrideOptions); - // await ethersConfiguration.setTradeMakerFee(utils.bigNumberify(0), utils.parseUnits('0.001', 18), [1], [utils.parseUnits('0.1', 18)], overrideOptions); - // const nominal = await ethersConfiguration.getTradeMakerFee(0, 0); - // console.log(`nominal: ${nominal.toNumber()}`); - // const discounted = await ethersConfiguration.getTradeMakerFee(0, 1); - // console.log(`discounted: ${discounted.toNumber()}`); await ethersConfiguration.setTradeMakerMinimumFee(utils.bigNumberify(0), utils.parseUnits('0.0001', 18), overrideOptions); await ethersConfiguration.setTradeTakerFee(utils.bigNumberify(0), utils.parseUnits('0.002', 18), [1], [utils.parseUnits('0.1', 18)], overrideOptions); await ethersConfiguration.setTradeTakerMinimumFee(utils.bigNumberify(0), utils.parseUnits('0.0002', 18), overrideOptions); @@ -223,7 +218,7 @@ module.exports = (glob) => { }; }); - it.only('should revert if trade is genuine', async () => { + it('should revert if trade is genuine', async () => { return ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions).should.be.rejected; }); @@ -381,6 +376,66 @@ module.exports = (glob) => { }); }); + describe('if (buyer\'s) maker fee is greater than the nominal maker fee', () => { + let seizedWalletCount; + + beforeEach(async () => { + trade.singleFees.intended = trade.singleFees.intended.mul(utils.bigNumberify(10)); + const count = await ethersExchange.seizedWalletsCount(); + seizedWalletCount = count.toNumber() + 1; + }); + + it('should record fraudulent trade, toggle operational mode and emit event', async () => { + ethersExchange.onchallengefraudulentdealbytradeevent = async () => { + (await ethersExchange.fraudulentTrade())[0].toNumber().should.equal(trade.nonce.toNumber()); + (await ethersExchange.operationalMode()).should.equal(1); + }; + await ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions); + const count = await ethersExchange.seizedWalletsCount(); + count.toNumber().should.equal(seizedWalletCount); + }); + }); + + describe('if (buyer\'s) maker fee is different than provided by Configuration contract', () => { + let seizedWalletCount; + + beforeEach(async () => { + trade.singleFees.intended = trade.singleFees.intended.mul(utils.bigNumberify(90)).div(utils.bigNumberify(100)); + const count = await ethersExchange.seizedWalletsCount(); + seizedWalletCount = count.toNumber() + 1; + }); + + it('should record fraudulent trade, toggle operational mode and emit event', async () => { + ethersExchange.onchallengefraudulentdealbytradeevent = async () => { + (await ethersExchange.fraudulentTrade())[0].toNumber().should.equal(trade.nonce.toNumber()); + (await ethersExchange.operationalMode()).should.equal(1); + }; + await ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions); + const count = await ethersExchange.seizedWalletsCount(); + count.toNumber().should.equal(seizedWalletCount); + }); + }); + + describe('if (buyer\'s) maker fee is smaller than the minimum maker fee', () => { + let seizedWalletCount; + + beforeEach(async () => { + trade.singleFees.intended = trade.singleFees.intended.div(utils.bigNumberify(100)); + const count = await ethersExchange.seizedWalletsCount(); + seizedWalletCount = count.toNumber() + 1; + }); + + it('should record fraudulent trade, toggle operational mode and emit event', async () => { + ethersExchange.onchallengefraudulentdealbytradeevent = async () => { + (await ethersExchange.fraudulentTrade())[0].toNumber().should.equal(trade.nonce.toNumber()); + (await ethersExchange.operationalMode()).should.equal(1); + }; + await ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions); + const count = await ethersExchange.seizedWalletsCount(); + count.toNumber().should.equal(seizedWalletCount); + }); + }); + describe('if seller address equals owner address', () => { let seizedWalletCount; @@ -500,6 +555,66 @@ module.exports = (glob) => { count.toNumber().should.equal(seizedWalletCount); }); }); + + describe('if (seller\'s) taker fee is greater than the nominal taker fee', () => { + let seizedWalletCount; + + beforeEach(async () => { + trade.singleFees.conjugate = trade.singleFees.conjugate.mul(utils.bigNumberify(10)); + const count = await ethersExchange.seizedWalletsCount(); + seizedWalletCount = count.toNumber() + 1; + }); + + it('should record fraudulent trade, toggle operational mode and emit event', async () => { + ethersExchange.onchallengefraudulentdealbytradeevent = async () => { + (await ethersExchange.fraudulentTrade())[0].toNumber().should.equal(trade.nonce.toNumber()); + (await ethersExchange.operationalMode()).should.equal(1); + }; + await ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions); + const count = await ethersExchange.seizedWalletsCount(); + count.toNumber().should.equal(seizedWalletCount); + }); + }); + + describe('if (seller\'s) taker fee is different than provided by Configuration contract', () => { + let seizedWalletCount; + + beforeEach(async () => { + trade.singleFees.conjugate = trade.singleFees.conjugate.mul(utils.bigNumberify(90)).div(utils.bigNumberify(100)); + const count = await ethersExchange.seizedWalletsCount(); + seizedWalletCount = count.toNumber() + 1; + }); + + it('should record fraudulent trade, toggle operational mode and emit event', async () => { + ethersExchange.onchallengefraudulentdealbytradeevent = async () => { + (await ethersExchange.fraudulentTrade())[0].toNumber().should.equal(trade.nonce.toNumber()); + (await ethersExchange.operationalMode()).should.equal(1); + }; + await ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions); + const count = await ethersExchange.seizedWalletsCount(); + count.toNumber().should.equal(seizedWalletCount); + }); + }); + + describe('if (seller\'s) taker fee is smaller than the minimum taker fee', () => { + let seizedWalletCount; + + beforeEach(async () => { + trade.singleFees.conjugate = trade.singleFees.conjugate.div(utils.bigNumberify(100)); + const count = await ethersExchange.seizedWalletsCount(); + seizedWalletCount = count.toNumber() + 1; + }); + + it('should record fraudulent trade, toggle operational mode and emit event', async () => { + ethersExchange.onchallengefraudulentdealbytradeevent = async () => { + (await ethersExchange.fraudulentTrade())[0].toNumber().should.equal(trade.nonce.toNumber()); + (await ethersExchange.operationalMode()).should.equal(1); + }; + await ethersExchange.challengeFraudulentDealByTrade(trade, overrideOptions); + const count = await ethersExchange.seizedWalletsCount(); + count.toNumber().should.equal(seizedWalletCount); + }); + }); }); }); };