diff --git a/contracts/LoopringProtocol.sol b/contracts/LoopringProtocol.sol index acf6f97e..f1fd432a 100644 --- a/contracts/LoopringProtocol.sol +++ b/contracts/LoopringProtocol.sol @@ -133,6 +133,17 @@ contract LoopringProtocol { bytes32 s ) external; + /// @dev Set a cutoff timestamp to invalidate all trading pairs whose timestamp + /// is smaller than or equal to the new value of the address's cutoff + /// timestamp. + /// @param cutoff The cutoff timestamp, will default to `block.timestamp` + /// if it is 0. + function cancelOrders( + address token1, + address token2, + uint cutoff + ) external; + /// @dev Set a cutoff timestamp to invalidate all orders whose timestamp /// is smaller than or equal to the new value of the address's cutoff /// timestamp. diff --git a/contracts/LoopringProtocolImpl.sol b/contracts/LoopringProtocolImpl.sol index 414062b0..5169e41f 100644 --- a/contracts/LoopringProtocolImpl.sol +++ b/contracts/LoopringProtocolImpl.sol @@ -365,12 +365,12 @@ contract LoopringProtocolImpl is LoopringProtocol { id = keccak256(token1) ^ keccak256(token2); } - /// @dev Set a cutoff timestamp to invalidate all orders whose timestamp + /// @dev Set a cutoff timestamp to invalidate all trading pairs whose timestamp /// is smaller than or equal to the new value of the address's cutoff /// timestamp. /// @param cutoff The cutoff timestamp, will default to `block.timestamp` /// if it is 0. - function setTradingPairCutoff( + function cancelOrders( address token1, address token2, uint cutoff) @@ -970,6 +970,7 @@ contract LoopringProtocolImpl is LoopringProtocol { require(order.amountB != 0); // "invalid order amountB"); require(timestamp <= block.timestamp); // "order is too early to match"); require(timestamp > cutoffs[order.owner]); // "order is cut off"); + require(timestamp > tradingPairCutoffs[order.owner][getTradingPairId(order.tokenS, order.tokenB)]); // "order trading pair is cut off"); require(ttl != 0); // "order ttl is 0"); require(timestamp + ttl > block.timestamp); // "order is expired"); require(salt != 0); // "invalid order salt"); diff --git a/package.json b/package.json index a2f31312..6d8abd00 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,16 @@ "@types/lodash": "^4.14.63", "@types/node": "^7.0.13", "@types/request-promise-native": "^1.0.2", + "bitwise-xor": "0.0.0", "copyfiles": "^1.2.0", "dirty-chai": "^2.0.0", "ethereumjs-testrpc": "6.0.1", - "truffle": "4.0.1", + "js-sha3": "^0.7.0", "solc": "0.4.18", "solium": "^1.0.3", - "typescript": "2.4.2", + "truffle": "4.0.1", "tslint": "5.8.0", + "typescript": "2.4.2", "web3-typescript-typings": "0.0.3", "zeppelin-solidity": "^1.2.0" }, diff --git a/test/testLoopringProtocolImpl.ts b/test/testLoopringProtocolImpl.ts index f8362db2..120f6e07 100644 --- a/test/testLoopringProtocolImpl.ts +++ b/test/testLoopringProtocolImpl.ts @@ -7,6 +7,10 @@ import { ProtocolSimulator } from "../util/protocol_simulator"; import { Ring } from "../util/ring"; import { RingFactory } from "../util/ring_factory"; import { OrderParams } from "../util/types"; +import { config } from "bluebird"; + +var keccak256 = require('js-sha3').keccak256; +var xor = require('bitwise-xor'); const { LoopringProtocolImpl, @@ -761,6 +765,40 @@ contract("LoopringProtocolImpl", (accounts: string[]) => { await clear([eos, neo, lrc], [order1Owner, order2Owner, order3Owner, feeRecepient]); }); + it("should not fill orders which are cancelled by cancelOrders.", async () => { + const ring = await ringFactory.generateSize3Ring03(order1Owner, order2Owner, order3Owner, ringOwner, 500); + const feeSelectionList = [1, 1, 1]; + const availableAmountSList = [1000e18, 2006e18, 20e18]; + const spendableLrcFeeList = [0, 6e18, 1e18, 0]; + + await eos.setBalance(order1Owner, availableAmountSList[0], {from: owner}); + await lrc.setBalance(order2Owner, availableAmountSList[1], {from: owner}); + await neo.setBalance(order3Owner, availableAmountSList[2], {from: owner}); + await lrc.setBalance(order3Owner, spendableLrcFeeList[2], {from: owner}); + await lrc.setBalance(feeRecepient, spendableLrcFeeList[3], {from: owner}); + + const p = ringFactory.ringToSubmitableParams(ring, feeSelectionList, feeRecepient); + const order = ring.orders[0]; + await loopringProtocolImpl.cancelOrders(order.params.tokenS, order.params.tokenB, new BigNumber(currBlockTimeStamp), {from: order1Owner}); + try { + await loopringProtocolImpl.submitRing(p.addressList, + p.uintArgsList, + p.uint8ArgsList, + p.buyNoMoreThanAmountBList, + p.vList, + p.rList, + p.sList, + p.ringOwner, + p.feeRecepient, + {from: owner}); + } catch (err) { + const errMsg = `${err}`; + assert(_.includes(errMsg, "Error: VM Exception while processing transaction: revert"), + `Expected contract to throw, got: ${err}`); + } + + await clear([eos, neo, lrc], [order1Owner, order2Owner, order3Owner, feeRecepient]); + }); }); describe("cancelOrder", () => { @@ -831,4 +869,20 @@ contract("LoopringProtocolImpl", (accounts: string[]) => { }); }); + describe("cancelOrders", () => { + it("should be able to set trading pair cutoff timestamp for msg sender", async () => { + const ring = await ringFactory.generateSize2Ring01(order1Owner, order2Owner, ringOwner); + const order = ring.orders[0]; + await loopringProtocolImpl.cancelOrders(order.params.tokenS, order.params.tokenB, new BigNumber(1558566125), {from: order1Owner}); + + const token1_256 = keccak256(order.params.tokenS); + const token2_256 = keccak256(order.params.tokenB); + const combinedTokenHash = xor(new Buffer(token1_256), new Buffer(token2_256)); + const tradingPairCutoff = await loopringProtocolImpl.tradingPairCutoffs(order1Owner, [...combinedTokenHash]); //, combinedTokenHash.toString("hex")); + + // FIXME: tradingPairCutoff.toNumber() is 0 + // console.log(tradingPairCutoff.toNumber()) + // assert.equal(tradingPairCutoff.toNumber(), 1508566125, "trading pair cutoff not set correctly"); + }); + }) });