diff --git a/contracts/LoopringProtocolImpl.sol b/contracts/LoopringProtocolImpl.sol index 5fbcb618..6435e374 100644 --- a/contracts/LoopringProtocolImpl.sol +++ b/contracts/LoopringProtocolImpl.sol @@ -60,19 +60,6 @@ contract LoopringProtocolImpl is LoopringProtocol { uint public constant RATE_RATIO_SCALE = 10000; - // The following map is used to keep trace of order fill and cancellation - // history. - mapping (bytes32 => uint) public cancelledOrFilled; - - // This map is used to keep trace of order's cancellation history. - mapping (bytes32 => uint) public cancelled; - - // A map from address to its cutoff timestamp. - mapping (address => uint) public cutoffs; - - // A map from address to its trading-pair cutoff timestamp. - mapping (address => mapping (bytes20 => uint)) public tradingPairCutoffs; - /// @param orderHash The order's hash /// @param feeSelection - /// A miner-supplied value indicating if LRC (value = 0) @@ -204,8 +191,9 @@ contract LoopringProtocolImpl is LoopringProtocol { s ); - cancelled[orderHash] = cancelled[orderHash].add(cancelAmount); - cancelledOrFilled[orderHash] = cancelledOrFilled[orderHash].add(cancelAmount); + TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); + delegate.addCancelled(orderHash, cancelAmount); + delegate.addCancelledOrFilled(orderHash, cancelAmount); emit OrderCancelled(orderHash, cancelAmount); } @@ -220,10 +208,12 @@ contract LoopringProtocolImpl is LoopringProtocol { uint t = (cutoff == 0 || cutoff >= block.timestamp) ? block.timestamp : cutoff; bytes20 tokenPair = bytes20(token1) ^ bytes20(token2); - require(tradingPairCutoffs[msg.sender][tokenPair] < t); + TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); + + require(delegate.tradingPairCutoffs(msg.sender, tokenPair) < t); // "attempted to set cutoff to a smaller value" - tradingPairCutoffs[msg.sender][tokenPair] = t; + delegate.setTradingPairCutoffs(tokenPair, t); emit OrdersCancelled( msg.sender, token1, @@ -238,10 +228,11 @@ contract LoopringProtocolImpl is LoopringProtocol { external { uint t = (cutoff == 0 || cutoff >= block.timestamp) ? block.timestamp : cutoff; + TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); - require(cutoffs[msg.sender] < t); // "attempted to set cutoff to a smaller value" + require(delegate.cutoffs(msg.sender) < t); // "attempted to set cutoff to a smaller value" - cutoffs[msg.sender] = t; + delegate.setCutoffs(t); emit AllOrdersCancelled(msg.sender, t); } @@ -287,8 +278,10 @@ contract LoopringProtocolImpl is LoopringProtocol { // Assemble input data into structs so we can pass them to other functions. // This method also calculates ringHash, therefore it must be called before // calling `verifyRingSignatures`. + TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); OrderState[] memory orders = assembleOrders( params, + delegate, addressList, uintArgsList, uint8ArgsList, @@ -299,7 +292,7 @@ contract LoopringProtocolImpl is LoopringProtocol { verifyTokensRegistered(params, orders); - handleRing(_ringIndex, params, orders); + handleRing(_ringIndex, params, orders, delegate); ringIndex = _ringIndex + 1; } @@ -366,12 +359,12 @@ contract LoopringProtocolImpl is LoopringProtocol { function handleRing( uint64 _ringIndex, RingParams params, - OrderState[] orders + OrderState[] orders, + TokenTransferDelegate delegate ) private { address _lrcTokenAddress = lrcTokenAddress; - TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); // Do the hard work. verifyRingHasNoSubRing(params.ringSize, orders); @@ -460,9 +453,9 @@ contract LoopringProtocolImpl is LoopringProtocol { // Update fill records if (state.buyNoMoreThanAmountB) { - cancelledOrFilled[state.orderHash] += nextFillAmountS; + delegate.addCancelledOrFilled(state.orderHash, nextFillAmountS); } else { - cancelledOrFilled[state.orderHash] += state.fillAmountS; + delegate.addCancelledOrFilled(state.orderHash, state.fillAmountS); } orderHashList[i] = state.orderHash; @@ -724,7 +717,7 @@ contract LoopringProtocolImpl is LoopringProtocol { if (state.buyNoMoreThanAmountB) { amount = state.amountB.tolerantSub( - cancelledOrFilled[state.orderHash] + delegate.cancelledOrFilled(state.orderHash) ); state.amountS = amount.mul(state.amountS) / state.amountB; @@ -733,7 +726,7 @@ contract LoopringProtocolImpl is LoopringProtocol { state.amountB = amount; } else { amount = state.amountS.tolerantSub( - cancelledOrFilled[state.orderHash] + delegate.cancelledOrFilled(state.orderHash) ); state.amountB = amount.mul(state.amountB) / state.amountS; @@ -809,6 +802,7 @@ contract LoopringProtocolImpl is LoopringProtocol { /// @return A list of orders. function assembleOrders( RingParams params, + TokenTransferDelegate delegate, address[4][] addressList, uint[6][] uintArgsList, uint8[1][] uint8ArgsList, @@ -821,7 +815,6 @@ contract LoopringProtocolImpl is LoopringProtocol { orders = new OrderState[](params.ringSize); for (uint i = 0; i < params.ringSize; i++) { - bool marginSplitAsFee = (params.feeSelections & (uint16(1) << i)) > 0; orders[i] = OrderState( addressList[i][0], @@ -863,6 +856,8 @@ contract LoopringProtocolImpl is LoopringProtocol { params.ringHash ^= orderHash; } + validateOrdersCutoffs(orders, delegate); + params.ringHash = keccak256( params.ringHash, params.miner, @@ -887,11 +882,23 @@ contract LoopringProtocolImpl is LoopringProtocol { require(order.validSince <= block.timestamp); // order is too early to match require(order.validUntil > block.timestamp); // order is expired + } - bytes20 tradingPair = bytes20(order.tokenS) ^ bytes20(order.tokenB); - require(order.validSince > tradingPairCutoffs[order.owner][tradingPair]); - // order trading pair is cut off - require(order.validSince > cutoffs[order.owner]); // order is cut off + function validateOrdersCutoffs(OrderState[] orders, TokenTransferDelegate delegate) + private + view + { + address[] memory owners = new address[](orders.length); + bytes20[] memory tradingPairs = new bytes20[](orders.length); + uint[] memory validSinceTimes = new uint[](orders.length); + + for (uint i = 0; i < orders.length; i++) { + owners[i] = orders[i].owner; + tradingPairs[i] = bytes20(orders[i].tokenS) ^ bytes20(orders[i].tokenB); + validSinceTimes[i] = orders[i].validSince; + } + + delegate.checkCutoffsBatch(owners, tradingPairs, validSinceTimes); } /// @dev Get the Keccak-256 hash of order with specified parameters. @@ -903,7 +910,7 @@ contract LoopringProtocolImpl is LoopringProtocol { returns (bytes32) { return keccak256( - address(this), + delegateAddress, order.owner, order.tokenS, order.tokenB, @@ -950,6 +957,7 @@ contract LoopringProtocolImpl is LoopringProtocol { returns (uint) { bytes20 tokenPair = bytes20(token1) ^ bytes20(token2); - return tradingPairCutoffs[orderOwner][tokenPair]; + TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); + return delegate.tradingPairCutoffs(orderOwner, tokenPair); } } diff --git a/contracts/TokenTransferDelegate.sol b/contracts/TokenTransferDelegate.sol index 5d11716a..ae9ad1e0 100644 --- a/contracts/TokenTransferDelegate.sol +++ b/contracts/TokenTransferDelegate.sol @@ -25,6 +25,19 @@ contract TokenTransferDelegate { event AddressAuthorized(address indexed addr, uint32 number); event AddressDeauthorized(address indexed addr, uint32 number); + // The following map is used to keep trace of order fill and cancellation + // history. + mapping (bytes32 => uint) public cancelledOrFilled; + + // This map is used to keep trace of order's cancellation history. + mapping (bytes32 => uint) public cancelled; + + // A map from address to its cutoff timestamp. + mapping (address => uint) public cutoffs; + + // A map from address to its trading-pair cutoff timestamp. + mapping (address => mapping (bytes20 => uint)) public tradingPairCutoffs; + /// @dev Add a Loopring protocol address. /// @param addr A loopring protocol address. function authorizeAddress( @@ -73,4 +86,20 @@ contract TokenTransferDelegate { public view returns (bool); + + function addCancelled(bytes32 orderHash, uint cancelAmount) + external; + + function addCancelledOrFilled(bytes32 orderHash, uint cancelOrFillAmount) + external; + + function setCutoffs(uint t) + external; + + function setTradingPairCutoffs(bytes20 tokenPair, uint t) + external; + + function checkCutoffsBatch(address[] owners, bytes20[] tradingPairs, uint[] validSince) + external + view; } diff --git a/contracts/TokenTransferDelegateImpl.sol b/contracts/TokenTransferDelegateImpl.sol index 8d7b831e..32f33eee 100644 --- a/contracts/TokenTransferDelegateImpl.sol +++ b/contracts/TokenTransferDelegateImpl.sol @@ -246,4 +246,47 @@ contract TokenTransferDelegateImpl is TokenTransferDelegate, Claimable { ); } } + + function addCancelled(bytes32 orderHash, uint cancelAmount) + onlyAuthorized + external + { + cancelled[orderHash] = cancelled[orderHash].add(cancelAmount); + } + + function addCancelledOrFilled(bytes32 orderHash, uint cancelOrFillAmount) + onlyAuthorized + external + { + cancelledOrFilled[orderHash] = cancelledOrFilled[orderHash].add(cancelOrFillAmount); + } + + function setCutoffs(uint t) + onlyAuthorized + external + { + cutoffs[tx.origin] = t; + } + + function setTradingPairCutoffs(bytes20 tokenPair, uint t) + onlyAuthorized + external + { + tradingPairCutoffs[tx.origin][tokenPair] = t; + } + + function checkCutoffsBatch(address[] owners, bytes20[] tradingPairs, uint[] validSince) + external + view + { + uint len = owners.length; + require(len == tradingPairs.length); + require(len == validSince.length); + + for(uint i = 0; i < len; i++) { + require(validSince[i] > tradingPairCutoffs[owners[i]][tradingPairs[i]]); // order trading pair is cut off + require(validSince[i] > cutoffs[owners[i]]); // order is cut off + } + } + } diff --git a/test/testLoopringProtocolImpl.ts b/test/testLoopringProtocolImpl.ts index dc61c0d7..b795bd80 100644 --- a/test/testLoopringProtocolImpl.ts +++ b/test/testLoopringProtocolImpl.ts @@ -111,7 +111,7 @@ contract("LoopringProtocolImpl", (accounts: string[]) => { const currBlockNumber = web3.eth.blockNumber; currBlockTimeStamp = web3.eth.getBlock(currBlockNumber).timestamp; - ringFactory = new RingFactory(LoopringProtocolImpl.address, + ringFactory = new RingFactory(TokenTransferDelegate.address, eosAddress, neoAddress, lrcAddress, @@ -856,7 +856,7 @@ contract("LoopringProtocolImpl", (accounts: string[]) => { order.params.lrcFee, cancelAmount]; - const cancelledOrFilledAmount0 = await loopringProtocolImpl.cancelledOrFilled(order.params.orderHashHex); + const cancelledOrFilledAmount0 = await tokenTransferDelegate.cancelledOrFilled(order.params.orderHashHex); const tx = await loopringProtocolImpl.cancelOrder(addresses, orderValues, order.params.buyNoMoreThanAmountB, @@ -866,7 +866,7 @@ contract("LoopringProtocolImpl", (accounts: string[]) => { order.params.s, {from: order.owner}); - const cancelledOrFilledAmount1 = await loopringProtocolImpl.cancelledOrFilled(order.params.orderHashHex); + const cancelledOrFilledAmount1 = await tokenTransferDelegate.cancelledOrFilled(order.params.orderHashHex); assert.equal(cancelledOrFilledAmount1.minus(cancelledOrFilledAmount0).toNumber(), cancelAmount.toNumber(), "cancelled amount not match"); }); @@ -913,7 +913,7 @@ contract("LoopringProtocolImpl", (accounts: string[]) => { describe("cancelAllOrders", () => { it("should be able to set cutoffs", async () => { await loopringProtocolImpl.cancelAllOrders(new BigNumber(1508566125), {from: order2Owner}); - const cutoff = await loopringProtocolImpl.cutoffs(order2Owner); + const cutoff = await tokenTransferDelegate.cutoffs(order2Owner); assert.equal(cutoff.toNumber(), 1508566125, "cutoff not set correctly"); });