BscScan - Sponsored slots available. Book your slot here!
More Info
Private Name Tags
ContractCreator
Latest 25 from a total of 281,689 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Execute Trade | 45325309 | 14 secs ago | IN | 0 BNB | 0.00027802 | ||||
Execute Trade | 45325309 | 14 secs ago | IN | 0 BNB | 0.00038222 | ||||
Deposit Token By... | 45325246 | 3 mins ago | IN | 0 BNB | 0.00039093 | ||||
Execute Trade | 45325228 | 4 mins ago | IN | 0 BNB | 0.00027803 | ||||
Execute Trade | 45325218 | 4 mins ago | IN | 0 BNB | 0.00029484 | ||||
Execute Trade | 45325198 | 5 mins ago | IN | 0 BNB | 0.00029363 | ||||
Withdraw | 45325188 | 6 mins ago | IN | 0 BNB | 0.0001486 | ||||
Withdraw | 45325117 | 9 mins ago | IN | 0 BNB | 0.00016194 | ||||
Deposit Token By... | 45325101 | 10 mins ago | IN | 0 BNB | 0.00038528 | ||||
Execute Trade | 45325056 | 12 mins ago | IN | 0 BNB | 0.00038521 | ||||
Execute Trade | 45325056 | 12 mins ago | IN | 0 BNB | 0.00046676 | ||||
Execute Trade | 45325036 | 13 mins ago | IN | 0 BNB | 0.00029575 | ||||
Withdraw | 45325017 | 14 mins ago | IN | 0 BNB | 0.00014483 | ||||
Withdraw | 45324997 | 15 mins ago | IN | 0 BNB | 0.00016454 | ||||
Execute Trade | 45324925 | 19 mins ago | IN | 0 BNB | 0.00029311 | ||||
Execute Trade | 45324925 | 19 mins ago | IN | 0 BNB | 0.00038224 | ||||
Deposit Token By... | 45324922 | 19 mins ago | IN | 0 BNB | 0.00019272 | ||||
Execute Trade | 45324895 | 20 mins ago | IN | 0 BNB | 0.00029368 | ||||
Execute Trade | 45324895 | 20 mins ago | IN | 0 BNB | 0.00027774 | ||||
Withdraw | 45324837 | 23 mins ago | IN | 0 BNB | 0.00014486 | ||||
Execute Trade | 45324804 | 25 mins ago | IN | 0 BNB | 0.00037854 | ||||
Deposit Token By... | 45324774 | 26 mins ago | IN | 0 BNB | 0.00021989 | ||||
Deposit Token By... | 45324740 | 28 mins ago | IN | 0 BNB | 0.00038146 | ||||
Execute Trade | 45324703 | 30 mins ago | IN | 0 BNB | 0.00029164 | ||||
Execute Trade | 45324703 | 30 mins ago | IN | 0 BNB | 0.00038003 |
Latest 25 internal transactions (View All)
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
45291666 | 28 hrs ago | 0.04 BNB | ||||
44822435 | 17 days ago | 0.001 BNB | ||||
44601183 | 25 days ago | 0.02 BNB | ||||
44543412 | 27 days ago | 0.02 BNB | ||||
44543344 | 27 days ago | 0.0025261 BNB | ||||
44482548 | 29 days ago | 0.016 BNB | ||||
44217954 | 38 days ago | 0.01 BNB | ||||
43920109 | 48 days ago | 0.002 BNB | ||||
43712580 | 56 days ago | 0.002 BNB | ||||
43675634 | 57 days ago | 0.005 BNB | ||||
43613546 | 59 days ago | 0.22 BNB | ||||
43393823 | 67 days ago | 0.1 BNB | ||||
43389265 | 67 days ago | 0.003 BNB | ||||
43388784 | 67 days ago | 0.0002 BNB | ||||
43366446 | 68 days ago | 0.002 BNB | ||||
43254382 | 71 days ago | 0.6 BNB | ||||
43123308 | 76 days ago | 0.03 BNB | ||||
43122784 | 76 days ago | 0.8 BNB | ||||
42986793 | 81 days ago | 0.01 BNB | ||||
42881956 | 84 days ago | 0.08 BNB | ||||
42879670 | 84 days ago | 0.003 BNB | ||||
42852458 | 85 days ago | 0.03 BNB | ||||
42803039 | 87 days ago | 0.03 BNB | ||||
42796814 | 87 days ago | 0.009 BNB | ||||
42766855 | 88 days ago | 0.3 BNB |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x9881513a...2970b42Da The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
Exchange
Compiler Version
v0.8.4+commit.c7e474f2
Optimization Enabled:
Yes with 13 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; import { Address } from './libraries/Address.sol'; import { SafeMath as SafeMath256 } from './libraries/SafeMath.sol'; import { AssetRegistry } from './libraries/AssetRegistry.sol'; import { AssetTransfers } from './libraries/AssetTransfers.sol'; import { AssetUnitConversions } from './libraries/AssetUnitConversions.sol'; import { Owned } from './Owned.sol'; import { SafeMath64 } from './libraries/SafeMath64.sol'; import { Signatures } from './libraries/Signatures.sol'; import { Enums, ICustodian, IBEP20, IExchange, Structs } from './libraries/Interfaces.sol'; import { UUID } from './libraries/UUID.sol'; import { IterableMapping } from './libraries/IterableMapping.sol'; // import 'hardhat/console.sol'; /** * @notice The Exchange contract. Implements all deposit, trade, and withdrawal logic and associated balance tracking * * @dev The term `asset` refers collectively to BNB and BEP-20 tokens, the term `token` refers only to the latter * @dev Events with indexed string parameters (Deposited and TradeExecuted) only log the hash values for those * parameters, from which the original raw string values cannot be retrieved. For convenience these events contain * the un-indexed string parameter values in addition to the indexed values */ contract Exchange is IExchange, Owned { using SafeMath64 for uint64; using SafeMath256 for uint256; using AssetRegistry for AssetRegistry.Storage; using IterableMapping for IterableMapping.Map; // Events // /** * @notice Emitted when an admin changes the Chain Propagation Period tunable parameter with `setChainPropagationPeriod` */ event ChainPropagationPeriodChanged(uint256 previousValue, uint256 newValue); /** * @notice Emitted when a user deposits BNB with `depositBNB` or a token with `depositAsset` or `depositAssetBySymbol` */ event Deposited( uint64 index, address indexed wallet, address indexed assetAddress, string indexed assetSymbolIndex, string assetSymbol, uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ); /** * @notice Emitted when an admin changes the Dispatch Wallet tunable parameter with `setDispatcher` */ event AddDispatcher(address newValue); event RemoveDispatcher(address value); /** * @notice Emitted when an admin changes the Fee Wallet tunable parameter with `setFeeWallet` */ event FeeWalletChanged(address previousValue, address newValue); /** * @notice Emitted when a user invalidates an order nonce with `invalidateOrderNonce` */ event OrderNonceInvalidated( address indexed wallet, uint128 nonce, uint128 timestampInMs, uint256 effectiveBlockNumber ); /** * @notice Emitted when an admin initiates the token registration process with `registerToken` */ event TokenRegistered( IBEP20 indexed assetAddress, string assetSymbol, uint8 decimals ); /** * @notice Emitted when an admin finalizes the token registration process with `confirmAssetRegistration`, after * which it can be deposited, traded, or withdrawn */ event TokenRegistrationConfirmed( IBEP20 indexed assetAddress, string assetSymbol, uint8 decimals ); /** * @notice Emitted when an admin adds a symbol to a previously registered and confirmed token * via `addTokenSymbol` */ event TokenSymbolAdded(IBEP20 indexed assetAddress, string assetSymbol); /** * @notice Emitted when the Dispatcher Wallet submits a trade for execution with `executeTrade` */ event TradeExecuted( address buyWallet, address sellWallet, string indexed baseAssetSymbolIndex, string indexed quoteAssetSymbolIndex, string baseAssetSymbol, string quoteAssetSymbol, uint64 baseQuantityInPips, uint64 quoteQuantityInPips, uint64 tradePriceInPips, bytes32 buyOrderHash, bytes32 sellOrderHash ); /** * @notice Emitted when the Dispatcher Wallet submits trades for execution with `executeTrades` */ // event TradesExecuted(Structs.ExecRet[] vals); /** * @notice Emitted when a user invokes the Exit Wallet mechanism with `exitWallet` */ event WalletExited(address indexed wallet, uint256 effectiveBlockNumber); /** * @notice Emitted when a user withdraws an asset balance through the Exit Wallet mechanism with `withdrawExit` */ event WalletExitWithdrawn( address indexed wallet, address indexed assetAddress, string assetSymbol, uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ); /** * @notice Emitted when a user clears the exited status of a wallet previously exited with `exitWallet` */ event WalletExitCleared(address indexed wallet); /** * @notice Emitted when the Dispatcher Wallet submits a withdrawal with `withdraw` */ event Withdrawn( address indexed wallet, address indexed assetAddress, string assetSymbol, uint64 quantityInPips, uint64 newExchangeBalanceInPips, uint256 newExchangeBalanceInAssetUnits ); // Internally used structs // struct NonceInvalidation { bool exists; uint64 timestampInMs; uint256 effectiveBlockNumber; } struct WalletExit { bool exists; uint256 effectiveBlockNumber; } // Storage // // Asset registry data AssetRegistry.Storage _assetRegistry; // Mapping of order wallet hash => isComplete mapping(bytes32 => bool) _completedOrderHashes; // Mapping of withdrawal wallet hash => isComplete mapping(bytes32 => bool) _completedWithdrawalHashes; address payable _custodian; uint64 _depositIndex; // Mapping of wallet => IterableMapping.Map mapping(address => IterableMapping.Map) _balancesInPips; // Mapping of wallet => last invalidated timestampInMs mapping(address => NonceInvalidation) _nonceInvalidations; // Mapping of order hash => filled quantity in pips mapping(bytes32 => uint64) _partiallyFilledOrderQuantitiesInPips; mapping(address => WalletExit) _walletExits; // Tunable parameters uint256 _chainPropagationPeriod; mapping (address => bool) _dispatcherWallet; address _feeWallet; // Constant values // uint256 constant _maxChainPropagationPeriod = 201600; //(7 * 24 * 60 * 60) / 3; 1 week at 3s/block uint64 constant _maxTradeFeeBasisPoints = 2000; // 20 * 100; 20%; uint64 constant _maxWithdrawalFeeBasisPoints = 2000; // 20 * 100; // 20%; /** * @notice Instantiate a new `Exchange` contract * * @dev Sets `_owner` and `_admin` to `msg.sender` */ constructor() Owned() {} /** * @notice Sets the address of the `Custodian` contract * * @dev The `Custodian` accepts `Exchange` and `Governance` addresses in its constructor, after * which they can only be changed by the `Governance` contract itself. Therefore the `Custodian` * must be deployed last and its address set here on an existing `Exchange` contract. This value * is immutable once set and cannot be changed again * * @param newCustodian The address of the `Custodian` contract deployed against this `Exchange` * contract's address */ function setCustodian(address payable newCustodian) external onlyAdmin { require(_custodian == address(0x0), 'already set'); require(Address.isContract(newCustodian), 'Invalid address'); _custodian = newCustodian; } /*** Tunable parameters ***/ /** * @notice Sets a new Chain Propagation Period - the block delay after which order nonce invalidations * are respected by `executeTrade` and wallet exits are respected by `executeTrade` and `withdraw` * * @param newChainPropagationPeriod The new Chain Propagation Period expressed as a number of blocks. Must * be less than `_maxChainPropagationPeriod` */ function setChainPropagationPeriod(uint256 newChainPropagationPeriod) external onlyAdmin { require( newChainPropagationPeriod < _maxChainPropagationPeriod, 'Bigger than 1 week' ); uint256 oldChainPropagationPeriod = _chainPropagationPeriod; _chainPropagationPeriod = newChainPropagationPeriod; emit ChainPropagationPeriodChanged( oldChainPropagationPeriod, newChainPropagationPeriod ); } /** * @notice Sets the address of the Fee wallet * * @dev Trade and Withdraw fees will accrue in the `_balancesInPips` mappings for this wallet * * @param newFeeWallet The new Fee wallet. Must be different from the current one */ function setFeeWallet(address newFeeWallet) external onlyAdmin { require(newFeeWallet != address(0x0), 'Invalid address'); require( newFeeWallet != _feeWallet, 'Same fee wallet' ); address oldFeeWallet = _feeWallet; _feeWallet = newFeeWallet; emit FeeWalletChanged(oldFeeWallet, newFeeWallet); } // Accessors // /** * @notice Load a wallet's balance by asset address, in asset units * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetAddress The asset address to load the wallet's balance for * * @return The quantity denominated in asset units of asset at `assetAddress` currently * deposited by `wallet` */ function loadBalanceInAssetUnitsByAddress( address wallet, address assetAddress ) external view returns (uint256) { require(wallet != address(0x0), 'Invalid address'); Structs.Asset memory asset = _assetRegistry.loadAssetByAddress( assetAddress ); return AssetUnitConversions.pipsToAssetUnits( _balancesInPips[wallet].get(assetAddress), asset.decimals ); } /** * @notice Load a wallet's balance by asset address, in asset units * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetSymbol The asset symbol to load the wallet's balance for * * @return The quantity denominated in asset units of asset `assetSymbol` currently deposited * by `wallet` */ function loadBalanceInAssetUnitsBySymbol( address wallet, string calldata assetSymbol ) external view returns (uint256) { require(wallet != address(0x0), 'Invalid address'); Structs.Asset memory asset = _assetRegistry.loadAssetBySymbol( assetSymbol, getCurrentTimestampInMs() ); return AssetUnitConversions.pipsToAssetUnits( _balancesInPips[wallet].get(asset.assetAddress), asset.decimals ); } /** * @notice Load a wallet's balance by asset address, in pips * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetAddress The asset address to load the wallet's balance for * * @return The quantity denominated in pips of asset at `assetAddress` currently deposited by `wallet` */ function loadBalanceInPipsByAddress(address wallet, address assetAddress) external view returns (uint64) { require(wallet != address(0x0), 'Invalid address'); return _balancesInPips[wallet].get(assetAddress); } /** * @notice Load a wallet's balance by asset symbol, in pips * * @param wallet The wallet address to load the balance for. Can be different from `msg.sender` * @param assetSymbol The asset symbol to load the wallet's balance for * * @return The quantity denominated in pips of asset with `assetSymbol` currently deposited by `wallet` */ function loadBalanceInPipsBySymbol( address wallet, string calldata assetSymbol ) external view returns (uint64) { require(wallet != address(0x0), 'Invalid address'); address assetAddress = _assetRegistry .loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs()) .assetAddress; return _balancesInPips[wallet].get(assetAddress); } /** * @notice Load a wallet's asset address of balance * * @param wallet The wallet address to load the balance asset address for. * * @return All assets address of a wallet's balance */ function loadBalanceAssetAddress(address wallet) external view returns (address[] memory) { address[] memory assetsAddress = new address[](_balancesInPips[wallet].size()); for (uint i = 0; i < _balancesInPips[wallet].size(); i++) { address key = _balancesInPips[wallet].getKeyAtIndex(i); assetsAddress[i] = key; } return assetsAddress; } /** * @notice Load the address of the Fee wallet * * @return The address of the Fee wallet */ function loadFeeWallet() external view returns (address) { return _feeWallet; } /** * @notice Load the quantity filled so far for a partially filled orders * @dev Invalidating an order nonce will not clear partial fill quantities for earlier orders because * the gas cost would potentially be unbound * * @param orderHash The order hash as originally signed by placing wallet that uniquely identifies an order * * @return For partially filled orders, the amount filled so far in pips. For orders in all other states, 0 */ function loadPartiallyFilledOrderQuantityInPips(bytes32 orderHash) external view returns (uint64) { return _partiallyFilledOrderQuantitiesInPips[orderHash]; } // Depositing // /** * @notice Deposit BNB */ function depositBNB() external payable { require(msg.value%uint256(10000000000)==0, "dust value"); deposit(msg.sender, address(0x0), msg.value); } /** * @notice Deposit `IBEP20` compliant tokens * * @param tokenAddress The token contract address * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on * the token contract for at least this quantity first */ function depositTokenByAddress( IBEP20 tokenAddress, uint256 quantityInAssetUnits ) external { require( address(tokenAddress) != address(0x0), 'Invalid address' ); deposit(msg.sender, address(tokenAddress), quantityInAssetUnits); } /** * @notice Deposit `IBEP20` compliant tokens * * @param assetSymbol The case-sensitive symbol string for the token * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on * the token contract for at least this quantity first */ function depositTokenBySymbol( string calldata assetSymbol, uint256 quantityInAssetUnits ) external { IBEP20 tokenAddress = IBEP20( _assetRegistry .loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs()) .assetAddress ); require( address(tokenAddress) != address(0x0), 'Invalid address' ); deposit(msg.sender, address(tokenAddress), quantityInAssetUnits); } function deposit( address wallet, address assetAddress, uint256 quantityInAssetUnits ) private { // Calling exitWallet disables deposits immediately on mining, in contrast to withdrawals and // trades which respect the Chain Propagation Period given by `effectiveBlockNumber` via // `isWalletExitFinalized` require(!_walletExits[wallet].exists, 'Wallet exited'); Structs.Asset memory asset = _assetRegistry.loadAssetByAddress( assetAddress ); uint64 quantityInPips = AssetUnitConversions.assetUnitsToPips( quantityInAssetUnits, asset.decimals ); require(quantityInPips > 0, 'Quantity is too low'); // Convert from pips back into asset units to remove any fractional amount that is too small // to express in pips. If the asset is BNB, this leftover fractional amount accumulates as dust // in the `Exchange` contract. If the asset is a token the `Exchange` will call `transferFrom` // without this fractional amount and there will be no dust uint256 quantityInAssetUnitsWithoutFractionalPips = AssetUnitConversions .pipsToAssetUnits(quantityInPips, asset.decimals); // If the asset is BNB then the funds were already assigned to this contract via msg.value. If // the asset is a token, additionally call the transferFrom function on the token contract for // the pre-approved asset quantity if (assetAddress != address(0x0)) { AssetTransfers.transferFrom( wallet, IBEP20(assetAddress), quantityInAssetUnitsWithoutFractionalPips ); } // Forward the funds to the `Custodian` AssetTransfers.transferTo( _custodian, assetAddress, quantityInAssetUnitsWithoutFractionalPips ); uint64 newExchangeBalanceInPips = _balancesInPips[wallet].get(assetAddress).add( quantityInPips ); uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions .pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals); // Update balance with actual transferred quantity _balancesInPips[wallet].set(assetAddress, newExchangeBalanceInPips); _depositIndex++; emit Deposited( _depositIndex, wallet, assetAddress, asset.symbol, asset.symbol, quantityInPips, newExchangeBalanceInPips, newExchangeBalanceInAssetUnits ); } // Invalidation // /** * @notice Invalidate all order nonces with a timestampInMs lower than the one provided * * @param nonce A Version 1 UUID. After calling and once the Chain Propagation Period has elapsed, * `executeTrade` will reject order nonces from this wallet with a timestampInMs component lower than * the one provided */ function invalidateOrderNonce(uint128 nonce) external { uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(nonce); // Enforce a maximum skew for invalidating nonce timestamps in the future so the user doesn't // lock their wallet from trades indefinitely require( timestampInMs < getOneDayFromNowInMs(), 'Nonce too far in future' ); if (_nonceInvalidations[msg.sender].exists) { require( _nonceInvalidations[msg.sender].timestampInMs < timestampInMs, 'Invalidated' ); require( _nonceInvalidations[msg.sender].effectiveBlockNumber <= block.number, 'Awaiting propagation' ); } // Changing the Chain Propagation Period will not affect the effectiveBlockNumber for this invalidation uint256 effectiveBlockNumber = block.number + _chainPropagationPeriod; _nonceInvalidations[msg.sender] = NonceInvalidation( true, timestampInMs, effectiveBlockNumber ); emit OrderNonceInvalidated( msg.sender, nonce, timestampInMs, effectiveBlockNumber ); } // Withdrawing // /** * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently whitelisted Dispatcher wallet * * @param withdrawal A `Structs.Withdrawal` struct encoding the parameters of the withdrawal */ function withdraw(Structs.Withdrawal memory withdrawal) public override onlyDispatcher { // Validations require(!isWalletExitFinalized(withdrawal.walletAddress), 'Wallet exited'); require( getFeeBasisPoints(withdrawal.gasFeeInPips, withdrawal.quantityInPips) <= _maxWithdrawalFeeBasisPoints, 'Excessive fee' ); bytes32 withdrawalHash = validateWithdrawalSignature(withdrawal); require( !_completedWithdrawalHashes[withdrawalHash], 'Withdrawn' ); // If withdrawal is by asset symbol (most common) then resolve to asset address Structs.Asset memory asset = withdrawal.withdrawalType == Enums.WithdrawalType.BySymbol ? _assetRegistry.loadAssetBySymbol( withdrawal.assetSymbol, UUID.getTimestampInMsFromUuidV1(withdrawal.nonce) ) : _assetRegistry.loadAssetByAddress(withdrawal.assetAddress); // SafeMath reverts if balance is overdrawn uint64 netAssetQuantityInPips = withdrawal.quantityInPips.sub( withdrawal.gasFeeInPips ); uint256 netAssetQuantityInAssetUnits = AssetUnitConversions .pipsToAssetUnits(netAssetQuantityInPips, asset.decimals); uint64 newExchangeBalanceInPips = _balancesInPips[withdrawal .walletAddress].get(asset.assetAddress) .sub(withdrawal.quantityInPips); uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions .pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals); if (newExchangeBalanceInPips==0) { _balancesInPips[withdrawal.walletAddress].remove(asset .assetAddress); } else { _balancesInPips[withdrawal.walletAddress].set(asset .assetAddress, newExchangeBalanceInPips); } _balancesInPips[_feeWallet].set(asset .assetAddress, _balancesInPips[_feeWallet].get(asset.assetAddress).add( withdrawal.gasFeeInPips )); ICustodian(_custodian).withdraw( withdrawal.walletAddress, asset.assetAddress, netAssetQuantityInAssetUnits ); _completedWithdrawalHashes[withdrawalHash] = true; emit Withdrawn( withdrawal.walletAddress, asset.assetAddress, asset.symbol, withdrawal.quantityInPips, newExchangeBalanceInPips, newExchangeBalanceInAssetUnits ); } // Wallet exits // /** * @notice Flags the sending wallet as exited, immediately disabling deposits upon mining. * After the Chain Propagation Period passes trades and withdrawals are also disabled for the wallet, * and assets may then be withdrawn one at a time via `withdrawExit` */ function exitWallet() external { require(!_walletExits[msg.sender].exists, 'Wallet exited'); _walletExits[msg.sender] = WalletExit( true, block.number + _chainPropagationPeriod ); emit WalletExited(msg.sender, block.number + _chainPropagationPeriod); } /** * @notice Withdraw the entire balance of an asset for an exited wallet. The Chain Propagation Period must * have already passed since calling `exitWallet` on `assetAddress` * * @param assetAddress The address of the asset to withdraw */ function withdrawExit(address assetAddress) external { require(isWalletExitFinalized(msg.sender), 'Wallet not finalized'); Structs.Asset memory asset = _assetRegistry.loadAssetByAddress( assetAddress ); uint64 balanceInPips = _balancesInPips[msg.sender].get(assetAddress); uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits( balanceInPips, asset.decimals ); require(balanceInAssetUnits > 0, 'Balance is 0'); _balancesInPips[msg.sender].remove(assetAddress); ICustodian(_custodian).withdraw( payable(msg.sender), assetAddress, balanceInAssetUnits ); emit WalletExitWithdrawn( msg.sender, assetAddress, asset.symbol, balanceInPips, 0, 0 ); } /** * @notice Withdraw the entire balance of all assets for an exited wallet. The Chain Propagation Period must * have already passed since calling `exitWallet` */ function withdrawAllExit() external { require(isWalletExitFinalized(msg.sender), 'Wallet not finalized'); address[] memory assetsAddress = new address[](_balancesInPips[msg.sender].size()); for (uint i = 0; i < _balancesInPips[msg.sender].size(); i++) { address assetAddress = _balancesInPips[msg.sender].getKeyAtIndex(i); assetsAddress[i] = assetAddress; Structs.Asset memory asset = _assetRegistry.loadAssetByAddress( assetAddress ); uint64 balanceInPips = _balancesInPips[msg.sender].get(assetAddress); uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits( balanceInPips, asset.decimals ); if (balanceInAssetUnits == 0) { continue; } ICustodian(_custodian).withdraw( payable(msg.sender), assetAddress, balanceInAssetUnits ); emit WalletExitWithdrawn( msg.sender, assetAddress, asset.symbol, balanceInPips, 0, 0 ); } for (uint i = 0; i < assetsAddress.length; i++) { _balancesInPips[msg.sender].remove(assetsAddress[i]); } } /** * @notice Clears exited status of sending wallet. Upon mining immediately enables * deposits, trades, and withdrawals by sending wallet */ function clearWalletExit() external { require(_walletExits[msg.sender].exists, 'Wallet not exited'); delete _walletExits[msg.sender]; emit WalletExitCleared(msg.sender); } /** * @notice Check if the wallet is exited, regardless of whether it is finalized * * @param wallet The address of the wallet to check * * @return The exited status of wallet */ function isWalletExit(address wallet) public view returns (bool) { WalletExit storage exit = _walletExits[wallet]; return exit.exists; } /** * @notice Check if the wallet is exited and finalized * * @param wallet The address of the wallet to check * * @return The exited and finalized status of wallet */ function isWalletExitFinalized(address wallet) public view returns (bool) { WalletExit storage exit = _walletExits[wallet]; return exit.exists && exit.effectiveBlockNumber <= block.number; } // Trades // /** * @notice Execute multiple trades * * @param buys A `Structs.Order` struct array encoding the parameters of the buy-side orders (receiving base, giving quote) * @param sells A `Structs.Order` struct array encoding the parameters of the sell-side order (giving base, receiving quote) * @param trades A `Structs.Trade` struct array encoding the parameters of this trade execution of the counterparty orders */ function executeTrades( Structs.Order[] memory buys, Structs.Order[] memory sells, Structs.Trade[] memory trades ) external override onlyDispatcher { require( buys.length==sells.length && sells.length==trades.length, "length mismatch" ); require( buys.length!=0, "length is zero" ); // Structs.ExecRet[] memory rets = new Structs.ExecRet[](buys.length); // for (uint i = 0; i < buys.length; i++) { // (bool success, bytes memory data) = address(this).delegatecall( // abi.encodeWithSignature("executeTrade((uint8,uint128,address,uint8,uint8,uint64,bool,uint64,uint64,string,uint8,uint8,uint64,bytes),(uint8,uint128,address,uint8,uint8,uint64,bool,uint64,uint64,string,uint8,uint8,uint64,bytes),(string,string,address,address,uint64,uint64,uint64,uint64,address,address,uint64,uint64,uint64,uint8))", buys[i], sells[i], trades[i]) // ); // if (success) { // rets[i] = Structs.ExecRet(true, ""); // } else { // rets[i] = Structs.ExecRet(false, extractRevertReason(data)); // } // } // emit TradesExecuted(rets); for (uint256 i = 0; i < buys.length; i++) { // bytes32 orderHash = Signatures.getOrderWalletHash( // buys[i], // trades[i].baseAssetSymbol, // trades[i].quoteAssetSymbol // ); // console.log("contractHash:"); // console.logBytes32(orderHash); // bytes32 orderHash2 = Signatures.getOrderWalletHash( // sells[i], // trades[i].baseAssetSymbol, // trades[i].quoteAssetSymbol // ); // console.log("contractHash2:"); // console.logBytes32(orderHash2); _executeTrade(buys[i], sells[i], trades[i]); } } // function extractRevertReason (bytes memory revertData) internal pure returns (string memory reason) { // uint l = revertData.length; // if (l < 68) return "Silently reverted"; // uint t; // assembly { // revertData := add (revertData, 4) // t := mload (revertData) // Save the content of the length slot // mstore (revertData, sub (l, 4)) // Set proper length // } // reason = abi.decode (revertData, (string)); // assembly { // mstore (revertData, t) // Restore the content of the length slot // } // } /** * @notice Settles a trade between two orders submitted and matched off-chain * * @dev As a gas optimization, base and quote symbols are passed in separately and combined to verify * the wallet hash, since this is cheaper than splitting the market symbol into its two constituent asset symbols * @dev Stack level too deep if declared external * * @param buy A `Structs.Order` struct encoding the parameters of the buy-side order (receiving base, giving quote) * @param sell A `Structs.Order` struct encoding the parameters of the sell-side order (giving base, receiving quote) * @param trade A `Structs.Trade` struct encoding the parameters of this trade execution of the counterparty orders */ function executeTrade( Structs.Order memory buy, Structs.Order memory sell, Structs.Trade memory trade ) public override onlyDispatcher { _executeTrade(buy, sell, trade); } // Updates buyer, seller, and fee wallet balances for both assets in trade pair according to trade parameters function updateBalancesForTrade( Structs.Order memory buy, Structs.Order memory sell, Structs.Trade memory trade ) private { // Seller gives base asset including fees uint64 sellerBaseQuantity = _balancesInPips[sell.walletAddress].get(trade .baseAssetAddress) .sub(trade.grossBaseQuantityInPips); if (sellerBaseQuantity > 0) { _balancesInPips[sell.walletAddress].set(trade .baseAssetAddress, sellerBaseQuantity); } else { _balancesInPips[sell.walletAddress].remove(trade .baseAssetAddress); } // Buyer receives base asset minus fees uint64 buyerBaseQuantity = _balancesInPips[buy.walletAddress].get(trade .baseAssetAddress) .add(trade.netBaseQuantityInPips); if (buyerBaseQuantity > 0) { _balancesInPips[buy.walletAddress].set(trade .baseAssetAddress, buyerBaseQuantity); } else { _balancesInPips[buy.walletAddress].remove(trade .baseAssetAddress); } // Buyer gives quote asset including fees uint64 buyerQuoteQuantity = _balancesInPips[buy.walletAddress].get(trade .quoteAssetAddress) .sub(trade.grossQuoteQuantityInPips); if (buyerQuoteQuantity > 0) { _balancesInPips[buy.walletAddress].set(trade .quoteAssetAddress, buyerQuoteQuantity); } else { _balancesInPips[buy.walletAddress].remove(trade .quoteAssetAddress); } // Seller receives quote asset minus fees uint64 sellerQuoteQuantity = _balancesInPips[sell.walletAddress].get(trade .quoteAssetAddress) .add(trade.netQuoteQuantityInPips); if (sellerQuoteQuantity > 0) { _balancesInPips[sell.walletAddress].set(trade .quoteAssetAddress, sellerQuoteQuantity); } else { _balancesInPips[sell.walletAddress].remove(trade .quoteAssetAddress); } // Maker and taker fees to fee wallet uint64 makerFeeQuantity = _balancesInPips[_feeWallet].get(trade .makerFeeAssetAddress) .add(trade.makerFeeQuantityInPips); if (makerFeeQuantity > 0) { _balancesInPips[_feeWallet].set(trade .makerFeeAssetAddress, makerFeeQuantity); } else { _balancesInPips[_feeWallet].remove(trade .makerFeeAssetAddress); } uint64 takerFeeQuantity = _balancesInPips[_feeWallet].get(trade .takerFeeAssetAddress) .add(trade.takerFeeQuantityInPips); if (takerFeeQuantity > 0) { _balancesInPips[_feeWallet].set(trade .takerFeeAssetAddress, takerFeeQuantity); } else { _balancesInPips[_feeWallet].remove(trade .takerFeeAssetAddress); } } function updateOrderFilledQuantities( Structs.Order memory buyOrder, bytes32 buyOrderHash, Structs.Order memory sellOrder, bytes32 sellOrderHash, Structs.Trade memory trade ) private { updateOrderFilledQuantity(buyOrder, buyOrderHash, trade); updateOrderFilledQuantity(sellOrder, sellOrderHash, trade); } // Update filled quantities tracking for order to prevent over- or double-filling orders function updateOrderFilledQuantity( Structs.Order memory order, bytes32 orderHash, Structs.Trade memory trade ) private { require(!_completedOrderHashes[orderHash], 'Double filled'); // Total quantity of above filled as a result of all trade executions, including this one uint64 newFilledQuantityInPips; // Market orders can express quantity in quote terms, and can be partially filled by multiple // limit maker orders necessitating tracking partially filled amounts in quote terms to // determine completion if (order.isQuantityInQuote) { require( isMarketOrderType(order.orderType), 'Not market orders' ); newFilledQuantityInPips = trade.grossQuoteQuantityInPips.add( _partiallyFilledOrderQuantitiesInPips[orderHash] ); } else { // All other orders track partially filled quantities in base terms newFilledQuantityInPips = trade.grossBaseQuantityInPips.add( _partiallyFilledOrderQuantitiesInPips[orderHash] ); } require( newFilledQuantityInPips <= order.quantityInPips, 'Overfilled' ); if (newFilledQuantityInPips < order.quantityInPips) { // If the order was partially filled, track the new filled quantity _partiallyFilledOrderQuantitiesInPips[orderHash] = newFilledQuantityInPips; } else { // If the order was completed, delete any partial fill tracking and instead track its completion // to prevent future double fills delete _partiallyFilledOrderQuantitiesInPips[orderHash]; _completedOrderHashes[orderHash] = true; } } // Validations // function validateAssetPair( Structs.Order memory buy, Structs.Order memory sell, Structs.Trade memory trade ) private view { require( trade.baseAssetAddress != trade.quoteAssetAddress, 'Base/quote assets must be different' ); // Buy order market pair Structs.Asset memory buyBaseAsset = _assetRegistry.loadAssetBySymbol( trade.baseAssetSymbol, UUID.getTimestampInMsFromUuidV1(buy.nonce) ); Structs.Asset memory buyQuoteAsset = _assetRegistry.loadAssetBySymbol( trade.quoteAssetSymbol, UUID.getTimestampInMsFromUuidV1(buy.nonce) ); require( buyBaseAsset.assetAddress == trade.baseAssetAddress && buyQuoteAsset.assetAddress == trade.quoteAssetAddress, 'Buy order symbol mismatch' ); // Sell order market pair Structs.Asset memory sellBaseAsset = _assetRegistry.loadAssetBySymbol( trade.baseAssetSymbol, UUID.getTimestampInMsFromUuidV1(sell.nonce) ); Structs.Asset memory sellQuoteAsset = _assetRegistry.loadAssetBySymbol( trade.quoteAssetSymbol, UUID.getTimestampInMsFromUuidV1(sell.nonce) ); require( sellBaseAsset.assetAddress == trade.baseAssetAddress && sellQuoteAsset.assetAddress == trade.quoteAssetAddress, 'Sell order symbol mismatch' ); // Fee asset validation require( trade.makerFeeAssetAddress == trade.baseAssetAddress || trade.makerFeeAssetAddress == trade.quoteAssetAddress, 'Maker fee asset is not in trade pair' ); require( trade.takerFeeAssetAddress == trade.baseAssetAddress || trade.takerFeeAssetAddress == trade.quoteAssetAddress, 'Taker fee asset is not in trade pair' ); require( trade.makerFeeAssetAddress != trade.takerFeeAssetAddress, 'Maker/taker fee assets must be different' ); } function validateLimitPrices( Structs.Order memory buy, Structs.Order memory sell, Structs.Trade memory trade ) private pure { require( trade.grossBaseQuantityInPips > 0, 'Base quantity is 0' ); require( trade.grossQuoteQuantityInPips > 0, 'Quote quantity is 0' ); if (isLimitOrderType(buy.orderType)) { require( getImpliedQuoteQuantityInPips( trade.grossBaseQuantityInPips, buy.limitPriceInPips ) >= trade.grossQuoteQuantityInPips, 'Buy limit price exceeded' ); } if (isLimitOrderType(sell.orderType)) { require( getImpliedQuoteQuantityInPips( trade.grossBaseQuantityInPips, sell.limitPriceInPips ) <= trade.grossQuoteQuantityInPips, 'Sell limit price exceeded' ); } } function validateTradeFees(Structs.Trade memory trade) private pure { uint64 makerTotalQuantityInPips = trade.makerFeeAssetAddress == trade.baseAssetAddress ? trade.grossBaseQuantityInPips : trade.grossQuoteQuantityInPips; require( getFeeBasisPoints( trade.makerFeeQuantityInPips, makerTotalQuantityInPips ) <= _maxTradeFeeBasisPoints, 'Excessive maker fee' ); uint64 takerTotalQuantityInPips = trade.takerFeeAssetAddress == trade.baseAssetAddress ? trade.grossBaseQuantityInPips : trade.grossQuoteQuantityInPips; require( getFeeBasisPoints( trade.takerFeeQuantityInPips, takerTotalQuantityInPips ) <= _maxTradeFeeBasisPoints, 'Excessive taker fee' ); require( trade.netBaseQuantityInPips.add( trade.makerFeeAssetAddress == trade.baseAssetAddress ? trade.makerFeeQuantityInPips : trade.takerFeeQuantityInPips ) == trade.grossBaseQuantityInPips, 'Base plus fee is not equal to gross' ); require( trade.netQuoteQuantityInPips.add( trade.makerFeeAssetAddress == trade.quoteAssetAddress ? trade.makerFeeQuantityInPips : trade.takerFeeQuantityInPips ) == trade.grossQuoteQuantityInPips, 'Quote plus fee is not equal to gross' ); } function validateOrderSignatures( Structs.Order memory buy, Structs.Order memory sell, Structs.Trade memory trade ) private pure returns (bytes32, bytes32) { bytes32 buyOrderHash = validateOrderSignature(buy, trade); bytes32 sellOrderHash = validateOrderSignature(sell, trade); return (buyOrderHash, sellOrderHash); } function validateOrderSignature( Structs.Order memory order, Structs.Trade memory trade ) private pure returns (bytes32) { bytes32 orderHash = Signatures.getOrderWalletHash( order, trade.baseAssetSymbol, trade.quoteAssetSymbol ); require( Signatures.isSignatureValid( orderHash, order.walletSignature, order.walletAddress ), order.side == Enums.OrderSide.Buy ? 'Invalid buy order signature' : 'Invalid sell order signature' ); return orderHash; } function validateOrderNonces( Structs.Order memory buy, Structs.Order memory sell ) private view { require( UUID.getTimestampInMsFromUuidV1(buy.nonce) > getLastInvalidatedTimestamp(buy.walletAddress), 'Buy order nonce too low' ); require( UUID.getTimestampInMsFromUuidV1(sell.nonce) > getLastInvalidatedTimestamp(sell.walletAddress), 'Sell order nonce too low' ); } function validateWithdrawalSignature(Structs.Withdrawal memory withdrawal) private pure returns (bytes32) { bytes32 withdrawalHash = Signatures.getWithdrawalWalletHash(withdrawal); require( Signatures.isSignatureValid( withdrawalHash, withdrawal.walletSignature, withdrawal.walletAddress ), 'Invalid signature' ); return withdrawalHash; } // Asset registry // /** * @notice Initiate registration process for a token asset. Only `IBEP20` compliant tokens can be * added - BNB is hardcoded in the registry * * @param tokenAddress The address of the `IBEP20` compliant token contract to add * @param symbol The symbol identifying the token asset * @param decimals The decimal precision of the token */ function registerToken( IBEP20 tokenAddress, string calldata symbol, uint8 decimals ) external onlyAdmin { _assetRegistry.registerToken(tokenAddress, symbol, decimals); emit TokenRegistered(tokenAddress, symbol, decimals); } /** * @notice Finalize registration process for a token asset. All parameters must exactly match a previous * call to `registerToken` * * @param tokenAddress The address of the `IBEP20` compliant token contract to add * @param symbol The symbol identifying the token asset * @param decimals The decimal precision of the token */ function confirmTokenRegistration( IBEP20 tokenAddress, string calldata symbol, uint8 decimals ) external onlyAdmin { _assetRegistry.confirmTokenRegistration(tokenAddress, symbol, decimals); emit TokenRegistrationConfirmed(tokenAddress, symbol, decimals); } /** * @notice Add a symbol to a token that has already been registered and confirmed * * @param tokenAddress The address of the `IBEP20` compliant token contract the symbol will identify * @param symbol The symbol identifying the token asset */ function addTokenSymbol(IBEP20 tokenAddress, string calldata symbol) external onlyAdmin { _assetRegistry.addTokenSymbol(tokenAddress, symbol); emit TokenSymbolAdded(tokenAddress, symbol); } /** * @notice Loads an asset descriptor struct by its symbol and timestamp * * @dev Since multiple token addresses can potentially share the same symbol (in case of a token * swap/contract upgrade) the provided `timestampInMs` is compared against each asset's * `confirmedTimestampInMs` to uniquely determine the newest asset for the symbol at that point in time * * @param assetSymbol The asset's symbol * @param timestampInMs Point in time used to disambiguate multiple tokens with same symbol * * @return A `Structs.Asset` record describing the asset */ function loadAssetBySymbol(string calldata assetSymbol, uint64 timestampInMs) external view returns (Structs.Asset memory) { return _assetRegistry.loadAssetBySymbol(assetSymbol, timestampInMs); } // Dispatcher whitelisting // /** * @notice Sets the wallet whitelisted to dispatch transactions calling the `executeTrade` and `withdraw` functions * * @param newDispatcherWallet The new whitelisted dispatcher wallet. Must be different from the current one */ function addDispatcher(address newDispatcherWallet) external onlyAdmin { require(newDispatcherWallet != address(0x0), 'Invalid address'); require( !_dispatcherWallet[newDispatcherWallet], 'Same address' ); _dispatcherWallet[newDispatcherWallet] = true; emit AddDispatcher(newDispatcherWallet); } /** * @notice Clears the currently set whitelisted dispatcher wallet, effectively disabling calling the * `executeTrade` and `withdraw` functions until a new wallet is set with `setDispatcher` */ function removeDispatcher(address account) external onlyAdmin { emit RemoveDispatcher(account); _dispatcherWallet[account] = false; } function isDispatcher(address account) external view returns (bool) { return _dispatcherWallet[account]; } modifier onlyDispatcher() { require(_dispatcherWallet[msg.sender], 'Must be dispatcher'); _; } // Utils // function isLimitOrderType(Enums.OrderType orderType) private pure returns (bool) { return orderType == Enums.OrderType.Limit || orderType == Enums.OrderType.LimitMaker || orderType == Enums.OrderType.StopLossLimit || orderType == Enums.OrderType.TakeProfitLimit; } function isMarketOrderType(Enums.OrderType orderType) private pure returns (bool) { return orderType == Enums.OrderType.Market || orderType == Enums.OrderType.StopLoss || orderType == Enums.OrderType.TakeProfit; } function getCurrentTimestampInMs() private view returns (uint64) { uint64 msInOneSecond = 1000; return uint64(block.timestamp) * msInOneSecond; } function getFeeBasisPoints(uint64 fee, uint64 total) private pure returns (uint64) { uint64 basisPointsInTotal = 100 * 100; // 100 basis points/percent * 100 percent/total return fee.mul(basisPointsInTotal).div(total); } function getImpliedQuoteQuantityInPips( uint64 baseQuantityInPips, uint64 limitPriceInPips ) private pure returns (uint64) { // To convert a fractional price to integer pips, shift right by the pip precision of 8 decimals uint256 pipsMultiplier = 10**8; uint256 impliedQuoteQuantityInPips = uint256(baseQuantityInPips) .mul(uint256(limitPriceInPips)) .div(pipsMultiplier); require( impliedQuoteQuantityInPips < 2**64, 'Implied quantity overflows' ); return uint64(impliedQuoteQuantityInPips); } function getLastInvalidatedTimestamp(address walletAddress) private view returns (uint64) { if ( _nonceInvalidations[walletAddress].exists && _nonceInvalidations[walletAddress].effectiveBlockNumber <= block.number ) { return _nonceInvalidations[walletAddress].timestampInMs; } return 0; } function getOneDayFromNowInMs() private view returns (uint64) { uint64 secondsInOneDay = 24 * 60 * 60; // 24 hours/day * 60 min/hour * 60 seconds/min uint64 msInOneSecond = 1000; return (uint64(block.timestamp) + secondsInOneDay) * msInOneSecond; } function _executeTrade( Structs.Order memory buy, Structs.Order memory sell, Structs.Trade memory trade ) private { require( !isWalletExitFinalized(buy.walletAddress), 'Buy wallet finalized' ); require( !isWalletExitFinalized(sell.walletAddress), 'Sell wallet finalized' ); require( buy.walletAddress != sell.walletAddress, 'Self-trading not allowed' ); validateAssetPair(buy, sell, trade); validateLimitPrices(buy, sell, trade); validateOrderNonces(buy, sell); (bytes32 buyHash, bytes32 sellHash) = validateOrderSignatures( buy, sell, trade ); validateTradeFees(trade); updateOrderFilledQuantities(buy, buyHash, sell, sellHash, trade); updateBalancesForTrade(buy, sell, trade); emit TradeExecuted( buy.walletAddress, sell.walletAddress, trade.baseAssetSymbol, trade.quoteAssetSymbol, trade.baseAssetSymbol, trade.quoteAssetSymbol, trade.grossBaseQuantityInPips, trade.grossQuoteQuantityInPips, trade.priceInPips, buyHash, sellHash ); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; assembly { size := extcodesize(account) } return size > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // CAUTION // This version of SafeMath should only be used with Solidity 0.8 or later, // because it relies on the compiler's built in overflow checks. /** * @dev Wrappers over Solidity's arithmetic operations. * * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler * now has built in overflow checking. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the substraction of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. * * _Available since v3.4._ */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. * * _Available since v3.4._ */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return a - b; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { return a * b; } /** * @dev Returns the integer division of two unsigned integers, reverting on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return a / b; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return a % b; } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {trySub}. * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub( uint256 a, uint256 b, string memory errorMessage ) internal pure returns (uint256) { unchecked { require(b <= a, errorMessage); return a - b; } } /** * @dev Returns the integer division of two unsigned integers, reverting with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div( uint256 a, uint256 b, string memory errorMessage ) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a / b; } } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * reverting with custom message when dividing by zero. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryMod}. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod( uint256 a, uint256 b, string memory errorMessage ) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a % b; } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; import { Address } from './Address.sol'; import { IBEP20, Structs } from './Interfaces.sol'; /** * @notice Library helper functions for managing a registry of asset descriptors indexed by address and symbol */ library AssetRegistry { struct Storage { mapping(address => Structs.Asset) assetsByAddress; // Mapping value is array since the same symbol can be re-used for a different address // (usually as a result of a token swap or upgrade) mapping(string => Structs.Asset[]) assetsBySymbol; } function registerToken( Storage storage self, IBEP20 tokenAddress, string memory symbol, uint8 decimals ) external { require(decimals <= 32, 'More than 32 decimals'); require( tokenAddress != IBEP20(address(0x0)) && Address.isContract(address(tokenAddress)), 'Invalid address' ); // The string type does not have a length property so cast to bytes to check for empty string require(bytes(symbol).length > 0, 'Invalid symbol'); require( !self.assetsByAddress[address(tokenAddress)].isConfirmed, 'Aready finalized' ); self.assetsByAddress[address(tokenAddress)] = Structs.Asset({ exists: true, assetAddress: address(tokenAddress), symbol: symbol, decimals: decimals, isConfirmed: false, confirmedTimestampInMs: 0 }); } function confirmTokenRegistration( Storage storage self, IBEP20 tokenAddress, string memory symbol, uint8 decimals ) external { Structs.Asset memory asset = self.assetsByAddress[address(tokenAddress)]; require(asset.exists, 'Unknown token'); require(!asset.isConfirmed, 'Already finalized'); require(isStringEqual(asset.symbol, symbol), 'Symbols do not match'); require(asset.decimals == decimals, 'Decimals do not match'); asset.isConfirmed = true; asset.confirmedTimestampInMs = uint64(block.timestamp * 1000); // Block timestamp is in seconds, store ms self.assetsByAddress[address(tokenAddress)] = asset; self.assetsBySymbol[symbol].push(asset); } function addTokenSymbol( Storage storage self, IBEP20 tokenAddress, string memory symbol ) external { Structs.Asset memory asset = self.assetsByAddress[address(tokenAddress)]; require( asset.exists && asset.isConfirmed, 'Registration not finalized' ); require(!isStringEqual(symbol, 'BNB'), 'Reserved symbol'); // This will prevent swapping assets for previously existing orders uint64 msInOneSecond = 1000; asset.confirmedTimestampInMs = uint64(block.timestamp * msInOneSecond); self.assetsBySymbol[symbol].push(asset); } /** * @dev Resolves an asset address into corresponding Asset struct * * @param assetAddress BNB address of asset */ function loadAssetByAddress(Storage storage self, address assetAddress) external view returns (Structs.Asset memory) { if (assetAddress == address(0x0)) { return getBnbAsset(); } Structs.Asset memory asset = self.assetsByAddress[assetAddress]; require( asset.exists && asset.isConfirmed, 'No confirmed asset found' ); return asset; } /** * @dev Resolves a asset symbol into corresponding Asset struct * * @param symbol Asset symbol, e.g. 'IDEX' * @param timestampInMs Milliseconds since Unix epoch, usually parsed from a UUID v1 order nonce. * Constrains symbol resolution to the asset most recently confirmed prior to timestampInMs. Reverts * if no such asset exists */ function loadAssetBySymbol( Storage storage self, string memory symbol, uint64 timestampInMs ) external view returns (Structs.Asset memory) { if (isStringEqual('BNB', symbol)) { return getBnbAsset(); } Structs.Asset memory asset; if (self.assetsBySymbol[symbol].length > 0) { for (uint8 i = 0; i < self.assetsBySymbol[symbol].length; i++) { if ( self.assetsBySymbol[symbol][i].confirmedTimestampInMs <= timestampInMs ) { asset = self.assetsBySymbol[symbol][i]; } } } require( asset.exists && asset.isConfirmed, 'No confirmed asset found' ); return asset; } /** * @dev BNB is modeled as an always-confirmed Asset struct for programmatic consistency */ function getBnbAsset() private pure returns (Structs.Asset memory) { return Structs.Asset(true, address(0x0), 'BNB', 18, true, 0); } // See https://solidity.readthedocs.io/en/latest/types.html#bytes-and-strings-as-arrays function isStringEqual(string memory a, string memory b) private pure returns (bool) { return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; import { SafeMath as SafeMath256 } from './SafeMath.sol'; import { IBEP20 } from './Interfaces.sol'; /** * @notice This library provides helper utilities for transfering assets in and out of contracts. * It further validates ERC-20 compliant balance updates in the case of token assets */ library AssetTransfers { using SafeMath256 for uint256; /** * @dev Transfers tokens from a wallet into a contract during deposits. `wallet` must already * have called `approve` on the token contract for at least `tokenQuantity`. Note this only * applies to tokens since BNB is sent in the deposit transaction via `msg.value` */ function transferFrom( address wallet, IBEP20 tokenAddress, uint256 quantityInAssetUnits ) external { uint256 balanceBefore = tokenAddress.balanceOf(address(this)); // Because we check for the expected balance change we can safely ignore the return value of transferFrom tokenAddress.transferFrom(wallet, address(this), quantityInAssetUnits); uint256 balanceAfter = tokenAddress.balanceOf(address(this)); require( balanceAfter.sub(balanceBefore) == quantityInAssetUnits, 'balance do not match' ); } /** * @dev Transfers BNB or token assets from a contract to 1) another contract, when `Exchange` * forwards funds to `Custodian` during deposit or 2) a wallet, when withdrawing */ function transferTo( address payable walletOrContract, address asset, uint256 quantityInAssetUnits ) external { if (asset == address(0x0)) { require( walletOrContract.send(quantityInAssetUnits), 'BNB transfer failed' ); } else { uint256 balanceBefore = IBEP20(asset).balanceOf(walletOrContract); // Because we check for the expected balance change we can safely ignore the return value of transfer IBEP20(asset).transfer(walletOrContract, quantityInAssetUnits); uint256 balanceAfter = IBEP20(asset).balanceOf(walletOrContract); require( balanceAfter.sub(balanceBefore) == quantityInAssetUnits, 'balance do not match' ); } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; import { SafeMath as SafeMath256 } from './SafeMath.sol'; /** * @notice Library helpers for converting asset quantities between asset units and pips */ library AssetUnitConversions { using SafeMath256 for uint256; function pipsToAssetUnits(uint64 quantityInPips, uint8 assetDecimals) internal pure returns (uint256) { require(assetDecimals <= 32, 'More than 32 decimals'); // Exponents cannot be negative, so divide or multiply based on exponent signedness if (assetDecimals > 8) { return uint256(quantityInPips).mul(uint256(10)**(assetDecimals - 8)); } return uint256(quantityInPips).div(uint256(10)**(8 - assetDecimals)); } function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals) internal pure returns (uint64) { require(assetDecimals <= 32, 'More than 32 decimals'); uint256 quantityInPips; // Exponents cannot be negative, so divide or multiply based on exponent signedness if (assetDecimals > 8) { quantityInPips = quantityInAssetUnits.div( uint256(10)**(assetDecimals - 8) ); } else { quantityInPips = quantityInAssetUnits.mul( uint256(10)**(8 - assetDecimals) ); } require(quantityInPips < 2**64, 'Pip quantity overflows'); return uint64(quantityInPips); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; /** * @notice Mixin that provide separate owner and admin roles for RBAC */ abstract contract Owned { address immutable _owner; address _admin; modifier onlyOwner { require(msg.sender == _owner, 'Caller must be owner'); _; } modifier onlyAdmin { require(msg.sender == _admin, 'Caller must be admin'); _; } /** * @notice Sets both the owner and admin roles to the contract creator */ constructor() { _owner = msg.sender; _admin = msg.sender; } /** * @notice Sets a new whitelisted admin wallet * * @param newAdmin The new whitelisted admin wallet. Must be different from the current one */ function setAdmin(address newAdmin) external onlyOwner { require(newAdmin != address(0x0), 'Invalid wallet address'); require(newAdmin != _admin, 'Must be different from current admin'); _admin = newAdmin; } /** * @notice Clears the currently whitelisted admin wallet, effectively disabling any functions requiring * the admin role */ function removeAdmin() external onlyOwner { _admin = address(0x0); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath64 { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint64 a, uint64 b) internal pure returns (uint64) { uint64 c = a + b; require(c >= a, 'SafeMath: addition overflow'); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint64 a, uint64 b) internal pure returns (uint64) { return sub(a, b, 'SafeMath: subtraction overflow'); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. * * _Available since v2.4.0._ */ function sub( uint64 a, uint64 b, string memory errorMessage ) internal pure returns (uint64) { require(b <= a, errorMessage); uint64 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint64 a, uint64 b) internal pure returns (uint64) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint64 c = a * b; require(c / a == b, 'SafeMath: multiplication overflow'); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint64 a, uint64 b) internal pure returns (uint64) { return div(a, b, 'SafeMath: division by zero'); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. * * _Available since v2.4.0._ */ function div( uint64 a, uint64 b, string memory errorMessage ) internal pure returns (uint64) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint64 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; import { ECDSA } from './ECDSA.sol'; import { Enums, Structs } from './Interfaces.sol'; /** * Library helpers for building hashes and verifying wallet signatures on `Order` and `Withdrawal` structs */ library Signatures { function isSignatureValid( bytes32 hash, bytes memory signature, address signer ) external pure returns (bool) { return ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) == signer; } function getOrderWalletHash( Structs.Order memory order, string memory baseSymbol, string memory quoteSymbol ) external pure returns (bytes32) { require( order.signatureHashVersion == 1, 'Version must be 1' ); return keccak256( // Placing all the fields in a single `abi.encodePacked` call causes a `stack too deep` error abi.encodePacked( abi.encodePacked( order.signatureHashVersion, order.nonce, order.walletAddress, getMarketSymbol(baseSymbol, quoteSymbol), uint8(order.orderType), uint8(order.side), // Ledger qtys and prices are in pip, but order was signed by wallet owner with decimal values pipToDecimal(order.quantityInPips) ), abi.encodePacked( order.isQuantityInQuote, order.limitPriceInPips > 0 ? pipToDecimal(order.limitPriceInPips) : '', order.stopPriceInPips > 0 ? pipToDecimal(order.stopPriceInPips) : '', order.clientOrderId, uint8(order.timeInForce), uint8(order.selfTradePrevention), order.cancelAfter ) ) ); } function getWithdrawalWalletHash(Structs.Withdrawal memory withdrawal) external pure returns (bytes32) { return keccak256( abi.encodePacked( withdrawal.nonce, withdrawal.walletAddress, // Ternary branches must resolve to the same type, so wrap in idempotent encodePacked withdrawal.withdrawalType == Enums.WithdrawalType.BySymbol ? abi.encodePacked(withdrawal.assetSymbol) : abi.encodePacked(withdrawal.assetAddress), pipToDecimal(withdrawal.quantityInPips), withdrawal.autoDispatchEnabled ) ); } /** * @dev Combines base and quote asset symbols into the market symbol originally signed by the * wallet. For example if base is 'IDEX' and quote is 'ETH', the resulting market symbol is * 'IDEX-ETH'. This approach is used rather than passing in the market symbol and splitting it * since the latter incurs a higher gas cost */ function getMarketSymbol(string memory baseSymbol, string memory quoteSymbol) private pure returns (string memory) { bytes memory baseSymbolBytes = bytes(baseSymbol); bytes memory hyphenBytes = bytes('-'); bytes memory quoteSymbolBytes = bytes(quoteSymbol); bytes memory marketSymbolBytes = bytes( new string( baseSymbolBytes.length + quoteSymbolBytes.length + hyphenBytes.length ) ); uint256 i; uint256 j; for (i = 0; i < baseSymbolBytes.length; i++) { marketSymbolBytes[j++] = baseSymbolBytes[i]; } // Hyphen is one byte marketSymbolBytes[j++] = hyphenBytes[0]; for (i = 0; i < quoteSymbolBytes.length; i++) { marketSymbolBytes[j++] = quoteSymbolBytes[i]; } return string(marketSymbolBytes); } /** * @dev Converts an integer pip quantity back into the fixed-precision decimal pip string * originally signed by the wallet. For example, 1234567890 becomes '12.34567890' */ function pipToDecimal(uint256 pips) private pure returns (string memory) { // Inspired by https://github.com/provable-things/ethereum-api/blob/831f4123816f7a3e57ebea171a3cdcf3b528e475/oraclizeAPI_0.5.sol#L1045-L1062 uint256 copy = pips; uint256 length; while (copy != 0) { length++; copy /= 10; } if (length < 9) { length = 9; // a zero before the decimal point plus 8 decimals } length++; // for the decimal point bytes memory decimal = new bytes(length); for (uint256 i = length; i > 0; i--) { if (length - i == 8) { decimal[i - 1] = bytes1(uint8(46)); // period } else { decimal[i - 1] = bytes1(uint8(48 + (pips % 10))); pips /= 10; } } return string(decimal); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; /** * @notice Enums used in `Order` and `Withdrawal` structs */ contract Enums { enum OrderSelfTradePrevention { // Decrement and cancel dc, // Cancel oldest co, // Cancel newest cn, // Cancel both cb } enum OrderSide { Buy, Sell } enum OrderTimeInForce { // Good until cancelled gtc, // Good until time gtt, // Immediate or cancel ioc, // Fill or kill fok } enum OrderType { Market, Limit, LimitMaker, StopLoss, StopLossLimit, TakeProfit, TakeProfitLimit } enum WithdrawalType { BySymbol, ByAddress } } /** * @notice Struct definitions */ contract Structs { /** * @notice Argument type for `Exchange.executeTrade` and `Signatures.getOrderWalletHash` */ struct Order { // Not currently used but reserved for future use. Must be 1 uint8 signatureHashVersion; // UUIDv1 unique to wallet uint128 nonce; // Wallet address that placed order and signed hash address walletAddress; // Type of order Enums.OrderType orderType; // Order side wallet is on Enums.OrderSide side; // Order quantity in base or quote asset terms depending on isQuantityInQuote flag uint64 quantityInPips; // Is quantityInPips in quote terms bool isQuantityInQuote; // For limit orders, price in decimal pips * 10^8 in quote terms uint64 limitPriceInPips; // For stop orders, stop loss or take profit price in decimal pips * 10^8 in quote terms uint64 stopPriceInPips; // Optional custom client order ID string clientOrderId; // TIF option specified by wallet for order Enums.OrderTimeInForce timeInForce; // STP behavior specified by wallet for order Enums.OrderSelfTradePrevention selfTradePrevention; // Cancellation time specified by wallet for GTT TIF order uint64 cancelAfter; // The ECDSA signature of the order hash as produced by Signatures.getOrderWalletHash bytes walletSignature; } /** * @notice Return type for `Exchange.loadAssetBySymbol`, and `Exchange.loadAssetByAddress`; also * used internally by `AssetRegistry` */ struct Asset { // Flag to distinguish from empty struct bool exists; // The asset's address address assetAddress; // The asset's symbol string symbol; // The asset's decimal precision uint8 decimals; // Flag set when asset registration confirmed. Asset deposits, trades, or withdrawals only allowed if true bool isConfirmed; // Timestamp as ms since Unix epoch when isConfirmed was asserted uint64 confirmedTimestampInMs; } /** * @notice Argument type for `Exchange.executeTrade` specifying execution parameters for matching orders */ struct Trade { // Base asset symbol string baseAssetSymbol; // Quote asset symbol string quoteAssetSymbol; // Base asset address address baseAssetAddress; // Quote asset address address quoteAssetAddress; // Gross amount including fees of base asset executed uint64 grossBaseQuantityInPips; // Gross amount including fees of quote asset executed uint64 grossQuoteQuantityInPips; // Net amount of base asset received by buy side wallet after fees uint64 netBaseQuantityInPips; // Net amount of quote asset received by sell side wallet after fees uint64 netQuoteQuantityInPips; // Asset address for liquidity maker's fee address makerFeeAssetAddress; // Asset address for liquidity taker's fee address takerFeeAssetAddress; // Fee paid by liquidity maker uint64 makerFeeQuantityInPips; // Fee paid by liquidity taker uint64 takerFeeQuantityInPips; // Execution price of trade in decimal pips * 10^8 in quote terms uint64 priceInPips; // Which side of the order (buy or sell) the liquidity maker was on Enums.OrderSide makerSide; } /** * @notice Argument type for `Exchange.withdraw` and `Signatures.getWithdrawalWalletHash` */ struct Withdrawal { // Distinguishes between withdrawals by asset symbol or address Enums.WithdrawalType withdrawalType; // UUIDv1 unique to wallet uint128 nonce; // Address of wallet to which funds will be returned address payable walletAddress; // Asset symbol string assetSymbol; // Asset address address assetAddress; // Used when assetSymbol not specified // Withdrawal quantity uint64 quantityInPips; // Gas fee deducted from withdrawn quantity to cover dispatcher tx costs uint64 gasFeeInPips; // Not currently used but reserved for future use. Must be true bool autoDispatchEnabled; // The ECDSA signature of the withdrawal hash as produced by Signatures.getWithdrawalWalletHash bytes walletSignature; } /** * @notice return type for `Exchange.executeTrades` */ struct ExecRet { bool success; string err; } } /** * @notice Interface of the BEP-20, but with no return values for transfer and transferFrom. * By asserting expected balance changes when calling these two methods we can safely ignore * their return values. This allows support of non-compliant tokens that do not return a boolean. * See https://github.com/ethereum/solidity/issues/4116 */ interface IBEP20 { /** * @notice Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @notice Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @notice Moves `amount` tokens from the caller's account to `recipient`. * * Most implementing contracts return a boolean value indicating whether the operation succeeded, but * we ignore this and rely on asserting balance changes instead * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external; /** * @notice Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @notice Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @notice Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Most implementing contracts return a boolean value indicating whether the operation succeeded, but * we ignore this and rely on asserting balance changes instead * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external; /** * @notice Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @notice Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } /** * @notice Interface to Custodian contract. Used by Exchange and Governance contracts for internal * delegate calls */ interface ICustodian { /** * @notice BNB can only be sent by the Exchange */ receive() external payable; /** * @notice Withdraw any asset and amount to a target wallet * * @dev No balance checking performed * * @param wallet The wallet to which assets will be returned * @param asset The address of the asset to withdraw (BNB or BEP20 contract) * @param quantityInAssetUnits The quantity in asset units to withdraw */ function withdraw( address payable wallet, address asset, uint256 quantityInAssetUnits ) external; /** * @notice Load address of the currently whitelisted Exchange contract * * @return The address of the currently whitelisted Exchange contract */ function loadExchange() external view returns (address); /** * @notice Sets a new Exchange contract address * * @param newExchange The address of the new whitelisted Exchange contract */ function setExchange(address newExchange) external; } /** * @notice Interface to Exchange contract. Provided only to document struct usage */ interface IExchange { /** * @notice Settles a trade between two orders submitted and matched off-chain * * @param buy A `Structs.Order` struct encoding the parameters of the buy-side order (receiving base, giving quote) * @param sell A `Structs.Order` struct encoding the parameters of the sell-side order (giving base, receiving quote) * @param trade A `Structs.Trade` struct encoding the parameters of this trade execution of the counterparty orders */ function executeTrade( Structs.Order calldata buy, Structs.Order calldata sell, Structs.Trade calldata trade ) external; /** * @notice Execute multiple trades * * @param buy A `Structs.Order` struct array encoding the parameters of the buy-side orders (receiving base, giving quote) * @param sell A `Structs.Order` struct array encoding the parameters of the sell-side order (giving base, receiving quote) * @param trade A `Structs.Trade` struct array encoding the parameters of this trade execution of the counterparty orders */ function executeTrades( Structs.Order[] memory buy, Structs.Order[] memory sell, Structs.Trade[] memory trade ) external; /** * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently whitelisted Dispatcher wallet * * @param withdrawal A `Structs.Withdrawal` struct encoding the parameters of the withdrawal */ function withdraw(Structs.Withdrawal calldata withdrawal) external; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; import { SafeMath64 } from './SafeMath64.sol'; /** * Library helper for extracting timestamp component of Version 1 UUIDs */ library UUID { using SafeMath64 for uint64; /** * Extracts the timestamp component of a Version 1 UUID. Used to make time-based assertions * against a wallet-privided nonce */ function getTimestampInMsFromUuidV1(uint128 uuid) internal pure returns (uint64 msSinceUnixEpoch) { // https://tools.ietf.org/html/rfc4122#section-4.1.2 uint128 version = (uuid >> 76) & 0x0000000000000000000000000000000F; require(version == 1, 'Must be v1 UUID'); // Time components are in reverse order so shift+mask each to reassemble uint128 timeHigh = (uuid >> 16) & 0x00000000000000000FFF000000000000; uint128 timeMid = (uuid >> 48) & 0x00000000000000000000FFFF00000000; uint128 timeLow = (uuid >> 96) & 0x000000000000000000000000FFFFFFFF; uint128 nsSinceGregorianEpoch = (timeHigh | timeMid | timeLow); // Gregorian offset given in seconds by https://www.wolframalpha.com/input/?i=convert+1582-10-15+UTC+to+unix+time msSinceUnixEpoch = uint64(nsSinceGregorianEpoch / 10000).sub( 12219292800000 ); return msSinceUnixEpoch; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.0; library IterableMapping { // Iterable mapping from address to uint64; struct Map { address[] keys; mapping(address => uint64) values; mapping(address => uint) indexOf; mapping(address => bool) inserted; } function get(Map storage map, address key) internal view returns (uint64) { return map.values[key]; } function getKeyAtIndex(Map storage map, uint index) internal view returns (address) { return map.keys[index]; } function size(Map storage map) internal view returns (uint) { return map.keys.length; } function set( Map storage map, address key, uint64 val ) internal { if (map.inserted[key]) { map.values[key] = val; } else { map.inserted[key] = true; map.values[key] = val; map.indexOf[key] = map.keys.length; map.keys.push(key); } } function remove(Map storage map, address key) internal { if (!map.inserted[key]) { return; } delete map.inserted[key]; delete map.values[key]; uint index = map.indexOf[key]; uint lastIndex = map.keys.length - 1; address lastKey = map.keys[lastIndex]; map.indexOf[lastKey] = index; delete map.indexOf[key]; map.keys[index] = lastKey; map.keys.pop(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS, InvalidSignatureV } function _throwError(RecoverError error) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert("ECDSA: invalid signature"); } else if (error == RecoverError.InvalidSignatureLength) { revert("ECDSA: invalid signature length"); } else if (error == RecoverError.InvalidSignatureS) { revert("ECDSA: invalid signature 's' value"); } else if (error == RecoverError.InvalidSignatureV) { revert("ECDSA: invalid signature 'v' value"); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature` or error string. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] * * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { // Check the signature length // - case 65: r,s,v signature (standard) // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else if (signature.length == 64) { bytes32 r; bytes32 vs; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. assembly { r := mload(add(signature, 0x20)) vs := mload(add(signature, 0x40)) } return tryRecover(hash, r, vs); } else { return (address(0), RecoverError.InvalidSignatureLength); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, signature); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] * * _Available since v4.3._ */ function tryRecover( bytes32 hash, bytes32 r, bytes32 vs ) internal pure returns (address, RecoverError) { bytes32 s; uint8 v; assembly { s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) v := add(shr(255, vs), 27) } return tryRecover(hash, v, r, s); } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. * * _Available since v4.2._ */ function recover( bytes32 hash, bytes32 r, bytes32 vs ) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. * * _Available since v4.3._ */ function tryRecover( bytes32 hash, uint8 v, bytes32 r, bytes32 s ) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS); } if (v != 27 && v != 28) { return (address(0), RecoverError.InvalidSignatureV); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature); } return (signer, RecoverError.NoError); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover( bytes32 hash, uint8 v, bytes32 r, bytes32 s ) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; } /** * @dev Returns an Ethereum Signed Message, created from a `hash`. This * produces hash corresponding to the one signed with the * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * JSON-RPC method as part of EIP-191. * * See {recover}. */ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { // 32 is the length in bytes of hash, // enforced by the type signature above return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); } /** * @dev Returns an Ethereum Signed Typed Data, created from a * `domainSeparator` and a `structHash`. This produces hash corresponding * to the one signed with the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] * JSON-RPC method as part of EIP-712. * * See {recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); } }
{ "optimizer": { "enabled": true, "runs": 13 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": { "contracts/libraries/AssetRegistry.sol": { "AssetRegistry": "0x2ae371fbb3d4a45ce174b5ca559974928133bd4c" }, "contracts/libraries/AssetTransfers.sol": { "AssetTransfers": "0x1af7b032e507fc235be7d3d5fe56f39ccb7dceca" }, "contracts/libraries/Signatures.sol": { "Signatures": "0xcb85f6b03ce6238786aeebca4581dd167fa6a110" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"AddDispatcher","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ChainPropagationPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"index","type":"uint64"},{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":true,"internalType":"string","name":"assetSymbolIndex","type":"string"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"FeeWalletChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint128","name":"nonce","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"timestampInMs","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"OrderNonceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"value","type":"address"}],"name":"RemoveDispatcher","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IBEP20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IBEP20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"TokenRegistrationConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IBEP20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"}],"name":"TokenSymbolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":true,"internalType":"string","name":"baseAssetSymbolIndex","type":"string"},{"indexed":true,"internalType":"string","name":"quoteAssetSymbolIndex","type":"string"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"tradePriceInPips","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"buyOrderHash","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"sellOrderHash","type":"bytes32"}],"name":"TradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"WalletExitCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"WalletExitWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"WalletExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"newDispatcherWallet","type":"address"}],"name":"addDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"}],"name":"addTokenSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"confirmTokenRegistration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositBNB","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenByAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenBySymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order","name":"buy","type":"tuple"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order","name":"sell","type":"tuple"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum Enums.OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct Structs.Trade","name":"trade","type":"tuple"}],"name":"executeTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order[]","name":"buys","type":"tuple[]"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order[]","name":"sells","type":"tuple[]"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum Enums.OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct Structs.Trade[]","name":"trades","type":"tuple[]"}],"name":"executeTrades","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"nonce","type":"uint128"}],"name":"invalidateOrderNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isDispatcher","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"isWalletExit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"isWalletExitFinalized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint64","name":"timestampInMs","type":"uint64"}],"name":"loadAssetBySymbol","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"bool","name":"isConfirmed","type":"bool"},{"internalType":"uint64","name":"confirmedTimestampInMs","type":"uint64"}],"internalType":"struct Structs.Asset","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadBalanceAssetAddress","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInAssetUnitsByAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInAssetUnitsBySymbol","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInPipsByAddress","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInPipsBySymbol","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadFeeWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"loadPartiallyFilledOrderQuantityInPips","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"removeDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newChainPropagationPeriod","type":"uint256"}],"name":"setChainPropagationPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newCustodian","type":"address"}],"name":"setCustodian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeWallet","type":"address"}],"name":"setFeeWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum Enums.WithdrawalType","name":"withdrawalType","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address payable","name":"walletAddress","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"uint64","name":"gasFeeInPips","type":"uint64"},{"internalType":"bool","name":"autoDispatchEnabled","type":"bool"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Withdrawal","name":"withdrawal","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawAllExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"assetAddress","type":"address"}],"name":"withdrawExit","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Deployed Bytecode
0x6080604052600436106101735760003560e01c80630226b70e1461017857806302ca60021461019a57806313cfda2c146101c95780631abb5832146101f7578063203624a41461021757806320e6a8e314610237578063403f373114610257578063419681821461027757806342220f3414610297578063457aa3c61461029f5780634a284ef9146102bf57806365fce5b7146102d45780636bb509f2146102f4578063704b6c021461031457806372e8f08d1461033457806375348d351461035457806380d0dee71461037457806384ae6c6c146103a4578063869af212146103c457806390d49b9d146103f157806391ec8c8b14610411578063985c4af51461043e5780639a202d471461045e578063ae0e969e14610473578063d257fee9146104c1578063d7a6aec7146104fa578063dbb365351461051a578063dcc634901461053a578063dfa518701461055a578063eb5068f214610593578063ef3b9d4a146105a8578063f34e2ef1146105c8575b600080fd5b34801561018457600080fd5b50610198610193366004614c66565b6105dd565b005b3480156101a657600080fd5b50600c546001600160a01b03166040516101c091906150b7565b60405180910390f35b3480156101d557600080fd5b506101e96101e4366004614a9c565b6106cb565b6040519081526020016101c0565b34801561020357600080fd5b506101e9610212366004614ad4565b6107c1565b34801561022357600080fd5b50610198610232366004614a80565b6108bc565b34801561024357600080fd5b50610198610252366004614c36565b61093e565b34801561026357600080fd5b50610198610272366004614a80565b6109f5565b34801561028357600080fd5b50610198610292366004614cf6565b610aa6565b610198610b7e565b3480156102ab57600080fd5b506101986102ba366004614c66565b610bd5565b3480156102cb57600080fd5b50610198610cac565b3480156102e057600080fd5b506101986102ef366004614e54565b610d44565b34801561030057600080fd5b5061019861030f366004614ed7565b610d83565b34801561032057600080fd5b5061019861032f366004614a80565b611265565b34801561034057600080fd5b5061019861034f366004614ad4565b611388565b34801561036057600080fd5b5061019861036f366004614a80565b611468565b34801561038057600080fd5b5061039461038f366004614a80565b61156e565b60405190151581526020016101c0565b3480156103b057600080fd5b506101986103bf366004614b26565b6115a2565b3480156103d057600080fd5b506103e46103df366004614d3f565b6116fe565b6040516101c0919061539f565b3480156103fd57600080fd5b5061019861040c366004614a80565b6117c6565b34801561041d57600080fd5b5061043161042c366004614a80565b6118c0565b6040516101c09190615168565b34801561044a57600080fd5b50610198610459366004614a80565b6119c8565b34801561046a57600080fd5b50610198611bb7565b34801561047f57600080fd5b506104a961048e366004614c36565b6000908152600860205260409020546001600160401b031690565b6040516001600160401b0390911681526020016101c0565b3480156104cd57600080fd5b506103946104dc366004614a80565b6001600160a01b03166000908152600b602052604090205460ff1690565b34801561050657600080fd5b50610198610515366004614fde565b611c11565b34801561052657600080fd5b506104a9610535366004614a9c565b611e17565b34801561054657600080fd5b50610198610555366004614ccb565b611e61565b34801561056657600080fd5b50610394610575366004614a80565b6001600160a01b031660009081526009602052604090205460ff1690565b34801561059f57600080fd5b50610198611e96565b3480156105b457600080fd5b506104a96105c3366004614ad4565b611f58565b3480156105d457600080fd5b50610198612047565b6000546001600160a01b031633146106105760405162461bcd60e51b81526004016106079061531b565b60405180910390fd5b60405163f282a4f560e01b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c9063f282a4f5906106509060019088908890889088906004016155c3565b60006040518083038186803b15801561066857600080fd5b505af415801561067c573d6000803e3d6000fd5b50505050836001600160a01b03167fa8968236dc4afb2c189afeb69c91b02445e6bd09f0ffe27256b62838edc99edb8484846040516106bd939291906151fa565b60405180910390a250505050565b60006001600160a01b0383166106f35760405162461bcd60e51b81526004016106079061526a565b604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e590610730906001908790600401615581565b60006040518083038186803b15801561074857600080fd5b505af415801561075c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526107849190810190614d93565b6001600160a01b03851660009081526006602052604090209091506107b7906107ad9085612330565b8260600151612358565b9150505b92915050565b60006001600160a01b0384166107e95760405162461bcd60e51b81526004016106079061526a565b6000732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b600186866108116123e3565b6040518563ffffffff1660e01b81526004016108309493929190615603565b60006040518083038186803b15801561084857600080fd5b505af415801561085c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108849190810190614d93565b6020808201516001600160a01b0388166000908152600690925260409091209192506108b3916107ad91612330565b95945050505050565b6000546001600160a01b031633146108e65760405162461bcd60e51b81526004016106079061531b565b7f223ed04a545ed5a1e24b45a98f0eb4677763a92dac1ba71c4ec5f227422c49968160405161091591906150b7565b60405180910390a16001600160a01b03166000908152600b60205260409020805460ff19169055565b6000546001600160a01b031633146109685760405162461bcd60e51b81526004016106079061531b565b6203138081106109af5760405162461bcd60e51b8152602060048201526012602482015271426967676572207468616e2031207765656b60701b6044820152606401610607565b600a80549082905560408051828152602081018490527f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f91015b60405180910390a15050565b6000546001600160a01b03163314610a1f5760405162461bcd60e51b81526004016106079061531b565b6005546001600160a01b031615610a665760405162461bcd60e51b815260206004820152600b60248201526a185b1c9958591e481cd95d60aa1b6044820152606401610607565b803b610a845760405162461bcd60e51b81526004016106079061526a565b600580546001600160a01b0319166001600160a01b0392909216919091179055565b6000732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b60018686610ace6123e3565b6040518563ffffffff1660e01b8152600401610aed9493929190615603565b60006040518083038186803b158015610b0557600080fd5b505af4158015610b19573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610b419190810190614d93565b6020015190506001600160a01b038116610b6d5760405162461bcd60e51b81526004016106079061526a565b610b783382846123f8565b50505050565b610b8d6402540be40034615af8565b15610bc75760405162461bcd60e51b815260206004820152600a602482015269647573742076616c756560b01b6044820152606401610607565b610bd3336000346123f8565b565b6000546001600160a01b03163314610bff5760405162461bcd60e51b81526004016106079061531b565b6040516319047a2f60e31b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c9063c823d17890610c3f9060019088908890889088906004016155c3565b60006040518083038186803b158015610c5757600080fd5b505af4158015610c6b573d6000803e3d6000fd5b50505050836001600160a01b03167fcf66acda7673c71cd9458c59067eb30a473eeaf2fa05d008acf47616cbbbe01c8484846040516106bd939291906151fa565b3360009081526009602052604090205460ff16610cff5760405162461bcd60e51b815260206004820152601160248201527015d85b1b195d081b9bdd08195e1a5d1959607a1b6044820152606401610607565b33600081815260096020526040808220805460ff19168155600101829055517fb771d4b2a83beca38f442c8903629e0e8ab1a07cf76e94eb2977153167e209369190a2565b336000908152600b602052604090205460ff16610d735760405162461bcd60e51b815260040161060790615293565b610d7e83838361275d565b505050565b336000908152600b602052604090205460ff16610db25760405162461bcd60e51b815260040161060790615293565b610dbf816040015161156e565b15610ddc5760405162461bcd60e51b815260040161060790615378565b6107d06001600160401b0316610dfa8260c001518360a00151612954565b6001600160401b03161115610e415760405162461bcd60e51b815260206004820152600d60248201526c4578636573736976652066656560981b6044820152606401610607565b6000610e4c8261297f565b60008181526004602052604090205490915060ff1615610e9a5760405162461bcd60e51b81526020600482015260096024820152682bb4ba34323930bbb760b91b6044820152606401610607565b60008083516001811115610ebe57634e487b7160e01b600052602160045260246000fd5b14610f59576080830151604051638c4ae9e560e01b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c91638c4ae9e591610f0091600191600401615581565b60006040518083038186803b158015610f1857600080fd5b505af4158015610f2c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610f549190810190614d93565b610ffb565b6001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b90918560600151610f898760200151612ade565b6040518463ffffffff1660e01b8152600401610fa793929190615636565b60006040518083038186803b158015610fbf57600080fd5b505af4158015610fd3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ffb9190810190614d93565b905060006110238460c001518560a001516001600160401b0316612b7390919063ffffffff16565b90506000611035828460600151612358565b9050600061108f8660a001516110808660200151600660008b604001516001600160a01b03166001600160a01b0316815260200190815260200160002061233090919063ffffffff16565b6001600160401b031690612b73565b905060006110a1828660600151612358565b90506001600160401b0382166110e1576020808601516040808a01516001600160a01b0316600090815260069093529091206110dc91612bb5565b61110d565b6020808601516040808a01516001600160a01b03166000908152600690935290912061110d9184612d25565b60208086015160c0890151600c546001600160a01b031660009081526006909352604090922061117592611154916111459084612330565b6001600160401b031690612dfe565b600c546001600160a01b031660009081526006602052604090209190612d25565b60055460408089015160208801519151636ce5768960e11b81526001600160a01b039093169263d9caed12926111b0929188906004016150cb565b600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b505050600087815260046020908152604091829020805460ff19166001179055870151898201518883015160a08c015193516001600160a01b03938416955091909216927f6960a1f64ecf9da0d1d1bcbfa3dd27f8c1c60de69b13faa28127dafa36c111e4926112549290919088908890615234565b60405180910390a350505050505050565b336001600160a01b037f0000000000000000000000006206bc17993abd8cd45ff191e20c0842bd92908616146112ad5760405162461bcd60e51b8152600401610607906152ed565b6001600160a01b0381166112fc5760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642077616c6c6574206164647265737360501b6044820152606401610607565b6000546001600160a01b03828116911614156113665760405162461bcd60e51b8152602060048201526024808201527f4d75737420626520646966666572656e742066726f6d2063757272656e7420616044820152633236b4b760e11b6064820152608401610607565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146113b25760405162461bcd60e51b81526004016106079061531b565b604051633ce8f24d60e11b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c906379d1e49a906113f090600190879087908790600401615598565b60006040518083038186803b15801561140857600080fd5b505af415801561141c573d6000803e3d6000fd5b50505050826001600160a01b03167f7f63a63f4a2ea318da0b031794d0b5a3183a1b2de54e053ceb17cabdba031735838360405161145b9291906151e6565b60405180910390a2505050565b6000546001600160a01b031633146114925760405162461bcd60e51b81526004016106079061531b565b6001600160a01b0381166114b85760405162461bcd60e51b81526004016106079061526a565b6001600160a01b0381166000908152600b602052604090205460ff16156115105760405162461bcd60e51b815260206004820152600c60248201526b53616d65206164647265737360a01b6044820152606401610607565b6001600160a01b0381166000908152600b602052604090819020805460ff19166001179055517f10c2db09cf947efb59f8c4e821d8f419f975db6c94ffe5c8145bc6e8c91a0461906115639083906150b7565b60405180910390a150565b6001600160a01b0381166000908152600960205260408120805460ff16801561159b575043816001015411155b9392505050565b336000908152600b602052604090205460ff166115d15760405162461bcd60e51b815260040161060790615293565b815183511480156115e3575080518251145b6116215760405162461bcd60e51b815260206004820152600f60248201526e0d8cadccee8d040dad2e6dac2e8c6d608b1b6044820152606401610607565b82516116605760405162461bcd60e51b815260206004820152600e60248201526d6c656e677468206973207a65726f60901b6044820152606401610607565b60005b8351811015610b78576116ec84828151811061168f57634e487b7160e01b600052603260045260246000fd5b60200260200101518483815181106116b757634e487b7160e01b600052603260045260246000fd5b60200260200101518484815181106116df57634e487b7160e01b600052603260045260246000fd5b602002602001015161275d565b806116f681615ab6565b915050611663565b6040805160c081018252600080825260208201819052606082840181905282018190526080820181905260a0820152905163e970ba6b60e01b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c9063e970ba6b9061176a90600190889088908890600401615603565b60006040518083038186803b15801561178257600080fd5b505af4158015611796573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526117be9190810190614d93565b949350505050565b6000546001600160a01b031633146117f05760405162461bcd60e51b81526004016106079061531b565b6001600160a01b0381166118165760405162461bcd60e51b81526004016106079061526a565b600c546001600160a01b03828116911614156118665760405162461bcd60e51b815260206004820152600f60248201526e14d85b5948199959481dd85b1b195d608a1b6044820152606401610607565b600c80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f9f4f5dce3c4d197b5d7496cb96e25f0a89809167195964b0daa3ef5fed63c00a91016109e9565b6001600160a01b038116600090815260066020526040812054606091906001600160401b0381111561190257634e487b7160e01b600052604160045260246000fd5b60405190808252806020026020018201604052801561192b578160200160208202803683370190505b50905060005b6001600160a01b0384166000908152600660205260409020548110156119c1576001600160a01b03841660009081526006602052604081206119739083612e6d565b90508083838151811061199657634e487b7160e01b600052603260045260246000fd5b6001600160a01b039092166020928302919091019091015250806119b981615ab6565b915050611931565b5092915050565b6119d13361156e565b6119ed5760405162461bcd60e51b8152600401610607906152bf565b604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e590611a2a906001908690600401615581565b60006040518083038186803b158015611a4257600080fd5b505af4158015611a56573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611a7e9190810190614d93565b33600090815260066020526040812091925090611a9b9084612330565b90506000611aad828460600151612358565b905060008111611aee5760405162461bcd60e51b815260206004820152600c60248201526b042616c616e636520697320360a41b6044820152606401610607565b336000908152600660205260409020611b079085612bb5565b600554604051636ce5768960e11b81526001600160a01b039091169063d9caed1290611b3b903390889086906004016150cb565b600060405180830381600087803b158015611b5557600080fd5b505af1158015611b69573d6000803e3d6000fd5b50505050836001600160a01b0316336001600160a01b0316600080516020615baf833981519152856040015185600080604051611ba99493929190615234565b60405180910390a350505050565b336001600160a01b037f0000000000000000000000006206bc17993abd8cd45ff191e20c0842bd9290861614611bff5760405162461bcd60e51b8152600401610607906152ed565b600080546001600160a01b0319169055565b6000611c1c82612ade565b9050611c26612eae565b6001600160401b0316816001600160401b031610611c805760405162461bcd60e51b81526020600482015260176024820152764e6f6e636520746f6f2066617220696e2066757475726560481b6044820152606401610607565b3360009081526007602052604090205460ff1615611d4e57336000908152600760205260409020546001600160401b038083166101009092041610611cf55760405162461bcd60e51b815260206004820152600b60248201526a125b9d985b1a59185d195960aa1b6044820152606401610607565b33600090815260076020526040902060010154431015611d4e5760405162461bcd60e51b815260206004820152601460248201527320bbb0b4ba34b73390383937b830b3b0ba34b7b760611b6044820152606401610607565b6000600a5443611d5e9190615855565b604080516060808201835260018083526001600160401b038781166020808601828152868801898152336000818152600785528a90209851895493516001600160481b0319909416901515610100600160481b0319161761010093909616929092029490941787559251959093019490945584516001600160801b038a16815291820193909352928301849052929350917f10cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809b910161145b565b60006001600160a01b038316611e3f5760405162461bcd60e51b81526004016106079061526a565b6001600160a01b038316600090815260066020526040902061159b9083612330565b6001600160a01b038216611e875760405162461bcd60e51b81526004016106079061526a565b611e923383836123f8565b5050565b3360009081526009602052604090205460ff1615611ec65760405162461bcd60e51b815260040161060790615378565b6040518060400160405280600115158152602001600a5443611ee89190615855565b90523360008181526009602090815260409091208351815460ff1916901515178155920151600190920191909155600a547fd60f9f7b2f1a208268475a927bd727c4e198fc8b40aab3004ebcc2bc78ca848090611f459043615855565b60405190815260200160405180910390a2565b60006001600160a01b038416611f805760405162461bcd60e51b81526004016106079061526a565b6000732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b60018686611fa86123e3565b6040518563ffffffff1660e01b8152600401611fc79493929190615603565b60006040518083038186803b158015611fdf57600080fd5b505af4158015611ff3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261201b9190810190614d93565b6020908101516001600160a01b0387166000908152600690925260409091209091506108b39082612330565b6120503361156e565b61206c5760405162461bcd60e51b8152600401610607906152bf565b336000908152600660205260408120546001600160401b038111156120a157634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156120ca578160200160208202803683370190505b50905060005b336000908152600660205260409020548110156122cc573360009081526006602052604081206121009083612e6d565b90508083838151811061212357634e487b7160e01b600052603260045260246000fd5b6001600160a01b0390921660209283029190910190910152604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e590612178906001908690600401615581565b60006040518083038186803b15801561219057600080fd5b505af41580156121a4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526121cc9190810190614d93565b336000908152600660205260408120919250906121e99084612330565b905060006121fb828460600151612358565b90508061220b57505050506122ba565b600554604051636ce5768960e11b81526001600160a01b039091169063d9caed129061223f903390889086906004016150cb565b600060405180830381600087803b15801561225957600080fd5b505af115801561226d573d6000803e3d6000fd5b50505050836001600160a01b0316336001600160a01b0316600080516020615baf8339815191528560400151856000806040516122ad9493929190615234565b60405180910390a3505050505b806122c481615ab6565b9150506120d0565b5060005b8151811015611e925761231e8282815181106122fc57634e487b7160e01b600052603260045260246000fd5b6020908102919091018101513360009081526006909252604090912090612bb5565b8061232881615ab6565b9150506122d0565b6001600160a01b0316600090815260019190910160205260409020546001600160401b031690565b600060208260ff16111561237e5760405162461bcd60e51b815260040161060790615349565b60088260ff1611156123ba576123b3612398600884615a67565b6123a390600a61592f565b6001600160401b03851690612ed3565b90506107bb565b61159b6123c8836008615a67565b6123d390600a61592f565b6001600160401b03851690612edf565b60006103e86123f281426159f9565b91505090565b6001600160a01b03831660009081526009602052604090205460ff16156124315760405162461bcd60e51b815260040161060790615378565b604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e59061246e906001908790600401615581565b60006040518083038186803b15801561248657600080fd5b505af415801561249a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526124c29190810190614d93565b905060006124d4838360600151612eeb565b90506000816001600160401b0316116125255760405162461bcd60e51b81526020600482015260136024820152725175616e7469747920697320746f6f206c6f7760681b6044820152606401610607565b6000612535828460600151612358565b90506001600160a01b038516156125b25760405163251b43c960e21b8152731af7b032e507fc235be7d3d5fe56f39ccb7dceca9063946d0f2490612581908990899086906004016150cb565b60006040518083038186803b15801561259957600080fd5b505af41580156125ad573d6000803e3d6000fd5b505050505b6005546040516352f950a960e11b8152731af7b032e507fc235be7d3d5fe56f39ccb7dceca9163a5f2a152916125f8916001600160a01b031690899086906004016150cb565b60006040518083038186803b15801561261057600080fd5b505af4158015612624573d6000803e3d6000fd5b505050506001600160a01b03861660009081526006602052604081206126509084906111459089612330565b90506000612662828660600151612358565b6001600160a01b0389166000908152600660205260409020909150612688908884612d25565b60058054600160a01b90046001600160401b03169060146126a883615ad1565b91906101000a8154816001600160401b0302191690836001600160401b031602179055505084604001516040516126df919061509b565b6040518091039020876001600160a01b0316896001600160a01b03167fc2813a84168de59cf710030ba2acb13031b567b8ba76f70e8ab0782aa9469a7a600560149054906101000a90046001600160401b0316896040015189888860405161274b95949392919061572f565b60405180910390a45050505050505050565b61276a836040015161156e565b156127ae5760405162461bcd60e51b8152602060048201526014602482015273109d5e481dd85b1b195d08199a5b985b1a5e995960621b6044820152606401610607565b6127bb826040015161156e565b156128005760405162461bcd60e51b815260206004820152601560248201527414d95b1b081dd85b1b195d08199a5b985b1a5e9959605a1b6044820152606401610607565b81604001516001600160a01b031683604001516001600160a01b031614156128655760405162461bcd60e51b815260206004820152601860248201527714d95b198b5d1c98591a5b99c81b9bdd08185b1b1bddd95960421b6044820152606401610607565b612870838383612fb5565b61287b838383613594565b6128858383613742565b600080612893858585613829565b915091506128a083613854565b6128ad8583868487613b06565b6128b8858585613b23565b82602001516040516128ca919061509b565b604051908190038120845190916128e1919061509b565b60405180910390207fbd75b2e24c3d43ec9f10f3583265606c0a7d8b67d147f9dc48376b81456823a1876040015187604001518760000151886020015189608001518a60a001518b61018001518b8b604051612945999897969594939291906150ef565b60405180910390a35050505050565b60006127106107b7836129706001600160401b03871684613eee565b6001600160401b031690613f88565b60008073cb85f6b03ce6238786aeebca4581dd167fa6a11063f398aaf9846040518263ffffffff1660e01b81526004016129b99190615667565b60206040518083038186803b1580156129d157600080fd5b505af41580156129e5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a099190614c4e565b61010084015160408086015190516365a18acf60e01b815292935073cb85f6b03ce6238786aeebca4581dd167fa6a110926365a18acf92612a4e9286926004016151b5565b60206040518083038186803b158015612a6657600080fd5b505af4158015612a7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a9e9190614c1a565b6107bb5760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b6044820152606401610607565b6000600f604c83901c1660018114612b2a5760405162461bcd60e51b815260206004820152600f60248201526e135d5cdd081899481d8c4815555251608a1b6044820152606401610607565b610fff60301b601084901c1661ffff60201b603085901c1663ffffffff606086901c168183178117612b68650b1d069b540061108061271084615898565b979650505050505050565b600061159b83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613fc7565b6001600160a01b038116600090815260038301602052604090205460ff16612bdb575050565b6001600160a01b03811660009081526003830160209081526040808320805460ff191690556001808601835281842080546001600160401b0319169055600286019092528220548454909291612c3091615a28565b90506000846000018281548110612c5757634e487b7160e01b600052603260045260246000fd5b60009182526020808320909101546001600160a01b03908116808452600289019092526040808420879055908716835282209190915585549091508190869085908110612cb457634e487b7160e01b600052603260045260246000fd5b600091825260209091200180546001600160a01b0319166001600160a01b03929092169190911790558454859080612cfc57634e487b7160e01b600052603160045260246000fd5b600082815260209020810160001990810180546001600160a01b03191690550190555050505050565b6001600160a01b038216600090815260038401602052604090205460ff1615612d82576001600160a01b0382166000908152600184016020526040902080546001600160401b0383166001600160401b0319909116179055505050565b6001600160a01b03821660008181526003850160209081526040808320805460ff19166001908117909155878101835281842080546001600160401b0388166001600160401b031990911617905587546002890184529184208290558101875586835291200180546001600160a01b0319169091179055505050565b600080612e0b838561586d565b9050836001600160401b0316816001600160401b0316101561159b5760405162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b6044820152606401610607565b6000826000018281548110612e9257634e487b7160e01b600052603260045260246000fd5b6000918252602090912001546001600160a01b03169392505050565b6000620151806103e880612ec2834261586d565b612ecc91906159f9565b9250505090565b600061159b82846159da565b600061159b82846158be565b600060208260ff161115612f115760405162461bcd60e51b815260040161060790615349565b600060088360ff161115612f4657612f3f612f2d600885615a67565b612f3890600a61592f565b8590612edf565b9050612f69565b612f66612f54846008615a67565b612f5f90600a61592f565b8590612ed3565b90505b600160401b811061159b5760405162461bcd60e51b8152602060048201526016602482015275506970207175616e74697479206f766572666c6f777360501b6044820152606401610607565b80606001516001600160a01b031681604001516001600160a01b0316141561302b5760405162461bcd60e51b815260206004820152602360248201527f426173652f71756f746520617373657473206d75737420626520646966666572604482015262195b9d60ea1b6064820152608401610607565b60006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b9091846000015161305d8860200151612ade565b6040518463ffffffff1660e01b815260040161307b93929190615636565b60006040518083038186803b15801561309357600080fd5b505af41580156130a7573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526130cf9190810190614d93565b905060006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b909185602001516131038960200151612ade565b6040518463ffffffff1660e01b815260040161312193929190615636565b60006040518083038186803b15801561313957600080fd5b505af415801561314d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526131759190810190614d93565b905082604001516001600160a01b031682602001516001600160a01b03161480156131b9575082606001516001600160a01b031681602001516001600160a01b0316145b6132015760405162461bcd60e51b8152602060048201526019602482015278084eaf240dee4c8cae440e6f2dac4ded840dad2e6dac2e8c6d603b1b6044820152606401610607565b60006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b909186600001516132338960200151612ade565b6040518463ffffffff1660e01b815260040161325193929190615636565b60006040518083038186803b15801561326957600080fd5b505af415801561327d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526132a59190810190614d93565b905060006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b909187602001516132d98a60200151612ade565b6040518463ffffffff1660e01b81526004016132f793929190615636565b60006040518083038186803b15801561330f57600080fd5b505af4158015613323573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261334b9190810190614d93565b905084604001516001600160a01b031682602001516001600160a01b031614801561338f575084606001516001600160a01b031681602001516001600160a01b0316145b6133d85760405162461bcd60e51b815260206004820152601a6024820152790a6cad8d840dee4c8cae440e6f2dac4ded840dad2e6dac2e8c6d60331b6044820152606401610607565b84604001516001600160a01b03168561010001516001600160a01b0316148061341b575084606001516001600160a01b03168561010001516001600160a01b0316145b6134735760405162461bcd60e51b8152602060048201526024808201527f4d616b657220666565206173736574206973206e6f7420696e207472616465206044820152633830b4b960e11b6064820152608401610607565b84604001516001600160a01b03168561012001516001600160a01b031614806134b6575084606001516001600160a01b03168561012001516001600160a01b0316145b61350e5760405162461bcd60e51b8152602060048201526024808201527f54616b657220666565206173736574206973206e6f7420696e207472616465206044820152633830b4b960e11b6064820152608401610607565b8461012001516001600160a01b03168561010001516001600160a01b0316141561358b5760405162461bcd60e51b815260206004820152602860248201527f4d616b65722f74616b65722066656520617373657473206d75737420626520646044820152671a5999995c995b9d60c21b6064820152608401610607565b50505050505050565b600081608001516001600160401b0316116135e65760405162461bcd60e51b8152602060048201526012602482015271042617365207175616e7469747920697320360741b6044820152606401610607565b60008160a001516001600160401b0316116136395760405162461bcd60e51b8152602060048201526013602482015272051756f7465207175616e74697479206973203606c1b6044820152606401610607565b613646836060015161400b565b156136bd578060a001516001600160401b031661366b82608001518560e001516140b4565b6001600160401b031610156136bd5760405162461bcd60e51b8152602060048201526018602482015277109d5e481b1a5b5a5d081c1c9a58d948195e18d95959195960421b6044820152606401610607565b6136ca826060015161400b565b15610d7e578060a001516001600160401b03166136ef82608001518460e001516140b4565b6001600160401b03161115610d7e5760405162461bcd60e51b815260206004820152601960248201527814d95b1b081b1a5b5a5d081c1c9a58d948195e18d959591959603a1b6044820152606401610607565b61374f826040015161412e565b6001600160401b03166137658360200151612ade565b6001600160401b0316116137b55760405162461bcd60e51b8152602060048201526017602482015276427579206f72646572206e6f6e636520746f6f206c6f7760481b6044820152606401610607565b6137c2816040015161412e565b6001600160401b03166137d88260200151612ade565b6001600160401b031611611e925760405162461bcd60e51b815260206004820152601860248201527753656c6c206f72646572206e6f6e636520746f6f206c6f7760401b6044820152606401610607565b600080600061383886856141a8565b9050600061384686866141a8565b919791965090945050505050565b600081604001516001600160a01b03168261010001516001600160a01b031614613882578160a00151613888565b81608001515b90506107d06001600160401b03166138a583610140015183612954565b6001600160401b031611156138f25760405162461bcd60e51b8152602060048201526013602482015272457863657373697665206d616b65722066656560681b6044820152606401610607565b600082604001516001600160a01b03168361012001516001600160a01b031614613920578260a00151613926565b82608001515b90506107d06001600160401b031661394384610160015183612954565b6001600160401b031611156139905760405162461bcd60e51b81526020600482015260136024820152724578636573736976652074616b65722066656560681b6044820152606401610607565b82608001516001600160401b03166139e984604001516001600160a01b03168561010001516001600160a01b0316146139ce578461016001516139d5565b8461014001515b60c08601516001600160401b031690612dfe565b6001600160401b031614613a4b5760405162461bcd60e51b815260206004820152602360248201527f4261736520706c757320666565206973206e6f7420657175616c20746f2067726044820152626f737360e81b6064820152608401610607565b8260a001516001600160401b0316613aa484606001516001600160a01b03168561010001516001600160a01b031614613a8957846101600151613a90565b8461014001515b60e08601516001600160401b031690612dfe565b6001600160401b031614610d7e5760405162461bcd60e51b8152602060048201526024808201527f51756f746520706c757320666565206973206e6f7420657175616c20746f2067604482015263726f737360e01b6064820152608401610607565b613b11858583614390565b613b1c838383614390565b5050505050565b6080810151604080830151848201516001600160a01b0316600090815260066020529182209192613b5a9290916110809190612330565b90506001600160401b03811615613b9b57604080830151848201516001600160a01b03166000908152600660205291909120613b969183612d25565b613bc5565b604080830151848201516001600160a01b03166000908152600660205291909120613bc591612bb5565b60c0820151604080840151868201516001600160a01b0316600090815260066020529182209192613bfc9290916111459190612330565b90506001600160401b03811615613c3d57604080840151868201516001600160a01b03166000908152600660205291909120613c389183612d25565b613c67565b604080840151868201516001600160a01b03166000908152600660205291909120613c6791612bb5565b60a083015160608401516040808801516001600160a01b0316600090815260066020529081209092613c9e92909161108091612330565b90506001600160401b03811615613cdd5760608401516040808801516001600160a01b03166000908152600660205220613cd89183612d25565b613d05565b60608401516040808801516001600160a01b03166000908152600660205220613d0591612bb5565b60e084015160608501516040808801516001600160a01b0316600090815260066020529081209092613d3c92909161114591612330565b90506001600160401b03811615613d7b5760608501516040808801516001600160a01b03166000908152600660205220613d769183612d25565b613da3565b60608501516040808801516001600160a01b03166000908152600660205220613da391612bb5565b610140850151610100860151600c546001600160a01b031660009081526006602052604081209092613dda92909161114591612330565b90506001600160401b03811615613e1a57610100860151600c546001600160a01b03166000908152600660205260409020613e159183612d25565b613e43565b610100860151600c546001600160a01b03166000908152600660205260409020613e4391612bb5565b610160860151610120870151600c546001600160a01b031660009081526006602052604081209092613e7a92909161114591612330565b90506001600160401b03811615613eba57610120870151600c546001600160a01b03166000908152600660205260409020613eb59183612d25565b613ee3565b610120870151600c546001600160a01b03166000908152600660205260409020613ee391612bb5565b505050505050505050565b60006001600160401b038316613f06575060006107bb565b6000613f1283856159f9565b90506001600160401b038316613f2885836158d2565b6001600160401b03161461159b5760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b6064820152608401610607565b600061159b83836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b815250614569565b6000836001600160401b0316836001600160401b031611158290613ffe5760405162461bcd60e51b81526004016106079190615221565b5060006108b38486615a3f565b6000600182600681111561402f57634e487b7160e01b600052602160045260246000fd5b148061405a5750600282600681111561405857634e487b7160e01b600052602160045260246000fd5b145b806140845750600482600681111561408257634e487b7160e01b600052602160045260246000fd5b145b806107bb575060065b8260068111156140ad57634e487b7160e01b600052602160045260246000fd5b1492915050565b60006305f5e100816140dc826140d66001600160401b03888116908816612ed3565b90612edf565b9050600160401b81106107b75760405162461bcd60e51b815260206004820152601a602482015279496d706c696564207175616e74697479206f766572666c6f777360301b6044820152606401610607565b6001600160a01b03811660009081526007602052604081205460ff16801561417157506001600160a01b0382166000908152600760205260409020600101544310155b156141a057506001600160a01b031660009081526007602052604090205461010090046001600160401b031690565b506000919050565b80516020820151604051632779981560e11b8152600092839273cb85f6b03ce6238786aeebca4581dd167fa6a11092634ef3302a926141eb928992600401615410565b60206040518083038186803b15801561420357600080fd5b505af4158015614217573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061423b9190614c4e565b6101a085015160408087015190516365a18acf60e01b815292935073cb85f6b03ce6238786aeebca4581dd167fa6a110926365a18acf926142809286926004016151b5565b60206040518083038186803b15801561429857600080fd5b505af41580156142ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142d09190614c1a565b6000856080015160018111156142f657634e487b7160e01b600052602160045260246000fd5b14614335576040518060400160405280601c81526020017b496e76616c69642073656c6c206f72646572207369676e617475726560201b81525061436a565b6040518060400160405280601b81526020017a496e76616c696420627579206f72646572207369676e617475726560281b8152505b906143885760405162461bcd60e51b81526004016106079190615221565b509392505050565b60008281526003602052604090205460ff16156143df5760405162461bcd60e51b815260206004820152600d60248201526c111bdd589b1948199a5b1b1959609a1b6044820152606401610607565b60008360c0015115614469576143f884606001516145a0565b6144385760405162461bcd60e51b81526020600482015260116024820152704e6f74206d61726b6574206f726465727360781b6044820152606401610607565b60008381526008602052604090205460a0830151614462916001600160401b039182169116612dfe565b9050614496565b6000838152600860205260409020546080830151614493916001600160401b039182169116612dfe565b90505b8360a001516001600160401b0316816001600160401b031611156144e95760405162461bcd60e51b815260206004820152600a60248201526913dd995c999a5b1b195960b21b6044820152606401610607565b8360a001516001600160401b0316816001600160401b0316101561453357600083815260086020526040902080546001600160401b0319166001600160401b038316179055610b78565b5050600090815260086020908152604080832080546001600160401b031916905560039091529020805460ff1916600117905550565b6000816001600160401b0384166145935760405162461bcd60e51b81526004016106079190615221565b5060006108b384866158d2565b6000808260068111156145c357634e487b7160e01b600052602160045260246000fd5b14806145ee575060038260068111156145ec57634e487b7160e01b600052602160045260246000fd5b145b806107bb5750600561408d565b803561460681615b64565b919050565b600082601f83011261461b578081fd5b8135602061463061462b8361580b565b6157db565b80838252828201915082860187848660051b890101111561464f578586fd5b855b8581101561468f5781356001600160401b0381111561466e578788fd5b61467c8a87838c01016147b4565b8552509284019290840190600101614651565b5090979650505050505050565b803561460681615b7c565b805161460681615b7c565b600082601f8301126146c2578081fd5b81356146d061462b8261582e565b8181528460208386010111156146e4578283fd5b816020850160208301379081016020019190915292915050565b80356004811061460657600080fd5b80356002811061460657600080fd5b80356007811061460657600080fd5b60008083601f84011261473c578182fd5b5081356001600160401b03811115614752578182fd5b60208301915083602082850101111561476a57600080fd5b9250929050565b600082601f830112614781578081fd5b815161478f61462b8261582e565b8181528460208386010111156147a3578283fd5b6107b7826020830160208701615a8a565b60006101c082840312156147c6578081fd5b6147ce61576d565b90506147d982614a6a565b81526147e760208301614a3d565b60208201526147f8604083016145fb565b60408201526148096060830161471c565b606082015261481a6080830161470d565b608082015261482b60a08301614a54565b60a082015261483c60c0830161469c565b60c082015261484d60e08301614a54565b60e0820152610100614860818401614a54565b90820152610120828101356001600160401b038082111561488057600080fd5b61488c868387016146b2565b8385015261014092506148a08386016146fe565b8385015261016092506148b48386016146fe565b8385015261018092506148c8838601614a54565b838501526101a09250828501359150808211156148e457600080fd5b506148f1858286016146b2565b82840152505092915050565b60006101c0828403121561490f578081fd5b61491761576d565b905081356001600160401b038082111561493057600080fd5b61493c858386016146b2565b8352602084013591508082111561495257600080fd5b5061495f848285016146b2565b602083015250614971604083016145fb565b6040820152614982606083016145fb565b606082015261499360808301614a54565b60808201526149a460a08301614a54565b60a08201526149b560c08301614a54565b60c08201526149c660e08301614a54565b60e08201526101006149d98184016145fb565b908201526101206149eb8382016145fb565b908201526101406149fd838201614a54565b90820152610160614a0f838201614a54565b90820152610180614a21838201614a54565b908201526101a0614a3383820161470d565b9082015292915050565b80356001600160801b038116811461460657600080fd5b803561460681615b8a565b805161460681615b8a565b803561460681615b9f565b805161460681615b9f565b600060208284031215614a91578081fd5b813561159b81615b64565b60008060408385031215614aae578081fd5b8235614ab981615b64565b91506020830135614ac981615b64565b809150509250929050565b600080600060408486031215614ae8578081fd5b8335614af381615b64565b925060208401356001600160401b03811115614b0d578182fd5b614b198682870161472b565b9497909650939450505050565b600080600060608486031215614b3a578081fd5b83356001600160401b0380821115614b50578283fd5b614b5c8783880161460b565b9450602091508186013581811115614b72578384fd5b614b7e8882890161460b565b945050604086013581811115614b92578384fd5b8601601f81018813614ba2578384fd5b8035614bb061462b8261580b565b8082825285820191508584018b878560051b8701011115614bcf578788fd5b875b84811015614c0857813587811115614be757898afd5b614bf58e8a838a01016148fd565b8552509287019290870190600101614bd1565b50508096505050505050509250925092565b600060208284031215614c2b578081fd5b815161159b81615b7c565b600060208284031215614c47578081fd5b5035919050565b600060208284031215614c5f578081fd5b5051919050565b60008060008060608587031215614c7b578182fd5b8435614c8681615b64565b935060208501356001600160401b03811115614ca0578283fd5b614cac8782880161472b565b9094509250506040850135614cc081615b9f565b939692955090935050565b60008060408385031215614cdd578182fd5b8235614ce881615b64565b946020939093013593505050565b600080600060408486031215614d0a578081fd5b83356001600160401b03811115614d1f578182fd5b614d2b8682870161472b565b909790965060209590950135949350505050565b600080600060408486031215614d53578081fd5b83356001600160401b03811115614d68578182fd5b614d748682870161472b565b9094509250506020840135614d8881615b8a565b809150509250925092565b600060208284031215614da4578081fd5b81516001600160401b0380821115614dba578283fd5b9083019060c08286031215614dcd578283fd5b614dd5615796565b8251614de081615b7c565b81526020830151614df081615b64565b6020820152604083015182811115614e06578485fd5b614e1287828601614771565b604083015250614e2460608401614a75565b6060820152614e35608084016146a7565b6080820152614e4660a08401614a5f565b60a082015295945050505050565b600080600060608486031215614e68578081fd5b83356001600160401b0380821115614e7e578283fd5b614e8a878388016147b4565b94506020860135915080821115614e9f578283fd5b614eab878388016147b4565b93506040860135915080821115614ec0578283fd5b50614ecd868287016148fd565b9150509250925092565b600060208284031215614ee8578081fd5b81356001600160401b0380821115614efe578283fd5b908301906101208286031215614f12578283fd5b614f1a6157b8565b614f238361470d565b8152614f3160208401614a3d565b6020820152614f42604084016145fb565b6040820152606083013582811115614f58578485fd5b614f64878286016146b2565b606083015250614f76608084016145fb565b6080820152614f8760a08401614a54565b60a0820152614f9860c08401614a54565b60c0820152614fa960e0840161469c565b60e08201526101008084013583811115614fc1578586fd5b614fcd888287016146b2565b918301919091525095945050505050565b600060208284031215614fef578081fd5b61159b82614a3d565b6001600160a01b03169052565b6000815180845261501d816020860160208601615a8a565b601f01601f19169290920160200192915050565b6004811061504157615041615b38565b9052565b6002811061504157615041615b38565b6007811061504157615041615b38565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160401b03169052565b600082516150ad818460208701615a8a565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b038a81168252891660208201526101206040820181905260009061511c8382018b615005565b90508281036060840152615130818a615005565b6001600160401b03988916608085015296881660a084015250509290941660c083015260e08201526101000191909152949350505050565b6020808252825182820181905260009190848201906040850190845b818110156151a95783516001600160a01b031683529284019291840191600101615184565b50909695505050505050565b8381526060602082015260006151ce6060830185615005565b905060018060a01b0383166040830152949350505050565b6020815260006117be602083018486615065565b60408152600061520e604083018587615065565b905060ff83166020830152949350505050565b60208152600061159b6020830184615005565b6080815260006152476080830187615005565b6001600160401b0395861660208401529390941660408201526060015292915050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b60208082526012908201527126bab9ba103132903234b9b830ba31b432b960711b604082015260600190565b60208082526014908201527315d85b1b195d081b9bdd08199a5b985b1a5e995960621b604082015260600190565b60208082526014908201527321b0b63632b91036bab9ba1031329037bbb732b960611b604082015260600190565b60208082526014908201527321b0b63632b91036bab9ba1031329030b236b4b760611b604082015260600190565b6020808252601590820152744d6f7265207468616e20333220646563696d616c7360581b604082015260600190565b6020808252600d908201526c15d85b1b195d08195e1a5d1959609a1b604082015260600190565b6020815281511515602082015260018060a01b0360208301511660408201526000604083015160c060608401526153d960e0840182615005565b905060ff60608501511660808401526080840151151560a084015260018060401b0360a08501511660c08401528091505092915050565b6060815261542460608201855160ff169052565b6000602085015161544060808401826001600160801b03169052565b50604085015161545360a0840182614ff8565b50606085015161546660c0840182615055565b50608085015161547960e0840182615045565b5060a085015161010061548e8185018361508e565b60c087015191506101206154a58186018415159052565b60e088015192506101406154bb8187018561508e565b918801519250610160916154d18684018561508e565b8189015193506101c0915061018082818801526154f2610220880186615005565b918a01519194506101a061550888820184615031565b938a01519361551988850186615031565b818b0151945061552d6101e089018661508e565b808b0151945050505050605f198483030161020085015261554e8282615005565b91505082810360208401526155638186615005565b905082810360408401526155778185615005565b9695505050505050565b9182526001600160a01b0316602082015260400190565b8481526001600160a01b03841660208201526060604082018190526000906155779083018486615065565b8581526001600160a01b03851660208201526080604082018190526000906155ee9083018587615065565b905060ff831660608301529695505050505050565b84815260606020820152600061561d606083018587615065565b905060018060401b038316604083015295945050505050565b83815260606020820152600061564f6060830185615005565b905060018060401b0383166040830152949350505050565b60208152615679602082018351615045565b6000602083015161569560408401826001600160801b03169052565b5060408301516156a86060840182614ff8565b5060608301516101208060808501526156c5610140850183615005565b915060808501516156d960a0860182614ff8565b5060a08501516156ec60c086018261508e565b5060c08501516156ff60e086018261508e565b5060e08501516101006157158187018315159052565b860151858403601f19018387015290506155778382615005565b600060018060401b03808816835260a0602084015261575160a0840188615005565b9581166040840152939093166060820152608001525092915050565b6040516101c081016001600160401b038111828210171561579057615790615b4e565b60405290565b60405160c081016001600160401b038111828210171561579057615790615b4e565b60405161012081016001600160401b038111828210171561579057615790615b4e565b604051601f8201601f191681016001600160401b038111828210171561580357615803615b4e565b604052919050565b60006001600160401b0382111561582457615824615b4e565b5060051b60200190565b60006001600160401b0382111561584757615847615b4e565b50601f01601f191660200190565b6000821982111561586857615868615b0c565b500190565b60006001600160401b0382811684821680830382111561588f5761588f615b0c565b01949350505050565b60006001600160801b03838116806158b2576158b2615b22565b92169190910492915050565b6000826158cd576158cd615b22565b500490565b60006001600160401b03838116806158b2576158b2615b22565b600181815b8085111561592757816000190482111561590d5761590d615b0c565b8085161561591a57918102915b93841c93908002906158f1565b509250929050565b600061159b60ff841683600082615948575060016107bb565b81615955575060006107bb565b816001811461596b576002811461597557615991565b60019150506107bb565b60ff84111561598657615986615b0c565b50506001821b6107bb565b5060208310610133831016604e8410600b84101617156159b4575081810a6107bb565b6159be83836158ec565b80600019048211156159d2576159d2615b0c565b029392505050565b60008160001904831182151516156159f4576159f4615b0c565b500290565b60006001600160401b0382811684821681151582840482111615615a1f57615a1f615b0c565b02949350505050565b600082821015615a3a57615a3a615b0c565b500390565b60006001600160401b0383811690831681811015615a5f57615a5f615b0c565b039392505050565b600060ff821660ff841680821015615a8157615a81615b0c565b90039392505050565b60005b83811015615aa5578181015183820152602001615a8d565b83811115610b785750506000910152565b6000600019821415615aca57615aca615b0c565b5060010190565b60006001600160401b0382811680821415615aee57615aee615b0c565b6001019392505050565b600082615b0757615b07615b22565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114615b7957600080fd5b50565b8015158114615b7957600080fd5b6001600160401b0381168114615b7957600080fd5b60ff81168114615b7957600080fdfe02e03f232c88c87047c5a9cfbad1213b842503fee6a002ec2eec9f5a64725bf4a26469706673582212204180e530e71c579ad804a4288880ec23e43ec397831567cb923c2140d35a7f6964736f6c63430008040033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
BSC | 100.00% | $0.998252 | 49.01 | $48.92 |
[ Download: CSV Export ]
[ 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.