Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
TokenTracker
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
RingSubmitter
Compiler Version
v0.5.2+commit.1df8f40c
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity)
/** *Submitted for verification at Etherscan.io on 2019-01-28 */ /* 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.5.2; /// @title Utility Functions for bytes /// @author Daniel Wang - <[email protected]> library BytesUtil { function bytesToBytes32( bytes memory b, uint offset ) internal pure returns (bytes32) { return bytes32(bytesToUintX(b, offset, 32)); } function bytesToUint( bytes memory b, uint offset ) internal pure returns (uint) { return bytesToUintX(b, offset, 32); } function bytesToAddress( bytes memory b, uint offset ) internal pure returns (address) { return address(bytesToUintX(b, offset, 20) & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); } function bytesToUint16( bytes memory b, uint offset ) internal pure returns (uint16) { return uint16(bytesToUintX(b, offset, 2) & 0xFFFF); } function bytesToUintX( bytes memory b, uint offset, uint numBytes ) private pure returns (uint data) { require(b.length >= offset + numBytes, "INVALID_SIZE"); assembly { data := mload(add(add(b, numBytes), offset)) } } function subBytes( bytes memory b, uint offset ) internal pure returns (bytes memory data) { require(b.length >= offset + 32, "INVALID_SIZE"); assembly { data := add(add(b, 32), offset) } } } /* 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. */ /// @title Utility Functions for uint /// @author Daniel Wang - <[email protected]> library MathUint { function mul( uint a, uint b ) internal pure returns (uint c) { c = a * b; require(a == 0 || c / a == b, "INVALID_VALUE"); } function sub( uint a, uint b ) internal pure returns (uint) { require(b <= a, "INVALID_VALUE"); return a - b; } function add( uint a, uint b ) internal pure returns (uint c) { c = a + b; require(c >= a, "INVALID_VALUE"); } function hasRoundingError( uint value, uint numerator, uint denominator ) internal pure returns (bool) { uint multiplied = mul(value, numerator); uint remainder = multiplied % denominator; // Return true if the rounding error is larger than 1% return mul(remainder, 100) > multiplied; } } /* 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. */ /// @title ITradeHistory /// @dev Stores the trade history and cancelled data of orders /// @author Brecht Devos - <[email protected]>. contract ITradeHistory { // The following map is used to keep trace of order fill and cancellation // history. mapping (bytes32 => uint) public filled; // This map is used to keep trace of order's cancellation history. mapping (address => mapping (bytes32 => bool)) public cancelled; // A map from a broker to its cutoff timestamp. mapping (address => uint) public cutoffs; // A map from a broker to its trading-pair cutoff timestamp. mapping (address => mapping (bytes20 => uint)) public tradingPairCutoffs; // A map from a broker to an order owner to its cutoff timestamp. mapping (address => mapping (address => uint)) public cutoffsOwner; // A map from a broker to an order owner to its trading-pair cutoff timestamp. mapping (address => mapping (address => mapping (bytes20 => uint))) public tradingPairCutoffsOwner; function batchUpdateFilled( bytes32[] calldata filledInfo ) external; function setCancelled( address broker, bytes32 orderHash ) external; function setCutoffs( address broker, uint cutoff ) external; function setTradingPairCutoffs( address broker, bytes20 tokenPair, uint cutoff ) external; function setCutoffsOfOwner( address broker, address owner, uint cutoff ) external; function setTradingPairCutoffsOfOwner( address broker, address owner, bytes20 tokenPair, uint cutoff ) external; function batchGetFilledAndCheckCancelled( bytes32[] calldata orderInfo ) external view returns (uint[] memory fills); /// @dev Add a Loopring protocol address. /// @param addr A loopring protocol address. function authorizeAddress( address addr ) external; /// @dev Remove a Loopring protocol address. /// @param addr A loopring protocol address. function deauthorizeAddress( address addr ) external; function isAddressAuthorized( address addr ) public view returns (bool); function suspend() external; function resume() external; function kill() external; } /* 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. */ /// @title ITradeDelegate /// @dev Acts as a middle man to transfer ERC20 tokens on behalf of different /// versions of Loopring protocol to avoid ERC20 re-authorization. /// @author Daniel Wang - <[email protected]>. contract ITradeDelegate { function batchTransfer( bytes32[] calldata batch ) external; /// @dev Add a Loopring protocol address. /// @param addr A loopring protocol address. function authorizeAddress( address addr ) external; /// @dev Remove a Loopring protocol address. /// @param addr A loopring protocol address. function deauthorizeAddress( address addr ) external; function isAddressAuthorized( address addr ) public view returns (bool); function suspend() external; function resume() external; function kill() external; } /* 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. */ /// @title IOrderRegistry /// @author Daniel Wang - <[email protected]>. contract IOrderRegistry { /// @dev Returns wether the order hash was registered in the registry. /// @param broker The broker of the order /// @param orderHash The hash of the order /// @return True if the order hash was registered, else false. function isOrderHashRegistered( address broker, bytes32 orderHash ) external view returns (bool); /// @dev Registers an order in the registry. /// msg.sender needs to be the broker of the order. /// @param orderHash The hash of the order function registerOrderHash( bytes32 orderHash ) external; } /* 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. */ /// @title IOrderBook /// @author Daniel Wang - <[email protected]>. /// @author Kongliang Zhong - <[email protected]>. contract IOrderBook { // The map of registered order hashes mapping(bytes32 => bool) public orderSubmitted; /// @dev Event emitted when an order was successfully submitted /// orderHash The hash of the order /// orderData The data of the order as passed to submitOrder() event OrderSubmitted( bytes32 orderHash, bytes orderData ); /// @dev Submits an order to the on-chain order book. /// No signature is needed. The order can only be sumbitted by its /// owner or its broker (the owner can be the address of a contract). /// @param orderData The data of the order. Contains all fields that are used /// for the order hash calculation. /// See OrderHelper.updateHash() for detailed information. function submitOrder( bytes calldata orderData ) external returns (bytes32); } /* 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. */ /// @author Kongliang Zhong - <[email protected]> /// @title IFeeHolder - A contract holding fees. contract IFeeHolder { event TokenWithdrawn( address owner, address token, uint value ); // A map of all fee balances mapping(address => mapping(address => uint)) public feeBalances; /// @dev Allows withdrawing the tokens to be burned by /// authorized contracts. /// @param token The token to be used to burn buy and burn LRC /// @param value The amount of tokens to withdraw function withdrawBurned( address token, uint value ) external returns (bool success); /// @dev Allows withdrawing the fee payments funds /// msg.sender is the recipient of the fee and the address /// to which the tokens will be sent. /// @param token The token to withdraw /// @param value The amount of tokens to withdraw function withdrawToken( address token, uint value ) external returns (bool success); function batchAddFeeBalances( bytes32[] calldata batch ) external; } /* 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. */ /// @author Brecht Devos - <[email protected]> /// @title IBurnRateTable - A contract for managing burn rates for tokens contract IBurnRateTable { struct TokenData { uint tier; uint validUntil; } mapping(address => TokenData) public tokens; uint public constant YEAR_TO_SECONDS = 31556952; // Tiers uint8 public constant TIER_4 = 0; uint8 public constant TIER_3 = 1; uint8 public constant TIER_2 = 2; uint8 public constant TIER_1 = 3; uint16 public constant BURN_BASE_PERCENTAGE = 100 * 10; // 100% // Cost of upgrading the tier level of a token in a percentage of the total LRC supply uint16 public constant TIER_UPGRADE_COST_PERCENTAGE = 1; // 0.1% // Burn rates // Matching uint16 public constant BURN_MATCHING_TIER1 = 25; // 2.5% uint16 public constant BURN_MATCHING_TIER2 = 15 * 10; // 15% uint16 public constant BURN_MATCHING_TIER3 = 30 * 10; // 30% uint16 public constant BURN_MATCHING_TIER4 = 50 * 10; // 50% // P2P uint16 public constant BURN_P2P_TIER1 = 25; // 2.5% uint16 public constant BURN_P2P_TIER2 = 15 * 10; // 15% uint16 public constant BURN_P2P_TIER3 = 30 * 10; // 30% uint16 public constant BURN_P2P_TIER4 = 50 * 10; // 50% event TokenTierUpgraded( address indexed addr, uint tier ); /// @dev Returns the P2P and matching burn rate for the token. /// @param token The token to get the burn rate for. /// @return The burn rate. The P2P burn rate and matching burn rate /// are packed together in the lowest 4 bytes. /// (2 bytes P2P, 2 bytes matching) function getBurnRate( address token ) external view returns (uint32 burnRate); /// @dev Returns the tier of a token. /// @param token The token to get the token tier for. /// @return The tier of the token function getTokenTier( address token ) public view returns (uint); /// @dev Upgrades the tier of a token. Before calling this function, /// msg.sender needs to approve this contract for the neccessary funds. /// @param token The token to upgrade the tier for. /// @return True if successful, false otherwise. function upgradeTokenTier( address token ) external returns (bool); } /* 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. */ /// @title IBrokerRegistry /// @dev A broker is an account that can submit orders on behalf of other /// accounts. When registering a broker, the owner can also specify a /// pre-deployed BrokerInterceptor to hook into the exchange smart contracts. /// @author Daniel Wang - <[email protected]>. contract IBrokerRegistry { event BrokerRegistered( address owner, address broker, address interceptor ); event BrokerUnregistered( address owner, address broker, address interceptor ); event AllBrokersUnregistered( address owner ); /// @dev Validates if the broker was registered for the order owner and /// returns the possible BrokerInterceptor to be used. /// @param owner The owner of the order /// @param broker The broker of the order /// @return True if the broker was registered for the owner /// and the BrokerInterceptor to use. function getBroker( address owner, address broker ) external view returns( bool registered, address interceptor ); /// @dev Gets all registered brokers for an owner. /// @param owner The owner /// @param start The start index of the list of brokers /// @param count The number of brokers to return /// @return The list of requested brokers and corresponding BrokerInterceptors function getBrokers( address owner, uint start, uint count ) external view returns ( address[] memory brokers, address[] memory interceptors ); /// @dev Registers a broker for msg.sender and an optional /// corresponding BrokerInterceptor. /// @param broker The broker to register /// @param interceptor The optional BrokerInterceptor to use (0x0 allowed) function registerBroker( address broker, address interceptor ) external; /// @dev Unregisters a broker for msg.sender /// @param broker The broker to unregister function unregisterBroker( address broker ) external; /// @dev Unregisters all brokers for msg.sender function unregisterAllBrokers( ) external; } /* 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. */ /// @title Utility Functions for Multihash signature verificaiton /// @author Daniel Wang - <[email protected]> /// For more information: /// - https://github.com/saurfang/ipfs-multihash-on-solidity /// - https://github.com/multiformats/multihash /// - https://github.com/multiformats/js-multihash library MultihashUtil { enum HashAlgorithm { Ethereum, EIP712 } string public constant SIG_PREFIX = "\x19Ethereum Signed Message:\n32"; function verifySignature( address signer, bytes32 plaintext, bytes memory multihash ) internal pure returns (bool) { uint length = multihash.length; require(length >= 2, "invalid multihash format"); uint8 algorithm; uint8 size; assembly { algorithm := mload(add(multihash, 1)) size := mload(add(multihash, 2)) } require(length == (2 + size), "bad multihash size"); if (algorithm == uint8(HashAlgorithm.Ethereum)) { require(signer != address(0x0), "invalid signer address"); require(size == 65, "bad Ethereum multihash size"); bytes32 hash; uint8 v; bytes32 r; bytes32 s; assembly { let data := mload(0x40) mstore(data, 0x19457468657265756d205369676e6564204d6573736167653a0a333200000000) // SIG_PREFIX mstore(add(data, 28), plaintext) // plaintext hash := keccak256(data, 60) // 28 + 32 // Extract v, r and s from the multihash data v := mload(add(multihash, 3)) r := mload(add(multihash, 35)) s := mload(add(multihash, 67)) } return signer == ecrecover( hash, v, r, s ); } else if (algorithm == uint8(HashAlgorithm.EIP712)) { require(signer != address(0x0), "invalid signer address"); require(size == 65, "bad EIP712 multihash size"); uint8 v; bytes32 r; bytes32 s; assembly { // Extract v, r and s from the multihash data v := mload(add(multihash, 3)) r := mload(add(multihash, 35)) s := mload(add(multihash, 67)) } return signer == ecrecover( plaintext, v, r, s ); } else { return false; } } } /* 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. */ /// @title ERC20 Token Interface /// @dev see https://github.com/ethereum/EIPs/issues/20 /// @author Daniel Wang - <[email protected]> contract ERC20 { function totalSupply() public view returns (uint256); function balanceOf( address who ) public view returns (uint256); function allowance( address owner, address spender ) public view returns (uint256); function transfer( address to, uint256 value ) public returns (bool); function transferFrom( address from, address to, uint256 value ) public returns (bool); function approve( address spender, uint256 value ) public returns (bool); function verifyTransfer( address from, address to, uint256 amount, bytes memory data ) public returns (bool); } /* 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. */ library Data { enum TokenType { ERC20 } struct Header { uint version; uint numOrders; uint numRings; uint numSpendables; } struct Context { address lrcTokenAddress; ITradeDelegate delegate; ITradeHistory tradeHistory; IBrokerRegistry orderBrokerRegistry; IOrderRegistry orderRegistry; IFeeHolder feeHolder; IOrderBook orderBook; IBurnRateTable burnRateTable; uint64 ringIndex; uint feePercentageBase; bytes32[] tokenBurnRates; uint feeData; uint feePtr; uint transferData; uint transferPtr; } struct Mining { // required fields address feeRecipient; // optional fields address miner; bytes sig; // computed fields bytes32 hash; address interceptor; } struct Spendable { bool initialized; uint amount; uint reserved; } struct Order { uint version; // required fields address owner; address tokenS; address tokenB; uint amountS; uint amountB; uint validSince; Spendable tokenSpendableS; Spendable tokenSpendableFee; // optional fields address dualAuthAddr; address broker; Spendable brokerSpendableS; Spendable brokerSpendableFee; address orderInterceptor; address wallet; uint validUntil; bytes sig; bytes dualAuthSig; bool allOrNone; address feeToken; uint feeAmount; int16 waiveFeePercentage; uint16 tokenSFeePercentage; // Pre-trading uint16 tokenBFeePercentage; // Post-trading address tokenRecipient; uint16 walletSplitPercentage; // computed fields bool P2P; bytes32 hash; address brokerInterceptor; uint filledAmountS; uint initialFilledAmountS; bool valid; TokenType tokenTypeS; TokenType tokenTypeB; TokenType tokenTypeFee; bytes32 trancheS; bytes32 trancheB; bytes transferDataS; } struct Participation { // required fields Order order; // computed fields uint splitS; uint feeAmount; uint feeAmountS; uint feeAmountB; uint rebateFee; uint rebateS; uint rebateB; uint fillAmountS; uint fillAmountB; } struct Ring{ uint size; Participation[] participations; bytes32 hash; uint minerFeesToOrdersPercentage; bool valid; } struct FeeContext { Data.Ring ring; Data.Context ctx; address feeRecipient; uint walletPercentage; int16 waiveFeePercentage; address owner; address wallet; bool P2P; } } /* 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. */ /// @title IRingSubmitter /// @author Daniel Wang - <[email protected]> /// @author Kongliang Zhong - <[email protected]> contract IRingSubmitter { uint16 public constant FEE_PERCENTAGE_BASE = 1000; /// @dev Event emitted when a ring was successfully mined /// _ringIndex The index of the ring /// _ringHash The hash of the ring /// _feeRecipient The recipient of the matching fee /// _fills The info of the orders in the ring stored like: /// [orderHash, owner, tokenS, amountS, split, feeAmount, feeAmountS, feeAmountB] event RingMined( uint _ringIndex, bytes32 indexed _ringHash, address indexed _feeRecipient, bytes _fills ); /// @dev Event emitted when a ring was not successfully mined /// _ringHash The hash of the ring event InvalidRing( bytes32 _ringHash ); /// @dev Submit order-rings for validation and settlement. /// @param data Packed data of all rings. function submitRings( bytes calldata data ) external; } /* 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. */ /* 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. */ /// @title MiningHelper /// @author Daniel Wang - <[email protected]>. library MiningHelper { function updateMinerAndInterceptor( Data.Mining memory mining ) internal pure { if (mining.miner == address(0x0)) { mining.miner = mining.feeRecipient; } // We do not support any interceptors for now /* else { */ /* (bool registered, address interceptor) = ctx.minerBrokerRegistry.getBroker( */ /* mining.feeRecipient, */ /* mining.miner */ /* ); */ /* if (registered) { */ /* mining.interceptor = interceptor; */ /* } */ /* } */ } function updateHash( Data.Mining memory mining, Data.Ring[] memory rings ) internal pure { bytes32 hash; assembly { let ring := mload(add(rings, 32)) // rings[0] let ringHashes := mload(add(ring, 64)) // ring.hash for { let i := 1 } lt(i, mload(rings)) { i := add(i, 1) } { ring := mload(add(rings, mul(add(i, 1), 32))) // rings[i] ringHashes := xor(ringHashes, mload(add(ring, 64))) // ring.hash } let data := mload(0x40) data := add(data, 12) // Store data back to front to allow overwriting data at the front because of padding mstore(add(data, 40), ringHashes) // ringHashes mstore(sub(add(data, 20), 12), mload(add(mining, 32))) // mining.miner mstore(sub(data, 12), mload(add(mining, 0))) // mining.feeRecipient hash := keccak256(data, 72) // 20 + 20 + 32 } mining.hash = hash; } function checkMinerSignature( Data.Mining memory mining ) internal view returns (bool) { if (mining.sig.length == 0) { return (msg.sender == mining.miner); } else { return MultihashUtil.verifySignature( mining.miner, mining.hash, mining.sig ); } } } /* 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. */ /// @title OrderHelper /// @author Daniel Wang - <[email protected]>. library OrderHelper { using MathUint for uint; string constant internal EIP191_HEADER = "\x19\x01"; string constant internal EIP712_DOMAIN_NAME = "Loopring Protocol"; string constant internal EIP712_DOMAIN_VERSION = "2"; bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256( abi.encodePacked( "EIP712Domain(", "string name,", "string version", ")" ) ); bytes32 constant internal EIP712_ORDER_SCHEMA_HASH = keccak256( abi.encodePacked( "Order(", "uint amountS,", "uint amountB,", "uint feeAmount,", "uint validSince,", "uint validUntil,", "address owner,", "address tokenS,", "address tokenB,", "address dualAuthAddr,", "address broker,", "address orderInterceptor,", "address wallet,", "address tokenRecipient,", "address feeToken,", "uint16 walletSplitPercentage,", "uint16 tokenSFeePercentage,", "uint16 tokenBFeePercentage,", "bool allOrNone,", "uint8 tokenTypeS,", "uint8 tokenTypeB,", "uint8 tokenTypeFee,", "bytes32 trancheS,", "bytes32 trancheB,", "bytes transferDataS", ")" ) ); bytes32 constant internal EIP712_DOMAIN_HASH = keccak256( abi.encodePacked( EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, keccak256(bytes(EIP712_DOMAIN_NAME)), keccak256(bytes(EIP712_DOMAIN_VERSION)) ) ); function updateHash(Data.Order memory order) internal pure { /* bytes32 message = keccak256( */ /* abi.encode( */ /* EIP712_ORDER_SCHEMA_HASH, */ /* order.amountS, */ /* order.amountB, */ /* order.feeAmount, */ /* order.validSince, */ /* order.validUntil, */ /* order.owner, */ /* order.tokenS, */ /* order.tokenB, */ /* order.dualAuthAddr, */ /* order.broker, */ /* order.orderInterceptor, */ /* order.wallet, */ /* order.tokenRecipient */ /* order.feeToken, */ /* order.walletSplitPercentage, */ /* order.tokenSFeePercentage, */ /* order.tokenBFeePercentage, */ /* order.allOrNone, */ /* order.tokenTypeS, */ /* order.tokenTypeB, */ /* order.tokenTypeFee, */ /* order.trancheS, */ /* order.trancheB, */ /* order.transferDataS */ /* ) */ /* ); */ /* order.hash = keccak256( */ /* abi.encodePacked( */ /* EIP191_HEADER, */ /* EIP712_DOMAIN_HASH, */ /* message */ /* ) */ /*); */ // Precalculated EIP712_ORDER_SCHEMA_HASH amd EIP712_DOMAIN_HASH because // the solidity compiler doesn't correctly precalculate them for us. bytes32 _EIP712_ORDER_SCHEMA_HASH = 0x40b942178d2a51f1f61934268590778feb8114db632db7d88537c98d2b05c5f2; bytes32 _EIP712_DOMAIN_HASH = 0xaea25658c273c666156bd427f83a666135fcde6887a6c25fc1cd1562bc4f3f34; bytes32 hash; assembly { // Calculate the hash for transferDataS separately let transferDataS := mload(add(order, 1184)) // order.transferDataS let transferDataSHash := keccak256(add(transferDataS, 32), mload(transferDataS)) let ptr := mload(64) mstore(add(ptr, 0), _EIP712_ORDER_SCHEMA_HASH) // EIP712_ORDER_SCHEMA_HASH mstore(add(ptr, 32), mload(add(order, 128))) // order.amountS mstore(add(ptr, 64), mload(add(order, 160))) // order.amountB mstore(add(ptr, 96), mload(add(order, 640))) // order.feeAmount mstore(add(ptr, 128), mload(add(order, 192))) // order.validSince mstore(add(ptr, 160), mload(add(order, 480))) // order.validUntil mstore(add(ptr, 192), mload(add(order, 32))) // order.owner mstore(add(ptr, 224), mload(add(order, 64))) // order.tokenS mstore(add(ptr, 256), mload(add(order, 96))) // order.tokenB mstore(add(ptr, 288), mload(add(order, 288))) // order.dualAuthAddr mstore(add(ptr, 320), mload(add(order, 320))) // order.broker mstore(add(ptr, 352), mload(add(order, 416))) // order.orderInterceptor mstore(add(ptr, 384), mload(add(order, 448))) // order.wallet mstore(add(ptr, 416), mload(add(order, 768))) // order.tokenRecipient mstore(add(ptr, 448), mload(add(order, 608))) // order.feeToken mstore(add(ptr, 480), mload(add(order, 800))) // order.walletSplitPercentage mstore(add(ptr, 512), mload(add(order, 704))) // order.tokenSFeePercentage mstore(add(ptr, 544), mload(add(order, 736))) // order.tokenBFeePercentage mstore(add(ptr, 576), mload(add(order, 576))) // order.allOrNone mstore(add(ptr, 608), mload(add(order, 1024))) // order.tokenTypeS mstore(add(ptr, 640), mload(add(order, 1056))) // order.tokenTypeB mstore(add(ptr, 672), mload(add(order, 1088))) // order.tokenTypeFee mstore(add(ptr, 704), mload(add(order, 1120))) // order.trancheS mstore(add(ptr, 736), mload(add(order, 1152))) // order.trancheB mstore(add(ptr, 768), transferDataSHash) // keccak256(order.transferDataS) let message := keccak256(ptr, 800) // 25 * 32 mstore(add(ptr, 0), 0x1901) // EIP191_HEADER mstore(add(ptr, 32), _EIP712_DOMAIN_HASH) // EIP712_DOMAIN_HASH mstore(add(ptr, 64), message) // message hash := keccak256(add(ptr, 30), 66) // 2 + 32 + 32 } order.hash = hash; } function updateBrokerAndInterceptor( Data.Order memory order, Data.Context memory ctx ) internal view { if (order.broker == address(0x0)) { order.broker = order.owner; } else { bool registered; (registered, /*order.brokerInterceptor*/) = ctx.orderBrokerRegistry.getBroker( order.owner, order.broker ); order.valid = order.valid && registered; } } function check( Data.Order memory order, Data.Context memory ctx ) internal view { // If the order was already partially filled // we don't have to check all of the infos and the signature again if(order.filledAmountS == 0) { validateAllInfo(order, ctx); checkBrokerSignature(order, ctx); } else { validateUnstableInfo(order, ctx); } checkP2P(order); } function validateAllInfo( Data.Order memory order, Data.Context memory ctx ) internal view { bool valid = true; valid = valid && (order.version == 0); // unsupported order version valid = valid && (order.owner != address(0x0)); // invalid order owner valid = valid && (order.tokenS != address(0x0)); // invalid order tokenS valid = valid && (order.tokenB != address(0x0)); // invalid order tokenB valid = valid && (order.amountS != 0); // invalid order amountS valid = valid && (order.amountB != 0); // invalid order amountB valid = valid && (order.feeToken != address(0x0)); // invalid fee token valid = valid && (order.tokenSFeePercentage < ctx.feePercentageBase); // invalid tokenS percentage valid = valid && (order.tokenBFeePercentage < ctx.feePercentageBase); // invalid tokenB percentage valid = valid && (order.walletSplitPercentage <= 100); // invalid wallet split percentage // We only support ERC20 for now valid = valid && (order.tokenTypeS == Data.TokenType.ERC20 && order.trancheS == 0x0); valid = valid && (order.tokenTypeB == Data.TokenType.ERC20 && order.trancheB == 0x0); valid = valid && (order.tokenTypeFee == Data.TokenType.ERC20); valid = valid && (order.transferDataS.length == 0); valid = valid && (order.validSince <= now); // order is too early to match order.valid = order.valid && valid; validateUnstableInfo(order, ctx); } function validateUnstableInfo( Data.Order memory order, Data.Context memory ctx ) internal view { bool valid = true; valid = valid && (order.validUntil == 0 || order.validUntil > now); // order is expired valid = valid && (order.waiveFeePercentage <= int16(ctx.feePercentageBase)); // invalid waive percentage valid = valid && (order.waiveFeePercentage >= -int16(ctx.feePercentageBase)); // invalid waive percentage if (order.dualAuthAddr != address(0x0)) { // if dualAuthAddr exists, dualAuthSig must be exist. valid = valid && (order.dualAuthSig.length > 0); } order.valid = order.valid && valid; } function checkP2P( Data.Order memory order ) internal pure { order.P2P = (order.tokenSFeePercentage > 0 || order.tokenBFeePercentage > 0); } function checkBrokerSignature( Data.Order memory order, Data.Context memory ctx ) internal view { if (order.sig.length == 0) { bool registered = ctx.orderRegistry.isOrderHashRegistered( order.broker, order.hash ); if (!registered) { order.valid = order.valid && ctx.orderBook.orderSubmitted(order.hash); } } else { order.valid = order.valid && MultihashUtil.verifySignature( order.broker, order.hash, order.sig ); } } function checkDualAuthSignature( Data.Order memory order, bytes32 miningHash ) internal pure { if (order.dualAuthSig.length != 0) { order.valid = order.valid && MultihashUtil.verifySignature( order.dualAuthAddr, miningHash, order.dualAuthSig ); } } function validateAllOrNone( Data.Order memory order ) internal pure { // Check if this order needs to be completely filled if(order.allOrNone) { order.valid = order.valid && (order.filledAmountS == order.amountS); } } function getSpendableS( Data.Order memory order, Data.Context memory ctx ) internal view returns (uint) { return getSpendable( ctx.delegate, order.tokenS, order.owner, order.tokenSpendableS ); } function getSpendableFee( Data.Order memory order, Data.Context memory ctx ) internal view returns (uint) { return getSpendable( ctx.delegate, order.feeToken, order.owner, order.tokenSpendableFee ); } function reserveAmountS( Data.Order memory order, uint amount ) internal pure { order.tokenSpendableS.reserved += amount; } function reserveAmountFee( Data.Order memory order, uint amount ) internal pure { order.tokenSpendableFee.reserved += amount; } function resetReservations( Data.Order memory order ) internal pure { order.tokenSpendableS.reserved = 0; order.tokenSpendableFee.reserved = 0; } /// @return Amount of ERC20 token that can be spent by this contract. function getERC20Spendable( ITradeDelegate delegate, address tokenAddress, address owner ) private view returns (uint spendable) { ERC20 token = ERC20(tokenAddress); spendable = token.allowance( owner, address(delegate) ); if (spendable != 0) { uint balance = token.balanceOf(owner); spendable = (balance < spendable) ? balance : spendable; } } function getSpendable( ITradeDelegate delegate, address tokenAddress, address owner, Data.Spendable memory tokenSpendable ) private view returns (uint spendable) { if (!tokenSpendable.initialized) { tokenSpendable.amount = getERC20Spendable( delegate, tokenAddress, owner ); tokenSpendable.initialized = true; } spendable = tokenSpendable.amount.sub(tokenSpendable.reserved); } } /* 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. */ /* 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. */ /// @title ParticipationHelper /// @author Daniel Wang - <[email protected]>. library ParticipationHelper { using MathUint for uint; using OrderHelper for Data.Order; function setMaxFillAmounts( Data.Participation memory p, Data.Context memory ctx ) internal view { uint spendableS = p.order.getSpendableS(ctx); uint remainingS = p.order.amountS.sub(p.order.filledAmountS); p.fillAmountS = (spendableS < remainingS) ? spendableS : remainingS; if (!p.order.P2P) { // No need to check the fee balance of the owner if feeToken == tokenB, // fillAmountB will be used to pay the fee. if (!(p.order.feeToken == p.order.tokenB && p.order.owner == p.order.tokenRecipient && p.order.feeAmount <= p.order.amountB)) { // Check how much fee needs to be paid. We limit fillAmountS to how much // fee the order owner can pay. uint feeAmount = p.order.feeAmount.mul(p.fillAmountS) / p.order.amountS; if (feeAmount > 0) { uint spendableFee = p.order.getSpendableFee(ctx); if (p.order.feeToken == p.order.tokenS && p.fillAmountS + feeAmount > spendableS) { assert(spendableFee == spendableS); // Equally divide the available tokens between fillAmountS and feeAmount uint totalAmount = p.order.amountS.add(p.order.feeAmount); p.fillAmountS = spendableS.mul(p.order.amountS) / totalAmount; feeAmount = spendableS.mul(p.order.feeAmount) / totalAmount; } else if (feeAmount > spendableFee) { // Scale down fillAmountS so the available feeAmount is sufficient feeAmount = spendableFee; p.fillAmountS = feeAmount.mul(p.order.amountS) / p.order.feeAmount; } } } } p.fillAmountB = p.fillAmountS.mul(p.order.amountB) / p.order.amountS; } function calculateFees( Data.Participation memory p, Data.Participation memory prevP, Data.Context memory ctx ) internal view returns (bool) { if (p.order.P2P) { // Calculate P2P fees p.feeAmount = 0; p.feeAmountS = p.fillAmountS.mul(p.order.tokenSFeePercentage) / ctx.feePercentageBase; p.feeAmountB = p.fillAmountB.mul(p.order.tokenBFeePercentage) / ctx.feePercentageBase; } else { // Calculate matching fees p.feeAmount = p.order.feeAmount.mul(p.fillAmountS) / p.order.amountS; p.feeAmountS = 0; p.feeAmountB = 0; // If feeToken == tokenB AND owner == tokenRecipient, try to pay using fillAmountB if (p.order.feeToken == p.order.tokenB && p.order.owner == p.order.tokenRecipient && p.fillAmountB >= p.feeAmount) { p.feeAmountB = p.feeAmount; p.feeAmount = 0; } if (p.feeAmount > 0) { // Make sure we can pay the feeAmount uint spendableFee = p.order.getSpendableFee(ctx); if (p.feeAmount > spendableFee) { // This normally should not happen, but this is possible when self-trading return false; } else { p.order.reserveAmountFee(p.feeAmount); } } } if ((p.fillAmountS - p.feeAmountS) >= prevP.fillAmountB) { // The miner (or in a P2P case, the taker) gets the margin p.splitS = (p.fillAmountS - p.feeAmountS) - prevP.fillAmountB; p.fillAmountS = prevP.fillAmountB + p.feeAmountS; return true; } else { return false; } } function checkFills( Data.Participation memory p ) internal pure returns (bool valid) { // Check if the rounding error of the calculated fillAmountB is larger than 1%. // If that's the case, this partipation in invalid // p.fillAmountB := p.fillAmountS.mul(p.order.amountB) / p.order.amountS valid = !MathUint.hasRoundingError( p.fillAmountS, p.order.amountB, p.order.amountS ); // We at least need to buy and sell something valid = valid && p.fillAmountS > 0; valid = valid && p.fillAmountB > 0; } function adjustOrderState( Data.Participation memory p ) internal pure { // Update filled amount p.order.filledAmountS += p.fillAmountS + p.splitS; // Update spendables uint totalAmountS = p.fillAmountS + p.splitS; uint totalAmountFee = p.feeAmount; p.order.tokenSpendableS.amount = p.order.tokenSpendableS.amount.sub(totalAmountS); p.order.tokenSpendableFee.amount = p.order.tokenSpendableFee.amount.sub(totalAmountFee); if (p.order.brokerInterceptor != address(0x0)) { p.order.brokerSpendableS.amount = p.order.brokerSpendableS.amount.sub(totalAmountS); p.order.brokerSpendableFee.amount = p.order.brokerSpendableFee.amount.sub(totalAmountFee); } } function revertOrderState( Data.Participation memory p ) internal pure { // Revert filled amount p.order.filledAmountS = p.order.filledAmountS.sub(p.fillAmountS + p.splitS); // We do not revert any spendables. Rings will not get rebalanced so this doesn't matter. } } /// @title RingHelper library RingHelper { using MathUint for uint; using OrderHelper for Data.Order; using ParticipationHelper for Data.Participation; function updateHash( Data.Ring memory ring ) internal pure { uint ringSize = ring.size; bytes32 hash; assembly { let data := mload(0x40) let ptr := data let participations := mload(add(ring, 32)) // ring.participations for { let i := 0 } lt(i, ringSize) { i := add(i, 1) } { let participation := mload(add(participations, add(32, mul(i, 32)))) // participations[i] let order := mload(participation) // participation.order let waiveFeePercentage := and(mload(add(order, 672)), 0xFFFF) // order.waiveFeePercentage let orderHash := mload(add(order, 864)) // order.hash mstore(add(ptr, 2), waiveFeePercentage) mstore(ptr, orderHash) ptr := add(ptr, 34) } hash := keccak256(data, sub(ptr, data)) } ring.hash = hash; } function calculateFillAmountAndFee( Data.Ring memory ring, Data.Context memory ctx ) internal { // Invalid order data could cause a divide by zero in the calculations if (!ring.valid) { return; } uint i; int j; uint prevIndex; for (i = 0; i < ring.size; i++) { ring.participations[i].setMaxFillAmounts( ctx ); } uint smallest = 0; for (j = int(ring.size) - 1; j >= 0; j--) { prevIndex = (uint(j) + ring.size - 1) % ring.size; smallest = calculateOrderFillAmounts( ctx, ring.participations[uint(j)], ring.participations[prevIndex], uint(j), smallest ); } for (j = int(ring.size) - 1; j >= int(smallest); j--) { prevIndex = (uint(j) + ring.size - 1) % ring.size; calculateOrderFillAmounts( ctx, ring.participations[uint(j)], ring.participations[prevIndex], uint(j), smallest ); } for (i = 0; i < ring.size; i++) { // Check if the fill amounts of the participation are valid ring.valid = ring.valid && ring.participations[i].checkFills(); // Reserve the total amount tokenS used for all the orders // (e.g. the owner of order 0 could use LRC as feeToken in order 0, while // the same owner can also sell LRC in order 2). ring.participations[i].order.reserveAmountS(ring.participations[i].fillAmountS); } for (i = 0; i < ring.size; i++) { prevIndex = (i + ring.size - 1) % ring.size; // Check if we can transfer the tokens (if ST-20) ring.valid = ring.valid && verifyTransferProxy( ring.participations[i].order.tokenS, ring.participations[i].order.owner, ring.participations[prevIndex].order.tokenRecipient, ring.participations[i].fillAmountS ); bool valid = ring.participations[i].calculateFees(ring.participations[prevIndex], ctx); if (!valid) { ring.valid = false; break; } int16 waiveFeePercentage = ring.participations[i].order.waiveFeePercentage; if (waiveFeePercentage < 0) { ring.minerFeesToOrdersPercentage += uint(-waiveFeePercentage); } } // Miner can only distribute 100% of its fees to all orders combined ring.valid = ring.valid && (ring.minerFeesToOrdersPercentage <= ctx.feePercentageBase); // Ring calculations are done. Make sure te remove all spendable reservations for this ring for (i = 0; i < ring.size; i++) { ring.participations[i].order.resetReservations(); } } // ST-20: transfer, transferFrom must respect the result of verifyTransfer function verifyTransferProxy( address token, address from, address to, uint256 amount ) internal returns (bool) { bytes memory callData = abi.encodeWithSelector( ERC20(token).verifyTransfer.selector, from, to, amount, new bytes(0) ); (bool success, bytes memory returnData) = token.call(callData); // We expect a single boolean as the return value if (success && returnData.length == 32) { // Check if a boolean was returned assembly { success := mload(add(returnData, 32)) } return success; } else { // No function found, normal ERC20 token return true; } } function calculateOrderFillAmounts( Data.Context memory ctx, Data.Participation memory p, Data.Participation memory prevP, uint i, uint smallest ) internal pure returns (uint smallest_) { // Default to the same smallest index smallest_ = smallest; uint postFeeFillAmountS = p.fillAmountS; uint tokenSFeePercentage = p.order.tokenSFeePercentage; if (tokenSFeePercentage > 0) { uint feeAmountS = p.fillAmountS.mul(tokenSFeePercentage) / ctx.feePercentageBase; postFeeFillAmountS = p.fillAmountS - feeAmountS; } if (prevP.fillAmountB > postFeeFillAmountS) { smallest_ = i; prevP.fillAmountB = postFeeFillAmountS; prevP.fillAmountS = postFeeFillAmountS.mul(prevP.order.amountS) / prevP.order.amountB; } } function checkOrdersValid( Data.Ring memory ring ) internal pure { ring.valid = ring.valid && (ring.size > 1 && ring.size <= 8); // invalid ring size for (uint i = 0; i < ring.size; i++) { uint prev = (i + ring.size - 1) % ring.size; ring.valid = ring.valid && ring.participations[i].order.valid; ring.valid = ring.valid && ring.participations[i].order.tokenS == ring.participations[prev].order.tokenB; } } function checkForSubRings( Data.Ring memory ring ) internal pure { for (uint i = 0; i < ring.size - 1; i++) { address tokenS = ring.participations[i].order.tokenS; for (uint j = i + 1; j < ring.size; j++) { ring.valid = ring.valid && (tokenS != ring.participations[j].order.tokenS); } } } function adjustOrderStates( Data.Ring memory ring ) internal pure { // Adjust the orders for (uint i = 0; i < ring.size; i++) { ring.participations[i].adjustOrderState(); } } function revertOrderStats( Data.Ring memory ring ) internal pure { for (uint i = 0; i < ring.size; i++) { ring.participations[i].revertOrderState(); } } function doPayments( Data.Ring memory ring, Data.Context memory ctx, Data.Mining memory mining ) internal view { payFees(ring, ctx, mining); transferTokens(ring, ctx, mining.feeRecipient); } function generateFills( Data.Ring memory ring, uint destPtr ) internal pure returns (uint fill) { uint ringSize = ring.size; uint fillSize = 8 * 32; assembly { fill := destPtr let participations := mload(add(ring, 32)) // ring.participations for { let i := 0 } lt(i, ringSize) { i := add(i, 1) } { let participation := mload(add(participations, add(32, mul(i, 32)))) // participations[i] let order := mload(participation) // participation.order // Calculate the actual fees paid after rebate let feeAmount := sub( mload(add(participation, 64)), // participation.feeAmount mload(add(participation, 160)) // participation.rebateFee ) let feeAmountS := sub( mload(add(participation, 96)), // participation.feeAmountS mload(add(participation, 192)) // participation.rebateFeeS ) let feeAmountB := sub( mload(add(participation, 128)), // participation.feeAmountB mload(add(participation, 224)) // participation.rebateFeeB ) mstore(add(fill, 0), mload(add(order, 864))) // order.hash mstore(add(fill, 32), mload(add(order, 32))) // order.owner mstore(add(fill, 64), mload(add(order, 64))) // order.tokenS mstore(add(fill, 96), mload(add(participation, 256))) // participation.fillAmountS mstore(add(fill, 128), mload(add(participation, 32))) // participation.splitS mstore(add(fill, 160), feeAmount) // feeAmount mstore(add(fill, 192), feeAmountS) // feeAmountS mstore(add(fill, 224), feeAmountB) // feeAmountB fill := add(fill, fillSize) } } } function transferTokens( Data.Ring memory ring, Data.Context memory ctx, address feeRecipient ) internal pure { for (uint i = 0; i < ring.size; i++) { transferTokensForParticipation( ctx, feeRecipient, ring.participations[i], ring.participations[(i + ring.size - 1) % ring.size] ); } } function transferTokensForParticipation( Data.Context memory ctx, address feeRecipient, Data.Participation memory p, Data.Participation memory prevP ) internal pure returns (uint) { uint buyerFeeAmountAfterRebateB = prevP.feeAmountB.sub(prevP.rebateB); // If the buyer needs to pay fees in tokenB, the seller needs // to send the tokenS amount to the fee holder contract uint amountSToBuyer = p.fillAmountS .sub(p.feeAmountS) .sub(buyerFeeAmountAfterRebateB); uint amountSToFeeHolder = p.feeAmountS .sub(p.rebateS) .add(buyerFeeAmountAfterRebateB); uint amountFeeToFeeHolder = p.feeAmount .sub(p.rebateFee); if (p.order.tokenS == p.order.feeToken) { amountSToFeeHolder = amountSToFeeHolder.add(amountFeeToFeeHolder); amountFeeToFeeHolder = 0; } // Transfers ctx.transferPtr = addTokenTransfer( ctx.transferData, ctx.transferPtr, p.order.feeToken, p.order.owner, address(ctx.feeHolder), amountFeeToFeeHolder ); ctx.transferPtr = addTokenTransfer( ctx.transferData, ctx.transferPtr, p.order.tokenS, p.order.owner, address(ctx.feeHolder), amountSToFeeHolder ); ctx.transferPtr = addTokenTransfer( ctx.transferData, ctx.transferPtr, p.order.tokenS, p.order.owner, prevP.order.tokenRecipient, amountSToBuyer ); // Miner (or for P2P the taker) gets the margin without sharing it with the wallet or burning ctx.transferPtr = addTokenTransfer( ctx.transferData, ctx.transferPtr, p.order.tokenS, p.order.owner, feeRecipient, p.splitS ); } function addTokenTransfer( uint data, uint ptr, address token, address from, address to, uint amount ) internal pure returns (uint) { if (amount > 0 && from != to) { assembly { // Try to find an existing fee payment of the same token to the same owner let addNew := 1 for { let p := data } lt(p, ptr) { p := add(p, 128) } { let dataToken := mload(add(p, 0)) let dataFrom := mload(add(p, 32)) let dataTo := mload(add(p, 64)) // if(token == dataToken && from == dataFrom && to == dataTo) if and(and(eq(token, dataToken), eq(from, dataFrom)), eq(to, dataTo)) { let dataAmount := mload(add(p, 96)) // dataAmount = amount.add(dataAmount); dataAmount := add(amount, dataAmount) // require(dataAmount >= amount) (safe math) if lt(dataAmount, amount) { revert(0, 0) } mstore(add(p, 96), dataAmount) addNew := 0 // End the loop p := ptr } } // Add a new transfer if eq(addNew, 1) { mstore(add(ptr, 0), token) mstore(add(ptr, 32), from) mstore(add(ptr, 64), to) mstore(add(ptr, 96), amount) ptr := add(ptr, 128) } } return ptr; } else { return ptr; } } function payFees( Data.Ring memory ring, Data.Context memory ctx, Data.Mining memory mining ) internal view { Data.FeeContext memory feeCtx; feeCtx.ring = ring; feeCtx.ctx = ctx; feeCtx.feeRecipient = mining.feeRecipient; for (uint i = 0; i < ring.size; i++) { payFeesForParticipation( feeCtx, ring.participations[i] ); } } function payFeesForParticipation( Data.FeeContext memory feeCtx, Data.Participation memory p ) internal view returns (uint) { feeCtx.walletPercentage = p.order.P2P ? 100 : ( (p.order.wallet == address(0x0) ? 0 : p.order.walletSplitPercentage) ); feeCtx.waiveFeePercentage = p.order.waiveFeePercentage; feeCtx.owner = p.order.owner; feeCtx.wallet = p.order.wallet; feeCtx.P2P = p.order.P2P; p.rebateFee = payFeesAndBurn( feeCtx, p.order.feeToken, p.feeAmount ); p.rebateS = payFeesAndBurn( feeCtx, p.order.tokenS, p.feeAmountS ); p.rebateB = payFeesAndBurn( feeCtx, p.order.tokenB, p.feeAmountB ); } function payFeesAndBurn( Data.FeeContext memory feeCtx, address token, uint totalAmount ) internal view returns (uint) { if (totalAmount == 0) { return 0; } uint amount = totalAmount; // No need to pay any fees in a P2P order without a wallet // (but the fee amount is a part of amountS of the order, so the fee amount is rebated). if (feeCtx.P2P && feeCtx.wallet == address(0x0)) { amount = 0; } uint feeToWallet = 0; uint minerFee = 0; uint minerFeeBurn = 0; uint walletFeeBurn = 0; if (amount > 0) { feeToWallet = amount.mul(feeCtx.walletPercentage) / 100; minerFee = amount - feeToWallet; // Miner can waive fees for this order. If waiveFeePercentage > 0 this is a simple reduction in fees. if (feeCtx.waiveFeePercentage > 0) { minerFee = minerFee.mul( feeCtx.ctx.feePercentageBase - uint(feeCtx.waiveFeePercentage)) / feeCtx.ctx.feePercentageBase; } else if (feeCtx.waiveFeePercentage < 0) { // No fees need to be paid by this order minerFee = 0; } uint32 burnRate = getBurnRate(feeCtx, token); assert(burnRate <= feeCtx.ctx.feePercentageBase); // Miner fee minerFeeBurn = minerFee.mul(burnRate) / feeCtx.ctx.feePercentageBase; minerFee = minerFee - minerFeeBurn; // Wallet fee walletFeeBurn = feeToWallet.mul(burnRate) / feeCtx.ctx.feePercentageBase; feeToWallet = feeToWallet - walletFeeBurn; // Pay the wallet feeCtx.ctx.feePtr = addFeePayment( feeCtx.ctx.feeData, feeCtx.ctx.feePtr, token, feeCtx.wallet, feeToWallet ); // Pay the burn rate with the feeHolder as owner feeCtx.ctx.feePtr = addFeePayment( feeCtx.ctx.feeData, feeCtx.ctx.feePtr, token, address(feeCtx.ctx.feeHolder), minerFeeBurn + walletFeeBurn ); // Fees can be paid out in different tokens so we can't easily accumulate the total fee // that needs to be paid out to order owners. So we pay out each part out here to all // orders that need it. uint feeToMiner = minerFee; if (feeCtx.ring.minerFeesToOrdersPercentage > 0 && minerFee > 0) { // Pay out the fees to the orders distributeMinerFeeToOwners( feeCtx, token, minerFee ); // Subtract all fees the miner pays to the orders feeToMiner = minerFee.mul(feeCtx.ctx.feePercentageBase - feeCtx.ring.minerFeesToOrdersPercentage) / feeCtx.ctx.feePercentageBase; } // Pay the miner feeCtx.ctx.feePtr = addFeePayment( feeCtx.ctx.feeData, feeCtx.ctx.feePtr, token, feeCtx.feeRecipient, feeToMiner ); } // Calculate the total fee payment after possible discounts (burn rebate + fee waiving) // and return the total rebate return totalAmount.sub((feeToWallet + minerFee) + (minerFeeBurn + walletFeeBurn)); } function getBurnRate( Data.FeeContext memory feeCtx, address token ) internal view returns (uint32) { bytes32[] memory tokenBurnRates = feeCtx.ctx.tokenBurnRates; uint length = tokenBurnRates.length; for (uint i = 0; i < length; i += 2) { if (token == address(bytes20(tokenBurnRates[i]))) { uint32 burnRate = uint32(bytes4(tokenBurnRates[i + 1])); return feeCtx.P2P ? (burnRate / 0x10000) : (burnRate & 0xFFFF); } } // Not found, add it to the list uint32 burnRate = feeCtx.ctx.burnRateTable.getBurnRate(token); assembly { let ptr := add(tokenBurnRates, mul(add(1, length), 32)) mstore(ptr, token) // token mstore(add(ptr, 32), burnRate) // burn rate mstore(tokenBurnRates, add(length, 2)) // length } return feeCtx.P2P ? (burnRate / 0x10000) : (burnRate & 0xFFFF); } function distributeMinerFeeToOwners( Data.FeeContext memory feeCtx, address token, uint minerFee ) internal pure { for (uint i = 0; i < feeCtx.ring.size; i++) { if (feeCtx.ring.participations[i].order.waiveFeePercentage < 0) { uint feeToOwner = minerFee .mul(uint(-feeCtx.ring.participations[i].order.waiveFeePercentage)) / feeCtx.ctx.feePercentageBase; feeCtx.ctx.feePtr = addFeePayment( feeCtx.ctx.feeData, feeCtx.ctx.feePtr, token, feeCtx.ring.participations[i].order.owner, feeToOwner); } } } function addFeePayment( uint data, uint ptr, address token, address owner, uint amount ) internal pure returns (uint) { if (amount == 0) { return ptr; } else { assembly { // Try to find an existing fee payment of the same token to the same owner let addNew := 1 for { let p := data } lt(p, ptr) { p := add(p, 96) } { let dataToken := mload(add(p, 0)) let dataOwner := mload(add(p, 32)) // if(token == dataToken && owner == dataOwner) if and(eq(token, dataToken), eq(owner, dataOwner)) { let dataAmount := mload(add(p, 64)) // dataAmount = amount.add(dataAmount); dataAmount := add(amount, dataAmount) // require(dataAmount >= amount) (safe math) if lt(dataAmount, amount) { revert(0, 0) } mstore(add(p, 64), dataAmount) addNew := 0 // End the loop p := ptr } } // Add a new fee payment if eq(addNew, 1) { mstore(add(ptr, 0), token) mstore(add(ptr, 32), owner) mstore(add(ptr, 64), amount) ptr := add(ptr, 96) } } return ptr; } } } /* 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. */ /* 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. */ /// @title Errors contract Errors { string constant ZERO_VALUE = "ZERO_VALUE"; string constant ZERO_ADDRESS = "ZERO_ADDRESS"; string constant INVALID_VALUE = "INVALID_VALUE"; string constant INVALID_ADDRESS = "INVALID_ADDRESS"; string constant INVALID_SIZE = "INVALID_SIZE"; string constant INVALID_SIG = "INVALID_SIG"; string constant INVALID_STATE = "INVALID_STATE"; string constant NOT_FOUND = "NOT_FOUND"; string constant ALREADY_EXIST = "ALREADY_EXIST"; string constant REENTRY = "REENTRY"; string constant UNAUTHORIZED = "UNAUTHORIZED"; string constant UNIMPLEMENTED = "UNIMPLEMENTED"; string constant UNSUPPORTED = "UNSUPPORTED"; string constant TRANSFER_FAILURE = "TRANSFER_FAILURE"; string constant WITHDRAWAL_FAILURE = "WITHDRAWAL_FAILURE"; string constant BURN_FAILURE = "BURN_FAILURE"; string constant BURN_RATE_FROZEN = "BURN_RATE_FROZEN"; string constant BURN_RATE_MINIMIZED = "BURN_RATE_MINIMIZED"; string constant UNAUTHORIZED_ONCHAIN_ORDER = "UNAUTHORIZED_ONCHAIN_ORDER"; string constant INVALID_CANDIDATE = "INVALID_CANDIDATE"; string constant ALREADY_VOTED = "ALREADY_VOTED"; string constant NOT_OWNER = "NOT_OWNER"; } /// @title NoDefaultFunc /// @dev Disable default functions. contract NoDefaultFunc is Errors { function () external payable { revert(UNSUPPORTED); } } /* 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. */ /// @title Deserializes the data passed to submitRings /// @author Daniel Wang - <[email protected]>, library ExchangeDeserializer { using BytesUtil for bytes; function deserialize( address lrcTokenAddress, bytes memory data ) internal view returns ( Data.Mining memory mining, Data.Order[] memory orders, Data.Ring[] memory rings ) { // Read the header Data.Header memory header; header.version = data.bytesToUint16(0); header.numOrders = data.bytesToUint16(2); header.numRings = data.bytesToUint16(4); header.numSpendables = data.bytesToUint16(6); // Validation require(header.version == 0, "Unsupported serialization format"); require(header.numOrders > 0, "Invalid number of orders"); require(header.numRings > 0, "Invalid number of rings"); require(header.numSpendables > 0, "Invalid number of spendables"); // Calculate data pointers uint dataPtr; assembly { dataPtr := data } uint miningDataPtr = dataPtr + 8; uint orderDataPtr = miningDataPtr + 3 * 2; uint ringDataPtr = orderDataPtr + (30 * header.numOrders) * 2; uint dataBlobPtr = ringDataPtr + (header.numRings * 9) + 32; // The data stream needs to be at least large enough for the // header/mining/orders/rings data + 64 bytes of zeros in the data blob. require(data.length >= (dataBlobPtr - dataPtr) + 32, "Invalid input data"); // Setup the rings mining = setupMiningData(dataBlobPtr, miningDataPtr + 2); orders = setupOrders(dataBlobPtr, orderDataPtr + 2, header.numOrders, header.numSpendables, lrcTokenAddress); rings = assembleRings(ringDataPtr + 1, header.numRings, orders); } function setupMiningData( uint data, uint tablesPtr ) internal view returns (Data.Mining memory mining) { bytes memory emptyBytes = new bytes(0); uint offset; assembly { // Default to transaction origin for feeRecipient mstore(add(data, 20), origin) // mining.feeRecipient offset := mul(and(mload(add(tablesPtr, 0)), 0xFFFF), 4) mstore( add(mining, 0), mload(add(add(data, 20), offset)) ) // Restore default to 0 mstore(add(data, 20), 0) // mining.miner offset := mul(and(mload(add(tablesPtr, 2)), 0xFFFF), 4) mstore( add(mining, 32), mload(add(add(data, 20), offset)) ) // Default to empty bytes array mstore(add(data, 32), emptyBytes) // mining.sig offset := mul(and(mload(add(tablesPtr, 4)), 0xFFFF), 4) mstore( add(mining, 64), add(data, add(offset, 32)) ) // Restore default to 0 mstore(add(data, 32), 0) } } function setupOrders( uint data, uint tablesPtr, uint numOrders, uint numSpendables, address lrcTokenAddress ) internal pure returns (Data.Order[] memory orders) { bytes memory emptyBytes = new bytes(0); uint orderStructSize = 38 * 32; // Memory for orders length + numOrders order pointers uint arrayDataSize = (1 + numOrders) * 32; Data.Spendable[] memory spendableList = new Data.Spendable[](numSpendables); uint offset; assembly { // Allocate memory for all orders orders := mload(0x40) mstore(add(orders, 0), numOrders) // orders.length // Reserve the memory for the orders array mstore(0x40, add(orders, add(arrayDataSize, mul(orderStructSize, numOrders)))) for { let i := 0 } lt(i, numOrders) { i := add(i, 1) } { let order := add(orders, add(arrayDataSize, mul(orderStructSize, i))) // Store the memory location of this order in the orders array mstore(add(orders, mul(add(1, i), 32)), order) // order.version offset := and(mload(add(tablesPtr, 0)), 0xFFFF) mstore( add(order, 0), offset ) // order.owner offset := mul(and(mload(add(tablesPtr, 2)), 0xFFFF), 4) mstore( add(order, 32), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.tokenS offset := mul(and(mload(add(tablesPtr, 4)), 0xFFFF), 4) mstore( add(order, 64), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.tokenB offset := mul(and(mload(add(tablesPtr, 6)), 0xFFFF), 4) mstore( add(order, 96), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.amountS offset := mul(and(mload(add(tablesPtr, 8)), 0xFFFF), 4) mstore( add(order, 128), mload(add(add(data, 32), offset)) ) // order.amountB offset := mul(and(mload(add(tablesPtr, 10)), 0xFFFF), 4) mstore( add(order, 160), mload(add(add(data, 32), offset)) ) // order.validSince offset := mul(and(mload(add(tablesPtr, 12)), 0xFFFF), 4) mstore( add(order, 192), and(mload(add(add(data, 4), offset)), 0xFFFFFFFF) ) // order.tokenSpendableS offset := and(mload(add(tablesPtr, 14)), 0xFFFF) // Force the spendable index to 0 if it's invalid offset := mul(offset, lt(offset, numSpendables)) mstore( add(order, 224), mload(add(spendableList, mul(add(offset, 1), 32))) ) // order.tokenSpendableFee offset := and(mload(add(tablesPtr, 16)), 0xFFFF) // Force the spendable index to 0 if it's invalid offset := mul(offset, lt(offset, numSpendables)) mstore( add(order, 256), mload(add(spendableList, mul(add(offset, 1), 32))) ) // order.dualAuthAddr offset := mul(and(mload(add(tablesPtr, 18)), 0xFFFF), 4) mstore( add(order, 288), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.broker offset := mul(and(mload(add(tablesPtr, 20)), 0xFFFF), 4) mstore( add(order, 320), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.orderInterceptor offset := mul(and(mload(add(tablesPtr, 22)), 0xFFFF), 4) mstore( add(order, 416), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.wallet offset := mul(and(mload(add(tablesPtr, 24)), 0xFFFF), 4) mstore( add(order, 448), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // order.validUntil offset := mul(and(mload(add(tablesPtr, 26)), 0xFFFF), 4) mstore( add(order, 480), and(mload(add(add(data, 4), offset)), 0xFFFFFFFF) ) // Default to empty bytes array for value sig and dualAuthSig mstore(add(data, 32), emptyBytes) // order.sig offset := mul(and(mload(add(tablesPtr, 28)), 0xFFFF), 4) mstore( add(order, 512), add(data, add(offset, 32)) ) // order.dualAuthSig offset := mul(and(mload(add(tablesPtr, 30)), 0xFFFF), 4) mstore( add(order, 544), add(data, add(offset, 32)) ) // Restore default to 0 mstore(add(data, 32), 0) // order.allOrNone offset := and(mload(add(tablesPtr, 32)), 0xFFFF) mstore( add(order, 576), gt(offset, 0) ) // lrcTokenAddress is the default value for feeToken mstore(add(data, 20), lrcTokenAddress) // order.feeToken offset := mul(and(mload(add(tablesPtr, 34)), 0xFFFF), 4) mstore( add(order, 608), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // Restore default to 0 mstore(add(data, 20), 0) // order.feeAmount offset := mul(and(mload(add(tablesPtr, 36)), 0xFFFF), 4) mstore( add(order, 640), mload(add(add(data, 32), offset)) ) // order.waiveFeePercentage offset := and(mload(add(tablesPtr, 38)), 0xFFFF) mstore( add(order, 672), offset ) // order.tokenSFeePercentage offset := and(mload(add(tablesPtr, 40)), 0xFFFF) mstore( add(order, 704), offset ) // order.tokenBFeePercentage offset := and(mload(add(tablesPtr, 42)), 0xFFFF) mstore( add(order, 736), offset ) // The owner is the default value of tokenRecipient mstore(add(data, 20), mload(add(order, 32))) // order.owner // order.tokenRecipient offset := mul(and(mload(add(tablesPtr, 44)), 0xFFFF), 4) mstore( add(order, 768), and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) // Restore default to 0 mstore(add(data, 20), 0) // order.walletSplitPercentage offset := and(mload(add(tablesPtr, 46)), 0xFFFF) mstore( add(order, 800), offset ) // order.tokenTypeS offset := and(mload(add(tablesPtr, 48)), 0xFFFF) mstore( add(order, 1024), offset ) // order.tokenTypeB offset := and(mload(add(tablesPtr, 50)), 0xFFFF) mstore( add(order, 1056), offset ) // order.tokenTypeFee offset := and(mload(add(tablesPtr, 52)), 0xFFFF) mstore( add(order, 1088), offset ) // order.trancheS offset := mul(and(mload(add(tablesPtr, 54)), 0xFFFF), 4) mstore( add(order, 1120), mload(add(add(data, 32), offset)) ) // order.trancheB offset := mul(and(mload(add(tablesPtr, 56)), 0xFFFF), 4) mstore( add(order, 1152), mload(add(add(data, 32), offset)) ) // Default to empty bytes array for transferDataS mstore(add(data, 32), emptyBytes) // order.transferDataS offset := mul(and(mload(add(tablesPtr, 58)), 0xFFFF), 4) mstore( add(order, 1184), add(data, add(offset, 32)) ) // Restore default to 0 mstore(add(data, 32), 0) // Set default values mstore(add(order, 832), 0) // order.P2P mstore(add(order, 864), 0) // order.hash mstore(add(order, 896), 0) // order.brokerInterceptor mstore(add(order, 928), 0) // order.filledAmountS mstore(add(order, 960), 0) // order.initialFilledAmountS mstore(add(order, 992), 1) // order.valid // Advance to the next order tablesPtr := add(tablesPtr, 60) } } } function assembleRings( uint data, uint numRings, Data.Order[] memory orders ) internal pure returns (Data.Ring[] memory rings) { uint ringsArrayDataSize = (1 + numRings) * 32; uint ringStructSize = 5 * 32; uint participationStructSize = 10 * 32; assembly { // Allocate memory for all rings rings := mload(0x40) mstore(add(rings, 0), numRings) // rings.length // Reserve the memory for the rings array mstore(0x40, add(rings, add(ringsArrayDataSize, mul(ringStructSize, numRings)))) for { let r := 0 } lt(r, numRings) { r := add(r, 1) } { let ring := add(rings, add(ringsArrayDataSize, mul(ringStructSize, r))) // Store the memory location of this ring in the rings array mstore(add(rings, mul(add(r, 1), 32)), ring) // Get the ring size let ringSize := and(mload(data), 0xFF) data := add(data, 1) // require(ringsSize <= 8) if gt(ringSize, 8) { revert(0, 0) } // Allocate memory for all participations let participations := mload(0x40) mstore(add(participations, 0), ringSize) // participations.length // Memory for participations length + ringSize participation pointers let participationsData := add(participations, mul(add(1, ringSize), 32)) // Reserve the memory for the participations mstore(0x40, add(participationsData, mul(participationStructSize, ringSize))) // Initialize ring properties mstore(add(ring, 0), ringSize) // ring.size mstore(add(ring, 32), participations) // ring.participations mstore(add(ring, 64), 0) // ring.hash mstore(add(ring, 96), 0) // ring.minerFeesToOrdersPercentage mstore(add(ring, 128), 1) // ring.valid for { let i := 0 } lt(i, ringSize) { i := add(i, 1) } { let participation := add(participationsData, mul(participationStructSize, i)) // Store the memory location of this participation in the participations array mstore(add(participations, mul(add(i, 1), 32)), participation) // Get the order index let orderIndex := and(mload(data), 0xFF) // require(orderIndex < orders.length) if iszero(lt(orderIndex, mload(orders))) { revert(0, 0) } data := add(data, 1) // participation.order mstore( add(participation, 0), mload(add(orders, mul(add(orderIndex, 1), 32))) ) // Set default values mstore(add(participation, 32), 0) // participation.splitS mstore(add(participation, 64), 0) // participation.feeAmount mstore(add(participation, 96), 0) // participation.feeAmountS mstore(add(participation, 128), 0) // participation.feeAmountB mstore(add(participation, 160), 0) // participation.rebateFee mstore(add(participation, 192), 0) // participation.rebateS mstore(add(participation, 224), 0) // participation.rebateB mstore(add(participation, 256), 0) // participation.fillAmountS mstore(add(participation, 288), 0) // participation.fillAmountB } // Advance to the next ring data := add(data, sub(8, ringSize)) } } } } /// @title An Implementation of IRingSubmitter. /// @author Daniel Wang - <[email protected]>, /// @author Kongliang Zhong - <[email protected]> /// @author Brechtpd - <[email protected]> /// Recognized contributing developers from the community: /// https://github.com/rainydio /// https://github.com/BenjaminPrice /// https://github.com/jonasshen /// https://github.com/Hephyrius contract RingSubmitter is IRingSubmitter, NoDefaultFunc { using MathUint for uint; using BytesUtil for bytes; using OrderHelper for Data.Order; using RingHelper for Data.Ring; using MiningHelper for Data.Mining; address public constant lrcTokenAddress = 0xEF68e7C694F40c8202821eDF525dE3782458639f; address public constant wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address public constant delegateAddress = 0xb258f5C190faDAB30B5fF0D6ab7E32a646A4BaAe; address public constant tradeHistoryAddress = 0xBF5a37670B3DE1E606EC68bE3558c536b2008669; address public constant orderBrokerRegistryAddress = 0x4e1E917F030556788AB3C9d8D0971Ebf0d5439E9; address public constant orderRegistryAddress = 0x6fb707F15Ab3657Dc52776b057B33cB7D95e4E90; address public constant feeHolderAddress = 0x5beaEA36efA78F43a6d61145817FDFf6A9929e60; address public constant orderBookAddress = 0xaC0F8a27012fe8dc5a0bB7f5fc7170934F7e3577; address public constant burnRateTableAddress = 0x20D90aefBA13F044C5d23c48C3b07e2E43a006DB; uint64 public ringIndex = 0; uint public constant MAX_RING_SIZE = 8; struct SubmitRingsParam { uint16[] encodeSpecs; uint16 miningSpec; uint16[] orderSpecs; uint8[][] ringSpecs; address[] addressList; uint[] uintList; bytes[] bytesList; } /* constructor( */ /* address _lrcTokenAddress, */ /* address _wethTokenAddress, */ /* address _delegateAddress, */ /* address _tradeHistoryAddress, */ /* address _orderBrokerRegistryAddress, */ /* address _orderRegistryAddress, */ /* address _feeHolderAddress, */ /* address _orderBookAddress, */ /* address _burnRateTableAddress */ /* ) */ /* public */ /* { */ /* require(_lrcTokenAddress != address(0x0), ZERO_ADDRESS); */ /* require(_wethTokenAddress != address(0x0), ZERO_ADDRESS); */ /* require(_delegateAddress != address(0x0), ZERO_ADDRESS); */ /* require(_tradeHistoryAddress != address(0x0), ZERO_ADDRESS); */ /* require(_orderBrokerRegistryAddress != address(0x0), ZERO_ADDRESS); */ /* require(_orderRegistryAddress != address(0x0), ZERO_ADDRESS); */ /* require(_feeHolderAddress != address(0x0), ZERO_ADDRESS); */ /* require(_orderBookAddress != address(0x0), ZERO_ADDRESS); */ /* require(_burnRateTableAddress != address(0x0), ZERO_ADDRESS); */ /* lrcTokenAddress = _lrcTokenAddress; */ /* wethTokenAddress = _wethTokenAddress; */ /* delegateAddress = _delegateAddress; */ /* tradeHistoryAddress = _tradeHistoryAddress; */ /* orderBrokerRegistryAddress = _orderBrokerRegistryAddress; */ /* orderRegistryAddress = _orderRegistryAddress; */ /* feeHolderAddress = _feeHolderAddress; */ /* orderBookAddress = _orderBookAddress; */ /* burnRateTableAddress = _burnRateTableAddress; */ /* } */ function submitRings( bytes calldata data ) external { uint i; bytes32[] memory tokenBurnRates; Data.Context memory ctx = Data.Context( lrcTokenAddress, ITradeDelegate(delegateAddress), ITradeHistory(tradeHistoryAddress), IBrokerRegistry(orderBrokerRegistryAddress), IOrderRegistry(orderRegistryAddress), IFeeHolder(feeHolderAddress), IOrderBook(orderBookAddress), IBurnRateTable(burnRateTableAddress), ringIndex, FEE_PERCENTAGE_BASE, tokenBurnRates, 0, 0, 0, 0 ); // Check if the highest bit of ringIndex is '1' require((ctx.ringIndex >> 63) == 0, REENTRY); // Set the highest bit of ringIndex to '1' (IN STORAGE!) ringIndex = ctx.ringIndex | (1 << 63); ( Data.Mining memory mining, Data.Order[] memory orders, Data.Ring[] memory rings ) = ExchangeDeserializer.deserialize(lrcTokenAddress, data); // Allocate memory that is used to batch things for all rings setupLists(ctx, orders, rings); for (i = 0; i < orders.length; i++) { orders[i].updateHash(); orders[i].updateBrokerAndInterceptor(ctx); } batchGetFilledAndCheckCancelled(ctx, orders); for (i = 0; i < orders.length; i++) { orders[i].check(ctx); // An order can only be sent once for (uint j = i + 1; j < orders.length; j++) { require(orders[i].hash != orders[j].hash, INVALID_VALUE); } } for (i = 0; i < rings.length; i++) { rings[i].updateHash(); } mining.updateHash(rings); mining.updateMinerAndInterceptor(); require(mining.checkMinerSignature(), INVALID_SIG); for (i = 0; i < orders.length; i++) { // We don't need to verify the dual author signature again if it uses the same // dual author address as the previous order (the miner can optimize the order of the orders // so this happens as much as possible). We don't need to check if the signature is the same // because the same mining hash is signed for all orders. if(i > 0 && orders[i].dualAuthAddr == orders[i - 1].dualAuthAddr) { continue; } orders[i].checkDualAuthSignature(mining.hash); } for (i = 0; i < rings.length; i++) { Data.Ring memory ring = rings[i]; ring.checkOrdersValid(); ring.checkForSubRings(); ring.calculateFillAmountAndFee(ctx); if (ring.valid) { ring.adjustOrderStates(); } } // Check if the allOrNone orders are completely filled over all rings // This can invalidate rings checkRings(orders, rings); for (i = 0; i < rings.length; i++) { Data.Ring memory ring = rings[i]; if (ring.valid) { // Only settle rings we have checked to be valid ring.doPayments(ctx, mining); emitRingMinedEvent( ring, ctx.ringIndex++, mining.feeRecipient ); } else { emit InvalidRing(ring.hash); } } // Do all token transfers for all rings batchTransferTokens(ctx); // Do all fee payments for all rings batchPayFees(ctx); // Update all order stats updateOrdersStats(ctx, orders); // Update ringIndex while setting the highest bit of ringIndex back to '0' ringIndex = ctx.ringIndex; } function checkRings( Data.Order[] memory orders, Data.Ring[] memory rings ) internal pure { // Check if allOrNone orders are completely filled // When a ring is turned invalid because of an allOrNone order we have to // recheck the other rings again because they may contain other allOrNone orders // that may not be completely filled anymore. bool reevaluateRings = true; while (reevaluateRings) { reevaluateRings = false; for (uint i = 0; i < orders.length; i++) { if (orders[i].valid) { orders[i].validateAllOrNone(); // Check if the order valid status has changed reevaluateRings = reevaluateRings || !orders[i].valid; } } if (reevaluateRings) { for (uint i = 0; i < rings.length; i++) { Data.Ring memory ring = rings[i]; if (ring.valid) { ring.checkOrdersValid(); if (!ring.valid) { // If the ring was valid before the completely filled check we have to revert the filled amountS // of the orders in the ring. This is a bit awkward so maybe there's a better solution. ring.revertOrderStats(); } } } } } } function emitRingMinedEvent( Data.Ring memory ring, uint _ringIndex, address feeRecipient ) internal { bytes32 ringHash = ring.hash; // keccak256("RingMined(uint256,bytes32,address,bytes)") bytes32 ringMinedSignature = 0xb2ef4bc5209dff0c46d5dfddb2b68a23bd4820e8f33107fde76ed15ba90695c9; uint fillsSize = ring.size * 8 * 32; uint data; uint ptr; assembly { data := mload(0x40) ptr := data mstore(ptr, _ringIndex) // ring index data mstore(add(ptr, 32), 0x40) // offset to fills data mstore(add(ptr, 64), fillsSize) // fills length ptr := add(ptr, 96) } ptr = ring.generateFills(ptr); assembly { log3( data, // data start sub(ptr, data), // data length ringMinedSignature, // Topic 0: RingMined signature ringHash, // Topic 1: ring hash feeRecipient // Topic 2: feeRecipient ) } } function setupLists( Data.Context memory ctx, Data.Order[] memory orders, Data.Ring[] memory rings ) internal pure { setupTokenBurnRateList(ctx, orders); setupFeePaymentList(ctx, rings); setupTokenTransferList(ctx, rings); } function setupTokenBurnRateList( Data.Context memory ctx, Data.Order[] memory orders ) internal pure { // Allocate enough memory to store burn rates for all tokens even // if every token is unique (max 2 unique tokens / order) uint maxNumTokenBurnRates = orders.length * 2; bytes32[] memory tokenBurnRates; assembly { tokenBurnRates := mload(0x40) mstore(tokenBurnRates, 0) // tokenBurnRates.length mstore(0x40, add( tokenBurnRates, add(32, mul(maxNumTokenBurnRates, 64)) )) } ctx.tokenBurnRates = tokenBurnRates; } function setupFeePaymentList( Data.Context memory ctx, Data.Ring[] memory rings ) internal pure { uint totalMaxSizeFeePayments = 0; for (uint i = 0; i < rings.length; i++) { // Up to (ringSize + 3) * 3 payments per order (because of fee sharing by miner) // (3 x 32 bytes for every fee payment) uint ringSize = rings[i].size; uint maxSize = (ringSize + 3) * 3 * ringSize * 3; totalMaxSizeFeePayments += maxSize; } // Store the data directly in the call data format as expected by batchAddFeeBalances: // - 0x00: batchAddFeeBalances selector (4 bytes) // - 0x04: parameter offset (batchAddFeeBalances has a single function parameter) (32 bytes) // - 0x24: length of the array passed into the function (32 bytes) // - 0x44: the array data (32 bytes x length) bytes4 batchAddFeeBalancesSelector = ctx.feeHolder.batchAddFeeBalances.selector; uint ptr; assembly { let data := mload(0x40) mstore(data, batchAddFeeBalancesSelector) mstore(add(data, 4), 32) ptr := add(data, 68) mstore(0x40, add(ptr, mul(totalMaxSizeFeePayments, 32))) } ctx.feeData = ptr; ctx.feePtr = ptr; } function setupTokenTransferList( Data.Context memory ctx, Data.Ring[] memory rings ) internal pure { uint totalMaxSizeTransfers = 0; for (uint i = 0; i < rings.length; i++) { // Up to 4 transfers per order // (4 x 32 bytes for every transfer) uint maxSize = 4 * rings[i].size * 4; totalMaxSizeTransfers += maxSize; } // Store the data directly in the call data format as expected by batchTransfer: // - 0x00: batchTransfer selector (4 bytes) // - 0x04: parameter offset (batchTransfer has a single function parameter) (32 bytes) // - 0x24: length of the array passed into the function (32 bytes) // - 0x44: the array data (32 bytes x length) bytes4 batchTransferSelector = ctx.delegate.batchTransfer.selector; uint ptr; assembly { let data := mload(0x40) mstore(data, batchTransferSelector) mstore(add(data, 4), 32) ptr := add(data, 68) mstore(0x40, add(ptr, mul(totalMaxSizeTransfers, 32))) } ctx.transferData = ptr; ctx.transferPtr = ptr; } function updateOrdersStats( Data.Context memory ctx, Data.Order[] memory orders ) internal { // Store the data directly in the call data format as expected by batchUpdateFilled: // - 0x00: batchUpdateFilled selector (4 bytes) // - 0x04: parameter offset (batchUpdateFilled has a single function parameter) (32 bytes) // - 0x24: length of the array passed into the function (32 bytes) // - 0x44: the array data (32 bytes x length) // For every (valid) order we store 2 words: // - order.hash // - order.filledAmountS after all rings bytes4 batchUpdateFilledSelector = ctx.tradeHistory.batchUpdateFilled.selector; address _tradeHistoryAddress = address(ctx.tradeHistory); assembly { let data := mload(0x40) mstore(data, batchUpdateFilledSelector) mstore(add(data, 4), 32) let ptr := add(data, 68) let arrayLength := 0 for { let i := 0 } lt(i, mload(orders)) { i := add(i, 1) } { let order := mload(add(orders, mul(add(i, 1), 32))) let filledAmount := mload(add(order, 928)) // order.filledAmountS let initialFilledAmount := mload(add(order, 960)) // order.initialFilledAmountS let filledAmountChanged := iszero(eq(filledAmount, initialFilledAmount)) // if (order.valid && filledAmountChanged) if and(gt(mload(add(order, 992)), 0), filledAmountChanged) { // order.valid mstore(add(ptr, 0), mload(add(order, 864))) // order.hash mstore(add(ptr, 32), filledAmount) ptr := add(ptr, 64) arrayLength := add(arrayLength, 2) } } // Only do the external call if the list is not empty if gt(arrayLength, 0) { mstore(add(data, 36), arrayLength) // filledInfo.length let success := call( gas, // forward all gas _tradeHistoryAddress, // external address 0, // wei data, // input start sub(ptr, data), // input length data, // output start 0 // output length ) if eq(success, 0) { // Propagate the revert message returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } } } function batchGetFilledAndCheckCancelled( Data.Context memory ctx, Data.Order[] memory orders ) internal { // Store the data in the call data format as expected by batchGetFilledAndCheckCancelled: // - 0x00: batchGetFilledAndCheckCancelled selector (4 bytes) // - 0x04: parameter offset (batchGetFilledAndCheckCancelled has a single function parameter) (32 bytes) // - 0x24: length of the array passed into the function (32 bytes) // - 0x44: the array data (32 bytes x length) // For every order we store 5 words: // - order.broker // - order.owner // - order.hash // - order.validSince // - The trading pair of the order: order.tokenS ^ order.tokenB bytes4 batchGetFilledAndCheckCancelledSelector = ctx.tradeHistory.batchGetFilledAndCheckCancelled.selector; address _tradeHistoryAddress = address(ctx.tradeHistory); assembly { let data := mload(0x40) mstore(data, batchGetFilledAndCheckCancelledSelector) mstore(add(data, 4), 32) mstore(add(data, 36), mul(mload(orders), 5)) // orders.length let ptr := add(data, 68) for { let i := 0 } lt(i, mload(orders)) { i := add(i, 1) } { let order := mload(add(orders, mul(add(i, 1), 32))) // orders[i] mstore(add(ptr, 0), mload(add(order, 320))) // order.broker mstore(add(ptr, 32), mload(add(order, 32))) // order.owner mstore(add(ptr, 64), mload(add(order, 864))) // order.hash mstore(add(ptr, 96), mload(add(order, 192))) // order.validSince // bytes20(order.tokenS) ^ bytes20(order.tokenB) // tradingPair mstore(add(ptr, 128), mul( xor( mload(add(order, 64)), // order.tokenS mload(add(order, 96)) // order.tokenB ), 0x1000000000000000000000000) // shift left 12 bytes (bytes20 is padded on the right) ) ptr := add(ptr, 160) // 5 * 32 } // Return data is stored just like the call data without the signature: // 0x00: Offset to data // 0x20: Array length // 0x40: Array data let returnDataSize := mul(add(2, mload(orders)), 32) let success := call( gas, // forward all gas _tradeHistoryAddress, // external address 0, // wei data, // input start sub(ptr, data), // input length data, // output start returnDataSize // output length ) // Check if the call was successful and the return data is the expected size if or(eq(success, 0), iszero(eq(returndatasize(), returnDataSize))) { if eq(success, 0) { // Propagate the revert message returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } revert(0, 0) } for { let i := 0 } lt(i, mload(orders)) { i := add(i, 1) } { let order := mload(add(orders, mul(add(i, 1), 32))) // orders[i] let fill := mload(add(data, mul(add(i, 2), 32))) // fills[i] mstore(add(order, 928), fill) // order.filledAmountS mstore(add(order, 960), fill) // order.initialFilledAmountS // If fills[i] == ~uint(0) the order was cancelled // order.valid = order.valid && (order.filledAmountS != ~uint(0)) mstore(add(order, 992), // order.valid and( gt(mload(add(order, 992)), 0), // order.valid iszero(eq(fill, not(0))) // fill != ~uint(0 ) ) } } } function batchTransferTokens( Data.Context memory ctx ) internal { // Check if there are any transfers if (ctx.transferData == ctx.transferPtr) { return; } // We stored the token transfers in the call data as expected by batchTransfer. // The only thing we still need to do is update the final length of the array and call // the function on the TradeDelegate contract with the generated data. address _tradeDelegateAddress = address(ctx.delegate); uint arrayLength = (ctx.transferPtr - ctx.transferData) / 32; uint data = ctx.transferData - 68; uint ptr = ctx.transferPtr; assembly { mstore(add(data, 36), arrayLength) // batch.length let success := call( gas, // forward all gas _tradeDelegateAddress, // external address 0, // wei data, // input start sub(ptr, data), // input length data, // output start 0 // output length ) if eq(success, 0) { // Propagate the revert message returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } } function batchPayFees( Data.Context memory ctx ) internal { // Check if there are any fee payments if (ctx.feeData == ctx.feePtr) { return; } // We stored the fee payments in the call data as expected by batchAddFeeBalances. // The only thing we still need to do is update the final length of the array and call // the function on the FeeHolder contract with the generated data. address _feeHolderAddress = address(ctx.feeHolder); uint arrayLength = (ctx.feePtr - ctx.feeData) / 32; uint data = ctx.feeData - 68; uint ptr = ctx.feePtr; assembly { mstore(add(data, 36), arrayLength) // batch.length let success := call( gas, // forward all gas _feeHolderAddress, // external address 0, // wei data, // input start sub(ptr, data), // input length data, // output start 0 // output length ) if eq(success, 0) { // Propagate the revert message returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"constant":true,"inputs":[],"name":"orderBrokerRegistryAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"wethTokenAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tradeHistoryAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ringIndex","outputs":[{"name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lrcTokenAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"delegateAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"orderBookAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"orderRegistryAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_RING_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"data","type":"bytes"}],"name":"submitRings","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"FEE_PERCENTAGE_BASE","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"burnRateTableAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"feeHolderAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_ringIndex","type":"uint256"},{"indexed":true,"name":"_ringHash","type":"bytes32"},{"indexed":true,"name":"_feeRecipient","type":"address"},{"indexed":false,"name":"_fills","type":"bytes"}],"name":"RingMined","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_ringHash","type":"bytes32"}],"name":"InvalidRing","type":"event"}]Contract Creation Code
60806040526000805467ffffffffffffffff1916905534801561002157600080fd5b50614322806100316000396000f3fe6080604052600436106100c65760003560e060020a900480639409d87011610083578063cbaef59b1161005d578063cbaef59b14610287578063cd117ae614610306578063f00fd4bb14610332578063fece782614610347576100c6565b80639409d870146102365780639c6a28371461024b578063a37641ff14610260576100c6565b80630af46ab01461017f5780630f9dad71146101b057806312f510f2146101c557806341ffbc1f146101da5780634c0a65321461020c5780636d96a2aa14610221575b604080518082018252600b81527f554e535550504f525445440000000000000000000000000000000000000000006020808301918252925160e560020a62461bcd0281526004810193845282516024820152825192939283926044909201919080838360005b8381101561014457818101518382015260200161012c565b50505050905090810190601f1680156101715780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b34801561018b57600080fd5b5061019461035c565b60408051600160a060020a039092168252519081900360200190f35b3480156101bc57600080fd5b50610194610374565b3480156101d157600080fd5b5061019461038c565b3480156101e657600080fd5b506101ef6103a4565b6040805167ffffffffffffffff9092168252519081900360200190f35b34801561021857600080fd5b506101946103b4565b34801561022d57600080fd5b506101946103cc565b34801561024257600080fd5b506101946103e4565b34801561025757600080fd5b506101946103fc565b34801561026c57600080fd5b50610275610414565b60408051918252519081900360200190f35b34801561029357600080fd5b50610304600480360360208110156102aa57600080fd5b8101906020810181356401000000008111156102c557600080fd5b8201836020820111156102d757600080fd5b803590602001918460018302840111640100000000831117156102f957600080fd5b509092509050610419565b005b34801561031257600080fd5b5061031b610b0b565b6040805161ffff9092168252519081900360200190f35b34801561033e57600080fd5b50610194610b11565b34801561035357600080fd5b50610194610b29565b734e1e917f030556788ab3c9d8d0971ebf0d5439e981565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b73bf5a37670b3de1e606ec68be3558c536b200866981565b60005467ffffffffffffffff1681565b73ef68e7c694f40c8202821edf525de3782458639f81565b73b258f5c190fadab30b5ff0d6ab7e32a646a4baae81565b73ac0f8a27012fe8dc5a0bb7f5fc7170934f7e357781565b736fb707f15ab3657dc52776b057b33cb7d95e4e9081565b600881565b60006060610425614158565b50604080516101e08101825273ef68e7c694f40c8202821edf525de3782458639f815273b258f5c190fadab30b5ff0d6ab7e32a646a4baae60208083019190915273bf5a37670b3de1e606ec68be3558c536b200866982840152734e1e917f030556788ab3c9d8d0971ebf0d5439e96060830152736fb707f15ab3657dc52776b057b33cb7d95e4e906080830152735beaea36efa78f43a6d61145817fdff6a9929e6060a083015273ac0f8a27012fe8dc5a0bb7f5fc7170934f7e357760c08301527320d90aefba13f044c5d23c48c3b07e2e43a006db60e08301526000805467ffffffffffffffff90811661010085018190526103e86101208601526101408501879052610160850183905261018085018390526101a085018390526101c08501929092528451808601909552600785527f5245454e5452590000000000000000000000000000000000000000000000000092850192909252919291678000000000000000900416156105de5760405160e560020a62461bcd0281526004018080602001828103825283818151815260200191508051906020019080838360008381101561014457818101518382015260200161012c565b50806101000151678000000000000000176000806101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555061061e6141d3565b60608061067573ef68e7c694f40c8202821edf525de3782458639f89898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610b4192505050565b925092509250610686848383610dfa565b600095505b81518610156106e7576106b482878151811015156106a557fe5b90602001906020020151610e1d565b6106dc8483888151811015156106c657fe5b602090810290910101519063ffffffff610fca16565b60019095019461068b565b6106f184836110bf565b600095505b81518610156107fc5761072784838881518110151561071157fe5b602090810290910101519063ffffffff61120516565b600186015b82518110156107f057828181518110151561074357fe5b906020019060200201516103600151838881518110151561076057fe5b90602001906020020151610360015114156040805190810160405280600d81526020016000805160206142d78339815191528152509015156107e75760405160e560020a62461bcd0281526004018080602001828103825283818151815260200191508051906020019080838360008381101561014457818101518382015260200161012c565b5060010161072c565b506001909501946106f6565b600095505b80518610156108355761082a818781518110151561081b57fe5b9060200190602002015161123d565b600190950194610801565b610845838263ffffffff61129d16565b61084e83611301565b61085783611329565b60408051808201909152600b81527f494e56414c49445f53494700000000000000000000000000000000000000000060208201529015156108dd5760405160e560020a62461bcd0281526004018080602001828103825283818151815260200191508051906020019080838360008381101561014457818101518382015260200161012c565b50600095505b8151861015610989576000861180156109485750816001870381518110151561090857fe5b906020019060200201516101200151600160a060020a0316828781518110151561092e57fe5b906020019060200201516101200151600160a060020a0316145b156109525761097e565b61097e8360600151838881518110151561096857fe5b602090810290910101519063ffffffff61136e16565b6001909501946108e3565b600095505b80518610156109fa5761099f614202565b81878151811015156109ad57fe5b9060200190602002015190506109c2816113a8565b6109cb81611498565b6109db818663ffffffff61153916565b8060800151156109ee576109ee816118c9565b5060019095019461098e565b610a048282611901565b600095505b8051861015610ac057610a1a614202565b8187815181101515610a2857fe5b906020019060200201519050806080015115610a7c57610a4f81868663ffffffff6119f716565b6101008501805167ffffffffffffffff6001820181169092528551610a779284921690611a11565b610ab4565b604080820151815190815290517f977ca9d66ddf6e18cac50dbf89ee6dcce72d4635c60a13314945868798f73cdc9181900360200190a15b50600190950194610a09565b610ac984611a7d565b610ad284611af4565b610adc8483611b51565b50505061010001516000805467ffffffffffffffff191667ffffffffffffffff90921691909117905550505050565b6103e881565b7320d90aefba13f044c5d23c48c3b07e2e43a006db81565b735beaea36efa78f43a6d61145817fdff6a9929e6081565b610b496141d3565b606080610b54614237565b610b6585600063ffffffff611c2616565b61ffff168152610b7c85600263ffffffff611c2616565b61ffff166020820152610b9685600463ffffffff611c2616565b61ffff166040820152610bb085600663ffffffff611c2616565b61ffff166060820152805115610c10576040805160e560020a62461bcd02815260206004820181905260248201527f556e737570706f727465642073657269616c697a6174696f6e20666f726d6174604482015290519081900360640190fd5b6020810151600010610c6c576040805160e560020a62461bcd02815260206004820152601860248201527f496e76616c6964206e756d626572206f66206f72646572730000000000000000604482015290519081900360640190fd5b6040810151600010610cc8576040805160e560020a62461bcd02815260206004820152601760248201527f496e76616c6964206e756d626572206f662072696e6773000000000000000000604482015290519081900360640190fd5b6060810151600010610d24576040805160e560020a62461bcd02815260206004820152601c60248201527f496e76616c6964206e756d626572206f66207370656e6461626c657300000000604482015290519081900360640190fd5b60208101516040820151865187926008840192600e80860193603c909302860190810192600990920201602e81019190869003604e011115610db0576040805160e560020a62461bcd02815260206004820152601260248201527f496e76616c696420696e70757420646174610000000000000000000000000000604482015290519081900360640190fd5b610dbd8185600201611c41565b9850610dd78184600201886020015189606001518f611cac565b9750610deb8260010187604001518a6120f8565b96505050505050509250925092565b610e048383612239565b610e0e8382612257565b610e1883826122e8565b505050565b60007f40b942178d2a51f1f61934268590778feb8114db632db7d88537c98d2b05c5f2600102905060007faea25658c273c666156bd427f83a666135fcde6887a6c25fc1cd1562bc4f3f34600102905060006104a0840151805160208201206040518560008201526080870151602082015260a08701516040820152610280870151606082015260c087015160808201526101e087015160a0820152602087015160c0820152604087015160e082015260608701516101008201526101208701516101208201526101408701516101408201526101a08701516101608201526101c08701516101808201526103008701516101a08201526102608701516101c08201526103208701516101e08201526102c08701516102008201526102e08701516102208201526102408701516102408201526104008701516102608201526104208701516102808201526104408701516102a08201526104608701516102c08201526104808701516102e082015281610300820152610320812061190160008301528560208301528060408301526042601e830120945050505050808461036001818152505050505050565b610140820151600160a060020a03161515610ff8576020820151600160a060020a03166101408301526110bb565b60608101516020830151610140840151604080517f2dfc6eed000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015280516000949390931692632dfc6eed926044808201939291829003018186803b15801561107357600080fd5b505afa158015611087573d6000803e3d6000fd5b505050506040513d604081101561109d57600080fd5b50516103e084015190915080156110b15750805b15156103e0840152505b5050565b60408083015190517faec782d400000000000000000000000000000000000000000000000000000000808252602060048301528351600502602483015291906044810160005b85518110156111735760206001820102860151610140810151600084015260208101516020840152610360810151604084015260c081015160608401526c01000000000000000000000000606082015160408301511802608084015260a08301925050600181019050611105565b5060208551600201028083848403856000885af180153d83141517156111a9578015156111a4573d6000803e3d6000fd5b600080fd5b60005b87518110156111fa576020600182010288015160206002830102860151806103a0830152806103c083015260001981141560006103e084015111166103e083015250506001810190506111ac565b505050505050505050565b6103a0820151151561122a5761121b8282612373565b6112258282612518565b611234565b611234828261268b565b6110bb8261272e565b80516040516020830151600091908190835b8581101561128957602081810283010151516102a08101516103609091015161ffff9091166002850152835260229092019160010161124f565b505081900390206040909301929092525050565b60006020820151604081015160015b84518110156112d15760010160208102850151604081015190935091909118906112ac565b50604051603481019190915260208501516014820152845181526048600c90910120606090940193909352505050565b6020810151600160a060020a03161515611326578051600160a060020a031660208201525b50565b60008160400151516000141561134f57506020810151600160a060020a03163314611369565b61136682602001518360600151846040015161275a565b90505b919050565b61022082015151156110bb57816103e00151801561139c575061139c8261012001518284610220015161275a565b15156103e08301525050565b806080015180156113c75750805160011080156113c757508051600810155b1515608082015260005b81518110156110bb578151600090600019838201018115156113ef57fe5b069050826080015180156114205750602083015180518390811061140f57fe5b60209081029091010151516103e001515b15801560808501819052906114885750602083015180518290811061144157fe5b906020019060200201516000015160600151600160a060020a031683602001518381518110151561146e57fe5b602090810290910101515160400151600160a060020a0316145b15156080840152506001016113d1565b60005b8151600019018110156110bb5760008260200151828151811015156114bc57fe5b6020908102909101015151604001519050600182015b835181101561152f5783608001518015611520575060208401518051829081106114f857fe5b906020019060200201516000015160400151600160a060020a031682600160a060020a031614155b151560808501526001016114d2565b505060010161149b565b81608001511515611549576110bb565b600080805b845183101561158e576115838486602001518581518110151561156d57fe5b602090810290910101519063ffffffff612b1516565b60019092019161154e565b845160001901915060005b60008312611607578551600019848201018115156115b357fe5b0691506115f9858760200151858151811015156115cc57fe5b906020019060200201518860200151858151811015156115e857fe5b906020019060200201518685612d5d565b600019909301929050611599565b85516000190192505b80831261164f5785516000198482010181151561162957fe5b069150611642858760200151858151811015156115cc57fe5b5060001990920191611610565b600093505b85518410156116ed578560800151801561168d575061168d86602001518581518110151561167e57fe5b90602001906020020151612e04565b15156080870152602086015180516116e29190869081106116aa57fe5b9060200190602002015161010001518760200151868151811015156116cb57fe5b60209081029091010151519063ffffffff612e5116565b600190930192611654565b600093505b85518410156118625785516000198582010181151561170d57fe5b069150856080015180156117a557506117a586602001518581518110151561173157fe5b602090810290910181015151604001519088015180518790811061175157fe5b6020908102909101810151518101519089015180518690811061177057fe5b6020908102909101810151516103000151908a015180518990811061179157fe5b906020019060200201516101000151612e63565b15156080870152602086015180516000916117f891859081106117c457fe5b90602001906020020151878960200151888151811015156117e157fe5b60209081029091010151919063ffffffff61306916565b905080151561180e575060006080870152611862565b600087602001518681518110151561182257fe5b90602001906020020151600001516102a00151905060008160010b121561185557606088018051600083900360010b0190525b50506001909301926116f2565b8560800151801561187c5750846101200151866060015111155b15156080870152600093505b85518410156118c1576118b68660200151858151811015156118a657fe5b602090810290910101515161323f565b600190930192611888565b505050505050565b60005b81518110156110bb576118f98260200151828151811015156118ea57fe5b90602001906020020151613259565b6001016118cc565b60015b8015610e1857506000805b835181101561198b57838181518110151561192657fe5b906020019060200201516103e00151156119835761195a848281518110151561194b57fe5b9060200190602002015161333a565b81806119805750838181518110151561196f57fe5b906020019060200201516103e00151155b91505b60010161190f565b5080156119f25760005b82518110156119f0576119a6614202565b83828151811015156119b457fe5b9060200190602002015190508060800151156119e7576119d3816113a8565b806080015115156119e7576119e78161336a565b50600101611995565b505b611904565b611a028383836133a2565b610e1883838360000151613405565b604080840151845182518581526020810184905261010090910292810183905290917fb2ef4bc5209dff0c46d5dfddb2b68a23bd4820e8f33107fde76ed15ba90695c99160608101611a69888263ffffffff61347416565b905085858584840385a35050505050505050565b806101c00151816101a001511415611a9457611326565b60008160200151905060006020836101a00151846101c0015103811515611ab757fe5b04905060006044846101a001510390506000846101c001519050826024830152600082838303846000885af18015156118c1573d6000803e3d6000fd5b8061018001518161016001511415611b0b57611326565b60a08101516101608201516101808301516020828203908104601f1984018190529260431901919060009083906044018183885af18015156118c1573d6000803e3d6000fd5b60408083015190517f843ff0e500000000000000000000000000000000000000000000000000000000808252602060048301529190604481016000805b8651811015611bee57602060018201028701516103a08101516103c0820151808214158060006103e0860151111615611bdf5761036084015160008801528260208801526040870196506002860195505b50505050600181019050611b8e565b506000811115611c1d57806024840152600083848403856000885af1801515611c1b573d6000803e3d6000fd5b505b50505050505050565b6000611c348383600261351f565b61ffff1690505b92915050565b611c496141d3565b604080516000808252602080830184523260148881019182528751600461ffff91821681028b018301518952928590526002890151811683028a019091015187840152888301948552968101519096169095029095019093019082015291905290565b60408051600081526020808201858152858202830184019093526060926104c0916001880102908490878015611cfc57816020015b611ce9614260565b815260200190600190039081611ce15790505b509050600060405195508860008701528884028301860160405260005b898110156120e95780850284018701806020836001010289015261ffff60008d0151169250826000820152600461ffff60028e015116029250600160a060020a038360148f010151166020820152600461ffff60048e015116029250600160a060020a038360148f010151166040820152600461ffff60068e015116029250600160a060020a038360148f010151166060820152600461ffff60088e0151160292508260208e0101516080820152600461ffff600a8e0151160292508260208e01015160a0820152600461ffff600c8e01511602925063ffffffff8360048f0101511660c082015261ffff600e8d0151169250898310830292506020600184010284015160e082015261ffff60108d01511692508983108302925060206001840102840151610100820152600461ffff60128e015116029250600160a060020a038360148f01015116610120820152600461ffff60148e015116029250600160a060020a038360148f01015116610140820152600461ffff60168e015116029250600160a060020a038360148f010151166101a0820152600461ffff60188e015116029250600160a060020a038360148f010151166101c0820152600461ffff601a8e01511602925063ffffffff8360048f010151166101e08201528660208e0152600461ffff601c8e015116029250602083018d01610200820152600461ffff601e8e015116029250602083018d01610220820152600060208e015261ffff60208d0151169250600083116102408201528860148e0152600461ffff60228e015116029250600160a060020a038360148f01015116610260820152600060148e0152600461ffff60248e0151160292508260208e01015161028082015261ffff60268d0151169250826102a082015261ffff60288d0151169250826102c082015261ffff602a8d0151169250826102e0820152602081015160148e0152600461ffff602c8e015116029250600160a060020a038360148f01015116610300820152600060148e015261ffff602e8d01511692508261032082015261ffff60308d01511692508261040082015261ffff60328d01511692508261042082015261ffff60348d015116925082610440820152600461ffff60368e0151160292508260208e010151610460820152600461ffff60388e0151160292508260208e0101516104808201528660208e0152600461ffff603a8e015116029250602083018d016104a0820152600060208e015260006103408201526000610360820152600061038082015260006103a082015260006103c082015260016103e0820152603c8c019b5050600181019050611d19565b50505050505095945050505050565b604080518381526001840160200260a080860282018301909352909161014060005b8681101561222e5780830284018501806020600184010287015260ff89511660018a019950600881111561214d57600080fd5b604051816000820152602082600101028101828602810160405282600085015281602085015260006040850152600060608501526001608085015260005b8381101561221a578087028201806020600184010285015260ff8e51168c51811015156121b757600080fd5b60018f019e50602060018201028d0151600083015260006020830152600060408301526000606083015260006080830152600060a0830152600060c0830152600060e083015260006101008301526000610120830152505060018101905061218b565b50505060080398909801975060010161211a565b505050509392505050565b51604080516000815260809092028201602001905261014090910152565b6000805b8251811015612296576000838281518110151561227457fe5b602090810291909101015151600381010260090292909201915060010161225b565b50604080517f4a874dd900000000000000000000000000000000000000000000000000000000815260206004820181905260449302810183019091520161016083018190526101809092019190915250565b6000805b8251811015612321576000838281518110151561230557fe5b60209081029091010151516010029290920191506001016122ec565b50604080517ffa369e660000000000000000000000000000000000000000000000000000000081526020600482018190526044930281018301909152016101a083018190526101c09092019190915250565b81511580801561238f57506020830151600160a060020a031615155b90508080156123aa57506040830151600160a060020a031615155b90508080156123c557506060830151600160a060020a031615155b90508080156123d75750608083015115155b90508080156123e9575060a083015115155b90508080156124055750610260830151600160a060020a031615155b90508080156124215750816101200151836102c0015161ffff16105b905080801561243d5750816101200151836102e0015161ffff16105b90508080156124565750606483610320015161ffff1611155b905080801561248357506000836104000151600081111561247357fe5b1480156124835750610460830151155b90508080156124b05750600083610420015160008111156124a057fe5b1480156124b05750610480830151155b90508080156124cf5750600083610440015160008111156124cd57fe5b145b90508080156124e257506104a083015151155b90508080156124f55750428360c0015111155b9050826103e0015180156125065750805b15156103e0840152610e18838361268b565b6102008201515115156126645760008160800151600160a060020a0316631c8566968461014001518561036001516040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a031681526020018281526020019250505060206040518083038186803b15801561259657600080fd5b505afa1580156125aa573d6000803e3d6000fd5b505050506040513d60208110156125c057600080fd5b5051905080151561265e57826103e00151801561265557508160c00151600160a060020a0316634778a5be8461036001516040518263ffffffff1660e060020a0281526004018082815260200191505060206040518083038186803b15801561262857600080fd5b505afa15801561263c573d6000803e3d6000fd5b505050506040513d602081101561265257600080fd5b50515b15156103e08401525b506110bb565b816103e00151801561139c575061139c82610140015183610360015184610200015161275a565b60016101e083015115806126a3575042836101e00151115b90508080156126c2575081610120015160010b836102a0015160010b13155b90508080156126e4575081610120015160000360010b836102a0015160010b12155b610120840151909150600160a060020a0316156127115780801561270e5750600083610220015151115b90505b826103e0015180156110b1575015156103e0929092019190915250565b6000816102c0015161ffff16118061274f57506000816102e0015161ffff16115b151561034090910152565b805160009060028110156127b8576040805160e560020a62461bcd02815260206004820152601860248201527f696e76616c6964206d756c74696861736820666f726d61740000000000000000604482015290519081900360640190fd5b600183015160028085015190810160ff16831461281f576040805160e560020a62461bcd02815260206004820152601260248201527f626164206d756c7469686173682073697a650000000000000000000000000000604482015290519081900360640190fd5b60ff821615156129ae57600160a060020a0387161515612889576040805160e560020a62461bcd02815260206004820152601660248201527f696e76616c6964207369676e6572206164647265737300000000000000000000604482015290519081900360640190fd5b604160ff8216146128e4576040805160e560020a62461bcd02815260206004820152601b60248201527f62616420457468657265756d206d756c7469686173682073697a650000000000604482015290519081900360640190fd5b604080517f19457468657265756d205369676e6564204d6573736167653a0a3332000000008152601c8101889052603c9020600387015160238801516043890151845160008152602081810180885286905260ff85168288015260608201849052608082018390529551949593949293919260019260a0808401939192601f1981019281900390910190855afa158015612982573d6000803e3d6000fd5b50505060206040510351600160a060020a03168b600160a060020a031614975050505050505050612b0e565b60ff821660011415612b0657600160a060020a0387161515612a1a576040805160e560020a62461bcd02815260206004820152601660248201527f696e76616c6964207369676e6572206164647265737300000000000000000000604482015290519081900360640190fd5b604160ff821614612a75576040805160e560020a62461bcd02815260206004820152601960248201527f62616420454950373132206d756c7469686173682073697a6500000000000000604482015290519081900360640190fd5b600385015160238601516043870151604080516000815260208082018084528c905260ff8616828401526060820185905260808201849052915160019260a0808401939192601f1981019281900390910190855afa158015612adb573d6000803e3d6000fd5b50505060206040510351600160a060020a03168a600160a060020a0316149650505050505050612b0e565b600093505050505b9392505050565b8151600090612b2a908363ffffffff61358816565b83516103a0810151608090910151919250600091612b4d9163ffffffff6135a616565b9050808210612b5c5780612b5e565b815b610100850152835161034001511515612d22578351606081015161026090910151600160a060020a039081169116148015612bb157508351610300810151602090910151600160a060020a039081169116145b8015612bc95750835160a08101516102809091015111155b1515612d22578351608081015161010086015161028090920151600092612bf6919063ffffffff6135f416565b811515612bff57fe5b0490506000811115612d20578451600090612c20908663ffffffff61365216565b9050856000015160400151600160a060020a031686600001516102600151600160a060020a0316148015612c5a5750838287610100015101115b15612ce157808414612c6857fe5b8551610280810151608090910151600091612c89919063ffffffff61367216565b8751608001519091508190612ca590879063ffffffff6135f416565b811515612cae57fe5b04610100880152865161028001518190612ccf90879063ffffffff6135f416565b811515612cd857fe5b04925050612d1e565b80821115612d1e5785516102808101516080909101519192508291612d0d90839063ffffffff6135f416565b811515612d1657fe5b046101008701525b505b505b8351608081015160a090910151610100860151612d449163ffffffff6135f416565b811515612d4d57fe5b0461012090940193909352505050565b61010084015184516102c0015182919061ffff166000811115612db2576000886101200151612d9a838a61010001516135f490919063ffffffff16565b811515612da357fe5b04905080886101000151039250505b818661012001511115612df9576101208601829052855160a0810151608090910151869450612de890849063ffffffff6135f416565b811515612df157fe5b046101008701525b505095945050505050565b6000612e26826101000151836000015160a001518460000151608001516136bb565b159050808015612e3b57506000826101000151115b9050808015611c3b575050610120015160001090565b60e09091015160400180519091019052565b6040805160008082526020820192839052600160a060020a0380871660448401908152908616606484015260848301859052608060a48401908152835160c4850181905292946060947ff1d74b0f00000000000000000000000000000000000000000000000000000000948a948a948a949192909160e48501918083838e5b83811015612efa578181015183820152602001612ee2565b50505050905090810190601f168015612f275780820380516001836020036101000a031916815260200191505b5095505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506000606087600160a060020a0316836040518082805190602001908083835b60208310612fcc5780518252601f199092019160209182019101612fad565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461302e576040519150601f19603f3d011682016040523d82523d6000602084013e613033565b606091505b5091509150818015613046575080516020145b1561305957602001519250613061915050565b600193505050505b949350505050565b82516103400151600090156130f0576000604085015261012082015184516102c001516101008601516130a59161ffff1663ffffffff6135f416565b8115156130ae57fe5b0460608501526101208083015185516102e001519186015190916130dc919061ffff1663ffffffff6135f416565b8115156130e557fe5b0460808501526131f2565b83516080810151610100860151610280909201519091613116919063ffffffff6135f416565b81151561311f57fe5b04604085015260006060808601829052608086019190915284519081015161026090910151600160a060020a03908116911614801561317657508351610300810151602090910151600160a060020a039081169116145b801561318b5750836040015184610120015110155b156131a0576040840180516080860152600090525b6000846040015111156131f25783516000906131c2908463ffffffff61365216565b905080856040015111156131da576000915050612b0e565b604085015185516131f09163ffffffff6136f116565b505b61012083015160608501516101008601510310613237575061012082018051606085015161010086018051829003929092036020870152915190910190526001612b0e565b506000612b0e565b60e081015160006040918201819052610100909201510152565b602080820180516101008401805185516103a00180519190930101909152905190516040840151845160e00151909301519101919061329e908363ffffffff6135a616565b835160e001516020908101919091528351610100015101516132c6908263ffffffff6135a616565b835161010001516020015282516103800151600160a060020a031615610e18578251610160015160200151613301908363ffffffff6135a616565b8351610160015160209081019190915283516101800151015161332a908263ffffffff6135a616565b8351610180015160200152505050565b8061024001511561132657806103e00151801561335f57508060800151816103a00151145b15156103e082015250565b60005b81518110156110bb5761339a82602001518281518110151561338b57fe5b90602001906020020151613704565b60010161336d565b6133aa614284565b838152602081018390528151600160a060020a0316604082015260005b84518110156133fe576133f5828660200151838151811015156133e657fe5b90602001906020020151613731565b506001016133c7565b5050505050565b60005b835181101561346e57613465838386602001518481518110151561342857fe5b60209081029091018101519088015188516000198782010181151561344957fe5b0681518110151561345657fe5b90602001906020020151613813565b50600101613408565b50505050565b815160208301518291906101009060005b838110156135155760208102602001820151805160a082015160408301510360c083015160608401510360e084015160808501510361036084015160008b0152602084015160208b0152604084015160408b015261010085015160608b0152602085015160808b01528260a08b01528160c08b01528060e08b0152878a0199505050505050600181019050613485565b5050505092915050565b600081830184511015151561357e576040805160e560020a62461bcd02815260206004820152600c60248201527f494e56414c49445f53495a450000000000000000000000000000000000000000604482015290519081900360640190fd5b5091909101015190565b6000612b0e8260200151846040015185602001518660e001516139be565b6000828211156135ee576040805160e560020a62461bcd02815260206004820152600d60248201526000805160206142d7833981519152604482015290519081900360640190fd5b50900390565b81810282158061360e575081838281151561360b57fe5b04145b1515611c3b576040805160e560020a62461bcd02815260206004820152600d60248201526000805160206142d7833981519152604482015290519081900360640190fd5b6000612b0e826020015184610260015185602001518661010001516139be565b81810182811015611c3b576040805160e560020a62461bcd02815260206004820152600d60248201526000805160206142d7833981519152604482015290519081900360640190fd5b6000806136c885856135f4565b9050600083828115156136d757fe5b069050816136e68260646135f4565b119695505050505050565b6101009091015160400180519091019052565b602081015161010082015182516103a001516137289290910163ffffffff6135a616565b90516103a00152565b805161034001516000906137685781516101c00151600160a060020a0316156137605781516103200151613763565b60005b61376b565b60645b61ffff16606084015281516102a00151600190810b900b6080840152815160200151600160a060020a0390811660a085015282516101c001511660c084015281516103400151151560e08401528151610260015160408301516137cf918591613a00565b60a083015281516040015160608301516137ea918591613a00565b60c08301528151606001516080830151613805918591613a00565b60e090920191909152919050565b6000806138318360e0015184608001516135a690919063ffffffff16565b905060006138618261385587606001518861010001516135a690919063ffffffff16565b9063ffffffff6135a616565b90506000613890836138848860c0015189606001516135a690919063ffffffff16565b9063ffffffff61367216565b905060006138af8760a0015188604001516135a690919063ffffffff16565b905086600001516102600151600160a060020a0316876000015160400151600160a060020a031614156138f3576138ec828263ffffffff61367216565b9150600090505b613920896101a001518a6101c00151896000015161026001518a60000151602001518d60a0015186613c5c565b6101c08a018190526101a08a01518851604081015160209091015160a08d015161394d9492919087613c5c565b6101c08a018190526101a08a0151885160408101516020909101518951610300015161397c9492919088613c5c565b6101c08a018190526101a08a015188516040810151602091820151918b01516139a89492908d90613c5c565b896101c001818152505050505050949350505050565b805160009015156139de576139d4858585613d19565b6020830152600182525b604082015160208301516139f79163ffffffff6135a616565b95945050505050565b6000811515613a1157506000612b0e565b60e084015182908015613a2f575060c0850151600160a060020a0316155b15613a38575060005b600080808080851115613c39576064613a5e8a60600151876135f490919063ffffffff16565b811515613a6757fe5b04935083850392506000896080015160010b1315613ab5576020890151610120015160808a0151613aa490859060010b830363ffffffff6135f416565b811515613aad57fe5b049250613aca565b6000896080015160010b1215613aca57600092505b6000613ad68a8a613e61565b60208b0151610120015190915063ffffffff82161115613af257fe5b60208a01516101200151613b0f8563ffffffff808516906135f416565b811515613b1857fe5b049250828403935089602001516101200151613b438263ffffffff16876135f490919063ffffffff16565b811515613b4c57fe5b0491508185039450613b778a6020015161016001518b6020015161018001518b8d60c0015189613fe8565b60208b01805161018090810192909252516101608101519181015160a090910151613ba89291908c90878701613fe8565b60208b0151610180015289516060015184906000108015613bc95750600085115b15613c0957613bd98b8b87614072565b60208b015161012001518b5160600151613bfc908790830363ffffffff6135f416565b811515613c0557fe5b0490505b613c2c8b6020015161016001518c6020015161018001518c8e6040015185613fe8565b60208c0151610180015250505b613c4f878585018484010163ffffffff6135a616565b9998505050505050505050565b60008082118015613c7f575082600160a060020a031684600160a060020a031614155b15613d0c576001875b87811015613cda57805160208201516040830151898314898314168882141615613ccf576060840151870187811015613cc057600080fd5b806060860152600095508b9450505b505050608001613c88565b506001811415613d03578560008801528460208801528360408801528260608801526080870196505b50859050613d0f565b50845b9695505050505050565b604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152600160a060020a0383811660048301528581166024830152915160009285929083169163dd62ed3e91604480820192602092909190829003018186803b158015613d8957600080fd5b505afa158015613d9d573d6000803e3d6000fd5b505050506040513d6020811015613db357600080fd5b505191508115613e5957600081600160a060020a03166370a08231856040518263ffffffff1660e060020a0281526004018082600160a060020a0316600160a060020a0316815260200191505060206040518083038186803b158015613e1857600080fd5b505afa158015613e2c573d6000803e3d6000fd5b505050506040513d6020811015613e4257600080fd5b50519050828110613e535782613e55565b805b9250505b509392505050565b60208201516101400151805160009190825b81811015613f15578281815181101515613e8957fe5b906020019060200201516c010000000000000000000000009004600160a060020a031685600160a060020a03161415613f0d5760008382600101815181101515613ecf57fe5b9060200190602002015160e060020a900490508660e00151613ef5578061ffff16613f02565b6201000063ffffffff8216045b945050505050611c3b565b600201613e73565b506000856020015160e00151600160a060020a03166342b5f375866040518263ffffffff1660e060020a0281526004018082600160a060020a0316600160a060020a0316815260200191505060206040518083038186803b158015613f7957600080fd5b505afa158015613f8d573d6000803e3d6000fd5b505050506040513d6020811015613fa357600080fd5b5051602060018401810285018781520181905260028301845260e0870151909150613fd2578061ffff16613d0f565b6201000063ffffffff8216049695505050505050565b6000811515613ff85750836139f7565b6001865b868110156140445780516020820151878214878214161561403a57604083015186018681101561402b57600080fd5b80604085015260009450899350505b5050606001613ffc565b506001811415614067578460008701528360208701528260408701526060860195505b509395945050505050565b60005b83515181101561346e578351602001518051600091908390811061409557fe5b60209081029091010151516102a0015160010b1215614150576000846020015161012001516140f6866000015160200151848151811015156140d357fe5b60209081029091010151516102a00151859060000360010b63ffffffff6135f416565b8115156140ff57fe5b0490506141448560200151610160015186602001516101800151868860000151602001518681518110151561413057fe5b602090810290910181015151015185613fe8565b60208601516101800152505b600101614075565b604080516101e08101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820152610160810182905261018081018290526101a081018290526101c081019190915290565b6040805160a0810182526000808252602082018190526060928201839052918101829052608081019190915290565b60a060405190810160405280600081526020016060815260200160008019168152602001600081526020016000151581525090565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60606040519081016040528060001515815260200160008152602001600081525090565b61034060405190810160405280614299614202565b81526020016142a6614158565b815260006020820181905260408201819052606082018190526080820181905260a0820181905260c0909101529056fe494e56414c49445f56414c554500000000000000000000000000000000000000a165627a7a72305820bd367d07795d59c0b6621a02bfdad1b6a3b5bc56ea8c4cd18112b3ca55fd14130029
Deployed Bytecode
0x6080604052600436106100c65760003560e060020a900480639409d87011610083578063cbaef59b1161005d578063cbaef59b14610287578063cd117ae614610306578063f00fd4bb14610332578063fece782614610347576100c6565b80639409d870146102365780639c6a28371461024b578063a37641ff14610260576100c6565b80630af46ab01461017f5780630f9dad71146101b057806312f510f2146101c557806341ffbc1f146101da5780634c0a65321461020c5780636d96a2aa14610221575b604080518082018252600b81527f554e535550504f525445440000000000000000000000000000000000000000006020808301918252925160e560020a62461bcd0281526004810193845282516024820152825192939283926044909201919080838360005b8381101561014457818101518382015260200161012c565b50505050905090810190601f1680156101715780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b34801561018b57600080fd5b5061019461035c565b60408051600160a060020a039092168252519081900360200190f35b3480156101bc57600080fd5b50610194610374565b3480156101d157600080fd5b5061019461038c565b3480156101e657600080fd5b506101ef6103a4565b6040805167ffffffffffffffff9092168252519081900360200190f35b34801561021857600080fd5b506101946103b4565b34801561022d57600080fd5b506101946103cc565b34801561024257600080fd5b506101946103e4565b34801561025757600080fd5b506101946103fc565b34801561026c57600080fd5b50610275610414565b60408051918252519081900360200190f35b34801561029357600080fd5b50610304600480360360208110156102aa57600080fd5b8101906020810181356401000000008111156102c557600080fd5b8201836020820111156102d757600080fd5b803590602001918460018302840111640100000000831117156102f957600080fd5b509092509050610419565b005b34801561031257600080fd5b5061031b610b0b565b6040805161ffff9092168252519081900360200190f35b34801561033e57600080fd5b50610194610b11565b34801561035357600080fd5b50610194610b29565b734e1e917f030556788ab3c9d8d0971ebf0d5439e981565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b73bf5a37670b3de1e606ec68be3558c536b200866981565b60005467ffffffffffffffff1681565b73ef68e7c694f40c8202821edf525de3782458639f81565b73b258f5c190fadab30b5ff0d6ab7e32a646a4baae81565b73ac0f8a27012fe8dc5a0bb7f5fc7170934f7e357781565b736fb707f15ab3657dc52776b057b33cb7d95e4e9081565b600881565b60006060610425614158565b50604080516101e08101825273ef68e7c694f40c8202821edf525de3782458639f815273b258f5c190fadab30b5ff0d6ab7e32a646a4baae60208083019190915273bf5a37670b3de1e606ec68be3558c536b200866982840152734e1e917f030556788ab3c9d8d0971ebf0d5439e96060830152736fb707f15ab3657dc52776b057b33cb7d95e4e906080830152735beaea36efa78f43a6d61145817fdff6a9929e6060a083015273ac0f8a27012fe8dc5a0bb7f5fc7170934f7e357760c08301527320d90aefba13f044c5d23c48c3b07e2e43a006db60e08301526000805467ffffffffffffffff90811661010085018190526103e86101208601526101408501879052610160850183905261018085018390526101a085018390526101c08501929092528451808601909552600785527f5245454e5452590000000000000000000000000000000000000000000000000092850192909252919291678000000000000000900416156105de5760405160e560020a62461bcd0281526004018080602001828103825283818151815260200191508051906020019080838360008381101561014457818101518382015260200161012c565b50806101000151678000000000000000176000806101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555061061e6141d3565b60608061067573ef68e7c694f40c8202821edf525de3782458639f89898080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610b4192505050565b925092509250610686848383610dfa565b600095505b81518610156106e7576106b482878151811015156106a557fe5b90602001906020020151610e1d565b6106dc8483888151811015156106c657fe5b602090810290910101519063ffffffff610fca16565b60019095019461068b565b6106f184836110bf565b600095505b81518610156107fc5761072784838881518110151561071157fe5b602090810290910101519063ffffffff61120516565b600186015b82518110156107f057828181518110151561074357fe5b906020019060200201516103600151838881518110151561076057fe5b90602001906020020151610360015114156040805190810160405280600d81526020016000805160206142d78339815191528152509015156107e75760405160e560020a62461bcd0281526004018080602001828103825283818151815260200191508051906020019080838360008381101561014457818101518382015260200161012c565b5060010161072c565b506001909501946106f6565b600095505b80518610156108355761082a818781518110151561081b57fe5b9060200190602002015161123d565b600190950194610801565b610845838263ffffffff61129d16565b61084e83611301565b61085783611329565b60408051808201909152600b81527f494e56414c49445f53494700000000000000000000000000000000000000000060208201529015156108dd5760405160e560020a62461bcd0281526004018080602001828103825283818151815260200191508051906020019080838360008381101561014457818101518382015260200161012c565b50600095505b8151861015610989576000861180156109485750816001870381518110151561090857fe5b906020019060200201516101200151600160a060020a0316828781518110151561092e57fe5b906020019060200201516101200151600160a060020a0316145b156109525761097e565b61097e8360600151838881518110151561096857fe5b602090810290910101519063ffffffff61136e16565b6001909501946108e3565b600095505b80518610156109fa5761099f614202565b81878151811015156109ad57fe5b9060200190602002015190506109c2816113a8565b6109cb81611498565b6109db818663ffffffff61153916565b8060800151156109ee576109ee816118c9565b5060019095019461098e565b610a048282611901565b600095505b8051861015610ac057610a1a614202565b8187815181101515610a2857fe5b906020019060200201519050806080015115610a7c57610a4f81868663ffffffff6119f716565b6101008501805167ffffffffffffffff6001820181169092528551610a779284921690611a11565b610ab4565b604080820151815190815290517f977ca9d66ddf6e18cac50dbf89ee6dcce72d4635c60a13314945868798f73cdc9181900360200190a15b50600190950194610a09565b610ac984611a7d565b610ad284611af4565b610adc8483611b51565b50505061010001516000805467ffffffffffffffff191667ffffffffffffffff90921691909117905550505050565b6103e881565b7320d90aefba13f044c5d23c48c3b07e2e43a006db81565b735beaea36efa78f43a6d61145817fdff6a9929e6081565b610b496141d3565b606080610b54614237565b610b6585600063ffffffff611c2616565b61ffff168152610b7c85600263ffffffff611c2616565b61ffff166020820152610b9685600463ffffffff611c2616565b61ffff166040820152610bb085600663ffffffff611c2616565b61ffff166060820152805115610c10576040805160e560020a62461bcd02815260206004820181905260248201527f556e737570706f727465642073657269616c697a6174696f6e20666f726d6174604482015290519081900360640190fd5b6020810151600010610c6c576040805160e560020a62461bcd02815260206004820152601860248201527f496e76616c6964206e756d626572206f66206f72646572730000000000000000604482015290519081900360640190fd5b6040810151600010610cc8576040805160e560020a62461bcd02815260206004820152601760248201527f496e76616c6964206e756d626572206f662072696e6773000000000000000000604482015290519081900360640190fd5b6060810151600010610d24576040805160e560020a62461bcd02815260206004820152601c60248201527f496e76616c6964206e756d626572206f66207370656e6461626c657300000000604482015290519081900360640190fd5b60208101516040820151865187926008840192600e80860193603c909302860190810192600990920201602e81019190869003604e011115610db0576040805160e560020a62461bcd02815260206004820152601260248201527f496e76616c696420696e70757420646174610000000000000000000000000000604482015290519081900360640190fd5b610dbd8185600201611c41565b9850610dd78184600201886020015189606001518f611cac565b9750610deb8260010187604001518a6120f8565b96505050505050509250925092565b610e048383612239565b610e0e8382612257565b610e1883826122e8565b505050565b60007f40b942178d2a51f1f61934268590778feb8114db632db7d88537c98d2b05c5f2600102905060007faea25658c273c666156bd427f83a666135fcde6887a6c25fc1cd1562bc4f3f34600102905060006104a0840151805160208201206040518560008201526080870151602082015260a08701516040820152610280870151606082015260c087015160808201526101e087015160a0820152602087015160c0820152604087015160e082015260608701516101008201526101208701516101208201526101408701516101408201526101a08701516101608201526101c08701516101808201526103008701516101a08201526102608701516101c08201526103208701516101e08201526102c08701516102008201526102e08701516102208201526102408701516102408201526104008701516102608201526104208701516102808201526104408701516102a08201526104608701516102c08201526104808701516102e082015281610300820152610320812061190160008301528560208301528060408301526042601e830120945050505050808461036001818152505050505050565b610140820151600160a060020a03161515610ff8576020820151600160a060020a03166101408301526110bb565b60608101516020830151610140840151604080517f2dfc6eed000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015280516000949390931692632dfc6eed926044808201939291829003018186803b15801561107357600080fd5b505afa158015611087573d6000803e3d6000fd5b505050506040513d604081101561109d57600080fd5b50516103e084015190915080156110b15750805b15156103e0840152505b5050565b60408083015190517faec782d400000000000000000000000000000000000000000000000000000000808252602060048301528351600502602483015291906044810160005b85518110156111735760206001820102860151610140810151600084015260208101516020840152610360810151604084015260c081015160608401526c01000000000000000000000000606082015160408301511802608084015260a08301925050600181019050611105565b5060208551600201028083848403856000885af180153d83141517156111a9578015156111a4573d6000803e3d6000fd5b600080fd5b60005b87518110156111fa576020600182010288015160206002830102860151806103a0830152806103c083015260001981141560006103e084015111166103e083015250506001810190506111ac565b505050505050505050565b6103a0820151151561122a5761121b8282612373565b6112258282612518565b611234565b611234828261268b565b6110bb8261272e565b80516040516020830151600091908190835b8581101561128957602081810283010151516102a08101516103609091015161ffff9091166002850152835260229092019160010161124f565b505081900390206040909301929092525050565b60006020820151604081015160015b84518110156112d15760010160208102850151604081015190935091909118906112ac565b50604051603481019190915260208501516014820152845181526048600c90910120606090940193909352505050565b6020810151600160a060020a03161515611326578051600160a060020a031660208201525b50565b60008160400151516000141561134f57506020810151600160a060020a03163314611369565b61136682602001518360600151846040015161275a565b90505b919050565b61022082015151156110bb57816103e00151801561139c575061139c8261012001518284610220015161275a565b15156103e08301525050565b806080015180156113c75750805160011080156113c757508051600810155b1515608082015260005b81518110156110bb578151600090600019838201018115156113ef57fe5b069050826080015180156114205750602083015180518390811061140f57fe5b60209081029091010151516103e001515b15801560808501819052906114885750602083015180518290811061144157fe5b906020019060200201516000015160600151600160a060020a031683602001518381518110151561146e57fe5b602090810290910101515160400151600160a060020a0316145b15156080840152506001016113d1565b60005b8151600019018110156110bb5760008260200151828151811015156114bc57fe5b6020908102909101015151604001519050600182015b835181101561152f5783608001518015611520575060208401518051829081106114f857fe5b906020019060200201516000015160400151600160a060020a031682600160a060020a031614155b151560808501526001016114d2565b505060010161149b565b81608001511515611549576110bb565b600080805b845183101561158e576115838486602001518581518110151561156d57fe5b602090810290910101519063ffffffff612b1516565b60019092019161154e565b845160001901915060005b60008312611607578551600019848201018115156115b357fe5b0691506115f9858760200151858151811015156115cc57fe5b906020019060200201518860200151858151811015156115e857fe5b906020019060200201518685612d5d565b600019909301929050611599565b85516000190192505b80831261164f5785516000198482010181151561162957fe5b069150611642858760200151858151811015156115cc57fe5b5060001990920191611610565b600093505b85518410156116ed578560800151801561168d575061168d86602001518581518110151561167e57fe5b90602001906020020151612e04565b15156080870152602086015180516116e29190869081106116aa57fe5b9060200190602002015161010001518760200151868151811015156116cb57fe5b60209081029091010151519063ffffffff612e5116565b600190930192611654565b600093505b85518410156118625785516000198582010181151561170d57fe5b069150856080015180156117a557506117a586602001518581518110151561173157fe5b602090810290910181015151604001519088015180518790811061175157fe5b6020908102909101810151518101519089015180518690811061177057fe5b6020908102909101810151516103000151908a015180518990811061179157fe5b906020019060200201516101000151612e63565b15156080870152602086015180516000916117f891859081106117c457fe5b90602001906020020151878960200151888151811015156117e157fe5b60209081029091010151919063ffffffff61306916565b905080151561180e575060006080870152611862565b600087602001518681518110151561182257fe5b90602001906020020151600001516102a00151905060008160010b121561185557606088018051600083900360010b0190525b50506001909301926116f2565b8560800151801561187c5750846101200151866060015111155b15156080870152600093505b85518410156118c1576118b68660200151858151811015156118a657fe5b602090810290910101515161323f565b600190930192611888565b505050505050565b60005b81518110156110bb576118f98260200151828151811015156118ea57fe5b90602001906020020151613259565b6001016118cc565b60015b8015610e1857506000805b835181101561198b57838181518110151561192657fe5b906020019060200201516103e00151156119835761195a848281518110151561194b57fe5b9060200190602002015161333a565b81806119805750838181518110151561196f57fe5b906020019060200201516103e00151155b91505b60010161190f565b5080156119f25760005b82518110156119f0576119a6614202565b83828151811015156119b457fe5b9060200190602002015190508060800151156119e7576119d3816113a8565b806080015115156119e7576119e78161336a565b50600101611995565b505b611904565b611a028383836133a2565b610e1883838360000151613405565b604080840151845182518581526020810184905261010090910292810183905290917fb2ef4bc5209dff0c46d5dfddb2b68a23bd4820e8f33107fde76ed15ba90695c99160608101611a69888263ffffffff61347416565b905085858584840385a35050505050505050565b806101c00151816101a001511415611a9457611326565b60008160200151905060006020836101a00151846101c0015103811515611ab757fe5b04905060006044846101a001510390506000846101c001519050826024830152600082838303846000885af18015156118c1573d6000803e3d6000fd5b8061018001518161016001511415611b0b57611326565b60a08101516101608201516101808301516020828203908104601f1984018190529260431901919060009083906044018183885af18015156118c1573d6000803e3d6000fd5b60408083015190517f843ff0e500000000000000000000000000000000000000000000000000000000808252602060048301529190604481016000805b8651811015611bee57602060018201028701516103a08101516103c0820151808214158060006103e0860151111615611bdf5761036084015160008801528260208801526040870196506002860195505b50505050600181019050611b8e565b506000811115611c1d57806024840152600083848403856000885af1801515611c1b573d6000803e3d6000fd5b505b50505050505050565b6000611c348383600261351f565b61ffff1690505b92915050565b611c496141d3565b604080516000808252602080830184523260148881019182528751600461ffff91821681028b018301518952928590526002890151811683028a019091015187840152888301948552968101519096169095029095019093019082015291905290565b60408051600081526020808201858152858202830184019093526060926104c0916001880102908490878015611cfc57816020015b611ce9614260565b815260200190600190039081611ce15790505b509050600060405195508860008701528884028301860160405260005b898110156120e95780850284018701806020836001010289015261ffff60008d0151169250826000820152600461ffff60028e015116029250600160a060020a038360148f010151166020820152600461ffff60048e015116029250600160a060020a038360148f010151166040820152600461ffff60068e015116029250600160a060020a038360148f010151166060820152600461ffff60088e0151160292508260208e0101516080820152600461ffff600a8e0151160292508260208e01015160a0820152600461ffff600c8e01511602925063ffffffff8360048f0101511660c082015261ffff600e8d0151169250898310830292506020600184010284015160e082015261ffff60108d01511692508983108302925060206001840102840151610100820152600461ffff60128e015116029250600160a060020a038360148f01015116610120820152600461ffff60148e015116029250600160a060020a038360148f01015116610140820152600461ffff60168e015116029250600160a060020a038360148f010151166101a0820152600461ffff60188e015116029250600160a060020a038360148f010151166101c0820152600461ffff601a8e01511602925063ffffffff8360048f010151166101e08201528660208e0152600461ffff601c8e015116029250602083018d01610200820152600461ffff601e8e015116029250602083018d01610220820152600060208e015261ffff60208d0151169250600083116102408201528860148e0152600461ffff60228e015116029250600160a060020a038360148f01015116610260820152600060148e0152600461ffff60248e0151160292508260208e01015161028082015261ffff60268d0151169250826102a082015261ffff60288d0151169250826102c082015261ffff602a8d0151169250826102e0820152602081015160148e0152600461ffff602c8e015116029250600160a060020a038360148f01015116610300820152600060148e015261ffff602e8d01511692508261032082015261ffff60308d01511692508261040082015261ffff60328d01511692508261042082015261ffff60348d015116925082610440820152600461ffff60368e0151160292508260208e010151610460820152600461ffff60388e0151160292508260208e0101516104808201528660208e0152600461ffff603a8e015116029250602083018d016104a0820152600060208e015260006103408201526000610360820152600061038082015260006103a082015260006103c082015260016103e0820152603c8c019b5050600181019050611d19565b50505050505095945050505050565b604080518381526001840160200260a080860282018301909352909161014060005b8681101561222e5780830284018501806020600184010287015260ff89511660018a019950600881111561214d57600080fd5b604051816000820152602082600101028101828602810160405282600085015281602085015260006040850152600060608501526001608085015260005b8381101561221a578087028201806020600184010285015260ff8e51168c51811015156121b757600080fd5b60018f019e50602060018201028d0151600083015260006020830152600060408301526000606083015260006080830152600060a0830152600060c0830152600060e083015260006101008301526000610120830152505060018101905061218b565b50505060080398909801975060010161211a565b505050509392505050565b51604080516000815260809092028201602001905261014090910152565b6000805b8251811015612296576000838281518110151561227457fe5b602090810291909101015151600381010260090292909201915060010161225b565b50604080517f4a874dd900000000000000000000000000000000000000000000000000000000815260206004820181905260449302810183019091520161016083018190526101809092019190915250565b6000805b8251811015612321576000838281518110151561230557fe5b60209081029091010151516010029290920191506001016122ec565b50604080517ffa369e660000000000000000000000000000000000000000000000000000000081526020600482018190526044930281018301909152016101a083018190526101c09092019190915250565b81511580801561238f57506020830151600160a060020a031615155b90508080156123aa57506040830151600160a060020a031615155b90508080156123c557506060830151600160a060020a031615155b90508080156123d75750608083015115155b90508080156123e9575060a083015115155b90508080156124055750610260830151600160a060020a031615155b90508080156124215750816101200151836102c0015161ffff16105b905080801561243d5750816101200151836102e0015161ffff16105b90508080156124565750606483610320015161ffff1611155b905080801561248357506000836104000151600081111561247357fe5b1480156124835750610460830151155b90508080156124b05750600083610420015160008111156124a057fe5b1480156124b05750610480830151155b90508080156124cf5750600083610440015160008111156124cd57fe5b145b90508080156124e257506104a083015151155b90508080156124f55750428360c0015111155b9050826103e0015180156125065750805b15156103e0840152610e18838361268b565b6102008201515115156126645760008160800151600160a060020a0316631c8566968461014001518561036001516040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a031681526020018281526020019250505060206040518083038186803b15801561259657600080fd5b505afa1580156125aa573d6000803e3d6000fd5b505050506040513d60208110156125c057600080fd5b5051905080151561265e57826103e00151801561265557508160c00151600160a060020a0316634778a5be8461036001516040518263ffffffff1660e060020a0281526004018082815260200191505060206040518083038186803b15801561262857600080fd5b505afa15801561263c573d6000803e3d6000fd5b505050506040513d602081101561265257600080fd5b50515b15156103e08401525b506110bb565b816103e00151801561139c575061139c82610140015183610360015184610200015161275a565b60016101e083015115806126a3575042836101e00151115b90508080156126c2575081610120015160010b836102a0015160010b13155b90508080156126e4575081610120015160000360010b836102a0015160010b12155b610120840151909150600160a060020a0316156127115780801561270e5750600083610220015151115b90505b826103e0015180156110b1575015156103e0929092019190915250565b6000816102c0015161ffff16118061274f57506000816102e0015161ffff16115b151561034090910152565b805160009060028110156127b8576040805160e560020a62461bcd02815260206004820152601860248201527f696e76616c6964206d756c74696861736820666f726d61740000000000000000604482015290519081900360640190fd5b600183015160028085015190810160ff16831461281f576040805160e560020a62461bcd02815260206004820152601260248201527f626164206d756c7469686173682073697a650000000000000000000000000000604482015290519081900360640190fd5b60ff821615156129ae57600160a060020a0387161515612889576040805160e560020a62461bcd02815260206004820152601660248201527f696e76616c6964207369676e6572206164647265737300000000000000000000604482015290519081900360640190fd5b604160ff8216146128e4576040805160e560020a62461bcd02815260206004820152601b60248201527f62616420457468657265756d206d756c7469686173682073697a650000000000604482015290519081900360640190fd5b604080517f19457468657265756d205369676e6564204d6573736167653a0a3332000000008152601c8101889052603c9020600387015160238801516043890151845160008152602081810180885286905260ff85168288015260608201849052608082018390529551949593949293919260019260a0808401939192601f1981019281900390910190855afa158015612982573d6000803e3d6000fd5b50505060206040510351600160a060020a03168b600160a060020a031614975050505050505050612b0e565b60ff821660011415612b0657600160a060020a0387161515612a1a576040805160e560020a62461bcd02815260206004820152601660248201527f696e76616c6964207369676e6572206164647265737300000000000000000000604482015290519081900360640190fd5b604160ff821614612a75576040805160e560020a62461bcd02815260206004820152601960248201527f62616420454950373132206d756c7469686173682073697a6500000000000000604482015290519081900360640190fd5b600385015160238601516043870151604080516000815260208082018084528c905260ff8616828401526060820185905260808201849052915160019260a0808401939192601f1981019281900390910190855afa158015612adb573d6000803e3d6000fd5b50505060206040510351600160a060020a03168a600160a060020a0316149650505050505050612b0e565b600093505050505b9392505050565b8151600090612b2a908363ffffffff61358816565b83516103a0810151608090910151919250600091612b4d9163ffffffff6135a616565b9050808210612b5c5780612b5e565b815b610100850152835161034001511515612d22578351606081015161026090910151600160a060020a039081169116148015612bb157508351610300810151602090910151600160a060020a039081169116145b8015612bc95750835160a08101516102809091015111155b1515612d22578351608081015161010086015161028090920151600092612bf6919063ffffffff6135f416565b811515612bff57fe5b0490506000811115612d20578451600090612c20908663ffffffff61365216565b9050856000015160400151600160a060020a031686600001516102600151600160a060020a0316148015612c5a5750838287610100015101115b15612ce157808414612c6857fe5b8551610280810151608090910151600091612c89919063ffffffff61367216565b8751608001519091508190612ca590879063ffffffff6135f416565b811515612cae57fe5b04610100880152865161028001518190612ccf90879063ffffffff6135f416565b811515612cd857fe5b04925050612d1e565b80821115612d1e5785516102808101516080909101519192508291612d0d90839063ffffffff6135f416565b811515612d1657fe5b046101008701525b505b505b8351608081015160a090910151610100860151612d449163ffffffff6135f416565b811515612d4d57fe5b0461012090940193909352505050565b61010084015184516102c0015182919061ffff166000811115612db2576000886101200151612d9a838a61010001516135f490919063ffffffff16565b811515612da357fe5b04905080886101000151039250505b818661012001511115612df9576101208601829052855160a0810151608090910151869450612de890849063ffffffff6135f416565b811515612df157fe5b046101008701525b505095945050505050565b6000612e26826101000151836000015160a001518460000151608001516136bb565b159050808015612e3b57506000826101000151115b9050808015611c3b575050610120015160001090565b60e09091015160400180519091019052565b6040805160008082526020820192839052600160a060020a0380871660448401908152908616606484015260848301859052608060a48401908152835160c4850181905292946060947ff1d74b0f00000000000000000000000000000000000000000000000000000000948a948a948a949192909160e48501918083838e5b83811015612efa578181015183820152602001612ee2565b50505050905090810190601f168015612f275780820380516001836020036101000a031916815260200191505b5095505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506000606087600160a060020a0316836040518082805190602001908083835b60208310612fcc5780518252601f199092019160209182019101612fad565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461302e576040519150601f19603f3d011682016040523d82523d6000602084013e613033565b606091505b5091509150818015613046575080516020145b1561305957602001519250613061915050565b600193505050505b949350505050565b82516103400151600090156130f0576000604085015261012082015184516102c001516101008601516130a59161ffff1663ffffffff6135f416565b8115156130ae57fe5b0460608501526101208083015185516102e001519186015190916130dc919061ffff1663ffffffff6135f416565b8115156130e557fe5b0460808501526131f2565b83516080810151610100860151610280909201519091613116919063ffffffff6135f416565b81151561311f57fe5b04604085015260006060808601829052608086019190915284519081015161026090910151600160a060020a03908116911614801561317657508351610300810151602090910151600160a060020a039081169116145b801561318b5750836040015184610120015110155b156131a0576040840180516080860152600090525b6000846040015111156131f25783516000906131c2908463ffffffff61365216565b905080856040015111156131da576000915050612b0e565b604085015185516131f09163ffffffff6136f116565b505b61012083015160608501516101008601510310613237575061012082018051606085015161010086018051829003929092036020870152915190910190526001612b0e565b506000612b0e565b60e081015160006040918201819052610100909201510152565b602080820180516101008401805185516103a00180519190930101909152905190516040840151845160e00151909301519101919061329e908363ffffffff6135a616565b835160e001516020908101919091528351610100015101516132c6908263ffffffff6135a616565b835161010001516020015282516103800151600160a060020a031615610e18578251610160015160200151613301908363ffffffff6135a616565b8351610160015160209081019190915283516101800151015161332a908263ffffffff6135a616565b8351610180015160200152505050565b8061024001511561132657806103e00151801561335f57508060800151816103a00151145b15156103e082015250565b60005b81518110156110bb5761339a82602001518281518110151561338b57fe5b90602001906020020151613704565b60010161336d565b6133aa614284565b838152602081018390528151600160a060020a0316604082015260005b84518110156133fe576133f5828660200151838151811015156133e657fe5b90602001906020020151613731565b506001016133c7565b5050505050565b60005b835181101561346e57613465838386602001518481518110151561342857fe5b60209081029091018101519088015188516000198782010181151561344957fe5b0681518110151561345657fe5b90602001906020020151613813565b50600101613408565b50505050565b815160208301518291906101009060005b838110156135155760208102602001820151805160a082015160408301510360c083015160608401510360e084015160808501510361036084015160008b0152602084015160208b0152604084015160408b015261010085015160608b0152602085015160808b01528260a08b01528160c08b01528060e08b0152878a0199505050505050600181019050613485565b5050505092915050565b600081830184511015151561357e576040805160e560020a62461bcd02815260206004820152600c60248201527f494e56414c49445f53495a450000000000000000000000000000000000000000604482015290519081900360640190fd5b5091909101015190565b6000612b0e8260200151846040015185602001518660e001516139be565b6000828211156135ee576040805160e560020a62461bcd02815260206004820152600d60248201526000805160206142d7833981519152604482015290519081900360640190fd5b50900390565b81810282158061360e575081838281151561360b57fe5b04145b1515611c3b576040805160e560020a62461bcd02815260206004820152600d60248201526000805160206142d7833981519152604482015290519081900360640190fd5b6000612b0e826020015184610260015185602001518661010001516139be565b81810182811015611c3b576040805160e560020a62461bcd02815260206004820152600d60248201526000805160206142d7833981519152604482015290519081900360640190fd5b6000806136c885856135f4565b9050600083828115156136d757fe5b069050816136e68260646135f4565b119695505050505050565b6101009091015160400180519091019052565b602081015161010082015182516103a001516137289290910163ffffffff6135a616565b90516103a00152565b805161034001516000906137685781516101c00151600160a060020a0316156137605781516103200151613763565b60005b61376b565b60645b61ffff16606084015281516102a00151600190810b900b6080840152815160200151600160a060020a0390811660a085015282516101c001511660c084015281516103400151151560e08401528151610260015160408301516137cf918591613a00565b60a083015281516040015160608301516137ea918591613a00565b60c08301528151606001516080830151613805918591613a00565b60e090920191909152919050565b6000806138318360e0015184608001516135a690919063ffffffff16565b905060006138618261385587606001518861010001516135a690919063ffffffff16565b9063ffffffff6135a616565b90506000613890836138848860c0015189606001516135a690919063ffffffff16565b9063ffffffff61367216565b905060006138af8760a0015188604001516135a690919063ffffffff16565b905086600001516102600151600160a060020a0316876000015160400151600160a060020a031614156138f3576138ec828263ffffffff61367216565b9150600090505b613920896101a001518a6101c00151896000015161026001518a60000151602001518d60a0015186613c5c565b6101c08a018190526101a08a01518851604081015160209091015160a08d015161394d9492919087613c5c565b6101c08a018190526101a08a0151885160408101516020909101518951610300015161397c9492919088613c5c565b6101c08a018190526101a08a015188516040810151602091820151918b01516139a89492908d90613c5c565b896101c001818152505050505050949350505050565b805160009015156139de576139d4858585613d19565b6020830152600182525b604082015160208301516139f79163ffffffff6135a616565b95945050505050565b6000811515613a1157506000612b0e565b60e084015182908015613a2f575060c0850151600160a060020a0316155b15613a38575060005b600080808080851115613c39576064613a5e8a60600151876135f490919063ffffffff16565b811515613a6757fe5b04935083850392506000896080015160010b1315613ab5576020890151610120015160808a0151613aa490859060010b830363ffffffff6135f416565b811515613aad57fe5b049250613aca565b6000896080015160010b1215613aca57600092505b6000613ad68a8a613e61565b60208b0151610120015190915063ffffffff82161115613af257fe5b60208a01516101200151613b0f8563ffffffff808516906135f416565b811515613b1857fe5b049250828403935089602001516101200151613b438263ffffffff16876135f490919063ffffffff16565b811515613b4c57fe5b0491508185039450613b778a6020015161016001518b6020015161018001518b8d60c0015189613fe8565b60208b01805161018090810192909252516101608101519181015160a090910151613ba89291908c90878701613fe8565b60208b0151610180015289516060015184906000108015613bc95750600085115b15613c0957613bd98b8b87614072565b60208b015161012001518b5160600151613bfc908790830363ffffffff6135f416565b811515613c0557fe5b0490505b613c2c8b6020015161016001518c6020015161018001518c8e6040015185613fe8565b60208c0151610180015250505b613c4f878585018484010163ffffffff6135a616565b9998505050505050505050565b60008082118015613c7f575082600160a060020a031684600160a060020a031614155b15613d0c576001875b87811015613cda57805160208201516040830151898314898314168882141615613ccf576060840151870187811015613cc057600080fd5b806060860152600095508b9450505b505050608001613c88565b506001811415613d03578560008801528460208801528360408801528260608801526080870196505b50859050613d0f565b50845b9695505050505050565b604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152600160a060020a0383811660048301528581166024830152915160009285929083169163dd62ed3e91604480820192602092909190829003018186803b158015613d8957600080fd5b505afa158015613d9d573d6000803e3d6000fd5b505050506040513d6020811015613db357600080fd5b505191508115613e5957600081600160a060020a03166370a08231856040518263ffffffff1660e060020a0281526004018082600160a060020a0316600160a060020a0316815260200191505060206040518083038186803b158015613e1857600080fd5b505afa158015613e2c573d6000803e3d6000fd5b505050506040513d6020811015613e4257600080fd5b50519050828110613e535782613e55565b805b9250505b509392505050565b60208201516101400151805160009190825b81811015613f15578281815181101515613e8957fe5b906020019060200201516c010000000000000000000000009004600160a060020a031685600160a060020a03161415613f0d5760008382600101815181101515613ecf57fe5b9060200190602002015160e060020a900490508660e00151613ef5578061ffff16613f02565b6201000063ffffffff8216045b945050505050611c3b565b600201613e73565b506000856020015160e00151600160a060020a03166342b5f375866040518263ffffffff1660e060020a0281526004018082600160a060020a0316600160a060020a0316815260200191505060206040518083038186803b158015613f7957600080fd5b505afa158015613f8d573d6000803e3d6000fd5b505050506040513d6020811015613fa357600080fd5b5051602060018401810285018781520181905260028301845260e0870151909150613fd2578061ffff16613d0f565b6201000063ffffffff8216049695505050505050565b6000811515613ff85750836139f7565b6001865b868110156140445780516020820151878214878214161561403a57604083015186018681101561402b57600080fd5b80604085015260009450899350505b5050606001613ffc565b506001811415614067578460008701528360208701528260408701526060860195505b509395945050505050565b60005b83515181101561346e578351602001518051600091908390811061409557fe5b60209081029091010151516102a0015160010b1215614150576000846020015161012001516140f6866000015160200151848151811015156140d357fe5b60209081029091010151516102a00151859060000360010b63ffffffff6135f416565b8115156140ff57fe5b0490506141448560200151610160015186602001516101800151868860000151602001518681518110151561413057fe5b602090810290910181015151015185613fe8565b60208601516101800152505b600101614075565b604080516101e08101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082018390526101208201839052610140820152610160810182905261018081018290526101a081018290526101c081019190915290565b6040805160a0810182526000808252602082018190526060928201839052918101829052608081019190915290565b60a060405190810160405280600081526020016060815260200160008019168152602001600081526020016000151581525090565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60606040519081016040528060001515815260200160008152602001600081525090565b61034060405190810160405280614299614202565b81526020016142a6614158565b815260006020820181905260408201819052606082018190526080820181905260a0820181905260c0909101529056fe494e56414c49445f56414c554500000000000000000000000000000000000000a165627a7a72305820bd367d07795d59c0b6621a02bfdad1b6a3b5bc56ea8c4cd18112b3ca55fd14130029
Swarm Source
bzzr://bd367d07795d59c0b6621a02bfdad1b6a3b5bc56ea8c4cd18112b3ca55fd1413
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.