Contract 0x3253A7e75539EdaEb1Db608ce6Ef9AA1ac9126B6

 

Contract Overview

Balance:
0 MATIC

MATIC Value:
$0.00

Token:
 
Txn Hash Method
Block
From
To
Value [Txn Fee]
0x332845c4fc75bb484b14374c980499a3f4b8ca6c0e65d725e3333adc3db9d51f0xf91b6e68(pending)2022-05-26 5:02:482 secs ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC(Pending)
0x912b860f0be198f956809ab0d0576342db4eb779dc48a20e4ff1867ceeca72790xf91b6e68287798372022-05-26 5:02:0743 secs ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.022464128
0x6f045aadbf02f087308cca6d132bfb91e46a50e5f49a32afe700141c338f2716Deposit Token By...287798042022-05-26 4:59:552 mins ago0xa785066147ca97d72e77688d3a26863d8e9b19d5 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.003768897 33
0x8a29f2ce75c27a0ce121c5996eb06e903f991743c067495a04aad3aa29d548000xf91b6e68287797322022-05-26 4:56:296 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.02033315115
0xe9b96336bef15f734854ba92883b8fc860b97adf5fd05d347f31bf9cf238ee5a0xf91b6e68287797312022-05-26 4:56:276 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.016425448104
0x3c7ce6954230f8f56680d4de09cae0fe858bd6a440e47d64cab21789d2345b650xf91b6e68287797292022-05-26 4:56:236 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.021473024128
0x06c5a1f101c385b2db10881f6f854c1aaa68dff897b219589c7285c495b3b2460xf91b6e68287797292022-05-26 4:56:236 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.021569024128
0x94f88c4e7d25d590aee96f1dae50353f6cf0fae63fb7e73d7bb5b8f3594b53850xf91b6e68287797252022-05-26 4:56:156 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.020218112128
0xfb68c7c60e634c06c2e9601c476a4d5fce4b201e028c529d7beb34d32d48d9630xf91b6e68287797082022-05-26 4:55:377 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.020971875125
0xc4e792ee50cde25d795b00085ac2082db26779b2ec5404cf2b12d41c40a0ba540xf91b6e68287796622022-05-26 4:54:058 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.015988595
0x334269af5718d3d73653f6ad164b50f8b704add4ef2f1997e44c810c737dc4780xf91b6e68287796612022-05-26 4:54:038 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.018389488104
0x9acd2561b07d4054fe19aceab797422c9aaa7aa117c0852c1a339ff3bdad13e20xf91b6e68287796612022-05-26 4:54:038 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.017523584104
0xa57e3933361bc4eccd81c6e81871595aae8bc9f26f38b5d8d03145317a14adbb0xf91b6e68287796612022-05-26 4:54:038 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.017500184104
0xcefe6cbb00e60f648568db8dbc06383e23d3d06ee318c3d15689539265a76fd60xf91b6e68287796592022-05-26 4:53:598 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.019352545115
0x14aabf1f0e98f98832cf4cfe8cc264d7f82c12253d9fc3b28cf30f5de89921150xf91b6e68287796232022-05-26 4:52:4310 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.020970375125
0x4812ab909bce7aed9c94dabda37756e7dc4aced21c192562e6111d3254e2bf5a0xf91b6e68287794572022-05-26 4:47:0015 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.017695125105
0x86ece63a00f28c9a393604d8989fd4fd046a134883796484a6753a5775dd7c8f0xf91b6e68287794572022-05-26 4:47:0015 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.018564525105
0xdcb755ed3e7bc6a37670adaca3d42a17b069afb0a04b7bc99c91e72a1c83fa9b0xf91b6e68287793922022-05-26 4:44:4618 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.01591715595
0xf0a602d326233ba02d3526667389ad0c3acc07e6d721c17d385081230696614b0xf91b6e68287793922022-05-26 4:44:4618 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.017163642102
0xd23172758b96c722624caad8a876de3bc03c7f062c9bff78d1cba6b0f1a378380xf91b6e68287793892022-05-26 4:44:3518 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.0168288100
0x9fe120898e0b77dfbd21eb2fe694108703c192e41d056274b7a710d46e6bdd730xf91b6e68287793862022-05-26 4:44:2918 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.0193545115
0xd538973957d8143e9929d9a498a1954b0144b6cff2e2d8733a6970e78cf655ef0xf91b6e68287793662022-05-26 4:43:4919 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.015435392
0xc60716ea8ff37dadead4a30f7a13d04538770dbd35f54275d79a256ae8355fffDeposit Token By...287793602022-05-26 4:43:3719 mins ago0x8cc0c4dde8eeb8fd7266701fd6763ab71043aa52 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.002776323 33
0x64935f2f92f044c399c160c82a38ce94c61b9c86242eee885597533774668fad0xf91b6e68287792982022-05-26 4:41:2721 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.022346752128
0xaebffbeb82cdb87aaa2f92d7b85d8e0d7c312f1a2a9d205b9d505896bc4b207a0xf91b6e68287792972022-05-26 4:41:2521 mins ago0xba5edf9b065161f13eb89f10505ee2e23d9faf59 IN  0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b60 MATIC0.0226496128
[ Download CSV Export 
Latest 25 internal transaction
Parent Txn Hash Block From To Value
0x1de37de4c9549782838052f6e3d50df4e746b91a82c133b06bdc14d9bb497488287713672022-05-25 23:58:165 hrs 4 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x85870b44416f69164c228b3d1f6766d419507bdbe07aa2e9cd7808865015fd03287590612022-05-25 16:36:3512 hrs 26 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x066c516da84cc27acd6478e7594df13ef5b537cc84effaefad76a756479547d3287486412022-05-25 10:26:4118 hrs 36 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x199cffc6d00b0090555720103c8f2d883d82940ab597a8f4c796b5b8ca5151c3287484952022-05-25 10:21:4118 hrs 41 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x04f4c225e47811db93af00f497858e4dd601aec1fe8c843a389359535139f787287419132022-05-25 6:21:5922 hrs 40 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xcaf8fd1bf328899c9b8e241af4ae28263b12cbc42ddb5ea4aa51d36115cb2a64287416192022-05-25 6:11:5122 hrs 50 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x6af5f2118ab438a8ee246e98fb447233388d78b61742c32ff1f27c65ffc5fc75287414802022-05-25 6:07:0522 hrs 55 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x63f3bb341785033c40ea62673d4916a58b52aa597863a9e4159bb44add9bccfc287396282022-05-25 5:01:111 day 1 min ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xf9a6d08431c4d920e060e3cbb0100925e0b393548a7273f14fc28b08980da7bd287394672022-05-25 4:55:401 day 7 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x0f74bb142b1a003475220a9930b496d0e84d6a92be3365ae60dda049421d0619287387882022-05-25 4:30:081 day 32 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xe0305da760f4e3bacb1117cd784e1a0ae9c8da0a1b35752608644262f1198a1f287386182022-05-25 4:24:161 day 38 mins ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xe4f720e3cddcf277043bcd8298e39e44ebbf9186d4524563198695c97b73aff1287368072022-05-25 3:19:521 day 1 hr ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x986a755d8cc0b8a0cfcb2f22102f667c371183136c00ac97db000c3710478aff287366352022-05-25 3:13:561 day 1 hr ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x0f67caf69a507718111c2031d691021ce692e1835b3073b7d2392bb2f69c73ec287197972022-05-24 17:11:361 day 11 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xddd2dc4248acb40288f0062de9878ad82b549b2df0229a5fec3eb7780520aebf287152872022-05-24 14:36:241 day 14 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb4685.49303604 MATIC
0x939c8875ed79afa1f2ea7447e192389a97b61f99353b067c61eeaa0b5095aedf287079932022-05-24 10:24:461 day 18 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb46834.58023952 MATIC
0x5f105efcc1fa96deee4f5a9f698043375ce539a94385495979c803e4085ebbc2287065692022-05-24 9:35:501 day 19 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xaaece5e0db6fc42bcce79d603196d7647cbbb43ad5838050b7bcc503cabe0bbb287056362022-05-24 9:03:391 day 19 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468400 MATIC
0x38d9fdb6f3e3f95f9dd92a2668b1a26e48910d5f739326ba8824f0e1dc900695287053672022-05-24 8:54:251 day 20 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0xcfc296465be3065602878cd2db71ee7fdd693e8c28eb04d307c7375b01e24ce0287051522022-05-24 8:47:031 day 20 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468400 MATIC
0xd92f7db7b61c0bb84a8c56b4d8c83e9514f5feaec7d9e2a6c3a5743504e7abd8287035722022-05-24 7:52:381 day 21 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x0e1df631eaf0fde1c0fcd5c10560f5a0deb94e713a8917d3a5742666acf91432286998262022-05-24 5:41:131 day 23 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468430 MATIC
0x01389695c7ee4d78d8a2a512baeb77b3732d555f869f2bf219fa6f852bc3c9eb286876872022-05-23 22:24:452 days 6 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb46833.89979254 MATIC
0xead75f23b080da34a02d8954a8c04047d505f04f01b8ff14d0902fd777ab5c0c286755242022-05-23 15:13:132 days 13 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb4680.5 MATIC
0x5c83efc45cc063591a00fc3a30b9e23b90bb2cc4bf25076bbb35a664808ede39286720312022-05-23 13:13:062 days 15 hrs ago 0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6 0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb46857 MATIC
[ Download CSV Export 
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.

Contract Source Code Verified (Exact Match)

Contract Name:
Exchange_v3_1

Compiler Version
v0.8.10+commit.fc410830

Optimization Enabled:
Yes with 1 runs

Other Settings:
default evmVersion, GNU LGPLv3 license
File 1 of 38 : Address.sol
// 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;
        // solhint-disable-next-line no-inline-assembly
        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");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (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");

        // solhint-disable-next-line avoid-low-level-calls
        (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");

        // solhint-disable-next-line avoid-low-level-calls
        (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");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private 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

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 2 of 38 : AssetRegistry.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { Address } from './Address.sol';

import { Asset } from './Structs.sol';
import { AssetTransfers } from './AssetTransfers.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { IERC20 } from './Interfaces.sol';

/**
 * @notice Library helper functions for reading from a registry of asset descriptors indexed by address and symbol
 */
library AssetRegistry {
  struct Storage {
    mapping(address => 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 => Asset[]) assetsBySymbol;
    // Blockchain-specific native asset symbol
    string nativeAssetSymbol;
  }

  // Admin //

  function registerToken(
    AssetRegistry.Storage storage self,
    IERC20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external {
    require(decimals <= 32, 'Token cannot have more than 32 decimals');
    require(
      tokenAddress != IERC20(address(0x0)) &&
        Address.isContract(address(tokenAddress)),
      'Invalid token 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 token symbol');
    require(
      !self.assetsByAddress[address(tokenAddress)].isConfirmed,
      'Token already finalized'
    );

    self.assetsByAddress[address(tokenAddress)] = Asset({
      exists: true,
      assetAddress: address(tokenAddress),
      symbol: symbol,
      decimals: decimals,
      isConfirmed: false,
      confirmedTimestampInMs: 0
    });
  }

  function confirmTokenRegistration(
    AssetRegistry.Storage storage self,
    IERC20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external {
    Asset memory asset = self.assetsByAddress[address(tokenAddress)];
    require(asset.exists, 'Unknown token');
    require(!asset.isConfirmed, 'Token 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(
    AssetRegistry.Storage storage self,
    IERC20 tokenAddress,
    string calldata symbol
  ) external {
    Asset memory asset = self.assetsByAddress[address(tokenAddress)];
    require(
      asset.exists && asset.isConfirmed,
      'Registration of token not finalized'
    );
    require(
      !isStringEqual(symbol, self.nativeAssetSymbol),
      'Symbol reserved for native asset'
    );

    // This will prevent swapping assets for previously existing orders
    uint64 msInOneSecond = 1000;
    asset.confirmedTimestampInMs = uint64(block.timestamp * msInOneSecond);

    self.assetsBySymbol[symbol].push(asset);
  }

  function skim(address tokenAddress, address feeWallet) external {
    require(Address.isContract(tokenAddress), 'Invalid token address');

    uint256 balance = IERC20(tokenAddress).balanceOf(address(this));
    AssetTransfers.transferTo(payable(feeWallet), tokenAddress, balance);
  }

  // Accessors //

  function loadBalanceInAssetUnitsByAddress(
    AssetRegistry.Storage storage self,
    address wallet,
    address assetAddress,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid wallet address');

    Asset memory asset = loadAssetByAddress(self, assetAddress);
    return
      AssetUnitConversions.pipsToAssetUnits(
        loadBalanceInPipsFromMigrationSourceIfNeeded(
          wallet,
          assetAddress,
          balanceTracking
        ),
        asset.decimals
      );
  }

  function loadBalanceInAssetUnitsBySymbol(
    AssetRegistry.Storage storage self,
    address wallet,
    string calldata assetSymbol,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid wallet address');

    Asset memory asset =
      loadAssetBySymbol(self, assetSymbol, getCurrentTimestampInMs());
    return
      AssetUnitConversions.pipsToAssetUnits(
        loadBalanceInPipsFromMigrationSourceIfNeeded(
          wallet,
          asset.assetAddress,
          balanceTracking
        ),
        asset.decimals
      );
  }

  function loadBalanceInPipsByAddress(
    address wallet,
    address assetAddress,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint64) {
    require(wallet != address(0x0), 'Invalid wallet address');

    return
      loadBalanceInPipsFromMigrationSourceIfNeeded(
        wallet,
        assetAddress,
        balanceTracking
      );
  }

  function loadBalanceInPipsBySymbol(
    Storage storage self,
    address wallet,
    string calldata assetSymbol,
    BalanceTracking.Storage storage balanceTracking
  ) external view returns (uint64) {
    require(wallet != address(0x0), 'Invalid wallet address');

    address assetAddress =
      loadAssetBySymbol(self, assetSymbol, getCurrentTimestampInMs())
        .assetAddress;
    return
      loadBalanceInPipsFromMigrationSourceIfNeeded(
        wallet,
        assetAddress,
        balanceTracking
      );
  }

  function loadBalanceInPipsFromMigrationSourceIfNeeded(
    address wallet,
    address assetAddress,
    BalanceTracking.Storage storage balanceTracking
  ) private view returns (uint64) {
    BalanceTracking.Balance memory balance =
      balanceTracking.balancesByWalletAssetPair[wallet][assetAddress];

    if (
      !balance.isMigrated &&
      address(balanceTracking.migrationSource) != address(0x0)
    ) {
      return
        balanceTracking.migrationSource.loadBalanceInPipsByAddress(
          wallet,
          assetAddress
        );
    }

    return balance.balanceInPips;
  }

  /**
   * @dev Resolves an asset address into corresponding Asset struct
   *
   * @param assetAddress Ethereum address of asset
   */
  function loadAssetByAddress(Storage storage self, address assetAddress)
    internal
    view
    returns (Asset memory)
  {
    if (assetAddress == address(0x0)) {
      return getEthAsset(self.nativeAssetSymbol);
    }

    Asset memory asset = self.assetsByAddress[assetAddress];
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found for address'
    );

    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
  ) internal view returns (Asset memory) {
    if (isStringEqual(self.nativeAssetSymbol, symbol)) {
      return getEthAsset(self.nativeAssetSymbol);
    }

    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 for symbol'
    );

    return asset;
  }

  // Util //

  function getCurrentTimestampInMs() internal view returns (uint64) {
    uint64 msInOneSecond = 1000;

    return uint64(block.timestamp) * msInOneSecond;
  }

  /**
   * @dev ETH is modeled as an always-confirmed Asset struct for programmatic consistency
   */
  function getEthAsset(string memory nativeAssetSymbol)
    private
    pure
    returns (Asset memory)
  {
    return Asset(true, address(0x0), nativeAssetSymbol, 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));
  }
}

File 3 of 38 : AssetTransfers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { IERC20 } 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 {
  /**
   * @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 ETH is sent in the deposit transaction via `msg.value`
   */
  function transferFrom(
    address wallet,
    IERC20 tokenAddress,
    address to,
    uint256 quantityInAssetUnits
  ) internal {
    uint256 balanceBefore = tokenAddress.balanceOf(to);

    // Because we check for the expected balance change we can safely ignore the return value of transferFrom
    tokenAddress.transferFrom(wallet, to, quantityInAssetUnits);

    uint256 balanceAfter = tokenAddress.balanceOf(to);
    require(
      balanceAfter - balanceBefore == quantityInAssetUnits,
      'Token contract returned transferFrom success without expected balance change'
    );
  }

  /**
   * @dev Transfers ETH or token assets from a contract to a wallet when withdrawing or removing liquidity
   */
  function transferTo(
    address payable walletOrContract,
    address asset,
    uint256 quantityInAssetUnits
  ) internal {
    if (asset == address(0x0)) {
      require(
        walletOrContract.send(quantityInAssetUnits),
        'ETH transfer failed'
      );
    } else {
      uint256 balanceBefore = IERC20(asset).balanceOf(walletOrContract);

      // Because we check for the expected balance change we can safely ignore the return value of transfer
      IERC20(asset).transfer(walletOrContract, quantityInAssetUnits);

      uint256 balanceAfter = IERC20(asset).balanceOf(walletOrContract);
      require(
        balanceAfter - balanceBefore == quantityInAssetUnits,
        'Token contract returned transfer success without expected balance change'
      );
    }
  }
}

File 4 of 38 : AssetUnitConversions.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

/**
 * @notice Library helpers for converting asset quantities between asset units and pips
 */
library AssetUnitConversions {
  function pipsToAssetUnits(uint64 quantityInPips, uint8 assetDecimals)
    internal
    pure
    returns (uint256)
  {
    require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals');

    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      return uint256(quantityInPips) * (uint256(10)**(assetDecimals - 8));
    }
    return uint256(quantityInPips) / (uint256(10)**(8 - assetDecimals));
  }

  function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals)
    internal
    pure
    returns (uint64)
  {
    require(assetDecimals <= 32, 'Asset cannot have more than 32 decimals');

    uint256 quantityInPips;
    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      quantityInPips =
        quantityInAssetUnits /
        (uint256(10)**(assetDecimals - 8));
    } else {
      quantityInPips =
        quantityInAssetUnits *
        (uint256(10)**(8 - assetDecimals));
    }
    require(quantityInPips < 2**64, 'Pip quantity overflows uint64');

    return uint64(quantityInPips);
  }
}

File 5 of 38 : BalanceTracking.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { OrderSide } from './Enums.sol';
import { PoolTradeHelpers } from './PoolTradeHelpers.sol';
import {
  Asset,
  HybridTrade,
  LiquidityAddition,
  LiquidityChangeExecution,
  LiquidityRemoval,
  Order,
  OrderBookTrade,
  PoolTrade,
  Withdrawal
} from './Structs.sol';
import { IExchange, ILiquidityProviderToken } from './Interfaces.sol';

library BalanceTracking {
  using AssetRegistry for AssetRegistry.Storage;
  using PoolTradeHelpers for PoolTrade;

  struct Balance {
    bool isMigrated;
    uint64 balanceInPips;
  }

  struct Storage {
    mapping(address => mapping(address => Balance)) balancesByWalletAssetPair;
    // Predecessor Exchange contract from which to lazily migrate balances
    IExchange migrationSource;
  }

  // Depositing //

  function updateForDeposit(
    Storage storage self,
    address wallet,
    address assetAddress,
    uint64 quantityInPips
  ) internal returns (uint64 newBalanceInPips) {
    Balance storage balance =
      loadBalanceAndMigrateIfNeeded(self, wallet, assetAddress);
    balance.balanceInPips += quantityInPips;

    return balance.balanceInPips;
  }

  // Trading //

  /**
   * @dev Updates buyer, seller, and fee wallet balances for both assets in trade pair according to
   * trade parameters
   */
  function updateForOrderBookTrade(
    Storage storage self,
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory trade,
    address feeWallet
  ) internal {
    Balance storage balance;

    // Seller gives base asset including fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      sell.walletAddress,
      trade.baseAssetAddress
    );
    balance.balanceInPips -= trade.grossBaseQuantityInPips;
    // Buyer receives base asset minus fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      buy.walletAddress,
      trade.baseAssetAddress
    );
    balance.balanceInPips += trade.netBaseQuantityInPips;

    // Buyer gives quote asset including fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      buy.walletAddress,
      trade.quoteAssetAddress
    );
    balance.balanceInPips -= trade.grossQuoteQuantityInPips;
    // Seller receives quote asset minus fees
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      sell.walletAddress,
      trade.quoteAssetAddress
    );
    balance.balanceInPips += trade.netQuoteQuantityInPips;

    // Maker fee to fee wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      trade.makerFeeAssetAddress
    );
    balance.balanceInPips += trade.makerFeeQuantityInPips;
    // Taker fee to fee wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      trade.takerFeeAssetAddress
    );
    balance.balanceInPips += trade.takerFeeQuantityInPips;
  }

  function updateForPoolTrade(
    Storage storage self,
    Order memory order,
    PoolTrade memory poolTrade,
    address feeWallet
  ) internal {
    Balance storage balance;

    // Debit from order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      order.walletAddress,
      poolTrade.getOrderDebitAssetAddress(order.side)
    );
    balance.balanceInPips -= poolTrade.getOrderDebitQuantityInPips(order.side);
    // Credit to order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      order.walletAddress,
      poolTrade.getOrderCreditAssetAddress(order.side)
    );
    balance.balanceInPips += poolTrade.calculateOrderCreditQuantityInPips(
      order.side
    );

    // Fee wallet receives protocol fee from asset debited from order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      poolTrade.getOrderDebitAssetAddress(order.side)
    );
    balance.balanceInPips += poolTrade.takerProtocolFeeQuantityInPips;
    // Fee wallet receives gas fee from asset credited to order wallet
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      poolTrade.getOrderCreditAssetAddress(order.side)
    );
    balance.balanceInPips += poolTrade.takerGasFeeQuantityInPips;

    // Liquidity pool reserves are updated in LiquidityPoolRegistry
  }

  function updateForHybridTradeFees(
    Storage storage self,
    HybridTrade memory hybridTrade,
    address takerWallet,
    address feeWallet
  ) internal {
    Balance storage balance;
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      hybridTrade.orderBookTrade.takerFeeAssetAddress
    );
    balance.balanceInPips += hybridTrade.takerGasFeeQuantityInPips;

    balance = loadBalanceAndMigrateIfNeeded(
      self,
      takerWallet,
      hybridTrade.orderBookTrade.takerFeeAssetAddress
    );
    balance.balanceInPips -=
      hybridTrade.takerGasFeeQuantityInPips +
      hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips;

    // Liquidity pool reserves are updated in LiquidityPoolRegistry
  }

  // Withdrawing //

  function updateForWithdrawal(
    Storage storage self,
    Withdrawal memory withdrawal,
    address assetAddress,
    address feeWallet
  ) internal returns (uint64 newExchangeBalanceInPips) {
    Balance storage balance;

    balance = loadBalanceAndMigrateIfNeeded(
      self,
      withdrawal.walletAddress,
      assetAddress
    );
    // Reverts if balance is overdrawn
    balance.balanceInPips -= withdrawal.grossQuantityInPips;
    newExchangeBalanceInPips = balance.balanceInPips;

    if (withdrawal.gasFeeInPips > 0) {
      balance = loadBalanceAndMigrateIfNeeded(self, feeWallet, assetAddress);

      balance.balanceInPips += withdrawal.gasFeeInPips;
    }
  }

  // Wallet exits //

  function updateForExit(
    Storage storage self,
    address wallet,
    address assetAddress
  ) internal returns (uint64 previousExchangeBalanceInPips) {
    Balance storage balance;

    balance = loadBalanceAndMigrateIfNeeded(self, wallet, assetAddress);
    previousExchangeBalanceInPips = balance.balanceInPips;

    require(previousExchangeBalanceInPips > 0, 'No balance for asset');

    balance.balanceInPips = 0;
  }

  // Liquidity pools //

  function updateForAddLiquidity(
    Storage storage self,
    LiquidityAddition memory addition,
    LiquidityChangeExecution memory execution,
    address feeWallet,
    address custodianAddress,
    ILiquidityProviderToken liquidityProviderToken
  ) internal returns (uint64 outputLiquidityInPips) {
    // Base gross debit
    Balance storage balance =
      loadBalanceAndMigrateIfNeeded(
        self,
        addition.wallet,
        execution.baseAssetAddress
      );
    balance.balanceInPips -= execution.grossBaseQuantityInPips;

    // Base fee credit
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      execution.baseAssetAddress
    );
    balance.balanceInPips +=
      execution.grossBaseQuantityInPips -
      execution.netBaseQuantityInPips;

    // Quote gross debit

    balance = loadBalanceAndMigrateIfNeeded(
      self,
      addition.wallet,
      execution.quoteAssetAddress
    );
    balance.balanceInPips -= execution.grossQuoteQuantityInPips;

    // Quote fee credit
    balance = loadBalanceAndMigrateIfNeeded(
      self,
      feeWallet,
      execution.quoteAssetAddress
    );
    balance.balanceInPips +=
      execution.grossQuoteQuantityInPips -
      execution.netQuoteQuantityInPips;

    // Only add output assets to wallet's balances in the Exchange if Custodian is target
    if (addition.to == custodianAddress) {
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        addition.wallet,
        address(liquidityProviderToken)
      );
      balance.balanceInPips += execution.liquidityInPips;
    } else {
      outputLiquidityInPips = execution.liquidityInPips;
    }
  }

  function updateForRemoveLiquidity(
    Storage storage self,
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    address feeWallet,
    address custodianAddress,
    ILiquidityProviderToken liquidityProviderToken
  )
    internal
    returns (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    )
  {
    Balance storage balance;

    // Base asset updates
    {
      // Only add output assets to wallet's balances in the Exchange if Custodian is target
      if (removal.to == custodianAddress) {
        // Base net credit
        balance = loadBalanceAndMigrateIfNeeded(
          self,
          removal.wallet,
          execution.baseAssetAddress
        );
        balance.balanceInPips += execution.netBaseQuantityInPips;
      } else {
        outputBaseAssetQuantityInPips = execution.netBaseQuantityInPips;
      }

      // Base fee credit
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        feeWallet,
        execution.baseAssetAddress
      );
      balance.balanceInPips +=
        execution.grossBaseQuantityInPips -
        execution.netBaseQuantityInPips;
    }

    // Quote asset updates
    {
      // Only add output assets to wallet's balances in the Exchange if Custodian is target
      if (removal.to == custodianAddress) {
        // Quote net credit
        balance = loadBalanceAndMigrateIfNeeded(
          self,
          removal.wallet,
          execution.quoteAssetAddress
        );
        balance.balanceInPips += execution.netQuoteQuantityInPips;
      } else {
        outputQuoteAssetQuantityInPips = execution.netQuoteQuantityInPips;
      }

      // Quote fee credit
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        feeWallet,
        execution.quoteAssetAddress
      );
      balance.balanceInPips +=
        execution.grossQuoteQuantityInPips -
        execution.netQuoteQuantityInPips;
    }

    // Pair token burn
    {
      balance = loadBalanceAndMigrateIfNeeded(
        self,
        removal.wallet,
        address(liquidityProviderToken)
      );
      balance.balanceInPips -= execution.liquidityInPips;
    }
  }

  // Helpers //

  function loadBalanceAndMigrateIfNeeded(
    Storage storage self,
    address wallet,
    address assetAddress
  ) private returns (Balance storage) {
    Balance storage balance =
      self.balancesByWalletAssetPair[wallet][assetAddress];

    if (!balance.isMigrated && address(self.migrationSource) != address(0x0)) {
      balance.balanceInPips = self.migrationSource.loadBalanceInPipsByAddress(
        wallet,
        assetAddress
      );
      balance.isMigrated = true;
    }

    return balance;
  }
}

File 6 of 38 : Constants.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

/**
 * @dev See GOVERNANCE.md for descriptions of fixed parameters and fees
 */

library Constants {
  // 100 basis points/percent * 100 percent/total
  uint64 public constant basisPointsInTotal = 100 * 100;

  uint64 public constant depositIndexNotSet = 2**64 - 1;

  uint8 public constant liquidityProviderTokenDecimals = 18;

  // 1 week at 3s/block
  uint256 public constant maxChainPropagationPeriod = (7 * 24 * 60 * 60) / 3;

  // 20%
  uint64 public constant maxFeeBasisPoints = 20 * 100;

  // Pool reserve balance ratio above which price dips below 1 pip and can no longer be represented
  uint64 public constant maxLiquidityPoolReserveRatio = 10**8;

  // Pool reserve balance below which prices can no longer be represented with full pip precision
  uint64 public constant minLiquidityPoolReserveInPips = 10**8;

  // 2%
  uint64 public constant maxPoolInputFeeBasisPoints = 2 * 100;

  // 5%
  uint64 public constant maxPoolOutputAdjustmentBasisPoints = 5 * 100;

  // 1%
  uint64 public constant maxPoolPriceCorrectionBasisPoints = 1 * 100;

  // To convert integer pips to a fractional price shift decimal left by the pip precision of 8
  // decimals places
  uint64 public constant pipPriceMultiplier = 10**8;

  // To convert double integer pips to a fractional price shift decimal left by the double pip
  // precision of 16 decimals places
  uint64 public constant doublePipPriceMultiplier = 10**16;

  uint8 public constant signatureHashVersion = 4;
}

File 7 of 38 : Context.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

File 8 of 38 : Custodian.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { Address } from './Address.sol';

import { ICustodian } from './Interfaces.sol';
import { Owned } from './Owned.sol';
import { AssetTransfers } from './AssetTransfers.sol';

/**
 * @notice The Custodian contract. Holds custody of all deposited funds for whitelisted Exchange
 * contract with minimal additional logic
 */
contract Custodian is ICustodian, Owned {
  // Events //

  /**
   * @notice Emitted on construction and when Governance upgrades the Exchange contract address
   */
  event ExchangeChanged(address oldExchange, address newExchange);
  /**
   * @notice Emitted on construction and when Governance replaces itself by upgrading the Governance contract address
   */
  event GovernanceChanged(address oldGovernance, address newGovernance);

  address _exchange;
  address _governance;

  /**
   * @notice Instantiate a new Custodian
   *
   * @dev Sets `owner` and `admin` to `msg.sender`. Sets initial values for Exchange and Governance
   * contract addresses, after which they can only be changed by the currently set Governance contract
   * itself
   *
   * @param exchange Address of deployed Exchange contract to whitelist
   * @param governance ddress of deployed Governance contract to whitelist
   */
  constructor(address exchange, address governance) Owned() {
    require(Address.isContract(exchange), 'Invalid exchange contract address');
    require(
      Address.isContract(governance),
      'Invalid governance contract address'
    );

    _exchange = exchange;
    _governance = governance;

    emit ExchangeChanged(address(0x0), exchange);
    emit GovernanceChanged(address(0x0), governance);
  }

  /**
   * @notice ETH can only be sent by the Exchange
   */
  receive() external payable override onlyExchange {}

  /**
   * @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 (ETH or ERC-20 contract)
   * @param quantityInAssetUnits The quantity in asset units to withdraw
   */
  function withdraw(
    address payable wallet,
    address asset,
    uint256 quantityInAssetUnits
  ) external override onlyExchange {
    AssetTransfers.transferTo(wallet, asset, quantityInAssetUnits);
  }

  /**
   * @notice Load address of the currently whitelisted Exchange contract
   *
   * @return The address of the currently whitelisted Exchange contract
   */
  function loadExchange() external view override returns (address) {
    return _exchange;
  }

  /**
   * @notice Sets a new Exchange contract address
   *
   * @param newExchange The address of the new whitelisted Exchange contract
   */
  function setExchange(address newExchange) external override onlyGovernance {
    require(Address.isContract(newExchange), 'Invalid contract address');

    address oldExchange = _exchange;
    _exchange = newExchange;

    emit ExchangeChanged(oldExchange, newExchange);
  }

  /**
   * @notice Load address of the currently whitelisted Governance contract
   *
   * @return The address of the currently whitelisted Governance contract
   */
  function loadGovernance() external view override returns (address) {
    return _governance;
  }

  /**
   * @notice Sets a new Governance contract address
   *
   * @param newGovernance The address of the new whitelisted Governance contract
   */
  function setGovernance(address newGovernance)
    external
    override
    onlyGovernance
  {
    require(Address.isContract(newGovernance), 'Invalid contract address');

    address oldGovernance = _governance;
    _governance = newGovernance;

    emit GovernanceChanged(oldGovernance, newGovernance);
  }

  // RBAC //

  modifier onlyExchange() {
    require(msg.sender == _exchange, 'Caller must be Exchange contract');
    _;
  }

  modifier onlyGovernance() {
    require(msg.sender == _governance, 'Caller must be Governance contract');
    _;
  }
}

File 9 of 38 : Depositing.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetTransfers } from './AssetTransfers.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { ICustodian, IERC20 } from './Interfaces.sol';
import {
  Asset,
  LiquidityAdditionDepositResult,
  LiquidityRemovalDepositResult
} from './Structs.sol';

library Depositing {
  using AssetRegistry for AssetRegistry.Storage;
  using BalanceTracking for BalanceTracking.Storage;

  /**
   * @dev delegatecall entry point for `Exchange` when depositing native or token assets
   */
  function deposit(
    address wallet,
    Asset memory asset,
    uint256 quantityInAssetUnits,
    ICustodian custodian,
    BalanceTracking.Storage storage balanceTracking
  )
    public
    returns (
      uint64 quantityInPips,
      uint64 newExchangeBalanceInPips,
      uint256 newExchangeBalanceInAssetUnits
    )
  {
    return
      depositAsset(
        wallet,
        asset,
        quantityInAssetUnits,
        custodian,
        balanceTracking
      );
  }

  function depositLiquidityReserves(
    address wallet,
    address assetA,
    address assetB,
    uint256 quantityAInAssetUnits,
    uint256 quantityBInAssetUnits,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) internal returns (LiquidityAdditionDepositResult memory result) {
    Asset memory asset;

    asset = assetRegistry.loadAssetByAddress(assetA);
    result.assetASymbol = asset.symbol;
    (
      result.assetAQuantityInPips,
      result.assetANewExchangeBalanceInPips,
      result.assetANewExchangeBalanceInAssetUnits
    ) = depositAsset(
      wallet,
      asset,
      quantityAInAssetUnits,
      custodian,
      balanceTracking
    );

    asset = assetRegistry.loadAssetByAddress(assetB);
    result.assetBSymbol = asset.symbol;
    (
      result.assetBQuantityInPips,
      result.assetBNewExchangeBalanceInPips,
      result.assetBNewExchangeBalanceInAssetUnits
    ) = depositAsset(
      wallet,
      asset,
      quantityBInAssetUnits,
      custodian,
      balanceTracking
    );
  }

  function depositLiquidityTokens(
    address wallet,
    address liquidityProviderToken,
    uint256 quantityInAssetUnits,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) internal returns (LiquidityRemovalDepositResult memory result) {
    Asset memory asset =
      assetRegistry.loadAssetByAddress(liquidityProviderToken);
    result.assetSymbol = asset.symbol;
    result.assetAddress = liquidityProviderToken;

    (
      result.assetQuantityInPips,
      result.assetNewExchangeBalanceInPips,
      result.assetNewExchangeBalanceInAssetUnits
    ) = depositAsset(
      wallet,
      asset,
      quantityInAssetUnits,
      custodian,
      balanceTracking
    );
  }

  function depositAsset(
    address wallet,
    Asset memory asset,
    uint256 quantityInAssetUnits,
    ICustodian custodian,
    BalanceTracking.Storage storage balanceTracking
  )
    internal
    returns (
      uint64 quantityInPips,
      uint64 newExchangeBalanceInPips,
      uint256 newExchangeBalanceInAssetUnits
    )
  {
    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 ETH, 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);

    // Forward the funds to the `Custodian`
    if (asset.assetAddress == address(0x0)) {
      // If the asset is ETH then the funds were already assigned to the `Exchange` via msg.value.
      AssetTransfers.transferTo(
        payable(address(custodian)),
        asset.assetAddress,
        quantityInAssetUnitsWithoutFractionalPips
      );
    } else {
      // If the asset is a token,  call the transferFrom function on the token contract for the
      // pre-approved asset quantity
      AssetTransfers.transferFrom(
        wallet,
        IERC20(asset.assetAddress),
        payable(address(custodian)),
        quantityInAssetUnitsWithoutFractionalPips
      );
    }

    // Update balance with actual transferred quantity
    newExchangeBalanceInPips = balanceTracking.updateForDeposit(
      wallet,
      asset.assetAddress,
      quantityInPips
    );
    newExchangeBalanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      newExchangeBalanceInPips,
      asset.decimals
    );
  }
}

File 10 of 38 : ECDSA.sol
// 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 {
    /**
     * @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) {
        // Divide the signature in r, s and v variables
        bytes32 r;
        bytes32 s;
        uint8 v;

        // 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) {
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
        } else if (signature.length == 64) {
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                let vs := mload(add(signature, 0x40))
                r := mload(add(signature, 0x20))
                s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
                v := add(shr(255, vs), 27)
            }
        } else {
            revert("ECDSA: invalid signature length");
        }

        return recover(hash, v, r, s);
    }

    /**
     * @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) {
        // 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 (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): 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.
        require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
        require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @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));
    }
}

File 11 of 38 : ERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./IERC20Metadata.sol";
import "./Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The defaut value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the value {ERC20} uses, unless this function is
     * overridden;
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, _msgSender(), currentAllowance - amount);

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        _approve(_msgSender(), spender, currentAllowance - subtractedValue);

        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        _balances[account] = accountBalance - amount;
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be to transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

File 12 of 38 : Enums.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

/**
 * @notice Enums definitions
 */

// Liquidity pools //

enum LiquidityChangeOrigination { OnChain, OffChain }

enum LiquidityChangeType { Addition, Removal }

enum LiquidityChangeState { NotInitiated, Initiated, Executed }

// Order book //

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
}

// Withdrawals //

enum WithdrawalType { BySymbol, ByAddress }

File 13 of 38 : Exchange.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { Address } from './Address.sol';

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { Constants } from './Constants.sol';
import { Depositing } from './Depositing.sol';
import { LiquidityPools } from './LiquidityPools.sol';
import { LiquidityPoolAdmin } from './LiquidityPoolAdmin.sol';
import { NonceInvalidations } from './NonceInvalidations.sol';
import { Owned } from './Owned.sol';
import { Trading } from './Trading.sol';
import { UUID } from './UUID.sol';
import { Withdrawing } from './Withdrawing.sol';
import { LiquidityChangeOrigination, OrderSide } from './Enums.sol';
import {
  ICustodian,
  IERC20,
  IExchange,
  ILiquidityProviderToken,
  IWETH9
} from './Interfaces.sol';
import {
  Asset,
  HybridTrade,
  LiquidityAddition,
  LiquidityAdditionDepositResult,
  LiquidityChangeExecution,
  LiquidityMigration,
  LiquidityPool,
  LiquidityRemoval,
  LiquidityRemovalDepositResult,
  NonceInvalidation,
  Order,
  OrderBookTrade,
  PoolTrade,
  Withdrawal
} from './Structs.sol';

/**
 * @notice The Exchange contract. Implements all deposit, trade, and withdrawal logic and
 * associated balance tracking
 *
 * @dev The term `asset` refers collectively to ETH and ERC-20 tokens, the term `token` refers only
 * to the latter
 */
contract Exchange_v3_1 is IExchange, Owned {
  using AssetRegistry for AssetRegistry.Storage;
  using BalanceTracking for BalanceTracking.Storage;
  using LiquidityPools for LiquidityPools.Storage;
  using LiquidityPoolAdmin for LiquidityPools.Storage;
  using NonceInvalidations for mapping(address => NonceInvalidation);

  // 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 ETH with `depositEther` or a token with
   * `depositTokenByAddress` or `depositTokenBySymbol`
   */
  event Deposited(
    uint64 index,
    address wallet,
    address assetAddress,
    string assetSymbol,
    uint64 quantityInPips,
    uint64 newExchangeBalanceInPips,
    uint256 newExchangeBalanceInAssetUnits
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a hybrid trade for execution with
   * `executeHybridTrade`
   */
  event HybridTradeExecuted(
    address buyWallet,
    address sellWallet,
    string baseAssetSymbol,
    string quoteAssetSymbol,
    uint64 orderBookBaseQuantityInPips,
    uint64 orderBookQuoteQuantityInPips,
    uint64 poolBaseQuantityInPips,
    uint64 poolQuoteQuantityInPips,
    uint64 totalBaseQuantityInPips,
    uint64 totalQuoteQuantityInPips,
    OrderSide takerSide
  );
  /**
   * @notice Emitted when a user initiates an Add Liquidity request via `addLiquidity` or
   * `addLiquidityETH`
   */
  event LiquidityAdditionInitiated(
    address wallet,
    address assetA,
    address assetB,
    uint256 amountADesired,
    uint256 amountBDesired,
    uint256 amountAMin,
    uint256 amountBMin,
    address to,
    uint256 deadline
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a liquidity addition for execution with
   * `executeAddLiquidity`
   */
  event LiquidityAdditionExecuted(
    address wallet,
    address baseAssetAddress,
    address quoteAssetAddress,
    uint64 baseQuantityInPips,
    uint64 quoteQuantityInPips,
    uint64 liquidityInPips
  );
  /**
   * @notice Emitted when an Admin switches liquidity pool asset direction via
   * `reverseLiquidityPoolAssets`
   */
  event LiquidityPoolAssetsReversed(
    address originalBaseAssetAddress,
    address originalQuoteAssetAddress
  );
  /**
   * @notice Emitted when a user initiates a Remove Liquidity request via `removeLiquidity` or
   * `removeLiquidityETH`
   */
  event LiquidityRemovalInitiated(
    address wallet,
    address assetA,
    address assetB,
    uint256 liquidity,
    uint256 amountAMin,
    uint256 amountBMin,
    address to,
    uint256 deadline
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a liquidity removal for execution with
   * `executeRemoveLiquidity`
   */
  event LiquidityRemovalExecuted(
    address wallet,
    address baseAssetAddress,
    address quoteAssetAddress,
    uint64 baseQuantityInPips,
    uint64 quoteQuantityInPips,
    uint64 liquidityInPips
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a trade for execution with
   * `executeOrderBookTrade`
   */
  event OrderBookTradeExecuted(
    address buyWallet,
    address sellWallet,
    string baseAssetSymbol,
    string quoteAssetSymbol,
    uint64 baseQuantityInPips,
    uint64 quoteQuantityInPips,
    OrderSide takerSide
  );
  /**
   * @notice Emitted when a user invalidates an order nonce with `invalidateOrderNonce`
   */
  event OrderNonceInvalidated(
    address wallet,
    uint128 nonce,
    uint128 timestampInMs,
    uint256 effectiveBlockNumber
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits a pool trade for execution with
   * `executePoolTrade`
   */
  event PoolTradeExecuted(
    address wallet,
    string baseAssetSymbol,
    string quoteAssetSymbol,
    uint64 baseQuantityInPips,
    uint64 quoteQuantityInPips,
    OrderSide takerSide
  );
  /**
   * @notice Emitted when an admin adds a symbol to a previously registered and confirmed token
   * via `addTokenSymbol`
   */
  event TokenSymbolAdded(IERC20 assetAddress, string assetSymbol);
  /**
   * @notice Emitted when a user invokes the Exit Wallet mechanism with `exitWallet`
   */
  event WalletExited(address wallet, uint256 effectiveBlockNumber);
  /**
   * @notice Emitted when a user withdraws liquidity reserve assets through the Exit Wallet
   * mechanism with `removeLiquidityExit`
   */
  event WalletExitLiquidityRemoved(
    address wallet,
    address baseAssetAddress,
    address quoteAssetAddress,
    uint64 baseAssetQuantityInPips,
    uint64 quoteAssetQuantityInPips
  );
  /**
   * @notice Emitted when a user withdraws an asset balance through the Exit Wallet mechanism with
   * `withdrawExit`
   */
  event WalletExitWithdrawn(
    address wallet,
    address assetAddress,
    uint64 quantityInPips
  );
  /**
   * @notice Emitted when a user clears the exited status of a wallet previously exited with
   * `exitWallet`
   */
  event WalletExitCleared(address wallet);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a withdrawal with `withdraw`
   */
  event Withdrawn(
    address wallet,
    address assetAddress,
    string assetSymbol,
    uint64 quantityInPips,
    uint64 newExchangeBalanceInPips,
    uint256 newExchangeBalanceInAssetUnits
  );

  // Internally used structs //

  struct WalletExit {
    bool exists;
    uint256 effectiveBlockNumber;
  }

  // Storage //

  // Asset registry data
  AssetRegistry.Storage _assetRegistry;
  // Balance tracking
  BalanceTracking.Storage _balanceTracking;
  // CLOB - mapping of order wallet hash => isComplete
  mapping(bytes32 => bool) _completedOrderHashes;
  // CLOB - mapping of wallet => last invalidated timestampInMs
  mapping(address => NonceInvalidation) _nonceInvalidations;
  // CLOB - mapping of order hash => filled quantity in pips
  mapping(bytes32 => uint64) _partiallyFilledOrderQuantitiesInPips;
  // Custodian
  ICustodian _custodian;
  // Deposit index
  uint64 public _depositIndex;
  // Exits
  mapping(address => WalletExit) public _walletExits;
  // Liquidity pools
  address _liquidityMigrator;
  LiquidityPools.Storage _liquidityPools;
  // Withdrawals - mapping of withdrawal wallet hash => isComplete
  mapping(bytes32 => bool) _completedWithdrawalHashes;
  // Tunable parameters
  uint256 _chainPropagationPeriod;
  address _dispatcherWallet;
  address _feeWallet;

  /**
   * @notice Instantiate a new `Exchange` contract
   *
   * @dev Sets `_balanceTracking.migrationSource` to first argument, and `_owner` and `_admin` to
   * `msg.sender`
   */
  constructor(
    IExchange balanceMigrationSource,
    address feeWallet,
    string memory nativeAssetSymbol
  ) Owned() {
    require(
      address(balanceMigrationSource) == address(0x0) ||
        Address.isContract(address(balanceMigrationSource)),
      'Invalid migration source'
    );
    _balanceTracking.migrationSource = balanceMigrationSource;

    setFeeWallet(feeWallet);

    _assetRegistry.nativeAssetSymbol = nativeAssetSymbol;

    // Deposits must be manually enabled via `setDepositIndex`
    _depositIndex = Constants.depositIndexNotSet;
  }

  /**
   * @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(ICustodian newCustodian) external onlyAdmin {
    require(
      _custodian == ICustodian(payable(address(0x0))),
      'Custodian can only be set once'
    );
    require(Address.isContract(address(newCustodian)), 'Invalid address');

    _custodian = newCustodian;
  }

  /**
   * @notice Enable depositing assets into the Exchange by setting the current deposit index from
   * the old Exchange contract's value. This function can only be called once
   *
   */
  function setDepositIndex() external onlyAdmin {
    require(
      _depositIndex == Constants.depositIndexNotSet,
      'Can only be set once'
    );

    _depositIndex = address(_balanceTracking.migrationSource) == address(0x0)
      ? 0
      : _balanceTracking.migrationSource._depositIndex();
  } 

  /*** Tunable parameters ***/

  /**
   * @notice Sets a new Chain Propagation Period - the block delay after which order nonce
   * invalidations and wallet exits go into effect
   *
   * @param newChainPropagationPeriod The new Chain Propagation Period expressed as a number of
   * blocks. Must be less than `Constants.maxChainPropagationPeriod`
   */
  function setChainPropagationPeriod(uint256 newChainPropagationPeriod)
    external
    onlyAdmin
  {
    require(
      newChainPropagationPeriod < Constants.maxChainPropagationPeriod,
      'New period greater than max'
    );

    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 `_balances` mappings for this wallet
   * @dev Visibility public instead of external to allow invocation from `constructor`
   *
   * @param newFeeWallet The new Fee wallet. Must be different from the current one
   */
  function setFeeWallet(address newFeeWallet) public onlyAdmin {
    require(newFeeWallet != address(0x0), 'Invalid wallet address');
    require(newFeeWallet != _feeWallet, 'Must be different from current');

    _feeWallet = newFeeWallet;
  }

  /**
   * @notice Sets the address of the `Migrator` contract
   *
   * @param newMigrator The new Migrator contract. Must be different from the current one
   */
  function setMigrator(address newMigrator) external onlyAdmin {
    require(Address.isContract(address(newMigrator)), 'Invalid address');
    require(
      newMigrator != _liquidityMigrator,
      'Must be different from current'
    );

    _liquidityMigrator = newMigrator;
  }

  // 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) {
    return
      _assetRegistry.loadBalanceInAssetUnitsByAddress(
        wallet,
        assetAddress,
        _balanceTracking
      );
  }

  /**
   * @notice Load a wallet's balance by asset symbol, 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) {
    return
      _assetRegistry.loadBalanceInAssetUnitsBySymbol(
        wallet,
        assetSymbol,
        _balanceTracking
      );
  }

  /**
   * @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
    override
    returns (uint64)
  {
    return
      AssetRegistry.loadBalanceInPipsByAddress(
        wallet,
        assetAddress,
        _balanceTracking
      );
  }

  /**
   * @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) {
    return
      _assetRegistry.loadBalanceInPipsBySymbol(
        wallet,
        assetSymbol,
        _balanceTracking
      );
  }

  /**
   * @notice Load the address of the Custodian contract
   *
   * @return The address of the Custodian contract
   */
  function loadCustodian() external view override returns (ICustodian) {
    return _custodian;
  }

  /**
   * @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 internally-tracked liquidity pool descriptor for a base-quote asset pair
   *
   * @return A `LiquidityPool` struct encapsulating the current state of the internally-tracked
   * liquidity pool for the given base-quote asset pair. Reverts if no such pool exists
   */
  function loadLiquidityPoolByAssetAddresses(
    address baseAssetAddress,
    address quoteAssetAddress
  ) external view override returns (LiquidityPool memory) {
    return
      _liquidityPools.loadLiquidityPoolByAssetAddresses(
        baseAssetAddress,
        quoteAssetAddress
      );
  }

  /**
   * @notice Load the address of the Migrator contract
   *
   * @return The address of the Migrator contract
   */
  function loadLiquidityMigrator() external view returns (address) {
    return _liquidityMigrator;
  }

  /**
   * @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 DO NOT send assets directly to the `Exchange`, instead use the appropriate deposit
   * function
   *
   * @dev Internally used to unwrap WETH during liquidity pool migrations via
   * `migrateLiquidityPool`. The sender is only required to be a contract rather than locking it
   * to a particular WETH instance to allow for migrating from multiple pools that use different
   * WETH contracts
   */
  receive() external payable {
    require(Address.isContract(msg.sender), 'Use depositEther');
  }

  /**
   * @notice Deposit ETH
   */
  function depositEther() external payable {
    deposit(
      msg.sender,
      _assetRegistry.loadAssetByAddress(address(0x0)),
      msg.value
    );
  }

  /**
   * @notice Deposit `IERC20` 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(
    address tokenAddress,
    uint256 quantityInAssetUnits
  ) external {
    Asset memory asset = _assetRegistry.loadAssetByAddress(tokenAddress);

    require(address(tokenAddress) != address(0x0), 'Use depositEther');

    deposit(msg.sender, asset, quantityInAssetUnits);
  }

  /**
   * @notice Deposit `IERC20` 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 memory assetSymbol,
    uint256 quantityInAssetUnits
  ) external {
    Asset memory asset =
      _assetRegistry.loadAssetBySymbol(
        assetSymbol,
        AssetRegistry.getCurrentTimestampInMs()
      );

    require(address(asset.assetAddress) != address(0x0), 'Use depositEther');

    deposit(msg.sender, asset, quantityInAssetUnits);
  }

  function deposit(
    address wallet,
    Asset memory asset,
    uint256 quantityInAssetUnits
  ) private {
    // Deposits are disabled until `setDepositIndex` is called successfully
    require(_depositIndex != Constants.depositIndexNotSet, 'Deposits disabled');

    // 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');

    (
      uint64 quantityInPips,
      uint64 newExchangeBalanceInPips,
      uint256 newExchangeBalanceInAssetUnits
    ) =
      Depositing.deposit(
        wallet,
        asset,
        quantityInAssetUnits,
        _custodian,
        _balanceTracking
      );

    _depositIndex++;

    emit Deposited(
      _depositIndex,
      wallet,
      asset.assetAddress,
      asset.symbol,
      quantityInPips,
      newExchangeBalanceInPips,
      newExchangeBalanceInAssetUnits
    );
  }

  // Trades //

  /**
   * @notice Settles a trade between two orders submitted and matched off-chain
   *
   * @param buy An `Order` struct encoding the parameters of the buy-side order (receiving base,
   * giving quote)
   * @param sell An `Order` struct encoding the parameters of the sell-side order (giving base,
   * receiving quote)
   * @param orderBookTrade An `OrderBookTrade` struct encoding the parameters of this trade
   * execution of the two orders
   */
  function executeOrderBookTrade(
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory orderBookTrade
  ) external onlyDispatcher {
    require(
      !isWalletExitFinalized(buy.walletAddress),
      'Buy wallet exit finalized'
    );
    require(
      !isWalletExitFinalized(sell.walletAddress),
      'Sell wallet exit finalized'
    );

    Trading.executeOrderBookTrade(
      buy,
      sell,
      orderBookTrade,
      _feeWallet,
      _assetRegistry,
      _balanceTracking,
      _completedOrderHashes,
      _nonceInvalidations,
      _partiallyFilledOrderQuantitiesInPips
    );

    emit OrderBookTradeExecuted(
      buy.walletAddress,
      sell.walletAddress,
      orderBookTrade.baseAssetSymbol,
      orderBookTrade.quoteAssetSymbol,
      orderBookTrade.grossBaseQuantityInPips,
      orderBookTrade.grossQuoteQuantityInPips,
      orderBookTrade.makerSide == OrderSide.Buy ? OrderSide.Sell : OrderSide.Buy
    );
  }

  /**
   * @notice Settles a trade between pool liquidity and an order submitted and matched off-chain
   *
   * @param order An `Order` struct encoding the parameters of the taker order
   * @param poolTrade A `PoolTrade` struct encoding the parameters of this trade execution between
   * the order and pool liquidity
   */
  function executePoolTrade(Order memory order, PoolTrade memory poolTrade)
    external
    onlyDispatcher
  {
    require(
      !isWalletExitFinalized(order.walletAddress),
      'Order wallet exit finalized'
    );

    Trading.executePoolTrade(
      order,
      poolTrade,
      _feeWallet,
      _assetRegistry,
      _liquidityPools,
      _balanceTracking,
      _completedOrderHashes,
      _nonceInvalidations,
      _partiallyFilledOrderQuantitiesInPips
    );

    emit PoolTradeExecuted(
      order.walletAddress,
      poolTrade.baseAssetSymbol,
      poolTrade.quoteAssetSymbol,
      poolTrade.grossBaseQuantityInPips,
      poolTrade.grossQuoteQuantityInPips,
      order.side
    );
  }

  /**
   * @notice Settles a trade between pool liquidity and two order submitted and matched off-chain.
   * The taker order is filled by pool liquidity up to the maker order price and the remainder of
   * the taker order quantity is then filled by the maker order
   *
   * @param buy An `Order` struct encoding the parameters of the buy-side order (receiving base,
   * giving quote)
   * @param sell An `Order` struct encoding the parameters of the sell-side order (giving base,
   * receiving quote)
   * @param hybridTrade A `HybridTrade` struct encoding the parameters of this trade execution
   * between the two orders and pool liquidity
   */
  function executeHybridTrade(
    Order memory buy,
    Order memory sell,
    HybridTrade memory hybridTrade
  ) external onlyDispatcher {
    // OrderBook trade validations
    require(
      !isWalletExitFinalized(buy.walletAddress),
      'Buy wallet exit finalized'
    );
    require(
      !isWalletExitFinalized(sell.walletAddress),
      'Sell wallet exit finalized'
    );

    Trading.executeHybridTrade(
      buy,
      sell,
      hybridTrade,
      _feeWallet,
      _assetRegistry,
      _liquidityPools,
      _balanceTracking,
      _completedOrderHashes,
      _nonceInvalidations,
      _partiallyFilledOrderQuantitiesInPips
    );

    emit HybridTradeExecuted(
      buy.walletAddress,
      sell.walletAddress,
      hybridTrade.orderBookTrade.baseAssetSymbol,
      hybridTrade.orderBookTrade.quoteAssetSymbol,
      hybridTrade.orderBookTrade.grossBaseQuantityInPips,
      hybridTrade.orderBookTrade.grossQuoteQuantityInPips,
      hybridTrade.poolTrade.grossBaseQuantityInPips,
      hybridTrade.poolTrade.grossQuoteQuantityInPips,
      hybridTrade.orderBookTrade.grossBaseQuantityInPips +
        hybridTrade.poolTrade.grossBaseQuantityInPips,
      hybridTrade.orderBookTrade.grossQuoteQuantityInPips +
        hybridTrade.poolTrade.grossQuoteQuantityInPips,
      hybridTrade.orderBookTrade.makerSide == OrderSide.Buy
        ? OrderSide.Sell
        : OrderSide.Buy
    );
  }

  // Withdrawing //

  /**
   * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently
   * whitelisted Dispatcher wallet
   *
   * @param withdrawal A `Withdrawal` struct encoding the parameters of the withdrawal
   */
  function withdraw(Withdrawal memory withdrawal) public onlyDispatcher {
    require(!isWalletExitFinalized(withdrawal.walletAddress), 'Wallet exited');

    (
      uint64 newExchangeBalanceInPips,
      uint256 newExchangeBalanceInAssetUnits,
      address assetAddress,
      string memory assetSymbol
    ) =
      Withdrawing.withdraw(
        withdrawal,
        _custodian,
        _feeWallet,
        _assetRegistry,
        _balanceTracking,
        _completedWithdrawalHashes
      );

    emit Withdrawn(
      withdrawal.walletAddress,
      assetAddress,
      assetSymbol,
      withdrawal.grossQuantityInPips,
      newExchangeBalanceInPips,
      newExchangeBalanceInAssetUnits
    );
  }

  // Liquidity pools //

  /**
   * @notice Create a new internally tracked liquidity pool copied from a previous Exchange contract
   *
   * @param baseAssetAddress The base asset address
   * @param quoteAssetAddress The quote asset address
   */
  function upgradeLiquidityPool(
    address baseAssetAddress,
    address quoteAssetAddress
  ) external onlyAdmin {
    _liquidityPools.upgradeLiquidityPool(
      baseAssetAddress,
      quoteAssetAddress,
      _assetRegistry,
      _balanceTracking.migrationSource,
      _custodian
    );
  }

  /**
   * @notice Create a new internally tracked liquidity pool and associated LP token
   *
   * @param baseAssetAddress The base asset address
   * @param quoteAssetAddress The quote asset address
   */
  function createLiquidityPool(
    address baseAssetAddress,
    address quoteAssetAddress
  ) external onlyAdmin {
    _liquidityPools.createLiquidityPool(
      baseAssetAddress,
      quoteAssetAddress,
      _assetRegistry
    );
  }

  /**
   * @notice Migrate reserve assets into an internally tracked liquidity pool and mint the
   * specified quantity of the associated LP token. If the pool and LP token do not already exist
   * then create new ones
   *
   * @dev This function should be called by a Migrator contract associated with a Farm by invoking
   * the `migrate` function on a Farm instance, passing in the `pid` of a pool holding tokens
   * compliant with the `IUniswapV2Pair` interface. The Migrator will then liquidate all tokens
   * held in the pool by calling the `burn` function on the Pair contract, transfer the output
   * reserve assets to the Exchange, and call this function. The Exchange then mints the
   * `desiredQuantity` of the new IDEX LP tokens back to the Migrator. This `desiredLiquidity`
   * should be exactly equal to the asset unit quantity of Pair tokens originally deposited in the
   * Farm pool
   *
   * @param token0 The address of `token0` in the Pair contract being migrated
   * @param token1 The address of `token1` in the Pair contract being migrated
   * @param isToken1Quote If true, maps `token0` to the base asset and `token1` to the quote asset
   * in the internally tracked pool; otherwise maps `token1` to base and `token0` to quote
   * @param desiredLiquidity The quantity of asset units of the new LP token to mint back to the
   * Migrator
   * @param to Recipient of the liquidity tokens
   *
   * @return liquidityProviderToken The address of the liquidity provider ERC-20 token representing
   * liquidity in the internally-tracked pool corresponding to the asset pair
   */

  function migrateLiquidityPool(
    address token0,
    address token1,
    bool isToken1Quote,
    uint256 desiredLiquidity,
    address to,
    address payable WETH
  ) external onlyMigrator returns (address liquidityProviderToken) {
    liquidityProviderToken = _liquidityPools.migrateLiquidityPool(
      LiquidityMigration(
        token0,
        token1,
        isToken1Quote,
        desiredLiquidity,
        to,
        IWETH9(WETH)
      ),
      _custodian,
      _assetRegistry
    );
  }

  /**
   * @notice Reverse the base and quote assets in an internally tracked liquidity pool
   *
   * @param baseAssetAddress The base asset address
   * @param quoteAssetAddress The quote asset address
   */
  function reverseLiquidityPoolAssets(
    address baseAssetAddress,
    address quoteAssetAddress
  ) external onlyAdmin {
    _liquidityPools.reverseLiquidityPoolAssets(
      baseAssetAddress,
      quoteAssetAddress
    );

    emit LiquidityPoolAssetsReversed(baseAssetAddress, quoteAssetAddress);
  }

  /**
   * @notice Adds liquidity to a ERC-20⇄ERC-20 pool
   *
   * @dev To cover all possible scenarios, `msg.sender` should have already given the Exchange an
   * allowance of at least `amountADesired`/`amountBDesired` on `tokenA`/`tokenB`
   *
   * @param tokenA The contract address of the desired token
   * @param tokenB The contract address of the desired token
   * @param amountADesired The amount of `tokenA` to add as liquidity if the B/A price is <=
   * `amountBDesired`/`amountADesired` (A depreciates)
   * @param amountBDesired The amount of `tokenB` to add as liquidity if the A/B price is <=
   * `amountADesired`/`amountBDesired` (B depreciates)
   * @param amountAMin Bounds the extent to which the B/A price can go up. Must be <=
   * `amountADesired`
   * @param amountBMin Bounds the extent to which the A/B price can go up. Must be <=
   * `amountBDesired`
   * @param to Recipient of the liquidity tokens
   * @param deadline Unix timestamp in seconds after which the transaction will revert
   */
  function addLiquidity(
    address tokenA,
    address tokenB,
    uint256 amountADesired,
    uint256 amountBDesired,
    uint256 amountAMin,
    uint256 amountBMin,
    address to,
    uint256 deadline
  ) external {
    // Calling exitWallet disables on-chain add liquidity initiation immediately on mining, in
    // contrast to withdrawals, trades, and liquidity change executions which respect the Chain
    // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized`
    require(!_walletExits[msg.sender].exists, 'Wallet exited');

    LiquidityAdditionDepositResult memory result =
      _liquidityPools.addLiquidity(
        LiquidityAddition(
          Constants.signatureHashVersion,
          LiquidityChangeOrigination.OnChain,
          0,
          msg.sender,
          tokenA,
          tokenB,
          amountADesired,
          amountBDesired,
          amountAMin,
          amountBMin,
          to,
          deadline,
          bytes('')
        ),
        _custodian,
        _assetRegistry,
        _balanceTracking
      );

    emit Deposited(
      ++_depositIndex,
      msg.sender,
      tokenA,
      result.assetASymbol,
      result.assetAQuantityInPips,
      result.assetANewExchangeBalanceInPips,
      result.assetANewExchangeBalanceInAssetUnits
    );

    emit Deposited(
      ++_depositIndex,
      msg.sender,
      tokenB,
      result.assetBSymbol,
      result.assetBQuantityInPips,
      result.assetBNewExchangeBalanceInPips,
      result.assetBNewExchangeBalanceInAssetUnits
    );

    emit LiquidityAdditionInitiated(
      msg.sender,
      tokenA,
      tokenB,
      amountADesired,
      amountBDesired,
      amountAMin,
      amountBMin,
      to,
      deadline
    );
  }

  /**
   * @notice Adds liquidity to a ERC-20⇄ETH pool
   *
   * @dev To cover all possible scenarios, `msg.sender` should have already given the router an
   * allowance of at least `amountTokenDesired` on `token`. `msg.value` is treated as
   * `amountETHDesired`
   *
   * @param token The contract address of the desired token
   * @param amountTokenDesired The amount of token to add as liquidity if the ETH/token
   * price is <= `msg.value`/`amountTokenDesired` (token depreciates)
   * @param amountTokenMin The amount of ETH to add as liquidity if the token/ETH
   * price is <= `amountTokenDesired`/`msg.value` (ETH depreciates)
   * @param amountETHMin Bounds the extent to which the token/ETH price can go up. Must be
   * <= `msg.value`
   * @param to Recipient of the liquidity tokens
   * @param deadline Unix timestamp in seconds after which the transaction will revert
   */
  function addLiquidityETH(
    address token,
    uint256 amountTokenDesired,
    uint256 amountTokenMin,
    uint256 amountETHMin,
    address to,
    uint256 deadline
  ) external payable {
    // Calling exitWallet disables on-chain add liquidity initiation immediately on mining, in
    // contrast to withdrawals, trades, and liquidity change executions which respect the Chain
    // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized`
    require(!_walletExits[msg.sender].exists, 'Wallet exited');

    LiquidityAdditionDepositResult memory result =
      _liquidityPools.addLiquidity(
        LiquidityAddition(
          Constants.signatureHashVersion,
          LiquidityChangeOrigination.OnChain,
          0,
          msg.sender,
          token,
          address(0x0),
          amountTokenDesired,
          msg.value,
          amountTokenMin,
          amountETHMin,
          to,
          deadline,
          bytes('')
        ),
        _custodian,
        _assetRegistry,
        _balanceTracking
      );

    emit Deposited(
      ++_depositIndex,
      msg.sender,
      token,
      result.assetASymbol,
      result.assetAQuantityInPips,
      result.assetANewExchangeBalanceInPips,
      result.assetANewExchangeBalanceInAssetUnits
    );

    emit Deposited(
      ++_depositIndex,
      msg.sender,
      address(0x0),
      result.assetBSymbol,
      result.assetBQuantityInPips,
      result.assetBNewExchangeBalanceInPips,
      result.assetBNewExchangeBalanceInAssetUnits
    );

    emit LiquidityAdditionInitiated(
      msg.sender,
      token,
      address(0x0),
      amountTokenDesired,
      msg.value,
      amountTokenMin,
      amountETHMin,
      to,
      deadline
    );
  }

  /**
   * @notice Settles a liquidity addition by transferring deposited assets from wallet balances to
   * pool reserves and minting LP tokens
   *
   * @param addition A `LiquidityAddition` struct encoding the parameters of the addition requested
   * by the user either on-chain via `addLiquidity` or `addLiquidityETH` or off-chain via
   * ECDSA-signed API request
   * @param execution A `LiquidityChangeExecution` struct encoding the parameters of this liquidity
   * addition execution that meets the terms of the request
   */
  function executeAddLiquidity(
    LiquidityAddition calldata addition,
    LiquidityChangeExecution calldata execution
  ) external onlyDispatcher {
    require(!isWalletExitFinalized(addition.wallet), 'Wallet exit finalized');

    _liquidityPools.executeAddLiquidity(
      addition,
      execution,
      _feeWallet,
      address(_custodian),
      _balanceTracking
    );

    emit LiquidityAdditionExecuted(
      addition.wallet,
      execution.baseAssetAddress,
      execution.quoteAssetAddress,
      execution.grossBaseQuantityInPips,
      execution.grossQuoteQuantityInPips,
      execution.liquidityInPips
    );
  }

  /**
   * @notice Removes liquidity from an ERC-20⇄ERC-20 pool
   *
   * @dev `msg.sender` should have already given the Exchange an allowance of at least `liquidity`
   * on the pool
   *
   * @param tokenA The contract address of the desired token
   * @param tokenB The contract address of the desired token
   * @param liquidity The amount of liquidity tokens to remove
   * @param amountAMin The minimum amount of `tokenA` that must be received
   * @param amountBMin The minimum amount of `tokenB` that must be received
   * @param to Recipient of the underlying assets
   * @param deadline Unix timestamp in seconds after which the transaction will revert
   */

  function removeLiquidity(
    address tokenA,
    address tokenB,
    uint256 liquidity,
    uint256 amountAMin,
    uint256 amountBMin,
    address to,
    uint256 deadline
  ) public {
    // Calling exitWallet disables on-chain remove liquidity initiation immediately on mining, in
    // contrast to withdrawals, trades, and liquidity change executions which respect the Chain
    // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized`
    require(!_walletExits[msg.sender].exists, 'Wallet exited');

    LiquidityRemovalDepositResult memory result =
      _liquidityPools.removeLiquidity(
        LiquidityRemoval(
          Constants.signatureHashVersion,
          LiquidityChangeOrigination.OnChain,
          0,
          msg.sender,
          tokenA,
          tokenB,
          liquidity,
          amountAMin,
          amountBMin,
          payable(to),
          deadline,
          bytes('')
        ),
        _custodian,
        _assetRegistry,
        _balanceTracking
      );

    emit Deposited(
      ++_depositIndex,
      msg.sender,
      result.assetAddress,
      result.assetSymbol,
      result.assetQuantityInPips,
      result.assetNewExchangeBalanceInPips,
      result.assetNewExchangeBalanceInAssetUnits
    );

    emit LiquidityRemovalInitiated(
      msg.sender,
      tokenA,
      tokenB,
      liquidity,
      amountAMin,
      amountBMin,
      to,
      deadline
    );
  }

  /**
   * @notice Removes liquidity from an ERC-20⇄ETH pool and receive ETH
   *
   * @dev `msg.sender` should have already given the Exchange an allowance of at least `liquidity`
   * on the pool
   *
   * @param token token The contract address of the desired token
   * @param token liquidity The amount of liquidity tokens to remove
   * @param token amountTokenMin The minimum amount of token that must be received
   * @param token amountETHMin The minimum amount of ETH that must be received
   * @param to Recipient of the underlying assets
   * @param deadline Unix timestamp in seconds after which the transaction will revert
   */
  function removeLiquidityETH(
    address token,
    uint256 liquidity,
    uint256 amountTokenMin,
    uint256 amountETHMin,
    address to,
    uint256 deadline
  ) external {
    // Calling exitWallet disables on-chain remove liquidity initiation immediately on mining, in
    // contrast to withdrawals, trades, and liquidity change executions which respect the Chain
    // Propagation Period given by `effectiveBlockNumber` via `isWalletExitFinalized`
    require(!_walletExits[msg.sender].exists, 'Wallet exited');

    LiquidityRemovalDepositResult memory result =
      _liquidityPools.removeLiquidity(
        LiquidityRemoval(
          Constants.signatureHashVersion,
          LiquidityChangeOrigination.OnChain,
          0,
          msg.sender,
          token,
          address(0x0),
          liquidity,
          amountTokenMin,
          amountETHMin,
          payable(to),
          deadline,
          bytes('')
        ),
        _custodian,
        _assetRegistry,
        _balanceTracking
      );

    emit Deposited(
      ++_depositIndex,
      msg.sender,
      result.assetAddress,
      result.assetSymbol,
      result.assetQuantityInPips,
      result.assetNewExchangeBalanceInPips,
      result.assetNewExchangeBalanceInAssetUnits
    );

    emit LiquidityRemovalInitiated(
      msg.sender,
      token,
      address(0x0),
      liquidity,
      amountTokenMin,
      amountETHMin,
      payable(to),
      deadline
    );
  }

  /**
   * @notice Settles a liquidity removal by burning deposited LP tokens and transferring reserve
   * assets from pool reserves to the recipient
   *
   * @param removal A `LiquidityRemoval` struct encoding the parameters of the removal requested
   * by the user either 1) on-chain via `removeLiquidity` or `removeLiquidityETH`, 2) off-chain via
   * ECDSA-signed API request, or 3) requested by the Dispatcher wallet itself in case the wallet
   * has exited and its liquidity positions must be liquidated automatically
   * @param execution A `LiquidityChangeExecution` struct encoding the parameters of this liquidity
   * removal execution that meets the terms of the request
   */
  function executeRemoveLiquidity(
    LiquidityRemoval calldata removal,
    LiquidityChangeExecution calldata execution
  ) external onlyDispatcher {
    _liquidityPools.executeRemoveLiquidity(
      removal,
      execution,
      _walletExits[removal.wallet].exists,
      ICustodian(_custodian),
      _feeWallet,
      _assetRegistry,
      _balanceTracking
    );

    emit LiquidityRemovalExecuted(
      removal.wallet,
      execution.baseAssetAddress,
      execution.quoteAssetAddress,
      execution.grossBaseQuantityInPips,
      execution.grossQuoteQuantityInPips,
      execution.liquidityInPips
    );
  }

  /**
   * @notice Remove liquidity from a pool immediately without the need for Dispatcher wallet
   * settlement. The wallet must be exited and the Chain Propagation Period must have already
   * passed since calling `exitWallet`. The LP tokens must already be deposited in the Exchange
   *
   * @param baseAssetAddress The base asset address
   * @param quoteAssetAddress The quote asset address
   */
  function removeLiquidityExit(
    address baseAssetAddress,
    address quoteAssetAddress
  ) external {
    require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized');

    (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    ) =
      _liquidityPools.removeLiquidityExit(
        baseAssetAddress,
        quoteAssetAddress,
        ICustodian(_custodian),
        _balanceTracking
      );

    emit WalletExitLiquidityRemoved(
      msg.sender,
      baseAssetAddress,
      quoteAssetAddress,
      outputBaseAssetQuantityInPips,
      outputQuoteAssetQuantityInPips
    );
  }

  // Wallet exits //

  /**
   * @notice Flags the sending wallet as exited, immediately disabling deposits and on-chain
   * intitiation of liquidity changes upon mining. After the Chain Propagation Period passes
   * trades, withdrawals, and liquidity change executions 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 already 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`
   *
   * @param assetAddress The address of the asset to withdraw
   */
  function withdrawExit(address assetAddress) external {
    require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized');

    // Update wallet balance
    uint64 previousExchangeBalanceInPips =
      Withdrawing.withdrawExit(
        assetAddress,
        _custodian,
        _assetRegistry,
        _balanceTracking
      );

    emit WalletExitWithdrawn(
      msg.sender,
      assetAddress,
      previousExchangeBalanceInPips
    );
  }

  /**
   * @notice Clears exited status of sending wallet. Upon mining immediately enables
   * deposits, trades, and withdrawals by sending wallet
   */
  function clearWalletExit() external {
    require(isWalletExitFinalized(msg.sender), 'Wallet exit not finalized');

    delete _walletExits[msg.sender];

    emit WalletExitCleared(msg.sender);
  }

  function isWalletExitFinalized(address wallet) internal view returns (bool) {
    WalletExit storage exit = _walletExits[wallet];
    return exit.exists && exit.effectiveBlockNumber <= block.number;
  }

  // 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, `executeOrderBookTrade` will reject order nonces from this wallet with a
   * timestampInMs component lower than the one provided
   */
  function invalidateOrderNonce(uint128 nonce) external {
    (uint64 timestampInMs, uint256 effectiveBlockNumber) =
      _nonceInvalidations.invalidateOrderNonce(nonce, _chainPropagationPeriod);

    emit OrderNonceInvalidated(
      msg.sender,
      nonce,
      timestampInMs,
      effectiveBlockNumber
    );
  }

  // Asset registry //

  /**
   * @notice Initiate registration process for a token asset. Only `IERC20` compliant tokens can be
   * added - ETH is hardcoded in the registry
   *
   * @param tokenAddress The address of the `IERC20` compliant token contract to add
   * @param symbol The symbol identifying the token asset
   * @param decimals The decimal precision of the token
   */
  function registerToken(
    IERC20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external onlyAdmin {
    _assetRegistry.registerToken(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 `IERC20` compliant token contract to add
   * @param symbol The symbol identifying the token asset
   * @param decimals The decimal precision of the token
   */
  function confirmTokenRegistration(
    IERC20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external onlyAdmin {
    _assetRegistry.confirmTokenRegistration(tokenAddress, symbol, decimals);
  }

  /**
   * @notice Add a symbol to a token that has already been registered and confirmed
   *
   * @param tokenAddress The address of the `IERC20` compliant token contract the symbol will
   * identify
   * @param symbol The symbol identifying the token asset
   */
  function addTokenSymbol(IERC20 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 `Asset` record describing the asset
   */
  function loadAssetBySymbol(string calldata assetSymbol, uint64 timestampInMs)
    external
    view
    returns (Asset memory)
  {
    return _assetRegistry.loadAssetBySymbol(assetSymbol, timestampInMs);
  }

  // Dispatcher whitelisting //

  /**
   * @notice Sets the wallet whitelisted to dispatch transactions calling the
   * `executeOrderBookTrade`, `executePoolTrade`, `executeHybridTrade`, `withdraw`,
   * `executeAddLiquidity`, and `executeRemoveLiquidity` functions
   *
   * @param newDispatcherWallet The new whitelisted dispatcher wallet. Must be different from the
   * current one
   */
  function setDispatcher(address newDispatcherWallet) external onlyAdmin {
    require(newDispatcherWallet != address(0x0), 'Invalid wallet address');
    require(
      newDispatcherWallet != _dispatcherWallet,
      'Must be different from current'
    );
    _dispatcherWallet = newDispatcherWallet;
  }

  /**
   * @notice Clears the currently set whitelisted dispatcher wallet, effectively disabling calling
   * the `executeOrderBookTrade`, `executePoolTrade`, `executeHybridTrade`, `withdraw`,
   * `executeAddLiquidity`, and `executeRemoveLiquidity` functions until a new wallet is set with
   * `setDispatcher`
   */
  function removeDispatcher() external onlyAdmin {
    _dispatcherWallet = address(0x0);
  }

  modifier onlyDispatcher() {
    require(msg.sender == _dispatcherWallet, 'Caller is not dispatcher');
    _;
  }

  // Migrator whitelisting //

  modifier onlyMigrator() {
    require(msg.sender == _liquidityMigrator, 'Caller is not Migrator');
    _;
  }

  // Asset skimming //

  /**
   * @notice Sends tokens mistakenly sent directly to the `Exchange` to the fee wallet (the
   * `receive` function rejects ETH except when wrapping/unwrapping)
   */
  function skim(address tokenAddress) external onlyAdmin {
    AssetRegistry.skim(tokenAddress, _feeWallet);
  }

  // Exchange upgrades //

  /**
   * @notice Following an Exchange upgrade via the Governance contract, this function allows the
   * new Exchange to reclaim blockchain storage by cleanup up old balance tracking
   */
  function cleanupWalletBalance(address wallet, address assetAddress) external {
    address currentExchange = ICustodian(_custodian).loadExchange();
    require(msg.sender == currentExchange, 'Caller is not Exchange');

    delete _balanceTracking.balancesByWalletAssetPair[wallet][assetAddress];
  }
}

File 14 of 38 : Governance.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { Address } from './Address.sol';

import { ICustodian } from './Interfaces.sol';
import { Owned } from './Owned.sol';

contract Governance is Owned {
  /**
   * @notice Emitted when admin initiates upgrade of `Exchange` contract address on `Custodian` via
   * `initiateExchangeUpgrade`
   */
  event ExchangeUpgradeInitiated(
    address oldExchange,
    address newExchange,
    uint256 blockThreshold
  );
  /**
   * @notice Emitted when admin cancels previously started `Exchange` upgrade with `cancelExchangeUpgrade`
   */
  event ExchangeUpgradeCanceled(address oldExchange, address newExchange);
  /**
   * @notice Emitted when admin finalizes `Exchange` upgrade via `finalizeExchangeUpgrade`
   */
  event ExchangeUpgradeFinalized(address oldExchange, address newExchange);
  /**
   * @notice Emitted when admin initiates upgrade of `Governance` contract address on `Custodian` via
   * `initiateGovernanceUpgrade`
   */
  event GovernanceUpgradeInitiated(
    address oldGovernance,
    address newGovernance,
    uint256 blockThreshold
  );
  /**
   * @notice Emitted when admin cancels previously started `Governance` upgrade with `cancelGovernanceUpgrade`
   */
  event GovernanceUpgradeCanceled(address oldGovernance, address newGovernance);
  /**
   * @notice Emitted when admin finalizes `Governance` upgrade via `finalizeGovernanceUpgrade`, effectively replacing
   * this contract and rendering it non-functioning
   */
  event GovernanceUpgradeFinalized(
    address oldGovernance,
    address newGovernance
  );

  // Internally used structs //

  struct ContractUpgrade {
    bool exists;
    address newContract;
    uint256 blockThreshold;
  }

  // Storage //

  uint256 immutable _blockDelay;
  ICustodian _custodian;
  ContractUpgrade _currentExchangeUpgrade;
  ContractUpgrade _currentGovernanceUpgrade;

  /**
   * @notice Instantiate a new `Governance` contract
   *
   * @dev Sets `owner` and `admin` to `msg.sender`. Sets the values for `_blockDelay` governing `Exchange`
   * and `Governance` upgrades. This value is immutable, and cannot be changed after construction
   *
   * @param blockDelay The minimum number of blocks that must be mined after initiating an `Exchange`
   * or `Governance` upgrade before the upgrade may be finalized
   */
  constructor(uint256 blockDelay) Owned() {
    _blockDelay = blockDelay;
  }

  /**
   * @notice Sets the address of the `Custodian` contract. 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 `Governance` contract. This value is immutable once set and cannot be
   * changed again
   *
   * @param newCustodian The address of the `Custodian` contract deployed against this `Governance`
   * contract's address
   */
  function setCustodian(ICustodian newCustodian) external onlyAdmin {
    require(
      _custodian == ICustodian(payable(address(0x0))),
      'Custodian can only be set once'
    );
    require(Address.isContract(address(newCustodian)), 'Invalid address');

    _custodian = newCustodian;
  }

  // Exchange upgrade //

  /**
   * @notice Initiates `Exchange` contract upgrade proccess on `Custodian`. Once `blockDelay` has passed
   * the process can be finalized with `finalizeExchangeUpgrade`
   *
   * @param newExchange The address of the new `Exchange` contract
   */
  function initiateExchangeUpgrade(address newExchange) external onlyAdmin {
    require(Address.isContract(address(newExchange)), 'Invalid address');
    require(
      newExchange != _custodian.loadExchange(),
      'Must be different from current Exchange'
    );
    require(
      !_currentExchangeUpgrade.exists,
      'Exchange upgrade already in progress'
    );

    _currentExchangeUpgrade = ContractUpgrade(
      true,
      newExchange,
      block.number + _blockDelay
    );

    emit ExchangeUpgradeInitiated(
      _custodian.loadExchange(),
      newExchange,
      _currentExchangeUpgrade.blockThreshold
    );
  }

  /**
   * @notice Cancels an in-flight `Exchange` contract upgrade that has not yet been finalized
   */
  function cancelExchangeUpgrade() external onlyAdmin {
    require(_currentExchangeUpgrade.exists, 'No Exchange upgrade in progress');

    address newExchange = _currentExchangeUpgrade.newContract;
    delete _currentExchangeUpgrade;

    emit ExchangeUpgradeCanceled(_custodian.loadExchange(), newExchange);
  }

  /**
   * @notice Finalizes the `Exchange` contract upgrade by changing the contract address on the `Custodian`
   * contract with `setExchange`. The number of blocks specified by `_blockDelay` must have passed since calling
   * `initiateExchangeUpgrade`
   *
   * @param newExchange The address of the new `Exchange` contract. Must equal the address provided to
   * `initiateExchangeUpgrade`
   */
  function finalizeExchangeUpgrade(address newExchange) external onlyAdmin {
    require(_currentExchangeUpgrade.exists, 'No Exchange upgrade in progress');
    require(
      _currentExchangeUpgrade.newContract == newExchange,
      'Address mismatch'
    );
    require(
      block.number >= _currentExchangeUpgrade.blockThreshold,
      'Block threshold not yet reached'
    );

    address oldExchange = _custodian.loadExchange();
    _custodian.setExchange(newExchange);
    delete _currentExchangeUpgrade;

    emit ExchangeUpgradeFinalized(oldExchange, newExchange);
  }

  // Governance upgrade //

  /**
   * @notice Initiates `Governance` contract upgrade proccess on `Custodian`. Once `blockDelay` has passed
   * the process can be finalized with `finalizeGovernanceUpgrade`
   *
   * @param newGovernance The address of the new `Governance` contract
   */
  function initiateGovernanceUpgrade(address newGovernance) external onlyAdmin {
    require(Address.isContract(address(newGovernance)), 'Invalid address');
    require(
      newGovernance != _custodian.loadGovernance(),
      'Must be different from current Governance'
    );
    require(
      !_currentGovernanceUpgrade.exists,
      'Governance upgrade already in progress'
    );

    _currentGovernanceUpgrade = ContractUpgrade(
      true,
      newGovernance,
      block.number + _blockDelay
    );

    emit GovernanceUpgradeInitiated(
      _custodian.loadGovernance(),
      newGovernance,
      _currentGovernanceUpgrade.blockThreshold
    );
  }

  /**
   * @notice Cancels an in-flight `Governance` contract upgrade that has not yet been finalized
   */
  function cancelGovernanceUpgrade() external onlyAdmin {
    require(
      _currentGovernanceUpgrade.exists,
      'No Governance upgrade in progress'
    );

    address newGovernance = _currentGovernanceUpgrade.newContract;
    delete _currentGovernanceUpgrade;

    emit GovernanceUpgradeCanceled(_custodian.loadGovernance(), newGovernance);
  }

  /**
   * @notice Finalizes the `Governance` contract upgrade by changing the contract address on the `Custodian`
   * contract with `setGovernance`. The number of blocks specified by `_blockDelay` must have passed since calling
   * `initiateGovernanceUpgrade`.
   *
   * @dev After successfully calling this function, this contract will become useless since it is no
   * longer whitelisted in the `Custodian`
   *
   * @param newGovernance The address of the new `Governance` contract. Must equal the address provided to
   * `initiateGovernanceUpgrade`
   */
  function finalizeGovernanceUpgrade(address newGovernance) external onlyAdmin {
    require(
      _currentGovernanceUpgrade.exists,
      'No Governance upgrade in progress'
    );
    require(
      _currentGovernanceUpgrade.newContract == newGovernance,
      'Address mismatch'
    );
    require(
      block.number >= _currentGovernanceUpgrade.blockThreshold,
      'Block threshold not yet reached'
    );

    address oldGovernance = _custodian.loadGovernance();
    _custodian.setGovernance(newGovernance);
    delete _currentGovernanceUpgrade;

    emit GovernanceUpgradeFinalized(oldGovernance, newGovernance);
  }
}

File 15 of 38 : Hashing.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { ECDSA } from './ECDSA.sol';

import { Constants } from './Constants.sol';
import { LiquidityChangeType, WithdrawalType } from './Enums.sol';
import {
  LiquidityAddition,
  LiquidityRemoval,
  Order,
  Withdrawal
} from './Structs.sol';

/**
 * @notice Library helpers for building hashes and verifying wallet signatures
 */
library Hashing {
  function isSignatureValid(
    bytes32 hash,
    bytes memory signature,
    address signer
  ) internal pure returns (bool) {
    return
      ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) == signer;
  }

  // Hash construction //

  function getLiquidityAdditionHash(LiquidityAddition memory addition)
    internal
    pure
    returns (bytes32)
  {
    require(
      addition.signatureHashVersion == Constants.signatureHashVersion,
      'Signature hash version invalid'
    );

    return
      keccak256(
        abi.encodePacked(
          addition.signatureHashVersion,
          uint8(LiquidityChangeType.Addition),
          uint8(addition.origination),
          addition.nonce,
          addition.wallet,
          addition.assetA,
          addition.assetB,
          addition.amountADesired,
          addition.amountBDesired,
          addition.amountAMin,
          addition.amountBMin,
          addition.to,
          addition.deadline
        )
      );
  }

  function getLiquidityRemovalHash(LiquidityRemoval memory removal)
    internal
    pure
    returns (bytes32)
  {
    require(
      removal.signatureHashVersion == Constants.signatureHashVersion,
      'Signature hash version invalid'
    );

    return
      keccak256(
        abi.encodePacked(
          removal.signatureHashVersion,
          uint8(LiquidityChangeType.Removal),
          uint8(removal.origination),
          removal.nonce,
          removal.wallet,
          removal.assetA,
          removal.assetB,
          removal.liquidity,
          removal.amountAMin,
          removal.amountBMin,
          removal.to,
          removal.deadline
        )
      );
  }

  /**
   * @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
   */
  function getOrderHash(
    Order memory order,
    string memory baseSymbol,
    string memory quoteSymbol
  ) internal pure returns (bytes32) {
    require(
      order.signatureHashVersion == Constants.signatureHashVersion,
      'Signature hash version invalid'
    );
    // Placing all the fields in a single `abi.encodePacked` call causes a `stack too deep` error
    return
      keccak256(
        abi.encodePacked(
          abi.encodePacked(
            order.signatureHashVersion,
            order.nonce,
            order.walletAddress,
            string(abi.encodePacked(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 getWithdrawalHash(Withdrawal memory withdrawal)
    internal
    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 == WithdrawalType.BySymbol
            ? abi.encodePacked(withdrawal.assetSymbol)
            : abi.encodePacked(withdrawal.assetAddress),
          pipToDecimal(withdrawal.grossQuantityInPips),
          withdrawal.autoDispatchEnabled
        )
      );
  }

  /**
   * @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);
  }
}

File 16 of 38 : HybridTradeHelpers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { HybridTrade } from './Structs.sol';

library HybridTradeHelpers {
  /**
   * @dev Total fees paid by taker from received asset across orderbook and pool trades. Does not
   * include pool input fees nor pool output adjustment
   */
  function calculateTakerFeeQuantityInPips(HybridTrade memory self)
    internal
    pure
    returns (uint64)
  {
    return
      self.takerGasFeeQuantityInPips +
      self.orderBookTrade.takerFeeQuantityInPips;
  }

  /**
   * @dev Gross quantity received by taker
   */
  function calculateTakerGrossReceivedQuantityInPips(HybridTrade memory self)
    internal
    pure
    returns (uint64)
  {
    return (
      self.orderBookTrade.takerFeeAssetAddress ==
        self.orderBookTrade.baseAssetAddress
        ? self.orderBookTrade.grossBaseQuantityInPips +
          self.poolTrade.grossBaseQuantityInPips
        : self.orderBookTrade.grossQuoteQuantityInPips +
          self.poolTrade.grossQuoteQuantityInPips
    );
  }

  /**
   * @dev Gross quantity received by maker
   */
  function getMakerGrossQuantityInPips(HybridTrade memory self)
    internal
    pure
    returns (uint64)
  {
    return
      self.orderBookTrade.takerFeeAssetAddress ==
        self.orderBookTrade.baseAssetAddress
        ? self.orderBookTrade.grossQuoteQuantityInPips
        : self.orderBookTrade.grossBaseQuantityInPips;
  }

  /**
   * @dev Net quantity received by maker
   */
  function getMakerNetQuantityInPips(HybridTrade memory self)
    internal
    pure
    returns (uint64)
  {
    return
      self.orderBookTrade.takerFeeAssetAddress ==
        self.orderBookTrade.baseAssetAddress
        ? self.orderBookTrade.netQuoteQuantityInPips
        : self.orderBookTrade.netBaseQuantityInPips;
  }
}

File 17 of 38 : HybridTradeValidations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { Constants } from './Constants.sol';
import { HybridTradeHelpers } from './HybridTradeHelpers.sol';
import { OrderBookTradeValidations } from './OrderBookTradeValidations.sol';
import { OrderSide } from './Enums.sol';
import { PoolTradeHelpers } from './PoolTradeHelpers.sol';
import { PoolTradeValidations } from './PoolTradeValidations.sol';
import { Validations } from './Validations.sol';
import {
  Asset,
  HybridTrade,
  Order,
  OrderBookTrade,
  NonceInvalidation,
  PoolTrade
} from './Structs.sol';

library HybridTradeValidations {
  using AssetRegistry for AssetRegistry.Storage;
  using HybridTradeHelpers for HybridTrade;
  using PoolTradeHelpers for PoolTrade;

  function validateHybridTrade(
    Order memory buy,
    Order memory sell,
    HybridTrade memory hybridTrade,
    AssetRegistry.Storage storage assetRegistry,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view returns (bytes32 buyHash, bytes32 sellHash) {
    require(
      buy.walletAddress != sell.walletAddress,
      'Self-trading not allowed'
    );

    require(
      hybridTrade.orderBookTrade.baseAssetAddress ==
        hybridTrade.poolTrade.baseAssetAddress &&
        hybridTrade.orderBookTrade.quoteAssetAddress ==
        hybridTrade.poolTrade.quoteAssetAddress,
      'Mismatched trade assets'
    );
    validateFees(hybridTrade);

    // Order book trade validations
    Validations.validateOrderNonces(buy, sell, nonceInvalidations);
    (buyHash, sellHash) = OrderBookTradeValidations.validateOrderSignatures(
      buy,
      sell,
      hybridTrade.orderBookTrade
    );
    OrderBookTradeValidations.validateAssetPair(
      buy,
      sell,
      hybridTrade.orderBookTrade,
      assetRegistry
    );
    OrderBookTradeValidations.validateLimitPrices(
      buy,
      sell,
      hybridTrade.orderBookTrade
    );

    // Pool trade validations
    Order memory takerOrder =
      hybridTrade.orderBookTrade.makerSide == OrderSide.Buy ? sell : buy;
    PoolTradeValidations.validateLimitPrice(takerOrder, hybridTrade.poolTrade);
  }

  function validatePoolPrice(
    Order memory makerOrder,
    uint64 baseAssetReserveInPips,
    uint64 quoteAssetReserveInPips
  ) internal pure {
    if (
      makerOrder.side == OrderSide.Buy &&
      Validations.isLimitOrderType(makerOrder.orderType)
    ) {
      // Price of pool must not be better (lower) than resting buy price
      require(
        Validations.calculateImpliedQuoteQuantityInPips(
          baseAssetReserveInPips,
          makerOrder.limitPriceInPips
        ) <= quoteAssetReserveInPips,
        'Pool marginal buy price exceeded'
      );
    }

    if (
      makerOrder.side == OrderSide.Sell &&
      Validations.isLimitOrderType(makerOrder.orderType)
    ) {
      // Price of pool must not be better (higher) than resting sell price
      require(
        Validations.calculateImpliedQuoteQuantityInPips(
          baseAssetReserveInPips,
          makerOrder.limitPriceInPips
          // Allow additional pip buffers for integer rounding
        ) +
          1 >=
          quoteAssetReserveInPips - 1,
        'Pool marginal sell price exceeded'
      );
    }
  }

  function validateFees(HybridTrade memory hybridTrade) private pure {
    require(
      hybridTrade.poolTrade.takerGasFeeQuantityInPips == 0,
      'Non-zero pool gas fee'
    );

    // Validate maker fee on orderbook trade
    uint64 grossQuantityInPips = hybridTrade.getMakerGrossQuantityInPips();
    require(
      Validations.isFeeQuantityValid(
        (grossQuantityInPips - hybridTrade.getMakerNetQuantityInPips()),
        grossQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive maker fee'
    );

    OrderSide takerOrderSide =
      hybridTrade.orderBookTrade.makerSide == OrderSide.Buy
        ? OrderSide.Sell
        : OrderSide.Buy;

    // Validate taker fees across orderbook and pool trades
    grossQuantityInPips = hybridTrade
      .calculateTakerGrossReceivedQuantityInPips();
    require(
      Validations.isFeeQuantityValid(
        hybridTrade.poolTrade.calculatePoolOutputAdjustment(takerOrderSide),
        grossQuantityInPips,
        Constants.maxPoolOutputAdjustmentBasisPoints
      ),
      'Excessive pool output adjustment'
    );
    require(
      Validations.isFeeQuantityValid(
        hybridTrade.calculateTakerFeeQuantityInPips(),
        grossQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive taker fee'
    );

    // Validate price correction, if present
    if (hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips > 0) {
      // Price correction only allowed for hybrid trades with a taker sell
      require(
        hybridTrade.orderBookTrade.makerSide == OrderSide.Buy,
        'Price correction not allowed'
      );

      // Do not allow quote output with a price correction as the latter is effectively a negative
      // net quote output
      require(
        hybridTrade.poolTrade.netQuoteQuantityInPips == 0,
        'Quote out not allowed with price correction'
      );

      grossQuantityInPips = hybridTrade
        .poolTrade
        .getOrderGrossReceivedQuantityInPips(takerOrderSide);
      if (
        hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips >
        grossQuantityInPips
      ) {
        require(
          Validations.isFeeQuantityValid(
            hybridTrade.poolTrade.takerPriceCorrectionFeeQuantityInPips -
              grossQuantityInPips,
            grossQuantityInPips,
            Constants.maxPoolPriceCorrectionBasisPoints
          ),
          'Excessive price correction'
        );
      }
    }

    Validations.validatePoolTradeInputFees(
      takerOrderSide,
      hybridTrade.poolTrade
    );
    Validations.validateOrderBookTradeFees(hybridTrade.orderBookTrade);
  }
}

File 18 of 38 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev 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);

    /**
     * @dev 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);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev 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);

    /**
     * @dev 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);
}

File 19 of 38 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 20 of 38 : Interfaces.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import {
  LiquidityPool,
  Order,
  OrderBookTrade,
  Withdrawal
} from './Structs.sol';

/**
 * @notice Interface of the ERC20 standard as defined in the EIP, 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 IERC20 {
  /**
   * @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 ETH 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 (native asset or ERC-20 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 Load address of the currently whitelisted Governance contract
   *
   * @return The address of the currently whitelisted Governance contract
   */
  function loadGovernance() external view returns (address);

  /**
   * @notice Sets a new Governance contract address
   *
   * @param newGovernance The address of the new whitelisted Governance contract
   */
  function setGovernance(address newGovernance) external;
}

/**
 * @notice Interface to Whistler Exchange contract
 *
 * @dev Used for lazy balance migrations from old to new Exchange after upgrade
 */
interface IExchange {
  /**
   * @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);

  /**
   * @notice Load the address of the Custodian contract
   *
   * @return The address of the Custodian contract
   */
  function loadCustodian() external view returns (ICustodian);

  /**
   * @notice Load the internally-tracked liquidity pool descriptor for a base-quote asset pair
   *
   * @return A `LiquidityPool` struct encapsulating the current state of the internally-tracked
   * liquidity pool for the given base-quote asset pair. Reverts if no such pool exists
   */
  function loadLiquidityPoolByAssetAddresses(
    address baseAssetAddress,
    address quoteAssetAddress
  ) external view returns (LiquidityPool memory);

  /**
   * @notice Load the number of deposits made to the contract, for use when upgrading to a new
   * Exchange via Governance
   *
   * @return The number of deposits successfully made to the Exchange
   */
  function _depositIndex() external view returns (uint64);
}

interface ILiquidityProviderToken {
  function custodian() external returns (ICustodian);

  function baseAssetAddress() external returns (address);

  function quoteAssetAddress() external returns (address);

  function baseAssetSymbol() external returns (string memory);

  function quoteAssetSymbol() external returns (string memory);

  function token0() external returns (address);

  function token1() external returns (address);

  function burn(
    address wallet,
    uint256 liquidity,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address to
  ) external;

  function mint(
    address wallet,
    uint256 liquidity,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address to
  ) external;

  function reverseAssets() external;
}

interface IWETH9 is IERC20 {
  receive() external payable;

  function deposit() external payable;

  function withdraw(uint256 wad) external;
}

File 21 of 38 : LiquidityChangeExecutionHelpers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { LiquidityChangeExecution } from './Structs.sol';

library LiquidityChangeExecutionHelpers {
  /**
   * @dev Quantity in pips of gross base quantity sent to fee wallet
   */
  function calculateBaseFeeQuantityInPips(LiquidityChangeExecution memory self)
    internal
    pure
    returns (uint64)
  {
    return self.grossBaseQuantityInPips - self.netBaseQuantityInPips;
  }

  /**
   * @dev Quantity in pips of gross quote quantity sent to fee wallet
   */
  function calculateQuoteFeeQuantityInPips(LiquidityChangeExecution memory self)
    internal
    pure
    returns (uint64)
  {
    return self.grossQuoteQuantityInPips - self.netQuoteQuantityInPips;
  }
}

File 22 of 38 : LiquidityChangeExecutionValidations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { IERC20 } from './Interfaces.sol';
import {
  LiquidityChangeExecutionHelpers
} from './LiquidityChangeExecutionHelpers.sol';
import { LiquidityPoolHelpers } from './LiquidityPoolHelpers.sol';
import { Validations } from './Validations.sol';
import {
  LiquidityAddition,
  LiquidityChangeExecution,
  LiquidityPool,
  LiquidityRemoval
} from './Structs.sol';

library LiquidityChangeExecutionValidations {
  using LiquidityChangeExecutionHelpers for LiquidityChangeExecution;
  using LiquidityPoolHelpers for LiquidityPool;

  function validateLiquidityAddition(
    LiquidityAddition memory addition,
    LiquidityChangeExecution memory execution,
    LiquidityPool memory pool
  ) internal view {
    require(
      ((execution.baseAssetAddress == addition.assetA &&
        execution.quoteAssetAddress == addition.assetB) ||
        (execution.baseAssetAddress == addition.assetB &&
          execution.quoteAssetAddress == addition.assetA)),
      'Asset address mismatch'
    );

    (
      uint256 minBaseInAssetUnits,
      uint256 desiredBaseInAssetUnits,
      uint256 minQuoteInAssetUnits,
      uint256 desiredQuoteInAssetUnits
    ) =
      execution.baseAssetAddress == addition.assetA
        ? (
          addition.amountAMin,
          addition.amountADesired,
          addition.amountBMin,
          addition.amountBDesired
        )
        : (
          addition.amountBMin,
          addition.amountBDesired,
          addition.amountAMin,
          addition.amountADesired
        );
    (uint64 minBase, uint64 maxBase, uint64 minQuote, uint64 maxQuote) =
      (
        AssetUnitConversions.assetUnitsToPips(
          minBaseInAssetUnits,
          pool.baseAssetDecimals
        ),
        AssetUnitConversions.assetUnitsToPips(
          desiredBaseInAssetUnits,
          pool.baseAssetDecimals
        ),
        AssetUnitConversions.assetUnitsToPips(
          minQuoteInAssetUnits,
          pool.quoteAssetDecimals
        ),
        AssetUnitConversions.assetUnitsToPips(
          desiredQuoteInAssetUnits,
          pool.quoteAssetDecimals
        )
      );

    require(
      execution.grossBaseQuantityInPips >= minBase,
      'Min base quantity not met'
    );
    require(
      execution.grossBaseQuantityInPips <= maxBase,
      'Desired base quantity exceeded'
    );
    require(
      execution.grossQuoteQuantityInPips >= minQuote,
      'Min quote quantity not met'
    );
    require(
      execution.grossQuoteQuantityInPips <= maxQuote,
      'Desired quote quantity exceeded'
    );

    require(
      execution.liquidityInPips > 0 &&
        execution.liquidityInPips ==
        pool.calculateOutputLiquidityInPips(
          execution.netBaseQuantityInPips,
          execution.netQuoteQuantityInPips
        ),
      'Invalid liquidity minted'
    );

    validateLiquidityChangeExecutionFees(execution);
  }

  function validateLiquidityRemoval(
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    LiquidityPool memory pool
  ) internal view {
    require(
      ((execution.baseAssetAddress == removal.assetA &&
        execution.quoteAssetAddress == removal.assetB) ||
        (execution.baseAssetAddress == removal.assetB &&
          execution.quoteAssetAddress == removal.assetA)),
      'Asset address mismatch'
    );

    require(
      execution.grossBaseQuantityInPips > 0 &&
        execution.grossQuoteQuantityInPips > 0,
      'Gross quantities must be nonzero'
    );

    (uint256 minBaseInAssetUnits, uint256 minQuoteInAssetUnits) =
      execution.baseAssetAddress == removal.assetA
        ? (removal.amountAMin, removal.amountBMin)
        : (removal.amountBMin, removal.amountAMin);
    (uint64 minBase, uint64 minQuote) =
      (
        AssetUnitConversions.assetUnitsToPips(
          minBaseInAssetUnits,
          pool.baseAssetDecimals
        ),
        AssetUnitConversions.assetUnitsToPips(
          minQuoteInAssetUnits,
          pool.quoteAssetDecimals
        )
      );

    require(
      execution.grossBaseQuantityInPips >= minBase,
      'Min base quantity not met'
    );
    require(
      execution.grossQuoteQuantityInPips >= minQuote,
      'Min quote quantity not met'
    );

    require(
      execution.liquidityInPips ==
        AssetUnitConversions.assetUnitsToPips(
          removal.liquidity,
          Constants.liquidityProviderTokenDecimals
        ),
      'Invalid liquidity burned'
    );

    (uint256 expectedBaseAssetQuantityInPips, ) =
      pool.calculateOutputAssetQuantitiesInPips(execution.liquidityInPips);

    // To allow for integer rounding precision loss, only validate exact output quantity for the
    // base asset, and allow the invariant price check performed in
    // validateAndUpdateForLiquidityRemoval to indirectly validate the output quantity of the quote
    // asset
    require(
      execution.grossBaseQuantityInPips == expectedBaseAssetQuantityInPips,
      'Invalid base quantity'
    );

    validateLiquidityChangeExecutionFees(execution);
  }

  function validateLiquidityChangeExecutionFees(
    LiquidityChangeExecution memory execution
  ) private pure {
    require(
      Validations.isFeeQuantityValid(
        execution.calculateBaseFeeQuantityInPips(),
        execution.grossBaseQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive base fee'
    );
    require(
      Validations.isFeeQuantityValid(
        execution.calculateQuoteFeeQuantityInPips(),
        execution.grossQuoteQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive quote fee'
    );
  }
}

File 23 of 38 : LiquidityPoolAdmin.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetTransfers } from './AssetTransfers.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { LiquidityPools } from './LiquidityPools.sol';
import { LiquidityProviderToken } from '../LiquidityProviderToken.sol';
import { Validations } from './Validations.sol';
import {
  ICustodian,
  IERC20,
  IExchange,
  ILiquidityProviderToken,
  IWETH9
} from './Interfaces.sol';

import { Asset, LiquidityMigration, LiquidityPool } from './Structs.sol';

library LiquidityPoolAdmin {
  using AssetRegistry for AssetRegistry.Storage;

  function upgradeLiquidityPool(
    LiquidityPools.Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress,
    AssetRegistry.Storage storage assetRegistry,
    IExchange balanceMigrationSource,
    ICustodian custodian
  ) public {
    require(
      custodian.loadExchange() == address(this),
      'Can only be set on Exchange whitelisted with Custodian'
    );

    // Use bidirectional mapping to require uniqueness of pools by asset pair regardless of
    // base-quote positions
    require(
      address(
        self.liquidityProviderTokensByAddress[baseAssetAddress][
          quoteAssetAddress
        ]
      ) == address(0x0),
      'Pool already exists'
    );

    // loadLiquidityPoolByAssetAddresses will revert if the pool does not exist
    LiquidityPool memory migrationSourcePool =
      balanceMigrationSource.loadLiquidityPoolByAssetAddresses(
        baseAssetAddress,
        quoteAssetAddress
      );

    // Create internally-tracked liquidity pool
    self.poolsByAddresses[baseAssetAddress][
      quoteAssetAddress
    ] = migrationSourcePool;

    // Store LP token address in both pair directions to allow lookup by unordered asset pairs
    // during on-chain initiated liquidity removals
    self.liquidityProviderTokensByAddress[baseAssetAddress][
      quoteAssetAddress
    ] = ILiquidityProviderToken(migrationSourcePool.liquidityProviderToken);
    self.liquidityProviderTokensByAddress[quoteAssetAddress][
      baseAssetAddress
    ] = ILiquidityProviderToken(migrationSourcePool.liquidityProviderToken);

    // Build an asset descriptor for the LP token and add it to the registry.
    Asset memory lpTokenAsset =
      Asset({
        exists: true,
        assetAddress: address(migrationSourcePool.liquidityProviderToken),
        symbol: string(
          abi.encodePacked(
            'ILP-',
            migrationSourcePool.liquidityProviderToken.baseAssetSymbol(),
            '-',
            migrationSourcePool.liquidityProviderToken.quoteAssetSymbol()
          )
        ),
        decimals: Constants.liquidityProviderTokenDecimals,
        isConfirmed: true,
        confirmedTimestampInMs: uint64(block.timestamp * 1000) // Block timestamp is in seconds, store ms
      });
    assetRegistry.assetsByAddress[lpTokenAsset.assetAddress] = lpTokenAsset;
    assetRegistry.assetsBySymbol[lpTokenAsset.symbol].push(lpTokenAsset);
  }

  function createLiquidityPool(
    LiquidityPools.Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress,
    AssetRegistry.Storage storage assetRegistry
  ) public {
    {
      createLiquidityPoolByAssetAddresses(
        self,
        baseAssetAddress,
        quoteAssetAddress,
        assetRegistry
      );
    }
  }

  function migrateLiquidityPool(
    LiquidityPools.Storage storage self,
    LiquidityMigration memory migration,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry
  ) public returns (address liquidityProviderToken) {
    require(
      AssetUnitConversions.assetUnitsToPips(
        migration.desiredLiquidity,
        Constants.liquidityProviderTokenDecimals
      ) > 0,
      'Desired liquidity too low'
    );

    {
      // Map Pair token reserve addresses to provided market base/quote addresses
      (
        address baseAssetAddress,
        address quoteAssetAddress,
        uint256 baseAssetQuantityInAssetUnits,
        uint256 quoteAssetQuantityInAssetUnits
      ) = transferMigratedTokenReservesToCustodian(migration, custodian);

      LiquidityPool storage pool;
      pool = loadOrCreateLiquidityPoolByAssetAddresses(
        self,
        baseAssetAddress,
        quoteAssetAddress,
        assetRegistry
      );
      liquidityProviderToken = address(pool.liquidityProviderToken);

      {
        // Convert transferred reserve amounts to pips and store
        pool.baseAssetReserveInPips += AssetUnitConversions.assetUnitsToPips(
          baseAssetQuantityInAssetUnits,
          pool.baseAssetDecimals
        );
        require(pool.baseAssetReserveInPips > 0, 'Insufficient base quantity');

        pool.quoteAssetReserveInPips += AssetUnitConversions.assetUnitsToPips(
          quoteAssetQuantityInAssetUnits,
          pool.quoteAssetDecimals
        );
        require(
          pool.quoteAssetReserveInPips > 0,
          'Insufficient quote quantity'
        );

        Validations.validatePoolReserveRatio(pool);
      }

      // Mint desired liquidity to Farm to complete migration
      ILiquidityProviderToken(liquidityProviderToken).mint(
        address(this),
        migration.desiredLiquidity,
        baseAssetQuantityInAssetUnits,
        quoteAssetQuantityInAssetUnits,
        migration.to
      );
    }
  }

  function reverseLiquidityPoolAssets(
    LiquidityPools.Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress
  ) public {
    LiquidityPool memory pool =
      LiquidityPools.loadLiquidityPoolByAssetAddresses(
        self,
        baseAssetAddress,
        quoteAssetAddress
      );

    delete self.poolsByAddresses[baseAssetAddress][quoteAssetAddress];
    self.poolsByAddresses[quoteAssetAddress][baseAssetAddress] = pool;

    (
      pool.baseAssetReserveInPips,
      pool.baseAssetDecimals,
      pool.quoteAssetReserveInPips,
      pool.quoteAssetDecimals
    ) = (
      pool.quoteAssetReserveInPips,
      pool.quoteAssetDecimals,
      pool.baseAssetReserveInPips,
      pool.baseAssetDecimals
    );
    pool.liquidityProviderToken.reverseAssets();
  }

  // Helpers //

  function loadOrCreateLiquidityPoolByAssetAddresses(
    LiquidityPools.Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress,
    AssetRegistry.Storage storage assetRegistry
  ) private returns (LiquidityPool storage pool) {
    pool = self.poolsByAddresses[baseAssetAddress][quoteAssetAddress];

    if (!pool.exists) {
      pool = createLiquidityPoolByAssetAddresses(
        self,
        baseAssetAddress,
        quoteAssetAddress,
        assetRegistry
      );
    }
  }

  function createLiquidityPoolByAssetAddresses(
    LiquidityPools.Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress,
    AssetRegistry.Storage storage assetRegistry
  ) private returns (LiquidityPool storage pool) {
    // Use bidirectional mapping to require uniqueness of pools by asset pair regardless of
    // base-quote positions
    require(
      address(
        self.liquidityProviderTokensByAddress[baseAssetAddress][
          quoteAssetAddress
        ]
      ) == address(0x0),
      'Pool already exists'
    );

    // Create internally-tracked liquidity pool
    pool = self.poolsByAddresses[baseAssetAddress][quoteAssetAddress];
    pool.exists = true;

    // Store asset decimals to avoid redundant asset registry lookups
    Asset memory asset;
    asset = assetRegistry.loadAssetByAddress(baseAssetAddress);
    string memory baseAssetSymbol = asset.symbol;
    pool.baseAssetDecimals = asset.decimals;
    asset = assetRegistry.loadAssetByAddress(quoteAssetAddress);
    string memory quoteAssetSymbol = asset.symbol;
    pool.quoteAssetDecimals = asset.decimals;

    // Create an LP token contract tied to this market. Construct salt from byte-sorted assets to
    // maintain a stable address if asset direction is reversed via `reverseLiquidityPoolAssets`
    bytes32 salt =
      keccak256(
        baseAssetAddress < quoteAssetAddress
          ? abi.encodePacked(baseAssetAddress, quoteAssetAddress)
          : abi.encodePacked(quoteAssetAddress, baseAssetAddress)
      );
    ILiquidityProviderToken liquidityProviderToken =
      new LiquidityProviderToken{ salt: salt }(
        baseAssetAddress,
        quoteAssetAddress,
        baseAssetSymbol,
        quoteAssetSymbol
      );

    // Store LP token address in both pair directions to allow lookup by unordered asset pairs
    // during on-chain initiated liquidity removals
    self.liquidityProviderTokensByAddress[baseAssetAddress][
      quoteAssetAddress
    ] = ILiquidityProviderToken(liquidityProviderToken);
    self.liquidityProviderTokensByAddress[quoteAssetAddress][
      baseAssetAddress
    ] = ILiquidityProviderToken(liquidityProviderToken);

    // Associate the newly created LP token contract with the pool
    pool.liquidityProviderToken = ILiquidityProviderToken(
      liquidityProviderToken
    );

    // Build an asset descriptor for the new LP token and add it to the registry. There is no need
    // to validate against it already existing as the preceeding CREATE2 will revert on duplicate
    // asset pairs
    Asset memory lpTokenAsset =
      Asset({
        exists: true,
        assetAddress: address(liquidityProviderToken),
        symbol: string(
          abi.encodePacked('ILP-', baseAssetSymbol, '-', quoteAssetSymbol)
        ),
        decimals: Constants.liquidityProviderTokenDecimals,
        isConfirmed: true,
        confirmedTimestampInMs: uint64(block.timestamp * 1000) // Block timestamp is in seconds, store ms
      });
    assetRegistry.assetsByAddress[lpTokenAsset.assetAddress] = lpTokenAsset;
    assetRegistry.assetsBySymbol[lpTokenAsset.symbol].push(lpTokenAsset);
  }

  function transferMigratedTokenReservesToCustodian(
    LiquidityMigration memory migration,
    ICustodian custodian
  )
    private
    returns (
      address baseAssetAddress,
      address quoteAssetAddress,
      uint256 baseAssetQuantityInAssetUnits,
      uint256 quoteAssetQuantityInAssetUnits
    )
  {
    // Obtain reserve amounts sent to the Exchange
    uint256 reserve0 = IERC20(migration.token0).balanceOf(address(this));
    uint256 reserve1 = IERC20(migration.token1).balanceOf(address(this));
    // Transfer reserves to Custodian and unwrap WETH if needed
    transferMigratedTokenReserveToCustodian(
      migration.token0,
      reserve0,
      migration.WETH,
      custodian
    );
    transferMigratedTokenReserveToCustodian(
      migration.token1,
      reserve1,
      migration.WETH,
      custodian
    );

    address unwrappedToken0 =
      migration.token0 == address(migration.WETH)
        ? address(0x0)
        : migration.token0;
    address unwrappedToken1 =
      migration.token1 == address(migration.WETH)
        ? address(0x0)
        : migration.token1;

    return
      migration.isToken1Quote
        ? (unwrappedToken0, unwrappedToken1, reserve0, reserve1)
        : (unwrappedToken1, unwrappedToken0, reserve1, reserve0);
  }

  function transferMigratedTokenReserveToCustodian(
    address token,
    uint256 reserve,
    IWETH9 WETH,
    ICustodian custodian
  ) private {
    // Unwrap WETH
    if (token == address(WETH)) {
      WETH.withdraw(reserve);
      AssetTransfers.transferTo(
        payable(address(custodian)),
        address(0x0),
        reserve
      );
    } else {
      AssetTransfers.transferTo(payable(address(custodian)), token, reserve);
    }
  }
}

File 24 of 38 : LiquidityPoolHelpers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { SafeCast } from './SafeCast.sol';

import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { IERC20 } from './Interfaces.sol';
import { LiquidityPool } from './Structs.sol';
import { Math } from './Math.sol';

library LiquidityPoolHelpers {
  function calculateCurrentPoolPriceInPips(LiquidityPool memory self)
    internal
    pure
    returns (uint64)
  {
    if (self.baseAssetReserveInPips == 0) {
      return 0;
    }

    return
      Math.multiplyPipsByFraction(
        Constants.pipPriceMultiplier,
        self.quoteAssetReserveInPips,
        self.baseAssetReserveInPips
      );
  }

  /**
   * @dev Calculate reserve asset quantities to remove from a pool for a given liquidity amount
   * @dev This function will revert if the base reserve is zero
   */
  function calculateOutputAssetQuantitiesInPips(
    LiquidityPool memory self,
    uint64 liquidityToBurnInPips
  )
    internal
    view
    returns (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    )
  {
    uint64 totalLiquidityInPips =
      AssetUnitConversions.assetUnitsToPips(
        IERC20(address(self.liquidityProviderToken)).totalSupply(),
        Constants.liquidityProviderTokenDecimals
      );

    // Use fraction of total liquidity burned to calculate proportionate base amount out
    outputBaseAssetQuantityInPips = Math.multiplyPipsByFraction(
      self.baseAssetReserveInPips,
      liquidityToBurnInPips,
      totalLiquidityInPips
    );

    // Calculate quote amount out that maintains the current pool price given above base amount out.
    // Use double pip precision to avoid precision loss for very high prices
    uint256 poolPriceInDoublePips =
      (uint256(self.quoteAssetReserveInPips) *
        Constants.doublePipPriceMultiplier) / self.baseAssetReserveInPips;
    uint64 targetQuoteAssetReserveInPips =
      SafeCast.toUint64(
        (uint256(self.baseAssetReserveInPips - outputBaseAssetQuantityInPips) *
          poolPriceInDoublePips) / Constants.doublePipPriceMultiplier
      );

    outputQuoteAssetQuantityInPips =
      self.quoteAssetReserveInPips -
      targetQuoteAssetReserveInPips;
  }

  /**
   * @dev Calculate LP token quantity to mint for given reserve asset quantities
   */
  function calculateOutputLiquidityInPips(
    LiquidityPool memory self,
    uint64 baseQuantityInPips,
    uint64 quoteQuantityInPips
  ) internal view returns (uint64 outputLiquidityInPips) {
    uint256 totalSupplyInAssetUnits =
      IERC20(address(self.liquidityProviderToken)).totalSupply();

    // For initial deposit use geometric mean of reserve quantities
    if (totalSupplyInAssetUnits == 0) {
      // There is no need to check for uint64 overflow since sqrt(max * max) = max
      return
        uint64(Math.sqrt(uint256(baseQuantityInPips) * quoteQuantityInPips));
    }

    uint64 totalLiquidityInPips =
      AssetUnitConversions.assetUnitsToPips(
        totalSupplyInAssetUnits,
        Constants.liquidityProviderTokenDecimals
      );

    return
      Math.min(
        Math.multiplyPipsByFraction(
          totalLiquidityInPips,
          baseQuantityInPips,
          self.baseAssetReserveInPips
        ),
        Math.multiplyPipsByFraction(
          totalLiquidityInPips,
          quoteQuantityInPips,
          self.quoteAssetReserveInPips
        )
      );
  }
}

File 25 of 38 : LiquidityPools.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetTransfers } from './AssetTransfers.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { Constants } from './Constants.sol';
import { Depositing } from './Depositing.sol';
import { Hashing } from './Hashing.sol';
import {
  LiquidityChangeExecutionValidations
} from './LiquidityChangeExecutionValidations.sol';
import { LiquidityPoolHelpers } from './LiquidityPoolHelpers.sol';
import { LiquidityProviderToken } from '../LiquidityProviderToken.sol';
import { PoolTradeHelpers } from './PoolTradeHelpers.sol';
import { Validations } from './Validations.sol';
import { Withdrawing } from './Withdrawing.sol';
import {
  ICustodian,
  IERC20,
  ILiquidityProviderToken,
  IWETH9
} from './Interfaces.sol';
import {
  LiquidityChangeOrigination,
  LiquidityChangeState,
  LiquidityChangeType,
  OrderSide
} from './Enums.sol';
import {
  Asset,
  LiquidityAddition,
  LiquidityAdditionDepositResult,
  LiquidityChangeExecution,
  LiquidityPool,
  LiquidityRemoval,
  LiquidityRemovalDepositResult,
  PoolTrade
} from './Structs.sol';

library LiquidityPools {
  using AssetRegistry for AssetRegistry.Storage;
  using BalanceTracking for BalanceTracking.Storage;
  using LiquidityPoolHelpers for LiquidityPool;
  using PoolTradeHelpers for PoolTrade;

  struct Storage {
    mapping(address => mapping(address => ILiquidityProviderToken)) liquidityProviderTokensByAddress;
    mapping(address => mapping(address => LiquidityPool)) poolsByAddresses;
    mapping(bytes32 => LiquidityChangeState) changes;
  }

  uint64 public constant MINIMUM_LIQUIDITY = 10**3;

  // Add liquidity //

  function addLiquidity(
    Storage storage self,
    LiquidityAddition memory addition,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) public returns (LiquidityAdditionDepositResult memory) {
    require(addition.deadline >= block.timestamp, 'IDEX: EXPIRED');

    bytes32 hash = Hashing.getLiquidityAdditionHash(addition);
    require(
      self.changes[hash] == LiquidityChangeState.NotInitiated,
      'Already initiated'
    );
    self.changes[hash] = LiquidityChangeState.Initiated;

    // Transfer assets to Custodian and credit balances
    return
      Depositing.depositLiquidityReserves(
        addition.wallet,
        addition.assetA,
        addition.assetB,
        addition.amountADesired,
        addition.amountBDesired,
        custodian,
        assetRegistry,
        balanceTracking
      );
  }

  function executeAddLiquidity(
    Storage storage self,
    LiquidityAddition memory addition,
    LiquidityChangeExecution memory execution,
    address feeWallet,
    address custodianAddress,
    BalanceTracking.Storage storage balanceTracking
  ) external {
    ILiquidityProviderToken liquidityProviderToken =
      validateAndUpdateForLiquidityAddition(self, addition, execution);

    // Debit wallet Pair token balance and credit fee wallet reserve asset balances
    balanceTracking.updateForAddLiquidity(
      addition,
      execution,
      feeWallet,
      custodianAddress,
      liquidityProviderToken
    );
  }

  function validateAndUpdateForLiquidityAddition(
    Storage storage self,
    LiquidityAddition memory addition,
    LiquidityChangeExecution memory execution
  ) private returns (ILiquidityProviderToken liquidityProviderToken) {
    {
      bytes32 hash = Hashing.getLiquidityAdditionHash(addition);
      LiquidityChangeState state = self.changes[hash];

      if (addition.origination == LiquidityChangeOrigination.OnChain) {
        require(
          state == LiquidityChangeState.Initiated,
          'Not executable from on-chain'
        );
      } else {
        require(
          state == LiquidityChangeState.NotInitiated,
          'Not executable from off-chain'
        );
        require(
          Hashing.isSignatureValid(hash, addition.signature, addition.wallet),
          'Invalid signature'
        );
      }
      self.changes[hash] = LiquidityChangeState.Executed;
    }

    LiquidityPool storage pool =
      loadLiquidityPoolByAssetAddresses(
        self,
        execution.baseAssetAddress,
        execution.quoteAssetAddress
      );
    liquidityProviderToken = pool.liquidityProviderToken;

    LiquidityChangeExecutionValidations.validateLiquidityAddition(
      addition,
      execution,
      pool
    );

    validateAndUpdateReservesForLiquidityAddition(pool, execution);

    // Mint LP tokens to destination wallet
    liquidityProviderToken.mint(
      addition.wallet,
      AssetUnitConversions.pipsToAssetUnits(
        execution.liquidityInPips,
        Constants.liquidityProviderTokenDecimals
      ),
      AssetUnitConversions.pipsToAssetUnits(
        execution.netBaseQuantityInPips,
        pool.baseAssetDecimals
      ),
      AssetUnitConversions.pipsToAssetUnits(
        execution.netQuoteQuantityInPips,
        pool.quoteAssetDecimals
      ),
      addition.to
    );
  }

  // Remove liquidity //

  function removeLiquidity(
    Storage storage self,
    LiquidityRemoval memory removal,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) public returns (LiquidityRemovalDepositResult memory) {
    require(removal.deadline >= block.timestamp, 'IDEX: EXPIRED');

    bytes32 hash = Hashing.getLiquidityRemovalHash(removal);
    require(
      self.changes[hash] == LiquidityChangeState.NotInitiated,
      'Already initiated'
    );
    self.changes[hash] = LiquidityChangeState.Initiated;

    // Resolve LP token address
    address liquidityProviderToken =
      address(
        loadLiquidityProviderTokenByAssetAddresses(
          self,
          removal.assetA,
          removal.assetB
        )
      );

    // Transfer LP tokens to Custodian and credit balances
    return
      Depositing.depositLiquidityTokens(
        removal.wallet,
        liquidityProviderToken,
        removal.liquidity,
        custodian,
        assetRegistry,
        balanceTracking
      );
  }

  function executeRemoveLiquidity(
    Storage storage self,
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    bool isWalletExited,
    ICustodian custodian,
    address feeWallet,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) public {
    ILiquidityProviderToken liquidityProviderToken =
      validateAndUpdateForLiquidityRemoval(
        self,
        removal,
        execution,
        isWalletExited
      );

    Withdrawing.withdrawLiquidity(
      removal,
      execution,
      custodian,
      feeWallet,
      liquidityProviderToken,
      assetRegistry,
      balanceTracking
    );
  }

  function validateAndUpdateForLiquidityRemoval(
    Storage storage self,
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    bool isWalletExited
  ) private returns (ILiquidityProviderToken liquidityProviderToken) {
    {
      // Following a wallet exit the Dispatcher can liquidate the wallet's liquidity pool positions
      // without the need for the wallet itself to first initiate the removal. Without this
      // mechanism, the wallet could change the pool's price at any time following the exit by
      // calling `removeLiquidityExit` and cause the reversion of pending pool settlements from the
      // Dispatcher
      if (!isWalletExited) {
        bytes32 hash = Hashing.getLiquidityRemovalHash(removal);
        LiquidityChangeState state = self.changes[hash];

        if (removal.origination == LiquidityChangeOrigination.OnChain) {
          require(
            state == LiquidityChangeState.Initiated,
            'Not executable from on-chain'
          );
        } else {
          require(
            state == LiquidityChangeState.NotInitiated,
            'Not executable from off-chain'
          );
          require(
            Hashing.isSignatureValid(hash, removal.signature, removal.wallet),
            'Invalid signature'
          );
        }
        self.changes[hash] = LiquidityChangeState.Executed;
      }
    }

    LiquidityPool storage pool =
      loadLiquidityPoolByAssetAddresses(
        self,
        execution.baseAssetAddress,
        execution.quoteAssetAddress
      );
    liquidityProviderToken = pool.liquidityProviderToken;

    LiquidityChangeExecutionValidations.validateLiquidityRemoval(
      removal,
      execution,
      pool
    );

    uint64 initialPrice = pool.calculateCurrentPoolPriceInPips();

    // Debit pool reserves
    pool.baseAssetReserveInPips -= execution.grossBaseQuantityInPips;
    pool.quoteAssetReserveInPips -= execution.grossQuoteQuantityInPips;

    uint64 updatedPrice = pool.calculateCurrentPoolPriceInPips();
    // Skip price validation if 1) emptying a pool of all liquidity since the price can validly
    // change to zero or 2) either reserve is below the minimum as prices can no longer be
    // represented with full pip precision
    if (
      updatedPrice > 0 &&
      pool.baseAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips &&
      pool.quoteAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips
    ) {
      require(initialPrice == updatedPrice, 'Pool price cannot change');
    }

    liquidityProviderToken.burn(
      removal.wallet,
      AssetUnitConversions.pipsToAssetUnits(
        execution.liquidityInPips,
        Constants.liquidityProviderTokenDecimals
      ),
      AssetUnitConversions.pipsToAssetUnits(
        execution.grossBaseQuantityInPips,
        pool.baseAssetDecimals
      ),
      AssetUnitConversions.pipsToAssetUnits(
        execution.grossQuoteQuantityInPips,
        pool.quoteAssetDecimals
      ),
      removal.to
    );
  }

  // Exit liquidity //

  function removeLiquidityExit(
    Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress,
    ICustodian custodian,
    BalanceTracking.Storage storage balanceTracking
  )
    public
    returns (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    )
  {
    LiquidityPool storage pool =
      loadLiquidityPoolByAssetAddresses(
        self,
        baseAssetAddress,
        quoteAssetAddress
      );

    uint64 liquidityToBurnInPips =
      balanceTracking.updateForExit(
        msg.sender,
        address(pool.liquidityProviderToken)
      );

    // Calculate output asset quantities
    (outputBaseAssetQuantityInPips, outputQuoteAssetQuantityInPips) = pool
      .calculateOutputAssetQuantitiesInPips(liquidityToBurnInPips);
    uint256 outputBaseAssetQuantityInAssetUnits =
      AssetUnitConversions.pipsToAssetUnits(
        outputBaseAssetQuantityInPips,
        pool.baseAssetDecimals
      );
    uint256 outputQuoteAssetQuantityInAssetUnits =
      AssetUnitConversions.pipsToAssetUnits(
        outputQuoteAssetQuantityInPips,
        pool.quoteAssetDecimals
      );

    // Debit pool reserves
    pool.baseAssetReserveInPips -= outputBaseAssetQuantityInPips;
    pool.quoteAssetReserveInPips -= outputQuoteAssetQuantityInPips;

    // Burn deposited Pair tokens
    pool.liquidityProviderToken.burn(
      msg.sender,
      AssetUnitConversions.pipsToAssetUnits(
        liquidityToBurnInPips,
        Constants.liquidityProviderTokenDecimals
      ),
      outputBaseAssetQuantityInAssetUnits,
      outputQuoteAssetQuantityInAssetUnits,
      msg.sender
    );

    // Transfer reserve assets to wallet
    custodian.withdraw(
      payable(msg.sender),
      baseAssetAddress,
      outputBaseAssetQuantityInAssetUnits
    );
    custodian.withdraw(
      payable(msg.sender),
      quoteAssetAddress,
      outputQuoteAssetQuantityInAssetUnits
    );
  }

  // Trading //

  function updateReservesForPoolTrade(
    Storage storage self,
    PoolTrade memory poolTrade,
    OrderSide orderSide
  )
    internal
    returns (uint64 baseAssetReserveInPips, uint64 quoteAssetReserveInPips)
  {
    LiquidityPool storage pool =
      loadLiquidityPoolByAssetAddresses(
        self,
        poolTrade.baseAssetAddress,
        poolTrade.quoteAssetAddress
      );

    uint128 initialProduct =
      uint128(pool.baseAssetReserveInPips) *
        uint128(pool.quoteAssetReserveInPips);
    uint128 updatedProduct;

    if (orderSide == OrderSide.Buy) {
      pool.baseAssetReserveInPips -= poolTrade.getPoolDebitQuantityInPips(
        orderSide
      );
      pool.quoteAssetReserveInPips += poolTrade
        .calculatePoolCreditQuantityInPips(orderSide);

      updatedProduct =
        uint128(pool.baseAssetReserveInPips) *
        uint128(
          pool.quoteAssetReserveInPips - poolTrade.takerPoolFeeQuantityInPips
        );
    } else {
      pool.baseAssetReserveInPips += poolTrade
        .calculatePoolCreditQuantityInPips(orderSide);
      if (poolTrade.takerPriceCorrectionFeeQuantityInPips > 0) {
        // Add the taker sell's price correction fee to the pool - there is no quote output
        pool.quoteAssetReserveInPips += poolTrade
          .takerPriceCorrectionFeeQuantityInPips;
      } else {
        pool.quoteAssetReserveInPips -= poolTrade.getPoolDebitQuantityInPips(
          orderSide
        );
      }

      updatedProduct =
        uint128(
          pool.baseAssetReserveInPips - poolTrade.takerPoolFeeQuantityInPips
        ) *
        uint128(pool.quoteAssetReserveInPips);
    }

    // Constant product will increase when there are fees collected
    require(
      updatedProduct >= initialProduct,
      'Constant product cannot decrease'
    );

    // Disallow either ratio to dip below the minimum as prices can no longer be represented with
    // full pip precision
    require(
      pool.baseAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips,
      'Base reserves below min'
    );
    require(
      pool.quoteAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips,
      'Quote reserves below min'
    );
    Validations.validatePoolReserveRatio(pool);

    return (pool.baseAssetReserveInPips, pool.quoteAssetReserveInPips);
  }

  // Helpers //

  function loadLiquidityPoolByAssetAddresses(
    Storage storage self,
    address baseAssetAddress,
    address quoteAssetAddress
  ) internal view returns (LiquidityPool storage pool) {
    pool = self.poolsByAddresses[baseAssetAddress][quoteAssetAddress];
    require(pool.exists, 'No pool for address pair');
  }

  function loadLiquidityProviderTokenByAssetAddresses(
    Storage storage self,
    address assetA,
    address assetB
  ) private view returns (ILiquidityProviderToken liquidityProviderToken) {
    liquidityProviderToken = self.liquidityProviderTokensByAddress[assetA][
      assetB
    ];
    require(
      address(liquidityProviderToken) != address(0x0),
      'No LP token for address pair'
    );
  }

  function validateAndUpdateReservesForLiquidityAddition(
    LiquidityPool storage pool,
    LiquidityChangeExecution memory execution
  ) private {
    uint64 initialPrice = pool.calculateCurrentPoolPriceInPips();

    // Credit pool reserves
    pool.baseAssetReserveInPips += execution.netBaseQuantityInPips;
    pool.quoteAssetReserveInPips += execution.netQuoteQuantityInPips;

    // Require pool price to remain constant on addition. Skip this validation if either reserve is
    // below the minimum as prices can no longer be represented with full pip precision
    if (initialPrice == 0) {
      // First liquidity addition to empty pool establishes price which must be within max ratio
      Validations.validatePoolReserveRatio(pool);
    } else if (
      pool.baseAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips &&
      pool.quoteAssetReserveInPips >= Constants.minLiquidityPoolReserveInPips
    ) {
      uint64 updatedPrice = pool.calculateCurrentPoolPriceInPips();
      require(initialPrice == updatedPrice, 'Pool price cannot change');
    }
  }
}

File 26 of 38 : LiquidityProviderToken.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { Address } from './Address.sol';
import { ERC20 } from './ERC20.sol';

import { Constants } from './Constants.sol';
import {
  ICustodian,
  IExchange,
  IERC20,
  ILiquidityProviderToken
} from './Interfaces.sol';

/**
 * @notice Liquidity Provider ERC-20 token contract
 *
 * @dev Reference OpenZeppelin implementation with whitelisted minting and burning
 */
contract LiquidityProviderToken is ERC20, ILiquidityProviderToken {
  // Used to whitelist Exchange-only functions by loading address of current Exchange from Custodian
  ICustodian public override custodian;

  // Base and quote asset addresses provided only for informational purposes
  address public override baseAssetAddress;
  address public override quoteAssetAddress;
  string public override baseAssetSymbol;
  string public override quoteAssetSymbol;

  /**
   * @notice Emitted when the Exchange mints new LP tokens to a wallet via `mint`
   */
  event Mint(
    address indexed sender,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits
  );
  /**
   * @notice Emitted when the Exchange burns a wallet's LP tokens via `burn`
   */
  event Burn(
    address indexed sender,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address indexed to
  );

  modifier onlyExchange() {
    require(msg.sender == custodian.loadExchange(), 'Caller is not Exchange');
    _;
  }

  /**
   * @notice Instantiate a new `LiquidityProviderToken` contract
   *
   * @dev Should be called by the Exchange via a CREATE2 op to generate stable deterministic
   * addresses and setup whitelist for `onlyExchange`-restricted functions. Asset addresses and
   * symbols are stored for informational purposes
   *
   * @param _baseAssetAddress The base asset address
   * @param _quoteAssetAddress The quote asset address
   * @param _baseAssetSymbol The base asset symbol
   * @param _quoteAssetSymbol The quote asset symbol
   */

  constructor(
    address _baseAssetAddress,
    address _quoteAssetAddress,
    string memory _baseAssetSymbol,
    string memory _quoteAssetSymbol
  ) ERC20('', 'IDEX-LP') {
    custodian = IExchange(msg.sender).loadCustodian();
    require(address(custodian) != address(0x0), 'Invalid Custodian address');

    // Assets cannot be equal
    require(
      _baseAssetAddress != _quoteAssetAddress,
      'Assets must be different'
    );

    // Each asset must be the native asset or contract
    require(
      _baseAssetAddress == address(0x0) ||
        Address.isContract(_baseAssetAddress),
      'Invalid base asset'
    );
    require(
      _quoteAssetAddress == address(0x0) ||
        Address.isContract(_quoteAssetAddress),
      'Invalid quote asset'
    );

    baseAssetAddress = _baseAssetAddress;
    quoteAssetAddress = _quoteAssetAddress;
    baseAssetSymbol = _baseAssetSymbol;
    quoteAssetSymbol = _quoteAssetSymbol;
  }

  /**
   * @notice Returns the name of the token
   */
  function name() public view override returns (string memory) {
    return
      string(
        abi.encodePacked('IDEX LP: ', baseAssetSymbol, '-', quoteAssetSymbol)
      );
  }

  /**
   * @notice Returns the address of the base-quote pair asset with the lower sort order
   */
  function token0() external view override returns (address) {
    return
      baseAssetAddress < quoteAssetAddress
        ? baseAssetAddress
        : quoteAssetAddress;
  }

  /**
   * @notice Returns the address of the base-quote pair asset with the higher sort order
   */
  function token1() external view override returns (address) {
    return
      baseAssetAddress < quoteAssetAddress
        ? quoteAssetAddress
        : baseAssetAddress;
  }

  /**
   * @notice Burns LP tokens by removing them from `wallet`'s balance and total supply
   */
  function burn(
    address wallet,
    uint256 liquidity,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address to
  ) external override onlyExchange {
    _burn(address(custodian), liquidity);

    emit Burn(
      wallet,
      baseAssetQuantityInAssetUnits,
      quoteAssetQuantityInAssetUnits,
      to
    );
  }

  /**
   * @notice Mints LP tokens by adding them to `wallet`'s balance and total supply
   */
  function mint(
    address wallet,
    uint256 liquidity,
    uint256 baseAssetQuantityInAssetUnits,
    uint256 quoteAssetQuantityInAssetUnits,
    address to
  ) external override onlyExchange {
    _mint(to, liquidity);

    emit Mint(
      wallet,
      baseAssetQuantityInAssetUnits,
      quoteAssetQuantityInAssetUnits
    );
  }

  /**
   * @notice Reverses the asset pair represented by this token by swapping `baseAssetAddress` with
   * `quoteAssetAddress` and `baseAssetSymbol` with `quoteAssetSymbol`
   */
  function reverseAssets() external override onlyExchange {
    // Assign swapped values to intermediate values first as Solidity won't allow multiple storage
    // writes in a single statement
    (
      address _baseAssetAddress,
      address _quoteAssetAddress,
      string memory _baseAssetSymbol,
      string memory _quoteAssetSymbol
    ) =
      (quoteAssetAddress, baseAssetAddress, quoteAssetSymbol, baseAssetSymbol);
    (baseAssetAddress, quoteAssetAddress, baseAssetSymbol, quoteAssetSymbol) = (
      _baseAssetAddress,
      _quoteAssetAddress,
      _baseAssetSymbol,
      _quoteAssetSymbol
    );
  }
}

File 27 of 38 : Math.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

library Math {
  function multiplyPipsByFraction(
    uint64 multiplicand,
    uint64 fractionDividend,
    uint64 fractionDivisor
  ) internal pure returns (uint64) {
    uint256 dividend = uint256(multiplicand) * fractionDividend;
    uint256 result = dividend / fractionDivisor;

    require(result < 2**64, 'Pip quantity overflows uint64');

    return uint64(result);
  }

  function min(uint64 x, uint64 y) internal pure returns (uint64 z) {
    z = x < y ? x : y;
  }

  function sqrt(uint256 y) internal pure returns (uint256 z) {
    if (y > 3) {
      z = y;
      uint256 x = y / 2 + 1;
      while (x < z) {
        z = x;
        x = (y / x + x) / 2;
      }
    } else if (y != 0) {
      z = 1;
    }
  }
}

File 28 of 38 : NonceInvalidations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { NonceInvalidation } from './Structs.sol';
import { UUID } from './UUID.sol';

library NonceInvalidations {
  function invalidateOrderNonce(
    mapping(address => NonceInvalidation) storage self,
    uint128 nonce,
    uint256 chainPropagationPeriod
  ) external returns (uint64 timestampInMs, uint256 effectiveBlockNumber) {
    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 timestamp too high');

    if (self[msg.sender].exists) {
      require(
        self[msg.sender].timestampInMs < timestampInMs,
        'Nonce timestamp invalidated'
      );
      require(
        self[msg.sender].effectiveBlockNumber <= block.number,
        'Last invalidation not finalized'
      );
    }

    // Changing the Chain Propagation Period will not affect the effectiveBlockNumber for this invalidation
    effectiveBlockNumber = block.number + chainPropagationPeriod;
    self[msg.sender] = NonceInvalidation(
      true,
      timestampInMs,
      effectiveBlockNumber
    );
  }

  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;
  }
}

File 29 of 38 : OrderBookTradeValidations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { Constants } from './Constants.sol';
import { OrderSide } from './Enums.sol';
import { Hashing } from './Hashing.sol';
import { UUID } from './UUID.sol';
import { Validations } from './Validations.sol';
import { Asset, Order, OrderBookTrade, NonceInvalidation } from './Structs.sol';

library OrderBookTradeValidations {
  using AssetRegistry for AssetRegistry.Storage;

  function validateOrderBookTrade(
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory trade,
    AssetRegistry.Storage storage assetRegistry,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view returns (bytes32, bytes32) {
    require(
      buy.walletAddress != sell.walletAddress,
      'Self-trading not allowed'
    );

    // Order book trade validations
    validateAssetPair(buy, sell, trade, assetRegistry);
    validateLimitPrices(buy, sell, trade);
    Validations.validateOrderNonces(buy, sell, nonceInvalidations);
    (bytes32 buyHash, bytes32 sellHash) =
      validateOrderSignatures(buy, sell, trade);
    validateFees(trade);

    return (buyHash, sellHash);
  }

  function validateAssetPair(
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory trade,
    AssetRegistry.Storage storage assetRegistry
  ) internal view {
    require(
      trade.baseAssetAddress != trade.quoteAssetAddress,
      'Trade assets must be different'
    );

    // Fee asset validation
    require(
      (trade.makerFeeAssetAddress == trade.baseAssetAddress &&
        trade.takerFeeAssetAddress == trade.quoteAssetAddress) ||
        (trade.makerFeeAssetAddress == trade.quoteAssetAddress &&
          trade.takerFeeAssetAddress == trade.baseAssetAddress),
      'Fee assets mismatch trade pair'
    );

    validateAssetPair(buy, trade, assetRegistry);
    validateAssetPair(sell, trade, assetRegistry);
  }

  function validateAssetPair(
    Order memory order,
    OrderBookTrade memory trade,
    AssetRegistry.Storage storage assetRegistry
  ) internal view {
    uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(order.nonce);
    Asset memory baseAsset =
      assetRegistry.loadAssetBySymbol(trade.baseAssetSymbol, timestampInMs);
    Asset memory quoteAsset =
      assetRegistry.loadAssetBySymbol(trade.quoteAssetSymbol, timestampInMs);

    require(
      baseAsset.assetAddress == trade.baseAssetAddress &&
        quoteAsset.assetAddress == trade.quoteAssetAddress,
      'Order symbol address mismatch'
    );
  }

  function validateLimitPrices(
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory trade
  ) internal pure {
    require(
      trade.grossBaseQuantityInPips > 0,
      'Base quantity must be greater than zero'
    );
    require(
      trade.grossQuoteQuantityInPips > 0,
      'Quote quantity must be greater than zero'
    );

    if (Validations.isLimitOrderType(buy.orderType)) {
      require(
        Validations.calculateImpliedQuoteQuantityInPips(
          trade.grossBaseQuantityInPips,
          buy.limitPriceInPips
        ) >= trade.grossQuoteQuantityInPips,
        'Buy order limit price exceeded'
      );
    }

    if (Validations.isLimitOrderType(sell.orderType)) {
      require(
        Validations.calculateImpliedQuoteQuantityInPips(
          trade.grossBaseQuantityInPips,
          sell.limitPriceInPips
        ) <= trade.grossQuoteQuantityInPips,
        'Sell order limit price exceeded'
      );
    }
  }

  function validateOrderSignatures(
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory trade
  ) internal pure returns (bytes32, bytes32) {
    bytes32 buyOrderHash =
      validateOrderSignature(
        buy,
        trade.baseAssetSymbol,
        trade.quoteAssetSymbol
      );
    bytes32 sellOrderHash =
      validateOrderSignature(
        sell,
        trade.baseAssetSymbol,
        trade.quoteAssetSymbol
      );

    return (buyOrderHash, sellOrderHash);
  }

  function validateOrderSignature(
    Order memory order,
    string memory baseAssetSymbol,
    string memory quoteAssetSymbol
  ) internal pure returns (bytes32) {
    bytes32 orderHash =
      Hashing.getOrderHash(order, baseAssetSymbol, quoteAssetSymbol);

    require(
      Hashing.isSignatureValid(
        orderHash,
        order.walletSignature,
        order.walletAddress
      ),
      order.side == OrderSide.Buy
        ? 'Invalid wallet signature for buy order'
        : 'Invalid wallet signature for sell order'
    );

    return orderHash;
  }

  function validateFees(OrderBookTrade memory trade) private pure {
    uint64 makerTotalQuantityInPips =
      trade.makerFeeAssetAddress == trade.baseAssetAddress
        ? trade.grossBaseQuantityInPips
        : trade.grossQuoteQuantityInPips;
    require(
      Validations.isFeeQuantityValid(
        trade.makerFeeQuantityInPips,
        makerTotalQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive maker fee'
    );

    uint64 takerTotalQuantityInPips =
      trade.takerFeeAssetAddress == trade.baseAssetAddress
        ? trade.grossBaseQuantityInPips
        : trade.grossQuoteQuantityInPips;
    require(
      Validations.isFeeQuantityValid(
        trade.takerFeeQuantityInPips,
        takerTotalQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive taker fee'
    );

    Validations.validateOrderBookTradeFees(trade);
  }
}

File 30 of 38 : Owned.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

/**
 * @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);
  }
}

File 31 of 38 : PoolTradeHelpers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { OrderSide } from './Enums.sol';
import { PoolTrade } from './Structs.sol';

library PoolTradeHelpers {
  /**
   * @dev Address of asset order wallet is receiving from pool
   */
  function getOrderCreditAssetAddress(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (address) {
    return
      orderSide == OrderSide.Buy
        ? self.baseAssetAddress
        : self.quoteAssetAddress;
  }

  /**
   * @dev Address of asset order wallet is giving to pool
   */
  function getOrderDebitAssetAddress(PoolTrade memory self, OrderSide orderSide)
    internal
    pure
    returns (address)
  {
    return
      orderSide == OrderSide.Buy
        ? self.quoteAssetAddress
        : self.baseAssetAddress;
  }

  /**
   * @dev Quantity in pips of asset that order wallet is receiving from pool
   */
  function calculateOrderCreditQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      (
        orderSide == OrderSide.Buy
          ? self.netBaseQuantityInPips
          : self.netQuoteQuantityInPips
      ) - self.takerGasFeeQuantityInPips;
  }

  /**
   * @dev Quantity in pips of asset that order wallet is giving to pool
   */
  function getOrderDebitQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      orderSide == OrderSide.Buy
        ? self.grossQuoteQuantityInPips
        : self.grossBaseQuantityInPips;
  }

  /**
   * @dev Quantity in pips of asset that pool receives from order wallet
   */
  function calculatePoolCreditQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      (
        orderSide == OrderSide.Buy
          ? self.netQuoteQuantityInPips
          : self.netBaseQuantityInPips
      ) + self.takerPoolFeeQuantityInPips;
  }

  /**
   * @dev Quantity in pips of asset that leaves pool as output
   */
  function getPoolDebitQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return (
      orderSide == OrderSide.Buy
        ? self.netBaseQuantityInPips // Pool gives net base asset plus taker gas fee
        : self.netQuoteQuantityInPips // Pool gives net quote asset plus taker gas fee
    );
  }

  /**
   * @dev Gross quantity received by order wallet
   */
  function getOrderGrossReceivedQuantityInPips(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      orderSide == OrderSide.Buy
        ? self.grossBaseQuantityInPips
        : self.grossQuoteQuantityInPips;
  }

  function calculatePoolOutputAdjustment(
    PoolTrade memory self,
    OrderSide orderSide
  ) internal pure returns (uint64) {
    return
      getOrderGrossReceivedQuantityInPips(self, orderSide) -
      getPoolDebitQuantityInPips(self, orderSide);
  }
}

File 32 of 38 : PoolTradeValidations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { Constants } from './Constants.sol';
import { OrderSide } from './Enums.sol';
import { PoolTradeHelpers } from './PoolTradeHelpers.sol';
import { UUID } from './UUID.sol';
import { Validations } from './Validations.sol';
import { Asset, Order, NonceInvalidation, PoolTrade } from './Structs.sol';

library PoolTradeValidations {
  using AssetRegistry for AssetRegistry.Storage;
  using PoolTradeHelpers for PoolTrade;

  function validatePoolTrade(
    Order memory order,
    PoolTrade memory poolTrade,
    AssetRegistry.Storage storage assetRegistry,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view returns (bytes32 orderHash) {
    orderHash = Validations.validateOrderSignature(
      order,
      poolTrade.baseAssetSymbol,
      poolTrade.quoteAssetSymbol
    );
    validateAssetPair(order, poolTrade, assetRegistry);
    validateLimitPrice(order, poolTrade);
    Validations.validateOrderNonce(order, nonceInvalidations);
    validateFees(order.side, poolTrade);
  }

  function validateAssetPair(
    Order memory order,
    PoolTrade memory poolTrade,
    AssetRegistry.Storage storage assetRegistry
  ) internal view {
    require(
      poolTrade.baseAssetAddress != poolTrade.quoteAssetAddress,
      'Trade assets must be different'
    );

    uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(order.nonce);
    Asset memory baseAsset =
      assetRegistry.loadAssetBySymbol(poolTrade.baseAssetSymbol, timestampInMs);
    Asset memory quoteAsset =
      assetRegistry.loadAssetBySymbol(
        poolTrade.quoteAssetSymbol,
        timestampInMs
      );

    require(
      baseAsset.assetAddress == poolTrade.baseAssetAddress &&
        quoteAsset.assetAddress == poolTrade.quoteAssetAddress,
      'Order symbol address mismatch'
    );
  }

  function validateLimitPrice(Order memory order, PoolTrade memory poolTrade)
    internal
    pure
  {
    require(
      poolTrade.grossBaseQuantityInPips > 0,
      'Base quantity must be greater than zero'
    );
    require(
      poolTrade.grossQuoteQuantityInPips > 0,
      'Quote quantity must be greater than zero'
    );

    if (
      order.side == OrderSide.Buy &&
      Validations.isLimitOrderType(order.orderType)
    ) {
      require(
        Validations.calculateImpliedQuoteQuantityInPips(
          poolTrade.grossBaseQuantityInPips,
          order.limitPriceInPips
        ) >= poolTrade.grossQuoteQuantityInPips,
        'Buy order limit price exceeded'
      );
    }

    if (
      order.side == OrderSide.Sell &&
      Validations.isLimitOrderType(order.orderType)
    ) {
      require(
        Validations.calculateImpliedQuoteQuantityInPips(
          poolTrade.grossBaseQuantityInPips - 1,
          order.limitPriceInPips
        ) <= poolTrade.grossQuoteQuantityInPips,
        'Sell order limit price exceeded'
      );
    }
  }

  function validateFees(OrderSide orderSide, PoolTrade memory poolTrade)
    private
    pure
  {
    require(
      Validations.isFeeQuantityValid(
        poolTrade.calculatePoolOutputAdjustment(orderSide),
        poolTrade.getOrderGrossReceivedQuantityInPips(orderSide),
        Constants.maxPoolOutputAdjustmentBasisPoints
      ),
      'Excessive pool output adjustment'
    );

    require(
      Validations.isFeeQuantityValid(
        poolTrade.takerGasFeeQuantityInPips,
        poolTrade.getOrderGrossReceivedQuantityInPips(orderSide),
        Constants.maxFeeBasisPoints
      ),
      'Excessive gas fee'
    );

    // Price correction only allowed for hybrid trades with a taker sell
    require(
      poolTrade.takerPriceCorrectionFeeQuantityInPips == 0,
      'Price correction not allowed'
    );

    Validations.validatePoolTradeInputFees(orderSide, poolTrade);
  }
}

File 33 of 38 : SafeCast.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such 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.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCast {
    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits");
        return int128(value);
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits");
        return int64(value);
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits");
        return int32(value);
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits");
        return int16(value);
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits");
        return int8(value);
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        require(value < 2**255, "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

File 34 of 38 : Structs.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { ILiquidityProviderToken, IWETH9 } from './Interfaces.sol';
import {
  LiquidityChangeOrigination,
  OrderSelfTradePrevention,
  OrderSide,
  OrderTimeInForce,
  OrderType,
  WithdrawalType
} from './Enums.sol';

/**
 * @notice Struct definitions
 */

/**
 * @notice State tracking for a hybrid liquidity pool
 *
 * @dev Base and quote asset decimals are denormalized here to avoid extra loads from
 * `AssetRegistry.Storage`
 */
struct LiquidityPool {
  // Flag to distinguish from empty struct
  bool exists;
  uint64 baseAssetReserveInPips;
  uint8 baseAssetDecimals;
  uint64 quoteAssetReserveInPips;
  uint8 quoteAssetDecimals;
  ILiquidityProviderToken liquidityProviderToken;
}

/**
 * @dev Internal struct capturing user-initiated liquidity addition request parameters
 */
struct LiquidityAddition {
  // Must equal `Constants.signatureHashVersion`
  uint8 signatureHashVersion;
  // Distinguishes between liquidity additions initated on- or off- chain
  LiquidityChangeOrigination origination;
  // UUIDv1 unique to wallet
  uint128 nonce;
  address wallet;
  address assetA;
  address assetB;
  uint256 amountADesired;
  uint256 amountBDesired;
  uint256 amountAMin;
  uint256 amountBMin;
  address to;
  uint256 deadline;
  bytes signature;
}

/**
 * @notice Internally used struct, return type from `LiquidityPools.addLiquidity`
 */
struct LiquidityAdditionDepositResult {
  string assetASymbol;
  uint64 assetAQuantityInPips;
  uint64 assetANewExchangeBalanceInPips;
  uint256 assetANewExchangeBalanceInAssetUnits;
  string assetBSymbol;
  uint64 assetBQuantityInPips;
  uint64 assetBNewExchangeBalanceInPips;
  uint256 assetBNewExchangeBalanceInAssetUnits;
}

/**
 * @notice Internally used struct, return type from `LiquidityPools.removeLiquidity`
 */
struct LiquidityRemovalDepositResult {
  address assetAddress;
  string assetSymbol;
  uint64 assetQuantityInPips;
  uint64 assetNewExchangeBalanceInPips;
  uint256 assetNewExchangeBalanceInAssetUnits;
}

/**
 * @dev Internal struct capturing user-initiated liquidity removal request parameters
 */
struct LiquidityRemoval {
  // Must equal `Constants.signatureHashVersion`
  uint8 signatureHashVersion;
  // Distinguishes between liquidity additions initated on- or off- chain
  LiquidityChangeOrigination origination;
  uint128 nonce;
  address wallet;
  address assetA;
  address assetB;
  uint256 liquidity;
  uint256 amountAMin;
  uint256 amountBMin;
  address payable to;
  uint256 deadline;
  bytes signature;
}

/**
 * @notice Argument type to `Exchange.executeAddLiquidity` and `Exchange.executeRemoveLiquidity`
 */
struct LiquidityChangeExecution {
  address baseAssetAddress;
  address quoteAssetAddress;
  uint64 liquidityInPips;
  // 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 sent to pool for additions or received by wallet for removals
  uint64 netBaseQuantityInPips;
  // Net amount of quote asset sent to pool for additions or received by wallet for removals
  uint64 netQuoteQuantityInPips;
}

/**
 * @notice Internally used struct, argument type to `LiquidityPoolAdmin.migrateLiquidityPool`
 */
struct LiquidityMigration {
  address token0;
  address token1;
  bool isToken1Quote;
  uint256 desiredLiquidity;
  address to;
  IWETH9 WETH;
}

/**
 * @notice Internally used struct capturing wallet order nonce invalidations created via `invalidateOrderNonce`
 */
struct NonceInvalidation {
  bool exists;
  uint64 timestampInMs;
  uint256 effectiveBlockNumber;
}

/**
 * @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.executeOrderBookTrade` and `Hashing.getOrderWalletHash`
 */
struct Order {
  // Must equal `Constants.signatureHashVersion`
  uint8 signatureHashVersion;
  // UUIDv1 unique to wallet
  uint128 nonce;
  // Wallet address that placed order and signed hash
  address walletAddress;
  // Type of order
  OrderType orderType;
  // Order side wallet is on
  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
  OrderTimeInForce timeInForce;
  // STP behavior specified by wallet for order
  OrderSelfTradePrevention selfTradePrevention;
  // Cancellation time specified by wallet for GTT TIF order
  uint64 cancelAfter;
  // The ECDSA signature of the order hash as produced by Hashing.getOrderWalletHash
  bytes walletSignature;
}

/**
 * @notice Argument type for `Exchange.executeOrderBookTrade` specifying execution parameters for matching orders
 */
struct OrderBookTrade {
  // 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, inclusive of gas fees
  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
  OrderSide makerSide;
}

/**
 * @notice Argument type for `Exchange.executePoolTrade` specifying execution parameters for an
 * order against pool liquidity
 */
struct PoolTrade {
  // 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;
  // If wallet is buy side, net amount of quote input to pool used to calculate output; otherwise,
  // net amount of base asset leaving pool
  uint64 netBaseQuantityInPips;
  // If wallet is buy side, net amount of base input to pool used to calculate output; otherwise,
  // net amount of quote asset leaving pool
  uint64 netQuoteQuantityInPips;
  // Fee paid by liquidity taker to pool from sent asset
  uint64 takerPoolFeeQuantityInPips;
  // Fee paid by liquidity taker to fee wallet from sent asset
  uint64 takerProtocolFeeQuantityInPips;
  // Fee paid by liquidity taker to fee wallet from received asset
  uint64 takerGasFeeQuantityInPips;
  // Fee paid by liquidity taker sell to pool taken from pool's quote asset output
  uint64 takerPriceCorrectionFeeQuantityInPips;
}

struct HybridTrade {
  OrderBookTrade orderBookTrade;
  PoolTrade poolTrade;
  // Fee paid by liquidity taker to fee wallet from received asset
  uint64 takerGasFeeQuantityInPips;
}

/**
 * @notice Argument type for `Exchange.withdraw` and `Hashing.getWithdrawalWalletHash`
 */
struct Withdrawal {
  // Distinguishes between withdrawals by asset symbol or address
  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 grossQuantityInPips;
  // 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 Hashing.getWithdrawalWalletHash
  bytes walletSignature;
}

File 35 of 38 : Trading.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { HybridTradeValidations } from './HybridTradeValidations.sol';
import { LiquidityPools } from './LiquidityPools.sol';
import { OrderBookTradeValidations } from './OrderBookTradeValidations.sol';
import { PoolTradeHelpers } from './PoolTradeHelpers.sol';
import { PoolTradeValidations } from './PoolTradeValidations.sol';
import { Validations } from './Validations.sol';
import { OrderSide, OrderType } from './Enums.sol';
import {
  HybridTrade,
  Order,
  OrderBookTrade,
  NonceInvalidation,
  PoolTrade
} from './Structs.sol';

library Trading {
  using AssetRegistry for AssetRegistry.Storage;
  using BalanceTracking for BalanceTracking.Storage;
  using LiquidityPools for LiquidityPools.Storage;
  using PoolTradeHelpers for PoolTrade;

  function executeOrderBookTrade(
    Order memory buy,
    Order memory sell,
    OrderBookTrade memory orderBookTrade,
    address feeWallet,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(address => NonceInvalidation) storage nonceInvalidations,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips
  ) public {
    (bytes32 buyHash, bytes32 sellHash) =
      OrderBookTradeValidations.validateOrderBookTrade(
        buy,
        sell,
        orderBookTrade,
        assetRegistry,
        nonceInvalidations
      );

    updateOrderFilledQuantities(
      buy,
      buyHash,
      sell,
      sellHash,
      orderBookTrade,
      completedOrderHashes,
      partiallyFilledOrderQuantitiesInPips
    );

    balanceTracking.updateForOrderBookTrade(
      buy,
      sell,
      orderBookTrade,
      feeWallet
    );
  }

  function executePoolTrade(
    Order memory order,
    PoolTrade memory poolTrade,
    address feeWallet,
    AssetRegistry.Storage storage assetRegistry,
    LiquidityPools.Storage storage liquidityPoolRegistry,
    BalanceTracking.Storage storage balanceTracking,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(address => NonceInvalidation) storage nonceInvalidations,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips
  ) public {
    bytes32 orderHash =
      PoolTradeValidations.validatePoolTrade(
        order,
        poolTrade,
        assetRegistry,
        nonceInvalidations
      );

    updateOrderFilledQuantity(
      order,
      orderHash,
      poolTrade.grossBaseQuantityInPips,
      poolTrade.grossQuoteQuantityInPips,
      completedOrderHashes,
      partiallyFilledOrderQuantitiesInPips
    );

    balanceTracking.updateForPoolTrade(order, poolTrade, feeWallet);
    liquidityPoolRegistry.updateReservesForPoolTrade(poolTrade, order.side);
  }

  function executeHybridTrade(
    Order memory buy,
    Order memory sell,
    HybridTrade memory hybridTrade,
    address feeWallet,
    AssetRegistry.Storage storage assetRegistry,
    LiquidityPools.Storage storage liquidityPoolRegistry,
    BalanceTracking.Storage storage balanceTracking,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(address => NonceInvalidation) storage nonceInvalidations,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips
  ) public {
    (bytes32 buyHash, bytes32 sellHash) =
      HybridTradeValidations.validateHybridTrade(
        buy,
        sell,
        hybridTrade,
        assetRegistry,
        nonceInvalidations
      );

    executeHybridTradePoolComponent(
      buy,
      sell,
      buyHash,
      sellHash,
      hybridTrade,
      feeWallet,
      liquidityPoolRegistry,
      balanceTracking,
      completedOrderHashes,
      partiallyFilledOrderQuantitiesInPips
    );

    {
      // Order book trade
      updateOrderFilledQuantities(
        buy,
        buyHash,
        sell,
        sellHash,
        hybridTrade.orderBookTrade,
        completedOrderHashes,
        partiallyFilledOrderQuantitiesInPips
      );

      balanceTracking.updateForOrderBookTrade(
        buy,
        sell,
        hybridTrade.orderBookTrade,
        feeWallet
      );
    }

    {
      address takerWallet =
        hybridTrade.orderBookTrade.makerSide == OrderSide.Buy
          ? sell.walletAddress
          : buy.walletAddress;
      balanceTracking.updateForHybridTradeFees(
        hybridTrade,
        takerWallet,
        feeWallet
      );
    }
  }

  function executeHybridTradePoolComponent(
    Order memory buy,
    Order memory sell,
    bytes32 buyHash,
    bytes32 sellHash,
    HybridTrade memory hybridTrade,
    address feeWallet,
    LiquidityPools.Storage storage liquidityPoolRegistry,
    BalanceTracking.Storage storage balanceTracking,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips
  ) private {
    (Order memory makerOrder, Order memory takerOrder, bytes32 takerOrderHash) =
      hybridTrade.orderBookTrade.makerSide == OrderSide.Buy
        ? (buy, sell, sellHash)
        : (sell, buy, buyHash);

    updateOrderFilledQuantity(
      takerOrder,
      takerOrderHash,
      hybridTrade.poolTrade.grossBaseQuantityInPips,
      hybridTrade.poolTrade.grossQuoteQuantityInPips,
      completedOrderHashes,
      partiallyFilledOrderQuantitiesInPips
    );

    balanceTracking.updateForPoolTrade(
      takerOrder,
      hybridTrade.poolTrade,
      feeWallet
    );

    (uint64 baseAssetReserveInPips, uint64 quoteAssetReserveInPips) =
      liquidityPoolRegistry.updateReservesForPoolTrade(
        hybridTrade.poolTrade,
        takerOrder.side
      );

    HybridTradeValidations.validatePoolPrice(
      makerOrder,
      baseAssetReserveInPips,
      quoteAssetReserveInPips
    );
  }

  function updateOrderFilledQuantities(
    Order memory buy,
    bytes32 buyHash,
    Order memory sell,
    bytes32 sellHash,
    OrderBookTrade memory orderBookTrade,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips
  ) private {
    // Buy side
    updateOrderFilledQuantity(
      buy,
      buyHash,
      orderBookTrade.grossBaseQuantityInPips,
      orderBookTrade.grossQuoteQuantityInPips,
      completedOrderHashes,
      partiallyFilledOrderQuantitiesInPips
    );
    // Sell side
    updateOrderFilledQuantity(
      sell,
      sellHash,
      orderBookTrade.grossBaseQuantityInPips,
      orderBookTrade.grossQuoteQuantityInPips,
      completedOrderHashes,
      partiallyFilledOrderQuantitiesInPips
    );
  }

  // Update filled quantities tracking for order to prevent over- or double-filling orders
  function updateOrderFilledQuantity(
    Order memory order,
    bytes32 orderHash,
    uint64 grossBaseQuantityInPips,
    uint64 grossQuoteQuantityInPips,
    mapping(bytes32 => bool) storage completedOrderHashes,
    mapping(bytes32 => uint64) storage partiallyFilledOrderQuantitiesInPips
  ) private {
    require(!completedOrderHashes[orderHash], 'Order 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),
        'Order quote quantity only valid for market orders'
      );
      newFilledQuantityInPips =
        grossQuoteQuantityInPips +
        partiallyFilledOrderQuantitiesInPips[orderHash];
    } else {
      // All other orders track partially filled quantities in base terms
      newFilledQuantityInPips =
        grossBaseQuantityInPips +
        partiallyFilledOrderQuantitiesInPips[orderHash];
    }

    uint64 quantityInPips = order.quantityInPips;
    require(newFilledQuantityInPips <= quantityInPips, 'Order overfilled');
    if (newFilledQuantityInPips < 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;
    }
  }

  function isMarketOrderType(OrderType orderType) private pure returns (bool) {
    return
      orderType == OrderType.Market ||
      orderType == OrderType.StopLoss ||
      orderType == OrderType.TakeProfit;
  }
}

File 36 of 38 : UUID.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

/**
 * Library helper for extracting timestamp component of Version 1 UUIDs
 */
library UUID {
  /**
   * 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) - 12219292800000;

    return msSinceUnixEpoch;
  }
}

File 37 of 38 : Validations.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { Constants } from './Constants.sol';
import { Hashing } from './Hashing.sol';
import { IERC20 } from './Interfaces.sol';
import { Math } from './Math.sol';
import { UUID } from './UUID.sol';
import { OrderSide, OrderType } from './Enums.sol';
import {
  Asset,
  LiquidityPool,
  Order,
  OrderBookTrade,
  NonceInvalidation,
  PoolTrade,
  Withdrawal
} from './Structs.sol';

library Validations {
  using AssetRegistry for AssetRegistry.Storage;

  /**
   * @dev Perform fee validations common to both orderbook-only and hybrid trades. Does not
   * validate if fees are excessive as taker fee structure differs between these trade types
   *
   */
  function validateOrderBookTradeFees(OrderBookTrade memory trade)
    internal
    pure
  {
    require(
      trade.netBaseQuantityInPips +
        (
          trade.makerFeeAssetAddress == trade.baseAssetAddress
            ? trade.makerFeeQuantityInPips
            : trade.takerFeeQuantityInPips
        ) ==
        trade.grossBaseQuantityInPips,
      'Orderbook base fees unbalanced'
    );
    require(
      trade.netQuoteQuantityInPips +
        (
          trade.makerFeeAssetAddress == trade.quoteAssetAddress
            ? trade.makerFeeQuantityInPips
            : trade.takerFeeQuantityInPips
        ) ==
        trade.grossQuoteQuantityInPips,
      'Orderbook quote fees unbalanced'
    );
  }

  function validateOrderNonce(
    Order memory order,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view {
    require(
      UUID.getTimestampInMsFromUuidV1(order.nonce) >
        loadLastInvalidatedTimestamp(order.walletAddress, nonceInvalidations),
      'Order nonce timestamp too low'
    );
  }

  function validateOrderNonces(
    Order memory buy,
    Order memory sell,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) internal view {
    require(
      UUID.getTimestampInMsFromUuidV1(buy.nonce) >
        loadLastInvalidatedTimestamp(buy.walletAddress, nonceInvalidations),
      'Buy order nonce timestamp too low'
    );
    require(
      UUID.getTimestampInMsFromUuidV1(sell.nonce) >
        loadLastInvalidatedTimestamp(sell.walletAddress, nonceInvalidations),
      'Sell order nonce timestamp too low'
    );
  }

  function validateOrderSignature(
    Order memory order,
    string memory baseAssetSymbol,
    string memory quoteAssetSymbol
  ) internal pure returns (bytes32) {
    bytes32 orderHash =
      Hashing.getOrderHash(order, baseAssetSymbol, quoteAssetSymbol);

    require(
      Hashing.isSignatureValid(
        orderHash,
        order.walletSignature,
        order.walletAddress
      ),
      order.side == OrderSide.Buy
        ? 'Invalid wallet signature for buy order'
        : 'Invalid wallet signature for sell order'
    );

    return orderHash;
  }

  function validatePoolReserveRatio(LiquidityPool memory pool) internal pure {
    (uint64 sortedReserve0, uint64 sortedReserve1) =
      pool.baseAssetReserveInPips <= pool.quoteAssetReserveInPips
        ? (pool.baseAssetReserveInPips, pool.quoteAssetReserveInPips)
        : (pool.quoteAssetReserveInPips, pool.baseAssetReserveInPips);
    require(
      uint256(sortedReserve0) * Constants.maxLiquidityPoolReserveRatio >=
        sortedReserve1,
      'Exceeded max reserve ratio'
    );
  }

  /**
   * @dev Perform fee validations common to both pool-only and hybrid trades
   */
  function validatePoolTradeInputFees(
    OrderSide orderSide,
    PoolTrade memory poolTrade
  ) internal pure {
    // Buy order sends quote as pool input, receives base as pool output; sell order sends base as
    // pool input, receives quote as pool output
    (uint64 netInputQuantityInPips, uint64 grossInputQuantityInPips) =
      orderSide == OrderSide.Buy
        ? (poolTrade.netQuoteQuantityInPips, poolTrade.grossQuoteQuantityInPips)
        : (poolTrade.netBaseQuantityInPips, poolTrade.grossBaseQuantityInPips);

    require(
      netInputQuantityInPips +
        poolTrade.takerPoolFeeQuantityInPips +
        poolTrade.takerProtocolFeeQuantityInPips ==
        grossInputQuantityInPips,
      'Pool input fees unbalanced'
    );
    require(
      Validations.isFeeQuantityValid(
        grossInputQuantityInPips - netInputQuantityInPips,
        grossInputQuantityInPips,
        Constants.maxPoolInputFeeBasisPoints
      ),
      'Excessive pool input fee'
    );
  }

  function validateWithdrawalSignature(Withdrawal memory withdrawal)
    internal
    pure
    returns (bytes32)
  {
    bytes32 withdrawalHash = Hashing.getWithdrawalHash(withdrawal);

    require(
      Hashing.isSignatureValid(
        withdrawalHash,
        withdrawal.walletSignature,
        withdrawal.walletAddress
      ),
      'Invalid wallet signature'
    );

    return withdrawalHash;
  }

  // Utils //

  function calculateImpliedQuoteQuantityInPips(
    uint64 baseQuantityInPips,
    uint64 limitPriceInPips
  ) internal pure returns (uint64) {
    return
      Math.multiplyPipsByFraction(
        baseQuantityInPips,
        limitPriceInPips,
        Constants.pipPriceMultiplier
      );
  }

  function loadLastInvalidatedTimestamp(
    address walletAddress,
    mapping(address => NonceInvalidation) storage nonceInvalidations
  ) private view returns (uint64) {
    if (
      nonceInvalidations[walletAddress].exists &&
      nonceInvalidations[walletAddress].effectiveBlockNumber <= block.number
    ) {
      return nonceInvalidations[walletAddress].timestampInMs;
    }

    return 0;
  }

  function isFeeQuantityValid(
    uint64 fee,
    uint64 total,
    uint64 max
  ) internal pure returns (bool) {
    uint64 feeBasisPoints = (fee * Constants.basisPointsInTotal) / total;
    return feeBasisPoints <= max;
  }

  function isLimitOrderType(OrderType orderType) internal pure returns (bool) {
    return
      orderType == OrderType.Limit ||
      orderType == OrderType.LimitMaker ||
      orderType == OrderType.StopLossLimit ||
      orderType == OrderType.TakeProfitLimit;
  }
}

File 38 of 38 : Withdrawing.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity 0.8.10;

import { AssetRegistry } from './AssetRegistry.sol';
import { AssetUnitConversions } from './AssetUnitConversions.sol';
import { BalanceTracking } from './BalanceTracking.sol';
import { Constants } from './Constants.sol';
import { UUID } from './UUID.sol';
import { Validations } from './Validations.sol';
import { WithdrawalType } from './Enums.sol';
import {
  Asset,
  LiquidityChangeExecution,
  LiquidityRemoval,
  Withdrawal
} from './Structs.sol';
import { ICustodian, ILiquidityProviderToken } from './Interfaces.sol';

library Withdrawing {
  using AssetRegistry for AssetRegistry.Storage;
  using BalanceTracking for BalanceTracking.Storage;

  function withdraw(
    Withdrawal memory withdrawal,
    ICustodian custodian,
    address feeWallet,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking,
    mapping(bytes32 => bool) storage completedWithdrawalHashes
  )
    public
    returns (
      uint64 newExchangeBalanceInPips,
      uint256 newExchangeBalanceInAssetUnits,
      address assetAddress,
      string memory assetSymbol
    )
  {
    // Validations
    require(
      Validations.isFeeQuantityValid(
        withdrawal.gasFeeInPips,
        withdrawal.grossQuantityInPips,
        Constants.maxFeeBasisPoints
      ),
      'Excessive withdrawal fee'
    );
    bytes32 withdrawalHash =
      Validations.validateWithdrawalSignature(withdrawal);
    require(
      !completedWithdrawalHashes[withdrawalHash],
      'Hash already withdrawn'
    );

    // If withdrawal is by asset symbol (most common) then resolve to asset address
    Asset memory asset =
      withdrawal.withdrawalType == WithdrawalType.BySymbol
        ? assetRegistry.loadAssetBySymbol(
          withdrawal.assetSymbol,
          UUID.getTimestampInMsFromUuidV1(withdrawal.nonce)
        )
        : assetRegistry.loadAssetByAddress(withdrawal.assetAddress);

    assetSymbol = asset.symbol;
    assetAddress = asset.assetAddress;

    // Update wallet balances
    newExchangeBalanceInPips = balanceTracking.updateForWithdrawal(
      withdrawal,
      asset.assetAddress,
      feeWallet
    );
    newExchangeBalanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      newExchangeBalanceInPips,
      asset.decimals
    );

    // Transfer funds from Custodian to wallet
    uint256 netAssetQuantityInAssetUnits =
      AssetUnitConversions.pipsToAssetUnits(
        withdrawal.grossQuantityInPips - withdrawal.gasFeeInPips,
        asset.decimals
      );
    custodian.withdraw(
      withdrawal.walletAddress,
      asset.assetAddress,
      netAssetQuantityInAssetUnits
    );

    // Replay prevention
    completedWithdrawalHashes[withdrawalHash] = true;
  }

  function withdrawExit(
    address assetAddress,
    ICustodian custodian,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) external returns (uint64 previousExchangeBalanceInPips) {
    // Update wallet balance
    previousExchangeBalanceInPips = balanceTracking.updateForExit(
      msg.sender,
      assetAddress
    );

    // Transfer asset from Custodian to wallet
    Asset memory asset = assetRegistry.loadAssetByAddress(assetAddress);
    uint256 balanceInAssetUnits =
      AssetUnitConversions.pipsToAssetUnits(
        previousExchangeBalanceInPips,
        asset.decimals
      );
    ICustodian(custodian).withdraw(
      payable(msg.sender),
      assetAddress,
      balanceInAssetUnits
    );
  }

  function withdrawLiquidity(
    LiquidityRemoval memory removal,
    LiquidityChangeExecution memory execution,
    ICustodian custodian,
    address feeWallet,
    ILiquidityProviderToken liquidityProviderToken,
    AssetRegistry.Storage storage assetRegistry,
    BalanceTracking.Storage storage balanceTracking
  ) internal {
    (
      uint64 outputBaseAssetQuantityInPips,
      uint64 outputQuoteAssetQuantityInPips
    ) =
      balanceTracking.updateForRemoveLiquidity(
        removal,
        execution,
        feeWallet,
        address(custodian),
        liquidityProviderToken
      );

    Asset memory asset;
    if (outputBaseAssetQuantityInPips > 0) {
      asset = assetRegistry.loadAssetByAddress(execution.baseAssetAddress);
      custodian.withdraw(
        removal.to,
        execution.baseAssetAddress,
        AssetUnitConversions.pipsToAssetUnits(
          outputBaseAssetQuantityInPips,
          asset.decimals
        )
      );
    }
    if (outputQuoteAssetQuantityInPips > 0) {
      asset = assetRegistry.loadAssetByAddress(execution.quoteAssetAddress);
      custodian.withdraw(
        removal.to,
        execution.quoteAssetAddress,
        AssetUnitConversions.pipsToAssetUnits(
          outputQuoteAssetQuantityInPips,
          asset.decimals
        )
      );
    }
  }
}

Settings
{
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "abi"
      ]
    }
  },
  "optimizer": {
    "enabled": true,
    "runs": 1
  },
  "evmVersion": "berlin",
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "libraries": {
    "Exchange.sol": {
      "AssetRegistry": "0xbbdba7cc65545fc38bd6b10bf61ede08cd47bd5a",
      "Depositing": "0xedb960d352a6a52709c1dea56659d81755e04c41",
      "LiquidityPoolAdmin": "0x64b882ab50c2f94cefeab1568b2161b2c83080ce",
      "LiquidityPools": "0xfc881c4b2e5044a5b648f89e5fb0c9579f4a5075",
      "NonceInvalidations": "0xc956d06ee0ee5f7af081d609a409425c250ea95d",
      "Trading": "0x572eb8eaa977e34ad34ba5729cc4e099d823da3d",
      "Withdrawing": "0xd253f0f299db07f6661188466cde4ae407fe7c08"
    },
    "AssetRegistry.sol": {
      "AssetRegistry": "0xbbdba7cc65545fc38bd6b10bf61ede08cd47bd5a"
    },
    "Depositing.sol": {
      "Depositing": "0xedb960d352a6a52709c1dea56659d81755e04c41"
    },
    "LiquidityPoolAdmin.sol": {
      "LiquidityPoolAdmin": "0x64b882ab50c2f94cefeab1568b2161b2c83080ce"
    },
    "LiquidityPools.sol": {
      "LiquidityPools": "0xfc881c4b2e5044a5b648f89e5fb0c9579f4a5075"
    },
    "NonceInvalidations.sol": {
      "NonceInvalidations": "0xc956d06ee0ee5f7af081d609a409425c250ea95d"
    },
    "Trading.sol": {
      "Trading": "0x572eb8eaa977e34ad34ba5729cc4e099d823da3d"
    },
    "Withdrawing.sol": {
      "Withdrawing": "0xd253f0f299db07f6661188466cde4ae407fe7c08"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract IExchange","name":"balanceMigrationSource","type":"address"},{"internalType":"address","name":"feeWallet","type":"address"},{"internalType":"string","name":"nativeAssetSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"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":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"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":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"orderBookBaseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"orderBookQuoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"poolBaseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"poolQuoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"totalBaseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"totalQuoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"enum OrderSide","name":"takerSide","type":"uint8"}],"name":"HybridTradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidityInPips","type":"uint64"}],"name":"LiquidityAdditionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetA","type":"address"},{"indexed":false,"internalType":"address","name":"assetB","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountADesired","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountAMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountBMin","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"LiquidityAdditionInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"originalBaseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"originalQuoteAssetAddress","type":"address"}],"name":"LiquidityPoolAssetsReversed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"liquidityInPips","type":"uint64"}],"name":"LiquidityRemovalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetA","type":"address"},{"indexed":false,"internalType":"address","name":"assetB","type":"address"},{"indexed":false,"internalType":"uint256","name":"liquidity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountAMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountBMin","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"LiquidityRemovalInitiated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"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":"enum OrderSide","name":"takerSide","type":"uint8"}],"name":"OrderBookTradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"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":"wallet","type":"address"},{"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":"enum OrderSide","name":"takerSide","type":"uint8"}],"name":"PoolTradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IERC20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"}],"name":"TokenSymbolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"}],"name":"WalletExitCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"baseAssetAddress","type":"address"},{"indexed":false,"internalType":"address","name":"quoteAssetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"baseAssetQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteAssetQuantityInPips","type":"uint64"}],"name":"WalletExitLiquidityRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"}],"name":"WalletExitWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"WalletExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"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":[],"name":"_depositIndex","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"_walletExits","outputs":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"}],"name":"addTokenSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"cleanupWalletBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"confirmTokenRegistration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"createLiquidityPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","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":"enum LiquidityChangeOrigination","name":"origination","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetA","type":"address"},{"internalType":"address","name":"assetB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct LiquidityAddition","name":"addition","type":"tuple"},{"components":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"liquidityInPips","type":"uint64"},{"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":"struct LiquidityChangeExecution","name":"execution","type":"tuple"}],"name":"executeAddLiquidity","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 OrderType","name":"orderType","type":"uint8"},{"internalType":"enum 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 OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct 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 OrderType","name":"orderType","type":"uint8"},{"internalType":"enum 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 OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"sell","type":"tuple"},{"components":[{"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 OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct OrderBookTrade","name":"orderBookTrade","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":"uint64","name":"takerPoolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerProtocolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerGasFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerPriceCorrectionFeeQuantityInPips","type":"uint64"}],"internalType":"struct PoolTrade","name":"poolTrade","type":"tuple"},{"internalType":"uint64","name":"takerGasFeeQuantityInPips","type":"uint64"}],"internalType":"struct HybridTrade","name":"hybridTrade","type":"tuple"}],"name":"executeHybridTrade","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 OrderType","name":"orderType","type":"uint8"},{"internalType":"enum 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 OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct 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 OrderType","name":"orderType","type":"uint8"},{"internalType":"enum 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 OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct 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 OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct OrderBookTrade","name":"orderBookTrade","type":"tuple"}],"name":"executeOrderBookTrade","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 OrderType","name":"orderType","type":"uint8"},{"internalType":"enum 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 OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Order","name":"order","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":"uint64","name":"takerPoolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerProtocolFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerGasFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerPriceCorrectionFeeQuantityInPips","type":"uint64"}],"internalType":"struct PoolTrade","name":"poolTrade","type":"tuple"}],"name":"executePoolTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"enum LiquidityChangeOrigination","name":"origination","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetA","type":"address"},{"internalType":"address","name":"assetB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct LiquidityRemoval","name":"removal","type":"tuple"},{"components":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"liquidityInPips","type":"uint64"},{"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":"struct LiquidityChangeExecution","name":"execution","type":"tuple"}],"name":"executeRemoveLiquidity","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":"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 Asset","name":"","type":"tuple"}],"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":"loadCustodian","outputs":[{"internalType":"contract ICustodian","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadFeeWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadLiquidityMigrator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"loadLiquidityPoolByAssetAddresses","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"uint64","name":"baseAssetReserveInPips","type":"uint64"},{"internalType":"uint8","name":"baseAssetDecimals","type":"uint8"},{"internalType":"uint64","name":"quoteAssetReserveInPips","type":"uint64"},{"internalType":"uint8","name":"quoteAssetDecimals","type":"uint8"},{"internalType":"contract ILiquidityProviderToken","name":"liquidityProviderToken","type":"address"}],"internalType":"struct LiquidityPool","name":"","type":"tuple"}],"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":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"bool","name":"isToken1Quote","type":"bool"},{"internalType":"uint256","name":"desiredLiquidity","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"address payable","name":"WETH","type":"address"}],"name":"migrateLiquidityPool","outputs":[{"internalType":"address","name":"liquidityProviderToken","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","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":[],"name":"removeDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"removeLiquidityExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"reverseLiquidityPoolAssets","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":"contract ICustodian","name":"newCustodian","type":"address"}],"name":"setCustodian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setDepositIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDispatcherWallet","type":"address"}],"name":"setDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeWallet","type":"address"}],"name":"setFeeWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newMigrator","type":"address"}],"name":"setMigrator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"}],"name":"upgradeLiquidityPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum 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":"grossQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"gasFeeInPips","type":"uint64"},{"internalType":"bool","name":"autoDispatchEnabled","type":"bool"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Withdrawal","name":"withdrawal","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"assetAddress","type":"address"}],"name":"withdrawExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60a06040523480156200001157600080fd5b50604051620063eb380380620063eb83398101604081905262000034916200033d565b336080819052600080546001600160a01b03191690911790556001600160a01b038316158062000075575062000075836200012c60201b620008f51760201c565b620000c75760405162461bcd60e51b815260206004820152601860248201527f496e76616c6964206d6967726174696f6e20736f75726365000000000000000060448201526064015b60405180910390fd5b600580546001600160a01b0319166001600160a01b038516179055620000ed8262000132565b80516200010290600390602084019062000268565b505060098054600160a01b600160e01b031916600160a01b600160e01b0317905550620004849050565b3b151590565b6000546001600160a01b031633146200018e5760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206d7573742062652061646d696e0000000000000000000000006044820152606401620000be565b6001600160a01b038116620001e65760405162461bcd60e51b815260206004820152601660248201527f496e76616c69642077616c6c65742061646472657373000000000000000000006044820152606401620000be565b6012546001600160a01b0382811691161415620002465760405162461bcd60e51b815260206004820152601e60248201527f4d75737420626520646966666572656e742066726f6d2063757272656e7400006044820152606401620000be565b601280546001600160a01b0319166001600160a01b0392909216919091179055565b828054620002769062000447565b90600052602060002090601f0160209004810192826200029a5760008555620002e5565b82601f10620002b557805160ff1916838001178555620002e5565b82800160010185558215620002e5579182015b82811115620002e5578251825591602001919060010190620002c8565b50620002f3929150620002f7565b5090565b5b80821115620002f35760008155600101620002f8565b6001600160a01b03811681146200032457600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b6000806000606084860312156200035357600080fd5b835162000360816200030e565b8093505060208085015162000375816200030e565b60408601519093506001600160401b03808211156200039357600080fd5b818701915087601f830112620003a857600080fd5b815181811115620003bd57620003bd62000327565b604051601f8201601f19908116603f01168101908382118183101715620003e857620003e862000327565b816040528281528a868487010111156200040157600080fd5b600093505b8284101562000425578484018601518185018701529285019262000406565b82841115620004375760008684830101525b8096505050505050509250925092565b600181811c908216806200045c57607f821691505b602082108114156200047e57634e487b7160e01b600052602260045260246000fd5b50919050565b608051615f44620004a7600039600081816115d80152611b880152615f446000f3fe6080604052600436106102275760003560e01c80630226b70e1461025a578063026fc96e1461027a57806302751cec1461029a57806302ca6002146102ba57806309b388f1146102ea5780630c187a721461030a57806313cfda2c1461031f5780631abb58321461034d57806320e6a8e31461036d5780632384d3d01461038d57806323cf3118146103ad578063403f3731146103cd57806341968182146103ed578063457aa3c61461040d5780634a284ef91461042d578063503b37dc146104425780636bb509f214610462578063704b6c021461048257806372e8f08d146104a25780637cf0c1f4146104c2578063869af212146104e05780638d1d707d1461050d57806390d49b9d1461052d57806398166c0d1461054d578063985c4af51461059e57806398ea5fca146105be5780639a202d47146105c65780639fd96059146105db578063ae0e969e146105f0578063b39f07301461063e578063ba22bd7614610665578063baa2abde14610685578063bc25cf77146106a5578063c1b16c20146106c5578063c3e5af73146106e5578063c638199914610705578063ca2a245e14610725578063d3b2596d14610745578063d7677fbe14610765578063d7a6aec7146107ef578063dbb365351461080f578063dcc634901461082f578063e8e337001461084f578063eb5068f21461086f578063ed04a70714610884578063ef3b9d4a146108a2578063f305d719146108c2578063f91b6e68146108d557600080fd5b3661025557333b6102535760405162461bcd60e51b815260040161024a906138de565b60405180910390fd5b005b600080fd5b34801561026657600080fd5b5061025361027536600461397e565b6108fb565b34801561028657600080fd5b506102536102953660046139fc565b61099b565b3480156102a657600080fd5b506102536102b5366004613a5c565b610b55565b3480156102c657600080fd5b506012546001600160a01b03165b6040516102e19190613ac7565b60405180910390f35b3480156102f657600080fd5b506102d4610305366004613aeb565b610d4d565b34801561031657600080fd5b50610253610eac565b34801561032b57600080fd5b5061033f61033a366004613b62565b610ee8565b6040519081526020016102e1565b34801561035957600080fd5b5061033f610368366004613b9b565b610f73565b34801561037957600080fd5b50610253610388366004613bef565b611001565b34801561039957600080fd5b506102536103a8366004613b62565b6110b9565b3480156103b957600080fd5b506102536103c8366004613c08565b611156565b3480156103d957600080fd5b506102536103e8366004613c08565b6111ee565b3480156103f957600080fd5b50610253610408366004613dbe565b6112b1565b34801561041957600080fd5b5061025361042836600461397e565b611305565b34801561043957600080fd5b5061025361136f565b34801561044e57600080fd5b5061025361045d366004613b62565b6113e9565b34801561046e57600080fd5b5061025361047d366004613e48565b61147f565b34801561048e57600080fd5b5061025361049d366004613c08565b6115cd565b3480156104ae57600080fd5b506102536104bd366004613b9b565b6116c7565b3480156104ce57600080fd5b50600b546001600160a01b03166102d4565b3480156104ec57600080fd5b506105006104fb366004613f54565b61179f565b6040516102e19190614013565b34801561051957600080fd5b50610253610528366004614440565b6117eb565b34801561053957600080fd5b50610253610548366004613c08565b6119af565b34801561055957600080fd5b50610587610568366004613c08565b600a602052600090815260409020805460019091015460ff9091169082565b6040805192151583526020830191909152016102e1565b3480156105aa57600080fd5b506102536105b9366004613c08565b611a4f565b610253611b65565b3480156105d257600080fd5b50610253611b7d565b3480156105e757600080fd5b50610253611bd7565b3480156105fc57600080fd5b5061062661060b366004613bef565b6000908152600860205260409020546001600160401b031690565b6040516001600160401b0390911681526020016102e1565b34801561064a57600080fd5b5060095461062690600160a01b90046001600160401b031681565b34801561067157600080fd5b50610253610680366004613c08565b611d09565b34801561069157600080fd5b506102536106a0366004614531565b611da9565b3480156106b157600080fd5b506102536106c0366004613c08565b611fa2565b3480156106d157600080fd5b506102536106e03660046145a3565b612045565b3480156106f157600080fd5b50610253610700366004613b62565b6121c3565b34801561071157600080fd5b5061025361072036600461462a565b612298565b34801561073157600080fd5b50610253610740366004613b62565b6123d6565b34801561075157600080fd5b50610253610760366004613b62565b6124cf565b34801561077157600080fd5b50610785610780366004613b62565b612608565b6040516102e19190600060c082019050825115158252602083015160018060401b03808216602085015260ff6040860151166040850152806060860151166060850152505060ff608084015116608083015260018060a01b0360a08401511660a083015292915050565b3480156107fb57600080fd5b5061025361080a366004614667565b6126b9565b34801561081b57600080fd5b5061062661082a366004613b62565b6127a8565b34801561083b57600080fd5b5061025361084a366004614682565b612839565b34801561085b57600080fd5b5061025361086a3660046146ae565b61286e565b34801561087b57600080fd5b50610253612af2565b34801561089057600080fd5b506009546001600160a01b03166102d4565b3480156108ae57600080fd5b506106266108bd366004613b9b565b612be9565b6102536108d0366004613a5c565b612c6d565b3480156108e157600080fd5b506102536108f036600461472a565b612ede565b3b151590565b6000546001600160a01b031633146109255760405162461bcd60e51b815260040161024a9061478d565b60405163a7c5af2160e01b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063a7c5af21906109659060019088908890889088906004016147e4565b60006040518083038186803b15801561097d57600080fd5b505af4158015610991573d6000803e3d6000fd5b5050505050505050565b6011546001600160a01b031633146109c55760405162461bcd60e51b815260040161024a90614824565b73fc881c4b2e5044a5b648f89e5fb0c9579f4a507563089f7adc600c8484600a60006109f76080850160608601613c08565b6001600160a01b039081168252602082019290925260409081016000205460095460125492516001600160e01b031960e08a901b168152610a519796959460ff909316939183169291909116906001906004908101614982565b60006040518083038186803b158015610a6957600080fd5b505af4158015610a7d573d6000803e3d6000fd5b507f616311a023377d4cd48ac9f682cab7aa6e9876dd3a5f39d6ebd5cc14a6452c509250610ab49150506080840160608501613c08565b610ac16020840184613c08565b610ad16040850160208601613c08565b610ae16080860160608701614aef565b610af160a0870160808801614aef565b610b016060880160408901614aef565b604080516001600160a01b039788168152958716602087015293909516928401929092526001600160401b039081166060840152908116608083015290911660a082015260c0015b60405180910390a15050565b336000908152600a602052604090205460ff1615610b855760405162461bcd60e51b815260040161024a90614b0c565b60408051610180810182526004808252600060208084018290528385018290523360608501526001600160a01b038b8116608086015260a0850183905260c085018b905260e085018a905261010085018990528781166101208601526101408501879052855191820186528282526101608501919091526009549451639cffaf0f60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a507594639cffaf0f94610c4194600c94929390921691600191908101614b33565b600060405180830381865af4158015610c5e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c869190810190614c95565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316610cb890614d63565b91906101000a8154816001600160401b0302191690836001600160401b0316021790553383600001518460200151856040015186606001518760800151604051610d089796959493929190614d8a565b60405180910390a1600080516020615eaf833981519152338860008989898989604051610d3c989796959493929190614de5565b60405180910390a150505050505050565b600b546000906001600160a01b03163314610da35760405162461bcd60e51b815260206004820152601660248201527521b0b63632b91034b9903737ba1026b4b3b930ba37b960511b604482015260640161024a565b6040805160c0810182526001600160a01b03898116825288811660208301908152881515838501908152606084018981528884166080860190815288851660a087019081526009549751636c91badd60e01b8152600c600482015296518616602488015293518516604487015291511515606486015251608485015251821660a484015251811660c48301529190911660e482015260016101048201527364b882ab50c2f94cefeab1568b2161b2c83080ce90636c91badd9061012401602060405180830381865af4158015610e7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ea19190614e2e565b979650505050505050565b6000546001600160a01b03163314610ed65760405162461bcd60e51b815260040161024a9061478d565b601180546001600160a01b0319169055565b6040516347ac1b4560e01b815260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a906347ac1b4590610f2990600190879087906004908101614e4b565b602060405180830381865af4158015610f46573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f6a9190614e70565b90505b92915050565b60405163a2cfc7a960e01b815260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063a2cfc7a990610fb6906001908890889088906004908101614e89565b602060405180830381865af4158015610fd3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ff79190614e70565b90505b9392505050565b6000546001600160a01b0316331461102b5760405162461bcd60e51b815260040161024a9061478d565b62031380811061107b5760405162461bcd60e51b815260206004820152601b60248201527a09ccaee40e0cae4d2dec840cee4cac2e8cae440e8d0c2dc40dac2f602b1b604482015260640161024a565b601080549082905560408051828152602081018490527f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f9101610b49565b6000546001600160a01b031633146110e35760405162461bcd60e51b815260040161024a9061478d565b60405163e7bcf67360e01b81527364b882ab50c2f94cefeab1568b2161b2c83080ce9063e7bcf6739061112290600c9086908690600190600401614e4b565b60006040518083038186803b15801561113a57600080fd5b505af415801561114e573d6000803e3d6000fd5b505050505050565b6000546001600160a01b031633146111805760405162461bcd60e51b815260040161024a9061478d565b803b61119e5760405162461bcd60e51b815260040161024a90614ec6565b600b546001600160a01b03828116911614156111cc5760405162461bcd60e51b815260040161024a90614eef565b600b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146112185760405162461bcd60e51b815260040161024a9061478d565b6009546001600160a01b0316156112715760405162461bcd60e51b815260206004820152601e60248201527f437573746f6469616e2063616e206f6e6c7920626520736574206f6e63650000604482015260640161024a565b803b61128f5760405162461bcd60e51b815260040161024a90614ec6565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b60006112c8836112bf613034565b60019190613049565b60208101519091506001600160a01b03166112f55760405162461bcd60e51b815260040161024a906138de565b611300338284613408565b505050565b6000546001600160a01b0316331461132f5760405162461bcd60e51b815260040161024a9061478d565b604051635e55073b60e01b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a90635e55073b906109659060019088908890889088906004016147e4565b611378336135ce565b6113945760405162461bcd60e51b815260040161024a90614f26565b336000818152600a6020526040808220805460ff1916815560010191909155517fb771d4b2a83beca38f442c8903629e0e8ab1a07cf76e94eb2977153167e20936916113df91613ac7565b60405180910390a1565b6000546001600160a01b031633146114135760405162461bcd60e51b815260040161024a9061478d565b600554600954604051632076351960e01b8152600c60048201526001600160a01b0380861660248301528085166044830152600160648301529283166084820152911660a48201527364b882ab50c2f94cefeab1568b2161b2c83080ce9063207635199060c401611122565b6011546001600160a01b031633146114a95760405162461bcd60e51b815260040161024a90614824565b6114b681604001516135ce565b156114d35760405162461bcd60e51b815260040161024a90614b0c565b600954601254604051635879725360e11b815260009283928392839273d253f0f299db07f6661188466cde4ae407fe7c089263b0f2e4a692611530928a926001600160a01b0391821692911690600190600490600f908201614f59565b600060405180830381865af415801561154d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526115759190810190615050565b93509350935093507f6960a1f64ecf9da0d1d1bcbfa3dd27f8c1c60de69b13faa28127dafa36c111e4856040015183838860a0015188886040516115be969594939291906150bd565b60405180910390a15050505050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146116155760405162461bcd60e51b815260040161024a9061510e565b6001600160a01b03811661163b5760405162461bcd60e51b815260040161024a9061513c565b6000546001600160a01b03828116911614156116a55760405162461bcd60e51b8152602060048201526024808201527f4d75737420626520646966666572656e742066726f6d2063757272656e7420616044820152633236b4b760e11b606482015260840161024a565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146116f15760405162461bcd60e51b815260040161024a9061478d565b604051635ef40c1160e11b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063bde818229061172f9060019087908790879060040161516c565b60006040518083038186803b15801561174757600080fd5b505af415801561175b573d6000803e3d6000fd5b505050507f7f63a63f4a2ea318da0b031794d0b5a3183a1b2de54e053ceb17cabdba031735838383604051611792939291906151a1565b60405180910390a1505050565b6117a76138a8565b610ff784848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600193925086915050613049565b6011546001600160a01b031633146118155760405162461bcd60e51b815260040161024a90614824565b61182283604001516135ce565b1561183f5760405162461bcd60e51b815260040161024a906151cf565b61184c82604001516135ce565b156118695760405162461bcd60e51b815260040161024a90615202565b60125460405163279565bb60e11b815273572eb8eaa977e34ad34ba5729cc4e099d823da3d91634f2acb76916118c4918791879187916001600160a01b0390911690600190600c906004906006906007906008908401615592565b60006040518083038186803b1580156118dc57600080fd5b505af41580156118f0573d6000803e3d6000fd5b50505050604083810151908301518251805160208083015160808085015160a095860151938901519182015191909501517f0c2788d93efb3bdadabeb2b84b5900db0d210b5875d7e88a913b6a807a1f12cb97969592939190611953828561564d565b8a6020015160a001518b6000015160a0015161196f919061564d565b60008c516101a00151600181111561198957611989614856565b14611995576000611998565b60015b6040516117929b9a99989796959493929190615678565b6000546001600160a01b031633146119d95760405162461bcd60e51b815260040161024a9061478d565b6001600160a01b0381166119ff5760405162461bcd60e51b815260040161024a9061513c565b6012546001600160a01b0382811691161415611a2d5760405162461bcd60e51b815260040161024a90614eef565b601280546001600160a01b0319166001600160a01b0392909216919091179055565b611a58336135ce565b611a745760405162461bcd60e51b815260040161024a90614f26565b60095460405163c42cc91f60e01b81526001600160a01b038084166004808401919091529216602482015260016044820152606481019190915260009073d253f0f299db07f6661188466cde4ae407fe7c089063c42cc91f90608401602060405180830381865af4158015611aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b119190615714565b604080513381526001600160a01b03851660208201526001600160401b038316918101919091529091507f8a8c919824df7fc8e14ab0532b92f8305abaa1947ec4ecdeb744c4281aecd16690606001610b49565b611b7b33611b75600160006135ff565b34613408565b565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614611bc55760405162461bcd60e51b815260040161024a9061510e565b600080546001600160a01b0319169055565b6000546001600160a01b03163314611c015760405162461bcd60e51b815260040161024a9061478d565b600954600160a01b90046001600160401b0390811614611c5a5760405162461bcd60e51b815260206004820152601460248201527343616e206f6e6c7920626520736574206f6e636560601b604482015260640161024a565b6005546001600160a01b031615611cde5760055460408051630b39f07360e41b815290516001600160a01b039092169163b39f0730916004818101926020929091908290030181865afa158015611cb5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cd99190615714565b611ce1565b60005b600960146101000a8154816001600160401b0302191690836001600160401b03160217905550565b6000546001600160a01b03163314611d335760405162461bcd60e51b815260040161024a9061478d565b6001600160a01b038116611d595760405162461bcd60e51b815260040161024a9061513c565b6011546001600160a01b0382811691161415611d875760405162461bcd60e51b815260040161024a90614eef565b601180546001600160a01b0319166001600160a01b0392909216919091179055565b336000908152600a602052604090205460ff1615611dd95760405162461bcd60e51b815260040161024a90614b0c565b60408051610180810182526004808252600060208084018290528385018290523360608501526001600160a01b038c811660808601528b811660a086015260c085018b905260e085018a905261010085018990528781166101208601526101408501879052855191820186528282526101608501919091526009549451639cffaf0f60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a507594639cffaf0f94611e9694600c94929390921691600191908101614b33565b600060405180830381865af4158015611eb3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611edb9190810190614c95565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316611f0d90614d63565b91906101000a8154816001600160401b0302191690836001600160401b0316021790553383600001518460200151856040015186606001518760800151604051611f5d9796959493929190614d8a565b60405180910390a1600080516020615eaf8339815191523389898989898989604051611f90989796959493929190614de5565b60405180910390a15050505050505050565b6000546001600160a01b03163314611fcc5760405162461bcd60e51b815260040161024a9061478d565b60125460405163712b772f60e01b815273bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9163712b772f916120129185916001600160a01b0390911690600401615731565b60006040518083038186803b15801561202a57600080fd5b505af415801561203e573d6000803e3d6000fd5b5050505050565b6011546001600160a01b0316331461206f5760405162461bcd60e51b815260040161024a90614824565b61207c83604001516135ce565b156120995760405162461bcd60e51b815260040161024a906151cf565b6120a682604001516135ce565b156120c35760405162461bcd60e51b815260040161024a90615202565b6012546040516358a9b1ad60e11b815273572eb8eaa977e34ad34ba5729cc4e099d823da3d9163b153635a9161211b918791879187916001600160a01b0390911690600190600490600690600790600890840161574b565b60006040518083038186803b15801561213357600080fd5b505af4158015612147573d6000803e3d6000fd5b5050506040808501519084015183516020850151608086015160a08701517fe836416b090e27ecc87dd0bf23c36034cb0b2e2960c2f271f4a7b96fea65694296506000886101a0015160018111156121a1576121a1614856565b146121ad5760006121b0565b60015b60405161179297969594939291906157c5565b6000546001600160a01b031633146121ed5760405162461bcd60e51b815260040161024a9061478d565b60405163f065a68760e01b8152600c60048201526001600160a01b038084166024830152821660448201527364b882ab50c2f94cefeab1568b2161b2c83080ce9063f065a6879060640160006040518083038186803b15801561224f57600080fd5b505af4158015612263573d6000803e3d6000fd5b505050507f81f30d401791fa502c3a1610e6e194416482fcd7bbbfdba54bded335ff9edb598282604051610b49929190615731565b6011546001600160a01b031633146122c25760405162461bcd60e51b815260040161024a90614824565b6122da6122d56080840160608501613c08565b6135ce565b1561231f5760405162461bcd60e51b815260206004820152601560248201527415d85b1b195d08195e1a5d08199a5b985b1a5e9959605a1b604482015260640161024a565b601254600954604051637904862960e11b815273fc881c4b2e5044a5b648f89e5fb0c9579f4a50759263f2090c529261237392600c92889288926001600160a01b0392831692909116906004908101615839565b60006040518083038186803b15801561238b57600080fd5b505af415801561239f573d6000803e3d6000fd5b507f6e2fd2e6935d9da786db2536ac4061dca5dad5bacaac5b8b169d657c3e3475109250610ab49150506080840160608501613c08565b60095460408051633bffd49d60e01b815290516000926001600160a01b031691633bffd49d9160048083019260209291908290030181865afa158015612420573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124449190614e2e565b9050336001600160a01b038216146124975760405162461bcd60e51b815260206004820152601660248201527543616c6c6572206973206e6f742045786368616e676560501b604482015260640161024a565b506001600160a01b039182166000908152600460209081526040808320939094168252919091522080546001600160481b0319169055565b6124d8336135ce565b6124f45760405162461bcd60e51b815260040161024a90614f26565b6009546040516311a73c7560e31b8152600c6004808301919091526001600160a01b038086166024840152808516604484015290921660648201526084810191909152600090819073fc881c4b2e5044a5b648f89e5fb0c9579f4a507590638d39e3a89060a4016040805180830381865af4158015612577573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061259b919061599d565b604080513381526001600160a01b0388811660208301528716818301526001600160401b0384811660608301528316608082015290519294509092507f8102055af21849e835ad18fcbd334e529a8d18decf5cefc69e7120508e1aeaa4919081900360a00190a150505050565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a0810191909152612646600c848461379d565b6040805160c081018252825460ff808216151583526001600160401b03610100830481166020850152600160481b8304821694840194909452600160501b82049093166060830152600160901b900490911660808201526001909101546001600160a01b031660a0820152905092915050565b6010546040516303710ea360e61b8152600760048201526001600160801b03831660248201526044810191909152600090819073c956d06ee0ee5f7af081d609a409425c250ea95d9063dc43a8c0906064016040805180830381865af4158015612727573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061274b91906159cc565b604080513381526001600160801b03871660208201526001600160401b038416918101919091526060810182905291935091507f10cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809b90608001611792565b604051634916cb7160e11b81526001600160a01b038084166004808401919091529083166024830152604482015260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063922d96e290606401602060405180830381865af4158015612815573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f6a9190615714565b60006128466001846135ff565b90506001600160a01b0383166112f55760405162461bcd60e51b815260040161024a906138de565b336000908152600a602052604090205460ff161561289e5760405162461bcd60e51b815260040161024a90614b0c565b604080516101a0810182526004808252600060208084018290528385018290523360608501526001600160a01b038d811660808601528c811660a086015260c085018c905260e085018b905261010085018a90526101208501899052878116610140860152610160850187905285519182018652828252610180850191909152600954945163467b41fb60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a50759463467b41fb9461296394600c949293909216916001919081016159fa565b600060405180830381865af4158015612980573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526129a89190810190615af1565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b03166129da90614d63565b91906101000a8154816001600160401b0302191690836001600160401b031602179055338b8460000151856020015186604001518760600151604051612a269796959493929190614d8a565b60405180910390a1600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316612a5e90614d63565b91906101000a8154816001600160401b0302191690836001600160401b031602179055338a84608001518560a001518660c001518760e00151604051612aaa9796959493929190614d8a565b60405180910390a1600080516020615eef833981519152338a8a8a8a8a8a8a8a604051612adf99989796959493929190615bdd565b60405180910390a1505050505050505050565b336000908152600a602052604090205460ff1615612b4a5760405162461bcd60e51b815260206004820152601560248201527415d85b1b195d08185b1c9958591e48195e1a5d1959605a1b604482015260640161024a565b604051806040016040528060011515815260200160105443612b6c9190615c2c565b9052336000818152600a602090815260409091208351815460ff19169015151781559201516001909201919091556010547fd60f9f7b2f1a208268475a927bd727c4e198fc8b40aab3004ebcc2bc78ca84809190612bca9043615c2c565b604080516001600160a01b0390931683526020830191909152016113df565b60405163031b07f360e61b815260009073bbdba7cc65545fc38bd6b10bf61ede08cd47bd5a9063c6c1fcc090612c2c906001908890889088906004908101614e89565b602060405180830381865af4158015612c49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ff79190615714565b336000908152600a602052604090205460ff1615612c9d5760405162461bcd60e51b815260040161024a90614b0c565b604080516101a0810182526004808252600060208084018290528385018290523360608501526001600160a01b038b8116608086015260a0850183905260c085018b90523460e086015261010085018a90526101208501899052878116610140860152610160850187905285519182018652828252610180850191909152600954945163467b41fb60e01b8152919473fc881c4b2e5044a5b648f89e5fb0c9579f4a50759463467b41fb94612d6094600c949293909216916001919081016159fa565b600060405180830381865af4158015612d7d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612da59190810190615af1565b9050600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316612dd790614d63565b91906101000a8154816001600160401b0302191690836001600160401b03160217905533898460000151856020015186604001518760600151604051612e239796959493929190614d8a565b60405180910390a1600080516020615ecf8339815191526009601481819054906101000a90046001600160401b0316612e5b90614d63565b91906101000a8154816001600160401b0302191690836001600160401b03160217905533600084608001518560a001518660c001518760e00151604051612ea89796959493929190614d8a565b60405180910390a1600080516020615eef8339815191523388600089348a8a8a8a604051610d3c99989796959493929190615bdd565b6011546001600160a01b03163314612f085760405162461bcd60e51b815260040161024a90614824565b612f1582604001516135ce565b15612f605760405162461bcd60e51b815260206004820152601b60248201527a13dc99195c881dd85b1b195d08195e1a5d08199a5b985b1a5e9959602a1b604482015260640161024a565b601254604051636452281b60e11b815273572eb8eaa977e34ad34ba5729cc4e099d823da3d9163c8a4503691612fb791869186916001600160a01b031690600190600c906004906006906007906008908401615c44565b60006040518083038186803b158015612fcf57600080fd5b505af4158015612fe3573d6000803e3d6000fd5b5050506040808401518351602085015160808087015160a08801519189015195517fe33b9d086983d8450b17504f6bd50986f52a0f8c7aebad750c10a2cf7c7f7b289750610b499691929190615cae565b60006103e86130438142615d19565b91505090565b6130516138a8565b6130e784600201805461306390615d48565b80601f016020809104026020016040519081016040528092919081815260200182805461308f90615d48565b80156130dc5780601f106130b1576101008083540402835291602001916130dc565b820191906000526020600020905b8154815290600101906020018083116130bf57829003601f168201915b505050505084613810565b15613188576131818460020180546130fe90615d48565b80601f016020809104026020016040519081016040528092919081815260200182805461312a90615d48565b80156131775780601f1061314c57610100808354040283529160200191613177565b820191906000526020600020905b81548152906001019060200180831161315a57829003601f168201915b5050505050613869565b9050610ffa565b6131906138a8565b600085600101856040516131a49190615d7d565b9081526040519081900360200190205411156133a15760005b85600101856040516131cf9190615d7d565b9081526040519081900360200190205460ff8216101561339f57836001600160401b031686600101866040516132059190615d7d565b90815260200160405180910390208260ff168154811061322757613227615d99565b60009182526020909120600390910201600201546201000090046001600160401b03161161338d5785600101856040516132619190615d7d565b90815260200160405180910390208160ff168154811061328357613283615d99565b60009182526020918290206040805160c0810182526003909302909101805460ff8116151584526001600160a01b036101009091041693830193909352600183018054929392918401916132d690615d48565b80601f016020809104026020016040519081016040528092919081815260200182805461330290615d48565b801561334f5780601f106133245761010080835404028352916020019161334f565b820191906000526020600020905b81548152906001019060200180831161333257829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015291505b8061339781615daf565b9150506131bd565b505b805180156133b0575080608001515b610ff75760405162461bcd60e51b815260206004820152602360248201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f722073796d604482015262189bdb60ea1b606482015260840161024a565b600954600160a01b90046001600160401b03908116141561345f5760405162461bcd60e51b815260206004820152601160248201527011195c1bdcda5d1cc8191a5cd8589b1959607a1b604482015260640161024a565b6001600160a01b0383166000908152600a602052604090205460ff16156134985760405162461bcd60e51b815260040161024a90614b0c565b60095460405163a16a597960e01b81526000918291829173edb960d352a6a52709c1dea56659d81755e04c419163a16a5979916134e9918a918a918a916001600160a01b0316906004908101615dcf565b606060405180830381865af4158015613506573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061352a9190615e6b565b600980549396509194509250600160a01b9091046001600160401b031690601461355383614d63565b91906101000a8154816001600160401b0302191690836001600160401b0316021790555050600080516020615ecf833981519152600960149054906101000a90046001600160401b031687876020015188604001518787876040516135be9796959493929190614d8a565b60405180910390a1505050505050565b6001600160a01b0381166000908152600a60205260408120805460ff168015610ffa57506001015443101592915050565b6136076138a8565b6001600160a01b03821661362e576136278360020180546130fe90615d48565b9050610f6d565b6001600160a01b03808316600090815260208581526040808320815160c081018352815460ff8116151582526101009004909516928501929092526001820180549394939184019161367f90615d48565b80601f01602080910402602001604051908101604052809291908181526020018280546136ab90615d48565b80156136f85780601f106136cd576101008083540402835291602001916136f8565b820191906000526020600020905b8154815290600101906020018083116136db57829003601f168201915b50505091835250506002919091015460ff8082166020840152610100820416151560408301526201000090046001600160401b031660609091015280519091508015613745575080608001515b610f6a5760405162461bcd60e51b8152602060048201526024808201527f4e6f20636f6e6669726d656420617373657420666f756e6420666f72206164646044820152637265737360e01b606482015260840161024a565b6001600160a01b0380831660009081526001850160209081526040808320938516835292905220805460ff16610ffa5760405162461bcd60e51b81526020600482015260186024820152772737903837b7b6103337b91030b2323932b9b9903830b4b960411b604482015260640161024a565b6000816040516020016138239190615d7d565b604051602081830303815290604052805190602001208360405160200161384a9190615d7d565b6040516020818303038152906040528051906020012014905092915050565b6138716138a8565b506040805160c08101825260018082526000602083018190529282019390935260126060820152608081019290925260a082015290565b6040805160c08101825260008082526020820181905260609282018390529181018290526080810182905260a081019190915290565b60208082526010908201526f2ab9b2903232b837b9b4ba22ba3432b960811b604082015260600190565b6001600160a01b038116811461391d57600080fd5b50565b60008083601f84011261393257600080fd5b5081356001600160401b0381111561394957600080fd5b60208301915083602082850101111561396157600080fd5b9250929050565b803560ff8116811461397957600080fd5b919050565b6000806000806060858703121561399457600080fd5b843561399f81613908565b935060208501356001600160401b038111156139ba57600080fd5b6139c687828801613920565b90945092506139d9905060408601613968565b905092959194509250565b600060e082840312156139f657600080fd5b50919050565b6000806101008385031215613a1057600080fd5b82356001600160401b03811115613a2657600080fd5b83016101808186031215613a3957600080fd5b9150613a4884602085016139e4565b90509250929050565b803561397981613908565b60008060008060008060c08789031215613a7557600080fd5b8635613a8081613908565b95506020870135945060408701359350606087013592506080870135613aa581613908565b8092505060a087013590509295509295509295565b6001600160a01b03169052565b6001600160a01b0391909116815260200190565b8035801515811461397957600080fd5b60008060008060008060c08789031215613b0457600080fd5b8635613b0f81613908565b95506020870135613b1f81613908565b9450613b2d60408801613adb565b9350606087013592506080870135613b4481613908565b915060a0870135613b5481613908565b809150509295509295509295565b60008060408385031215613b7557600080fd5b8235613b8081613908565b91506020830135613b9081613908565b809150509250929050565b600080600060408486031215613bb057600080fd5b8335613bbb81613908565b925060208401356001600160401b03811115613bd657600080fd5b613be286828701613920565b9497909650939450505050565b600060208284031215613c0157600080fd5b5035919050565b600060208284031215613c1a57600080fd5b8135610f6a81613908565b634e487b7160e01b600052604160045260246000fd5b60405161012081016001600160401b0381118282101715613c5e57613c5e613c25565b60405290565b6040516101c081016001600160401b0381118282101715613c5e57613c5e613c25565b60405161018081016001600160401b0381118282101715613c5e57613c5e613c25565b604051606081016001600160401b0381118282101715613c5e57613c5e613c25565b60405160a081016001600160401b0381118282101715613c5e57613c5e613c25565b60405161010081016001600160401b0381118282101715613c5e57613c5e613c25565b604051601f8201601f191681016001600160401b0381118282101715613d3957613d39613c25565b604052919050565b60006001600160401b03821115613d5a57613d5a613c25565b50601f01601f191660200190565b600082601f830112613d7957600080fd5b8135613d8c613d8782613d41565b613d11565b818152846020838601011115613da157600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215613dd157600080fd5b82356001600160401b03811115613de757600080fd5b613df385828601613d68565b95602094909401359450505050565b80356002811061397957600080fd5b80356001600160801b038116811461397957600080fd5b6001600160401b038116811461391d57600080fd5b803561397981613e28565b600060208284031215613e5a57600080fd5b81356001600160401b0380821115613e7157600080fd5b908301906101208286031215613e8657600080fd5b613e8e613c3b565b613e9783613e02565b8152613ea560208401613e11565b6020820152613eb660408401613a51565b6040820152606083013582811115613ecd57600080fd5b613ed987828601613d68565b606083015250613eeb60808401613a51565b6080820152613efc60a08401613e3d565b60a0820152613f0d60c08401613e3d565b60c0820152613f1e60e08401613adb565b60e08201526101008084013583811115613f3757600080fd5b613f4388828701613d68565b918301919091525095945050505050565b600080600060408486031215613f6957600080fd5b83356001600160401b03811115613f7f57600080fd5b613f8b86828701613920565b9094509250506020840135613f9f81613e28565b809150509250925092565b60005b83811015613fc5578181015183820152602001613fad565b83811115613fd4576000848401525b50505050565b60008151808452613ff2816020860160208601613faa565b601f01601f19169290920160200192915050565b6001600160401b03169052565b6020815281511515602082015260018060a01b0360208301511660408201526000604083015160c0606084015261404d60e0840182613fda565b905060ff60608501511660808401526080840151151560a084015260018060401b0360a08501511660c08401528091505092915050565b80356007811061397957600080fd5b80356004811061397957600080fd5b60006101c082840312156140b557600080fd5b6140bd613c64565b90506140c882613968565b81526140d660208301613e11565b60208201526140e760408301613a51565b60408201526140f860608301614084565b606082015261410960808301613e02565b608082015261411a60a08301613e3d565b60a082015261412b60c08301613adb565b60c082015261413c60e08301613e3d565b60e082015261010061414f818401613e3d565b90820152610120828101356001600160401b038082111561416f57600080fd5b61417b86838701613d68565b83850152610140925061418f838601614093565b8385015261016092506141a3838601614093565b8385015261018092506141b7838601613e3d565b838501526101a09250828501359150808211156141d357600080fd5b506141e085828601613d68565b82840152505092915050565b60006101c082840312156141ff57600080fd5b614207613c64565b905081356001600160401b038082111561422057600080fd5b61422c85838601613d68565b8352602084013591508082111561424257600080fd5b5061424f84828501613d68565b60208301525061426160408301613a51565b604082015261427260608301613a51565b606082015261428360808301613e3d565b608082015261429460a08301613e3d565b60a08201526142a560c08301613e3d565b60c08201526142b660e08301613e3d565b60e08201526101006142c9818401613a51565b908201526101206142db838201613a51565b908201526101406142ed838201613e3d565b908201526101606142ff838201613e3d565b90820152610180614311838201613e3d565b908201526101a0614323838201613e02565b9082015292915050565b6000610180828403121561434057600080fd5b614348613c87565b905081356001600160401b038082111561436157600080fd5b61436d85838601613d68565b8352602084013591508082111561438357600080fd5b5061439084828501613d68565b6020830152506143a260408301613a51565b60408201526143b360608301613a51565b60608201526143c460808301613e3d565b60808201526143d560a08301613e3d565b60a08201526143e660c08301613e3d565b60c08201526143f760e08301613e3d565b60e082015261010061440a818401613e3d565b9082015261012061441c838201613e3d565b9082015261014061442e838201613e3d565b90820152610160614323838201613e3d565b60008060006060848603121561445557600080fd5b83356001600160401b038082111561446c57600080fd5b614478878388016140a2565b9450602086013591508082111561448e57600080fd5b61449a878388016140a2565b935060408601359150808211156144b057600080fd5b90850190606082880312156144c457600080fd5b6144cc613caa565b8235828111156144db57600080fd5b6144e7898286016141ec565b8252506020830135828111156144fc57600080fd5b6145088982860161432d565b6020830152506040830135925061451e83613e28565b8260408201528093505050509250925092565b600080600080600080600060e0888a03121561454c57600080fd5b873561455781613908565b9650602088013561456781613908565b955060408801359450606088013593506080880135925060a088013561458c81613908565b8092505060c0880135905092959891949750929550565b6000806000606084860312156145b857600080fd5b83356001600160401b03808211156145cf57600080fd5b6145db878388016140a2565b945060208601359150808211156145f157600080fd5b6145fd878388016140a2565b9350604086013591508082111561461357600080fd5b50614620868287016141ec565b9150509250925092565b600080610100838503121561463e57600080fd5b82356001600160401b0381111561465457600080fd5b83016101a08186031215613a3957600080fd5b60006020828403121561467957600080fd5b610f6a82613e11565b6000806040838503121561469557600080fd5b82356146a081613908565b946020939093013593505050565b600080600080600080600080610100898b0312156146cb57600080fd5b88356146d681613908565b975060208901356146e681613908565b965060408901359550606089013594506080890135935060a0890135925060c089013561471281613908565b8092505060e089013590509295985092959890939650565b6000806040838503121561473d57600080fd5b82356001600160401b038082111561475457600080fd5b614760868387016140a2565b9350602085013591508082111561477657600080fd5b506147838582860161432d565b9150509250929050565b60208082526014908201527321b0b63632b91036bab9ba1031329030b236b4b760611b604082015260600190565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b8581526001600160a01b038516602082015260806040820181905260009061480f90830185876147bb565b905060ff831660608301529695505050505050565b60208082526018908201527721b0b63632b91034b9903737ba103234b9b830ba31b432b960411b604082015260600190565b634e487b7160e01b600052602160045260246000fd5b6002811061391d5761391d614856565b6148858161486c565b9052565b6001600160801b03169052565b6000808335601e198436030181126148ad57600080fd5b83016020810192503590506001600160401b038111156148cc57600080fd5b80360383131561396157600080fd5b80356148e681613908565b6001600160a01b03908116835260208201359061490282613908565b166020830152604081013561491681613e28565b6001600160401b03908116604084015260608201359061493582613e28565b908116606084015260808201359061494c82613e28565b908116608084015260a08201359061496382613e28565b1660a083015261497560c08201613e3d565b61130060c0840182614006565b60006101c08a83528060208401526149a681840161499f8c613968565b60ff169052565b506149b360208a01613e02565b6149c16101e084018261487c565b506149ce60408a01613e11565b6149dc610200840182614889565b506149e960608a01613a51565b6149f7610220840182613aba565b50614a0460808a01613a51565b614a12610240840182613aba565b50614a1f60a08a01613a51565b614a2d610260840182613aba565b5060c089013561028083015260e08901356102a08301526101008901356102c0830152610120614a5e818b01613a51565b614a6c6102e0850182613aba565b50610140808b0135610300850152610160614a89818d018d614896565b61018080610320890152614aa2610340890183856147bb565b9650614ab1604089018f6148db565b8c151588870152614ac48589018d613aba565b614ad08489018c613aba565b8981890152505050505050826101a08301529998505050505050505050565b600060208284031215614b0157600080fd5b8135610f6a81613e28565b6020808252600d908201526c15d85b1b195d08195e1a5d1959609a1b604082015260600190565b85815260a06020820152614b4d60a08201865160ff169052565b60006020860151614b6160c084018261487c565b506040860151614b7460e0840182614889565b506060860151610100614b8981850183613aba565b60808801519150610120614b9f81860184613aba565b60a08901519250610140614bb581870185613aba565b60c08a01519350610160848188015260e08b015194506101808581890152848c01516101a0890152838c01519550614bf16101c0890187613aba565b918b01516101e08801528a01516102008701919091529250614c1a915050610220840182613fda565b915050614c2a6040830186613aba565b6060820193909352608001529392505050565b600082601f830112614c4e57600080fd5b8151614c5c613d8782613d41565b818152846020838601011115614c7157600080fd5b614c82826020830160208701613faa565b949350505050565b805161397981613e28565b600060208284031215614ca757600080fd5b81516001600160401b0380821115614cbe57600080fd5b9083019060a08286031215614cd257600080fd5b614cda613ccc565b8251614ce581613908565b8152602083015182811115614cf957600080fd5b614d0587828601614c3d565b60208301525060408301519150614d1b82613e28565b81604082015260608301519150614d3182613e28565b8160608201526080830151608082015280935050505092915050565b634e487b7160e01b600052601160045260246000fd5b60006001600160401b0382811680821415614d8057614d80614d4d565b6001019392505050565b6001600160401b0388811682526001600160a01b0388811660208401528716604083015260e06060830181905260009190614dc790840188613fda565b95811660808401529390931660a082015260c0015250949350505050565b6001600160a01b039889168152968816602088015294871660408701526060860193909352608085019190915260a084015290921660c082015260e08101919091526101000190565b600060208284031215614e4057600080fd5b8151610f6a81613908565b9384526001600160a01b03928316602085015291166040830152606082015260800190565b600060208284031215614e8257600080fd5b5051919050565b8581526001600160a01b0385166020820152608060408201819052600090614eb490830185876147bb565b90508260608301529695505050505050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b6020808252601e908201527f4d75737420626520646966666572656e742066726f6d2063757272656e740000604082015260600190565b60208082526019908201527815d85b1b195d08195e1a5d081b9bdd08199a5b985b1a5e9959603a1b604082015260600190565b60c08152614f6b60c08201885161487c565b60006020880151614f7f60e0840182614889565b506040880151610100614f9481850183613aba565b60608a0151610120858101529150614fb06101e0850183613fda565b915060808a0151614fc5610140860182613aba565b5060a08a0151614fd9610160860182614006565b5060c08a0151614fed610180860182614006565b5060e08a015115156101a085015289015183820360bf19016101c08501526150158282613fda565b925050506150266020830188613aba565b6150336040830187613aba565b8460608301528360808301528260a0830152979650505050505050565b6000806000806080858703121561506657600080fd5b845161507181613e28565b60208601516040870151919550935061508981613908565b60608601519092506001600160401b038111156150a557600080fd5b6150b187828801614c3d565b91505092959194509250565b6001600160a01b0387811682528616602082015260c0604082018190526000906150e990830187613fda565b6001600160401b03958616606084015293909416608082015260a00152949350505050565b60208082526014908201527321b0b63632b91036bab9ba1031329037bbb732b960611b604082015260600190565b602080825260169082015275496e76616c69642077616c6c6574206164647265737360501b604082015260600190565b8481526001600160a01b038416602082015260606040820181905260009061519790830184866147bb565b9695505050505050565b6001600160a01b03841681526040602082018190526000906151c690830184866147bb565b95945050505050565b602080825260199082015278109d5e481dd85b1b195d08195e1a5d08199a5b985b1a5e9959603a1b604082015260600190565b6020808252601a908201527914d95b1b081dd85b1b195d08195e1a5d08199a5b985b1a5e995960321b604082015260600190565b6007811061488557614885614856565b6004811061488557614885614856565b805160ff16825260006101c060208301516152746020860182614889565b5060408301516152876040860182613aba565b50606083015161529a6060860182615236565b5060808301516152ad608086018261487c565b5060a08301516152c060a0860182614006565b5060c08301516152d460c086018215159052565b5060e08301516152e760e0860182614006565b50610100808401516152fb82870182614006565b505061012080840151828287015261531583870182613fda565b925050506101408084015161532c82870182615246565b50506101608084015161534182870182615246565b50506101808084015161535682870182614006565b50506101a080840151858303828701526151978382613fda565b60006101c0825181855261538682860182613fda565b915050602083015184820360208601526153a08282613fda565b91505060408301516153b56040860182613aba565b5060608301516153c86060860182613aba565b5060808301516153db6080860182614006565b5060a08301516153ee60a0860182614006565b5060c083015161540160c0860182614006565b5060e083015161541460e0860182614006565b506101008084015161542882870182613aba565b50506101208084015161543d82870182613aba565b50506101408084015161545282870182614006565b50506101608084015161546782870182614006565b50506101808084015161547c82870182614006565b50506101a0808401516154918287018261487c565b5090949350505050565b600061018082518185526154b182860182613fda565b915050602083015184820360208601526154cb8282613fda565b91505060408301516154e06040860182613aba565b5060608301516154f36060860182613aba565b5060808301516155066080860182614006565b5060a083015161551960a0860182614006565b5060c083015161552c60c0860182614006565b5060e083015161553f60e0860182614006565b506101008084015161555382870182614006565b50506101208084015161556882870182614006565b50506101408084015161557d82870182614006565b50506101608084015161549182870182614006565b60006101408083526155a68184018e615256565b905082810360208401526155ba818d615256565b905082810360408401528a51606082526155d76060830182615370565b905060208c015182820360208401526155f0828261549b565b60408e8101516001600160401b0316940193909352509091506156189050606083018a613aba565b8760808301528660a08301528560c08301528460e083015283610100830152826101208301529b9a5050505050505050505050565b60006001600160401b0382811684821680830382111561566f5761566f614d4d565b01949350505050565b6001600160a01b038c811682528b166020820152610160604082018190526000906156a58382018d613fda565b905082810360608401526156b9818c613fda565b6001600160401b038b811660808601528a811660a086015289811660c086015288811660e086015287811661010086015286166101208501529150615704905061014083018461487c565b9c9b505050505050505050505050565b60006020828403121561572657600080fd5b8151610f6a81613e28565b6001600160a01b0392831681529116602082015260400190565b600061012080835261575f8184018d615256565b90508281036020840152615773818c615256565b90508281036040840152615787818b615370565b6001600160a01b039990991660608401525050608081019590955260a085019390935260c084019190915260e0830152610100909101529392505050565b6001600160a01b0388811682528716602082015260e0604082018190526000906157f190830188613fda565b82810360608401526158038188613fda565b6001600160401b038781166080860152861660a0850152915061582790508361486c565b8260c083015298975050505050505050565b600061018088835280602084015261585681840161499f8a613968565b61586260208901613e02565b6101a06158718186018361487c565b61587d60408b01613e11565b915061588d6101c0860183614889565b61589960608b01613a51565b91506158a96101e0860183613aba565b6158b560808b01613a51565b91506158c5610200860183613aba565b6158d160a08b01613a51565b91506158e1610220860183613aba565b60c08a013561024086015260e08a01356102608601526101008a01356102808601526101209150818a01356102a0860152610140615920818c01613a51565b61592e6102c0880182613aba565b50610160808c01356102e0880152615948858d018d614896565b955083610300890152615960610320890187836147bb565b965050615970604088018c6148db565b61597c8488018b613aba565b6159888288018a613aba565b87818801525050505050979650505050505050565b600080604083850312156159b057600080fd5b82516159bb81613e28565b6020840151909250613b9081613e28565b600080604083850312156159df57600080fd5b82516159ea81613e28565b6020939093015192949293505050565b85815260a06020820152615a1460a08201865160ff169052565b60006020860151615a2860c084018261487c565b506040860151615a3b60e0840182614889565b506060860151610100615a5081850183613aba565b60808801519150610120615a6681860184613aba565b60a08901519250610140615a7c81870185613aba565b60c08a01516101608781019190915260e08b015161018080890191909152938b01516101a080890191909152838c01516101c0890152828c0151955093615ac76101e0890187613aba565b818c0151610200890152808c01519550505050508061022085015250614c1a610240840182613fda565b600060208284031215615b0357600080fd5b81516001600160401b0380821115615b1a57600080fd5b908301906101008286031215615b2f57600080fd5b615b37613cee565b825182811115615b4657600080fd5b615b5287828601614c3d565b825250615b6160208401614c8a565b6020820152615b7260408401614c8a565b604082015260608301516060820152608083015182811115615b9357600080fd5b615b9f87828601614c3d565b608083015250615bb160a08401614c8a565b60a0820152615bc260c08401614c8a565b60c082015260e083015160e082015280935050505092915050565b6001600160a01b03998a168152978916602089015295881660408801526060870194909452608086019290925260a085015260c084015290921660e08201526101008101919091526101200190565b60008219821115615c3f57615c3f614d4d565b500190565b6000610120808352615c588184018d615256565b90508281036020840152615c6c818c61549b565b6001600160a01b039a909a16604084015250506060810196909652608086019490945260a085019290925260c084015260e08301526101009091015292915050565b6001600160a01b038716815260c060208201819052600090615cd290830188613fda565b8281036040840152615ce48188613fda565b6001600160401b038781166060860152861660808501529150615d0890508361486c565b8260a0830152979650505050505050565b60006001600160401b0382811684821681151582840482111615615d3f57615d3f614d4d565b02949350505050565b600181811c90821680615d5c57607f821691505b602082108114156139f657634e487b7160e01b600052602260045260246000fd5b60008251615d8f818460208701613faa565b9190910192915050565b634e487b7160e01b600052603260045260246000fd5b600060ff821660ff811415615dc657615dc6614d4d565b60010192915050565b600060018060a01b03808816835260a060208401528651151560a08401528060208801511660c084015250604086015160c060e0840152615e14610160840182613fda565b60608881015160ff166101008601526080890151151561012086015260a08901516001600160401b031661014086015260408501889052909250615e5b9150830185613aba565b8260808301529695505050505050565b600080600060608486031215615e8057600080fd5b8351615e8b81613e28565b6020850151909350615e9c81613e28565b8092505060408401519050925092509256fe5cf09499daa9369ceef90c68574078750d8aea7c2b4cdd57ba6a72fc94d4f02acb6602dc4ee50f8b02bbfaa7ecd5a9af2c634c93398d6dfe2dffa763ea71484dd172c12542e63cd0b4344ff6e06391807624c50716e6b6ae6fd16b91a9b9f86aa2646970667358221220bba20503b4dea47ea42298ace14db144e178106234090b5bba685b59f05a50ae64736f6c634300080a0033000000000000000000000000ab6912a6574af207a6b68276c204d39b97b9be69000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054d41544943000000000000000000000000000000000000000000000000000000

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000ab6912a6574af207a6b68276c204d39b97b9be69000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054d41544943000000000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : balanceMigrationSource (address): 0xab6912a6574af207a6b68276c204d39b97b9be69
Arg [1] : feeWallet (address): 0x034767f3c519f361c5ecf46ebfc08981c629d381
Arg [2] : nativeAssetSymbol (string): MATIC

-----Encoded View---------------
5 Constructor Arguments found :
Arg [0] : 000000000000000000000000ab6912a6574af207a6b68276c204d39b97b9be69
Arg [1] : 000000000000000000000000034767f3c519f361c5ecf46ebfc08981c629d381
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000060
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000005
Arg [4] : 4d41544943000000000000000000000000000000000000000000000000000000


Deployed ByteCode Sourcemap

1456:47623:12:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;16917:10;1078:20:0;16890:59:12;;;;-1:-1:-1;;;16890:59:12;;;;;;;:::i;:::-;;;;;;;;;1456:47623;;;;;45378:212;;;;;;;;;;-1:-1:-1;45378:212:12;;;;;:::i;:::-;;:::i;40153:621::-;;;;;;;;;;-1:-1:-1;40153:621:12;;;;;:::i;:::-;;:::i;37992:1464::-;;;;;;;;;;-1:-1:-1;37992:1464:12;;;;;:::i;:::-;;:::i;14836:85::-;;;;;;;;;;-1:-1:-1;14906:10:12;;-1:-1:-1;;;;;14906:10:12;14836:85;;;;;;;:::i;:::-;;;;;;;;27603:500;;;;;;;;;;-1:-1:-1;27603:500:12;;;;;:::i;:::-;;:::i;47894:90::-;;;;;;;;;;;;;:::i;12311:267::-;;;;;;;;;;-1:-1:-1;12311:267:12;;;;;:::i;:::-;;:::i;:::-;;;5125:25:38;;;5113:2;5098:18;12311:267:12;4979:177:38;12958:271:12;;;;;;;;;;-1:-1:-1;12958:271:12;;;;;:::i;:::-;;:::i;10414:467::-;;;;;;;;;;-1:-1:-1;10414:467:12;;;;;:::i;:::-;;:::i;25752:236::-;;;;;;;;;;-1:-1:-1;25752:236:12;;;;;:::i;:::-;;:::i;11626:279::-;;;;;;;;;;-1:-1:-1;11626:279:12;;;;;:::i;:::-;;:::i;9264:292::-;;;;;;;;;;-1:-1:-1;9264:292:12;;;;;:::i;:::-;;:::i;18068:390::-;;;;;;;;;;-1:-1:-1;18068:390:12;;;;;:::i;:::-;;:::i;44832:190::-;;;;;;;;;;-1:-1:-1;44832:190:12;;;;;:::i;:::-;;:::i;43353:197::-;;;;;;;;;;;;;:::i;25245:296::-;;;;;;;;;;-1:-1:-1;25245:296:12;;;;;:::i;:::-;;:::i;24289:703::-;;;;;;;;;;-1:-1:-1;24289:703:12;;;;;:::i;:::-;;:::i;726:222:29:-;;;;;;;;;;-1:-1:-1;726:222:29;;;;;:::i;:::-;;:::i;45861:209:12:-;;;;;;;;;;-1:-1:-1;45861:209:12;;;;;:::i;:::-;;:::i;15643:101::-;;;;;;;;;;-1:-1:-1;15721:18:12;;-1:-1:-1;;;;;15721:18:12;15643:101;;46663:207;;;;;;;;;;-1:-1:-1;46663:207:12;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;22628:1407::-;;;;;;;;;;-1:-1:-1;22628:1407:12;;;;;:::i;:::-;;:::i;11216:242::-;;;;;;;;;;-1:-1:-1;11216:242:12;;;;;:::i;:::-;;:::i;7579:50::-;;;;;;;;;;-1:-1:-1;7579:50:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;20447:14:38;;20440:22;20422:41;;20494:2;20479:18;;20472:34;;;;20395:18;7579:50:12;20254:258:38;42743:452:12;;;;;;;;;;-1:-1:-1;42743:452:12;;;;;:::i;:::-;;:::i;16995:155::-;;;:::i;1090:74:29:-;;;;;;;;;;;;;:::i;9753:300:12:-;;;;;;;;;;;;;:::i;16236:178::-;;;;;;;;;;-1:-1:-1;16236:178:12;;;;;:::i;:::-;16338:6;16361:48;;;:37;:48;;;;;;-1:-1:-1;;;;;16361:48:12;;16236:178;;;;-1:-1:-1;;;;;20864:31:38;;;20846:50;;20834:2;20819:18;16236:178:12;20702:200:38;7537:27:12;;;;;;;;;;-1:-1:-1;7537:27:12;;;;-1:-1:-1;;;7537:27:12;;-1:-1:-1;;;;;7537:27:12;;;47268:304;;;;;;;;;;-1:-1:-1;47268:304:12;;;;;:::i;:::-;;:::i;35900:1443::-;;;;;;;;;;-1:-1:-1;35900:1443:12;;;;;:::i;:::-;;:::i;48445:110::-;;;;;;;;;;-1:-1:-1;48445:110:12;;;;;:::i;:::-;;:::i;19981:954::-;;;;;;;;;;-1:-1:-1;19981:954:12;;;;;:::i;:::-;;:::i;28317:304::-;;;;;;;;;;-1:-1:-1;28317:304:12;;;;;:::i;:::-;;:::i;34591:632::-;;;;;;;;;;-1:-1:-1;34591:632:12;;;;;:::i;:::-;;:::i;48778:299::-;;;;;;;;;;-1:-1:-1;48778:299:12;;;;;:::i;:::-;;:::i;41184:636::-;;;;;;;;;;-1:-1:-1;41184:636:12;;;;;:::i;:::-;;:::i;15221:296::-;;;;;;;;;;-1:-1:-1;15221:296:12;;;;;:::i;:::-;;:::i;:::-;;;;;;23288:4:38;23330:3;23319:9;23315:19;23307:27;;23381:6;23375:13;23368:21;23361:29;23350:9;23343:48;23438:4;23430:6;23426:17;23420:24;23479:1;23475;23471:2;23467:10;23463:18;23537:2;23523:12;23519:21;23512:4;23501:9;23497:20;23490:51;23609:4;23601;23593:6;23589:17;23583:24;23579:35;23572:4;23561:9;23557:20;23550:65;23683:2;23675:4;23667:6;23663:17;23657:24;23653:33;23646:4;23635:9;23631:20;23624:63;;;23755:4;23747;23739:6;23735:17;23729:24;23725:35;23718:4;23707:9;23703:20;23696:65;23846:1;23842;23837:3;23833:11;23829:19;23821:4;23813:6;23809:17;23803:24;23799:50;23792:4;23781:9;23777:20;23770:80;23132:724;;;;;44125:317:12;;;;;;;;;;-1:-1:-1;44125:317:12;;;;;:::i;:::-;;:::i;13602:268::-;;;;;;;;;;-1:-1:-1;13602:268:12;;;;;:::i;:::-;;:::i;17444:311::-;;;;;;;;;;-1:-1:-1;17444:311:12;;;;;:::i;:::-;;:::i;29651:1752::-;;;;;;;;;;-1:-1:-1;29651:1752:12;;;;;:::i;:::-;;:::i;42207:292::-;;;;;;;;;;;;;:::i;14627:97::-;;;;;;;;;;-1:-1:-1;14709:10:12;;-1:-1:-1;;;;;14709:10:12;14627:97;;14241:258;;;;;;;;;;-1:-1:-1;14241:258:12;;;;;:::i;:::-;;:::i;32301:1749::-;;;;;;:::i;:::-;;:::i;21265:705::-;;;;;;;;;;-1:-1:-1;21265:705:12;;;;;:::i;:::-;;:::i;718:413:0:-;1078:20;1116:8;;;718:413::o;45378:212:12:-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;45514:71:12::1;::::0;-1:-1:-1;;;45514:71:12;;:39:::1;::::0;::::1;::::0;:71:::1;::::0;:14:::1;::::0;45554:12;;45568:6;;;;45576:8;;45514:71:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;45378:212:::0;;;;:::o;40153:621::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;40307:38:::1;;:15;40353:7:::0;40368:9;40385:12:::1;:28;40398:14;::::0;;;::::1;::::0;::::1;;:::i;:::-;-1:-1:-1::0;;;;;40385:28:12;;::::1;::::0;;::::1;::::0;::::1;::::0;;;;;;;;-1:-1:-1;40385:28:12;:35;40439:10:::1;::::0;40458::::1;::::0;40307:213;;-1:-1:-1;;;;;;40307:213:12::1;::::0;;;;;;::::1;::::0;;;;40385:35:::1;::::0;;::::1;::::0;40439:10;;::::1;::::0;40458;;;::::1;::::0;40385:35;;40498:16:::1;::::0;40307:213;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;40532:237:12::1;::::0;-1:-1:-1;40564:14:12::1;::::0;-1:-1:-1;;40564:14:12;;;::::1;::::0;::::1;;:::i;:::-;40586:26;;::::0;::::1;:9:::0;:26:::1;:::i;:::-;40620:27;::::0;;;::::1;::::0;::::1;;:::i;:::-;40655:33;::::0;;;::::1;::::0;::::1;;:::i;:::-;40696:34;::::0;;;::::1;::::0;::::1;;:::i;:::-;40738:25;::::0;;;::::1;::::0;::::1;;:::i;:::-;40532:237;::::0;;-1:-1:-1;;;;;32925:15:38;;;32907:34;;32977:15;;;32972:2;32957:18;;32950:43;33029:15;;;;33009:18;;;33002:43;;;;-1:-1:-1;;;;;33118:15:38;;;33113:2;33098:18;;33091:43;33171:15;;;33165:3;33150:19;;33143:44;33224:15;;;32887:3;33203:19;;33196:44;32856:3;32841:19;40532:237:12::1;;;;;;;;40153:621:::0;;:::o;37992:1464::-;38475:10;38462:24;;;;:12;:24;;;;;:31;;;38461:32;38453:58;;;;-1:-1:-1;;;38453:58:12;;;;;;;:::i;:::-;38611:325;;;;;;;;1534:1:5;38611:325:12;;;-1:-1:-1;38611:325:12;;;;;;;;;;;;;38740:10;38611:325;;;;-1:-1:-1;;;;;38611:325:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;38917:9;;;;;;;;;;38611:325;;;;;;;38946:10;;38570:444;;-1:-1:-1;;;38570:444:12;;-1:-1:-1;;38570:31:12;;;;:444;;:15;;38611:325;;38946:10;;;;;;1534:1:5;38570:444:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;38570:444:12;;;;;;;;;;;;:::i;:::-;38518:496;;-1:-1:-1;;;;;;;;;;;39045:13:12;;39043:15;;;;;;;;;-1:-1:-1;;;;;39043:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;39043:15:12;;;;;-1:-1:-1;;;;;39043:15:12;;;;;39066:10;39084:6;:19;;;39111:6;:18;;;39137:6;:26;;;39171:6;:36;;;39215:6;:42;;;39026:237;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;39308:10:12;39326:5;39347:3;39359:9;39376:14;39398:12;39426:2;39437:8;39275:176;;;;;;;;;;;;;:::i;:::-;;;;;;;;38167:1289;37992:1464;;;;;;:::o;27603:500::-;48187:18;;27804:30;;-1:-1:-1;;;;;48187:18:12;48173:10;:32;48165:67;;;;-1:-1:-1;;;48165:67:12;;39295:2:38;48165:67:12;;;39277:21:38;39334:2;39314:18;;;39307:30;-1:-1:-1;;;39353:18:38;;;39346:52;39415:18;;48165:67:12;39093:346:38;48165:67:12;27911:141:::1;::::0;;::::1;::::0;::::1;::::0;;-1:-1:-1;;;;;27911:141:12;;::::1;::::0;;;;::::1;;::::0;::::1;::::0;;;;::::1;;::::0;;;;;;;;;;;;;;::::1;::::0;;;;;;;;::::1;::::0;;;;;;28060:10:::1;::::0;27867:231;;-1:-1:-1;;;27867:231:12;;:15:::1;:231;::::0;::::1;39831:25:38::0;39934:13;;39930:22;;39910:18;;;39903:50;39993:22;;39989:31;;39969:18;;;39962:59;40071:22;;40064:30;40057:38;40037:18;;;40030:66;40133:22;40112:19;;;40105:51;40197:23;40193:32;;40172:19;;;40165:61;40267:23;40263:32;;40242:19;;;40235:61;28060:10:12;;;::::1;40312:19:38::0;;;40305:44;28060:10:12;40365:19:38;;;40358:35;27867:36:12::1;::::0;::::1;::::0;39803:19:38;;27867:231:12::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;27842:256:::0;27603:500;-1:-1:-1;;;;;;;27603:500:12:o;47894:90::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;47947:17:12::1;:32:::0;;-1:-1:-1;;;;;;47947:32:12::1;::::0;;47894:90::o;12311:267::-;12454:119;;-1:-1:-1;;;12454:119:12;;12426:7;;12454:47;;;;:119;;:14;;12511:6;;12527:12;;12549:16;;12454:119;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12441:132;;12311:267;;;;;:::o;12958:271::-;13107:117;;-1:-1:-1;;;13107:117:12;;13079:7;;13107:46;;;;:117;;:14;;13163:6;;13179:11;;;;13200:16;;13107:117;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;13094:130;;12958:271;;;;;;:::o;10414:467::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;475:22:5::1;10534:25:12;:63;10519:121;;;::::0;-1:-1:-1;;;10519:121:12;;42114:2:38;10519:121:12::1;::::0;::::1;42096:21:38::0;42153:2;42133:18;;;42126:30;-1:-1:-1;;;42172:18:38;;;42165:57;42239:18;;10519:121:12::1;41912:351:38::0;10519:121:12::1;10683:23;::::0;;10712:51;;;;10775:101:::1;::::0;;42442:25:38;;;42498:2;42483:18;;42476:34;;;10775:101:12::1;::::0;42415:18:38;10775:101:12::1;42268:248:38::0;25752:236:12;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;25871:112:12::1;::::0;-1:-1:-1;;;25871:112:12;;:35:::1;::::0;::::1;::::0;:112:::1;::::0;:15:::1;::::0;25914:16;;25938:17;;25963:14:::1;::::0;25871:112:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;25752:236:::0;;:::o;11626:279::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;1078:20:0;;11693:68:12::1;;;;-1:-1:-1::0;;;11693:68:12::1;;;;;;;:::i;:::-;11797:18;::::0;-1:-1:-1;;;;;11782:33:12;;::::1;11797:18:::0;::::1;11782:33;;11767:94;;;;-1:-1:-1::0;;;11767:94:12::1;;;;;;;:::i;:::-;11868:18;:32:::0;;-1:-1:-1;;;;;;11868:32:12::1;-1:-1:-1::0;;;;;11868:32:12;;;::::1;::::0;;;::::1;::::0;;11626:279::o;9264:292::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;9351:10:12::1;::::0;-1:-1:-1;;;;;9351:10:12::1;:47:::0;9336:108:::1;;;::::0;-1:-1:-1;;;9336:108:12;;43933:2:38;9336:108:12::1;::::0;::::1;43915:21:38::0;43972:2;43952:18;;;43945:30;44011:32;43991:18;;;43984:60;44061:18;;9336:108:12::1;43731:354:38::0;9336:108:12::1;1078:20:0::0;;9450:69:12::1;;;;-1:-1:-1::0;;;9450:69:12::1;;;;;;;:::i;:::-;9526:10;:25:::0;;-1:-1:-1;;;;;;9526:25:12::1;-1:-1:-1::0;;;;;9526:25:12;;;::::1;::::0;;;::::1;::::0;;9264:292::o;18068:390::-;18182:18;18209:110;18251:11;18272:39;:37;:39::i;:::-;18209:14;;:110;:32;:110::i;:::-;18342:18;;;;18182:137;;-1:-1:-1;;;;;;18334:43:12;18326:72;;;;-1:-1:-1;;;18326:72:12;;;;;;;:::i;:::-;18405:48;18413:10;18425:5;18432:20;18405:7;:48::i;:::-;18176:282;18068:390;;:::o;44832:190::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;44957:60:12::1;::::0;-1:-1:-1;;;44957:60:12;;:28:::1;::::0;::::1;::::0;:60:::1;::::0;:14:::1;::::0;44986:12;;45000:6;;;;45008:8;;44957:60:::1;;;:::i;43353:197::-:0;43403:33;43425:10;43403:21;:33::i;:::-;43395:71;;;;-1:-1:-1;;;43395:71:12;;;;;;;:::i;:::-;43493:10;43480:24;;;;:12;:24;;;;;;43473:31;;-1:-1:-1;;43473:31:12;;;;;;;;;43516:29;;;;;;:::i;:::-;;;;;;;;43353:197::o;25245:296::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;25480:32:12;;25520:10:::1;::::0;25365:171:::1;::::0;-1:-1:-1;;;25365:171:12;;:15:::1;25480:16;25365:171:::0;::::1;44831:25:38::0;-1:-1:-1;;;;;44930:15:38;;;44910:18;;;44903:43;44982:15;;;44962:18;;;44955:43;25458:14:12::1;45014:18:38::0;;;45007:34;25480:32:12;;::::1;45057:19:38::0;;;45050:44;25520:10:12;::::1;45110:19:38::0;;;45103:44;25365:36:12::1;::::0;::::1;::::0;44803:19:38;;25365:171:12::1;44444:709:38::0;24289:703:12;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;24374:47:::1;24396:10;:24;;;24374:21;:47::i;:::-;24373:48;24365:74;;;;-1:-1:-1::0;;;24365:74:12::1;;;;;;;:::i;:::-;24657:10;::::0;24677::::1;::::0;24607:174:::1;::::0;-1:-1:-1;;;24607:174:12;;24454:31:::1;::::0;;;;;;;24607:11:::1;::::0;:20:::1;::::0;:174:::1;::::0;24637:10;;-1:-1:-1;;;;;24657:10:12;;::::1;::::0;24677;::::1;::::0;24657;;24721:16:::1;::::0;24747:26:::1;::::0;24607:174;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;::::0;;::::1;-1:-1:-1::0;;24607:174:12::1;::::0;::::1;;::::0;::::1;::::0;;;::::1;::::0;::::1;:::i;:::-;24446:335;;;;;;;;24793:194;24810:10;:24;;;24842:12;24862:11;24881:10;:30;;;24919:24;24951:30;24793:194;;;;;;;;;;;:::i;:::-;;;;;;;;24359:633;;;;24289:703:::0;:::o;726:222:29:-;254:10;-1:-1:-1;;;;;268:6:29;254:20;;246:53;;;;-1:-1:-1;;;246:53:29;;;;;;;:::i;:::-;-1:-1:-1;;;;;795:24:29;::::1;787:59;;;;-1:-1:-1::0;;;787:59:29::1;;;;;;;:::i;:::-;872:6;::::0;-1:-1:-1;;;;;860:18:29;;::::1;872:6:::0;::::1;860:18;;852:67;;;::::0;-1:-1:-1;;;852:67:29;;49315:2:38;852:67:29::1;::::0;::::1;49297:21:38::0;49354:2;49334:18;;;49327:30;49393:34;49373:18;;;49366:62;-1:-1:-1;;;49444:18:38;;;49437:34;49488:19;;852:67:29::1;49113:400:38::0;852:67:29::1;926:6;:17:::0;;-1:-1:-1;;;;;;926:17:29::1;-1:-1:-1::0;;;;;926:17:29;;;::::1;::::0;;;::::1;::::0;;726:222::o;45861:209:12:-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;45965:51:12::1;::::0;-1:-1:-1;;;45965:51:12;;:29:::1;::::0;::::1;::::0;:51:::1;::::0;:14:::1;::::0;45995:12;;46009:6;;;;45965:51:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;46027:38;46044:12;46058:6;;46027:38;;;;;;;;:::i;:::-;;;;;;;;45861:209:::0;;;:::o;46663:207::-;46776:12;;:::i;:::-;46805:60;46838:11;;46805:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;46805:14:12;;:60;-1:-1:-1;46851:13:12;;-1:-1:-1;;46805:32:12;:60::i;22628:1407::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;22822:40:::1;22844:3;:17;;;22822:21;:40::i;:::-;22821:41;22806:97;;;;-1:-1:-1::0;;;22806:97:12::1;;;;;;;:::i;:::-;22925:41;22947:4;:18;;;22925:21;:41::i;:::-;22924:42;22909:99;;;;-1:-1:-1::0;;;22909:99:12::1;;;;;;;:::i;:::-;23091:10;::::0;23015:262:::1;::::0;-1:-1:-1;;;23015:262:12;;:7:::1;::::0;:26:::1;::::0;:262:::1;::::0;23049:3;;23060:4;;23072:11;;-1:-1:-1;;;;;23091:10:12;;::::1;::::0;;;23131:15:::1;::::0;23154:16:::1;::::0;23178:21:::1;::::0;23207:19:::1;::::0;23234:37:::1;::::0;23015:262;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;;23316:17:12::1;::::0;;::::1;::::0;23341:18;;::::1;::::0;23367:26;;:42;;23417:43:::1;::::0;;::::1;::::0;23468:50:::1;::::0;;::::1;::::0;23526:51:::1;::::0;;::::1;::::0;23585:21;;::::1;::::0;:45;;::::1;::::0;23638:46;;;::::1;::::0;23289:741:::1;::::0;23316:17;23341:18;23417:43;;23526:51;23585:45;23692:106:::1;23585:45:::0;23468:50;23692:106:::1;:::i;:::-;23868:11;:21;;;:46;;;23806:11;:26;;;:51;;;:108;;;;:::i;:::-;23962:13;23922:26:::0;;:36:::1;;::::0;:53:::1;::::0;::::1;;;;;;:::i;:::-;;:102;;24011:13;23922:102;;;23986:14;23922:102;23289:741;;;;;;;;;;;;;;;;:::i;11216:242::-:0;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;-1:-1:-1;;;;;11291:28:12;::::1;11283:63;;;;-1:-1:-1::0;;;11283:63:12::1;;;;;;;:::i;:::-;11376:10;::::0;-1:-1:-1;;;;;11360:26:12;;::::1;11376:10:::0;::::1;11360:26;;11352:69;;;;-1:-1:-1::0;;;11352:69:12::1;;;;;;;:::i;:::-;11428:10;:25:::0;;-1:-1:-1;;;;;;11428:25:12::1;-1:-1:-1::0;;;;;11428:25:12;;;::::1;::::0;;;::::1;::::0;;11216:242::o;42743:452::-;42810:33;42832:10;42810:21;:33::i;:::-;42802:71;;;;-1:-1:-1;;;42802:71:12;;;;;;;:::i;:::-;43010:10;;42954:124;;-1:-1:-1;;;42954:124:12;;-1:-1:-1;;;;;60392:15:38;;;43054:16:12;42954:124;;;60374:34:38;;;;43010:10:12;;60424:18:38;;;60417:43;43010:10:12;60476:18:38;;;60469:34;60519:18;;;60512:34;;;;42909:36:12;;42954:11;;:24;;60308:19:38;;42954:124:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;43090:100;;;43117:10;61049:34:38;;-1:-1:-1;;;;;61119:15:38;;61114:2;61099:18;;61092:43;-1:-1:-1;;;;;61171:31:38;;61151:18;;;61144:59;;;;42909:169:12;;-1:-1:-1;43090:100:12;;60999:2:38;60984:18;43090:100:12;60811:398:38;16995:155:12;17042:103;17057:10;17075:47;:14;17117:3;17075:33;:47::i;:::-;17130:9;17042:7;:103::i;:::-;16995:155::o;1090:74:29:-;254:10;-1:-1:-1;;;;;268:6:29;254:20;;246:53;;;;-1:-1:-1;;;246:53:29;;;;;;;:::i;:::-;1155:3:::1;1138:21:::0;;-1:-1:-1;;;;;;1138:21:29::1;::::0;;1090:74::o;9753:300:12:-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;9820:13:12::1;::::0;-1:-1:-1;;;9820:13:12;::::1;-1:-1:-1::0;;;;;9820:13:12;;::::1;:45;9805:96;;;::::0;-1:-1:-1;;;9805:96:12;;61416:2:38;9805:96:12::1;::::0;::::1;61398:21:38::0;61455:2;61435:18;;;61428:30;-1:-1:-1;;;61474:18:38;;;61467:50;61534:18;;9805:96:12::1;61214:344:38::0;9805:96:12::1;9932:32:::0;;-1:-1:-1;;;;;9932:32:12::1;9924:57:::0;:124:::1;;10000:32:::0;;:48:::1;::::0;;-1:-1:-1;;;10000:48:12;;;;-1:-1:-1;;;;;10000:32:12;;::::1;::::0;:46:::1;::::0;:16:::1;:48:::0;;::::1;::::0;::::1;::::0;;;;;;;;;:32;:48:::1;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;9924:124;;;9990:1;9924:124;9908:13;;:140;;;;;-1:-1:-1::0;;;;;9908:140:12::1;;;;;-1:-1:-1::0;;;;;9908:140:12::1;;;;;;9753:300::o:0;47268:304::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;-1:-1:-1;;;;;47353:35:12;::::1;47345:70;;;;-1:-1:-1::0;;;47345:70:12::1;;;;;;;:::i;:::-;47459:17;::::0;-1:-1:-1;;;;;47436:40:12;;::::1;47459:17:::0;::::1;47436:40;;47421:101;;;;-1:-1:-1::0;;;47421:101:12::1;;;;;;;:::i;:::-;47528:17;:39:::0;;-1:-1:-1;;;;;;47528:39:12::1;-1:-1:-1::0;;;;;47528:39:12;;;::::1;::::0;;;::::1;::::0;;47268:304::o;35900:1443::-;36393:10;36380:24;;;;:12;:24;;;;;:31;;;36379:32;36371:58;;;;-1:-1:-1;;;36371:58:12;;;;;;;:::i;:::-;36529:314;;;;;;;;1534:1:5;36529:314:12;;;-1:-1:-1;36529:314:12;;;;;;;;;;;;;36658:10;36529:314;;;;-1:-1:-1;;;;;36529:314:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;36824:9;;;;;;;;;;36529:314;;;;;;;36853:10;;36488:433;;-1:-1:-1;;;36488:433:12;;-1:-1:-1;;36488:31:12;;;;:433;;:15;;36529:314;;36853:10;;;;;;1534:1:5;36488:433:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;36488:433:12;;;;;;;;;;;;:::i;:::-;36436:485;;-1:-1:-1;;;;;;;;;;;36952:13:12;;36950:15;;;;;;;;;-1:-1:-1;;;;;36950:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;36950:15:12;;;;;-1:-1:-1;;;;;36950:15:12;;;;;36973:10;36991:6;:19;;;37018:6;:18;;;37044:6;:26;;;37078:6;:36;;;37122:6;:42;;;36933:237;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;37215:10:12;37233:6;37247;37261:9;37278:10;37296;37314:2;37324:8;37182:156;;;;;;;;;;;;;:::i;:::-;;;;;;;;36085:1258;35900:1443;;;;;;;:::o;48445:110::-;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;48539:10:12::1;::::0;48506:44:::1;::::0;-1:-1:-1;;;48506:44:12;;:13:::1;::::0;:18:::1;::::0;:44:::1;::::0;48525:12;;-1:-1:-1;;;;;48539:10:12;;::::1;::::0;48506:44:::1;;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;48445:110:::0;:::o;19981:954::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;20149:40:::1;20171:3;:17;;;20149:21;:40::i;:::-;20148:41;20133:97;;;;-1:-1:-1::0;;;20133:97:12::1;;;;;;;:::i;:::-;20252:41;20274:4;:18;;;20252:21;:41::i;:::-;20251:42;20236:99;;;;-1:-1:-1::0;;;20236:99:12::1;;;;;;;:::i;:::-;20424:10;::::0;20342:245:::1;::::0;-1:-1:-1;;;20342:245:12;;:7:::1;::::0;:29:::1;::::0;:245:::1;::::0;20379:3;;20390:4;;20402:14;;-1:-1:-1;;;;;20424:10:12;;::::1;::::0;;;20464:16:::1;::::0;20488:21:::1;::::0;20517:19:::1;::::0;20544:37:::1;::::0;20342:245;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;20629:17:12::1;::::0;;::::1;::::0;20654:18;;::::1;::::0;20680:30;;20718:31:::1;::::0;::::1;::::0;20757:38:::1;::::0;::::1;::::0;20803:39:::1;::::0;::::1;::::0;20599:331:::1;::::0;-1:-1:-1;20680:30:12::1;20850:14;:24;;;:41;;;;;;;;:::i;:::-;;:74;;20911:13;20850:74;;;20894:14;20850:74;20599:331;;;;;;;;;;;;:::i;28317:304::-:0;361:6:29;;-1:-1:-1;;;;;361:6:29;347:10;:20;339:53;;;;-1:-1:-1;;;339:53:29;;;;;;;:::i;:::-;28443:97:12::1;::::0;-1:-1:-1;;;28443:97:12;;:15:::1;:97;::::0;::::1;65120:25:38::0;-1:-1:-1;;;;;65219:15:38;;;65199:18;;;65192:43;65271:15;;65251:18;;;65244:43;28443:42:12::1;::::0;::::1;::::0;65093:18:38;;28443:97:12::1;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;;;;;28552:64;28580:16;28598:17;28552:64;;;;;;;:::i;34591:632::-:0;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;34753:38:::1;34775:15;::::0;;;::::1;::::0;::::1;;:::i;:::-;34753:21;:38::i;:::-;34752:39;34744:73;;;::::0;-1:-1:-1;;;34744:73:12;;65809:2:38;34744:73:12::1;::::0;::::1;65791:21:38::0;65848:2;65828:18;;;65821:30;-1:-1:-1;;;65867:18:38;;;65860:51;65928:18;;34744:73:12::1;65607:345:38::0;34744:73:12::1;34900:10;::::0;34926::::1;::::0;34824:143:::1;::::0;-1:-1:-1;;;34824:143:12;;:35:::1;::::0;::::1;::::0;:143:::1;::::0;:15:::1;::::0;34867:8;;34883:9;;-1:-1:-1;;;;;34900:10:12;;::::1;::::0;34926;;::::1;::::0;34945:16:::1;::::0;34824:143;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;34979:239:12::1;::::0;-1:-1:-1;35012:15:12::1;::::0;-1:-1:-1;;35012:15:12;;;::::1;::::0;::::1;;:::i;48778:299::-:0;48898:10;;48887:37;;;-1:-1:-1;;;48887:37:12;;;;48861:23;;-1:-1:-1;;;;;48898:10:12;;48887:35;;:37;;;;;;;;;;;;;;48898:10;48887:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;48861:63;-1:-1:-1;48938:10:12;-1:-1:-1;;;;;48938:29:12;;;48930:64;;;;-1:-1:-1;;;48930:64:12;;68535:2:38;48930:64:12;;;68517:21:38;68574:2;68554:18;;;68547:30;-1:-1:-1;;;68593:18:38;;;68586:52;68655:18;;48930:64:12;68333:346:38;48930:64:12;-1:-1:-1;;;;;;49008:50:12;;;:42;:50;;;:16;:50;;;;;;;;:64;;;;;;;;;;;49001:71;;-1:-1:-1;;;;;;49001:71:12;;;48778:299::o;41184:636::-;41301:33;41323:10;41301:21;:33::i;:::-;41293:71;;;;-1:-1:-1;;;41293:71:12;;;;;;;:::i;:::-;41584:10;;41475:154;;-1:-1:-1;;;41475:154:12;;:15;41605:16;41475:154;;;69026:25:38;;;;-1:-1:-1;;;;;69125:15:38;;;69105:18;;;69098:43;69177:15;;;69157:18;;;69150:43;41584:10:12;;;69209:18:38;;;69202:43;69261:19;;;69254:35;;;;41379:36:12;;;;41475:35;;;;68998:19:38;;41475:154:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;41641:174;;;41675:10;69979:34:38;;-1:-1:-1;;;;;70049:15:38;;;70044:2;70029:18;;70022:43;70101:15;;70081:18;;;70074:43;-1:-1:-1;;;;;70190:15:38;;;70185:2;70170:18;;70163:43;70243:15;;70237:3;70222:19;;70215:44;41641:174:12;;70190:15:38;;-1:-1:-1;70243:15:38;;-1:-1:-1;41641:174:12;;;;;;69928:3:38;41641:174:12;;;41287:533;;41184:636;;:::o;15221:296::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;15402:110:12;:15;15461:16;15487:17;15402:49;:110::i;:::-;15389:123;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;15389:123:12;;;;;;;;;-1:-1:-1;;;15389:123:12;;;;;;;;;;;-1:-1:-1;;;15389:123:12;;;;;;;;;-1:-1:-1;;;15389:123:12;;;;;;;;;;;;;;-1:-1:-1;;;;;15389:123:12;;;;;;-1:-1:-1;15221:296:12;;;;:::o;44125:317::-;44294:23;;44246:72;;-1:-1:-1;;;44246:72:12;;:19;:72;;;70538:25:38;-1:-1:-1;;;;;70599:32:38;;70579:18;;;70572:60;70648:18;;;70641:34;;;;44186:20:12;;;;44246:40;;;;70511:18:38;;44246:72:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;44330:107;;;44359:10;71231:51:38;;-1:-1:-1;;;;;71318:32:38;;71313:2;71298:18;;71291:60;-1:-1:-1;;;;;71387:31:38;;71367:18;;;71360:59;;;;71450:2;71435:18;;71428:34;;;44185:133:12;;-1:-1:-1;44185:133:12;-1:-1:-1;44330:107:12;;71218:3:38;71203:19;44330:107:12;71001:467:38;13602:268:12;13753:112;;-1:-1:-1;;;13753:112:12;;-1:-1:-1;;;;;71761:15:38;;;13841:16:12;13753:112;;;71743:34:38;;;;71813:15;;;71793:18;;;71786:43;71845:18;;;71838:34;13724:6:12;;13753:13;;:40;;71678:18:38;;13753:112:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;17444:311::-;17554:18;17575:47;:14;17609:12;17575:33;:47::i;:::-;17554:68;-1:-1:-1;;;;;;17637:37:12;;17629:66;;;;-1:-1:-1;;;17629:66:12;;;;;;;:::i;29651:1752::-;30173:10;30160:24;;;;:12;:24;;;;;:31;;;30159:32;30151:58;;;;-1:-1:-1;;;30151:58:12;;;;;;;:::i;:::-;30307:337;;;;;;;;1534:1:5;30307:337:12;;;-1:-1:-1;30307:337:12;;;;;;;;;;;;;30437:10;30307:337;;;;-1:-1:-1;;;;;30307:337:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30625:9;;;;;;;;;;30307:337;;;;;;;30654:10;;30269:453;;-1:-1:-1;;;30269:453:12;;-1:-1:-1;;30269:28:12;;;;:453;;:15;;30307:337;;30654:10;;;;;;1534:1:5;30269:453:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;30269:453:12;;;;;;;;;;;;:::i;:::-;30216:506;;-1:-1:-1;;;;;;;;;;;30753:13:12;;30751:15;;;;;;;;;-1:-1:-1;;;;;30751:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;30751:15:12;;;;;-1:-1:-1;;;;;30751:15:12;;;;;30774:10;30792:6;30806;:19;;;30833:6;:27;;;30868:6;:37;;;30913:6;:43;;;30734:228;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;30993:13:12;;30991:15;;;;;;;;;-1:-1:-1;;;;;30991:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;30991:15:12;;;;;-1:-1:-1;;;;;30991:15:12;;;;;31014:10;31032:6;31046;:19;;;31073:6;:27;;;31108:6;:37;;;31153:6;:43;;;30974:228;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;31248:10:12;31266:6;31280;31294:14;31316;31338:10;31356;31374:2;31384:8;31214:184;;;;;;;;;;;;;;:::i;:::-;;;;;;;;29868:1535;29651:1752;;;;;;;;:::o;42207:292::-;42266:10;42253:24;;;;:12;:24;;;;;:31;;;42252:32;42244:66;;;;-1:-1:-1;;;42244:66:12;;76216:2:38;42244:66:12;;;76198:21:38;76255:2;76235:18;;;76228:30;-1:-1:-1;;;76274:18:38;;;76267:51;76335:18;;42244:66:12;76014:345:38;42244:66:12;42344:74;;;;;;;;42362:4;42344:74;;;;;;42389:23;;42374:12;:38;;;;:::i;:::-;42344:74;;42330:10;42317:24;;;;:12;:24;;;;;;;;:101;;;;-1:-1:-1;;42317:101:12;;;;;;;;;;-1:-1:-1;42317:101:12;;;;;;;42470:23;;42430:64;;42330:10;42455:38;;:12;:38;:::i;:::-;42430:64;;;-1:-1:-1;;;;;76689:32:38;;;76671:51;;76753:2;76738:18;;76731:34;;;;76644:18;42430:64:12;76497:274:38;14241:258:12;14383:111;;-1:-1:-1;;;14383:111:12;;14356:6;;14383:40;;;;:111;;:14;;14433:6;;14449:11;;;;14470:16;;14383:111;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;32301:1749::-;32795:10;32782:24;;;;:12;:24;;;;;:31;;;32781:32;32773:58;;;;-1:-1:-1;;;32773:58:12;;;;;;;:::i;:::-;32929:347;;;;;;;;1534:1:5;32929:347:12;;;-1:-1:-1;32929:347:12;;;;;;;;;;;;;33059:10;32929:347;;;;-1:-1:-1;;;;;32929:347:12;;;;;;;;;;;;;;;;;;;33152:9;32929:347;;;;;;;;;;;;;;;;;;;;;;;;;;;;;33257:9;;;;;;;;;;32929:347;;;;;;;33286:10;;32891:463;;-1:-1:-1;;;32891:463:12;;-1:-1:-1;;32891:28:12;;;;:463;;:15;;32929:347;;33286:10;;;;;;1534:1:5;32891:463:12;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;32891:463:12;;;;;;;;;;;;:::i;:::-;32838:516;;-1:-1:-1;;;;;;;;;;;33385:13:12;;33383:15;;;;;;;;;-1:-1:-1;;;;;33383:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;33383:15:12;;;;;-1:-1:-1;;;;;33383:15:12;;;;;33406:10;33424:5;33437:6;:19;;;33464:6;:27;;;33499:6;:37;;;33544:6;:43;;;33366:227;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;33624:13:12;;33622:15;;;;;;;;;-1:-1:-1;;;;;33622:15:12;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;33622:15:12;;;;;-1:-1:-1;;;;;33622:15:12;;;;;33645:10;33671:3;33683:6;:19;;;33710:6;:27;;;33745:6;:37;;;33790:6;:43;;;33605:234;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;;;;;;;33885:10:12;33903:5;33924:3;33936:18;33962:9;33979:14;34001:12;34021:2;34031:8;33851:194;;;;;;;;;;;;;;:::i;21265:705::-;48042:17;;-1:-1:-1;;;;;48042:17:12;48028:10;:31;48020:68;;;;-1:-1:-1;;;48020:68:12;;;;;;;:::i;:::-;21395:42:::1;21417:5;:19;;;21395:21;:42::i;:::-;21394:43;21379:101;;;::::0;-1:-1:-1;;;21379:101:12;;76978:2:38;21379:101:12::1;::::0;::::1;76960:21:38::0;77017:2;76997:18;;;76990:30;-1:-1:-1;;;77036:18:38;;;77029:57;77103:18;;21379:101:12::1;76776:351:38::0;21379:101:12::1;21549:10;::::0;21487:248:::1;::::0;-1:-1:-1;;;21487:248:12;;:7:::1;::::0;:24:::1;::::0;:248:::1;::::0;21519:5;;21532:9;;-1:-1:-1;;;;;21549:10:12::1;::::0;;;21589:15:::1;::::0;21612:16:::1;::::0;21636:21:::1;::::0;21665:19:::1;::::0;21692:37:::1;::::0;21487:248;::::1;;:::i;:::-;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;::::0;::::1;;;;;-1:-1:-1::0;;;21772:19:12::1;::::0;;::::1;::::0;21799:25;;21832:26:::1;::::0;::::1;::::0;21866:33:::1;::::0;;::::1;::::0;21907:34:::1;::::0;::::1;::::0;21949:10;;::::1;::::0;21747:218;;::::1;::::0;-1:-1:-1;21747:218:12::1;::::0;21866:33;;21907:34;21949:10;21747:218:::1;:::i;7799:157:1:-:0;7857:6;7894:4;7912:39;7894:4;7919:15;7912:39;:::i;:::-;7905:46;;;7799:157;:::o;7067:714::-;7199:12;;:::i;:::-;7223:45;7237:4;:22;;7223:45;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7261:6;7223:13;:45::i;:::-;7219:108;;;7285:35;7297:4;:22;;7285:35;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:11;:35::i;:::-;7278:42;;;;7219:108;7333:18;;:::i;:::-;7398:1;7361:4;:19;;7381:6;7361:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;:38;7357:296;;;7414:7;7409:238;7431:4;:19;;7451:6;7431:27;;;;;;:::i;:::-;;;;;;;;;;;;;;:34;7427:38;;;;7409:238;;;7554:13;-1:-1:-1;;;;;7497:70:1;:4;:19;;7517:6;7497:27;;;;;;:::i;:::-;;;;;;;;;;;;;7525:1;7497:30;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;:53;;;;;;-1:-1:-1;;;;;7497:53:1;:70;7482:157;;7598:4;:19;;7618:6;7598:27;;;;;;:::i;:::-;;;;;;;;;;;;;7626:1;7598:30;;;;;;;;;;:::i;:::-;;;;;;;;;;7590:38;;;;;;;;7598:30;;;;;;;7590:38;;;;;;;;;-1:-1:-1;;;;;7590:38:1;;;;;;;;;;;;;;;;;;;7598:30;7590:38;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;7590:38:1;;;-1:-1:-1;;7590:38:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;7590:38:1;;;;;;;-1:-1:-1;7482:157:1;7467:3;;;;:::i;:::-;;;;7409:238;;;;7357:296;7673:12;;:33;;;;;7689:5;:17;;;7673:33;7658:99;;;;-1:-1:-1;;;7658:99:1;;80612:2:38;7658:99:1;;;80594:21:38;80651:2;80631:18;;;80624:30;80690:34;80670:18;;;80663:62;-1:-1:-1;;;80741:18:38;;;80734:33;80784:19;;7658:99:1;80410:399:38;18462:1043:12;18659:13;;-1:-1:-1;;;18659:13:12;;-1:-1:-1;;;;;18659:13:12;;;:45;;18651:75;;;;-1:-1:-1;;;18651:75:12;;81016:2:38;18651:75:12;;;80998:21:38;81055:2;81035:18;;;81028:30;-1:-1:-1;;;81074:18:38;;;81067:47;81131:18;;18651:75:12;80814:341:38;18651:75:12;-1:-1:-1;;;;;18964:20:12;;;;;;:12;:20;;;;;:27;;;18963:28;18955:54;;;;-1:-1:-1;;;18955:54:12;;;;;;;:::i;:::-;19234:10;;19145:133;;-1:-1:-1;;;19145:133:12;;19024:21;;;;;;19145:10;;:18;;:133;;19173:6;;19189:5;;19204:20;;-1:-1:-1;;;;;19234:10:12;;19254:16;;19145:133;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19285:13;:15;;19016:262;;-1:-1:-1;19016:262:12;;-1:-1:-1;19016:262:12;-1:-1:-1;;;;19285:15:12;;;-1:-1:-1;;;;;19285:15:12;;:13;:15;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;19285:15:12;;;;;-1:-1:-1;;;;;19285:15:12;;;;;;;-1:-1:-1;;;;;;;;;;;19329:13:12;;;;;;;;;-1:-1:-1;;;;;19329:13:12;19350:6;19364:5;:18;;;19390:5;:12;;;19410:14;19432:24;19464:30;19312:188;;;;;;;;;;;;:::i;:::-;;;;;;;;18569:936;;;18462:1043;;;:::o;43554:202::-;-1:-1:-1;;;;;43662:20:12;;43624:4;43662:20;;;:12;:20;;;;;43695:11;;;;:56;;;;-1:-1:-1;43710:25:12;;;43739:12;-1:-1:-1;43710:41:12;;43688:63;-1:-1:-1;;43554:202:12:o;6292:411:1:-;6399:12;;:::i;:::-;-1:-1:-1;;;;;6425:28:1;;6421:91;;6470:35;6482:4;:22;;6470:35;;;;;:::i;:::-;6463:42;;;;6421:91;-1:-1:-1;;;;;6539:34:1;;;6518:18;6539:34;;;;;;;;;;;6518:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:18;;:55;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;6518:55:1;;;-1:-1:-1;;6518:55:1;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;6518:55:1;;;;;;6594:12;;;;-1:-1:-1;6594:33:1;;;;;6610:5;:17;;;6594:33;6579:100;;;;-1:-1:-1;;;6579:100:1;;82974:2:38;6579:100:1;;;82956:21:38;83013:2;82993:18;;;82986:30;83052:34;83032:18;;;83025:62;-1:-1:-1;;;83103:18:38;;;83096:34;83147:19;;6579:100:1;82772:400:38;14349:315:24;-1:-1:-1;;;;;14547:39:24;;;14506:26;14547:39;;;:21;;;:39;;;;;;;;:58;;;;;;;;;14619:11;;;;14611:48;;;;-1:-1:-1;;;14611:48:24;;83379:2:38;14611:48:24;;;83361:21:38;83418:2;83398:18;;;83391:30;-1:-1:-1;;;83437:18:38;;;83430:54;83501:18;;14611:48:24;83177:348:38;8335:181:1;8426:4;8508:1;8491:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8481:30;;;;;;8474:1;8457:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;8447:30;;;;;;:64;8440:71;;8335:181;;;;:::o;8062:179::-;8150:12;;:::i;:::-;-1:-1:-1;8179:57:1;;;;;;;;8185:4;8179:57;;;-1:-1:-1;8179:57:1;;;;;;;;;;;;;8224:2;8179:57;;;;;;;;;;;;;;;;8062:179::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:340:38:-;216:2;198:21;;;255:2;235:18;;;228:30;-1:-1:-1;;;289:2:38;274:18;;267:46;345:2;330:18;;14:340::o;359:139::-;-1:-1:-1;;;;;442:31:38;;432:42;;422:70;;488:1;485;478:12;422:70;359:139;:::o;503:348::-;555:8;565:6;619:3;612:4;604:6;600:17;596:27;586:55;;637:1;634;627:12;586:55;-1:-1:-1;660:20:38;;-1:-1:-1;;;;;692:30:38;;689:50;;;735:1;732;725:12;689:50;772:4;764:6;760:17;748:29;;824:3;817:4;808:6;800;796:19;792:30;789:39;786:59;;;841:1;838;831:12;786:59;503:348;;;;;:::o;856:156::-;922:20;;982:4;971:16;;961:27;;951:55;;1002:1;999;992:12;951:55;856:156;;;:::o;1017:639::-;1119:6;1127;1135;1143;1196:2;1184:9;1175:7;1171:23;1167:32;1164:52;;;1212:1;1209;1202:12;1164:52;1251:9;1238:23;1270:39;1303:5;1270:39;:::i;:::-;1328:5;-1:-1:-1;1384:2:38;1369:18;;1356:32;-1:-1:-1;;;;;1400:30:38;;1397:50;;;1443:1;1440;1433:12;1397:50;1482:59;1533:7;1524:6;1513:9;1509:22;1482:59;:::i;:::-;1560:8;;-1:-1:-1;1456:85:38;-1:-1:-1;1614:36:38;;-1:-1:-1;1646:2:38;1631:18;;1614:36;:::i;:::-;1604:46;;1017:639;;;;;;;:::o;1661:173::-;1738:5;1783:3;1774:6;1769:3;1765:16;1761:26;1758:46;;;1800:1;1797;1790:12;1758:46;-1:-1:-1;1822:6:38;1661:173;-1:-1:-1;1661:173:38:o;1839:559::-;1989:6;1997;2050:3;2038:9;2029:7;2025:23;2021:33;2018:53;;;2067:1;2064;2057:12;2018:53;2094:23;;-1:-1:-1;;;;;2129:30:38;;2126:50;;;2172:1;2169;2162:12;2126:50;2195:22;;2251:3;2233:16;;;2229:26;2226:46;;;2268:1;2265;2258:12;2226:46;2291:2;-1:-1:-1;2312:80:38;2384:7;2379:2;2364:18;;2312:80;:::i;:::-;2302:90;;1839:559;;;;;:::o;2403:142::-;2471:20;;2500:39;2471:20;2500:39;:::i;2550:679::-;2654:6;2662;2670;2678;2686;2694;2747:3;2735:9;2726:7;2722:23;2718:33;2715:53;;;2764:1;2761;2754:12;2715:53;2803:9;2790:23;2822:39;2855:5;2822:39;:::i;:::-;2880:5;-1:-1:-1;2932:2:38;2917:18;;2904:32;;-1:-1:-1;2983:2:38;2968:18;;2955:32;;-1:-1:-1;3034:2:38;3019:18;;3006:32;;-1:-1:-1;3090:3:38;3075:19;;3062:33;3104:41;3062:33;3104:41;:::i;:::-;3164:7;3154:17;;;3218:3;3207:9;3203:19;3190:33;3180:43;;2550:679;;;;;;;;:::o;3234:104::-;-1:-1:-1;;;;;3300:31:38;3288:44;;3234:104::o;3343:203::-;-1:-1:-1;;;;;3507:32:38;;;;3489:51;;3477:2;3462:18;;3343:203::o;3551:160::-;3616:20;;3672:13;;3665:21;3655:32;;3645:60;;3701:1;3698;3691:12;3716:849;3825:6;3833;3841;3849;3857;3865;3918:3;3906:9;3897:7;3893:23;3889:33;3886:53;;;3935:1;3932;3925:12;3886:53;3974:9;3961:23;3993:39;4026:5;3993:39;:::i;:::-;4051:5;-1:-1:-1;4108:2:38;4093:18;;4080:32;4121:41;4080:32;4121:41;:::i;:::-;4181:7;-1:-1:-1;4207:35:38;4238:2;4223:18;;4207:35;:::i;:::-;4197:45;;4289:2;4278:9;4274:18;4261:32;4251:42;;4345:3;4334:9;4330:19;4317:33;4359:41;4392:7;4359:41;:::i;:::-;4419:7;-1:-1:-1;4478:3:38;4463:19;;4450:33;4492:41;4450:33;4492:41;:::i;:::-;4552:7;4542:17;;;3716:849;;;;;;;;:::o;4570:404::-;4638:6;4646;4699:2;4687:9;4678:7;4674:23;4670:32;4667:52;;;4715:1;4712;4705:12;4667:52;4754:9;4741:23;4773:39;4806:5;4773:39;:::i;:::-;4831:5;-1:-1:-1;4888:2:38;4873:18;;4860:32;4901:41;4860:32;4901:41;:::i;:::-;4961:7;4951:17;;;4570:404;;;;;:::o;5161:554::-;5241:6;5249;5257;5310:2;5298:9;5289:7;5285:23;5281:32;5278:52;;;5326:1;5323;5316:12;5278:52;5365:9;5352:23;5384:39;5417:5;5384:39;:::i;:::-;5442:5;-1:-1:-1;5498:2:38;5483:18;;5470:32;-1:-1:-1;;;;;5514:30:38;;5511:50;;;5557:1;5554;5547:12;5511:50;5596:59;5647:7;5638:6;5627:9;5623:22;5596:59;:::i;:::-;5161:554;;5674:8;;-1:-1:-1;5570:85:38;;-1:-1:-1;;;;5161:554:38:o;5720:180::-;5779:6;5832:2;5820:9;5811:7;5807:23;5803:32;5800:52;;;5848:1;5845;5838:12;5800:52;-1:-1:-1;5871:23:38;;5720:180;-1:-1:-1;5720:180:38:o;5905:255::-;5964:6;6017:2;6005:9;5996:7;5992:23;5988:32;5985:52;;;6033:1;6030;6023:12;5985:52;6072:9;6059:23;6091:39;6124:5;6091:39;:::i;6444:127::-;6505:10;6500:3;6496:20;6493:1;6486:31;6536:4;6533:1;6526:15;6560:4;6557:1;6550:15;6576:255;6648:2;6642:9;6690:6;6678:19;;-1:-1:-1;;;;;6712:34:38;;6748:22;;;6709:62;6706:88;;;6774:18;;:::i;:::-;6810:2;6803:22;6576:255;:::o;6836:::-;6908:2;6902:9;6950:6;6938:19;;-1:-1:-1;;;;;6972:34:38;;7008:22;;;6969:62;6966:88;;;7034:18;;:::i;7096:255::-;7168:2;7162:9;7210:6;7198:19;;-1:-1:-1;;;;;7232:34:38;;7268:22;;;7229:62;7226:88;;;7294:18;;:::i;7356:251::-;7428:2;7422:9;7470:2;7458:15;;-1:-1:-1;;;;;7488:34:38;;7524:22;;;7485:62;7482:88;;;7550:18;;:::i;7612:253::-;7684:2;7678:9;7726:4;7714:17;;-1:-1:-1;;;;;7746:34:38;;7782:22;;;7743:62;7740:88;;;7808:18;;:::i;7870:255::-;7942:2;7936:9;7984:6;7972:19;;-1:-1:-1;;;;;8006:34:38;;8042:22;;;8003:62;8000:88;;;8068:18;;:::i;8130:275::-;8201:2;8195:9;8266:2;8247:13;;-1:-1:-1;;8243:27:38;8231:40;;-1:-1:-1;;;;;8286:34:38;;8322:22;;;8283:62;8280:88;;;8348:18;;:::i;:::-;8384:2;8377:22;8130:275;;-1:-1:-1;8130:275:38:o;8410:187::-;8459:4;-1:-1:-1;;;;;8481:30:38;;8478:56;;;8514:18;;:::i;:::-;-1:-1:-1;8580:2:38;8559:15;-1:-1:-1;;8555:29:38;8586:4;8551:40;;8410:187::o;8602:464::-;8645:5;8698:3;8691:4;8683:6;8679:17;8675:27;8665:55;;8716:1;8713;8706:12;8665:55;8752:6;8739:20;8783:49;8799:32;8828:2;8799:32;:::i;:::-;8783:49;:::i;:::-;8857:2;8848:7;8841:19;8903:3;8896:4;8891:2;8883:6;8879:15;8875:26;8872:35;8869:55;;;8920:1;8917;8910:12;8869:55;8985:2;8978:4;8970:6;8966:17;8959:4;8950:7;8946:18;8933:55;9033:1;9008:16;;;9026:4;9004:27;8997:38;;;;9012:7;8602:464;-1:-1:-1;;;8602:464:38:o;9071:390::-;9149:6;9157;9210:2;9198:9;9189:7;9185:23;9181:32;9178:52;;;9226:1;9223;9216:12;9178:52;9253:23;;-1:-1:-1;;;;;9288:30:38;;9285:50;;;9331:1;9328;9321:12;9285:50;9354;9396:7;9387:6;9376:9;9372:22;9354:50;:::i;:::-;9344:60;9451:2;9436:18;;;;9423:32;;-1:-1:-1;;;;9071:390:38:o;9466:155::-;9546:20;;9595:1;9585:12;;9575:40;;9611:1;9608;9601:12;9626:173;9694:20;;-1:-1:-1;;;;;9743:31:38;;9733:42;;9723:70;;9789:1;9786;9779:12;9804:129;-1:-1:-1;;;;;9878:30:38;;9868:41;;9858:69;;9923:1;9920;9913:12;9938:132;10005:20;;10034:30;10005:20;10034:30;:::i;10075:1273::-;10163:6;10216:2;10204:9;10195:7;10191:23;10187:32;10184:52;;;10232:1;10229;10222:12;10184:52;10259:23;;-1:-1:-1;;;;;10331:14:38;;;10328:34;;;10358:1;10355;10348:12;10328:34;10381:22;;;;10437:6;10419:16;;;10415:29;10412:49;;;10457:1;10454;10447:12;10412:49;10483:22;;:::i;:::-;10528:34;10559:2;10528:34;:::i;:::-;10521:5;10514:49;10595:31;10622:2;10618;10614:11;10595:31;:::i;:::-;10590:2;10583:5;10579:14;10572:55;10659:31;10686:2;10682;10678:11;10659:31;:::i;:::-;10654:2;10647:5;10643:14;10636:55;10737:2;10733;10729:11;10716:25;10766:2;10756:8;10753:16;10750:36;;;10782:1;10779;10772:12;10750:36;10818:45;10855:7;10844:8;10840:2;10836:17;10818:45;:::i;:::-;10813:2;10806:5;10802:14;10795:69;;10897:32;10924:3;10920:2;10916:12;10897:32;:::i;:::-;10891:3;10884:5;10880:15;10873:57;10963:31;10989:3;10985:2;10981:12;10963:31;:::i;:::-;10957:3;10950:5;10946:15;10939:56;11028:31;11054:3;11050:2;11046:12;11028:31;:::i;:::-;11022:3;11015:5;11011:15;11004:56;11093:29;11117:3;11113:2;11109:12;11093:29;:::i;:::-;11087:3;11080:5;11076:15;11069:54;11142:3;11191:2;11187;11183:11;11170:25;11220:2;11210:8;11207:16;11204:36;;;11236:1;11233;11226:12;11204:36;11272:45;11309:7;11298:8;11294:2;11290:17;11272:45;:::i;:::-;11256:14;;;11249:69;;;;-1:-1:-1;11260:5:38;10075:1273;-1:-1:-1;;;;;10075:1273:38:o;11927:544::-;12006:6;12014;12022;12075:2;12063:9;12054:7;12050:23;12046:32;12043:52;;;12091:1;12088;12081:12;12043:52;12118:23;;-1:-1:-1;;;;;12153:30:38;;12150:50;;;12196:1;12193;12186:12;12150:50;12235:59;12286:7;12277:6;12266:9;12262:22;12235:59;:::i;:::-;12313:8;;-1:-1:-1;12209:85:38;-1:-1:-1;;12398:2:38;12383:18;;12370:32;12411:30;12370:32;12411:30;:::i;:::-;12460:5;12450:15;;;11927:544;;;;;:::o;12572:258::-;12644:1;12654:113;12668:6;12665:1;12662:13;12654:113;;;12744:11;;;12738:18;12725:11;;;12718:39;12690:2;12683:10;12654:113;;;12785:6;12782:1;12779:13;12776:48;;;12820:1;12811:6;12806:3;12802:16;12795:27;12776:48;;12572:258;;;:::o;12835:::-;12877:3;12915:5;12909:12;12942:6;12937:3;12930:19;12958:63;13014:6;13007:4;13002:3;12998:14;12991:4;12984:5;12980:16;12958:63;:::i;:::-;13075:2;13054:15;-1:-1:-1;;13050:29:38;13041:39;;;;13082:4;13037:50;;12835:258;-1:-1:-1;;12835:258:38:o;13178:102::-;-1:-1:-1;;;;;13243:30:38;13231:43;;13178:102::o;13285:762::-;13462:2;13451:9;13444:21;13521:6;13515:13;13508:21;13501:29;13496:2;13485:9;13481:18;13474:57;13612:1;13608;13603:3;13599:11;13595:19;13589:2;13581:6;13577:15;13571:22;13567:48;13562:2;13551:9;13547:18;13540:76;13425:4;13663:2;13655:6;13651:15;13645:22;13703:4;13698:2;13687:9;13683:18;13676:32;13731:52;13778:3;13767:9;13763:19;13749:12;13731:52;:::i;:::-;13717:66;;13848:4;13842:2;13834:6;13830:15;13824:22;13820:33;13814:3;13803:9;13799:19;13792:62;13923:3;13915:6;13911:16;13905:23;13898:31;13891:39;13885:3;13874:9;13870:19;13863:68;14014:1;14010;14006:2;14002:10;13998:18;13991:3;13983:6;13979:16;13973:23;13969:48;13962:4;13951:9;13947:20;13940:78;14035:6;14027:14;;;13285:762;;;;:::o;14052:150::-;14127:20;;14176:1;14166:12;;14156:40;;14192:1;14189;14182:12;14207:157;14289:20;;14338:1;14328:12;;14318:40;;14354:1;14351;14344:12;14369:1579;14421:5;14469:6;14457:9;14452:3;14448:19;14444:32;14441:52;;;14489:1;14486;14479:12;14441:52;14511:22;;:::i;:::-;14502:31;;14556:27;14573:9;14556:27;:::i;:::-;14549:5;14542:42;14616:38;14650:2;14639:9;14635:18;14616:38;:::i;:::-;14611:2;14604:5;14600:14;14593:62;14687:38;14721:2;14710:9;14706:18;14687:38;:::i;:::-;14682:2;14675:5;14671:14;14664:62;14758:45;14799:2;14788:9;14784:18;14758:45;:::i;:::-;14753:2;14746:5;14742:14;14735:69;14837:51;14883:3;14872:9;14868:19;14837:51;:::i;:::-;14831:3;14824:5;14820:15;14813:76;14922:38;14955:3;14944:9;14940:19;14922:38;:::i;:::-;14916:3;14909:5;14905:15;14898:63;14994:36;15025:3;15014:9;15010:19;14994:36;:::i;:::-;14988:3;14981:5;14977:15;14970:61;15064:38;15097:3;15086:9;15082:19;15064:38;:::i;:::-;15058:3;15051:5;15047:15;15040:63;15122:3;15157:37;15190:2;15179:9;15175:18;15157:37;:::i;:::-;15141:14;;;15134:61;15214:3;15253:18;;;15240:32;-1:-1:-1;;;;;15321:14:38;;;15318:34;;;15348:1;15345;15338:12;15318:34;15384:46;15426:3;15417:6;15406:9;15402:22;15384:46;:::i;:::-;15379:2;15372:5;15368:14;15361:70;15450:3;15440:13;;15485:52;15533:2;15522:9;15518:18;15485:52;:::i;:::-;15480:2;15473:5;15469:14;15462:76;15557:3;15547:13;;15592:52;15640:2;15629:9;15625:18;15592:52;:::i;:::-;15587:2;15580:5;15576:14;15569:76;15664:3;15654:13;;15699:37;15732:2;15721:9;15717:18;15699:37;:::i;:::-;15694:2;15687:5;15683:14;15676:61;15756:3;15746:13;;15812:2;15801:9;15797:18;15784:32;15768:48;;15841:2;15831:8;15828:16;15825:36;;;15857:1;15854;15847:12;15825:36;;15893:48;15937:3;15926:8;15915:9;15911:24;15893:48;:::i;:::-;15888:2;15881:5;15877:14;15870:72;;;14369:1579;;;;:::o;15953:1555::-;16014:5;16062:6;16050:9;16045:3;16041:19;16037:32;16034:52;;;16082:1;16079;16072:12;16034:52;16104:22;;:::i;:::-;16095:31;-1:-1:-1;16149:23:38;;-1:-1:-1;;;;;16221:14:38;;;16218:34;;;16248:1;16245;16238:12;16218:34;16275:46;16317:3;16308:6;16297:9;16293:22;16275:46;:::i;:::-;16268:5;16261:61;16375:2;16364:9;16360:18;16347:32;16331:48;;16404:2;16394:8;16391:16;16388:36;;;16420:1;16417;16410:12;16388:36;;16456:48;16500:3;16489:8;16478:9;16474:24;16456:48;:::i;:::-;16451:2;16444:5;16440:14;16433:72;;16537:38;16571:2;16560:9;16556:18;16537:38;:::i;:::-;16532:2;16525:5;16521:14;16514:62;16608:38;16642:2;16631:9;16627:18;16608:38;:::i;:::-;16603:2;16596:5;16592:14;16585:62;16680:38;16713:3;16702:9;16698:19;16680:38;:::i;:::-;16674:3;16667:5;16663:15;16656:63;16752:38;16785:3;16774:9;16770:19;16752:38;:::i;:::-;16746:3;16739:5;16735:15;16728:63;16824:38;16857:3;16846:9;16842:19;16824:38;:::i;:::-;16818:3;16811:5;16807:15;16800:63;16896:38;16929:3;16918:9;16914:19;16896:38;:::i;:::-;16890:3;16883:5;16879:15;16872:63;16954:3;16989:38;17023:2;17012:9;17008:18;16989:38;:::i;:::-;16973:14;;;16966:62;17047:3;17082:38;17101:18;;;17082:38;:::i;:::-;17066:14;;;17059:62;17140:3;17175:37;17193:18;;;17175:37;:::i;:::-;17159:14;;;17152:61;17232:3;17267:37;17285:18;;;17267:37;:::i;:::-;17251:14;;;17244:61;17324:3;17359:37;17377:18;;;17359:37;:::i;:::-;17343:14;;;17336:61;17416:3;17451:50;17482:18;;;17451:50;:::i;:::-;17435:14;;;17428:74;17439:5;15953:1555;-1:-1:-1;;15953:1555:38:o;17513:1351::-;17569:5;17617:6;17605:9;17600:3;17596:19;17592:32;17589:52;;;17637:1;17634;17627:12;17589:52;17659:22;;:::i;:::-;17650:31;-1:-1:-1;17704:23:38;;-1:-1:-1;;;;;17776:14:38;;;17773:34;;;17803:1;17800;17793:12;17773:34;17830:46;17872:3;17863:6;17852:9;17848:22;17830:46;:::i;:::-;17823:5;17816:61;17930:2;17919:9;17915:18;17902:32;17886:48;;17959:2;17949:8;17946:16;17943:36;;;17975:1;17972;17965:12;17943:36;;18011:48;18055:3;18044:8;18033:9;18029:24;18011:48;:::i;:::-;18006:2;17999:5;17995:14;17988:72;;18092:38;18126:2;18115:9;18111:18;18092:38;:::i;:::-;18087:2;18080:5;18076:14;18069:62;18163:38;18197:2;18186:9;18182:18;18163:38;:::i;:::-;18158:2;18151:5;18147:14;18140:62;18235:38;18268:3;18257:9;18253:19;18235:38;:::i;:::-;18229:3;18222:5;18218:15;18211:63;18307:38;18340:3;18329:9;18325:19;18307:38;:::i;:::-;18301:3;18294:5;18290:15;18283:63;18379:38;18412:3;18401:9;18397:19;18379:38;:::i;:::-;18373:3;18366:5;18362:15;18355:63;18451:38;18484:3;18473:9;18469:19;18451:38;:::i;:::-;18445:3;18438:5;18434:15;18427:63;18509:3;18544:37;18577:2;18566:9;18562:18;18544:37;:::i;:::-;18528:14;;;18521:61;18601:3;18636:37;18654:18;;;18636:37;:::i;:::-;18620:14;;;18613:61;18693:3;18728:37;18746:18;;;18728:37;:::i;:::-;18712:14;;;18705:61;18785:3;18820:37;18838:18;;;18820:37;:::i;18869:1380::-;19024:6;19032;19040;19093:2;19081:9;19072:7;19068:23;19064:32;19061:52;;;19109:1;19106;19099:12;19061:52;19136:23;;-1:-1:-1;;;;;19208:14:38;;;19205:34;;;19235:1;19232;19225:12;19205:34;19258:56;19306:7;19297:6;19286:9;19282:22;19258:56;:::i;:::-;19248:66;;19367:2;19356:9;19352:18;19339:32;19323:48;;19396:2;19386:8;19383:16;19380:36;;;19412:1;19409;19402:12;19380:36;19435:58;19485:7;19474:8;19463:9;19459:24;19435:58;:::i;:::-;19425:68;;19546:2;19535:9;19531:18;19518:32;19502:48;;19575:2;19565:8;19562:16;19559:36;;;19591:1;19588;19581:12;19559:36;19614:24;;;;19672:2;19654:16;;;19650:25;19647:45;;;19688:1;19685;19678:12;19647:45;19714:22;;:::i;:::-;19774:2;19761:16;19802:2;19792:8;19789:16;19786:36;;;19818:1;19815;19808:12;19786:36;19845:60;19897:7;19886:8;19882:2;19878:17;19845:60;:::i;:::-;19838:5;19831:75;;19952:2;19948;19944:11;19931:25;19981:2;19971:8;19968:16;19965:36;;;19997:1;19994;19987:12;19965:36;20033:55;20080:7;20069:8;20065:2;20061:17;20033:55;:::i;:::-;20028:2;20021:5;20017:14;20010:79;;20134:2;20130;20126:11;20113:25;20098:40;;20147:32;20171:7;20147:32;:::i;:::-;20211:7;20206:2;20199:5;20195:14;20188:31;20238:5;20228:15;;;;;18869:1380;;;;;:::o;20907:829::-;21020:6;21028;21036;21044;21052;21060;21068;21121:3;21109:9;21100:7;21096:23;21092:33;21089:53;;;21138:1;21135;21128:12;21089:53;21177:9;21164:23;21196:39;21229:5;21196:39;:::i;:::-;21254:5;-1:-1:-1;21311:2:38;21296:18;;21283:32;21324:41;21283:32;21324:41;:::i;:::-;21384:7;-1:-1:-1;21438:2:38;21423:18;;21410:32;;-1:-1:-1;21489:2:38;21474:18;;21461:32;;-1:-1:-1;21540:3:38;21525:19;;21512:33;;-1:-1:-1;21597:3:38;21582:19;;21569:33;21611:41;21569:33;21611:41;:::i;:::-;21671:7;21661:17;;;21725:3;21714:9;21710:19;21697:33;21687:43;;20907:829;;;;;;;;;;:::o;21741:821::-;21899:6;21907;21915;21968:2;21956:9;21947:7;21943:23;21939:32;21936:52;;;21984:1;21981;21974:12;21936:52;22011:23;;-1:-1:-1;;;;;22083:14:38;;;22080:34;;;22110:1;22107;22100:12;22080:34;22133:56;22181:7;22172:6;22161:9;22157:22;22133:56;:::i;:::-;22123:66;;22242:2;22231:9;22227:18;22214:32;22198:48;;22271:2;22261:8;22258:16;22255:36;;;22287:1;22284;22277:12;22255:36;22310:58;22360:7;22349:8;22338:9;22334:24;22310:58;:::i;:::-;22300:68;;22421:2;22410:9;22406:18;22393:32;22377:48;;22450:2;22440:8;22437:16;22434:36;;;22466:1;22463;22456:12;22434:36;;22489:67;22548:7;22537:8;22526:9;22522:24;22489:67;:::i;:::-;22479:77;;;21741:821;;;;;:::o;22567:560::-;22718:6;22726;22779:3;22767:9;22758:7;22754:23;22750:33;22747:53;;;22796:1;22793;22786:12;22747:53;22823:23;;-1:-1:-1;;;;;22858:30:38;;22855:50;;;22901:1;22898;22891:12;22855:50;22924:22;;22980:3;22962:16;;;22958:26;22955:46;;;22997:1;22994;22987:12;23861:186;23920:6;23973:2;23961:9;23952:7;23948:23;23944:32;23941:52;;;23989:1;23986;23979:12;23941:52;24012:29;24031:9;24012:29;:::i;24052:323::-;24120:6;24128;24181:2;24169:9;24160:7;24156:23;24152:32;24149:52;;;24197:1;24194;24187:12;24149:52;24236:9;24223:23;24255:39;24288:5;24255:39;:::i;:::-;24313:5;24365:2;24350:18;;;;24337:32;;-1:-1:-1;;;24052:323:38:o;24380:898::-;24502:6;24510;24518;24526;24534;24542;24550;24558;24611:3;24599:9;24590:7;24586:23;24582:33;24579:53;;;24628:1;24625;24618:12;24579:53;24667:9;24654:23;24686:39;24719:5;24686:39;:::i;:::-;24744:5;-1:-1:-1;24801:2:38;24786:18;;24773:32;24814:41;24773:32;24814:41;:::i;:::-;24874:7;-1:-1:-1;24928:2:38;24913:18;;24900:32;;-1:-1:-1;24979:2:38;24964:18;;24951:32;;-1:-1:-1;25030:3:38;25015:19;;25002:33;;-1:-1:-1;25082:3:38;25067:19;;25054:33;;-1:-1:-1;25139:3:38;25124:19;;25111:33;25153:41;25111:33;25153:41;:::i;:::-;25213:7;25203:17;;;25267:3;25256:9;25252:19;25239:33;25229:43;;24380:898;;;;;;;;;;;:::o;25518:591::-;25638:6;25646;25699:2;25687:9;25678:7;25674:23;25670:32;25667:52;;;25715:1;25712;25705:12;25667:52;25742:23;;-1:-1:-1;;;;;25814:14:38;;;25811:34;;;25841:1;25838;25831:12;25811:34;25864:56;25912:7;25903:6;25892:9;25888:22;25864:56;:::i;:::-;25854:66;;25973:2;25962:9;25958:18;25945:32;25929:48;;26002:2;25992:8;25989:16;25986:36;;;26018:1;26015;26008:12;25986:36;;26041:62;26095:7;26084:8;26073:9;26069:24;26041:62;:::i;:::-;26031:72;;;25518:591;;;;;:::o;26114:344::-;26316:2;26298:21;;;26355:2;26335:18;;;26328:30;-1:-1:-1;;;26389:2:38;26374:18;;26367:50;26449:2;26434:18;;26114:344::o;26463:267::-;26552:6;26547:3;26540:19;26604:6;26597:5;26590:4;26585:3;26581:14;26568:43;-1:-1:-1;26656:1:38;26631:16;;;26649:4;26627:27;;;26620:38;;;;26712:2;26691:15;;;-1:-1:-1;;26687:29:38;26678:39;;;26674:50;;26463:267::o;26735:543::-;27004:25;;;-1:-1:-1;;;;;27065:32:38;;27060:2;27045:18;;27038:60;27134:3;27129:2;27114:18;;27107:31;;;-1:-1:-1;;27155:63:38;;27198:19;;27190:6;27182;27155:63;:::i;:::-;27147:71;;27266:4;27258:6;27254:17;27249:2;27238:9;27234:18;27227:45;26735:543;;;;;;;;:::o;27283:348::-;27485:2;27467:21;;;27524:2;27504:18;;;27497:30;-1:-1:-1;;;27558:2:38;27543:18;;27536:54;27622:2;27607:18;;27283:348::o;27636:127::-;27697:10;27692:3;27688:20;27685:1;27678:31;27728:4;27725:1;27718:15;27752:4;27749:1;27742:15;27768:131;27867:1;27860:5;27857:12;27847:46;;27873:18;;:::i;27904:166::-;27982:55;28031:5;27982:55;:::i;:::-;28046:18;;27904:166::o;28075:104::-;-1:-1:-1;;;;;28141:31:38;28129:44;;28075:104::o;28184:503::-;28242:5;28249:6;28309:3;28296:17;28395:2;28391:7;28380:8;28364:14;28360:29;28356:43;28336:18;28332:68;28322:96;;28414:1;28411;28404:12;28322:96;28442:33;;28546:4;28533:18;;;-1:-1:-1;28494:21:38;;-1:-1:-1;;;;;;28563:30:38;;28560:50;;;28606:1;28603;28596:12;28560:50;28656:6;28640:14;28636:27;28626:8;28622:42;28619:62;;;28677:1;28674;28667:12;28692:1139;28807:5;28794:19;28822:41;28855:7;28822:41;:::i;:::-;-1:-1:-1;;;;;28922:16:38;;;28910:29;;28987:4;28976:16;;28963:30;;29002:41;28963:30;29002:41;:::i;:::-;29075:16;29068:4;29059:14;;29052:40;29140:4;29129:16;;29116:30;29155:32;29116:30;29155:32;:::i;:::-;-1:-1:-1;;;;;29256:16:38;;;29214:2;29240:14;;29233:40;29321:4;29310:16;;29297:30;;29336:32;29297:30;29336:32;:::i;:::-;29400:16;;;29393:4;29384:14;;29377:40;29465:4;29454:16;;29441:30;;29480:32;29441:30;29480:32;:::i;:::-;29544:16;;;29537:4;29528:14;;29521:40;29609:4;29598:16;;29585:30;;29624:32;29585:30;29624:32;:::i;:::-;29688:16;29681:4;29672:14;;29665:40;29734:35;29763:4;29752:16;;29734:35;:::i;:::-;29778:47;29819:4;29814:3;29810:14;29796:12;29778:47;:::i;29836:2497::-;30382:4;30411:3;30441:6;30430:9;30423:25;30484:2;30479;30468:9;30464:18;30457:30;30496:62;30554:2;30543:9;30539:18;30513:24;30530:6;30513:24;:::i;:::-;13165:4;13154:16;13142:29;;13098:75;30496:62;;30587:47;30630:2;30622:6;30618:15;30587:47;:::i;:::-;30643:77;30715:3;30704:9;30700:19;30686:12;30643:77;:::i;:::-;;30751:37;30782:4;30774:6;30770:17;30751:37;:::i;:::-;30797:55;30847:3;30836:9;30832:19;30816:14;30797:55;:::i;:::-;;30883:37;30914:4;30906:6;30902:17;30883:37;:::i;:::-;30929:55;30979:3;30968:9;30964:19;30948:14;30929:55;:::i;:::-;;31015:37;31046:4;31038:6;31034:17;31015:37;:::i;:::-;31061:55;31111:3;31100:9;31096:19;31080:14;31061:55;:::i;:::-;;31147:37;31178:4;31170:6;31166:17;31147:37;:::i;:::-;31193:55;31243:3;31232:9;31228:19;31212:14;31193:55;:::i;:::-;;31310:4;31302:6;31298:17;31285:31;31279:3;31268:9;31264:19;31257:60;31379:4;31371:6;31367:17;31354:31;31348:3;31337:9;31333:19;31326:60;31448:6;31440;31436:19;31423:33;31417:3;31406:9;31402:19;31395:62;31476:6;31513:35;31544:2;31536:6;31532:15;31513:35;:::i;:::-;31557:55;31607:3;31596:9;31592:19;31576:14;31557:55;:::i;:::-;;31631:6;31699:2;31691:6;31687:15;31674:29;31668:3;31657:9;31653:19;31646:58;31723:6;31774:55;31825:2;31817:6;31813:15;31805:6;31774:55;:::i;:::-;31848:6;31891:2;31885:3;31874:9;31870:19;31863:31;31911:77;31983:3;31972:9;31968:19;31954:12;31938:14;31911:77;:::i;:::-;31903:85;;31997:81;32072:4;32061:9;32057:20;32049:6;31997:81;:::i;:::-;12546:13;;12539:21;32111:18;;;12527:34;32139:46;32181:2;32170:9;32166:18;32158:6;32139:46;:::i;:::-;32194;32236:2;32225:9;32221:18;32213:6;32194:46;:::i;:::-;32276:6;32271:2;32260:9;32256:18;32249:34;;;;;;;32320:6;32314:3;32303:9;32299:19;32292:35;29836:2497;;;;;;;;;;;:::o;32338:245::-;32396:6;32449:2;32437:9;32428:7;32424:23;32420:32;32417:52;;;32465:1;32462;32455:12;32417:52;32504:9;32491:23;32523:30;32547:5;32523:30;:::i;33251:337::-;33453:2;33435:21;;;33492:2;33472:18;;;33465:30;-1:-1:-1;;;33526:2:38;33511:18;;33504:43;33579:2;33564:18;;33251:337::o;33593:1961::-;34008:6;33997:9;33990:25;34051:3;34046:2;34035:9;34031:18;34024:31;34064:52;34111:3;34100:9;34096:19;34087:6;34081:13;13165:4;13154:16;13142:29;;13098:75;34064:52;33971:4;34163:2;34155:6;34151:15;34145:22;34176:77;34248:3;34237:9;34233:19;34219:12;34176:77;:::i;:::-;;34302:4;34294:6;34290:17;34284:24;34317:55;34367:3;34356:9;34352:19;34336:14;34317:55;:::i;:::-;;34421:4;34413:6;34409:17;34403:24;34446:3;34458:54;34508:2;34497:9;34493:18;34477:14;34458:54;:::i;:::-;34561:4;34553:6;34549:17;34543:24;34521:46;;34586:3;34598:54;34648:2;34637:9;34633:18;34617:14;34598:54;:::i;:::-;34701:3;34693:6;34689:16;34683:23;34661:45;;34725:3;34737:54;34787:2;34776:9;34772:18;34756:14;34737:54;:::i;:::-;34828:3;34820:6;34816:16;34810:23;34800:33;;34852:3;34891:2;34886;34875:9;34871:18;34864:30;34931:3;34923:6;34919:16;34913:23;34903:33;;34955:6;34997:2;34992;34981:9;34977:18;34970:30;35055:2;35047:6;35043:15;35037:22;35031:3;35020:9;35016:19;35009:51;35109:2;35101:6;35097:15;35091:22;35069:44;;35122:55;35172:3;35161:9;35157:19;35141:14;35122:55;:::i;:::-;35220:15;;;35214:22;35208:3;35193:19;;35186:51;35274:15;;35268:22;35321:3;35306:19;;35299:31;;;;35268:22;-1:-1:-1;35347:54:38;;-1:-1:-1;;35396:3:38;35381:19;;35268:22;35347:54;:::i;:::-;35339:62;;;35410:48;35452:4;35441:9;35437:20;35429:6;35410:48;:::i;:::-;35489:4;35474:20;;35467:36;;;;35534:4;35519:20;35512:36;33593:1961;;-1:-1:-1;;;33593:1961:38:o;35559:430::-;35613:5;35666:3;35659:4;35651:6;35647:17;35643:27;35633:55;;35684:1;35681;35674:12;35633:55;35713:6;35707:13;35744:49;35760:32;35789:2;35760:32;:::i;35744:49::-;35818:2;35809:7;35802:19;35864:3;35857:4;35852:2;35844:6;35840:15;35836:26;35833:35;35830:55;;;35881:1;35878;35871:12;35830:55;35894:64;35955:2;35948:4;35939:7;35935:18;35928:4;35920:6;35916:17;35894:64;:::i;:::-;35976:7;35559:430;-1:-1:-1;;;;35559:430:38:o;35994:136::-;36072:13;;36094:30;36072:13;36094:30;:::i;36135:1071::-;36253:6;36306:2;36294:9;36285:7;36281:23;36277:32;36274:52;;;36322:1;36319;36312:12;36274:52;36349:16;;-1:-1:-1;;;;;36414:14:38;;;36411:34;;;36441:1;36438;36431:12;36411:34;36464:22;;;;36520:4;36502:16;;;36498:27;36495:47;;;36538:1;36535;36528:12;36495:47;36564:22;;:::i;:::-;36616:2;36610:9;36628:41;36661:7;36628:41;:::i;:::-;36678:22;;36739:2;36731:11;;36725:18;36755:16;;;36752:36;;;36784:1;36781;36774:12;36752:36;36820:56;36868:7;36857:8;36853:2;36849:17;36820:56;:::i;:::-;36815:2;36808:5;36804:14;36797:80;;36915:2;36911;36907:11;36901:18;36886:33;;36928:32;36952:7;36928:32;:::i;:::-;36992:7;36987:2;36980:5;36976:14;36969:31;37038:2;37034;37030:11;37024:18;37009:33;;37051:32;37075:7;37051:32;:::i;:::-;37115:7;37110:2;37103:5;37099:14;37092:31;37170:3;37166:2;37162:12;37156:19;37150:3;37143:5;37139:15;37132:44;37195:5;37185:15;;;;;36135:1071;;;;:::o;37211:127::-;37272:10;37267:3;37263:20;37260:1;37253:31;37303:4;37300:1;37293:15;37327:4;37324:1;37317:15;37343:209;37381:3;-1:-1:-1;;;;;37451:14:38;;;37477:15;;;37474:41;;;37495:18;;:::i;:::-;37544:1;37531:15;;37343:209;-1:-1:-1;;;37343:209:38:o;37557:765::-;-1:-1:-1;;;;;37905:15:38;;;37887:34;;-1:-1:-1;;;;;37995:15:38;;;37990:2;37975:18;;37968:43;38047:15;;37868:2;38027:18;;38020:43;38099:3;38094:2;38079:18;;38072:31;;;37831:4;;37860:18;38120:46;;38146:19;;38138:6;38120:46;:::i;:::-;38203:15;;;38197:3;38182:19;;38175:44;38256:15;;;;38250:3;38235:19;;38228:44;38303:3;38288:19;38281:35;-1:-1:-1;38112:54:38;37557:765;-1:-1:-1;;;;37557:765:38:o;38327:761::-;-1:-1:-1;;;;;38734:15:38;;;38716:34;;38786:15;;;38781:2;38766:18;;38759:43;38838:15;;;38833:2;38818:18;;38811:43;38885:2;38870:18;;38863:34;;;;38928:3;38913:19;;38906:35;;;;38696:3;38957:19;;38950:35;39022:15;;;39016:3;39001:19;;38994:44;39069:3;39054:19;;39047:35;;;;38665:3;38650:19;;38327:761::o;40404:259::-;40474:6;40527:2;40515:9;40506:7;40502:23;40498:32;40495:52;;;40543:1;40540;40533:12;40495:52;40575:9;40569:16;40594:39;40627:5;40594:39;:::i;40668:502::-;40954:25;;;-1:-1:-1;;;;;41053:15:38;;;41048:2;41033:18;;41026:43;41105:15;;41100:2;41085:18;;41078:43;41152:2;41137:18;;41130:34;40941:3;40926:19;;40668:502::o;41175:184::-;41245:6;41298:2;41286:9;41277:7;41273:23;41269:32;41266:52;;;41314:1;41311;41304:12;41266:52;-1:-1:-1;41337:16:38;;41175:184;-1:-1:-1;41175:184:38:o;41364:543::-;41644:25;;;-1:-1:-1;;;;;41705:32:38;;41700:2;41685:18;;41678:60;41774:3;41769:2;41754:18;;41747:31;;;-1:-1:-1;;41795:63:38;;41838:19;;41830:6;41822;41795:63;:::i;:::-;41787:71;;41894:6;41889:2;41878:9;41874:18;41867:34;41364:543;;;;;;;;:::o;43028:339::-;43230:2;43212:21;;;43269:2;43249:18;;;43242:30;-1:-1:-1;;;43303:2:38;43288:18;;43281:45;43358:2;43343:18;;43028:339::o;43372:354::-;43574:2;43556:21;;;43613:2;43593:18;;;43586:30;43652:32;43647:2;43632:18;;43625:60;43717:2;43702:18;;43372:354::o;44090:349::-;44292:2;44274:21;;;44331:2;44311:18;;;44304:30;-1:-1:-1;;;44365:2:38;44350:18;;44343:55;44430:2;44415:18;;44090:349::o;45158:1879::-;45585:3;45574:9;45567:22;45598:78;45671:3;45660:9;45656:19;45647:6;45641:13;45598:78;:::i;:::-;45548:4;45723;45715:6;45711:17;45705:24;45738:53;45786:3;45775:9;45771:19;45757:12;45738:53;:::i;:::-;;45840:4;45832:6;45828:17;45822:24;45865:3;45877:54;45927:2;45916:9;45912:18;45896:14;45877:54;:::i;:::-;45980:4;45968:17;;45962:24;46005:6;46027:18;;;46020:30;45962:24;-1:-1:-1;46073:54:38;46122:3;46107:19;;45962:24;46073:54;:::i;:::-;46059:68;;46176:4;46168:6;46164:17;46158:24;46191:55;46241:3;46230:9;46226:19;46210:14;46191:55;:::i;:::-;;46295:4;46287:6;46283:17;46277:24;46310:54;46359:3;46348:9;46344:19;46328:14;46310:54;:::i;:::-;;46413:3;46405:6;46401:16;46395:23;46427:54;46476:3;46465:9;46461:19;46445:14;46427:54;:::i;:::-;-1:-1:-1;46530:3:38;46518:16;;46512:23;12546:13;12539:21;46591:3;46576:19;;12527:34;46633:15;;46627:22;46690;;;-1:-1:-1;;46686:37:38;46680:3;46665:19;;46658:66;46741:41;46690:22;46627;46741:41;:::i;:::-;46733:49;;;;46791:48;46833:4;46822:9;46818:20;46810:6;46791:48;:::i;:::-;46848;46890:4;46879:9;46875:20;46867:6;46848:48;:::i;:::-;46934:6;46927:4;46916:9;46912:20;46905:36;46979:6;46972:4;46961:9;46957:20;46950:36;47024:6;47017:4;47006:9;47002:20;46995:36;45158:1879;;;;;;;;;:::o;47042:667::-;47148:6;47156;47164;47172;47225:3;47213:9;47204:7;47200:23;47196:33;47193:53;;;47242:1;47239;47232:12;47193:53;47274:9;47268:16;47293:30;47317:5;47293:30;:::i;:::-;47387:2;47372:18;;47366:25;47436:2;47421:18;;47415:25;47342:5;;-1:-1:-1;47366:25:38;-1:-1:-1;47449:41:38;47415:25;47449:41;:::i;:::-;47560:2;47545:18;;47539:25;47509:7;;-1:-1:-1;;;;;;47576:30:38;;47573:50;;;47619:1;47616;47609:12;47573:50;47642:61;47695:7;47686:6;47675:9;47671:22;47642:61;:::i;:::-;47632:71;;;47042:667;;;;;;;:::o;47714:694::-;-1:-1:-1;;;;;48045:15:38;;;48027:34;;48097:15;;48092:2;48077:18;;48070:43;48149:3;48144:2;48129:18;;48122:31;;;47970:4;;48170:46;;48196:19;;48188:6;48170:46;:::i;:::-;-1:-1:-1;;;;;48289:15:38;;;48284:2;48269:18;;48262:43;48342:15;;;;48336:3;48321:19;;48314:44;48389:3;48374:19;48367:35;48162:54;47714:694;-1:-1:-1;;;;47714:694:38:o;48413:344::-;48615:2;48597:21;;;48654:2;48634:18;;;48627:30;-1:-1:-1;;;48688:2:38;48673:18;;48666:50;48748:2;48733:18;;48413:344::o;48762:346::-;48964:2;48946:21;;;49003:2;48983:18;;;48976:30;-1:-1:-1;;;49037:2:38;49022:18;;49015:52;49099:2;49084:18;;48762:346::o;49518:463::-;49763:25;;;-1:-1:-1;;;;;49824:32:38;;49819:2;49804:18;;49797:60;49893:2;49888;49873:18;;49866:30;;;-1:-1:-1;;49913:62:38;;49956:18;;49948:6;49940;49913:62;:::i;:::-;49905:70;49518:463;-1:-1:-1;;;;;;49518:463:38:o;49986:359::-;-1:-1:-1;;;;;50188:32:38;;50170:51;;50257:2;50252;50237:18;;50230:30;;;-1:-1:-1;;50277:62:38;;50320:18;;50312:6;50304;50277:62;:::i;:::-;50269:70;49986:359;-1:-1:-1;;;;;49986:359:38:o;50350:349::-;50552:2;50534:21;;;50591:2;50571:18;;;50564:30;-1:-1:-1;;;50625:2:38;50610:18;;50603:55;50690:2;50675:18;;50350:349::o;50704:350::-;50906:2;50888:21;;;50945:2;50925:18;;;50918:30;-1:-1:-1;;;50979:2:38;50964:18;;50957:56;51045:2;51030:18;;50704:350::o;51059:140::-;51140:1;51133:5;51130:12;51120:46;;51146:18;;:::i;51204:147::-;51292:1;51285:5;51282:12;51272:46;;51298:18;;:::i;51356:1868::-;51464:12;;13165:4;13154:16;13142:29;;51404:3;51432:6;51528:4;51521:5;51517:16;51511:23;51543:48;51585:4;51580:3;51576:14;51562:12;51543:48;:::i;:::-;;51639:4;51632:5;51628:16;51622:23;51654:50;51698:4;51693:3;51689:14;51673;51654:50;:::i;:::-;;51752:4;51745:5;51741:16;51735:23;51767:57;51818:4;51813:3;51809:14;51793;51767:57;:::i;:::-;;51872:4;51865:5;51861:16;51855:23;51887:74;51955:4;51950:3;51946:14;51930;51887:74;:::i;:::-;;52009:4;52002:5;51998:16;51992:23;52024:49;52067:4;52062:3;52058:14;52042;52024:49;:::i;:::-;;52121:4;52114:5;52110:16;52104:23;52136:47;52177:4;52172:3;52168:14;52152;12546:13;12539:21;12527:34;;12476:91;52136:47;;52231:4;52224:5;52220:16;52214:23;52246:49;52289:4;52284:3;52280:14;52264;52246:49;:::i;:::-;;52314:6;52368:2;52361:5;52357:14;52351:21;52381:47;52424:2;52419:3;52415:12;52399:14;52381:47;:::i;:::-;;;52447:6;52501:2;52494:5;52490:14;52484:21;52535:2;52530;52525:3;52521:12;52514:24;52559:47;52602:2;52597:3;52593:12;52577:14;52559:47;:::i;:::-;52547:59;;;;52625:6;52679:2;52672:5;52668:14;52662:21;52692:62;52750:2;52745:3;52741:12;52725:14;52692:62;:::i;:::-;;;52773:6;52828:2;52821:5;52817:14;52811:21;52841:63;52900:2;52895:3;52891:12;52874:15;52841:63;:::i;:::-;;;52923:6;52978:2;52971:5;52967:14;52961:21;52991:48;53035:2;53030:3;53026:12;53009:15;52991:48;:::i;:::-;;;53058:6;53113:2;53106:5;53102:14;53096:21;53157:3;53151:4;53147:14;53142:2;53137:3;53133:12;53126:36;53178:40;53213:4;53196:15;53178:40;:::i;53229:1920::-;53286:3;53314:6;53355:5;53349:12;53382:2;53377:3;53370:15;53406:45;53447:2;53442:3;53438:12;53424;53406:45;:::i;:::-;53394:57;;;53499:4;53492:5;53488:16;53482:23;53547:3;53541:4;53537:14;53530:4;53525:3;53521:14;53514:38;53575:39;53609:4;53593:14;53575:39;:::i;:::-;53561:53;;;53662:4;53655:5;53651:16;53645:23;53677:50;53721:4;53716:3;53712:14;53696;53677:50;:::i;:::-;;53775:4;53768:5;53764:16;53758:23;53790:50;53834:4;53829:3;53825:14;53809;53790:50;:::i;:::-;;53888:4;53881:5;53877:16;53871:23;53903:49;53946:4;53941:3;53937:14;53921;53903:49;:::i;:::-;;54000:4;53993:5;53989:16;53983:23;54015:49;54058:4;54053:3;54049:14;54033;54015:49;:::i;:::-;;54112:4;54105:5;54101:16;54095:23;54127:49;54170:4;54165:3;54161:14;54145;54127:49;:::i;:::-;;54224:4;54217:5;54213:16;54207:23;54239:49;54282:4;54277:3;54273:14;54257;54239:49;:::i;:::-;;54307:6;54361:2;54354:5;54350:14;54344:21;54374:48;54418:2;54413:3;54409:12;54393:14;54374:48;:::i;:::-;;;54441:6;54495:2;54488:5;54484:14;54478:21;54508:48;54552:2;54547:3;54543:12;54527:14;54508:48;:::i;:::-;;;54575:6;54630:2;54623:5;54619:14;54613:21;54643:48;54687:2;54682:3;54678:12;54661:15;54643:48;:::i;:::-;;;54710:6;54765:2;54758:5;54754:14;54748:21;54778:48;54822:2;54817:3;54813:12;54796:15;54778:48;:::i;:::-;;;54845:6;54900:2;54893:5;54889:14;54883:21;54913:48;54957:2;54952:3;54948:12;54931:15;54913:48;:::i;:::-;;;54980:6;55035:2;55028:5;55024:14;55018:21;55048:73;55117:2;55112:3;55108:12;55091:15;55048:73;:::i;:::-;-1:-1:-1;55137:6:38;;53229:1920;-1:-1:-1;;;;53229:1920:38:o;55154:1618::-;55206:3;55234:6;55275:5;55269:12;55302:2;55297:3;55290:15;55326:45;55367:2;55362:3;55358:12;55344;55326:45;:::i;:::-;55314:57;;;55419:4;55412:5;55408:16;55402:23;55467:3;55461:4;55457:14;55450:4;55445:3;55441:14;55434:38;55495:39;55529:4;55513:14;55495:39;:::i;:::-;55481:53;;;55582:4;55575:5;55571:16;55565:23;55597:50;55641:4;55636:3;55632:14;55616;55597:50;:::i;:::-;;55695:4;55688:5;55684:16;55678:23;55710:50;55754:4;55749:3;55745:14;55729;55710:50;:::i;:::-;;55808:4;55801:5;55797:16;55791:23;55823:49;55866:4;55861:3;55857:14;55841;55823:49;:::i;:::-;;55920:4;55913:5;55909:16;55903:23;55935:49;55978:4;55973:3;55969:14;55953;55935:49;:::i;:::-;;56032:4;56025:5;56021:16;56015:23;56047:49;56090:4;56085:3;56081:14;56065;56047:49;:::i;:::-;;56144:4;56137:5;56133:16;56127:23;56159:49;56202:4;56197:3;56193:14;56177;56159:49;:::i;:::-;;56227:6;56281:2;56274:5;56270:14;56264:21;56294:47;56337:2;56332:3;56328:12;56312:14;56294:47;:::i;:::-;;;56360:6;56414:2;56407:5;56403:14;56397:21;56427:47;56470:2;56465:3;56461:12;56445:14;56427:47;:::i;:::-;;;56493:6;56548:2;56541:5;56537:14;56531:21;56561:48;56605:2;56600:3;56596:12;56579:15;56561:48;:::i;:::-;;;56628:6;56683:2;56676:5;56672:14;56666:21;56696:48;56740:2;56735:3;56731:12;56714:15;56696:48;:::i;56777:1763::-;57454:4;57483:3;57513:2;57502:9;57495:21;57539:51;57586:2;57575:9;57571:18;57563:6;57539:51;:::i;:::-;57525:65;;57638:9;57630:6;57626:22;57621:2;57610:9;57606:18;57599:50;57672:39;57704:6;57696;57672:39;:::i;:::-;57658:53;;57759:9;57751:6;57747:22;57742:2;57731:9;57727:18;57720:50;57805:6;57799:13;57836:4;57828:6;57821:20;57864:65;57923:4;57915:6;57911:17;57897:12;57864:65;:::i;:::-;57850:79;;57978:2;57970:6;57966:15;57960:22;58027:6;58019;58015:19;58010:2;58002:6;57998:15;57991:44;58058:51;58102:6;58086:14;58058:51;:::i;:::-;58178:2;58152:15;;;58146:22;-1:-1:-1;;;;;58142:47:38;58125:15;;58118:72;;;;-1:-1:-1;58044:65:38;;-1:-1:-1;58222:48:38;;-1:-1:-1;58264:4:38;58249:20;;58241:6;58222:48;:::i;:::-;58307:6;58301:3;58290:9;58286:19;58279:35;58351:6;58345:3;58334:9;58330:19;58323:35;58395:6;58389:3;58378:9;58374:19;58367:35;58439:6;58433:3;58422:9;58418:19;58411:35;58483:6;58477:3;58466:9;58462:19;58455:35;58527:6;58521:3;58510:9;58506:19;58499:35;56777:1763;;;;;;;;;;;;;:::o;58545:236::-;58584:3;-1:-1:-1;;;;;58650:10:38;;;58680;;;58710:12;;;58702:21;;58699:47;;;58726:18;;:::i;:::-;58762:13;;58545:236;-1:-1:-1;;;;58545:236:38:o;58786:1236::-;-1:-1:-1;;;;;59296:15:38;;;59278:34;;59348:15;;59343:2;59328:18;;59321:43;59228:3;59395:2;59380:18;;59373:30;;;59199:4;;59426:45;59452:18;;;59444:6;59426:45;:::i;:::-;59412:59;;59519:9;59511:6;59507:22;59502:2;59491:9;59487:18;59480:50;59547:33;59573:6;59565;59547:33;:::i;:::-;-1:-1:-1;;;;;59654:15:38;;;59648:3;59633:19;;59626:44;59707:15;;;59701:3;59686:19;;59679:44;59760:15;;;59754:3;59739:19;;59732:44;59813:15;;;59807:3;59792:19;;59785:44;59866:15;;;59860:3;59845:19;;59838:44;59919:15;;59913:3;59898:19;;59891:44;59539:41;-1:-1:-1;59944:72:38;;-1:-1:-1;60011:3:38;59996:19;;59987:7;59944:72;:::i;:::-;58786:1236;;;;;;;;;;;;;;:::o;60557:249::-;60626:6;60679:2;60667:9;60658:7;60654:23;60650:32;60647:52;;;60695:1;60692;60685:12;60647:52;60727:9;60721:16;60746:30;60770:5;60746:30;:::i;62321:312::-;-1:-1:-1;;;;;62559:15:38;;;62541:34;;62611:15;;62606:2;62591:18;;62584:43;62491:2;62476:18;;62321:312::o;62638:1309::-;63271:4;63300:3;63330:2;63319:9;63312:21;63356:51;63403:2;63392:9;63388:18;63380:6;63356:51;:::i;:::-;63342:65;;63455:9;63447:6;63443:22;63438:2;63427:9;63423:18;63416:50;63489:39;63521:6;63513;63489:39;:::i;:::-;63475:53;;63576:9;63568:6;63564:22;63559:2;63548:9;63544:18;63537:50;63604:48;63645:6;63637;63604:48;:::i;:::-;-1:-1:-1;;;;;63688:32:38;;;;63683:2;63668:18;;63661:60;-1:-1:-1;;63752:3:38;63737:19;;63730:35;;;;63708:3;63781:19;;63774:35;;;;63840:3;63825:19;;63818:35;;;;63884:3;63869:19;;63862:35;63928:3;63913:19;;;63906:35;63596:56;62638:1309;-1:-1:-1;;;62638:1309:38:o;63952:927::-;-1:-1:-1;;;;;64335:15:38;;;64317:34;;64387:15;;64382:2;64367:18;;64360:43;64439:3;64434:2;64419:18;;64412:31;;;64260:4;;64466:46;;64492:19;;64484:6;64466:46;:::i;:::-;64560:9;64552:6;64548:22;64543:2;64532:9;64528:18;64521:50;64588:33;64614:6;64606;64588:33;:::i;:::-;-1:-1:-1;;;;;64695:15:38;;;64689:3;64674:19;;64667:44;64748:15;;64742:3;64727:19;;64720:44;64580:41;-1:-1:-1;64773:56:38;;-1:-1:-1;64822:6:38;64773:56;:::i;:::-;64866:6;64860:3;64849:9;64845:19;64838:35;63952:927;;;;;;;;;;:::o;65957:2371::-;66407:4;66436:3;66466:6;66455:9;66448:25;66509:2;66504;66493:9;66489:18;66482:30;66521:62;66579:2;66568:9;66564:18;66538:24;66555:6;66538:24;:::i;66521:62::-;66612:47;66655:2;66647:6;66643:15;66612:47;:::i;:::-;66678:6;66693:76;66765:2;66754:9;66750:18;66736:12;66693:76;:::i;:::-;66800:37;66831:4;66823:6;66819:17;66800:37;:::i;:::-;66778:59;;66846:55;66896:3;66885:9;66881:19;66865:14;66846:55;:::i;:::-;66932:37;66963:4;66955:6;66951:17;66932:37;:::i;:::-;66910:59;;66978:55;67028:3;67017:9;67013:19;66997:14;66978:55;:::i;:::-;67064:37;67095:4;67087:6;67083:17;67064:37;:::i;:::-;67042:59;;67110:55;67160:3;67149:9;67145:19;67129:14;67110:55;:::i;:::-;67196:37;67227:4;67219:6;67215:17;67196:37;:::i;:::-;67174:59;;67242:55;67292:3;67281:9;67277:19;67261:14;67242:55;:::i;:::-;67359:4;67351:6;67347:17;67334:31;67328:3;67317:9;67313:19;67306:60;67428:4;67420:6;67416:17;67403:31;67397:3;67386:9;67382:19;67375:60;67497:6;67489;67485:19;67472:33;67466:3;67455:9;67451:19;67444:62;67525:6;67515:16;;67593:2;67585:6;67581:15;67568:29;67562:3;67551:9;67547:19;67540:58;67617:6;67654:35;67685:2;67677:6;67673:15;67654:35;:::i;:::-;67698:55;67748:3;67737:9;67733:19;67717:14;67698:55;:::i;:::-;;67772:6;67840:2;67832:6;67828:15;67815:29;67809:3;67798:9;67794:19;67787:58;67890:55;67941:2;67933:6;67929:15;67921:6;67890:55;:::i;:::-;67854:91;;67982:2;67976:3;67965:9;67961:19;67954:31;68002:77;68074:3;68063:9;68059:19;68045:12;68029:14;68002:77;:::i;:::-;67994:85;;;68088:81;68163:4;68152:9;68148:20;68140:6;68088:81;:::i;:::-;68178:46;68220:2;68209:9;68205:18;68197:6;68178:46;:::i;:::-;68233;68275:2;68264:9;68260:18;68252:6;68233:46;:::i;:::-;68315:6;68310:2;68299:9;68295:18;68288:34;;;;;;65957:2371;;;;;;;;;:::o;69300:381::-;69377:6;69385;69438:2;69426:9;69417:7;69413:23;69409:32;69406:52;;;69454:1;69451;69444:12;69406:52;69486:9;69480:16;69505:30;69529:5;69505:30;:::i;:::-;69604:2;69589:18;;69583:25;69554:5;;-1:-1:-1;69617:32:38;69583:25;69617:32;:::i;70686:310::-;70764:6;70772;70825:2;70813:9;70804:7;70800:23;70796:32;70793:52;;;70841:1;70838;70831:12;70793:52;70873:9;70867:16;70892:30;70916:5;70892:30;:::i;:::-;70986:2;70971:18;;;;70965:25;70941:5;;70965:25;;-1:-1:-1;;;70686:310:38:o;71883:2065::-;72300:6;72289:9;72282:25;72343:3;72338:2;72327:9;72323:18;72316:31;72356:52;72403:3;72392:9;72388:19;72379:6;72373:13;13165:4;13154:16;13142:29;;13098:75;72356:52;72263:4;72455:2;72447:6;72443:15;72437:22;72468:77;72540:3;72529:9;72525:19;72511:12;72468:77;:::i;:::-;;72594:4;72586:6;72582:17;72576:24;72609:55;72659:3;72648:9;72644:19;72628:14;72609:55;:::i;:::-;;72713:4;72705:6;72701:17;72695:24;72738:3;72750:54;72800:2;72789:9;72785:18;72769:14;72750:54;:::i;:::-;72853:4;72845:6;72841:17;72835:24;72813:46;;72878:3;72890:54;72940:2;72929:9;72925:18;72909:14;72890:54;:::i;:::-;72993:3;72985:6;72981:16;72975:23;72953:45;;73017:3;73029:54;73079:2;73068:9;73064:18;73048:14;73029:54;:::i;:::-;73120:3;73108:16;;73102:23;73144:3;73163:18;;;73156:30;;;;73223:3;73211:16;;73205:23;73247:3;73266:18;;;73259:30;;;;73314:15;;;73308:22;73349:6;73371:18;;;73364:30;;;;73437:15;;;73431:22;73425:3;73410:19;;73403:51;73491:15;;;73485:22;;-1:-1:-1;73349:6:38;73516:55;73566:3;73551:19;;73485:22;73516:55;:::i;:::-;73626:2;73618:6;73614:15;73608:22;73602:3;73591:9;73587:19;73580:51;73680:2;73672:6;73668:15;73662:22;73640:44;;;;;;73721:2;73715:3;73704:9;73700:19;73693:31;;73741:54;73790:3;73779:9;73775:19;73759:14;73741:54;:::i;73953:1226::-;74072:6;74125:2;74113:9;74104:7;74100:23;74096:32;74093:52;;;74141:1;74138;74131:12;74093:52;74168:16;;-1:-1:-1;;;;;74233:14:38;;;74230:34;;;74260:1;74257;74250:12;74230:34;74283:22;;;;74339:6;74321:16;;;74317:29;74314:49;;;74359:1;74356;74349:12;74314:49;74385:22;;:::i;:::-;74438:2;74432:9;74466:2;74456:8;74453:16;74450:36;;;74482:1;74479;74472:12;74450:36;74509:56;74557:7;74546:8;74542:2;74538:17;74509:56;:::i;:::-;74502:5;74495:71;;74598:41;74635:2;74631;74627:11;74598:41;:::i;:::-;74593:2;74586:5;74582:14;74575:65;74672:41;74709:2;74705;74701:11;74672:41;:::i;:::-;74667:2;74660:5;74656:14;74649:65;74760:2;74756;74752:11;74746:18;74741:2;74734:5;74730:14;74723:42;74804:3;74800:2;74796:12;74790:19;74834:2;74824:8;74821:16;74818:36;;;74850:1;74847;74840:12;74818:36;74887:56;74935:7;74924:8;74920:2;74916:17;74887:56;:::i;:::-;74881:3;74874:5;74870:15;74863:81;;74977:42;75014:3;75010:2;75006:12;74977:42;:::i;:::-;74971:3;74964:5;74960:15;74953:67;75053:42;75090:3;75086:2;75082:12;75053:42;:::i;:::-;75047:3;75040:5;75036:15;75029:67;75143:3;75139:2;75135:12;75129:19;75123:3;75116:5;75112:15;75105:44;75168:5;75158:15;;;;;73953:1226;;;;:::o;75184:825::-;-1:-1:-1;;;;;75611:15:38;;;75593:34;;75663:15;;;75658:2;75643:18;;75636:43;75715:15;;;75710:2;75695:18;;75688:43;75762:2;75747:18;;75740:34;;;;75805:3;75790:19;;75783:35;;;;75573:3;75834:19;;75827:35;75893:3;75878:19;;75871:35;75943:15;;;75937:3;75922:19;;75915:44;75990:3;75975:19;;75968:35;;;;75542:3;75527:19;;75184:825::o;76364:128::-;76404:3;76435:1;76431:6;76428:1;76425:13;76422:39;;;76441:18;;:::i;:::-;-1:-1:-1;76477:9:38;;76364:128::o;77132:1190::-;77729:4;77758:3;77788:2;77777:9;77770:21;77814:51;77861:2;77850:9;77846:18;77838:6;77814:51;:::i;:::-;77800:65;;77913:9;77905:6;77901:22;77896:2;77885:9;77881:18;77874:50;77941:43;77977:6;77969;77941:43;:::i;:::-;-1:-1:-1;;;;;78020:32:38;;;;78015:2;78000:18;;77993:60;-1:-1:-1;;78084:2:38;78069:18;;78062:34;;;;78127:3;78112:19;;78105:35;;;;78040:3;78156:19;;78149:35;;;;78215:3;78200:19;;78193:35;78259:3;78244:19;;78237:35;78303:3;78288:19;;;78281:35;77933:51;77132:1190;-1:-1:-1;;77132:1190:38:o;78327:825::-;-1:-1:-1;;;;;78644:32:38;;78626:51;;78713:3;78708:2;78693:18;;78686:31;;;-1:-1:-1;;78740:46:38;;78766:19;;78758:6;78740:46;:::i;:::-;78834:9;78826:6;78822:22;78817:2;78806:9;78802:18;78795:50;78862:33;78888:6;78880;78862:33;:::i;:::-;-1:-1:-1;;;;;78968:15:38;;;78963:2;78948:18;;78941:43;79021:15;;79015:3;79000:19;;78993:44;78854:41;-1:-1:-1;79046:56:38;;-1:-1:-1;79095:6:38;79046:56;:::i;:::-;79139:6;79133:3;79122:9;79118:19;79111:35;78327:825;;;;;;;;;:::o;79157:270::-;79196:7;-1:-1:-1;;;;;79266:10:38;;;79296;;;79329:11;;79322:19;79351:12;;;79343:21;;79318:47;79315:73;;;79368:18;;:::i;:::-;79408:13;;79157:270;-1:-1:-1;;;;79157:270:38:o;79432:380::-;79511:1;79507:12;;;;79554;;;79575:61;;79629:4;79621:6;79617:17;79607:27;;79575:61;79682:2;79674:6;79671:14;79651:18;79648:38;79645:161;;;79728:10;79723:3;79719:20;79716:1;79709:31;79763:4;79760:1;79753:15;79791:4;79788:1;79781:15;79817:276;79948:3;79986:6;79980:13;80002:53;80048:6;80043:3;80036:4;80028:6;80024:17;80002:53;:::i;:::-;80071:16;;;;;79817:276;-1:-1:-1;;79817:276:38:o;80098:127::-;80159:10;80154:3;80150:20;80147:1;80140:31;80190:4;80187:1;80180:15;80214:4;80211:1;80204:15;80230:175;80267:3;80311:4;80304:5;80300:16;80340:4;80331:7;80328:17;80325:43;;;80348:18;;:::i;:::-;80397:1;80384:15;;80230:175;-1:-1:-1;;80230:175:38:o;81160:1160::-;81469:4;81515:1;81511;81506:3;81502:11;81498:19;81556:2;81548:6;81544:15;81533:9;81526:34;81596:3;81591:2;81580:9;81576:18;81569:31;81657:6;81651:13;81644:21;81637:29;81631:3;81620:9;81616:19;81609:58;81733:2;81727;81719:6;81715:15;81709:22;81705:31;81698:4;81687:9;81683:20;81676:61;;81784:4;81776:6;81772:17;81766:24;81827:4;81821:3;81810:9;81806:19;81799:33;81855:52;81902:3;81891:9;81887:19;81873:12;81855:52;:::i;:::-;81966:4;81954:17;;;81948:24;81974:4;81944:35;81938:3;81923:19;;81916:64;82049:4;82037:17;;82031:24;82024:32;82017:40;82011:3;81996:19;;81989:69;82117:3;82105:16;;82099:23;-1:-1:-1;;;;;82095:48:38;82089:3;82074:19;;82067:77;82132:2;82183:20;;82176:36;;;81841:66;;-1:-1:-1;82221:48:38;;-1:-1:-1;82248:20:38;;82240:6;82221:48;:::i;:::-;82307:6;82300:4;82289:9;82285:20;82278:36;81160:1160;;;;;;;;:::o;82325:442::-;82411:6;82419;82427;82480:2;82468:9;82459:7;82455:23;82451:32;82448:52;;;82496:1;82493;82486:12;82448:52;82528:9;82522:16;82547:30;82571:5;82547:30;:::i;:::-;82646:2;82631:18;;82625:25;82596:5;;-1:-1:-1;82659:32:38;82625:25;82659:32;:::i;:::-;82710:7;82700:17;;;82757:2;82746:9;82742:18;82736:25;82726:35;;82325:442;;;;;:::o

Swarm Source

ipfs://bba20503b4dea47ea42298ace14db144e178106234090b5bba685b59f05a50ae
Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.