From 6402bee276da583dd8767a4659fc359d68ac475b Mon Sep 17 00:00:00 2001 From: h2physics Date: Wed, 22 Nov 2023 17:44:46 +0700 Subject: [PATCH] add more safety check on Factory Validator and SwapRouting Redeemer of Pool Validation, add AMM V2 documentation and add code comments --- amm-v2-docs/amm-v2-specs.md | 340 ++++++++++++++++++++++++++++ lib/amm_dex_v2/order_validation.ak | 41 ++-- lib/amm_dex_v2/pool_validation.ak | 153 +++++++------ lib/amm_dex_v2/types.ak | 87 ++++++- lib/amm_dex_v2/utils.ak | 8 +- plutus.json | 8 +- validators/authen_minting_policy.ak | 14 +- validators/factory_validator.ak | 102 +++++++-- validators/order_validator.ak | 13 +- validators/pool_validator.ak | 43 ++-- 10 files changed, 664 insertions(+), 145 deletions(-) create mode 100644 amm-v2-docs/amm-v2-specs.md diff --git a/amm-v2-docs/amm-v2-specs.md b/amm-v2-docs/amm-v2-specs.md new file mode 100644 index 0000000..ce8a61f --- /dev/null +++ b/amm-v2-docs/amm-v2-specs.md @@ -0,0 +1,340 @@ +# Minswap AMM V2 Specification + +## 1. Overview + +- Minswap AMM V2 uses Constant Product Formula (x * y = k). This formula, most simply expressed as x * y = k, states that trades must not change the product (k) of a pair’s reserve balances (x and y). Because k remains unchanged from the reference frame of a trade, it is often referred to as the invariant. This formula has the desirable property that larger trades (relative to reserves) execute at exponentially worse rates than smaller ones. +- The AMM V2 uses Batching architecture to solve concurrency on Cardano. Each user action will create an "Order" and "Batcher" will look through them and apply them into a "Liquidity Pool". A valid "Batcher" is a wallet which contains Minswap's License Token. Batching transaction is permissioned and only is triggered by "Batcher". + +## 2. Architecture + +There're 5 contracts in the AMM V2 system: + +- Order Contract: represents "User Action", contains necessary funds and is waiting to be applied into a Pool +- Order Batching Contract: verify the representation of Liquidity Pool while spending Order Contract in Batching transaction +- Pool Contract: a.k.a Liquidity Pool, which holds all User's assets for trading. +- Factory Contract: verify the correctness of Pool Creation. Each Factory UTxO is an element of a Factory `Linked List` +- Authen Minting Policy: is responsible for creating initial Factory `Linked List`, minting legitimate Factory, Liquidity Pool and Liquidity Pool `Share` Tokens + +## 3. Specification + +### 3.1 Actors + +- User: An entity who wants to interact with Liquidity Pool to deposit/withdraw liquidity or swap. The only requirement of users is that they must not be the same as batcher (because of how we filter UTxOs) +- Batcher: An entity who aggregate order UTxOs from users and match them with liquidity pool UTxO. A batcher must hold a batcher's license token. The license token must not be expired and the expired time must be between current time and Maximum Deadline (to prevent minting license with infinity deadline). +- Admin (aka Minswap team): An entity who has permission to update Liquidity Pool's fee, withdraw fee sharing and change the pool's stake address. An Admin must hold admin's license token. + +### 3.2 Tokens + +- Factory NFT Token: the Factory legitimate Token, can be only minted in Pool Creation Transaction and cannot be outside Factory Contract. The minting must be followed by rule of `Authen Minting Policy` + - CurrencySymbol: Authen Minting Policy + - TokenName: Defined in `Authen Minting Policy` parameters (e.g. "MS") +- Pool NFT token: the Pool legitimate Token, can be only minted in Pool Creation Transaction and cannot be outside Pool Contract. The minting must be followed by rule of `Authen Minting Policy` + - CurrencySymbol: Authen Minting Policy + - TokenName: Defined in `Authen Minting Policy` parameters (e.g. "MSP") +- LP token: Represents Liquidity Provider's share of pool. Each pool has different LP token. + - CurrencySymbol: Authen Minting Policy + - TokenName: Hash of Pool's Asset A and Asset B (`SHA_256(SHA_256(AssetA), SHA_256(AssetB))`) +- Batcher license token: Permit batcher to apply pool + - CurrencySymbol: Defined in Pool parameters. The policy is managed by team (e.g. multisig policy) + - TokenName: POSIX timestamp represents license deadline +- Admin license token: + - CurrencySymbol: Defined in Pool parameters. The policy is managed by team (e.g. multisig policy) + - TokenName: A constant string defined in pool parameters (e.g. "ADMIN") + +### 3.3 Smart Contract + +#### 3.3.1 Order Batching Validator + +Order Batching validator is a Withdrawal Script, is responsible for validating Pool Representation in the Transaction Inputs. This validator will help reduce `Order Validator` cost in Batching Transaction. + +#### 3.3.1.1 Parameter + +- _pool_hash_: the hash of Liquidity Pool Script + +#### 3.3.1.2 Redeemer + +- **OrderBatchingRedeemer**: + - _pool_input_index_: Index of Pool UTxO in Transaction Inputs. + +#### 3.3.1.3 Validation + +- **OrderBatchingRedeemer**: The redeemer contains `pool_input_index`, it's used for finding Pool Input faster, it will be called on Batching Transaction. + - validate that there's a Pool Input which have Address's Payment Credential matching with `pool_hash` + +#### 3.3.2 Order Validator + +Order validator is responsible for holding "User Requests" funds and details about what users want to do with the liquidity pool. An order can only be applied to the liquidity pool by Batcher or cancelled by User's payment signature / Script Owner Representation (in case Owner is a Smart Contract) + +#### 3.3.2.1 Parameter + +- _stake_credential_: the Stake Credential of `Order Batching Validator` + +#### 3.3.2.2 Datum + +There are 10 order types: + +- **SwapExactIn**: is used for exchanging specific amount of single asset in the liquidity pool, the order will be executed if the received amount is greater than or equal to `minimum_receive` which is defined below + - _direction_: The direction (AToB or BToA) of swap request. + - _minimum_receive_: Minimum amount of Asset Out which users want to receive after exchanging +- **StopLoss**: is used for exchanging specific amount of single asset in the liquidity pool, the order will be executed if the received amount is less than or equal to `stop_loss_receive` which is defined below + - _direction_: The direction (AToB or BToA) of swap request. + - _stop_loss_receive_: Maximum amount of Asset Out which users want to receive after exchanging +- **OCO**: is used for exchanging specific amount of single asset in the liquidity pool, the order will be executed if the received amount is less than or equal to `stop_loss_receive` and greater than or equal to `minimum_receive` which are defined below + - _direction_: The direction (AToB or BToA) of swap request. + - _minimum_receive_: Minimum amount of Asset Out which users want to receive after exchanging + - _stop_loss_receive_: Maximum amount of Asset Out which users want to receive after exchanging +- **SwapExactOut**: is used for exchanging single asset in the liquidity pool and receiving the exact amout of other asset, the order will be executed if the received amount is equal to `expected_receive` which is defined below + - _direction_: The direction (AToB or BToA) of swap request. + - _expected_receive_: The exact amount of Asset Out which users want to receive after exchanging +- **Deposit**: is used for depositing pool's assets and receiving LP Token + - _minimum_lp_: The minimum amount of LP Token which users want to receive after depositing +- **Withdraw**: is used for withdrawing pool's asset with the exact assets ratio of the liquidity pool at that time + - _minimum_asset_a_: minimum received amounts of Asset A. + - _minimum_asset_b_: minimum received amounts of Asset B. +- **ZapOut**: is used for withdrawing a single pool asset out of Liquidity Pool. + - _direction_: The direction (AToB or BToA) of ZapOut request. `AToB` in case Asset Out is B and vice versa + - _minimum_receive_: Minimum amount of Asset Out which users want to receive after withdrawing +- **PartialSwap**: is used for exchange partial amount of single Asset. The Partial Swap can be executed multiple time if the price ratio is matched with user's expectation, and the time is defined in `hops`. + - _direction_: The direction (AToB or BToA) of swap request. + - _io_ratio_numerator_ and _io_ratio_denominator_: the price ratio which users want to exchange + - _hops_: The time PartialSwap can be executed. + - _minimum_swap_amount_required_: The minimum amount which is required to swap per each execution time. +- **WithdrawImbalance**: is used for withdrawing custom amount of assets. + - _ratio_asset_a_ and _ratio_asset_b_: The ratio of Asset A and Asset B users want to receive after withdrawing + - _minimum_asset_a_: The minimum amount of asset A which users want to receive, The amount of Asset will be followed by the ratio (_received_asset_b_ = _minimum_asset_a_ * _ratio_asset_b_ / _ratio_asset_a_) +- **SwapMultiRouting**: is used for exchanging specific amount of single asset across multiple Liquidity Pools. + - _routings_: The routings (including a list of _direction_ and _lp_asset_), which is defined Liquidity Pools the swap is routing through + - _minimum_receive_: Minimum amount of Asset Out which users want to receive after exchanging + +An Order Datum keeps information about Order Type and some other informations: + +- _sender_: The address of order's creator, only sender can cancel the order +- _receiver_: The address which receives the funds after order is processed +- _receiver_datum_hash_: (optional) the datum hash of the output after order is processed. +- _lp_asset_: The Liquidity Pool's LP Asset that the order will be applied to +- _step_: The information about Order Type which we mentioned above +- _batcher_fee_: The fee users have to pay to Batcher to execute batching transaction +- _output_ada_: As known as Minimum ADA which users need to put to the Order, and these amounts will be returned with _receiver_ Output + +#### 3.3.2.3 Redeemer + +- **ApplyOrder** +- **CancelOrder** + +#### 3.3.2.4 Validation + +- **ApplyOrder**: the redeemer will allow spending Order UTxO in Batching transaction + - validate that an Order can be spent if there's a `Order Batching` validator in the `withdrawals` +- **CancelOrder**: the redeemer will allow _sender_ to spend Order UTxO to get back locked funds. + - validate that the transaction has _sender_'s signature or _sender_ script UTxO in the Transaction Inputs + +### 3.3.3 Authen Minting Policy + +Authen Minting Policy is responsible for creating initial Factory `Linked List`, minting legitimate Factory, Liquidity Pool and Liquidity Pool `Share` Tokens + +#### 3.3.3.1 Parameter + +- _out_ref_: is a Reference of an Unspent Transaction Output, which will only be spent on `MintFactoryAuthen` redeemer to make sure this redeemer can only be called once +- _factory_auth_asset_name_: the legitimate Factory TokenName +- _pool_auth_asset_name_: the legitimate Pool TokenName + +#### 3.3.3.2 Redeemer +- **MintFactoryAuthen** +- **CreatePool** + +#### 3.3.3.3 Validation + +- **MintFactoryAuthen**: The redeemer can be called once to initialize the whole AMM V2 system + - validate that `out_ref` must be presented in the Transaction Inputs + - validate that there's only 1 Factory UTxO in the Transaction Outputs. The Factory UTxO must contain Factory Token in the value and its datum is: + - _head_: `#"00"` + - _tail_: `#"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"` + - validate that the redeemer only mint **a single Factory Token** +- **CreatePool**: The redeemer will allow creating a new Liquidity Pool. + - validate that there's a single Factory UTxO in the Transaction Inputs. Factory UTxO must contain Factory NFT Token in the value + - validate that transaction only mint 3 types of tokens: + - 1 Factory NFT Token + - 1 Pool NFT Token + - MAX_INT64 LP Token, LP Token must have PolicyID is **AuthenMintingPolicy** and TokenName is Hash of Pool's Asset A and Asset B (`SHA_256(SHA_256(AssetA), SHA_256(AssetB))`). Asset A and Asset B are in Factory Redeemer and they must be sorted + +### 3.3.4 Factory Validator + +Factory Validator is responsible for creating non-duplicated Liquidity Pool. Each Factory UTxO is an element of Factory `Linked List`, contains a head and tail which are existing Pool's LP Asset Token Name except `#"00"` and `#"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"` aka intial head and tail + +The Linked List structure is +(`#"00"`, tail 0) + +(head 1, tail 1), tail 0 == head 1 + +(head 2, tail 2), tail 1 == head 2 + +... + +(head n-1, tail n-1) + +(head n, `#"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"`) + +Anytime new Pool is created, a Factory UTxO will be spent can create the 2 new ones, which is followed by: + +- (old head, old tail) -> (old head, Pool LP Token Name) and (Pool LP Token Name, old tail) + +- old head < Pool LP Token Name < old tail + +#### 3.3.4.1 Parameter + +- _authen_policy_id_: The PolicyID of `Authen Minting Policy` +- _pool_hash_: ValidatorHash of Pool Contract +- _order_hash_: ValidationHash of Order Contract +- _factory_auth_asset_name_: the legitimate Factory TokenName +- _pool_auth_asset_name_: the legitimate Pool TokenName + +#### 3.3.4.2 Datum +- _head_: The Head of Factory `LinkedList`` element +- _tail_: The Head of Factory `LinkedList`` element + +#### 3.3.4.3 Redeemer + - **Factory Redeemer**: + - _asset_a_: Asset A of new Liquidity Pool + - _asset_b_: Asset B of new Liquidity Pool + +#### 3.3.4.4 Validation +- **Factory Redeemer**: + - validate that Asset A and Asset B must be sorted + - validate that there's single Factory UTxO in Transaction Input and contain single legitimate Factory NFT Token + - validate that there are only 2 Factory UTxOs in Transaction Outputs, they must contain single legitimate Factory NFT Token. + - validate that new Factory UTxO datum must be followed by Linked List rule + - validate that there is a new Pool UTxO in Transaction Outputs. Pool UTxO must contain single Pool NFT Token + - Pool Datum must have correct data: + - _asset_a_ and _asset_b_ must be the same with Factory Redeemer + - _total_liquidity_ must be sqrt(_amount_a_ * _amount_b_) + - _reserve_a_ and _reserve_b_ must be _amount_a_ and _amount_b_ + - _trading_fee_percentage_ must be between **0.05%** and **10%** + - _order_hash_ must be the same with parameter + - _profit_sharing_ must be empty + - Pool Value must only have necessary Token: Asset A, Asset B, remaining LP Token (_MAX_INT64_ - _total_liquidity_), 1 Pool NFT Token and 3 ADA (required ADA for an UTxO) + - validate that transaction only mint 3 types of tokens: + - 1 Factory NFT Token + - 1 Pool NFT Token + - MAX_INT64 LP Token, LP Token must have PolicyID is **AuthenMintingPolicy** and TokenName is Hash of Pool's Asset A and Asset B (`SHA_256(SHA_256(AssetA), SHA_256(AssetB))`). Asset A and Asset B are in Factory Redeemer and they must be sorted + +### 3.3.5 Pool Validator + +Pool validator is the most important part in the system. It's responsible for guaranteeing that Orders must be processed in the correct way and Liquidity Providers' funds cannot be stolen in any way. + +#### 3.3.5.1 Parameter + +- _authen_policy_id_: The PolicyID of `Authen Minting Policy` +- _license_policy_id_: is the policy ID managed by the Minswap team, used for minting Batcher License and Admin License assets +- _pool_auth_asset_name_: the legitimate Pool TokenName +- _admin_asset_name_: The TokenName of Admin Asset, determined by Minswap team +- _maximum_deadline_range_: is the maximum expiration time of Batcher license from now (to prevent minting infinity license) + +#### 3.3.5.2 Datum +- _asset_a_: The Pool's Asset A +- _asset_b_: The Pool's Asset B +- _total_liquidity_: Total Share of Liquidity Providers +- _reserve_a_: Asset A's balance of Liquidity Providers +- _reserve_b_: Asset B's balance of Liquidity Providers +- _trading_fee_numerator_: Numerator of Trading Fee +- _trading_fee_denominator_: Denominator of Trading Fee +- _order_hash_: ValidatorHash of Order Contract +- _profit_sharing_opt_: (Optional) Numerator and Denominator of Profit Sharing percentage, this is percentage of Trading Fee. (eg, Trading Fee is 3%, Profit Sharing is 1/6 -> Profit Sharing = 1/6 * 3%) + +#### 3.3.5.3 Redeemer + - **Batching**: + - _batcher_address_: Address of Batcher + - _input_indexes_: The Indexes of Orders are processing (it will be explained below) + - _license_index_: Index of the UTxO holding Batcher License Token in the Transaction Inputs. + - **MultiRouting**: + - _batcher_address_: Address of Batcher + - _license_index_: Index of the UTxO holding Batcher License Token in the Transaction Inputs. + - _routing_in_indexes_: Indexes of Pool UTxOs in the Transaction Inputs + - _routing_out_indexes_: Indexes of Pool UTxOs in the Transaction Outputs + - **UpdatePoolFeeOrStakeCredential**: + - _action_: There are 2 actions in this redeemer. + - _UpdatePoolFee_: Allow Admin update Liquidity Pool's fee (Trading Fee and Profit Sharing). + - _UpdatePoolStakeCredential_: Allow Admin update Pool's Stake Credential. It allows Minswap can delegate Liquidity Pool's ADA to different Stake Pools + - _admin_index_: Index of the UTxO holding Admin License Token in the Transaction Inputs. + - **WithdrawLiquidityShare**: + - _admin_index_: Index of the UTxO holding Admin License Token in the Transaction Inputs. + +#### 3.3.5.4 Validation +- **Batching**: This redeemer will be called on Batching Transaction. It can process all types of Orders except **SwapMultiRouting** Order + - validate batcher with valid License Token must be presented in Transaction Inputs: + - Batcher must sign Batching transaction. + - A valid license token is the token having expired timestamp as TokenName and must be within current time and current time + _maximum_deadline_range_ + - validate _input_indexes_ must not be empty and be unique list + - validate Transaction won't mint any assets + - validate there is a single Pool UTxO in both Transaction Inputs and Outputs and must have the same Address (both Payment and Stake Credential) + - validate all fields in Pool Datum must not be changed except _total_liquidity_, _reserve_a_ and _reserve_b_ + - validate Pool Input / Output Value contain necessary assets: + - 3 ADA + - Asset A + - Asset B + - LP Token + - Pool NFT Token + - validate the Pool State (_datum_reserve_a_, _datum_reserve_b_, _value_reserve_a_, _value_reserve_b_, _total_liquidity_) must be the same with the calculated amount after applying through all orders: + - The validator will loop through the list of batch inputs and outputs and validate each one, as well as calculate the final state of the pool. + - Important note that order inputs are sorted lexicographically due to Cardano ledger's design, so the Batcher will pre-calculate correct order inputs indexes, pass through the redeemer (_input_indexes_) and validator will sort the Order Inputs with the indexes to make sure that Orders will be processed with FIFO ordering + - Each order must validate: + - All amount fields in Order Step must be positive + - _batcher_fee_ and _output_ada_ must be positive + - _lp_asset_ in **OrderDatum** must be the same with processing Liquidity Pool + - Order Output must be returned to _receiver_ and might have _receiver_datum_hash_opt_ + - TODO: Write a doc to explain Order Calculation formula +- **MultiRouting**: This redeemer will be called on MultiRouting Transaction. It will process single **SwapMultiRouting** Order and multiple **Liquidity Pool** + - validate batcher with valid License Token must be presented in Transaction Inputs: + - Batcher must sign Batching transaction. + - A valid license token is the token having expired timestamp as TokenName and must be within current time and current time + _maximum_deadline_range_ + - validate _routing_in_indexes_ and _routing_out_indexes_ must be unique, have the same length and contain more than 1 element. + - validate Transaction won't mint any assets + - validate the number of Pool Inputs and Pool Outputs must be _routing_in_indexes_ length. and each Pool Input must have the same Address with Pool Output (both Payment and Stake Credential) + - validate all fields in each Pool Datum must not be changed except _reserve_a_ and _reserve_b_ + - validate each Pool Input / Output Value contain necessary assets: + - 3 ADA + - Asset A + - Asset B + - LP Token + - Pool NFT Token + - validate transaction must have single **SwapMultiRouting** Order and: + - All amount fields in Order Step must be positive + - _batcher_fee_ and _output_ada_ must be positive + - _lp_asset_ in **OrderDatum** must be the same with LP Asset of first Liquidity Pool in routing list + - Order Output must be returned to _receiver_ and might having _receiver_datum_hash_opt_ + - The number of Pool Inputs and Pool Outputs must be the same with _routings_ length + - Calculated Asset Out must be returned to _receiver_ + - TODO: Write a doc to explain Order Calculation formula +- **UpdatePoolFeeOrStakeCredential**: Allow Admin update Liquidity Pool's fee (Trading Fee and Profit Sharing) or update Pool's Stake Credential. It allows Minswap can delegate Liquidity Pool's ADA to different Stake Pools + - validate Admin with valid Admin License Token must be presented in Transaction Inputs + - validate there is a single Pool UTxO in Transaction Inputs and single Pool UTxO in Transaction Outputs and: + - Pool Input contains 1 valid Pool NFT Token + - Pool Input and Output Value must be unchanged + - Transaction contain only 1 Script (Pool Script). It will avoid bad Admin can steal money from Order Contract. + - validate Transaction won't mint any assets + - Both **UpdatePoolFeeOrStakeCredential** _action_ must not change these fields on the Pool Datum: + - _asset_a_ + - _asset_b_ + - _total_liquidity_ + - _reserve_a_ + - _reseve_b_ + - _order_hash_ + - Each _action_ must be followed: + - _UpdatePoolFee_: + - Trading Fee must be between **0.05%** and **10%** + - Profit Sharing can be on/off by setting _profit_sharing_opt_ is None or Some. Profit Sharing must be between **16.66%** and **50%** + - Pool Address must be unchanged (both Payment and Stake Credential) + - _UpdatePoolStakeCredential_: + - Trading Fee and Profit Sharing must be unchanged + - Pool's Stake Credential can be changed to any other Stake Address +- **WithdrawLiquidityShare**: Allow Admin can withdraw Liquidity Share to any Addresss. + - validate Admin with valid Admin License Token must be presented in Transaction Inputs. + - validate there is a single Pool UTxO in Transaction Inputs and single Pool UTxO in Transaction Outputs and: + - Pool Input contains 1 valid Pool NFT Token + - Pool Input and Output Address must be unchanged (both Payment and Stake Credential) + - Pool Datum must be unchanged + - Transaction contain only 1 Script (Pool Script). It will avoid bad Admin can steal money from Order Contract. + - validate Transaction won't mint any assets + - validate Admin withdraws the exact earned Profit Sharing amount: + - Earned Asset A: Reserve A in Value - Reserve A in Datum + - Earned Asset B: Reserve B in Value - Reserve B in Datum + - validate Pool Out Value must be Pool In Value sub Earned Asset A and Earned Asset B diff --git a/lib/amm_dex_v2/order_validation.ak b/lib/amm_dex_v2/order_validation.ak index e084a14..2f4fbaf 100644 --- a/lib/amm_dex_v2/order_validation.ak +++ b/lib/amm_dex_v2/order_validation.ak @@ -677,9 +677,9 @@ pub fn validate_swap_multi_routing_order( batcher_fee: Int, output_ada: Int, ) -> Bool { - let first_routing = utils.list_at_index(routings, 0) + let first_routing = routings |> builtin.head_list let last_routing = utils.list_at_index(routings, list.length(routings) - 1) - let first_pool = utils.list_at_index(pools, 0) + let first_pool = pools |> builtin.head_list let last_pool = utils.list_at_index(pools, list.length(pools) - 1) let SwapRouting { direction: first_routing_direction, .. } = first_routing let SwapRouting { direction: last_routing_direction, .. } = last_routing @@ -723,23 +723,14 @@ pub fn validate_swap_multi_routing_order( all_pools: pools, all_routings: routings, ) - expect amount_out >= minimum_receive - let actual_amount_out = - value.quantity_of( - order_out_value, - asset_out_policy_id, - asset_out_asset_name, - ) - let is_valid_amount_out = - if utils.is_ada_asset(asset_out_policy_id, asset_out_asset_name) { - amount_out + output_ada == actual_amount_out - } else { - let ada_amount = - value.quantity_of(order_out_value, ada_policy_id, ada_asset_name) - amount_out == actual_amount_out && ada_amount == output_ada - } - expect is_valid_amount_out - True + let expect_order_value_out = + value.zero() + |> value.add(ada_policy_id, ada_asset_name, output_ada) + |> value.add(asset_out_policy_id, asset_out_asset_name, amount_out) + and { + amount_out >= minimum_receive, + expect_order_value_out == order_out_value, + } } pub fn validate_order_receiver( @@ -766,7 +757,6 @@ pub fn validate_order_receiver( } } -// TODO: validate order input value size pub fn apply_orders( datum_map: DatumMap, asset_a: Asset, @@ -810,8 +800,10 @@ pub fn apply_orders( lp_asset: order_lp_asset, } = order_in_datum expect and { + // batcher_fee and output_ada must be positive batcher_fee > 0, output_ada > 0, + // lp_asset must be the same with processing Liquidity Pool lp_asset == order_lp_asset, } let new_state = @@ -819,6 +811,7 @@ pub fn apply_orders( SwapExactIn(direction, minimum_receive) -> { expect and { minimum_receive > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -850,6 +843,7 @@ pub fn apply_orders( StopLoss(direction, stop_loss_receive) -> { expect and { stop_loss_receive > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -882,6 +876,7 @@ pub fn apply_orders( expect and { minimum_receive > 0, stop_loss_receive > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -913,6 +908,7 @@ pub fn apply_orders( SwapExactOut(direction, expected_receive) -> { expect and { expected_receive > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -942,6 +938,7 @@ pub fn apply_orders( Deposit(minimum_lp) -> { expect and { minimum_lp > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -967,6 +964,7 @@ pub fn apply_orders( expect and { minimum_asset_a > 0, minimum_asset_b > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -988,6 +986,7 @@ pub fn apply_orders( ZapOut(direction, minimum_receive) -> { expect and { minimum_receive > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -1103,6 +1102,7 @@ pub fn apply_orders( minimum_swap_amount_required == order_out_minimum_swap_amount_required, } } else { + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, @@ -1122,6 +1122,7 @@ pub fn apply_orders( ratio_asset_a > 0, ratio_asset_b > 0, minimum_asset_a > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt validate_order_receiver( receiver: receiver, receiver_datum_hash_opt: receiver_datum_hash_opt, diff --git a/lib/amm_dex_v2/pool_validation.ak b/lib/amm_dex_v2/pool_validation.ak index 431211e..bf31a19 100644 --- a/lib/amm_dex_v2/pool_validation.ak +++ b/lib/amm_dex_v2/pool_validation.ak @@ -1,3 +1,4 @@ +use aiken/builtin use aiken/dict.{Dict} use aiken/interval.{Finite, Interval, IntervalBound} use aiken/list @@ -160,13 +161,6 @@ fn get_batching_pool( let lp_asset = Asset { policy_id: authen_policy_id, asset_name: lp_asset_name } - // Each Pool UTxO has a Pool Authen Asset (authen_policy_id + pool_auth_asset_name) - // This asset must be only stay in Pool UTxO - // Verify Pool Authen Asset must be existed in Pool Input and Output value - // expect and { - // value.quantity_of(pool_in_value, authen_policy_id, pool_auth_asset_name) == 1, - // value.quantity_of(pool_out_value, authen_policy_id, pool_auth_asset_name) == 1, - // } let estimate_value_reserve_a_in = value.quantity_of(pool_in_value, asset_a_policy_id, asset_a_asset_name) let estimate_value_reserve_a_out = @@ -356,9 +350,9 @@ pub fn validate_swap_multi_routing( address: Address { payment_credential: pool_payment_credential, .. }, lp_asset: pool_lp_asset, .. - } = utils.list_at_index(batching_pools, 0) + } = batching_pools |> builtin.head_list - let order_inputs = + expect [order_input] = list.filter( all_inputs, fn(input) { @@ -375,8 +369,7 @@ pub fn validate_swap_multi_routing( } }, ) - expect [order_input] = order_inputs - let order_outputs = + expect [order_output] = list.filter( all_outputs, fn(output) { @@ -385,12 +378,16 @@ pub fn validate_swap_multi_routing( addr != batcher_address && payment_cred != pool_payment_credential }, ) - expect [order_output] = order_outputs let Input { output: Output { value: order_in_value, datum: raw_order_in_datum, .. }, .. } = order_input let Output { value: order_out_value, .. } = order_output + expect order_in_datum: OrderDatum = + when raw_order_in_datum is { + InlineDatum(d) -> d + _ -> utils.must_find_script_datum(all_datums, raw_order_in_datum) + } let OrderDatum { receiver, receiver_datum_hash_opt, @@ -399,18 +396,27 @@ pub fn validate_swap_multi_routing( output_ada, lp_asset: order_lp_asset, .. - } = utils.must_find_order_datum(all_datums, raw_order_in_datum) - let is_valid_receiver = - order_validation.validate_order_receiver( - receiver: receiver, - receiver_datum_hash_opt: receiver_datum_hash_opt, - output: order_output, - ) - expect - batcher_fee > 0 && output_ada > 0 && is_valid_receiver && pool_lp_asset == order_lp_asset + } = order_in_datum + expect and { + // batcher_fee and output_ada must be positive + batcher_fee > 0, + output_ada > 0, + // Order Output must be returned to receiver and might have receiver_datum_hash_opt + order_validation.validate_order_receiver( + receiver: receiver, + receiver_datum_hash_opt: receiver_datum_hash_opt, + output: order_output, + ), + // lp_asset in OrderDatum must be the same with LP Asset of first Liquidity Pool in routing list + pool_lp_asset == order_lp_asset, + } when order_step is { SwapMultiRouting(routings, minimum_receive) -> { - expect minimum_receive > 0 + expect and { + minimum_receive > 0, + // The number of Pool Inputs and Pool Outputs must be the same with _routings_ length + utils.compare_list_length(batching_pools, routings), + } order_validation.validate_swap_multi_routing_order( pools: batching_pools, routings: routings, @@ -532,15 +538,9 @@ pub fn validate_update_pool_datum_or_stake_credential( let Address { payment_credential: pool_in_address_payment_credential, .. } = pool_in_address - expect - value.quantity_of(pool_in_value, authen_policy_id, pool_auth_asset_name) == 1 let admin_input = utils.list_at_index(all_inputs, admin_index) let Input { output: Output { value: admin_value, .. }, .. } = admin_input - // Only Admin token can trigger this redeemer - expect - value.quantity_of(admin_value, license_policy_id, admin_asset_name) == 1 - // This Redeemer won't mint anything - expect value.is_zero(value.from_minted_value(all_mints)) + // validate there is a single Pool UTxO in Transaction Inputs and single Pool UTxO in Transaction Outputs expect [pool_output] = list.filter( all_outputs, @@ -556,7 +556,17 @@ pub fn validate_update_pool_datum_or_stake_credential( datum: raw_pool_out_datum, .. } = pool_output - expect dict.size(all_redeemers) == 1 + expect and { + // Transaction contain only 1 Script (Pool Script). + // It will avoid bad Admin can steal money from Order Contract. + dict.size(all_redeemers) == 1, + // Pool Input contains 1 valid Pool NFT Token + value.quantity_of(pool_in_value, authen_policy_id, pool_auth_asset_name) == 1, + // validate Admin with valid Admin License Token must be presented in Transaction Inputs + value.quantity_of(admin_value, license_policy_id, admin_asset_name) == 1, + // This Redeemer won't mint anything + value.is_zero(value.from_minted_value(all_mints)), + } let pool_out_datum = utils.must_find_pool_datum(all_datums, raw_pool_out_datum) let PoolDatum { @@ -581,24 +591,19 @@ pub fn validate_update_pool_datum_or_stake_credential( order_hash: pool_out_order_hash, profit_sharing_opt: pool_out_profit_sharing_opt, } = pool_out_datum - let irrelevant_pool_data_unchanged = - pool_in_asset_a == pool_out_asset_a && // hihi - pool_in_asset_b == pool_out_asset_b && // hihi - pool_in_total_liquidity == pool_out_total_liquidity && // hihi - pool_in_reserve_a == pool_out_reserve_a && // hihi - pool_in_reserve_b == pool_out_reserve_b && // hihi - pool_in_order_hash == pool_out_order_hash && // hihi - pool_in_value == pool_out_value - // hihi - expect irrelevant_pool_data_unchanged + expect and { + pool_in_asset_a == pool_out_asset_a, + pool_in_asset_b == pool_out_asset_b, + pool_in_total_liquidity == pool_out_total_liquidity, + pool_in_reserve_a == pool_out_reserve_a, + pool_in_reserve_b == pool_out_reserve_b, + pool_in_order_hash == pool_out_order_hash, + pool_in_value == pool_out_value, + } when action is { UpdatePoolFee -> { - // only trading fee or fee sharing can be changed - let is_valid_trading_fee = - validate_trading_fee_percent( - trading_fee_numerator: pool_out_trading_fee_numerator, - trading_fee_denominator: pool_out_trading_fee_denominator, - ) + // Profit Sharing can be on/off by setting profit_sharing_opt is None or Some. + // Profit Sharing must be between **16.66%** and **50%** let is_valid_fee_sharing = when pool_out_profit_sharing_opt is { None -> True @@ -613,16 +618,23 @@ pub fn validate_update_pool_datum_or_stake_credential( ) } } - pool_in_address == pool_out_address && is_valid_trading_fee && is_valid_fee_sharing - } - UpdatePoolStakeCredential -> { - let is_valid_trading_fee = - pool_in_trading_fee_numerator == pool_out_trading_fee_numerator && // hihi - pool_in_trading_fee_denominator == pool_out_trading_fee_denominator - let is_valid_fee_sharing = - pool_in_profit_sharing_opt == pool_out_profit_sharing_opt - is_valid_trading_fee && is_valid_fee_sharing + and { + // Pool Address must be unchanged (both Payment and Stake Credential) + pool_in_address == pool_out_address, + // Trading Fee must be between **0.05%** and **10%** + validate_trading_fee_percent( + trading_fee_numerator: pool_out_trading_fee_numerator, + trading_fee_denominator: pool_out_trading_fee_denominator, + ), + is_valid_fee_sharing, + } } + UpdatePoolStakeCredential -> and { + // Trading Fee and Profit Sharing must be unchanged + pool_in_trading_fee_numerator == pool_out_trading_fee_numerator, + pool_in_trading_fee_denominator == pool_out_trading_fee_denominator, + pool_in_profit_sharing_opt == pool_out_profit_sharing_opt, + } } } @@ -647,7 +659,7 @@ pub fn validate_fee_sharing_percent( fee_sharing_numerator: Int, fee_sharing_denominator: Int, ) -> Bool { - // Max 10%, Min 0.05% + // Max 50%, Min 16.66% let max_fee_sharing_numerator = 1 let max_fee_sharing_denominator = 2 let min_fee_sharing_numerator = 1 @@ -679,6 +691,7 @@ pub fn validate_withdraw_liquidity_share( pool_in_output let Address { payment_credential: pool_in_payment_credential, .. } = pool_in_address + // validate there is a single Pool UTxO in Transaction Inputs and single Pool UTxO in Transaction Outputs expect [pool_output] = list.filter( all_outputs, @@ -694,20 +707,24 @@ pub fn validate_withdraw_liquidity_share( datum: raw_pool_out_datum, .. } = pool_output - expect pool_in_address == pool_out_address - expect dict.size(all_redeemers) == 1 let pool_out_datum = utils.must_find_pool_datum(all_datums, raw_pool_out_datum) - expect pool_in_datum == pool_out_datum - expect - value.quantity_of(pool_in_value, authen_policy_id, pool_auth_asset_name) == 1 let admin_input = utils.list_at_index(all_inputs, admin_index) let Input { output: Output { value: admin_value, .. }, .. } = admin_input - // Only Admin token can trigger this redeemer - expect - value.quantity_of(admin_value, license_policy_id, admin_asset_name) == 1 - // This Redeemer won't mint anything - expect value.is_zero(value.from_minted_value(all_mints)) + expect and { + // Pool Input and Output Address must be unchanged (both Payment and Stake Credential) + pool_in_address == pool_out_address, + // Transaction contain only 1 Script (Pool Script). + // It will avoid bad Admin can steal money from Order Contract. + dict.size(all_redeemers) == 1, + // Pool Datum must be unchanged + pool_in_datum == pool_out_datum, + value.quantity_of(pool_in_value, authen_policy_id, pool_auth_asset_name) == 1, + // validate Admin with valid Admin License Token must be presented in Transaction Inputs. + value.quantity_of(admin_value, license_policy_id, admin_asset_name) == 1, + // validate Transaction won't mint any assets + value.is_zero(value.from_minted_value(all_mints)), + } let PoolDatum { asset_a, asset_b, @@ -729,6 +746,9 @@ pub fn validate_withdraw_liquidity_share( } let value_reserve_b_in = value.quantity_of(pool_in_value, asset_b_policy_id, asset_b_asset_name) + // validate Admin withdraws the exact earned Profit Sharing amount: + // Earned Asset A: Reserve A in Value - Reserve A in Datum + // Earned Asset B: Reserve B in Value - Reserve B in Datum let earned_a = value_reserve_a_in - datum_reserve_a_in let earned_b = value_reserve_b_in - datum_reserve_b_in let pool_out_sub_earned_a = @@ -745,6 +765,7 @@ pub fn validate_withdraw_liquidity_share( asset_name: asset_b_asset_name, quantity: earned_b * -1, ) + // validate Pool Out Value must be Pool In Value sub Earned Asset A and Earned Asset B pool_in_value == pool_out_sub_earned_b } diff --git a/lib/amm_dex_v2/types.ak b/lib/amm_dex_v2/types.ak index 4e55cae..3879976 100644 --- a/lib/amm_dex_v2/types.ak +++ b/lib/amm_dex_v2/types.ak @@ -70,43 +70,109 @@ pub type SwapRouting { direction: OrderDirection, } -// TODO: handle MultiRouting Order pub type OrderStep { - // TODO: Combine SwapExactIn, SwapExactInStopLoss & SwapExactInOCO to single one - SwapExactIn { direction: OrderDirection, minimum_receive: Int } - // TODO: SwapExactInStopLoss & SwapExactInOCO: Find another way that's easier to understand for users - StopLoss { direction: OrderDirection, stop_loss_receive: Int } + // SwapExactIn is used for exchanging specific amount of single asset in the liquidity pool. + // The order will be executed if the received amount is greater than or equal to `minimum_receive`. + SwapExactIn { + // The direction (AToB or BToA) of swap request. + direction: OrderDirection, + // Minimum amount of Asset Out which users want to receive after exchanging + minimum_receive: Int, + } + // StopLoss is used for exchanging specific amount of single asset in the liquidity pool. + // The order will be executed if the received amount is less than or equal to `stop_loss_receive` + StopLoss { + // The direction (AToB or BToA) of swap request. + direction: OrderDirection, + // Maximum amount of Asset Out which users want to receive after exchanging + stop_loss_receive: Int, + } + // OCO is used for exchanging specific amount of single asset in the liquidity pool. + // The order will be executed if the received amount is less than or equal to `stop_loss_receive` + // and greater than or equal to `minimum_receive` OCO { + // The direction (AToB or BToA) of swap request. direction: OrderDirection, + // Minimum amount of Asset Out which users want to receive after exchanging minimum_receive: Int, + // Maximum amount of Asset Out which users want to receive after exchanging stop_loss_receive: Int, } - SwapExactOut { direction: OrderDirection, expected_receive: Int } - Deposit { minimum_lp: Int } - Withdraw { minimum_asset_a: Int, minimum_asset_b: Int } - ZapOut { direction: OrderDirection, minimum_receive: Int } + // SwapExactOut is used for exchanging single asset in the liquidity pool and receiving the exact amout of other asset. + // The order will be executed if the received amount is equal to `expected_receive` + SwapExactOut { + // The direction (AToB or BToA) of swap request. + direction: OrderDirection, + // The exact amount of Asset Out which users want to receive after exchanging + expected_receive: Int, + } + // Deposit is used for depositing pool's assets and receiving LP Token + Deposit { + // The minimum amount of LP Token which users want to receive after depositing + minimum_lp: Int, + } + // Withdraw is used for withdrawing pool's asset with the exact assets ratio of the liquidity pool at that time + Withdraw { + // minimum received amounts of Asset A + minimum_asset_a: Int, + // minimum received amounts of Asset B + minimum_asset_b: Int, + } + // ZapOut is used for withdrawing a single pool asset out of Liquidity Pool + ZapOut { + // The direction (AToB or BToA) of ZapOut request. `AToB` in case Asset Out is B and vice versa + direction: OrderDirection, + // Minimum amount of Asset Out which users want to receive after withdrawing + minimum_receive: Int, + } + // PartialSwap is used for exchange partial amount of single Asset. + // The Partial Swap can be executed multiple time if the price ratio is matched with user's expectation, + // and the time is defined in `hops` PartialSwap { + // The direction (AToB or BToA) of swap request. direction: OrderDirection, + // the price ratio which users want to exchange io_ratio_numerator: Int, io_ratio_denominator: Int, + // The time PartialSwap can be executed hops: Int, + // The minimum amount which is required to swap per each execution time minimum_swap_amount_required: Int, } + // WithdrawImbalance is used for withdrawing custom amount of assets. WithdrawImbalance { + // The ratio of Asset A and Asset B users want to receive after withdrawing ratio_asset_a: Int, ratio_asset_b: Int, + // The minimum amount of asset A which users want to receive. + // The amount of Asset will be followed by the ratio: + // (_received_asset_b_ = _minimum_asset_a_ * _ratio_asset_b_ / _ratio_asset_a_) minimum_asset_a: Int, } - SwapMultiRouting { routings: List, minimum_receive: Int } + // SwapMultiRouting is used for exchanging specific amount of single asset across multiple Liquidity Pools. + SwapMultiRouting { + // The routings (including a list of _direction_ and _lp_asset_), + // which is defined Liquidity Pools the swap is routing through + routings: List, + // Minimum amount of Asset Out which users want to receive after exchanging + minimum_receive: Int, + } } pub type OrderDatum { + // The address of order's creator, only sender can cancel the order sender: Address, + // The address which receives the funds after order is processed receiver: Address, + // The datum hash of the output after order is processed. receiver_datum_hash_opt: Option, + // The Liquidity Pool's LP Asset that the order will be applied to lp_asset: Asset, + // The information about Order Type step: OrderStep, + // The fee users have to pay to Batcher to execute batching transaction batcher_fee: Int, + // As known as Minimum ADA which users need to put to the Order, and these amounts will be returned with _receiver_ Output output_ada: Int, } @@ -132,6 +198,7 @@ pub type AuthenRedeemer { } pub type OrderBatchingRedeemer { + // it's used for finding Pool Input faster pool_input_index: Int, } diff --git a/lib/amm_dex_v2/utils.ak b/lib/amm_dex_v2/utils.ak index 3075a04..a3a840c 100644 --- a/lib/amm_dex_v2/utils.ak +++ b/lib/amm_dex_v2/utils.ak @@ -265,10 +265,14 @@ test test_zip_with_throw_err() fail { pub fn compare_list_length(arr1: List, arr2: List) -> Bool { when arr1 is { [] -> arr2 == [] - [_, ..xs] -> + _ -> when arr2 is { [] -> False - [_, ..ys] -> compare_list_length(xs, ys) + _ -> + compare_list_length( + arr1 |> builtin.tail_list, + arr2 |> builtin.tail_list, + ) } } } diff --git a/plutus.json b/plutus.json index d1daace..9c6ad24 100644 --- a/plutus.json +++ b/plutus.json @@ -88,8 +88,8 @@ } } ], - "compiledCode": "590b220100003232323232323232322322322322322322223232323253330143232323253330183370e9001180b0008991919191919191919191919191919191919191919191919191919191919191919299981c9919191919191919299982099b8f00700313372000a002266e4001c00cdd7182280098228011bae3043001303c011375c608200260820046eb8c0fc004c0e003c4c8c94ccc0eccdc3a40006072002264646464646464646464a66608a66e1cccc0040081040ed200213232323232533304d30500021323232323232323232323232323232323232533305c533305c533305c533305c533305c533305c3372000e00a266e4000c004528099b8f00702e14a0266e3c0040b0528099b8f02300514a0266e3c08c00c528099192999830983200109919191919191919191919191919191919191919191919299983a99b8748008c1cc0044c8c8c8c8c8c8c8c94ccc1f4cdd780c8298a99983e99baf0170511533307d3370e02a00a2a6660fa66e1c04c01c54ccc1f4cdc38088030a99983e9919299983f80108008a503371266e08039200a3370402090504e0099b893370401e900a19b8200d4800854ccc1f4cdc780583a8a99983e99baf374c64660020020ba44a66610402002297adef6c601323232325333083013371e91100002100313308701337606ea4008dd3000998030030019bab308401003375c610402004610c020046108020026e98cccc008cccc008cccc0092f5bded8c00f20e6900103c838a40040f2088907f7fffffffffffffff80899baf374c0026e980745280a5014a029405280a5014a02940cccc004cccc004cccc004cccc004cccc0052f5bded8c091100488100482026fb8081281200181181100141e010c00c1e01c120022222533307f3370e0029000080209999111919800800804112999843808008998440099bb037520106e980152f5bded8c0264646464a6661100266ebccc01c030009300103d879800013308c01337606ea4030dd30048028a9998440099b8f00c00213232533308a013370e90000008998470099bb0375201c611e0261120200400a200a610e020026660100180020122661180266ec0dd48011ba60013300600600337566112020066eb8c21c04008c22c04008c22404004cc88c800cc8cc00400400c894ccc2180400452613253330870100114984c8c8c8c8c8c8c94ccc22c04cdc3a40000022660140146611e0200c00a2c611002002660120040026eb8c2240400cdd7184400801984600801984500801184480801184480800998418099bb037520046ea00052f5bded8c000a44464a66610602a66610c0200229445280a6103d87a800013374a900019843809ba60014bd701919800800801912999843808008998440099bb0375200e6ea00192f5bded8c0264646464a6661100266ebccc03802c00930103d879800013308c01337606ea402cdd40050028a9998440099b8f00b00213232533308a013370e90000008998470099bb0375201a611e0261120200400a200a610e0200264a6661120266e1c005200014c103d87a800013374a900019846809ba80014bd7019b8000100a13308c01337606ea4008dd4000998030030019bad308901003375c610e0200461160200461120200200a44a6660f866e40008004530103d87980001533307c3371e0040022980103d87a800014c103d87b800033702907f7fffffffffffffff808009919299983d19b8833704002002004266e000052002100153330793371000290000b0a99983c99b870014800052000153330793370e00290010a40042a6660f266e1c00520041480084c8c8ccc00400400c0088894ccc1f4cdc4000801099980180180099b833370066e0c014004005200410023370066e0c005200448008cdc100100099981980b82081fa99983b299983b19b8f04448810013371e0849110014a0266e0400520809bee0210013330310150430411630790013079002375c60ee00260ee0046eb4c1d4004c1d4008dd6983980098398011bad30710013071002375a60de00260de0046eb4c1b4004c1b4008c1ac004c1ac008c1a4004c188c8c8008c94ccc194cdc3a40000022646464646464646464646464646464646464a6660f460fa00426464649319299983d19b87480000044c8c94ccc1fcc208040084c92632375a60fe0046eb4c1f400458c8cdd81840808009840809841008009bac30800100130790041533307a3370e90010008a99983e983c8020a4c2c2c60ee00660cc02060ca0222c60f600260f60046eb8c1e4004c1e4008dd6983b800983b8011bad30750013075002375a60e600260e60046eb4c1c4004c1c4008dd69837800983780118368009836801183580098320010b18310009980881d800983380098338011bab30650013065001305d0011630620013301703e23232323232323253330643370e900100089919299983319b8f06000113370e66604400c0c40b490010a50375c60d400260c60042940c184004c19c004c18000cdd5983280098328011831800982e0008b1bae30600013060002375c60bc00260ae6600a06000e6eb8c170004c170008dd7182d0009829998008160039119190011823000998018010009119299982a99b8748000c14c0044c168c15000458c94ccc154cdc3a40000022980103d87a8000153330553370e9001000899191980080080291299982d8008a6103d87a8000132323232533305c3371e00e004266e952000330600014bd70099803003001982e8019bae305b002305f002305d001375c60b460a8004266e95200033059305a30540024bd701829000982b000982b000982a800982680298290009829000982880098248018b18270009827001182600099800814119191919299982599baf00300a13370e66600e00208e08290010a503756609e002609e004609a002608c00244646600200200644a666098002297ae013232533304b300500213304f002330040040011330040040013050002304e0011622232332232533304b3370e9001000880109bad3050304a00330480023253330493370e90010008a6103d87a8000132323300100100222533304f00114c103d87a800013232323253330503371e014004266e95200033054375000297ae0133006006003375a60a20066eb8c13c008c14c008c144004dd5982718240011823000a4000646600200200844a6660980022980103d87a8000132323232533304d3371e010004266e95200033051374c00297ae01330060060033756609c0066eb8c130008c140008c138004dd5982400098240011823000981f80098220009822000981e0009820800981d0008b191980080081011299981f8008a60103d87a800013232533303e3375e6086607a00404a266e952000330420024bd70099802002000982180118208009999991911111803198029803198028020019803198028010009119b8a0020012372600200e00a0060022c6eb8c0f4004c0f4008dd7181d800981a0049bae30390013039002375c606e002606000e6eb8c0d4004c0d4008dd718198009816010181880098188011817800981400d9bab302d001302d001302c001302b001302a001302900130280023756604c002604c002604a0046eb0c08c004c08c004c088008dd61810000980c802980f000980b8008b180e000980e001180d00098098028a4c26cac64a66602866e1d2000001132323232533301b301e00213232498c01c008c01800c58c070004c070008c068004c04c01858c0440148c94ccc050cdc3a4000002264646464a666036603c0042930b1bae301c001301c002375c603400260260042c60220026002008464a66602466e1d20000011323232325333019301c002149858dd7180d000980d0011bae3018001301100216300f001375c0026eb8004dd70009bae001375c002460086ea80048c010dd5000ab9a5573aaae7955cfaba05742ae89", - "hash": "89d9274295ce81f08598956da94c74e209d7facf7619ec44b4393fc8" + "compiledCode": "590b660100003232323232323232322322322322322322223232323253330143232323253330183370e9001180b0008991919191919191919191919191919191919191919191919191919191919191919299981c9919191919191919299982099b8f00700313372000a002266e4001c00cdd7182280098228011bae3043001303c011375c608200260820046eb8c0fc004c0e003c4c8c94ccc0eccdc3a40006072002264646464646464646464646464a666096609c002264a66609266e1cccc0040181140fd2002132323232533305030530021323232323232323232323232323232323232533305f533305f3372000e00a2a6660be66e4000c00454ccc17ccdc78038188a99982f99b8f00102f1533305f3371e04c00a266e3c09800c5280a5014a02940528099192999832183380109919191919191919191919191919191919191919191919299983c19b8748008c1d80044c8c8c8c8c8c8c8c94ccc20004cdd780c82b0a9998400099baf01705415333080013370e02a00a2a6661000266e1c04c01c54ccc20004cdc38088030a99984000991929998410080108008a503371266e08039200a3370402090504e0099b893370401e900a19b8200d4800854ccc20004cdc780583c0a9998400099baf374c64660020020c044a66610a02002297adef6c601323232325333086013371e910100002100313308a01337606ea4008dd3000998030030019bab308701003375c610a02004611202004610e020026e98cccc008cccc008cccc0092f5bded8c00f80ec900103e03a240040f808e907f7fffffffffffffff80899baf374c0026e980745280a5014a029405280a5014a02940cccc004cccc004cccc004cccc004cccc0052f5bded8c091100488100482026fb80813412c01812411c0141ec11800c1ec1cd200222225333082013370e0029000080209999111919800800804112999845008008998458099bb037520106e980152f5bded8c0264646464a6661160266ebccc01c030009300103d879800013308f01337606ea4030dd30048028a9998458099b8f00c00213232533308d013370e90000008998488099bb0375201c61240261180200400a200a61140200266601001800201226611e0266ec0dd48011ba60013300600600337566118020066eb8c22804008c23804008c23004004cc88c800cc8cc00400400c894ccc22404004526132533308a0100114984c8c8c8c8c8c8c94ccc23804cdc3a4000002266014014661240200c00a2c611602002660120040026eb8c2300400cdd7184580801984780801984680801184600801184600800998430099bb037520046ea00052f5bded8c000a44464a66610c02a6661120200229445280a6103d87a800013374a900019845009ba60014bd701919800800801912999845008008998458099bb0375200e6ea00192f5bded8c0264646464a6661160266ebccc03802c00930103d879800013308f01337606ea402cdd40050028a9998458099b8f00b00213232533308d013370e90000008998488099bb0375201a61240261180200400a200a61140200264a6661180266e1c005200014c103d87a800013374a900019848009ba80014bd7019b8000100a13308f01337606ea4008dd4000998030030019bad308c01003375c611402004611c0200461180200200a44a6660fe66e40008004530103d87980001533307f3371e0040022980103d87a800014c103d87b800033702907f7fffffffffffffff808009919299983e99b8833704002002004266e0000520021001533307c3371000290000b0a99983e19b8700148000520001533307c3370e00290010a40042a6660f866e1c00520041480084c8c8ccc00400400c0088894ccc20004cdc4000801099980180180099b833370066e0c014004005200410023370066e0c005200448008cdc100100099981900b822021299983ca99983c99b8f04748810013371e08a9110014a0266e0400520809bee02100133303001504604416307c001307c002375c60f400260f40046eb4c1e0004c1e0008dd6983b000983b0011bad30740013074002375a60e400260e40046eb4c1c0004c1c0008c1b8004c1b8008c1b0004c194c8c8008c94ccc1a0cdc3a40000022646464646464646464646464646464646464a6660fa61000200426464649319299983e99b87480000044c8c94ccc20804c214040084c92632375a6104020046eb4c2000400458c8cdd81842008009842009842808009bac308301001307c0041533307d3370e90010008a99984000983e0020a4c2c2c60f400660d202060d00222c60fc00260fc0046eb8c1f0004c1f0008dd6983d000983d0011bad30780013078002375a60ec00260ec0046eb4c1d0004c1d0008dd69839000983900118380009838001183700098338010b18328009980881f000983500098350011bab3068001306800130600011630650013301904123232323232323253330673370e900100089919299983499b8f06300113370e66604200c0ca0ba90010a50375c60da00260cc0042940c190004c1a8004c18c00cdd5983400098340011833000982f8008b1bae30630013063002375c60c200260b46600a06600e6eb8c17c004c17c008dd7182e800982b198008178039119190011824800998018010009119299982c19b8748000c1580044c174c15c00458c94ccc160cdc3a40000022980103d87a8000153330583370e9001000899191980080080291299982f0008a6103d87a8000132323232533305f3371e00e004266e952000330630014bd7009980300300198300019bae305e00230620023060001375c60ba60ae004266e9520003305c305d30570024bd70182a800982c800982c800982c0009828002982a800982a800982a00098260018b182880098288011827800998018159191919191919299982819baf00b00113370e66601000609808c90010a503054001304d003375660a400260a400460a000260920022c44464664464a66609e66e1d200200110021375a60a8609c006609800464a66609a66e1d200200114c103d87a8000132323300100100222533305300114c103d87a800013232323253330543371e014004266e95200033058375000297ae0133006006003375a60aa0066eb8c14c008c15c008c154004dd5982918260011825000a4000646600200200844a6660a00022980103d87a800013232323253330513371e010004266e95200033055374c00297ae0133006006003375660a40066eb8c140008c150008c14800458cc0040b08cdd780198269823982698239826982718238009119198008008019129998268008a5eb804c8c94ccc130c0140084cc140008cc0100100044cc010010004c144008c13c004c128004c10c00cdd5982400098240011823000981f80098220009822000981e0009820800981d0008b191980080081011299981f8008a60103d87a800013232533303e3375e6086607a00404a266e952000330420024bd70099802002000982180118208009999991911111803198029803198028020019803198028010009119b8a0020012372600200e00a0060022c6eb8c0f4004c0f4008dd7181d800981a0049bae30390013039002375c606e002606000e6eb8c0d4004c0d4008dd718198009816010181880098188011817800981400d9bab302d001302d001302c001302b001302a001302900130280023756604c002604c002604a0046eb0c08c004c08c004c088008dd61810000980c802980f000980b8008b180e000980e001180d00098098028a4c26cac64a66602866e1d2000001132323232533301b301e00213232498c01c008c01800c58c070004c070008c068004c04c01858c0440148c94ccc050cdc3a4000002264646464a666036603c0042930b1bae301c001301c002375c603400260260042c60220026002008464a66602466e1d20000011323232325333019301c002149858dd7180d000980d0011bae3018001301100216300f001375c0026eb8004dd70009bae001375c002460086ea80048c010dd5000ab9a5573aaae7955cfaba05742ae89", + "hash": "8d2c20f5db015bded460c57374c7cba6806e0da04bbc075f1fbe5a23" }, { "title": "order_validator.validate_order", @@ -181,8 +181,8 @@ } } ], - "compiledCode": "593ada0100003232323232323232323223223223223223222232323232323232533301732323232533301b3370e9001180d000899191919191919191919191919191919191919191919191919191919299981b99b87480000684c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc140cdc3a4000609e0022646464a6660a66660a660b4032941288a999829999180080091299982c0008a51132533305900114a226464a6660b06660b0646600200200444a6660ba00229404c8c94ccc170cdc38030010a511330040040013061002375a60be002941288998028028008a50305d003375a60b600460b60020322a6660a66062606401e2a6660a6660660120042a6660a6666606800201609a08e26666666666464646464646464646464644444444446464646464646464646464646464646464646464646464646464a66610402a666104026604c03600626604800400229404cdd79ba7004374e64666600200200600400e4444a66611202006200226464a66611602008200626464646464646464646464646464646464646464646464646464646464a66614a02a66614a0266e212000003153330a501337109000000899baf03500714a029404cccc08c08c078070c94ccc29804cdc3a4000002264646464a66615402a6661540266e212000001133307901000e02214a02646666666666660a203802e00207e07a01200e07206e06a04c466e2400c004c94ccc2ac04cdc3a400000229445281854808018b1bad30ae0100130ae0100230ac0100130a401006153330a6013370e9001000899191919299985500a9998550099b88480000044ccc1e404003808852809919999999999982880e00b80081f81e80480381c81b81a813119b890010033253330ab013370e90000008a5114a06152020062c6eb4c2b804004c2b804008c2b004004c2900401854ccc29804cdc3a40080022646464646464a66615802a6661580266e212000003153330ac01337109000000899983d8090080120a5014a02646666666666660a603c03200208207e01601207607206e0504a66615c0266e24014004528899b890010033253330ad013370e90000008a5114a061560200a2c6eb4c2c004004c2c004008dd69857008009857008011856008009852008030a9998530099b87480180044c8c8c8c94ccc2a8054ccc2a804cdc42400000226660f202001c04429404cccccccccc8888888888c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc348054ccc34804cdc4240000082a6661a40266e2400c0104cdd79ba6001374c07c29405280a9998690081409986b009ba83370266e0007800c008cc35804dd419b8101c027330d601375066e0006800ccc35804dd419b81018027330d601375002c97ae01330d601375066e0407809ccc35804dd419b8133700038006004661ac026ea0cdc080d0139986b009ba833700030006661ac026ea00592f5c02c66661500266661500266661500297adef6c6048810048810002200700502600b0093370200600464a6661a20266e1d2000001132323333309d01004023022375a61a8020026eb4c35404004c8cdd8186c00800986c00986c808009bac30d70100130cf0101f148000c33c040794ccc33c04cdc4012005899b803370666e08cdc100681200f19b823370201604866e0407807d20021653330ce01330a60100800613370200266e0008007c4004ccc298040f801c014dd71868808009868808011bae30cf0100130c701009375c619a02002619a020046eb8c32c04004c30c0401cdd69864808009864808011bad30c70100130c70100230c50100130c50100230c30100153330bd010131330c101011330c101010330c101375001266182026ea001d2f5c026618202020661820202266182026ea001ccc30404dd4004a5eb80dd69860808009860808011bad30bf0100130bf01002375a617a02002617a020046eb4c2ec04004c2ec04008dd6985c808009929998558099b87480000045288a5030a90100300103e03c00800603803603402516375a615c02002615c0200461580200261480200c2a66614c0266e1d20080011323253330a80153330a801337109000000899983b8070060100a5013232323232323232323232323232323232323232323232323232323232323253330c70153330c7013371290000040a9998638099b894800002454ccc31c04cdc42400066e0002002454ccc31c04cdc4810001099baf374c0026e980cc5280a5014a029404cc32c04dd419b813370003e01000c66196026ea0cdc099b8001d009004330cb01375066e0006c020cc32c04dd419b80019009330cb01375066e0005c0092f5c02c666613a02666613a0297adef6c604890048810002200c00a001375a6194020026194020046eb4c32004004c32004008dd6986300800991929998610099b880010021323232323232330cc01375064a6661920266e1d200000113232333333074008007059057375a6198020026eb4c33404004c8cdd81868008009868009868808009bac30cf0100130c701053148000c31c04148cc33005301010000330cc01375066e0ccdc119b803370401400200403066e04cdc100f00080125eb80dd69864808011bad30c70100133333306d01d01b002001053051375a618c020046eb4c31004004cccccc1a400c01006806014013854ccc30804cdc400100089919191919191986600a61010000330cc01375064a6661920266e1d200000113232333333074008007059057375a6198020026eb4c33404004c8cdd81868008009868009868808009bac30cf0100130c701053148000c31c04148cc33004dd419b833370466e00cdc100480080100c19b813370404000200497ae0375a6192020046eb4c31c04004cccccc1b406c07400800414c144dd69863008011bad30c40100133333306900400301801a05004e1330c6014c1010000330c6014c1010000330c601375000497ae03370666e0800c04405ccdc199b8200101001853330bf01330970100d00b13370200466e0007406c4008ccc25c040bc020018ccc258040b802c024dd71860808009860808011bae30bf0100130b701049375c617a02002617a020046eb8c2ec04004c2cc0411cdd7185c80800985c808011bae30b70100130af01045375a616a02002616a020046eb4c2cc04004c2cc04008dd69858808009858808011bad30af0100130af01024375a615a020462c6eb4c2b004004c2900401854ccc29804cdc3a4014002264646464a66615402a6661540266e212000003153330aa01337109000000899983c8080070110a5014a026464646464646464646464646464646464646464646464646464a66618802a6661880266e212000005153330c4013371203a0062a6661880266e2406c0084cdd79ba6001374c06029405280a501330c801375066e0406800ccc32004dd419b81018002330c801375066e0405800ccc32004dd419b81014002330c801375066e040480152f5c02c66661340266661340266661340297adef6c604890048810001f00f00d00200b009001375a6188020046eb4c30804004cccc1a8058050004038ccc260040c000c004dd71861808009861808011bae30c10100130b90104b375c617e02002617e020046eb8c2f404004c2d404124dd7185d80800985d808011bae30b90100130b101047375a616e02002616e020046eb4c2d404004c2d404008dd69859808009859808011bad30b10100130b101026375a615e0204a2c6eb4c2b804004c2b804008dd69856008009852008030a9998530099b87480300044c8c8c8c94ccc2a8054ccc2a804cdc42400000226660f202001c04429404ccccccccc888888888c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc334054ccc33404cdc4240000102a66619a0266e240900084cdd79ba6001374c07229405280a99986680811899868809ba83370203400c661a2026ea0cdc099b81018004002330d101375002c661a2026ea0cdc080a00119868809ba83370202401097ae01330d101375066e04cdc080d00100319868809ba833702030008661a2026ea0cdc080b00119868809ba8014330d101375066e040480212f5c02c66661460266661460297adef6c604890048810001e00e00c001375a61a00200261a0020046eb4c33804004c33804008dd6986600800991919191929998658081089919868009ba83253330cd013370e9000000899191999984c8080400f80f1bad30d001001375a61a2020026466ec0c35004004c35004c35404004dd618698080098658080f0a400061960203a661a002981010000330d001375066e000100052f5c06666612c02004002008036034264661a0029801010000330d001375064a66619a0266e1d200000113232333330990100701f01e375a61a0020026eb4c34404004c8cdd8186a00800986a00986a808009bac30d30100130cb0101e148000c32c04074cc34004dd419b800050014bd701999984b0080080100180d80d19b810150023370202c0046eb4c32804008dd6986400800999983800980880080599984f0081b0018009bae30c90100130c901002375c618e02002617e020286eb8c31404004c31404008dd7186180800985d80800a99985e00809080808089bad30c00100130c001002375a617c02002617c020046eb4c2f004004c2f004008dd6985d00800985d008011bad30b8010013253330ab013370e90000008a5114a061520200607c07807400c06807006c04a2c6eb4c2b804004c2b804008c2b004004c2900401854ccc29804cdc3a401c002264646464646464646464a66616002a6661600266e212000007153330b001337109000003899b884800000c5280a5013232323232323232323232323253330bd0153330bd0100113232323232323253330c4013370e9001186180800899191919191919191919191919191919299986a0099b8748038c34c040144c8c8c8c8c8c8c8c8c8c94ccc37804cdc780c8330a99986f0099baf046017153330de013375e08802a2a6661bc0266ebc10804c54ccc37804cdc381e0068a99986f0099b8703a00b153330de013375e0800222a6661bc0266ebc0dc02454ccc37804cdc381a8038a99986f0099b87033005153330de013370e66e040c5200200313370e05e00229405280a5014a029405280a5014a029405280a50375a61c40200261c4020046eb4c38004004c38004008dd6986f00800986f008011bad30dc0100130dc0100230da0100130d20100516375a61b00200261b0020046eb4c35804004c35804008c35004004c35004008c34804004c34804008c34004004c34004008c33804004c33804008c33004004c31004cc2480418c014dd71865008009861008008b186400800986000802186300800986300800986280801186180800985d8081a89998460081181081a899860809ba800b330c101375001266182026ea001ccc30404dd400299860809ba80034bd700b19b8748008c2f404dd51860808009860808011bad30bf0100130bf01002375a617a02002617a020046eb4c2ec04004c2ec04008dd6985c80800985c808011bad30b70100132323232323232323232323232323232323232323232323232323232323232323232323253330d50153330d5013371090000048a99986a8099b884800001c54ccc35404cdc4813003899baf374c0026e981045280a5014a0264a6661ac0204c2661b4026ea0cdc099b80025008005330da01375066e0408c018cc36804dd419b80021008330da01375066e0407c018cc36804dd400e9986d0099986b00800a6103d87a80004c0103d87980004bd7009986d009ba83370204a00c661b4026ea0cdc099b80023008005330da01375066e04084018cc36804dd419b8001f008330da01375003a661b4026661ac02002980103d87a80004c0103d87980004bd70299986a8099b8848000cdc081424004266e240980185280b199985580999985580999985580a5eb7bdb1812210048810000100d00b3370000400802201e00a66e00cdc101899b81026480080bcccc2ac0410c02c024c94ccc34804cdc3a4000002264646666613c0200c0c40c06eb4c35404004dd6986b008009919bb030d90100130d90130da01001375861b00200261a0020b82900018680082d9999984d8080880780102f02e19b8100300153330ce01337120040022004200264646464a6661a20266e2120000011001163370600400266e08090008cdc099b823370404a00201a66e08cdc101182d00799b8105905b53330cc01330a40100900713370200200420026661480207801000c66e00cdc101400e8131bae30ce0100130ce01002375c6198020026188020126eb8c32804004c32804008dd71864008009860008039bad30c60100130c601002375a618802002618802004618402002618402004618002002a6661740201426617c0209c6617c020986617c026ea0024cc2f804dd4003a5eb804cc2f804130cc2f804138cc2f804dd40039985f009ba80094bd701bad30be0100130be01002375a6178020026178020046eb4c2e804004c2e804008dd6985c00800985c008169bad30b60102c3253330b1013370e90000008a5114a0615e020122c6eb4c2d004004c2d004008dd69859008009859008011bad30b00100130b001002375a615c02002615c0200461580200261480200c2a66614c0266e1d2010001132323232323253330ac0153330ac013371090000028a9998560099b884800000c54ccc2b004cdc42400000226660f602402004829405280a5013232323232323232323232323232323232323232323232323232323232323253330cb0153330cb013371090000050a9998658099b8902000413375e6e98004dd301b8a5014a026619e026ea0cdc099b8101f004008330cf01375066e04cdc080e80100319867809ba8337020360086619e026ea0cdc080c80119867809ba83370202e01497ae01633330a10133330a10133330a1014bd6f7b6302450048810002601401200301000e001375a619c02002619c020046eb4c33004004c33004008dd69865008009865008011bad30c8010013232323232323253330c90133710002004264646619e026ea0c94ccc33004cdc3a400000226464666661300200a0b80b46eb4c33c04004dd69868008009919bb030d30100130d30130d401001375861a4020026194020ac2900018650082a9986780a61010000330cf01375066e04020008cc33c04dd419b800070014bd701999984a8080280200082c02b1999999983a00300280200181101002b82a8a9998648099b8800200113232330cf014c1010000330cf01375064a6661980266e1d200000113232333330980100505c05a375a619e020026eb4c34004004c8cdd8186980800986980986a008009bac30d20100130ca01056148000c32804154cc33c04dd419b80008001330cf01375066e0401c0092f5c06666612a0200800a0020b00ac666666660e800a00c0060080400440ae0aa26619a02981010000330cd014c1010000330cd01375000c6619a026ea00152f5c066e08010084cdc100200f19b81018002337020320046eb4c31804008dd6986200800999983600b00a00080719984d008190018009bae30c50100130c501002375c61860200261760209a6eb8c30404004c30404008dd7185f80800985b808259bae30bd0100130bd01002375c6176020026166020926eb4c2e404004c2e404008dd6985b80800985b808011bad30b50100130b501002375a6166020026166020506eb4c2c40409c58dd69858008009858008011bad30ae0100130ae01002375a61580200261480200c2c61480200a2c6eb4c2a404004c2a404008dd6985380800985380801185280800985280801185180800985180801185080800985080801184f80800984f80801184e80800984a80801183180099299984b0099b87480100044c26c04c250040144cc1c00cc014c25004010dd5984c80800984c80800984800805184b00800984b008011bab309401001309401001308b01001309101001309101001308801004308f01005308d01004308d01004308b01003163305601f23232533308401333084013375e0020149412889998420099baf00101e4a09445281844008009840008009919980080080d25eb808894ccc2180400840044ccc00c00cc22404008cc88cc22404cc158018008004dd69844008010009982a00f1191919191919191929998440099b87480080044cdc79bae308d0130860100201014a0610c02002611602002610602002611202002610202002610e02002610e0200260fc0026eb0c20c04004c20c04008dd6184080800984080801183f800983f8011bae307d001307d002307b001307b002375a60f200260f20046eb4c1dc004c1dc008c1d4004c1d4008c1cc004c1cc008c1c4004c1a4ccccccc10400800403002c018021281836001183500099299983499b8748000c1a00044c8c8c8c8c8c8c8c8c8c8c94ccc1dcc1e80084c8c94ccc1d8cdd7800803899299983d183e800899bb000a004163304b01523375e60f860ea60f860ea60f860fa60ea00200c2c60f400260e40022c60f00026608e020466ebc008c1e0c1c4c1e0c1c4004c1d8004c1b8004c1d0004c1b0004c1c8004c1c8004c1a4004c1bc004c19c00458cc0e001c8cdd78029837183380098008009112999831801099baf374e0029801018000132533306400214a0266600800800260ce00460cc0046002002444a6660c2004266ebcdd3800a601018000132533306200214a0266600800800260ca00460c8004444444444444646464646464646464646464646464646464646464646464646464646464a66610802a6661080266e2120000041533308401301f00313375e6e98004dd30148a5014a02a66610802050266110026ea0cdc099b8001e0040023308801375066e0407000ccc22004dd419b8001a0043308801375066e0406000ccc22004dd400b25eb804cc22004dd419b8101e0033308801375066e04cdc000e00200119844009ba83370203400666110026ea0cdc000c00219844009ba80164bd700b199982d199982d25eb7bdb18122100488100023007005002325333083013370e900000089919199998278028120119bad308601001375a610e020026466ec0c22804004c22804c22c04004dd61844808009840808100a400061020203e6666609801a01600204003ea66610002660b001000c266e04004cdc0010810080099982c0128038029bae308301001308301002375c61020200260f20126eb8c1fc004c1fc008dd7183e800983a8039bad307b001307b002375a60f200260f200460ee00260ee00460ea002a6660de0262660e6024660e6022660e66ea0024cc1ccdd4003a5eb804cc1cc044cc1cc048cc1ccdd4003998399ba80094bd701bad30730013073002375a60e200260e20046eb4c1bc004c1bc008dd6983680098368019bad306b00222222232323232337606ea0cdc0980819b81300f0013370466e0800c014cdc08028030009ba83370400466e04014018cdc119b8133704900200200280199b8248010cdc000380299b8233704900419b800060043370266e08018c030014cdc119b820070050043370466e0001400c010888888c8cdd81ba83370466e08014004018dd419b803370466e0800801001ccdc100280099b810010022222223370666e08cdc100300200119b823370400a006002444466ec0dd419b83337040040080026ea0cdc199b82002003001222222223232323370666e04c034cdc099b820010013370466e0920080023370466e08010024cdc099b8200700a3370400c01600266e0920040023370066e08cdc100300119b800070093370400a66e04cdc100400199b8200200a3370400800266e040040088cdc10008009299982a19b88001480005854ccc150cdc3800a4000290000a99982a19b870014800852002153330543370e00290020a400426464666002002006004444a6660b066e200040084ccc00c00c004cdc199b803370600a0020029002080119b803370600290022400409e09602a02400c06e08c03603229405280a5014a02940cc08c050058dd7182b00098270008b182a000982600b1bab3052001305200130510023758609e002609e004609a002609a002609800260960046eacc124004c124004c120008dd61823000982300098228011bac3043001303b025375a608200260820046eb0c0fc004c0fc008c0f4004c0d40a44c94ccc0e0cdc3a40040362646464646464646464646464a666088a66608866e1c0080044cdc4a400800229404c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc154c0ccc0d00284c8c94ccc15ccdc3a400060ac00226464a6660b266072010002264a6660b466660760020160a809c2666666666646464646444444444464646464646464646464646464646464a6660f660fc00426464a6660fa610002004264646464646464646464646464646464646464646464646464a66612602a66612602a66612602a6661260266e21200000413371090000010a50100114a0266ebc09c020528099299984a0099b87480480044c8c8c8c94ccc26004cdc424000002264646464646464646464646464646464646464646464646464646464646464a66616e0266e240800044c8c94ccc2e4040045288b299985c009984800803802899b873370000404e002264a6661720266e1c00c0084cdc38008140a50333091010344881004881003330900103300600416333305a001480001380854ccc2d404cc234040200184cdc080099b80026024100133308d01035007005375c6170020026170020046eb8c2d804004c2b804014dd7185a00800985a008011bae30b20100130aa010023253330ac013370e9000000880188029855008051929998558099b874800000440204018c2a404030c2b804004c2b804008c2b004004c2900402cc2a804004c2a804008c2a004004c28004020c29804004c29804004c27404018c28c04004c28c04004c26804010cc1b00cccdc0981e819a4004660d606490001983500219b81303b00448008cc1a400d200016375a6138020026138020046eb0c26804004c2480401c5281849008030b19983080580480c9bad309601001309601002375a612802002612802004612402002612402004612002002612002004611c02002611c02004611802002611802002610602660a20440086eacc22404004c22404004c20004024c21804004c21804008dd5984200800984200800983d800984080800984080800983c0018b183f0009982680b119191919299983e99983e99baf0030184a09444ccc1f4cdd78008042504a22940c20404004c1e4004c1fc004c1dc00458c1f0004cc12c0548c8c8c8c8c8c8c8c94ccc1fccdc3a4004002266e3cdd7184200983e8010070a50307d001308201001307a0013080010013078001307e001307e0013075001307a001307200130780013078002375c60ec00260ec00260ea00260e800260e600460e200260e200260e000260ce660740029000191919191929998381839801099191919191919299983a19b8730163304901323375e60f460e660f460e660f460f660e60020040142a6660e866e1cc058cc1240488cdd7983d1839983d1839800801005099199800800806806111299983d0010a99983d0008a5eb80584c8c94ccc1f000c52f5c02660fa646464646464646464646464a66610a0266ebc01c0084ccccccc16c024030098094004089288b19826810801984400800984000804984300800984300800984280801184180800983d800984080800984080800983c001983f001999802802800983f801983f001983e0010b0b183c0009838000983b0009837000983a000983a00098358008b183880099820005119baf0083071306a001300c0023300f00223303b0080013300e00223303a00800130010012253330620011480004cdc0240046600400460ca002600200244444a6660c6004200626464a6660ca006200a26464646666601401400200200800464646464646464646464646464646464646464646464646464646464646464646464a66610c0266ebc07c00c4c8c8c94ccc22404cdc42400005a26464a6661160266e2120000021533308b0132533308c013370e90000008a9998460099b873370266e000600c000804454ccc23004cdc399b8101600300f1533308c013370e66e000500c00344cdc399b8101200300b14a029405280a9998460099b87337020300060222a6661180266e1ccdc099b8001603000200f1533308c013370e66e0405000c0344cdc399b8001203000b14a0294052818450080308010b0b1929998458099b87480000044c8c8ccccc15c0c4090088dd69847008009bad308f0100132337606124020026124026126020026eb0c24404004c224040785200030890101d3333305400200102d02001e16375a6114020046eb4c22004004c94ccc21c04cdc3a4000002266ec0dd40099ba80111337606ea0044dd40099842808008b1845008009845008011844008009840008101bad308601001308601002375a6108020026108020046eb4c20804004c20804024dd69840008041bad307f001307f002375a60fa00260fa0046eb4c1ec004c1ec010dd6983c8019bac30780013078002375860ec00260ec00260ea00260e800460e400260e40046eb4c1c0004c1c0008dd698370009837001183600098360009835800983100218348021833801983380198328011119198008008019129998308008a5eb804cc188c00cdd6983180099801001183200082b02900a80900301f01080e80d8b1981500a00f0b1bae305d001305500116305b001305301c16375660b200260b200260b00046eb0c158004c158008c150004c150004c14c004c148008dd59828000982800098278011bac304d001304d001304c0023758609400260840582c6004008600200a600200244a66608a00229000099b8048008cc008008c120004dd6182200098220011bac30420013042002375a60800026080004607c002606c054264646464a66607866e1d200401f132323232323232323232323232323232323232323253330513370e9000182800089919191919191919191919299982e19b87333035003058054480084c8c8c8c8c8c8c94ccc18ccdc399981e00082e82ca40042a6660c66082608403826464a6660d060d60042646464646464a6660d666e1cc0c00792002132323232323232323232323232323232323232323232323232323232323232323232323232325333091010011325333092013370e900000089919299984a00a99984a0099baf03d02e100214a020022940c94ccc25004cdc3a400400229444c8c8cc88c8c94ccc26804008400452819b8933704004900119b8200348030cdc499b8200248010cdc1000a40046eb4c25c04004dd6984c008009919bb0309b01001309b01309c01001375861340200261240200a6124020086464a6661280200420022940cdc499b8200848028cdc100524141380266e24cdc1004a402866e0801d200213232533309401002100114a066ebc0580114ccc24804cdc380d804899b8701900714a06120020a82ca66612002a66612002a66612002a66612002a66612002a6661200266ebc08c0444cdd78108078a5013370e03e01a29404cdc380e8058a5013370e03601229404cdc780a8018a5013375e6e980dcdd30140a50309401001309401002375c6124020026124020046eb4c24004004c24004008dd69847008009847008011bad308c01001308c01002375a6114020026114020046eb4c22004004c22004008c21804004c21804008c21004004c1f004cc20804004c20804008dd71840008009840008011bad307e001307e002375a60f800260f80046eb4c1e8004c1e8008dd6983c000983c0011bad30760013076002307400130740023072001306a05f3303401c00116306f001306f002375660da00260da00460d600260c60022c60d20026607003e466ebcc1a4c188c1a4c1880040245858dd598338009833800982f00098320009832000982d8009981680d80e8b1830000982c0019bab305e001305e002305c0013054001305a001305a00130510013057001304f001163301800f034375660a800260a80046eacc148004c148004c144004c140004c13c004c138008dd59826000982600098258011bac3049001304900130480023758608c002607c0506eb4c110004c110008c108004c0e80b84c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc13ccdc3a4000609c002264646464646464646464646464a6660be60c40042646464646464a6660c466ebc03401454ccc188cdc3981380ba4004264a6660c666ebc15800454ccc18ccdc399981e00602f82da4004264646464646464a6660d466e1cccc10c00419018120021533306a3048304902513232323232323232323232323232323232323375e6e98094dd31999829999982980e80400319b8233702a6660f8660a801000c266e0400520809bee02100100c48004010008cdc119b8133305502500400200a48004ccc15009001c014dd7183f800983f8011bae307d001307500a375c60f600260f60046eb8c1e4004c1c4020dd6983b800983b8011bad307500130750013074002307200130720023070001306805d1616375660dc00260dc00260ca00260d600260d600260c40026606804804c2c2c6605602a0022c2c60cc00260cc0046eacc190004c190008c188004c16800458c180004cc0bc0608cdd79830182c9830182c800801182f000982b0019bab305c001305c002305a001305200130580013058001304f0013055001304d001163301600f032375660a400260a40046eacc140004c140004c13c004c138004c134004c130008dd59825000982500098248011bac30470013047001304600237586088002607804c6eb4c108004c0e80b88c008004c004004894ccc0f80045200013370090011980100118208009119805001119baf30403039001002223232002302900133013002001222223370666e08cdc100280200119b820030012222232323370666e08004018cdc019b820030070013370400200866e0400400888c8c8008c010004cc0400080048c94ccc0d4cdc3a400000226464646464646464646464646464a66608c6092004264646464649319299982419b87480000044c8c8c8c94ccc13cc1480084c926301a00316375a60a000260a0004609c002608c0142a66609066e1d2002001132323232533304f3052002132498c06800c58dd698280009828001182700098230050a99982419b87480100044c8c8c8c8c8c94ccc144c1500084c926301c00516375a60a400260a40046eb4c140004c140008c138004c11802854ccc120cdc3a400c002264646464a66609e60a400426493180d0018b1bad30500013050002304e001304600a153330483370e900400089919299982698280010a4c2c6eb4c138004c11802854ccc120cdc3a4014002264646464a66609e60a40042930b1bad30500013050002375a609c002608c0142a66609066e1d200c001132323232533304f3052002132498c06800c58dd698280009828001182700098230050a99982419b87480380044c8c8c8c8c8c8c8c8c8c94ccc154c1600084c926302000916375a60ac00260ac0046eb4c150004c150008dd6982900098290011bad30500013050002304e001304600a153330483370e900800089919191919192999828982a0010a4c2c6eb4c148004c148008dd6982800098280011bad304e001304600a153330483370e90090008991919192999827982900109924c6606e006464a66609c66e1d20000011323232325333055305800213232498c084008c10800c58c158004c158008c150004c13000858c13000458dd6982800098280011bac304e001304600a163046009303600a3253330463370e900000089919299982598270010a4c2c6eb8c130004c11003054ccc118cdc3a40040022a66609260880182930b0b1822005981800618178068b1bad30470013047002375a608a002608a0046086002608600460820026082004607e002607e004607a002607a004607600260660042c6066002464a66606866e1d200000115333037303200214985854ccc0d0cdc3a40040022a66606e60640042930b0b1819000911191919191919299981d19baf009005100114a064a66607466e1d20000011323232533303d3370e90000008b0a99981e99b87480080044cdc79bae3042303b00500213371e6e50dd99821181d802801181d8021bae3040001303800813375e004980103d87980003038007303d001303d001303c002303a001303200122323300100100322533303700114c0103d87a8000132325333036300500213374a90001981d00125eb804cc010010004c0ec008c0e4004c0040048894ccc0c4cdc4a40280022666006006606e606e606e606e606e606e606e606e606e606e00466e0400520141330040020013001001222533302f3371200290000981a0010999801801981a80119b810014800888c8cc00400400c894ccc0c800452f5c026464a666062600a00426606a00466008008002266008008002606c004606800244444446464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a6660c0a6660c0060266e1c0840345288a999830299983019baf025011153330603375e04601e2a6660c066e1c06c01c54ccc180cdc380c8028a99983019b8f01700313375e02a00229405280a5014a029404c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc1d54ccc1d4cdd79ba6002374c0822a6660ea66ebcdd30009ba603e13370e66e0400c010cdc081b0110a5014a0266e9520003307903a330790383307900c330793750060660f26ea00b8cc1e40a8cc1e4dd48161983c8219983c9ba7330793750068660f26ea00c8cc1e4dd40041983c9ba800633079375006c97ae033079374e660f26ea0080cc1e4dd400f1983c9ba800733079375000a660f26ea00892f5c097ae016333304b333304b333304b333304b333304b4bd6f7b630244100488100482026fb80812011d200201301100600f00d00404800c002333304a333304a333304a333304a333304a4bd6f7b63024500488100482026fb80811c119200201201000600e00c00404700b00233304b03b04600a33304a03d04500933304903900b00933304803b00a008375a60e00046eb4c1b80054ccc1b0cc11002c0244cdd81ba83370200490404df7011ba83370200290404df701099bb037500046ea0004ccc1100d0028020ccc10c0d802401ccdd2a4000660da6ea40f4cc1b4dd4800a5eb80c110cc114c110cc11401c014c110cc11400c004dd7183600098360011bae306a0013062027375c60d000260d00046eb8c198004c1780945858c190004c190008dd7183100098310011bad30600013060002375a60bc00260bc0046eb4c170004c170008dd6982d000982d0011bad30580013058002305600130560023054001304c002303c00132533304d3370e900200089829182580a09981380f00a1825809982800098280011bae304e001304e002375a609800260980046eb4c128004c128008dd6982400098240011bad30460013046002375a608800260880046084002608400460800026070018607c002607c0046eacc0f0004c0f0004c0cc028dd5981c800981c801181b80098178039111299981699b870014800040104ccc888c8cc004004020894ccc0d40044cc0d8cdd81ba9008374c00a97adef6c6013232323253330363375e6600e01800498103d879800013303a337606ea4030dd30048028a99981b19b8f00c0021323253330383370e900000089981e19bb0375201c607a606c00400a200a606c00266601001800201226607466ec0dd48011ba6001330060060033756606e0066eb8c0d4008c0e4008c0dc004cc88c800cc8cc00400400c894ccc0d0004526132533303500114984c8c8c8c8c8c8c94ccc0e4cdc3a40000022660140146607a00c00a2c606e002660120040026eb8c0dc00cdd7181b001981d001981c001181b801181b8009981899bb037520046ea00052f5bded8c001044464a666062a66606800229445280a6103d87a800013374a90001981a9ba60014bd70191980080080191299981a80089981b19bb0375200e6ea00192f5bded8c0264646464a66606c66ebccc04402c00930103d879800013303a337606ea402cdd40050028a99981b19b8f00b0021323253330383370e900000089981e19bb0375201a607a606c00400a200a606c00264a66606e66e1c005200014c103d87a800013374a90001981d9ba80014bd7019b8000100a13303a337606ea4008dd4000998030030019bad3037003375c606a0046072004606e00201044a66605466e3c0092210013371e0029110014a044464664464a66605c66e1d200200110021375a60666058006605800464a66605866e1d200200114c103d87a8000132323300100100222533303200114c103d87a800013232323253330333371e014004266e95200033037375000297ae0133006006003375a60680066eb8c0c8008c0d8008c0d0004dd5981898150011815000a4000660180060044464a66605266e1d200030280011302e3027001163253330293370e90000008a60103d87a8000153330293370e900100089919198008008029129998178008a6103d87a800013232323253330303371e00e004266e952000330340014bd7009980300300198188019bae302f00230330023031001375c605c604e004266e9520003302d302e30270024bd70181380091299981399b9000200114c0103d8798000153330273371e0040022980103d87a800014c103d87b8000237260024466e280080048cdd79ba60014c101a0002323300100100222533302700114bd6f7b630099191919299981419b8f488100002100313302c337606ea4008dd3000998030030019bab3029003375c604e0046056004605200244646600200200644a66604e00229404c8c94ccc098cdc78010028a51133004004001302b002375c605200244446464646464646464a66605c606200426464646464646464646464a66606c66e1d200230350031323253330383370e9001181b80189919299981d19b8700e4800854ccc0e8cdc4800806899b8900d3370000603029405281bad303e001303600316375a607800260680062c6074002606400660700026060006606c002606c0046068002605801c6464666002002900024000444a66606266e1c00801040044c94ccc0c94ccc0c8cdc3801a4000266e1c005205a14a0266e08ccc010010cdc0001a400400490008a9998192999819299981919b8800148180528899b8800148180528899b88337009030240240022c266600800866e0000d20023370066e08009201433702002903019b8e006002371a0046eb4c0b8008dd718160008b1817800999119299981619b874800800440084dd59818981500198150011980580080425eb7bdb180dd59816800981680098120009815000981500098108021119198008008019129998128008a60103d87a800013232323253330263371e00e004266e9520003302a374c00297ae01330060060033756604e0066eb8c094008c0a4008c09c004c07003cc084004c06400458c07c004c07c008c074004c054020526136563253330173370e90000008991919191919299981018118010991924c6601200846eb4004c02401458dd6981080098108011bac301f001301f002301d0013015009153330173370e90010008991919191919191929998111812801099191924c6601800646eb4004cc02c0108dd680098058038b1bac302300130230023758604200260420046eb4c07c004c07c008c074004c05402454ccc05ccdc3a4008002264646464a66603c60420042649319299980e19b874800000454ccc07cc068010526161533301c3370e90010008a99980f980d0020a4c2c2c60340062c6eb4c07c004c07c008c074004c05402454ccc05ccdc3a400c00226464a666038603e0042930b1bad301d001301500916301500822323300100100322533301c00114984c8cc00c00cc080008c00cc0780048c94ccc058cdc3a4000002264646464a66603a6040004264649319299980e19b87480000044c8c94ccc084c0900084c92632533301f3370e9000000899192999812181380109924c601c0022c604a002603a0042a66603e66e1d200200113232323232325333028302b002149858dd6981480098148011bad30270013027002375a604a002603a0042c603a0022c604400260340062a66603866e1d20020011533301f301a00314985858c068008c01c00c58c078004c078008c070004c05000858c0500048c94ccc054cdc3a400000226464a666034603a0042930b1bae301b0013013002153330153370e900100089919299980d180e8010a4c2c6eb8c06c004c04c00858c04c004c0040148c94ccc04ccdc3a40000022646464646464646464646464646464646464a666050605600426464649319299981419b87480000044c8c94ccc0b4c0c00084c92632375a605a0046eb4c0ac00458c8cdd81817800981798180009bac302e0013026004153330283370e90010008a99981598130020a4c2c2c604c006602c020602a0222c605200260520046eb8c09c004c09c008dd6981280098128011bad30230013023002375a604200260420046eb4c07c004c07c008dd6980e800980e801180d800980d801180c80098088010b1808800919299980919b87480000044c8c8c8c94ccc064c07000852616375c603400260340046eb8c060004c04000858c040004dd68009bae001375c0026eb8004dd7000918029baa001230033754002ae6955ceaab9e5573eae815d0aba257461", - "hash": "ae9952ad72d86f103117d20ce8ff49d0b4180ae6644073244f98b8f5" + "compiledCode": "593b030100003232323232323232323223223223223223222232323232323232533301732323232533301b3370e9001180d000899191919191919191919191919191919191919191919191919191919191919299981d19b87480000744c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc14ccdc3a400060a40022646464a6660ac6660ac60ba032941288a99982b181900c8a99982b181a181a8078a99982b1981b0048010a99982b199981b8008058280250999999999991919191919191919191111111111919191919191919191919191919191919191919191919191919299984180a999841809981200d80189982e8010008a5013375e6e9c010dd39919998008008018010039111299984500801880089919299984600802080189919191919191919191919191919191919191919191919191919191919299985300a9998530099b884800000c54ccc29804cdc424000002266ebc0d401c5280a501333302302301e01c3253330a7013370e9000000899191919299985580a9998558099b88480000044ccc1d804003808852809919999999999982780e00b80081f81e80480381c81b81a813119b890030013253330ac013370e90000008a5114a06154020062c6eb4c2bc04004c2bc04008c2b404004c2940401854ccc29c04cdc3a4004002264646464a66615602a6661560266e212000001133307601000e02214a026466666666666609e03802e00207e07a01200e07206e06a04c466e2400400cc94ccc2b004cdc3a400000229445281855008018b1bad30af0100130af0100230ad0100130a501006153330a7013370e90020008991919191919299985680a9998568099b884800000c54ccc2b404cdc42400000226660f0024020048294052809919999999999982880f00c80082081f80580481d81c81b814129998578099b8900500114a2266e2400400cc94ccc2b804cdc3a400000229445281856008028b1bad30b10100130b101002375a615e02002615e02004615a02002614a0200c2a66614e0266e1d200600113232323253330ab0153330ab01337109000000899983b0080070110a5013333333333222222222232323232323232323232323232323232323232323232323232323232323253330d30153330d3013371090000020a9998698099b8900300413375e6e98004dd301f0a5014a02a6661a6020502661ae026ea0cdc099b8001e003002330d701375066e0407009ccc35c04dd419b8001a003330d701375066e0406009ccc35c04dd400b25eb804cc35c04dd419b8101e027330d701375066e04cdc000e0018011986b809ba83370203404e661ae026ea0cdc000c0019986b809ba80164bd700b199985280999985280999985280a5eb7bdb1812210048810002200700502600b0093370200600464a6661a40266e1d2000001132323333309b01004023022375a61aa020026eb4c35804004c8cdd8186c80800986c80986d008009bac30d80100130d00101f148000c340040794ccc34004cdc4012005899b803370666e08cdc100681200f19b823370201604866e0407807d20021653330cf01330a30100800613370200266e0008007c4004ccc28c040f801c014dd71869008009869008011bae30d00100130c801009375c619c02002619c020046eb8c33004004c3100401cdd69865008009865008011bad30c80100130c80100230c60100130c60100230c40100153330be010131330c201011330c201010330c201375001266184026ea001d2f5c026618402020661840202266184026ea001ccc30804dd4004a5eb80dd69861008009861008011bad30c00100130c001002375a617c02002617c020046eb4c2f004004c2f004008dd6985d008009929998560099b87480000045288a5030aa0100300103e03c00800603803603402516375a615e02002615e02004615a02002614a0200c2a66614e0266e1d20080011323253330a90153330a901337109000000899983a0070060100a5013232323232323232323232323232323232323232323232323232323232323253330c80153330c8013371290000040a9998640099b894800002454ccc32004cdc42400066e0002002454ccc32004cdc4810001099baf374c0026e980cc5280a5014a029404cc33004dd419b813370003e01000c66198026ea0cdc099b8001d009004330cc01375066e0006c020cc33004dd419b80019009330cc01375066e0005c0092f5c02c66661340266661340297adef6c604890048810002200c00a001375a6196020026196020046eb4c32404004c32404008dd6986380800991929998618099b880010021323232323232330cd01375064a6661940266e1d200000113232333333072008007059057375a619a020026eb4c33804004c8cdd81868808009868809869008009bac30d00100130c801053148000c32004148cc33405301010000330cd01375066e0ccdc119b803370401400200403066e04cdc100f00080125eb80dd69865008011bad30c80100133333306b01d01b002001053051375a618e020046eb4c31404004cccccc19c00c01006806014013854ccc30c04cdc400100089919191919191986680a61010000330cd01375064a6661940266e1d200000113232333333072008007059057375a619a020026eb4c33804004c8cdd81868808009868809869008009bac30d00100130c801053148000c32004148cc33404dd419b833370466e00cdc100480080100c19b813370404000200497ae0375a6194020046eb4c32004004cccccc1ac06c07400800414c144dd69863808011bad30c50100133333306700400301801a05004e1330c7014c1010000330c7014c1010000330c701375000497ae03370666e0800c04405ccdc199b8200101001853330c001330940100d00b13370200466e0007406c4008ccc250040bc020018ccc24c040b802c024dd71861008009861008011bae30c00100130b801049375c617c02002617c020046eb8c2f004004c2d00411cdd7185d00800985d008011bae30b80100130b001045375a616c02002616c020046eb4c2d004004c2d004008dd69859008009859008011bad30b00100130b001024375a615c020462c6eb4c2b404004c2940401854ccc29c04cdc3a4014002264646464a66615602a6661560266e212000003153330ab01337109000000899983b0080070110a5014a026464646464646464646464646464646464646464646464646464a66618a02a66618a0266e212000005153330c5013371203a0062a66618a0266e2406c0084cdd79ba6001374c06029405280a501330c901375066e0406800ccc32404dd419b81018002330c901375066e0405800ccc32404dd419b81014002330c901375066e040480152f5c02c666612e02666612e02666612e0297adef6c604890048810001f00f00d00200b009001375a618a020046eb4c30c04004cccc1a0058050004038ccc254040c000c004dd71862008009862008011bae30c20100130ba0104b375c6180020026180020046eb8c2f804004c2d804124dd7185e00800985e008011bae30ba0100130b201047375a6170020026170020046eb4c2d804004c2d804008dd6985a00800985a008011bad30b20100130b201026375a61600204a2c6eb4c2bc04004c2bc04008dd69856808009852808030a9998538099b87480300044c8c8c8c94ccc2ac054ccc2ac04cdc42400000226660ec02001c04429404ccccccccc888888888c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc338054ccc33804cdc4240000102a66619c0266e240900084cdd79ba6001374c07229405280a99986700811899869009ba83370203400c661a4026ea0cdc099b81018004002330d201375002c661a4026ea0cdc080a00119869009ba83370202401097ae01330d201375066e04cdc080d00100319869009ba833702030008661a4026ea0cdc080b00119869009ba8014330d201375066e040480212f5c02c66661400266661400297adef6c604890048810001e00e00c001375a61a20200261a2020046eb4c33c04004c33c04008dd6986680800991919191929998660081089919868809ba83253330ce013370e9000000899191999984b8080400f80f1bad30d101001375a61a4020026466ec0c35404004c35404c35804004dd6186a0080098660080f0a400061980203a661a202981010000330d101375066e000100052f5c06666612802004002008036034264661a2029801010000330d101375064a66619c0266e1d200000113232333330970100701f01e375a61a2020026eb4c34804004c8cdd8186a80800986a80986b008009bac30d40100130cc0101e148000c33004074cc34404dd419b800050014bd701999984a0080080100180d80d19b810150023370202c0046eb4c32c04008dd6986480800999983700980880080599984d8081b0018009bae30ca0100130ca01002375c6190020026180020286eb8c31804004c31804008dd7186200800985e00800a99985e80809080808089bad30c10100130c101002375a617e02002617e020046eb4c2f404004c2f404008dd6985d80800985d808011bad30b9010013253330ac013370e90000008a5114a061540200607c07807400c06807006c04a2c6eb4c2bc04004c2bc04008c2b404004c2940401854ccc29c04cdc3a401c002264646464646464646464a66616202a6661620266e212000007153330b101337109000003899b884800000c5280a5013232323232323232323232323253330be0153330be0100113232323232323253330c5013370e9001186200800899191919191919191919191919191919299986a8099b8748038c350040144c8c8c8c8c8c8c8c8c8c94ccc37c04cdc780c8330a99986f8099baf046017153330df013375e08802a2a6661be0266ebc10804c54ccc37c04cdc381e0068a99986f8099b8703a00b153330df013375e0800222a6661be0266ebc0dc02454ccc37c04cdc381a8038a99986f8099b87033005153330df013370e66e040c5200200313370e05e00229405280a5014a029405280a5014a029405280a50375a61c60200261c6020046eb4c38404004c38404008dd6986f80800986f808011bad30dd0100130dd0100230db0100130d30100516375a61b20200261b2020046eb4c35c04004c35c04008c35404004c35404008c34c04004c34c04008c34404004c34404008c33c04004c33c04008c33404004c31404c8c8008c24404004cc2740418c014dd71865808009861808008b186480800986080802186380800986380800986300801186200800985e0081a89998448081181081a899861009ba800b330c201375001266184026ea001ccc30804dd400299861009ba80034bd700b19b8748008c2f804dd51861008009861008011bad30c00100130c001002375a617c02002617c020046eb4c2f004004c2f004008dd6985d00800985d008011bad30b80100132323232323232323232323232323232323232323232323232323232323232323232323253330d60153330d6013371090000048a99986b0099b884800001c54ccc35804cdc4813003899baf374c0026e981045280a5014a0264a6661ae0204c2661b6026ea0cdc099b80025008005330db01375066e0408c018cc36c04dd419b80021008330db01375066e0407c018cc36c04dd400e9986d8099986b80800a6103d87a80004c0103d87980004bd7009986d809ba83370204a00c661b6026ea0cdc099b80023008005330db01375066e04084018cc36c04dd419b8001f008330db01375003a661b6026661ae02002980103d87a80004c0103d87980004bd70299986b0099b8848000cdc081424004266e240980185280b199985400999985400999985400a5eb7bdb1812210048810000100d00b3370000400802201e00a66e00cdc101899b81026480080bcccc2a00410c02c024c94ccc34c04cdc3a400000226464666661380200c0c40c06eb4c35804004dd6986b808009919bb030da0100130da0130db01001375861b20200261a2020b82900018688082d9999984c8080880780102f02e19b8100300153330cf01337120040022004200264646464a6661a40266e2120000011001163370600400266e08090008cdc099b823370404a00201a66e08cdc101182d00799b8105905b53330cd01330a10100900713370200200420026661420207801000c66e00cdc101400e8131bae30cf0100130cf01002375c619a02002618a020126eb8c32c04004c32c04008dd71864808009860808039bad30c70100130c701002375a618a02002618a02004618602002618602004618202002a6661760201426617e0209c6617e020986617e026ea0024cc2fc04dd4003a5eb804cc2fc04130cc2fc04138cc2fc04dd40039985f809ba80094bd701bad30bf0100130bf01002375a617a02002617a020046eb4c2ec04004c2ec04008dd6985c80800985c808169bad30b70102c3253330b2013370e90000008a5114a06160020122c6eb4c2d404004c2d404008dd69859808009859808011bad30b10100130b101002375a615e02002615e02004615a02002614a0200c2a66614e0266e1d2010001132323232323253330ad0153330ad013371090000028a9998568099b884800000c54ccc2b404cdc42400000226660f002402004829405280a5013232323232323232323232323232323232323232323232323232323232323253330cc0153330cc013371090000050a9998660099b8902000413375e6e98004dd301b8a5014a02661a0026ea0cdc099b8101f004008330d001375066e04cdc080e80100319868009ba833702036008661a0026ea0cdc080c80119868009ba83370202e01497ae016333309e01333309e01333309e014bd6f7b6302450048810002601401200301000e001375a619e02002619e020046eb4c33404004c33404008dd69865808009865808011bad30c9010013232323232323253330ca013371000200426464661a0026ea0c94ccc33404cdc3a4000002264646666612c0200a0b80b46eb4c34004004dd69868808009919bb030d40100130d40130d501001375861a6020026196020ac2900018658082a9986800a61010000330d001375066e04020008cc34004dd419b800070014bd70199998498080280200082c02b1999999983900300280200181101002b82a8a9998650099b8800200113232330d0014c1010000330d001375064a66619a0266e1d200000113232333330960100505c05a375a61a0020026eb4c34404004c8cdd8186a00800986a00986a808009bac30d30100130cb01056148000c32c04154cc34004dd419b80008001330d001375066e0401c0092f5c0666661260200800a0020b00ac666666660e400a00c0060080400440ae0aa26619c02981010000330ce014c1010000330ce01375000c6619c026ea00152f5c066e08010084cdc100200f19b81018002337020320046eb4c31c04008dd6986280800999983500b00a00080719984b808190018009bae30c60100130c601002375c61880200261780209a6eb8c30804004c30804008dd7186000800985c008259bae30be0100130be01002375c6178020026168020926eb4c2e804004c2e804008dd6985c00800985c008011bad30b60100130b601002375a6168020026168020506eb4c2c80409c58dd69858808009858808011bad30af0100130af01002375a615a02002614a0200c2c614a0200a2c6eb4c2a804004c2a804008dd6985400800985400801185300800985300801185200800985200801185100800985100801185000800985000801184f00800984b00801183000099299984b8099b87480100044c27004c254040144cc1b40cc014c25404010dd5984d00800984d00800984880805184b80800984b808011bab309501001309501001308c01001309201001309201001308901004309001005308e01004308e01004308c01003163305301f23232533308501333085013375e0020149412889998428099baf00101e4a09445281844808009840808009919980080080d25eb808894ccc21c0400840044ccc00c00cc22804008cc88cc22804cc14c018008004dd69844808010009982880f1191919191919191929998448099b87480080044cdc79bae308e0130870100201014a0610e0200261180200261080200261140200261040200261100200261100200260fe0026eb0c21004004c21004008dd61841008009841008011840008009840008011bae307e001307e002307c001307c002375a60f400260f40046eb4c1e0004c1e0008c1d8004c1d8008c1d0004c1d0008c1c8004c1a8ccccccc0f800800403002c018021281836801183580099299983519b8748000c1a40044c8c8c8c8c8c8c8c8c8c8c94ccc1e0c1ec0084c8c94ccc1dccdd7800803899299983d983f000899bb000a004163304801523375e60fa60ec60fa60ec60fa60fc60ec00200c2c60f600260e60022c60f200266088020466ebc008c1e4c1c8c1e4c1c8004c1dc004c1bc004c1d4004c1b4004c1cc004c1cc004c1a8004c1c0004c1a000458cc0d401c8cdd78029837983400098008009112999832001099baf374e00298010180001533306400114a0266600600660ce00460ce002444444444444646464646464646464646464646464646464646464646464646464646464a66610e02a66610e0266e2120000041533308701301f00313375e6e98004dd30148a5014a02a66610e02050266116026ea0cdc099b8001e0040023308b01375066e0407000ccc22c04dd419b8001a0043308b01375066e0406000ccc22c04dd400b25eb804cc22c04dd419b8101e0033308b01375066e04cdc000e00200119845809ba83370203400666116026ea0cdc000c00219845809ba80164bd700b199982c999982ca5eb7bdb181220100488100023007005002325333086013370e900000089919199998278028120119bad308901001375a6114020026466ec0c23404004c23404c23804004dd61846008009842008100a400061080203e6666609801a01600204003ea66610602660ae01000c266e04004cdc0010810080099982b8128038029bae308601001308601002375c61080200260f80126eb8c20804004c20804008dd7184000800983c0039bad307e001307e002375a60f800260f800460f400260f400460f0002a6660e40262660ec024660ec022660ec6ea0024cc1d8dd4003a5eb804cc1d8044cc1d8048cc1d8dd40039983b1ba80094bd701bad30760013076002375a60e800260e80046eb4c1c8004c1c8008dd6983800098380019bad306e00222222232323232337606ea0cdc0980819b81300f0013370466e0800c014cdc08028030009ba83370400466e04014018cdc119b8133704900200200280199b8248010cdc000380299b8233704900419b800060043370266e08018c030014cdc119b820070050043370466e0001400c010888888c8cdd81ba83370466e08014004018dd419b803370466e0800801001ccdc100280099b810010022222223370666e08cdc100300200119b823370400a006002444466ec0dd419b83337040040080026ea0cdc199b82002003001222222223232323370666e04c034cdc099b820010013370466e0920080023370466e08010024cdc099b8200700a3370400c01600266e0920040023370066e08cdc100300119b800070093370400a66e04cdc100400199b8200200a3370400800266e040040088cdc10008009299982b99b88001480005854ccc15ccdc3800a4000290000a99982b99b870014800852002153330573370e00290020a400426464666002002006004444a6660b666e200040084ccc00c00c004cdc199b803370600a0020029002080119b80337060029002240040a409c02a02400c07409203603229405280a5014a02940cc088050058dd7182c80098288008b182b800982780b1bab305500130550013054002375860a400260a400460a000260a0002609e002609c0046eacc130004c130004c12c008dd61824800982480098240011bac3046001303e028375a608800260880046eb0c108004c108008c100004c0e00b04c94ccc0eccdc3a400403c2646464646464646464646464a66608ea66608e66e1c00800454ccc11ccdc4a40080022a66608e604600e2604600a29405280a50132323232323232323232323232323232323232533305a3370e9000182c8008991919299982ea99982e981d981e0078a99982e9981e8048010999981f00080582b8288a5014a02666666666646464646444444444464646464646464646464646464646464a6660fc61020200426464a6661000261060200426464646464646464646464646464646464646464646464646464a66612e02a66612e0266e21200000315333097013371090000008a99984b8099983100580480d899baf02800714a02940528099299984c0099b87480480044c8c8c8c94ccc270054ccc27004cdc4240000022660ec06200629404c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc2f004cdc4810801099baf374c0026e980d4528199984700999984700a5eb7bdb181220100488100025006004001333305b0014800013c0854ccc2e404cc234040200184cdc080099b80025023100133308d01036007005375c6178020026178020046eb8c2e804004c2c804014dd7185c00800985c008011bae30b60100130ae010023253330b0013370e9000000880188029857008051929998578099b874800000440204018c2b404030c2c804004c2c804008c2c004004c2a00402cc2b804004c2b804008c2b004004c29004020c2a804004c2a804004c28404018c29c04004c29c04004c27804010cc1b00d0cdc0981f01a24004614602066660d400866e04c0f0011200230a10100316375a6140020026140020046eb0c27804004c25804018528184b008028b1bad309b01001309b01002375a613202002613202004612e02002612e02004612a02002612a0200461260200261260200461220200261220200261100200460a400264a6661120266e1d20040011308e0130870100513305f02300530870100437566118020026118020026106020126112020026112020046eacc21c04004c21c04004c1f8004c21004004c21004004c1ec00c58c20404004cc1300588c8c8c8c94ccc20004ccc20004cdd780180c2504a226661000266ebc0040212825114a061080200260f800261040200260f40022c60fe0026609402a46464646464646464a6661040266e1d200200113371e6eb8c21c04c20004008038528184000800984280800983e800984180800983d800984080800984080800983c000983e800983a800983d800983d8011bae30790013079001307800130770013076002307400130740013073001306a307100132323232325333073307600213232323232323253330773370e602c66090026466ebcc1f4c1d8c1f4c1d8c1f4c1f8c1d800400802854ccc1dccdc3980b19824009119baf307d3076307d307600100200a13233300100100d00c222533307d0021533307d00114bd700b09919299983f8018a5eb804cc20004c8c8c8c8c8c8c8c8c8c8c8c94ccc22004cdd78038010999999982d004806013012800811251163304d021003308b01001308301009308901001308901001308801002308601001307e001308401001308401001307b003308101003333005005001308201003308101003307f0021616307b00130730013079001307100130770013077001306e0011630740013303f00a23375e01060e860da00260180046601e0044660740100026601c004466072010002600200244a6660ca00229000099b8048008cc008008c1a0004c004004888894ccc198008400c4c8c94ccc1a000c40144c8c8c8ccccc028028004004010008c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc22404cdd780f801899191929998460099b88480000b44c8c94ccc23804cdc4240000042a66611c0264a66611e0266e1d20000011533308f013370e66e04cdc000c0180010088a9998478099b873370202c00601e2a66611e0266e1ccdc000a018006899b873370202400601629405280a501533308f013370e66e0406000c04454ccc23c04cdc399b813370002c06000401e2a66611e0266e1ccdc080a001806899b873370002406001629405280a50308d010061002161632533308e013370e9000000899191999982b8188120111bad309101001375a6124020026466ec0c25404004c25404c25804004dd6184a0080098460080f0a400061180203a666660a800400205a04003c2c6eb4c23404008dd69845808009929998450099b87480000044cdd81ba80133750022266ec0dd40089ba801330880100116308d01001308d01002308b01001308301020375a6112020026112020046eb4c21c04004c21c04008dd69842808009842808049bad308301008375a6104020026104020046eb4c20004004c20004008dd6983f000983f0021bad307c003375860f600260f60046eb0c1e4004c1e4004c1e0004c1dc008c1d4004c1d4008dd6983980098398011bad30710013071002306f001306f001306e0013065004306c004306a003306a003306800222323300100100322533306400114bd7009983298019bad306600133002002306700105905501501200604102101d01b163302901401e375c60c000260b00022c60bc00260ac0386eacc170004c170004c16c008dd6182c800982c801182b800982b800982b000982a8011bab305300130530013052002375860a000260a0002609e0046eb0c134004c1140bc58c008010c004014c004004894ccc1200045200013370090011980100118258009bac304700130470023758608a002608a0046eb4c10c004c10c008c104004c0e40b44c8c8c8c94ccc0fccdc3a40080442646464646464646464646464646464646464646464a6660a866e1d2000305300113232323232323232323232323232323232323232533306b306e0021323232323232533306e533306e3370e606003c90010a99983719b8733304301206a0664800854ccc1b8cdc39998218048340322400426098609a04829405280a501323232323232323232323232323232323232323232323232323232323232323232323232325333093015333093013375e0460222a6661260266ebc08403c54ccc24c04cdc380f8068a9998498099b8701d00b15333093013370e0360122a6661260266e3c05400c4cdd79ba6037374c05029405280a5014a02940528099299984a0099b87480000044c94ccc25404cdd781d8160a99984a809919299984b8080108008a503371266e08021200a3370401490504e0099b8933704012900a19b820074800840045280a50325333095013370e90010008a511323233223232533309b01002100114a066e24cdc10012400466e0800d200c3371266e0800920043370400290011bad309801001375a6132020026466ec0c27004004c27004c27404004dd6184d808009849808019849808010a99984a0099b8701a00815333094013370e03000c266ebc0500085280a5030920105316309701001309701002375c612a02002612a020046eb4c24c04004c24c04008dd69848808009848808011bad308f01001308f01002375a611a02002611a020046eb4c22c04004c22c04008c22404004c22404008c21c04004c1fc04cc21404004c21404008dd71841808009841808011bad308101001308101002375a60fe00260fe0046eb4c1f4004c1f4008dd6983d800983d8011bad30790013079002307700130770023075001306d0623303401c0011630720013072002375660e000260e000460dc00260cc0022c60d80026606e03e466ebcc1b0c194c1b0c194004024dd598350009835000983080098338009833800982f0009981600d80e9831800982d8019bab30610013061002305f0013057001305d001305d0013054001305a0013052001163301800f037375660ae00260ae0046eacc154004c154004c150004c14c004c148004c144008dd59827800982780098270011bac304c001304c001304b0023758609200260820566eb4c11c004c11c008c114004c0f40c44c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc148cdc3a400060a2002264646464646464646464646464a6660c460ca00426464646464646464646464646464a6660daa6660da66ebc05403454ccc1b4cdc3981780fa40042a6660da66ebc18002054ccc1b4cdc3999821009834832a40042a6660da66e1cccc10800419c18d20021304b304c02514a029405280a5014a0264646464646464646464646464646464646466ebcdd30129ba63333052333305201d0080063370466e054ccc1fccc14c0200184cdc0800a410137dc042002018900080200119b82337026660a804a00800401490009998298120038029bae308201001308201002375c61000200260f00146eb8c1f8004c1f8008dd7183e000983a0041bad307a001307a002375a60f000260f000260ee00460ea00260ea00460e600260d60c02c6eacc1c4004c1c4004c1a0004c1b8004c1b8004c194004cc0cc090098cc0ac054004c1a4004c1a4008dd5983380098338011832800982e8008b18318009981700c119baf3063305c3063305c00100230610013059003375660be00260be00460ba00260aa00260b600260b600260a400260b000260a00022c6602c01e06a6eacc154004c154008dd59829800982980098290009828800982800098278011bab304d001304d001304c00237586094002609400260920046eb0c11c004c0fc0a4dd69822800981e8189180100098008009129998208008a4000266e012002330020023044001223300900223375e608660780020044464640046058002660240040024444466e0ccdc119b820050040023370400600244444646466e0ccdc100080319b803370400600e00266e08004010cdc0800801119299981c99b87480000044c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc128c1340084c8c8c8c8c92632533304c3370e90000008991919192999829982b00109924c60340062c6eb4c150004c150008c148004c12802854ccc130cdc3a4004002264646464a6660a660ac00426493180d0018b1bad305400130540023052001304a00a1533304c3370e90020008991919191919299982a982c00109924c603800a2c6eb4c158004c158008dd6982a000982a001182900098250050a99982619b87480180044c8c8c8c94ccc14cc1580084c926301a00316375a60a800260a800460a400260940142a66609866e1d20080011323253330513054002149858dd6982900098250050a99982619b87480280044c8c8c8c94ccc14cc15800852616375a60a800260a80046eb4c148004c12802854ccc130cdc3a4018002264646464a6660a660ac00426493180d0018b1bad305400130540023052001304a00a1533304c3370e9007000899191919191919191919299982c982e00109924c60400122c6eb4c168004c168008dd6982c000982c0011bad30560013056002375a60a800260a800460a400260940142a66609866e1d2010001132323232323253330553058002149858dd6982b000982b0011bad30540013054002375a60a400260940142a66609866e1d201200113232323253330533056002132498cc0ec00c8c94ccc148cdc3a4000002264646464a6660b260b80042646493181080118230018b182d000982d001182c00098280010b18280008b1bad30540013054002375860a400260940142c6094012607401464a66609466e1d200000113232533304f3052002149858dd7182800098240060a99982519b874800800454ccc134c1200305261616304800b303400c303300d16375a609600260960046eb4c124004c124008c11c004c11c008c114004c114008c10c004c10c008c104004c104008c0fc004c0dc00858c0dc0048c94ccc0e0cdc3a40000022a666076606c0042930b0a99981c19b874800800454ccc0ecc0d800852616163036001222323232323232533303e3375e01200a20022940c94ccc0f8cdc3a40000022646464a66608266e1d200000116153330413370e9001000899b8f375c608c607e00a004266e3cdca1bb33046303f005002303f004375c60880026078010266ebc009300103d8798000303c007304100130410013040002303e001303600122323300100100322533303b00114c0103d87a800013232533303a300500213374a90001981f00125eb804cc010010004c0fc008c0f4004c0040048894ccc0d4cdc4a40280022666006006607660766076607660766076607660766076607600466e040052014133004002001300100122253330333371200290000981c0010999801801981c80119b810014800888c8cc00400400c894ccc0d800452f5c026464a66606a600a004266072004660080080022660080080026074004607000244444446464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a6660c8a6660c8060266e1c0840345288a999832299983219baf025011153330643375e04601e2a6660c866e1c06c01c54ccc190cdc380c8028a99983219b8f01700313375e02a00229405280a5014a029404c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc1e54ccc1e4cdd79ba6002374c0822a6660f266ebcdd30009ba603e13370e66e0400c010cdc081b0110a5014a0266e9520003307d03a3307d0383307d00c3307d3750060660fa6ea00b8cc1f40a8cc1f4dd48161983e8219983e9ba73307d3750068660fa6ea00c8cc1f4dd40041983e9ba80063307d375006c97ae03307d374e660fa6ea0080cc1f4dd400f1983e9ba80073307d375000a660fa6ea00892f5c097ae016333304b333304b333304b333304b333304b4bd6f7b630244100488100482026fb80812011d200201301100600f00d00404800c002333304a333304a333304a333304a333304a4bd6f7b63024500488100482026fb80811c119200201201000600e00c00404700b00233304b03b04600a33304a03d04500933304903900b00933304803b00a008375a60e80046eb4c1c80054ccc1c0cc11002c0244cdd81ba83370200490404df7011ba83370200290404df701099bb037500046ea0004ccc1100d0028020ccc10c0d802401ccdd2a4000660e26ea40f4cc1c4dd4800a5eb80c110cc114c110cc11401c014c110cc11400c004dd7183800098380011bae306e0013066027375c60d800260d80046eb8c1a8004c1880945858c1a0004c1a0008dd7183300098330011bad30640013064002375a60c400260c40046eb4c180004c180008dd6982f000982f0011bad305c001305c002305a001305a0023058001305000230400013253330513370e90020008982b182780a09981380f00a1827809982a000982a0011bae30520013052002375a60a000260a00046eb4c138004c138008dd6982600098260011bad304a001304a002375a60900026090004608c002608c00460880026078018608400260840046eacc100004c100004c0dc028dd5981e800981e801181d80098198039111299981899b870014800040104ccc888c8cc004004020894ccc0e40044cc0e8cdd81ba9008374c00a97adef6c60132323232533303a3375e6600e01800498103d879800013303e337606ea4030dd30048028a99981d19b8f00c00213232533303c3370e900000089982019bb0375201c6082607400400a200a607400266601001800201226607c66ec0dd48011ba600133006006003375660760066eb8c0e4008c0f4008c0ec004cc88c800cc8cc00400400c894ccc0e0004526132533303900114984c8c8c8c8c8c8c94ccc0f4cdc3a40000022660140146608200c00a2c6076002660120040026eb8c0ec00cdd7181d001981f001981e001181d801181d8009981a99bb037520046ea00052f5bded8c001044464a66606aa66607000229445280a6103d87a800013374a90001981c9ba60014bd70191980080080191299981c80089981d19bb0375200e6ea00192f5bded8c0264646464a66607466ebccc04402c00930103d879800013303e337606ea402cdd40050028a99981d19b8f00b00213232533303c3370e900000089982019bb0375201a6082607400400a200a607400264a66607666e1c005200014c103d87a800013374a90001981f9ba80014bd7019b8000100a13303e337606ea4008dd4000998030030019bad303b003375c6072004607a004607600201044a66605c66e3c0092210013371e0029110014a044464664464a66606466e1d200200110021375a606e6060006606000464a66606066e1d200200114c103d87a8000132323300100100222533303600114c103d87a800013232323253330373371e014004266e9520003303b375000297ae0133006006003375a60700066eb8c0d8008c0e8008c0e0004dd5981a98170011817000a4000660200060044464a66605a66e1d2000302c00113032302b0011632533302d3370e90000008a60103d87a80001533302d3370e900100089919198008008029129998198008a6103d87a800013232323253330343371e00e004266e952000330380014bd70099803003001981a8019bae303300230370023035001375c60646056004266e952000330313032302b0024bd70181580091299981599b9000200114c0103d87980001533302b3371e0040022980103d87a800014c103d87b8000237260024466e28008004c0040048894ccc0ac0084cdd79ba70014c10180001533302b00114a02666006006605c004605c002600200244a66605000229444c94ccc0a400452889919299981419981419198008008011129998168008a5013232533302c3370e00c00429444cc010010004c0c4008dd69817800a504a226600a00a0022940c0b400cdd698158011815800919baf374c002980101a0002323300100100222533302700114bd6f7b630099191919299981419b8f488100002100313302c337606ea4008dd3000998030030019bab3029003375c604e0046056004605200244646600200200644a66604e00229404c8c94ccc098cdc78010028a51133004004001302b002375c605200244446464646464646464a66605c606200426464646464646464646464a66606c66e1d200230350031323253330383370e9001181b80189919299981d19b8700e4800854ccc0e8cdc4800806899b8900d3370000603029405281bad303e001303600316375a607800260680062c6074002606400660700026060006606c002606c0046068002605801c6464666002002900024000444a66606266e1c00801040044c94ccc0c94ccc0c8cdc3801a4000266e1c005205a14a0266e08ccc010010cdc0001a400400490008a9998192999819299981919b8800148180528899b8800148180528899b88337009030240240022c266600800866e0000d20023370066e08009201433702002903019b8e006002371a0046eb4c0b8008dd718160008b1817800999119299981619b874800800440084dd59818981500198150011980580080425eb7bdb180dd59816800981680098120009815000981500098108021119198008008019129998128008a60103d87a800013232323253330263371e00e004266e9520003302a374c00297ae01330060060033756604e0066eb8c094008c0a4008c09c004c07003cc084004c06400458c07c004c07c008c074004c054020526136563253330173370e90000008991919191919299981018118010991924c6601200846eb4004c02401458dd6981080098108011bac301f001301f002301d0013015009153330173370e90010008991919191919191929998111812801099191924c6601800646eb4004cc02c0108dd680098058038b1bac302300130230023758604200260420046eb4c07c004c07c008c074004c05402454ccc05ccdc3a4008002264646464a66603c60420042649319299980e19b874800000454ccc07cc068010526161533301c3370e90010008a99980f980d0020a4c2c2c60340062c6eb4c07c004c07c008c074004c05402454ccc05ccdc3a400c00226464a666038603e0042930b1bad301d001301500916301500822323300100100322533301c00114984c8cc00c00cc080008c00cc0780048c94ccc058cdc3a4000002264646464a66603a6040004264649319299980e19b87480000044c8c94ccc084c0900084c92632533301f3370e9000000899192999812181380109924c601c0022c604a002603a0042a66603e66e1d200200113232323232325333028302b002149858dd6981480098148011bad30270013027002375a604a002603a0042c603a0022c604400260340062a66603866e1d20020011533301f301a00314985858c068008c01c00c58c078004c078008c070004c05000858c0500048c94ccc054cdc3a400000226464a666034603a0042930b1bae301b0013013002153330153370e900100089919299980d180e8010a4c2c6eb8c06c004c04c00858c04c004c0040148c94ccc04ccdc3a40000022646464646464646464646464646464646464a666050605600426464649319299981419b87480000044c8c94ccc0b4c0c00084c92632375a605a0046eb4c0ac00458c8cdd81817800981798180009bac302e0013026004153330283370e90010008a99981598130020a4c2c2c604c006602c020602a0222c605200260520046eb8c09c004c09c008dd6981280098128011bad30230013023002375a604200260420046eb4c07c004c07c008dd6980e800980e801180d800980d801180c80098088010b1808800919299980919b87480000044c8c8c8c94ccc064c07000852616375c603400260340046eb8c060004c04000858c040004dd68009bae001375c0026eb8004dd7000918029baa001230033754002ae6955ceaab9e5573eae815d0aba257461", + "hash": "c715281378c6d35ba1b150d5d7a3ddcbb942070d8313cf0fc8479f17" } ], "definitions": { diff --git a/validators/authen_minting_policy.ak b/validators/authen_minting_policy.ak index 982fff3..aaf3e63 100644 --- a/validators/authen_minting_policy.ak +++ b/validators/authen_minting_policy.ak @@ -12,18 +12,23 @@ use amm_dex_v2/types.{ use amm_dex_v2/utils validator( + // @out_ref is a Reference of an Unspent Transaction Output, + // which will only be spent on `MintFactoryAuthen` redeemer to make sure this redeemer can only be called once out_ref: OutputReference, + // the legitimate Factory TokenName factory_auth_asset_name: AssetName, + // the legitimate Pool TokenName pool_auth_asset_name: AssetName, ) { fn validate_authen(redeemer: AuthenRedeemer, context: ScriptContext) { let ScriptContext { transaction, purpose } = context expect Mint(authen_policy_id) = purpose when redeemer is { + // The redeemer can be called once to initialize the whole AMM V2 system MintFactoryAuthen -> { let Transaction { inputs, mint, outputs, datums, .. } = transaction - // Transaction must has @out_ref in the input to make sure that this redeemer can only be executed once + // validate that `out_ref` must be presented in the Transaction Inputs expect [_] = list.filter( inputs, @@ -32,15 +37,18 @@ validator( output_reference == out_ref }, ) + + // validate that the redeemer only mint a single Factory Token let mint_value = value.from_minted_value(mint) expect [(minted_pid, minted_an, minted_amount)] = value.flatten(mint_value) - // Transaction must mint only 1 Factory Auth Asset expect and { minted_pid == authen_policy_id, minted_an == factory_auth_asset_name, minted_amount == 1, } + // validate that there's only 1 Factory UTxO in the Transaction Outputs + // The Factory UTxO must contain Factory Token in the value expect [factory_output] = list.filter( outputs, @@ -63,6 +71,8 @@ validator( } CreatePool -> { let Transaction { inputs, mint, redeemers, .. } = transaction + // validate that there's a single Factory UTxO in the Transaction Inputs. + // Factory UTxO must contain Factory NFT Token in the value expect [factory_input] = list.filter( inputs, diff --git a/validators/factory_validator.ak b/validators/factory_validator.ak index 3a2eeb0..bc79bc5 100644 --- a/validators/factory_validator.ak +++ b/validators/factory_validator.ak @@ -11,10 +11,15 @@ use amm_dex_v2/types.{ use amm_dex_v2/utils validator( + // The PolicyID of Authen Minting Policy authen_policy_id: PolicyId, + // ValidatorHash of Pool Contract pool_hash: ValidatorHash, + // ValidationHash of Order Contract order_hash: ValidatorHash, + // the legitimate Factory TokenName factory_auth_asset_name: AssetName, + // the legitimate Pool TokenName pool_auth_asset_name: AssetName, ) { fn validate_factory( @@ -31,7 +36,7 @@ validator( asset_a let Asset { policy_id: asset_b_policy_id, asset_name: asset_b_asset_name } = asset_b - // Asset A must less than Asset B + // validate that Asset A and Asset B must be sorted expect utils.sorted_asset(asset_a, asset_b) let lp_asset_name = utils.compute_lp_asset_name( @@ -40,6 +45,8 @@ validator( asset_b_policy_id, asset_b_asset_name, ) + + // validate that there's single Factory UTxO in Transaction Input and contain single legitimate Factory NFT Token expect Some(factory_input) = list.find( inputs, @@ -48,9 +55,31 @@ validator( out_ref == factory_ref }, ) - let Input { output: factory_input_out, .. } = factory_input - let Output { value: factory_input_value, address: factory_address, .. } = - factory_input_out + let Input { + output: Output { + value: factory_input_value, + address: factory_address, + .. + }, + .. + } = factory_input + let Address { payment_credential: factory_payment_credential, .. } = + factory_address + expect [_] = + list.filter( + inputs, + fn(input) { + let Input { + output: Output { + address: Address { payment_credential: payment_cred, .. }, + .. + }, + .. + } = input + factory_payment_credential == payment_cred + }, + ) + // Transaction must have a Factory Asset in the Spending Script expect value.quantity_of( @@ -58,16 +87,25 @@ validator( authen_policy_id, factory_auth_asset_name, ) == 1 + // validate that there are only 2 Factory UTxOs in Transaction Outputs, + // they must contain single legitimate Factory NFT Token. expect [factory_output_1, factory_output_2] = list.filter( outputs, fn(output) { - let Output { address: out_addr, value: out_value, .. } = output - out_addr == factory_address && value.quantity_of( - out_value, - authen_policy_id, - factory_auth_asset_name, - ) == 1 + let Output { + address: Address { payment_credential: payment_cred, .. }, + value: out_value, + .. + } = output + and { + factory_payment_credential == payment_cred, + value.quantity_of( + out_value, + authen_policy_id, + factory_auth_asset_name, + ) == 1, + } }, ) let Output { datum: factory_output_1_raw_datum, .. } = factory_output_1 @@ -76,12 +114,19 @@ validator( utils.must_find_factory_datum(datums, factory_output_1_raw_datum) let FactoryDatum { head: new_head_2, tail: new_tail_2 } = utils.must_find_factory_datum(datums, factory_output_2_raw_datum) - // Transaction must put new hash between old hashes, (current_head, current_tail) -> (current_head, new_hash) && (new_hash, current_tail) - expect - builtin.less_than_bytearray(new_head_1, new_tail_1) && builtin.less_than_bytearray( - new_head_2, - new_tail_2, - ) && new_head_1 == current_head && new_tail_2 == current_tail && lp_asset_name == new_tail_1 && lp_asset_name == new_head_2 + // validate that new Factory UTxO datum must be followed by Linked List rule + // (old head, old tail) -> (old head, Pool LP Token Name) and (Pool LP Token Name, old tail) + // old head < Pool LP Token Name < old tail + expect and { + builtin.less_than_bytearray(new_head_1, new_tail_1), + builtin.less_than_bytearray(new_head_2, new_tail_2), + new_head_1 == current_head, + new_tail_2 == current_tail, + lp_asset_name == new_tail_1, + lp_asset_name == new_head_2, + } + // validate that there is a new Pool UTxO in Transaction Outputs. + // Pool UTxO must contain single Pool NFT Token expect [pool_output] = list.filter( outputs, @@ -90,12 +135,14 @@ validator( let Address { payment_credential: out_addr_payment_credential, .. } = out_addr when out_addr_payment_credential is { - ScriptCredential(hash) -> - pool_hash == hash && value.quantity_of( - out_value, - authen_policy_id, - pool_auth_asset_name, - ) == 1 + ScriptCredential(hash) -> and { + pool_hash == hash, + value.quantity_of( + out_value, + authen_policy_id, + pool_auth_asset_name, + ) == 1, + } _ -> False } }, @@ -113,6 +160,7 @@ validator( order_hash: pool_datum_order_hash, profit_sharing_opt: pool_datum_profit_sharing_opt, } = utils.must_find_pool_datum(datums, pool_output_raw_datum) + // profit_sharing must be empty expect None = pool_datum_profit_sharing_opt let estimated_amount_a = value.quantity_of( @@ -142,15 +190,15 @@ validator( |> value.add(authen_policy_id, lp_asset_name, remaining_liquidity) |> value.add(authen_policy_id, pool_auth_asset_name, 1) and { - // Asset A must be existed in Pool Datum + // asset_a and asset_b must be the same with Factory Redeemer pool_datum_asset_a == asset_a, - // Asset B must be existed in Pool Datum pool_datum_asset_b == asset_b, // Total Liquidity in PoolDatum must be sqrt(amount_a * amount_b) pool_datum_total_liquidity == total_liquidity, // Pool Reserve must be the same between datum and value pool_datum_reserve_a == amount_a, pool_datum_reserve_b == amount_b, + // trading_fee_percentage must be between **0.05%** and **10%** pool_validation.validate_trading_fee_percent( pool_datum_trading_fee_numerator, pool_datum_trading_fee_denominator, @@ -163,6 +211,12 @@ validator( pool_auth_asset_name: pool_auth_asset_name, lp_asset_name: lp_asset_name, ), + // Pool Value must only have necessary Token: + // - Asset A + // - Asset B + // - remaining LP Token (_MAX_INT64_ - _total_liquidity_) + // - 1 Pool NFT Token + // - 3 ADA (required ADA for an UTxO) expected_pool_out_value == pool_output_value, } } diff --git a/validators/order_validator.ak b/validators/order_validator.ak index 6d29811..d33d2a5 100644 --- a/validators/order_validator.ak +++ b/validators/order_validator.ak @@ -12,13 +12,17 @@ use amm_dex_v2/types.{ } use amm_dex_v2/utils -validator(stake_credential: StakeCredential) { +validator( + // the Stake Credential of Order Batching Validator + stake_credential: StakeCredential, +) { fn validate_order(raw_datum: Data, raw_redeemer: Data, context: ScriptContext) { expect ScriptContext { transaction, purpose: Spend(_) } = context expect redeemer: OrderRedeemer = raw_redeemer when redeemer is { ApplyOrder -> { let Transaction { withdrawals, .. } = transaction + // validate that an Order can be spent if there's a `Order Batching` validator in the `withdrawals` dict.has_key(withdrawals, stake_credential) } CancelOrder -> { @@ -27,13 +31,17 @@ validator(stake_credential: StakeCredential) { let OrderDatum { sender: Address { payment_credential, .. }, .. } = order_datum expect VerificationKeyCredential(owner_pkh) = payment_credential + // validate that the transaction has _sender_'s signature or _sender_ script UTxO in the Transaction Inputs list.has(extra_signatories, owner_pkh) } } } } -validator(pool_hash: ValidatorHash) { +validator( + // the hash of Liquidity Pool Script + pool_hash: ValidatorHash, +) { fn validate_order_spending_in_batching( redeemer: OrderBatchingRedeemer, context: ScriptContext, @@ -47,6 +55,7 @@ validator(pool_hash: ValidatorHash) { .. } = utils.list_at_index(inputs, pool_input_index) expect ScriptCredential(hash) = payment_credential + // validate that there's a Pool Input which have Address's Payment Credential matching with `pool_hash` pool_hash == hash } } diff --git a/validators/pool_validator.ak b/validators/pool_validator.ak index 51a80ff..35c754e 100644 --- a/validators/pool_validator.ak +++ b/validators/pool_validator.ak @@ -11,10 +11,15 @@ use amm_dex_v2/types.{ use amm_dex_v2/utils validator( + // The PolicyID of Authen Minting Policy authen_policy_id: PolicyId, + // Policy ID managed by the Minswap team, used for minting Batcher License and Admin License assets license_policy_id: PolicyId, + // the legitimate Pool TokenName pool_auth_asset_name: AssetName, + // The TokenName of Admin Asset, determined by Minswap team admin_asset_name: AssetName, + // Maximum expiration time of Batcher license from now (to prevent minting infinity license) maximum_deadline_range: Int, ) { fn validate_pool( @@ -46,10 +51,11 @@ validator( !builtin.null_list(input_indexes), // Input indexes must be unique utils.is_list_unique(input_indexes), - // Batching transaction won't mint anything + // validate Transaction won't mint any assets value.is_zero(value.from_minted_value(mint)), // Batcher with valid license token must be a signer of transaction list.has(extra_signatories, batcher_pkh), + // A valid license token is the token having expired timestamp as TokenName and must be within current time and current time + _maximum_deadline_range_ pool_validation.validate_batcher_license( license_input: license_input, validity_range: validity_range, @@ -77,9 +83,14 @@ validator( ) -> { let routing_in_indexes_len = list.length(routing_in_indexes) let routing_out_indexes_len = list.length(routing_out_indexes) - // TODO: Validate distinct indexes - expect - routing_in_indexes_len == routing_out_indexes_len && routing_out_indexes_len >= 2 + // validate routing_in_indexes and routing_out_indexes must be unique, + // have the same length and contain more than 1 element. + expect and { + routing_in_indexes_len == routing_out_indexes_len, + routing_out_indexes_len >= 2, + utils.is_list_unique(routing_in_indexes), + utils.is_list_unique(routing_out_indexes), + } let Transaction { inputs, outputs, @@ -89,23 +100,25 @@ validator( mint, .. } = transaction - // Doesn't mint anything - expect value.is_zero(value.from_minted_value(mint)) let Address { payment_credential: batcher_payment_credential, .. } = batcher_address expect VerificationKeyCredential(batcher_pkh) = batcher_payment_credential - // Verify Batcher with valid license token must be a signer of transaction - expect list.has(extra_signatories, batcher_pkh) // Batching Redeemer provides @license_index which help save calculation cost let license_input = utils.list_at_index(inputs, license_index) - expect - pool_validation.validate_batcher_license( - license_input: license_input, - validity_range: validity_range, - license_policy_id: license_policy_id, - maximum_deadline_range: maximum_deadline_range, - ) + expect and { + // validate Transaction won't mint any assets + value.is_zero(value.from_minted_value(mint)), + // Verify Batcher with valid license token must be a signer of transaction + list.has(extra_signatories, batcher_pkh), + // A valid license token is the token having expired timestamp as TokenName and must be within current time and current time + _maximum_deadline_range_ + pool_validation.validate_batcher_license( + license_input: license_input, + validity_range: validity_range, + license_policy_id: license_policy_id, + maximum_deadline_range: maximum_deadline_range, + ), + } pool_validation.validate_swap_multi_routing( authen_policy_id: authen_policy_id, pool_auth_asset_name: pool_auth_asset_name,