diff --git a/, b/, new file mode 100644 index 00000000..e69de29b diff --git a/contracts/BrokerRegistryImpl.sol b/contracts/BrokerRegistryImpl.sol index e8ac9b0d..fdff5610 100644 --- a/contracts/BrokerRegistryImpl.sol +++ b/contracts/BrokerRegistryImpl.sol @@ -47,7 +47,7 @@ contract BrokerRegistryImpl is BrokerRegistry { ) { Broker storage b = brokerMap[owner][broker]; - authenticated = (b.addr != 0x0); + authenticated = (b.addr == broker); tracker = b.tracker; } diff --git a/contracts/BrokerTracker.sol b/contracts/BrokerTracker.sol index 55c9247c..e52f5d61 100644 --- a/contracts/BrokerTracker.sol +++ b/contracts/BrokerTracker.sol @@ -25,30 +25,22 @@ contract BrokerTracker { function getAllowance( address owner, address broker, - address tokenB, - address tokenS + address token ) public view - returns ( - uint allowanceB, - uint allowanceS, - uint lrcAllowance - ); + returns (uint allowance); /// @dev This method will be called from TokenTransferDelegateImpl, so /// it must check `msg.sender` is the address of LoopringProtocol. /// Check https://github.com/Loopring/token-listing/blob/master/ethereum/deployment.md /// for the current address of LoopringProtocol deployment. - function onSettlement( + function onTokenSpent( address owner, address broker, - address tokenB, - uint amountB, - address tokenS, - uint amountS, - uint lrcFee, - uint lrcReward + address token, + uint amount ) - public; + public + returns (bool ok); } \ No newline at end of file diff --git a/contracts/LoopringProtocol.sol b/contracts/LoopringProtocol.sol index 6b4c4ea5..8bc1d4c1 100644 --- a/contracts/LoopringProtocol.sol +++ b/contracts/LoopringProtocol.sol @@ -62,7 +62,7 @@ contract LoopringProtocol { /// @dev Cancel a order. cancel amount(amountS or amountB) can be specified /// in values. - /// @param addresses owner, tokenS, tokenB, wallet, authAddr + /// @param addresses owner, signer, tokenS, tokenB, wallet, authAddr /// @param values amountS, amountB, validSince (second), /// validUntil (second), lrcFee, and cancelAmount. /// @param option This indicates when a order should be considered @@ -71,7 +71,7 @@ contract LoopringProtocol { /// @param r Order ECDSA signature parameters r. /// @param s Order ECDSA signature parameters s. function cancelOrder( - address[5] addresses, + address[6] addresses, uint[6] values, uint8 option, uint8 v, @@ -103,7 +103,8 @@ contract LoopringProtocol { external; /// @dev Submit a order-ring for validation and settlement. - /// @param addressesList List of each order's owner, tokenS, wallet, authAddr. + /// @param addressesList List of each order's owner, signer, tokenS, wallet, + /// and authAddr. /// Note that next order's `tokenS` equals this order's /// `tokenB`. /// @param valuesList List of uint-type arguments in this order: @@ -124,7 +125,7 @@ contract LoopringProtocol { /// Bits to indicate fee selections. `1` represents margin /// split and `0` represents LRC as fee. function submitRing( - address[4][] addressesList, + address[5][] addressesList, uint[6][] valuesList, bool[] optionList, uint8[] vList, diff --git a/contracts/LoopringProtocolImpl.sol b/contracts/LoopringProtocolImpl.sol index 54e33a15..b6e0af38 100644 --- a/contracts/LoopringProtocolImpl.sol +++ b/contracts/LoopringProtocolImpl.sol @@ -45,9 +45,9 @@ contract LoopringProtocolImpl is LoopringProtocol { address public lrcTokenAddress = 0x0; address public tokenRegistryAddress = 0x0; address public delegateAddress = 0x0; + address public brokerRegistryAddress = 0x0; uint64 public ringIndex = 0; - uint8 public walletSplitPercentage = 0; // Exchange rate (rate) is the amount to sell or sold divided by the amount // to buy or bought. @@ -64,34 +64,23 @@ contract LoopringProtocolImpl is LoopringProtocol { uint public constant RATE_RATIO_SCALE = 10000; - /// @param orderHash The order's hash - /// @param feeSelection - - /// A miner-supplied value indicating if LRC (value = 0) - /// or margin split is choosen by the miner (value = 1). - /// We may support more fee model in the future. - /// @param rateS Sell Exchange rate provided by miner. - /// @param rateB Buy Exchange rate provided by miner. - /// @param fillAmountS Amount of tokenS to sell, calculated by protocol. - /// @param lrcReward The amount of LRC paid by miner to order owner in - /// exchange for margin split. - /// @param lrcFeeState The amount of LR paid by order owner to miner. - /// @param splitS TokenS paid to miner. - /// @param splitB TokenB paid to miner. struct Order { address owner; + address signer; address tokenS; address tokenB; address wallet; address authAddr; - uint validSince; - uint validUntil; uint amountS; uint amountB; + uint validSince; + uint validUntil; uint lrcFee; uint8 option; bool capByAmountB; bool marginSplitAsFee; bytes32 orderHash; + address trackerAddr; uint rateS; uint rateB; uint fillAmountS; @@ -104,7 +93,7 @@ contract LoopringProtocolImpl is LoopringProtocol { /// @dev A struct to capture parameters passed to submitRing method and /// various of other variables used across the submitRing core logics. struct Context { - address[4][] addressesList; + address[5][] addressesList; uint[6][] valuesList; uint8[] optionList; uint8[] vList; @@ -115,6 +104,7 @@ contract LoopringProtocolImpl is LoopringProtocol { uint64 ringIndex; uint ringSize; // computed TokenTransferDelegate delegate; + BrokerRegistry brokerRegistry; Order[] orders; bytes32 ringHash; // computed } @@ -123,23 +113,23 @@ contract LoopringProtocolImpl is LoopringProtocol { address _lrcTokenAddress, address _tokenRegistryAddress, address _delegateAddress, - uint _rateRatioCVSThreshold, - uint8 _walletSplitPercentage + address _brokerRegistryAddress, + uint _rateRatioCVSThreshold ) public { require(_lrcTokenAddress.isContract()); require(_tokenRegistryAddress.isContract()); require(_delegateAddress.isContract()); + require(_brokerRegistryAddress.isContract()); require(_rateRatioCVSThreshold > 0); - require(_walletSplitPercentage > 0 && _walletSplitPercentage < 100); lrcTokenAddress = _lrcTokenAddress; tokenRegistryAddress = _tokenRegistryAddress; delegateAddress = _delegateAddress; + brokerRegistryAddress = _brokerRegistryAddress; rateRatioCVSThreshold = _rateRatioCVSThreshold; - walletSplitPercentage = _walletSplitPercentage; } /// @dev Disable default function. @@ -151,7 +141,7 @@ contract LoopringProtocolImpl is LoopringProtocol { } function cancelOrder( - address[5] addresses, + address[6] addresses, uint[6] orderValues, uint8 option, uint8 v, @@ -163,22 +153,23 @@ contract LoopringProtocolImpl is LoopringProtocol { uint cancelAmount = orderValues[5]; require(cancelAmount > 0, "invalid cancelAmount"); - Order memory order = Order( addresses[0], addresses[1], addresses[2], addresses[3], addresses[4], - orderValues[2], - orderValues[3], + addresses[5], orderValues[0], orderValues[1], + orderValues[2], + orderValues[3], orderValues[4], option, option & OPTION_MASK_CAP_BY_AMOUNTB > 0 ? true : false, false, 0x0, + 0x0, 0, 0, 0, @@ -187,22 +178,32 @@ contract LoopringProtocolImpl is LoopringProtocol { 0, 0 ); - require( - msg.sender == order.owner, - "cancelOrder not submitted by owner" + msg.sender == order.signer, + "cancelOrder not submitted by signer" ); bytes32 orderHash = calculateOrderHash(order); verifySignature( - order.owner, + order.signer, orderHash, v, r, s ); + if (order.signer != order.owner) { + BrokerRegistry brokerRegistry = BrokerRegistry(brokerRegistryAddress); + bool authenticated; + address tracker; + (authenticated, tracker) = brokerRegistry.getBroker( + order.owner, + order.signer + ); + require(authenticated, "invalid broker"); + } + TokenTransferDelegate delegate = TokenTransferDelegate(delegateAddress); delegate.addCancelled(orderHash, cancelAmount); delegate.addCancelledOrFilled(orderHash, cancelAmount); @@ -254,7 +255,7 @@ contract LoopringProtocolImpl is LoopringProtocol { } function submitRing( - address[4][] addressesList, + address[5][] addressesList, uint[6][] valuesList, uint8[] optionList, uint8[] vList, @@ -277,6 +278,7 @@ contract LoopringProtocolImpl is LoopringProtocol { ringIndex, addressesList.length, TokenTransferDelegate(delegateAddress), + BrokerRegistry(brokerRegistryAddress), new Order[](addressesList.length), 0x0 // ringHash ); @@ -367,44 +369,57 @@ contract LoopringProtocolImpl is LoopringProtocol { uint[6] memory uintArgs = ctx.valuesList[i]; bool marginSplitAsFee = (ctx.feeSelections & (uint8(1) << i)) > 0; - ctx.orders[i] = Order( + Order memory order = Order( ctx.addressesList[i][0], ctx.addressesList[i][1], - ctx.addressesList[(i + 1) % ctx.ringSize][1], ctx.addressesList[i][2], + ctx.addressesList[(i + 2) % ctx.ringSize][1], ctx.addressesList[i][3], - uintArgs[2], - uintArgs[3], + ctx.addressesList[i][4], uintArgs[0], uintArgs[1], + uintArgs[2], + uintArgs[3], uintArgs[4], ctx.optionList[i], ctx.optionList[i] & OPTION_MASK_CAP_BY_AMOUNTB > 0 ? true : false, marginSplitAsFee, - bytes32(0), + 0x0, + 0x0, // brokderTracker uintArgs[5], uintArgs[1], 0, // fillAmountS 0, // lrcReward 0, // lrcFee 0, // splitS - 0 // splitB + 0. // splitB ); - validateOrder(ctx.orders[i]); + validateOrder(order); - bytes32 orderHash = calculateOrderHash(ctx.orders[i]); - ctx.orders[i].orderHash = orderHash; + order.orderHash = calculateOrderHash(order); verifySignature( - ctx.orders[i].owner, - orderHash, + order.signer, + order.orderHash, ctx.vList[i], ctx.rList[i], ctx.sList[i] ); - ctx.ringHash ^= orderHash; + if (order.signer != order.owner) { + BrokerRegistry brokerRegistry = BrokerRegistry(brokerRegistryAddress); + bool authenticated; + (authenticated, order.trackerAddr) = brokerRegistry.getBroker( + order.owner, + order.signer + ); + + require(authenticated, "invalid broker"); + } + + ctx.orders[i] = order; + ctx.ringHash ^= order.orderHash; } ctx.ringHash = keccak256( @@ -555,7 +570,13 @@ contract LoopringProtocolImpl is LoopringProtocol { require(order.amountS > 0, "amountS scaled to 0"); require(order.amountB > 0, "amountB scaled to 0"); - uint availableAmountS = getSpendable(ctx.delegate, order.tokenS, order.owner); + uint availableAmountS = getSpendable( + ctx.delegate, + order.tokenS, + order.owner, + order.signer, + order.trackerAddr + ); require(availableAmountS > 0, "spendable is 0"); order.fillAmountS = ( @@ -625,7 +646,9 @@ contract LoopringProtocolImpl is LoopringProtocol { uint lrcSpendable = getSpendable( ctx.delegate, lrcTokenAddress, - order.owner + order.owner, + order.signer, + order.trackerAddr ); // If the order is selling LRC, we need to calculate how much LRC @@ -667,7 +690,13 @@ contract LoopringProtocolImpl is LoopringProtocol { // Only check the available miner balance when absolutely needed if (!checkedMinerLrcSpendable && minerLrcSpendable < order.lrcFeeState) { checkedMinerLrcSpendable = true; - minerLrcSpendable = getSpendable(ctx.delegate, lrcTokenAddress, ctx.miner); + minerLrcSpendable = getSpendable( + ctx.delegate, + lrcTokenAddress, + ctx.miner, + 0x0, + 0x0 + ); } // Only calculate split when miner has enough LRC; @@ -714,7 +743,7 @@ contract LoopringProtocolImpl is LoopringProtocol { ) private { - bytes32[] memory batch = new bytes32[](ctx.ringSize * 7); // ringSize * (owner + tokenS + 4 amounts + wallet) + bytes32[] memory batch = new bytes32[](ctx.ringSize * 7); Fill[] memory fills = new Fill[](ctx.ringSize); uint p = 0; @@ -725,21 +754,29 @@ contract LoopringProtocolImpl is LoopringProtocol { // Store owner and tokenS of every order batch[p] = bytes32(order.owner); - batch[p + 1] = bytes32(order.tokenS); + batch[p + 1] = bytes32(order.signer); + batch[p + 2] = bytes32(order.trackerAddr); + batch[p + 3] = bytes32(order.tokenS); // Store all amounts - batch[p + 2] = bytes32(order.fillAmountS - prevSplitB); - batch[p + 3] = bytes32(prevSplitB + order.splitS); - batch[p + 4] = bytes32(order.lrcReward); - batch[p + 5] = bytes32(order.lrcFeeState); - batch[p + 6] = bytes32(order.wallet); - p += 7; + batch[p + 4] = bytes32(order.fillAmountS - prevSplitB); + batch[p + 5] = bytes32(prevSplitB + order.splitS); + batch[p + 6] = bytes32(order.lrcReward); + batch[p + 7] = bytes32(order.lrcFeeState); + batch[p + 8] = bytes32(order.wallet); + p += 9; // Update fill records if (order.capByAmountB) { - ctx.delegate.addCancelledOrFilled(order.orderHash, nextFillAmountS); + ctx.delegate.addCancelledOrFilled( + order.orderHash, + nextFillAmountS + ); } else { - ctx.delegate.addCancelledOrFilled(order.orderHash, order.fillAmountS); + ctx.delegate.addCancelledOrFilled( + order.orderHash, + order.fillAmountS + ); } fills[i] = Fill( @@ -754,11 +791,11 @@ contract LoopringProtocolImpl is LoopringProtocol { prevSplitB = order.splitB; } + // Do all transactions ctx.delegate.batchTransferToken( lrcTokenAddress, ctx.miner, - walletSplitPercentage, batch ); @@ -774,9 +811,9 @@ contract LoopringProtocolImpl is LoopringProtocol { function calculateOrderFillAmount( Order order, Order next, - uint i, - uint j, - uint smallestIdx + uint i, + uint j, + uint smallestIdx ) private pure @@ -819,19 +856,40 @@ contract LoopringProtocolImpl is LoopringProtocol { function getSpendable( TokenTransferDelegate delegate, address tokenAddress, - address tokenOwner + address tokenOwner, + address broker, + address trackerAddr ) private view - returns (uint) + returns (uint spendable) { ERC20 token = ERC20(tokenAddress); - uint allowance = token.allowance( + spendable = token.allowance( tokenOwner, address(delegate) ); - uint balance = token.balanceOf(tokenOwner); - return (allowance < balance ? allowance : balance); + if (spendable == 0) { + return; + } + uint amount = token.balanceOf(tokenOwner); + if (amount < spendable) { + spendable = amount; + if (spendable == 0) { + return; + } + } + + if (trackerAddr != 0x0) { + amount = BrokerTracker(trackerAddr).getAllowance( + tokenOwner, + broker, + tokenAddress + ); + if (amount < spendable) { + spendable = amount; + } + } } /// @dev validate order's parameters are OK. @@ -861,6 +919,7 @@ contract LoopringProtocolImpl is LoopringProtocol { return keccak256( delegateAddress, order.owner, + order.signer, order.tokenS, order.tokenB, order.wallet, diff --git a/contracts/TokenRegistry.sol b/contracts/TokenRegistry.sol index afc680f2..3a1727c1 100644 --- a/contracts/TokenRegistry.sol +++ b/contracts/TokenRegistry.sol @@ -31,7 +31,7 @@ contract TokenRegistry { event TokenUnregistered( address indexed addr, - string indexed symbol + string symbol ); function registerToken( diff --git a/contracts/TokenTransferDelegate.sol b/contracts/TokenTransferDelegate.sol index 887be681..b5655dbc 100644 --- a/contracts/TokenTransferDelegate.sol +++ b/contracts/TokenTransferDelegate.sol @@ -84,7 +84,6 @@ contract TokenTransferDelegate { function batchTransferToken( address lrcTokenAddress, address minerFeeRecipient, - uint8 walletSplitPercentage, bytes32[] batch ) external; @@ -96,19 +95,34 @@ contract TokenTransferDelegate { view returns (bool); - function addCancelled(bytes32 orderHash, uint cancelAmount) + function addCancelled( + bytes32 orderHash, + uint cancelAmount + ) external; - function addCancelledOrFilled(bytes32 orderHash, uint cancelOrFillAmount) + function addCancelledOrFilled( + bytes32 orderHash, + uint cancelOrFillAmount + ) external; - function setCutoffs(uint t) + function setCutoffs( + uint cutoff + ) external; - function setTradingPairCutoffs(bytes20 tokenPair, uint t) + function setTradingPairCutoffs( + bytes20 tokenPair, + uint cutoff + ) external; - function checkCutoffsBatch(address[] owners, bytes20[] tradingPairs, uint[] validSince) + function checkCutoffsBatch( + address[] owners, + bytes20[] tradingPairs, + uint[] validSince + ) external view; } diff --git a/contracts/TokenTransferDelegateImpl.sol b/contracts/TokenTransferDelegateImpl.sol index fd1f8db8..4f885884 100644 --- a/contracts/TokenTransferDelegateImpl.sol +++ b/contracts/TokenTransferDelegateImpl.sol @@ -21,6 +21,7 @@ pragma experimental "ABIEncoderV2"; import "./lib/Claimable.sol"; import "./lib/ERC20.sol"; import "./lib/MathUint.sol"; +import "./BrokerTracker.sol"; import "./TokenTransferDelegate.sol"; @@ -29,6 +30,16 @@ import "./TokenTransferDelegate.sol"; contract TokenTransferDelegateImpl is TokenTransferDelegate, Claimable { using MathUint for uint; + uint8 public walletSplitPercentage = 0; + + constructor( + uint8 _walletSplitPercentage + ) + public + { + require(_walletSplitPercentage >= 0 && _walletSplitPercentage <= 100); + walletSplitPercentage = _walletSplitPercentage; + } struct AddressInfo { address previous; uint32 index; @@ -133,48 +144,58 @@ contract TokenTransferDelegateImpl is TokenTransferDelegate, Claimable { } function batchTransferToken( - address lrcTokenAddress, - address minerFeeRecipient, - uint8 walletSplitPercentage, + address lrcAddr, + address miner, bytes32[] batch ) onlyAuthorized external { - uint len = batch.length; - require(len % 7 == 0); - require(walletSplitPercentage > 0 && walletSplitPercentage < 100); + require(batch.length % 9 == 0, "invalid batch"); - ERC20 lrc = ERC20(lrcTokenAddress); + address prevOwner = address(batch[batch.length - 9]); - address prevOwner = address(batch[len - 7]); - for (uint i = 0; i < len; i += 7) { + for (uint i = 0; i < batch.length; i += 9) { address owner = address(batch[i]); + address signer = address(batch[i + 1]); + address tracker = address(batch[i + 2]); // Pay token to previous order, or to miner as previous order's // margin split or/and this order's margin split. + address token = address(batch[i + 3]); + uint amount; - ERC20 token = ERC20(address(batch[i + 1])); - - // Here batch[i + 2] has been checked not to be 0. + // Here batch[i + 4] has been checked not to be 0. if (owner != prevOwner) { + amount = uint(batch[i + 4]); require( - token.transferFrom( + ERC20(token).transferFrom( owner, prevOwner, - uint(batch[i + 2]) + amount ) ); + + if (tracker != 0x0) { + require( + BrokerTracker(tracker).onTokenSpent( + owner, + signer, + token, + amount + ) + ); + } } // Miner pays LRx fee to order owner - uint lrcReward = uint(batch[i + 4]); - if (lrcReward != 0 && minerFeeRecipient != owner) { + amount = uint(batch[i + 6]); + if (amount != 0 && miner != owner) { require( - lrc.transferFrom( - minerFeeRecipient, + ERC20(lrcAddr).transferFrom( + miner, owner, - lrcReward + amount ) ); } @@ -182,21 +203,23 @@ contract TokenTransferDelegateImpl is TokenTransferDelegate, Claimable { // Split margin-split income between miner and wallet splitPayFee( token, - uint(batch[i + 3]), owner, - minerFeeRecipient, - address(batch[i + 6]), - walletSplitPercentage + miner, + signer, + tracker, + address(batch[i + 8]), + uint(batch[i + 5]) ); - // Split LRx fee income between miner and wallet + // Split LRC fee income between miner and wallet splitPayFee( - lrc, - uint(batch[i + 5]), + lrcAddr, owner, - minerFeeRecipient, - address(batch[i + 6]), - walletSplitPercentage + miner, + signer, + tracker, + address(batch[i + 8]), + uint(batch[i + 7]) ); prevOwner = owner; @@ -214,12 +237,13 @@ contract TokenTransferDelegateImpl is TokenTransferDelegate, Claimable { } function splitPayFee( - ERC20 token, - uint fee, + address token, address owner, - address minerFeeRecipient, - address walletFeeRecipient, - uint walletSplitPercentage + address miner, + address broker, + address tracker, + address wallet, + uint fee ) internal { @@ -227,28 +251,39 @@ contract TokenTransferDelegateImpl is TokenTransferDelegate, Claimable { return; } - uint walletFee = (walletFeeRecipient == 0x0) ? 0 : fee.mul(walletSplitPercentage) / 100; + uint walletFee = (wallet == 0x0) ? 0 : fee.mul(walletSplitPercentage) / 100; uint minerFee = fee - walletFee; - if (walletFee > 0 && walletFeeRecipient != owner) { + if (walletFee > 0 && wallet != owner) { require( - token.transferFrom( + ERC20(token).transferFrom( owner, - walletFeeRecipient, + wallet, walletFee ) ); } - if (minerFee > 0 && minerFeeRecipient != 0x0 && minerFeeRecipient != owner) { + if (minerFee > 0 && miner != 0x0 && miner != owner) { require( - token.transferFrom( + ERC20(token).transferFrom( owner, - minerFeeRecipient, + miner, minerFee ) ); } + + if (broker != 0x0) { + require( + BrokerTracker(tracker).onTokenSpent( + owner, + broker, + token, + fee + ) + ); + } } function addCancelled(bytes32 orderHash, uint cancelAmount)