diff --git a/contracts/helper/InputsHelper.sol b/contracts/helper/InputsHelper.sol new file mode 100644 index 00000000..f938acaf --- /dev/null +++ b/contracts/helper/InputsHelper.sol @@ -0,0 +1,57 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library InputsHelper { + + function nextAddress( + Data.Inputs inputs + ) + public + pure + returns (address) + { + return inputs.addressList[inputs.i++]; + } + + function nextUint( + Data.Inputs inputs + ) + public + pure + returns (uint) + { + return inputs.uintList[inputs.j++]; + } + + function nextBytes( + Data.Inputs inputs + ) + public + pure + returns (bytes) + { + return inputs.bytesList[inputs.k++]; + } +} \ No newline at end of file diff --git a/contracts/helper/MiningHelper.sol b/contracts/helper/MiningHelper.sol new file mode 100644 index 00000000..e5b37982 --- /dev/null +++ b/contracts/helper/MiningHelper.sol @@ -0,0 +1,79 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; +import "../lib/MultihashUtil.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library MiningHelper { + + function updateMinerAndInterceptor( + Data.Mining mining, + Data.Context ctx + ) + public + view + returns (bytes32) + { + if (mining.miner == 0x0) { + mining.miner = mining.feeRecipient; + } else { + bool registered; + (registered, mining.interceptor) = ctx.minerBrokerRegistry.getBroker( + mining.feeRecipient, + mining.miner + ); + require(registered, "miner unregistered"); + } + } + + function updateHash( + Data.Mining mining + ) + public + pure + { + mining.hash = keccak256( + mining.feeRecipient, + mining.miner, + mining.hash + ); + } + function checkMinerSignature( + Data.Mining mining, + Data.Context ctx + ) + public + view + { + if (mining.sig.length == 0) { + require(tx.origin == mining.miner); + } else { + MultihashUtil.verifySignature( + mining.miner, + mining.hash, + mining.sig + ); + } + } + +} \ No newline at end of file diff --git a/contracts/helper/OrderHelper.sol b/contracts/helper/OrderHelper.sol new file mode 100644 index 00000000..d4510697 --- /dev/null +++ b/contracts/helper/OrderHelper.sol @@ -0,0 +1,196 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; +import "../iface/IBrokerInterceptor.sol"; +import "../lib/ERC20.sol"; +import "../lib/MathUint.sol"; +import "../lib/MultihashUtil.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library OrderHelper { + + using MathUint for uint; + + function updateHash(Data.Order order) + public + pure + { + order.hash = keccak256( + order.owner, + order.tokenS, + order.tokenB, + order.amountS, + order.amountB, + order.lrcFee, + order.dualAuthAddr, + order.broker, + order.orderInterceptor, + order.wallet, + order.validSince, + order.validUntil, + order.limitByAmountB, + order.allOrNone + ); + } + + function updateBrokerAndInterceptor( + Data.Order order, + Data.Context ctx + ) + public + view + { + if (order.broker == address(0x0)) { + order.broker = order.owner; + } else { + bool registered; + (registered, order.brokerInterceptor) = ctx.orderBrokerRegistry.getBroker( + order.owner, + order.broker + ); + require(registered, "broker unregistered"); + } + } + + function updateStates( + Data.Order order, + Data.Context ctx + ) + public + view + { + order.maxAmountLrcFee = getSpendable( + ctx.delegate, + ctx.lrcTokenAddress, + order.owner, + order.broker, + order.brokerInterceptor + ); + + uint filled = ctx.delegate.filled(order.hash); + + if (order.limitByAmountB) { + order.maxAmountB = order.amountB.sub(filled); + order.maxAmountS = order.amountS.mul(order.maxAmountB) / order.amountB; + } else { + order.maxAmountS = order.amountS.sub(filled); + order.maxAmountB = order.amountB.mul(order.maxAmountS) / order.amountB; + } + + uint spendableS = getSpendable( + ctx.delegate, + order.tokenS, + order.owner, + order.broker, + order.brokerInterceptor + ); + + if (order.maxAmountS > spendableS) { + order.maxAmountS = spendableS; + } + + if (order.tokenS == ctx.lrcTokenAddress) { + order.sellLRC = true; + } + } + + function checkBrokerSignature( + Data.Order order, + Data.Context ctx + ) + public + view + { + if (order.sig.length == 0) { + require( + ctx.orderRegistry.isOrderHashRegistered( + order.broker, + order.hash + ), + "order unauthorized" + ); + } else { + MultihashUtil.verifySignature( + order.broker, + order.hash, + order.sig + ); + } + } + + function checkDualAuthSignature( + Data.Order order, + bytes32 miningHash + ) + public + pure + { + if (order.dualAuthSig.length != 0) { + MultihashUtil.verifySignature( + order.dualAuthAddr, + miningHash, + order.dualAuthSig + ); + } + } + + /// @return Amount of ERC20 token that can be spent by this contract. + function getSpendable( + ITradeDelegate delegate, + address tokenAddress, + address tokenOwner, + address broker, + address brokerInterceptor + ) + private + view + returns (uint spendable) + { + ERC20 token = ERC20(tokenAddress); + spendable = token.allowance( + tokenOwner, + address(delegate) + ); + if (spendable == 0) { + return; + } + uint amount = token.balanceOf(tokenOwner); + if (amount < spendable) { + spendable = amount; + if (spendable == 0) { + return; + } + } + + if (brokerInterceptor != tokenOwner) { + amount = IBrokerInterceptor(brokerInterceptor).getAllowance( + tokenOwner, + broker, + tokenAddress + ); + if (amount < spendable) { + spendable = amount; + } + } + } +} \ No newline at end of file diff --git a/contracts/helper/ParticipationHelper.sol b/contracts/helper/ParticipationHelper.sol new file mode 100644 index 00000000..b34ee835 --- /dev/null +++ b/contracts/helper/ParticipationHelper.sol @@ -0,0 +1,78 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; +import "../lib/MathUint.sol"; + + +/// @title ParticipationHelper +/// @author Daniel Wang - . +library ParticipationHelper { + using MathUint for uint; + + function adjustOrderState( + Data.Participation p + ) + public + pure + { + p.order.maxAmountS = p.order.maxAmountS.sub(p.fillAmountS); + p.order.maxAmountB = p.order.maxAmountB.sub(p.fillAmountB); + p.order.maxAmountLrcFee = p.order.maxAmountLrcFee.sub(p.lrcFee); + + if (p.order.sellLRC) { + p.order.maxAmountLrcFee = p.order.maxAmountLrcFee.sub(p.fillAmountS); + } + } + + function calculateFillAmounts( + Data.Participation p + ) + public + pure + returns (bool thisOrderIsSmaller) + { + Data.Order memory order = p.order; + + p.fillAmountB = p.fillAmountS.mul(p.rateB) / p.rateS; + + if (order.limitByAmountB) { + if (p.fillAmountB > order.maxAmountB) { + p.fillAmountB = order.maxAmountB; + p.fillAmountS = p.fillAmountB.mul(p.rateS) / p.rateB; + thisOrderIsSmaller = true; + } + p.lrcFee = order.lrcFee.mul(p.fillAmountB) / order.amountB; + } else { + p.lrcFee = order.lrcFee.mul(p.fillAmountS) / order.amountS; + } + } + + function calculateFeeAmounts( + Data.Participation p, + Data.Mining mining + ) + public + pure + { + Data.Order memory order = p.order; + + } +} \ No newline at end of file diff --git a/contracts/helper/RingHelper.sol b/contracts/helper/RingHelper.sol new file mode 100644 index 00000000..fe78b88d --- /dev/null +++ b/contracts/helper/RingHelper.sol @@ -0,0 +1,105 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; +import "../lib/MathUint.sol"; +import "../lib/MultihashUtil.sol"; +import "./ParticipationHelper.sol"; + + +/// @title An Implementation of IOrderbook. +library RingHelper { + using MathUint for uint; + using ParticipationHelper for Data.Participation; + + function updateHash( + Data.Ring ring + ) + public + pure + { + for (uint i = 0; i < ring.size; i++) { + Data.Participation memory p = ring.participations[i]; + ring.hash = keccak256( + ring.hash, + p.order.hash, + p.marginSplitAsFee + ); + } + } + + function calculateFillAmountAndFee( + Data.Ring ring, + Data.Mining mining + ) + public + view + { + for (uint i = 0; i < ring.size; i++) { + Data.Participation memory p = ring.participations[i]; + Data.Order memory order = p.order; + p.fillAmountS = order.maxAmountS; + p.fillAmountB = order.maxAmountB; + } + + uint smallest = 0; + + for (uint i = 0; i < ring.size; i++) { + smallest = calculateOrderFillAmounts(ring, i, smallest); + } + + for (uint i = 0; i < smallest; i++) { + calculateOrderFillAmounts(ring, i, smallest); + } + + for (uint i = 0; i < ring.size; i++) { + Data.Participation memory p = ring.participations[i]; + p.calculateFeeAmounts(mining); + p.adjustOrderState(); + } + } + + function calculateOrderFillAmounts( + Data.Ring ring, + uint i, + uint smallest + ) + internal + pure + returns (uint smallest_) + { + // Default to the same smallest index + smallest_ = smallest; + + Data.Participation memory p = ring.participations[i]; + if (p.calculateFillAmounts()) { + smallest_ = i; + } + + uint j = (i + 1) % ring.size; + Data.Participation memory nextP = ring.participations[j]; + + if (p.fillAmountB < nextP.fillAmountS) { + nextP.fillAmountS = p.fillAmountB; + } else { + smallest_ = j; + } + } +} \ No newline at end of file diff --git a/contracts/IBrokerInterceptor.sol b/contracts/iface/IBrokerInterceptor.sol similarity index 100% rename from contracts/IBrokerInterceptor.sol rename to contracts/iface/IBrokerInterceptor.sol diff --git a/contracts/IBrokerRegistry.sol b/contracts/iface/IBrokerRegistry.sol similarity index 100% rename from contracts/IBrokerRegistry.sol rename to contracts/iface/IBrokerRegistry.sol diff --git a/contracts/IExchange.sol b/contracts/iface/IExchange.sol similarity index 100% rename from contracts/IExchange.sol rename to contracts/iface/IExchange.sol diff --git a/contracts/iface/IMinerInterceptor.sol b/contracts/iface/IMinerInterceptor.sol new file mode 100644 index 00000000..81c0611e --- /dev/null +++ b/contracts/iface/IMinerInterceptor.sol @@ -0,0 +1,25 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + + +/// @title IMinerInterceptor +contract IMinerInterceptor { + +} \ No newline at end of file diff --git a/contracts/IMinerRegistry.sol b/contracts/iface/IMinerRegistry.sol similarity index 100% rename from contracts/IMinerRegistry.sol rename to contracts/iface/IMinerRegistry.sol diff --git a/contracts/IOrderBook.sol b/contracts/iface/IOrderBook.sol similarity index 100% rename from contracts/IOrderBook.sol rename to contracts/iface/IOrderBook.sol diff --git a/contracts/IOrderInterceptor.sol b/contracts/iface/IOrderInterceptor.sol similarity index 100% rename from contracts/IOrderInterceptor.sol rename to contracts/iface/IOrderInterceptor.sol diff --git a/contracts/iface/IOrderRegistry.sol b/contracts/iface/IOrderRegistry.sol new file mode 100644 index 00000000..f7ce20ed --- /dev/null +++ b/contracts/iface/IOrderRegistry.sol @@ -0,0 +1,33 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + + +/// @title IOrderRegistry +/// @author Daniel Wang - . +contract IOrderRegistry { + + function isOrderHashRegistered( + address owner, + bytes32 hash + ) + public + view + returns (bool); +} diff --git a/contracts/IRingInterceptor.sol b/contracts/iface/IRingInterceptor.sol similarity index 100% rename from contracts/IRingInterceptor.sol rename to contracts/iface/IRingInterceptor.sol diff --git a/contracts/ITokenFactory.sol b/contracts/iface/ITokenFactory.sol similarity index 100% rename from contracts/ITokenFactory.sol rename to contracts/iface/ITokenFactory.sol diff --git a/contracts/ITokenRegistry.sol b/contracts/iface/ITokenRegistry.sol similarity index 100% rename from contracts/ITokenRegistry.sol rename to contracts/iface/ITokenRegistry.sol diff --git a/contracts/ITradeDelegate.sol b/contracts/iface/ITradeDelegate.sol similarity index 96% rename from contracts/ITradeDelegate.sol rename to contracts/iface/ITradeDelegate.sol index c477b08e..a85d091b 100644 --- a/contracts/ITradeDelegate.sol +++ b/contracts/iface/ITradeDelegate.sol @@ -85,7 +85,13 @@ contract ITradeDelegate { function addFilled( bytes32 orderHash, - uint cancelOrFillAmount + uint amount + ) + external; + + function setFilled( + bytes32 orderHash, + uint amount ) external; diff --git a/contracts/BrokerRegistry.sol b/contracts/impl/BrokerRegistry.sol similarity index 98% rename from contracts/BrokerRegistry.sol rename to contracts/impl/BrokerRegistry.sol index 10a1ebb3..d7bade78 100644 --- a/contracts/BrokerRegistry.sol +++ b/contracts/impl/BrokerRegistry.sol @@ -18,8 +18,8 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/NoDefaultFunc.sol"; -import "./IBrokerRegistry.sol"; +import "../iface/IBrokerRegistry.sol"; +import "../lib/NoDefaultFunc.sol"; /// @title An Implementation of IBrokerRegistry. diff --git a/contracts/impl/Data.sol b/contracts/impl/Data.sol new file mode 100644 index 00000000..b3a14895 --- /dev/null +++ b/contracts/impl/Data.sol @@ -0,0 +1,114 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../iface/IBrokerRegistry.sol"; +import "../iface/IOrderRegistry.sol"; +import "../iface/ITokenRegistry.sol"; +import "../iface/ITradeDelegate.sol"; +import "../iface/IMinerRegistry.sol"; + + +library Data { + + struct Inputs { + address[] addressList; + uint[] uintList; + bytes[] bytesList; + uint i; + uint j; + uint k; + } + + struct Context { + address lrcTokenAddress; + ITokenRegistry tokenRegistry; + ITradeDelegate delegate; + IBrokerRegistry orderBrokerRegistry; + IBrokerRegistry minerBrokerRegistry; + IOrderRegistry orderRegistry; + IMinerRegistry minerRegistry; + } + + struct Mining { + // required fields + address feeRecipient; + + // optional fields + address miner; + bytes sig; + + // computed fields + bytes32 hash; + address interceptor; + uint spendableLRC; + } + + struct Order { + // required fields + address owner; + address tokenS; + address tokenB; + uint amountS; + uint amountB; + uint lrcFee; + + // optional fields + address dualAuthAddr; + address broker; + address orderInterceptor; + address wallet; + uint validSince; + uint validUntil; + bytes sig; + bytes dualAuthSig; + bool limitByAmountB; + bool allOrNone; + + // computed fields + bytes32 hash; + address brokerInterceptor; + uint maxAmountLrcFee; + uint maxAmountS; + uint maxAmountB; + bool sellLRC; + } + + struct Participation { + // required fields + Order order; + bool marginSplitAsFee; + uint rateS; + uint rateB; + + // computed fields + uint splitS; + uint splitB; + uint lrcFee; + uint lrcReward; + uint fillAmountS; + uint fillAmountB; + } + + struct Ring{ + uint size; + Participation[] participations; + bytes32 hash; + } +} \ No newline at end of file diff --git a/contracts/Exchange.sol b/contracts/impl/Exchange.sol similarity index 98% rename from contracts/Exchange.sol rename to contracts/impl/Exchange.sol index 04425b0e..c8dfb4ca 100644 --- a/contracts/Exchange.sol +++ b/contracts/impl/Exchange.sol @@ -18,17 +18,17 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/AddressUtil.sol"; -import "./lib/BytesUtil.sol"; -import "./lib/ERC20.sol"; -import "./lib/MathUint.sol"; -import "./lib/MultihashUtil.sol"; -import "./lib/NoDefaultFunc.sol"; -import "./IBrokerRegistry.sol"; -import "./IBrokerInterceptor.sol"; -import "./IExchange.sol"; -import "./ITokenRegistry.sol"; -import "./ITradeDelegate.sol"; +import "../lib/AddressUtil.sol"; +import "../lib/BytesUtil.sol"; +import "../lib/ERC20.sol"; +import "../lib/MathUint.sol"; +import "../lib/MultihashUtil.sol"; +import "../lib/NoDefaultFunc.sol"; +import "../iface/IBrokerRegistry.sol"; +import "../iface/IBrokerInterceptor.sol"; +import "../iface/IExchange.sol"; +import "../iface/ITokenRegistry.sol"; +import "../iface/ITradeDelegate.sol"; /// @title An Implementation of IExchange. @@ -144,7 +144,7 @@ contract Exchange is IExchange, NoDefaultFunc { { uint size = orderHashes.length; require(size > 0 && size % 32 == 0); - + verifyAuthenticationGetInterceptor( owner, tx.origin @@ -180,7 +180,7 @@ contract Exchange is IExchange, NoDefaultFunc { uint t = (cutoff == 0 || cutoff >= block.timestamp) ? block.timestamp : cutoff; bytes20 tokenPair = bytes20(token1) ^ bytes20(token2); - + ITradeDelegate(delegateAddress).setTradingPairCutoffs( owner, tokenPair, diff --git a/contracts/impl/Exchange2.sol b/contracts/impl/Exchange2.sol new file mode 100644 index 00000000..fd2d7375 --- /dev/null +++ b/contracts/impl/Exchange2.sol @@ -0,0 +1,207 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../iface/IBrokerRegistry.sol"; +import "../iface/IBrokerInterceptor.sol"; +import "../iface/IExchange.sol"; +import "../iface/IOrderRegistry.sol"; +import "../iface/ITokenRegistry.sol"; +import "../iface/ITradeDelegate.sol"; +import "../iface/IMinerRegistry.sol"; + +import "../lib/AddressUtil.sol"; +import "../lib/BytesUtil.sol"; +import "../lib/ERC20.sol"; +import "../lib/MathUint.sol"; +import "../lib/MultihashUtil.sol"; +import "../lib/NoDefaultFunc.sol"; + +import "../spec/OrderSpecs.sol"; +import "../spec/MiningSpec.sol"; +import "../spec/RingSpecs.sol"; + +import "../helper/InputsHelper.sol"; +import "../helper/OrderHelper.sol"; +import "../helper/RingHelper.sol"; +import "../helper/MiningHelper.sol"; + +import "./Data.sol"; + + +/// @title An Implementation of IExchange. +/// @author Daniel Wang - , +/// @author Kongliang Zhong - +/// +/// Recognized contributing developers from the community: +/// https://github.com/Brechtpd +/// https://github.com/rainydio +/// https://github.com/BenjaminPrice +/// https://github.com/jonasshen +/// https://github.com/Hephyrius +contract Exchange is IExchange, NoDefaultFunc { + using AddressUtil for address; + using MathUint for uint; + using MiningSpec for uint16; + using OrderSpecs for uint16[]; + using RingSpecs for uint8[][]; + using OrderHelper for Data.Order; + using RingHelper for Data.Ring; + using InputsHelper for Data.Inputs; + using MiningHelper for Data.Mining; + + address public lrcTokenAddress = 0x0; + address public tokenRegistryAddress = 0x0; + address public delegateAddress = 0x0; + address public orderBrokerRegistryAddress = 0x0; + address public minerBrokerRegistryAddress = 0x0; + address public orderRegistryAddress = 0x0; + address public minerRegistryAddress = 0x0; + + uint64 public ringIndex = 0; + + // Exchange rate (rate) is the amount to sell or sold divided by the amount + // to buy or bought. + // + // Rate ratio is the ratio between executed rate and an order's original + // rate. + // + // To require all orders' rate ratios to have coefficient ofvariation (CV) + // smaller than 2.5%, for an example , rateRatioCVSThreshold should be: + // `(0.025 * RATE_RATIO_SCALE)^2` or 62500. + uint public rateRatioCVSThreshold = 0; + + uint public constant MAX_RING_SIZE = 8; + + uint public constant RATE_RATIO_SCALE = 10000; + + function submitRings( + uint16 miningSpec, + uint16[] orderSpecs, + uint8[][] ringSpecs, + address[] addressLists, + uint[] uintList, + bytes[] bytesList + ) + public + { + Data.Context memory ctx = Data.Context( + lrcTokenAddress, + ITokenRegistry(tokenRegistryAddress), + ITradeDelegate(delegateAddress), + IBrokerRegistry(orderBrokerRegistryAddress), + IBrokerRegistry(minerBrokerRegistryAddress), + IOrderRegistry(orderRegistryAddress), + IMinerRegistry(minerRegistryAddress) + ); + + Data.Inputs memory inputs = Data.Inputs( + addressLists, + uintList, + bytesList, + 0, 0, 0 // current indices of addressLists, uintList, and bytesList. + ); + + Data.Mining memory mining = Data.Mining( + inputs.nextAddress(), + (miningSpec.hasMiner() ? inputs.nextAddress() : address(0x0)), + (miningSpec.hasSignature() ? inputs.nextBytes() : new bytes(0)), + bytes32(0x0), // hash + address(0x0), // interceptor + getSpendable( + ctx.delegate, + ctx.lrcTokenAddress, + tx.origin, // TODO(daniel): pay from msg.sender? + 0x0, // broker + 0x0 // brokerInterceptor + ) + ); + + Data.Order[] memory orders = orderSpecs.assembleOrders(inputs); + Data.Ring[] memory rings = ringSpecs.assembleRings(orders, inputs); + + for (uint i = 0; i < orders.length; i++) { + orders[i].updateHash(); + orders[i].updateBrokerAndInterceptor(ctx); + orders[i].checkBrokerSignature(ctx); + } + + for (uint i = 0; i < rings.length; i++) { + rings[i].updateHash(); + mining.hash ^= rings[i].hash; + } + + mining.updateHash(); + mining.updateMinerAndInterceptor(ctx); + mining.checkMinerSignature(ctx); + + for (uint i = 0; i < orders.length; i++) { + orders[i].checkDualAuthSignature(mining.hash); + } + + for (uint i = 0; i < orders.length; i++) { + orders[i].updateStates(ctx); + } + + for (uint i = 0; i < rings.length; i++){ + rings[i].calculateFillAmountAndFee(mining); + } + } + + /// @return Amount of ERC20 token that can be spent by this contract. + // TODO(daniel): there is another getSpendable in OrderHelper. + function getSpendable( + ITradeDelegate delegate, + address tokenAddress, + address tokenOwner, + address broker, + address brokerInterceptor + ) + private + view + returns (uint spendable) + { + ERC20 token = ERC20(tokenAddress); + spendable = token.allowance( + tokenOwner, + address(delegate) + ); + if (spendable == 0) { + return; + } + uint amount = token.balanceOf(tokenOwner); + if (amount < spendable) { + spendable = amount; + if (spendable == 0) { + return; + } + } + + if (brokerInterceptor != tokenOwner) { + amount = IBrokerInterceptor(brokerInterceptor).getAllowance( + tokenOwner, + broker, + tokenAddress + ); + if (amount < spendable) { + spendable = amount; + } + } + } +} diff --git a/contracts/Migrations.sol b/contracts/impl/Migrations.sol similarity index 100% rename from contracts/Migrations.sol rename to contracts/impl/Migrations.sol diff --git a/contracts/MinerRegistry.sol b/contracts/impl/MinerRegistry.sol similarity index 97% rename from contracts/MinerRegistry.sol rename to contracts/impl/MinerRegistry.sol index 6e318f6b..8008301c 100644 --- a/contracts/MinerRegistry.sol +++ b/contracts/impl/MinerRegistry.sol @@ -18,8 +18,8 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/NoDefaultFunc.sol"; -import "./IMinerRegistry.sol"; +import "../iface/IMinerRegistry.sol"; +import "../lib/NoDefaultFunc.sol"; /// @title An Implementation of IMinerRegistry. diff --git a/contracts/OrderBook.sol b/contracts/impl/OrderBook.sol similarity index 93% rename from contracts/OrderBook.sol rename to contracts/impl/OrderBook.sol index 3d559e35..57e995a3 100644 --- a/contracts/OrderBook.sol +++ b/contracts/impl/OrderBook.sol @@ -18,8 +18,8 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/NoDefaultFunc.sol"; -import "./IOrderBook.sol"; +import "../iface/IOrderBook.sol"; +import "../lib/NoDefaultFunc.sol"; /// @title An Implementation of IOrderbook. diff --git a/contracts/impl/OrderRegistry.sol b/contracts/impl/OrderRegistry.sol new file mode 100644 index 00000000..b3b04533 --- /dev/null +++ b/contracts/impl/OrderRegistry.sol @@ -0,0 +1,41 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../iface/IOrderRegistry.sol"; +import "../lib/NoDefaultFunc.sol"; + + +/// @title An Implementation of IBrokerRegistry. +/// @author Daniel Wang - . +contract OrderRegistry is IOrderRegistry, NoDefaultFunc { + + mapping (address => mapping (bytes32 => bool)) public hashMap; + + function isOrderHashRegistered( + address owner, + bytes32 hash + ) + public + view + returns (bool) + { + return hashMap[owner][hash]; + } +} diff --git a/contracts/TokenFactory.sol b/contracts/impl/TokenFactory.sol similarity index 90% rename from contracts/TokenFactory.sol rename to contracts/impl/TokenFactory.sol index 96e8b824..0a0d1f02 100644 --- a/contracts/TokenFactory.sol +++ b/contracts/impl/TokenFactory.sol @@ -18,12 +18,12 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/AddressUtil.sol"; -import "./lib/ERC20Token.sol"; -import "./lib/NoDefaultFunc.sol"; -import "./lib/StringUtil.sol"; -import "./ITokenFactory.sol"; -import "./ITokenRegistry.sol"; +import "../iface/ITokenFactory.sol"; +import "../iface/ITokenRegistry.sol"; +import "../lib/AddressUtil.sol"; +import "../lib/ERC20Token.sol"; +import "../lib/NoDefaultFunc.sol"; +import "../lib/StringUtil.sol"; /// @title An Implementation of ITokenFactory. diff --git a/contracts/TokenRegistry.sol b/contracts/impl/TokenRegistry.sol similarity index 97% rename from contracts/TokenRegistry.sol rename to contracts/impl/TokenRegistry.sol index 7f4b8136..e5fb29e7 100644 --- a/contracts/TokenRegistry.sol +++ b/contracts/impl/TokenRegistry.sol @@ -18,10 +18,10 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/AddressUtil.sol"; -import "./lib/Claimable.sol"; -import "./lib/NoDefaultFunc.sol"; -import "./ITokenRegistry.sol"; +import "../iface/ITokenRegistry.sol"; +import "../lib/AddressUtil.sol"; +import "../lib/Claimable.sol"; +import "../lib/NoDefaultFunc.sol"; /// @title An Implementation of TokenRegistry. diff --git a/contracts/TradeDelegate.sol b/contracts/impl/TradeDelegate.sol similarity index 95% rename from contracts/TradeDelegate.sol rename to contracts/impl/TradeDelegate.sol index 1c90cf37..78fe4c0b 100644 --- a/contracts/TradeDelegate.sol +++ b/contracts/impl/TradeDelegate.sol @@ -18,12 +18,12 @@ pragma solidity 0.4.23; pragma experimental "v0.5.0"; pragma experimental "ABIEncoderV2"; -import "./lib/Claimable.sol"; -import "./lib/ERC20.sol"; -import "./lib/MathUint.sol"; -import "./lib/NoDefaultFunc.sol"; -import "./IBrokerInterceptor.sol"; -import "./ITradeDelegate.sol"; +import "../iface/IBrokerInterceptor.sol"; +import "../iface/ITradeDelegate.sol"; +import "../lib/Claimable.sol"; +import "../lib/ERC20.sol"; +import "../lib/MathUint.sol"; +import "../lib/NoDefaultFunc.sol"; /// @title An Implementation of ITradeDelegate. @@ -290,6 +290,17 @@ contract TradeDelegate is ITradeDelegate, Claimable, NoDefaultFunc { filled[orderHash] = filled[orderHash].add(amount); } + function setFilled( + bytes32 orderHash, + uint amount + ) + onlyAuthorized + notSuspended + external + { + filled[orderHash] = amount; + } + function setCutoffs( address owner, diff --git a/contracts/spec/MiningSpec.sol b/contracts/spec/MiningSpec.sol new file mode 100644 index 00000000..f9099ca6 --- /dev/null +++ b/contracts/spec/MiningSpec.sol @@ -0,0 +1,58 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library MiningSpec { + function hasMiner(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x2 != 0; + } + + function hasBroker(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x2 != 0; + } + + function hasMinerInterceptor(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x2 != 0; + } + + function hasSignature(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x2 != 0; + } +} \ No newline at end of file diff --git a/contracts/spec/OrderSpec.sol b/contracts/spec/OrderSpec.sol new file mode 100644 index 00000000..085eb672 --- /dev/null +++ b/contracts/spec/OrderSpec.sol @@ -0,0 +1,106 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library OrderSpec { + function limitByAmountB(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x2 != 0; + } + + function allOrNone(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x4 != 0; + } + + function hasDualAuth(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasSignature(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasBroker(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasBrokerInterceptor(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasWallet(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasValidSince(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasValidUntil(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } + + function hasOrderInterceptor(uint16 spec) + public + pure + returns (bool) + { + return spec & 0x8 != 0; + } +} \ No newline at end of file diff --git a/contracts/spec/OrderSpecs.sol b/contracts/spec/OrderSpecs.sol new file mode 100644 index 00000000..d811fa4c --- /dev/null +++ b/contracts/spec/OrderSpecs.sol @@ -0,0 +1,80 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../helper/InputsHelper.sol"; +import "../impl/Data.sol"; +import "./OrderSpec.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library OrderSpecs { + using OrderSpec for uint16; + using InputsHelper for Data.Inputs; + + function assembleOrders( + uint16[] specs, + Data.Inputs inputs + ) + public + pure + returns (Data.Order[] memory orders) + { + uint size = specs.length; + orders = new Data.Order[](size); + for (uint i = 0; i < size; i++) { + orders[i] = assembleOrder(specs[i], inputs); + } + } + + function assembleOrder( + uint16 spec, + Data.Inputs inputs + ) + internal + pure + returns (Data.Order memory) + { + return Data.Order( + inputs.nextAddress(), // owner + inputs.nextAddress(), // tokenS + address(0x0), // tokenB + inputs.nextUint(), // amountS + inputs.nextUint(), // amountB + inputs.nextUint(), // lrcFee + spec.hasDualAuth() ? inputs.nextAddress() : address(0x0), + spec.hasBroker() ? inputs.nextAddress() : address(0x0), + spec.hasOrderInterceptor() ? inputs.nextAddress() : address(0x0), + spec.hasWallet() ? inputs.nextAddress() : address(0x0), + spec.hasValidSince() ? inputs.nextUint() : 0, + spec.hasValidUntil() ? inputs.nextUint() : uint(0) - 1, + spec.hasSignature() ? inputs.nextBytes() : new bytes(0), + spec.hasDualAuth() ? inputs.nextBytes() : new bytes(0), + spec.limitByAmountB(), + spec.allOrNone(), + bytes32(0x0), // hash + address(0x0), // orderBrokerInterceptor + 0, // spendableLRC + 0, // maxAmountS + 0, // maxAmountB, + false // sellLRC + ); + } +} \ No newline at end of file diff --git a/contracts/spec/ParticipationSpec.sol b/contracts/spec/ParticipationSpec.sol new file mode 100644 index 00000000..cc37e903 --- /dev/null +++ b/contracts/spec/ParticipationSpec.sol @@ -0,0 +1,43 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../impl/Data.sol"; + + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library ParticipationSpec { + + function orderIndex(uint8 spec) + public + pure + returns (uint) + { + return 0; + } + + function marginSplitAsFee(uint8 spec) + public + pure + returns (bool) + { + return false; + } +} \ No newline at end of file diff --git a/contracts/spec/RingSpecs.sol b/contracts/spec/RingSpecs.sol new file mode 100644 index 00000000..a212e699 --- /dev/null +++ b/contracts/spec/RingSpecs.sol @@ -0,0 +1,93 @@ +/* + + Copyright 2017 Loopring Project Ltd (Loopring Foundation). + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pragma solidity 0.4.23; +pragma experimental "v0.5.0"; +pragma experimental "ABIEncoderV2"; + +import "../helper/InputsHelper.sol"; +import "../impl/Data.sol"; +import "./ParticipationSpec.sol"; + +/// @title An Implementation of IOrderbook. +/// @author Daniel Wang - . +library RingSpecs { + using ParticipationSpec for uint8; + using InputsHelper for Data.Inputs; + + function assembleRings( + uint8[][] specs, + Data.Order[] orders, + Data.Inputs inputs + ) + public + pure + returns (Data.Ring[] memory rings) + { + uint size = specs.length; + rings = new Data.Ring[](size); + for (uint i = 0; i < size; i++) { + rings[i] = assembleRing( + specs[i], + orders, + inputs + ); + } + } + + function assembleRing( + uint8[] pspecs, + Data.Order[] orders, + Data.Inputs inputs + ) + internal + pure + returns (Data.Ring memory) + { + uint size = pspecs.length; + require(size < 2 || size > 8, "bad ring size"); + + Data.Participation[] memory parts = new Data.Participation[](size); + address prevTokenS = address(0x0); + + for (uint i = 0; i < size; i++) { + uint8 pspec = pspecs[i]; + parts[i] = Data.Participation( + orders[pspec.orderIndex()], + pspec.marginSplitAsFee(), + inputs.nextUint(), + inputs.nextUint(), + 0, // splitS + 0, // splitB + 0, // lrcFee + 0, // lrcReward + 0, // fillAmountS + 0 // fillAmountB + ); + + parts[i].order.tokenB = prevTokenS; + prevTokenS = parts[i].order.tokenS; + } + + parts[0].order.tokenB = prevTokenS; + + return Data.Ring( + size, + parts, + bytes32(0x0) // hash + ); + } +} \ No newline at end of file