diff --git a/Cargo.lock b/Cargo.lock index 147584418..331e07e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4571,7 +4571,7 @@ dependencies = [ [[package]] name = "hydradx-adapters" -version = "1.2.3" +version = "1.2.4" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -4621,7 +4621,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "219.0.0" +version = "220.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -4633,6 +4633,8 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "ethabi", + "evm 0.39.1", + "fp-evm", "fp-rpc", "fp-self-contained", "frame-benchmarking", @@ -4745,7 +4747,7 @@ dependencies = [ [[package]] name = "hydradx-traits" -version = "3.0.0" +version = "3.0.1" dependencies = [ "frame-support", "impl-trait-for-tuples", @@ -8587,7 +8589,7 @@ dependencies = [ [[package]] name = "pallet-transaction-multi-payment" -version = "9.3.0" +version = "9.3.1" dependencies = [ "frame-support", "frame-system", @@ -11361,7 +11363,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.19.5" +version = "1.19.6" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", diff --git a/Cargo.toml b/Cargo.toml index ded90d71a..75d1c7174 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ 'pallets/dca', 'primitives', 'utils/build-script-utils', - 'integration-tests', + 'integration-tests', 'pallets/circuit-breaker', 'pallets/xcm-rate-limiter', 'pallets/omnipool-liquidity-mining', @@ -247,6 +247,8 @@ pallet-evm-precompile-simple = { git = "https://github.com/moonbeam-foundation/f pallet-evm-precompile-modexp = { git = "https://github.com/moonbeam-foundation/frontier", rev = "bf5885a982041cc744ecbb62a2afc13d56d464dc", default-features = false } pallet-evm-precompile-bn128 = { git = "https://github.com/moonbeam-foundation/frontier", rev = "bf5885a982041cc744ecbb62a2afc13d56d464dc", default-features = false } pallet-evm-precompile-blake2 = { git = "https://github.com/moonbeam-foundation/frontier", rev = "bf5885a982041cc744ecbb62a2afc13d56d464dc", default-features = false } +evm = { git = "https://github.com/moonbeam-foundation/evm", rev = "a33ac87ad7462b7e7029d12c385492b2a8311d1c", default-features = false } + # EVM from acala module-evm-utility-macro = { path = "runtime/hydradx/src/evm/evm-utility/macro", default-features = false} diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 6f69bc5e9..a0e40d004 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.19.5" +version = "1.19.6" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/evm.rs b/integration-tests/src/evm.rs index d5434af38..19695b289 100644 --- a/integration-tests/src/evm.rs +++ b/integration-tests/src/evm.rs @@ -6,6 +6,7 @@ use fp_rpc::runtime_decl_for_ethereum_runtime_rpc_api::EthereumRuntimeRPCApi; use frame_support::{assert_ok, dispatch::GetDispatchInfo, sp_runtime::codec::Encode, traits::Contains}; use frame_system::RawOrigin; use hex_literal::hex; +use hydradx_runtime::evm::ExtendedAddressMapping; use hydradx_runtime::{ evm::precompiles::{ addr, @@ -13,12 +14,13 @@ use hydradx_runtime::{ multicurrency::{Action, MultiCurrencyPrecompile}, Address, Bytes, EvmAddress, HydraDXPrecompiles, }, - AssetRegistry, Balances, CallFilter, Currencies, EVMAccounts, RuntimeCall, RuntimeOrigin, Tokens, TransactionPause, - EVM, + AssetRegistry, Balances, CallFilter, Currencies, EVMAccounts, Omnipool, RuntimeCall, RuntimeOrigin, Tokens, + TransactionPause, EVM, }; use orml_traits::MultiCurrency; use pallet_evm::*; use pretty_assertions::assert_eq; +use primitives::{AssetId, Balance}; use sp_core::{blake2_256, H160, H256, U256}; use sp_runtime::{traits::SignedExtension, FixedU128, Permill}; use std::borrow::Cow; @@ -983,6 +985,10 @@ mod currency_precompile { //H160::from(hex_literal::hex!("1000000000000000000000000000000000000001")) account_to_default_evm_address(&ALICE) } + + pub fn alice_substrate_evm_addr() -> AccountId { + ExtendedAddressMapping::into_account_id(alice_evm_addr()) + } } mod contract_deployment { @@ -1066,27 +1072,50 @@ fn dispatch_should_work_with_transfer() { TestNet::reset(); Hydra::execute_with(|| { + //Set up to idle state where the chain is not utilized at all + pallet_transaction_payment::pallet::NextFeeMultiplier::::put( + hydradx_runtime::MinimumMultiplier::get(), + ); + assert_ok!(EVMAccounts::bind_evm_address(hydradx_runtime::RuntimeOrigin::signed( + ALICE.into() + ))); + + let evm_address = EVMAccounts::evm_address(&Into::::into(ALICE)); + init_omnipool_with_oracle_for_block_10(); + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + ALICE.into(), + WETH, + (100 * UNITS * 1_000_000) as i128, + )); + assert_ok!(hydradx_runtime::MultiTransactionPayment::set_currency( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + WETH, + )); + //Arrange let data = hex!["4d0045544800d1820d45118d78d091e685490c674d7596e62d1f0000000000000000140000000f0000c16ff28623"] .to_vec(); - let balance = Tokens::free_balance(WETH, &evm_account()); + let balance = Tokens::free_balance(WETH, &AccountId::from(ALICE)); + + let (gas_price, _) = hydradx_runtime::DynamicEvmFee::min_gas_price(); //Act assert_ok!(EVM::call( - evm_signed_origin(evm_address()), - evm_address(), + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + evm_address, DISPATCH_ADDR, data, U256::from(0), 1000000, - gas_price(), + gas_price * 10, None, Some(U256::zero()), [].into() )); //Assert - assert!(Tokens::free_balance(WETH, &evm_account()) < balance - 10u128.pow(16)); + assert!(Tokens::free_balance(WETH, &AccountId::from(ALICE)) < balance - 10u128.pow(16)); }); } @@ -1124,8 +1153,8 @@ fn dispatch_should_respect_call_filter() { TestNet::reset(); Hydra::execute_with(|| { + init_omnipool_with_oracle_for_block_10(); //Arrange - let balance = Tokens::free_balance(WETH, &evm_account()); let amount = 10u128.pow(16); let gas_limit = 1000000; let transfer_call = RuntimeCall::Tokens(orml_tokens::Call::transfer { @@ -1141,27 +1170,40 @@ fn dispatch_should_respect_call_filter() { )); assert!(!CallFilter::contains(&transfer_call)); + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + currency_precompile::alice_substrate_evm_addr(), + WETH, + (100 * UNITS * 1_000_000) as i128, + )); + assert_ok!(hydradx_runtime::MultiTransactionPayment::set_currency( + evm_signed_origin(currency_precompile::alice_evm_addr()), + WETH, + )); + let balance = Tokens::free_balance(WETH, ¤cy_precompile::alice_substrate_evm_addr()); + + let (gas_price, _) = hydradx_runtime::DynamicEvmFee::min_gas_price(); //Act assert_ok!(EVM::call( - evm_signed_origin(evm_address()), - evm_address(), + evm_signed_origin(currency_precompile::alice_evm_addr()), + currency_precompile::alice_evm_addr(), DISPATCH_ADDR, transfer_call.encode(), U256::from(0), gas_limit, - gas_price(), + gas_price * 10, None, Some(U256::zero()), [].into(), )); //Assert - let new_balance = Tokens::free_balance(WETH, &evm_account()); + let new_balance = Tokens::free_balance(WETH, ¤cy_precompile::alice_substrate_evm_addr()); assert!(new_balance < balance, "fee wasn't charged"); assert!(new_balance > balance - amount, "more than fee was taken from account"); assert_eq!( new_balance, - balance - (U256::from(gas_limit) * gas_price()).as_u128(), + balance - (U256::from(gas_limit) * gas_price).as_u128(), "gas limit was not charged" ); assert_eq!( @@ -1174,137 +1216,327 @@ fn dispatch_should_respect_call_filter() { ); }); } + #[test] -fn compare_fee_between_evm_and_native_omnipool_calls() { +fn compare_fee_in_eth_between_evm_and_native_omnipool_calls() { TestNet::reset(); - Hydra::execute_with(|| { + let fee_currency = WETH; + let evm_address = EVMAccounts::evm_address(&Into::::into(ALICE)); + assert_ok!(EVMAccounts::bind_evm_address(hydradx_runtime::RuntimeOrigin::signed( + ALICE.into() + ))); + //Set up to idle state where the chain is not utilized at all pallet_transaction_payment::pallet::NextFeeMultiplier::::put( hydradx_runtime::MinimumMultiplier::get(), ); - //Set alice with as fee currency and fund it - assert_ok!(hydradx_runtime::MultiTransactionPayment::set_currency( - hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), - WETH, - )); + init_omnipool_with_oracle_for_block_10(); + assert_ok!(hydradx_runtime::Currencies::update_balance( hydradx_runtime::RuntimeOrigin::root(), ALICE.into(), WETH, - 1000000000 * UNITS as i128, + (10_000_000 * UNITS) as i128, + )); + assert_ok!(hydradx_runtime::MultiTransactionPayment::set_currency( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + fee_currency, )); - //Fund evm account with HDX to dispatch omnipool sell + // give alice evm addr seom weth to sell in omnipool assert_ok!(hydradx_runtime::Currencies::update_balance( hydradx_runtime::RuntimeOrigin::root(), - evm_account(), - HDX, - 100 * UNITS as i128, + ALICE.into(), + DOT, + (10 * UNITS) as i128, )); - init_omnipool_with_oracle_for_block_10(); - let treasury_eth_balance = Tokens::free_balance(WETH, &Treasury::account_id()); - let alice_weth_balance = Tokens::free_balance(WETH, &AccountId::from(ALICE)); + let treasury_currency_balance = Currencies::free_balance(fee_currency, &Treasury::account_id()); + let alice_currency_balance = Currencies::free_balance(fee_currency, &AccountId::from(ALICE)); //Act let omni_sell = hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { - asset_in: HDX, - asset_out: DAI, - amount: UNITS, + asset_in: DOT, + asset_out: HDX, + amount: 10_000_000_000, min_buy_amount: 0, }); - let gas_limit = 1000000; - let gas_price = hydradx_runtime::DynamicEvmFee::min_gas_price(); + let gas_limit = 1_000_000; + let (gas_price, _) = hydradx_runtime::DynamicEvmFee::min_gas_price(); - //Execute omnipool via EVM + //Execute omnipool sell via EVM assert_ok!(EVM::call( - evm_signed_origin(evm_address()), - evm_address(), + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + evm_address, DISPATCH_ADDR, omni_sell.encode(), U256::from(0), gas_limit, - gas_price.0 * 10, + gas_price * 10, None, Some(U256::zero()), [].into(), )); - //Pre dispatch the native omnipool call - so withdrawring only the fees for the execution + let new_treasury_currency_balance = Currencies::free_balance(fee_currency, &Treasury::account_id()); + let new_alice_currency_balance = Currencies::free_balance(fee_currency, &AccountId::from(ALICE)); + let evm_fee = alice_currency_balance - new_alice_currency_balance; + let treasury_evm_fee = new_treasury_currency_balance - treasury_currency_balance; + assert_eq!(treasury_evm_fee, evm_fee); + + //Pre dispatch the native omnipool call - so withdrawing only the fees for the execution let info = omni_sell.get_dispatch_info(); - assert_ok!( - pallet_transaction_payment::ChargeTransactionPayment::::from(0).pre_dispatch( - &AccountId::from(ALICE), - &omni_sell, - &info, - crate::fee_calculation::SWAP_ENCODED_LEN as usize, - ) + let len: usize = 146; + let pre = pallet_transaction_payment::ChargeTransactionPayment::::from(0) + .pre_dispatch(&AccountId::from(ALICE), &omni_sell, &info, len); + assert_ok!(&pre); + + let alice_currency_balance_pre_dispatch = Currencies::free_balance(fee_currency, &AccountId::from(ALICE)); + let native_fee = new_alice_currency_balance - alice_currency_balance_pre_dispatch; + assert!(evm_fee > native_fee); + + let fee_difference = evm_fee - native_fee; + assert!(fee_difference > 0); + + let relative_fee_difference = FixedU128::from_rational(fee_difference, native_fee); + let tolerated_fee_difference = FixedU128::from_rational(20, 100); + // EVM fees should be not higher than 20% + assert!(relative_fee_difference < tolerated_fee_difference); + }) +} + +#[test] +fn compare_fee_in_hdx_between_evm_and_native_omnipool_calls() { + TestNet::reset(); + + Hydra::execute_with(|| { + let fee_currency = HDX; + let evm_address = EVMAccounts::evm_address(&Into::::into(ALICE)); + assert_ok!(EVMAccounts::bind_evm_address(hydradx_runtime::RuntimeOrigin::signed( + ALICE.into() + ))); + + //Set up to idle state where the chain is not utilized at all + pallet_transaction_payment::pallet::NextFeeMultiplier::::put( + hydradx_runtime::MinimumMultiplier::get(), ); - //Determine fees and compare - let alice_new_weth_balance = Tokens::free_balance(WETH, &AccountId::from(ALICE)); - let fee_weth_native = alice_weth_balance - alice_new_weth_balance; + init_omnipool_with_oracle_for_block_10(); - let new_treasury_eth_balance = Tokens::free_balance(WETH, &Treasury::account_id()); - let fee_weth_evm = new_treasury_eth_balance - treasury_eth_balance; + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + ALICE.into(), + HDX, + (10_000 * UNITS) as i128, + )); + assert_ok!(hydradx_runtime::MultiTransactionPayment::set_currency( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + fee_currency, + )); - let fee_difference = fee_weth_evm - fee_weth_native; + // give alice evm addr seom weth to sell in omnipool + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + ALICE.into(), + DOT, + (10 * UNITS) as i128, + )); - let relative_fee_difference = FixedU128::from_rational(fee_difference, fee_weth_native); - let tolerated_fee_difference = FixedU128::from_rational(20, 100); + let treasury_currency_balance = Currencies::free_balance(fee_currency, &Treasury::account_id()); + let alice_currency_balance = Currencies::free_balance(fee_currency, &AccountId::from(ALICE)); - // EVM fees should be higher - assert!(fee_difference > 0); + //Act + let omni_sell = + hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: DOT, + asset_out: WETH, + amount: 10_000_000_000, + min_buy_amount: 0, + }); + + let gas_limit = 1_000_000; + let (gas_price, _) = hydradx_runtime::DynamicEvmFee::min_gas_price(); + + //Execute omnipool sell via EVM + assert_ok!(EVM::call( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + evm_address, + DISPATCH_ADDR, + omni_sell.encode(), + U256::from(0), + gas_limit, + gas_price * 10, + None, + Some(U256::zero()), + [].into(), + )); + + let new_treasury_currency_balance = Currencies::free_balance(fee_currency, &Treasury::account_id()); + let new_alice_currency_balance = Currencies::free_balance(fee_currency, &AccountId::from(ALICE)); + let evm_fee = alice_currency_balance - new_alice_currency_balance; + let treasury_evm_fee = new_treasury_currency_balance - treasury_currency_balance; + assert_eq!(treasury_evm_fee, evm_fee); + + //Pre dispatch the native omnipool call - so withdrawing only the fees for the execution + let info = omni_sell.get_dispatch_info(); + let len: usize = 146; + let pre = pallet_transaction_payment::ChargeTransactionPayment::::from(0) + .pre_dispatch(&AccountId::from(ALICE), &omni_sell, &info, len); + assert_ok!(&pre); + + let alice_currency_balance_pre_dispatch = Currencies::free_balance(fee_currency, &AccountId::from(ALICE)); + let native_fee = new_alice_currency_balance - alice_currency_balance_pre_dispatch; + assert!(evm_fee > native_fee); + let fee_difference = evm_fee - native_fee; + assert!(fee_difference > 0); + let relative_fee_difference = FixedU128::from_rational(fee_difference, native_fee); + let tolerated_fee_difference = FixedU128::from_rational(20, 100); // EVM fees should be not higher than 20% assert!(relative_fee_difference < tolerated_fee_difference); }) } +#[test] +fn fee_should_be_paid_in_hdx_when_no_currency_is_set() { + TestNet::reset(); + + Hydra::execute_with(|| { + let evm_address = EVMAccounts::evm_address(&Into::::into(ALICE)); + assert_ok!(EVMAccounts::bind_evm_address(hydradx_runtime::RuntimeOrigin::signed( + ALICE.into() + ))); + + //Set up to idle state where the chain is not utilized at all + pallet_transaction_payment::pallet::NextFeeMultiplier::::put( + hydradx_runtime::MinimumMultiplier::get(), + ); + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + ALICE.into(), + HDX, + 100_000_000_000_000i128, + )); + + init_omnipool_with_oracle_for_block_10(); + let treasury_hdx_balance = Currencies::free_balance(HDX, &Treasury::account_id()); + let alice_hdx_balance = Currencies::free_balance(HDX, &AccountId::from(ALICE)); + //Act + let omni_sell = + hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: DOT, + asset_out: WETH, + amount: 10_000_000, + min_buy_amount: 0, + }); + + let gas_limit = 1000000; + let gas_price = hydradx_runtime::DynamicEvmFee::min_gas_price(); + + //Execute omnipool via EVM + assert_ok!(EVM::call( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + evm_address, + DISPATCH_ADDR, + omni_sell.encode(), + U256::from(0), + gas_limit, + gas_price.0 * 10, + None, + Some(U256::zero()), + [].into(), + )); + //let alice_new_weth_balance = Tokens::free_balance(WETH, &AccountId::from(ALICE)); + let alice_new_hdx_balance = Currencies::free_balance(HDX, &AccountId::from(ALICE)); + let fee_amount = alice_hdx_balance - alice_new_hdx_balance; + assert!(fee_amount > 0); + + let new_treasury_hdx_balance = Currencies::free_balance(HDX, &Treasury::account_id()); + let treasury_hdx_diff = new_treasury_hdx_balance - treasury_hdx_balance; + assert_eq!(fee_amount, treasury_hdx_diff); + }) +} pub fn init_omnipool_with_oracle_for_block_10() { init_omnipol(); - //do_trade_to_populate_oracle(DAI, HDX, UNITS); - set_relaychain_block_number(10); - //do_trade_to_populate_oracle(DAI, HDX, UNITS); + hydradx_run_to_next_block(); + do_trade_to_populate_oracle(WETH, DOT, 1_000_000_000_000); + let to = 40; + let from = 11; + for _ in from..=to { + hydradx_run_to_next_block(); + do_trade_to_populate_oracle(DOT, HDX, 1_000_000_000_000); + do_trade_to_populate_oracle(WETH, DOT, 1_000_000_000_000); + } } +fn do_trade_to_populate_oracle(asset_1: AssetId, asset_2: AssetId, amount: Balance) { + assert_ok!(Tokens::set_balance( + RawOrigin::Root.into(), + CHARLIE.into(), + LRNA, + 1000000000000 * UNITS, + 0, + )); + + assert_ok!(Omnipool::sell( + RuntimeOrigin::signed(CHARLIE.into()), + LRNA, + asset_1, + amount, + Balance::MIN + )); + + assert_ok!(Omnipool::sell( + RuntimeOrigin::signed(CHARLIE.into()), + LRNA, + asset_2, + amount, + Balance::MIN + )); +} + +use frame_support::traits::fungible::Mutate; pub fn init_omnipol() { - let native_price = FixedU128::from_float(0.5); - let stable_price = FixedU128::from_float(0.7); + let native_price = FixedU128::from_rational(29903049701668757, 73927734532192294158); + let dot_price = FixedU128::from_rational(103158291366950047, 4566210555614178); let acc = hydradx_runtime::Omnipool::protocol_account(); - let stable_amount: Balance = 5_000_000_000_000_000_000_000u128; - let native_amount: Balance = 5_000_000_000_000_000_000_000u128; + let dot_amount: Balance = 4566210555614178u128; + let native_amount: Balance = 73927734532192294158u128; + let weth_amount: Balance = 1074271742496220564487u128; + let weth_price = FixedU128::from_rational(67852651072676287, 1074271742496220564487); assert_ok!(Tokens::set_balance( RawOrigin::Root.into(), acc.clone(), - DAI, - stable_amount, + DOT, + dot_amount, 0 )); - assert_ok!(Currencies::update_balance( + Balances::set_balance(&acc, native_amount); + assert_ok!(Tokens::set_balance(RawOrigin::Root.into(), acc, WETH, weth_amount, 0)); + assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - acc, HDX, - native_amount as i128, + native_price, + Permill::from_percent(60), + AccountId::from(ALICE), )); assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - HDX, - native_price, + DOT, + dot_price, Permill::from_percent(60), AccountId::from(ALICE), )); - assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - DAI, - stable_price, + WETH, + weth_price, Permill::from_percent(60), AccountId::from(ALICE), )); diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index 41dfa7c29..23636ff97 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -648,6 +648,7 @@ pub fn hydradx_run_to_next_block() { hydradx_runtime::System::on_initialize(b + 1); hydradx_runtime::EmaOracle::on_initialize(b + 1); hydradx_runtime::MultiTransactionPayment::on_initialize(b + 1); + hydradx_runtime::DynamicEvmFee::on_initialize(b + 1); hydradx_runtime::System::set_block_number(b + 1); } diff --git a/launch-configs/chopsticks/hydradx.yml b/launch-configs/chopsticks/hydradx.yml index 99664c37e..6fd777c7f 100644 --- a/launch-configs/chopsticks/hydradx.yml +++ b/launch-configs/chopsticks/hydradx.yml @@ -10,7 +10,8 @@ import-storage: - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY - - data: + - providers: 1 + data: free: 1000000000000000 Tokens: Accounts: @@ -29,6 +30,11 @@ import-storage: - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY - 5 # DOT - free: '100000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 20 # WETH + - free: '100000000000000000000' Council: Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] TechnicalCommittee: diff --git a/pallets/transaction-multi-payment/Cargo.toml b/pallets/transaction-multi-payment/Cargo.toml index cb2114ac1..c4ed31d2c 100644 --- a/pallets/transaction-multi-payment/Cargo.toml +++ b/pallets/transaction-multi-payment/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-transaction-multi-payment" -version = "9.3.0" +version = "9.3.1" description = "Transaction multi currency payment support module" authors = ["GalacticCoucil"] edition = "2021" diff --git a/pallets/transaction-multi-payment/src/lib.rs b/pallets/transaction-multi-payment/src/lib.rs index 67be14120..1a11a346f 100644 --- a/pallets/transaction-multi-payment/src/lib.rs +++ b/pallets/transaction-multi-payment/src/lib.rs @@ -45,7 +45,7 @@ use sp_std::marker::PhantomData; use frame_support::sp_runtime::FixedPointNumber; use frame_support::sp_runtime::FixedPointOperand; -use hydradx_traits::NativePriceOracle; +use hydradx_traits::{AccountFeeCurrency, NativePriceOracle}; use orml_traits::{GetByKey, Happened, MultiCurrency}; pub use crate::traits::*; @@ -356,64 +356,6 @@ impl DepositFee, BalanceOf> for Deposit } } -#[cfg(feature = "evm")] -use { - frame_support::traits::{Currency as PalletCurrency, Imbalance, OnUnbalanced}, - pallet_evm::{EVMCurrencyAdapter, OnChargeEVMTransaction}, - sp_core::{H160, U256}, - sp_runtime::traits::UniqueSaturatedInto, -}; -#[cfg(feature = "evm")] -type CurrencyAccountId = ::AccountId; -#[cfg(feature = "evm")] -type BalanceFor = <::Currency as PalletCurrency>>::Balance; -#[cfg(feature = "evm")] -type PositiveImbalanceFor = - <::Currency as PalletCurrency>>::PositiveImbalance; -#[cfg(feature = "evm")] -type NegativeImbalanceFor = - <::Currency as PalletCurrency>>::NegativeImbalance; - -#[cfg(feature = "evm")] -/// Implements the transaction payment for EVM transactions. -pub struct TransferEvmFees(PhantomData); - -#[cfg(feature = "evm")] -impl OnChargeEVMTransaction for TransferEvmFees -where - T: Config + pallet_evm::Config, - PositiveImbalanceFor: Imbalance, Opposite = NegativeImbalanceFor>, - NegativeImbalanceFor: Imbalance, Opposite = PositiveImbalanceFor>, - OU: OnUnbalanced>, - U256: UniqueSaturatedInto>, -{ - type LiquidityInfo = Option>; - - fn withdraw_fee(who: &H160, fee: U256) -> Result> { - EVMCurrencyAdapter::<::Currency, ()>::withdraw_fee(who, fee) - } - - fn can_withdraw(who: &H160, amount: U256) -> Result<(), pallet_evm::Error> { - EVMCurrencyAdapter::<::Currency, ()>::can_withdraw(who, amount) - } - fn correct_and_deposit_fee( - who: &H160, - corrected_fee: U256, - base_fee: U256, - already_withdrawn: Self::LiquidityInfo, - ) -> Self::LiquidityInfo { - ::Currency, OU> as OnChargeEVMTransaction< - T, - >>::correct_and_deposit_fee(who, corrected_fee, base_fee, already_withdrawn) - } - - fn pay_priority_fee(tip: Self::LiquidityInfo) { - if let Some(tip) = tip { - OU::on_unbalanced(tip); - } - } -} - /// Implements the transaction payment for native as well as non-native currencies pub struct TransferFees(PhantomData<(MC, DF, FR)>); @@ -573,3 +515,12 @@ impl GetByKey, Option> for Pallet { AcceptedCurrencyPrice::::get(k) } } + +/// Provides account's fee payment asset or default fee asset ( Native asset ) +impl AccountFeeCurrency for Pallet { + type AssetId = AssetIdOf; + + fn get(who: &T::AccountId) -> Self::AssetId { + Pallet::::account_currency(who) + } +} diff --git a/runtime/adapters/Cargo.toml b/runtime/adapters/Cargo.toml index ad2ef616d..494661492 100644 --- a/runtime/adapters/Cargo.toml +++ b/runtime/adapters/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-adapters" -version = "1.2.3" +version = "1.2.4" description = "Structs and other generic types for building runtimes." authors = ["GalacticCouncil"] edition = "2021" diff --git a/runtime/adapters/src/price.rs b/runtime/adapters/src/price.rs index 33c9191d0..1b05b7726 100644 --- a/runtime/adapters/src/price.rs +++ b/runtime/adapters/src/price.rs @@ -1,7 +1,16 @@ +use frame_support::traits::tokens::{Fortitude, Preservation}; +use frame_support::weights::Weight; +use hydra_dx_math::ema::EmaPrice; use hydradx_traits::price::PriceProvider; use hydradx_traits::router::{AssetPair, RouteProvider}; -use hydradx_traits::{OraclePeriod, PriceOracle}; +use hydradx_traits::{ + AccountFeeCurrency, AccountFeeCurrencyBalanceInCurrency, AggregatedPriceOracle, OraclePeriod, PriceOracle, +}; +use primitives::{AssetId, Balance}; use sp_core::Get; +use sp_runtime::helpers_128bit::multiply_by_rational_with_rounding; +use sp_runtime::traits::Convert; +use sp_runtime::Rounding; use sp_std::marker::PhantomData; pub struct OraclePriceProviderUsingRoute(PhantomData<(RP, OP, P)>); @@ -19,3 +28,55 @@ where OP::price(&route, P::get()) } } + +pub struct FeeAssetBalanceInCurrency(sp_std::marker::PhantomData<(T, C, AC, I)>); + +impl AccountFeeCurrencyBalanceInCurrency for FeeAssetBalanceInCurrency +where + T: pallet_ema_oracle::Config + frame_system::Config, + C: Convert<(AssetId, AssetId, Balance), Option<(Balance, EmaPrice)>>, + AC: AccountFeeCurrency, + I: frame_support::traits::fungibles::Inspect, +{ + type Output = (Balance, Weight); + + fn get_balance_in_currency(to_currency: AssetId, account: &T::AccountId) -> Self::Output { + let from_currency = AC::get(account); + let account_balance = I::reducible_balance(from_currency, account, Preservation::Preserve, Fortitude::Polite); + let mut price_weight = T::DbWeight::get().reads(2); // 1 read to get currency and 1 read to get balance + + if from_currency == to_currency { + return (account_balance, price_weight); + } + + // This is a workaround, as there is no other way to get weight of price retrieval, + // We get the weight from the ema-oracle weights to get price + // Weight * 2 because we are reading from the storage twice ( from_currency/lrna and lrna/to_currency) + // if this gets removed (eg. Convert returns weight), the constraint on T and ema-oracle is not necessary + price_weight.saturating_accrue(pallet_ema_oracle::Pallet::::get_price_weight().saturating_mul(2)); + + let Some((converted, _ )) = C::convert((from_currency, to_currency, account_balance)) else{ + return (0,price_weight); + }; + (converted, price_weight) + } +} + +pub struct ConvertAmount

(sp_std::marker::PhantomData

); + +// Converts `amount` of `from_currency` to `to_currency` using given oracle +// Input: (from_currency, to_currency, amount) +// Output: Option<(converted_amount, price)> +impl

Convert<(AssetId, AssetId, Balance), Option<(Balance, EmaPrice)>> for crate::price::ConvertAmount

+where + P: PriceProvider, +{ + fn convert((from_currency, to_currency, amount): (AssetId, AssetId, Balance)) -> Option<(Balance, EmaPrice)> { + if from_currency == to_currency { + return Some((amount, EmaPrice::one())); + } + let price = P::get_price(to_currency, from_currency)?; + let converted = multiply_by_rational_with_rounding(amount, price.n, price.d, Rounding::Up)?; + Some((converted, price)) + } +} diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index cae647349..cc2f696ab 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "219.0.0" +version = "220.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" @@ -20,6 +20,7 @@ scale-info = { version = "2.3.1", default-features = false, features = ["derive" smallvec = "1.9.0" log = { workspace = true } num_enum = { version = "0.5.1", default-features = false } +evm = { workspace = true, features = ["with-codec"] } # local dependencies primitives = { workspace = true } @@ -112,7 +113,6 @@ polkadot-xcm = { workspace = true } xcm-executor = { workspace = true } xcm-builder = { workspace = true } - # Substrate dependencies frame-benchmarking = { workspace = true, optional = true } frame-executive = { workspace = true } @@ -140,6 +140,7 @@ primitive-types = { workspace = true } # Frontier fp-rpc = { workspace = true } +fp-evm = { workspace = true } fp-self-contained = { workspace = true, features = ["serde"] } pallet-ethereum = { workspace = true } pallet-evm = { workspace = true } diff --git a/runtime/hydradx/src/evm/evm_fee.rs b/runtime/hydradx/src/evm/evm_fee.rs new file mode 100644 index 000000000..208dcf3ae --- /dev/null +++ b/runtime/hydradx/src/evm/evm_fee.rs @@ -0,0 +1,209 @@ +// : $$\ $$\ $$\ $$$$$$$\ $$\ $$\ +// !YJJ^ $$ | $$ | $$ | $$ __$$\ $$ | $$ | +// 7B5. ~B5^ $$ | $$ |$$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$ | $$ |\$$\ $$ | +// .?B@G ~@@P~ $$$$$$$$ |$$ | $$ |$$ __$$ |$$ __$$\ \____$$\ $$ | $$ | \$$$$ / +// :?#@@@Y .&@@@P!. $$ __$$ |$$ | $$ |$$ / $$ |$$ | \__|$$$$$$$ |$$ | $$ | $$ $$< +// ^?J^7P&@@! .5@@#Y~!J!. $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ __$$ |$$ | $$ |$$ /\$$\ +// ^JJ!. :!J5^ ?5?^ ^?Y7. $$ | $$ |\$$$$$$$ |\$$$$$$$ |$$ | \$$$$$$$ |$$$$$$$ |$$ / $$ | +// ~PP: 7#B5!. :?P#G: 7G?. \__| \__| \____$$ | \_______|\__| \_______|\_______/ \__| \__| +// .!P@G 7@@@#Y^ .!P@@@#. ~@&J: $$\ $$ | +// !&@@J :&@@@@P. !&@@@@5 #@@P. \$$$$$$ | +// :J##: Y@@&P! :JB@@&~ ?@G! \______/ +// .?P!.?GY7: .. . ^?PP^:JP~ +// .7Y7. .!YGP^ ?BP?^ ^JJ^ This file is part of https://github.com/galacticcouncil/HydraDX-node +// .!Y7Y#@@#: ?@@@G?JJ^ Built with <3 for decentralisation. +// !G@@@Y .&@@&J: +// ^5@#. 7@#?. Copyright (C) 2021-2023 Intergalactic, Limited (GIB). +// :5P^.?G7. SPDX-License-Identifier: Apache-2.0 +// :?Y! Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// http://www.apache.org/licenses/LICENSE-2.0 +use crate::TreasuryAccount; +use frame_support::traits::tokens::{Fortitude, Precision}; +use frame_support::traits::{Get, TryDrop}; +use hydra_dx_math::ema::EmaPrice; +use hydradx_traits::AccountFeeCurrency; +use pallet_evm::{AddressMapping, Error}; +use pallet_transaction_multi_payment::{DepositAll, DepositFee}; +use primitives::{AssetId, Balance}; +use sp_runtime::helpers_128bit::multiply_by_rational_with_rounding; +use sp_runtime::traits::Convert; +use sp_runtime::Rounding; +use sp_std::marker::PhantomData; +use { + frame_support::traits::OnUnbalanced, + pallet_evm::OnChargeEVMTransaction, + sp_core::{H160, U256}, + sp_runtime::traits::UniqueSaturatedInto, +}; + +#[derive(Copy, Clone, Default)] +pub struct EvmPaymentInfo { + amount: Balance, + asset_id: AssetId, + price: Price, +} + +impl EvmPaymentInfo { + pub fn merge(self, other: Self) -> Self { + EvmPaymentInfo { + amount: self.amount.saturating_add(other.amount), + asset_id: self.asset_id, + price: self.price, + } + } +} + +impl TryDrop for EvmPaymentInfo { + fn try_drop(self) -> Result<(), Self> { + if self.amount == 0 { + Ok(()) + } else { + Err(self) + } + } +} + +/// Implements the transaction payment for EVM transactions. +/// Supports multi-currency fees based on what is provided by AC - account currency. +pub struct TransferEvmFees(PhantomData<(OU, AC, EC, C, MC)>); + +impl OnChargeEVMTransaction for TransferEvmFees +where + T: pallet_evm::Config, + OU: OnUnbalanced>, + U256: UniqueSaturatedInto, + AC: AccountFeeCurrency, // AccountCurrency + EC: Get, // Evm default fee asset + C: Convert<(AssetId, AssetId, Balance), Option<(Balance, EmaPrice)>>, // Conversion from default fee asset to account currency + U256: UniqueSaturatedInto, + MC: frame_support::traits::tokens::fungibles::Mutate + + frame_support::traits::tokens::fungibles::Inspect, +{ + type LiquidityInfo = Option>; + + fn withdraw_fee(who: &H160, fee: U256) -> Result> { + if fee.is_zero() { + return Ok(None); + } + let account_id = T::AddressMapping::into_account_id(*who); + let fee_currency = AC::get(&account_id); + let Some((converted, price)) = C::convert((EC::get(), fee_currency, fee.unique_saturated_into())) else{ + return Err(Error::::WithdrawFailed); + }; + + // Ensure that converted fee is not zero + if converted == 0 { + return Err(Error::::WithdrawFailed); + } + + let burned = MC::burn_from( + fee_currency, + &account_id, + converted, + Precision::Exact, + Fortitude::Polite, + ) + .map_err(|_| Error::::BalanceLow)?; + + Ok(Some(EvmPaymentInfo { + amount: burned, + asset_id: fee_currency, + price, + })) + } + + fn can_withdraw(who: &H160, amount: U256) -> Result<(), pallet_evm::Error> { + let account_id = T::AddressMapping::into_account_id(*who); + let fee_currency = AC::get(&account_id); + let Some((converted, _)) = C::convert((EC::get(), fee_currency, amount.unique_saturated_into())) else{ + return Err(Error::::BalanceLow); + }; + + // Ensure that converted amount is not zero + if converted == 0 { + return Err(Error::::BalanceLow); + } + MC::can_withdraw(fee_currency, &account_id, converted) + .into_result(false) + .map_err(|_| Error::::BalanceLow)?; + Ok(()) + } + fn correct_and_deposit_fee( + who: &H160, + corrected_fee: U256, + _base_fee: U256, + already_withdrawn: Self::LiquidityInfo, + ) -> Self::LiquidityInfo { + if let Some(paid) = already_withdrawn { + let account_id = T::AddressMapping::into_account_id(*who); + + let adjusted_paid = if let Some(converted_corrected_fee) = multiply_by_rational_with_rounding( + corrected_fee.unique_saturated_into(), + paid.price.n, + paid.price.d, + Rounding::Up, + ) { + // Calculate how much refund we should return + let refund_amount = paid.amount.saturating_sub(converted_corrected_fee); + + // refund to the account that paid the fees. If this fails, the + // account might have dropped below the existential balance. In + // that case we don't refund anything. + let result = MC::mint_into(paid.asset_id, &account_id, refund_amount); + + let refund_imbalance = if let Ok(amount) = result { + // just in case of partial refund + // we are not expecting any imbalance, let's try to catch it in debug + debug_assert_eq!(amount, 0); + refund_amount.saturating_sub(amount) + } else { + // If error, we refund the whole amount back to treasury + refund_amount + }; + // figure out how much is left to mint back + // refund_amount already minted back to account, imbalance is what is left to mint if any + paid.amount + .saturating_sub(refund_amount) + .saturating_add(refund_imbalance) + } else { + // if conversion failed for some reason, we refund the whole amount back to treasury + paid.amount + }; + + // We can simply refund all the remaining amount back to treasury + OU::on_unbalanced(EvmPaymentInfo { + amount: adjusted_paid, + asset_id: paid.asset_id, + price: paid.price, + }); + return None; + } + None + } + + fn pay_priority_fee(tip: Self::LiquidityInfo) { + if let Some(tip) = tip { + OU::on_unbalanced(tip); + } + } +} + +pub struct DepositEvmFeeToTreasury; +impl OnUnbalanced> for DepositEvmFeeToTreasury { + // this is called for substrate-based transactions + fn on_unbalanceds(amounts: impl Iterator>) { + Self::on_unbalanced(amounts.fold(EvmPaymentInfo::default(), |i, x| x.merge(i))) + } + + // this is called from pallet_evm for Ethereum-based transactions + // (technically, it calls on_unbalanced, which calls this when non-zero) + fn on_nonzero_unbalanced(payment_info: EvmPaymentInfo) { + let result = DepositAll::::deposit_fee( + &TreasuryAccount::get(), + payment_info.asset_id, + payment_info.amount, + ); + debug_assert_eq!(result, Ok(())); + } +} diff --git a/runtime/hydradx/src/evm/mod.rs b/runtime/hydradx/src/evm/mod.rs index 10457b067..994c8db70 100644 --- a/runtime/hydradx/src/evm/mod.rs +++ b/runtime/hydradx/src/evm/mod.rs @@ -19,31 +19,38 @@ // you may not use this file except in compliance with the License. // http://www.apache.org/licenses/LICENSE-2.0 +use crate::evm::runner::WrapRunner; +use crate::types::ShortOraclePrice; pub use crate::{ evm::accounts_conversion::{ExtendedAddressMapping, FindAuthorTruncated}, AssetLocation, Aura, NORMAL_DISPATCH_RATIO, }; -use crate::{DCAOraclePeriod, NativeAssetId, TreasuryAccount, LRNA}; +use crate::{NativeAssetId, LRNA}; use frame_support::{ parameter_types, - traits::{Defensive, FindAuthor, Imbalance, OnUnbalanced}, + traits::{Defensive, FindAuthor}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, ConsensusEngineId, }; use hex_literal::hex; +use hydradx_adapters::price::ConvertAmount; use hydradx_adapters::{AssetFeeOraclePriceProvider, OraclePriceProvider}; +use hydradx_traits::oracle::OraclePeriod; use orml_tokens::CurrencyAdapter; +use pallet_currencies::fungibles::FungibleCurrencies; use pallet_evm::EnsureAddressTruncated; -use pallet_transaction_multi_payment::{DepositAll, DepositFee, TransferEvmFees}; use pallet_transaction_payment::Multiplier; use polkadot_xcm::{ latest::MultiLocation, prelude::{AccountKey20, PalletInstance, Parachain, X3}, }; -use primitives::{constants::chain::MAXIMUM_BLOCK_WEIGHT, AccountId, AssetId}; +use primitives::{constants::chain::MAXIMUM_BLOCK_WEIGHT, AssetId}; use sp_core::{Get, U256}; + mod accounts_conversion; +mod evm_fee; pub mod precompiles; +mod runner; // Current approximation of the gas per second consumption considering // EVM execution over compiled WASM (on 4.4Ghz CPU). @@ -92,21 +99,6 @@ impl Get for WethAssetId { } type WethCurrency = CurrencyAdapter; -use frame_support::traits::Currency as PalletCurrency; - -type NegativeImbalance = >::NegativeImbalance; - -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - // this is called for substrate-based transactions - fn on_unbalanceds(_: impl Iterator) {} - - // this is called from pallet_evm for Ethereum-based transactions - // (technically, it calls on_unbalanced, which calls this when non-zero) - fn on_nonzero_unbalanced(amount: NegativeImbalance) { - let _ = DepositAll::::deposit_fee(&TreasuryAccount::get(), WethAssetId::get(), amount.peek()); - } -} parameter_types! { pub PostLogContent: pallet_ethereum::PostLogContent = pallet_ethereum::PostLogContent::BlockAndTxnHashes; @@ -130,6 +122,8 @@ parameter_types! { /// The amount of gas per storage (in bytes): BLOCK_GAS_LIMIT / BLOCK_STORAGE_LIMIT /// The current definition of BLOCK_STORAGE_LIMIT is 40 KB, resulting in a value of 366. pub GasLimitStorageGrowthRatio: u64 = 366; + + pub const OracleEvmPeriod: OraclePeriod = OraclePeriod::Short; } impl pallet_evm::Config for crate::Runtime { @@ -142,11 +136,26 @@ impl pallet_evm::Config for crate::Runtime { type FeeCalculator = crate::DynamicEvmFee; type FindAuthor = FindAuthorTruncated; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; - type OnChargeTransaction = TransferEvmFees; + type OnChargeTransaction = evm_fee::TransferEvmFees< + evm_fee::DepositEvmFeeToTreasury, + crate::MultiTransactionPayment, // Get account's fee payment asset + WethAssetId, + ConvertAmount, + FungibleCurrencies, // Multi currency support + >; type OnCreate = (); type PrecompilesType = precompiles::HydraDXPrecompiles; type PrecompilesValue = PrecompilesValue; - type Runner = pallet_evm::runner::stack::Runner; + type Runner = WrapRunner< + Self, + pallet_evm::runner::stack::Runner, // Evm runner that we wrap + hydradx_adapters::price::FeeAssetBalanceInCurrency< + crate::Runtime, + ConvertAmount, + crate::MultiTransactionPayment, // Get account's fee payment asset + FungibleCurrencies, // Account balance inspector + >, + >; type RuntimeEvent = crate::RuntimeEvent; type WeightPerGas = WeightPerGas; type WithdrawOrigin = EnsureAddressTruncated; @@ -198,7 +207,7 @@ impl pallet_dynamic_evm_fee::Config for crate::Runtime { crate::Router, OraclePriceProvider, crate::MultiTransactionPayment, - DCAOraclePeriod, + OracleEvmPeriod, >; type WethAssetId = WethAssetId; type WeightInfo = crate::weights::dynamic_evm_fee::HydraWeight; diff --git a/runtime/hydradx/src/evm/runner.rs b/runtime/hydradx/src/evm/runner.rs new file mode 100644 index 000000000..13d8443a6 --- /dev/null +++ b/runtime/hydradx/src/evm/runner.rs @@ -0,0 +1,263 @@ +// : $$\ $$\ $$\ $$$$$$$\ $$\ $$\ +// !YJJ^ $$ | $$ | $$ | $$ __$$\ $$ | $$ | +// 7B5. ~B5^ $$ | $$ |$$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$ | $$ |\$$\ $$ | +// .?B@G ~@@P~ $$$$$$$$ |$$ | $$ |$$ __$$ |$$ __$$\ \____$$\ $$ | $$ | \$$$$ / +// :?#@@@Y .&@@@P!. $$ __$$ |$$ | $$ |$$ / $$ |$$ | \__|$$$$$$$ |$$ | $$ | $$ $$< +// ^?J^7P&@@! .5@@#Y~!J!. $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ __$$ |$$ | $$ |$$ /\$$\ +// ^JJ!. :!J5^ ?5?^ ^?Y7. $$ | $$ |\$$$$$$$ |\$$$$$$$ |$$ | \$$$$$$$ |$$$$$$$ |$$ / $$ | +// ~PP: 7#B5!. :?P#G: 7G?. \__| \__| \____$$ | \_______|\__| \_______|\_______/ \__| \__| +// .!P@G 7@@@#Y^ .!P@@@#. ~@&J: $$\ $$ | +// !&@@J :&@@@@P. !&@@@@5 #@@P. \$$$$$$ | +// :J##: Y@@&P! :JB@@&~ ?@G! \______/ +// .?P!.?GY7: .. . ^?PP^:JP~ +// .7Y7. .!YGP^ ?BP?^ ^JJ^ This file is part of https://github.com/galacticcouncil/HydraDX-node +// .!Y7Y#@@#: ?@@@G?JJ^ Built with <3 for decentralisation. +// !G@@@Y .&@@&J: +// ^5@#. 7@#?. Copyright (C) 2021-2023 Intergalactic, Limited (GIB). +// :5P^.?G7. SPDX-License-Identifier: Apache-2.0 +// :?Y! Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// http://www.apache.org/licenses/LICENSE-2.0 +//! EVM stack-based runner. +//! This runner is a wrapper around the default stack-based runner that adds possibility to charge fees in +//! different currencies and to validate transactions based on the account's fee payment asset. +//! +//! Shamelessly copied from pallet-evm and modified to support multi-currency fees. +use crate::evm::WethAssetId; +use fp_evm::{Account, InvalidEvmTransactionError}; +use frame_support::traits::Get; +use hydradx_traits::AccountFeeCurrencyBalanceInCurrency; +use pallet_evm::runner::Runner; +use pallet_evm::{AddressMapping, CallInfo, Config, CreateInfo, FeeCalculator, RunnerError}; +use pallet_genesis_history::migration::Weight; +use primitive_types::{H160, H256, U256}; +use primitives::{AssetId, Balance}; +use sp_runtime::traits::UniqueSaturatedInto; +use sp_std::vec::Vec; + +pub struct WrapRunner(sp_std::marker::PhantomData<(T, R, B)>); + +impl Runner for WrapRunner +where + T: Config, + R: Runner, + >::Error: core::convert::From, + B: AccountFeeCurrencyBalanceInCurrency, +{ + type Error = R::Error; + + fn validate( + source: H160, + target: Option, + input: Vec, + value: U256, + gas_limit: u64, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + access_list: Vec<(H160, Vec)>, + is_transactional: bool, + weight_limit: Option, + proof_size_base_cost: Option, + evm_config: &evm::Config, + ) -> Result<(), RunnerError> { + let (base_fee, mut weight) = T::FeeCalculator::min_gas_price(); + + let evm_currency = WethAssetId::get(); + let account_id = T::AddressMapping::into_account_id(source); + let account_nonce = frame_system::Pallet::::account_nonce(&account_id); + let (balance, b_weight) = B::get_balance_in_currency(evm_currency, &account_id); + + let (source_account, inner_weight) = ( + Account { + nonce: U256::from(UniqueSaturatedInto::::unique_saturated_into(account_nonce)), + balance: U256::from(UniqueSaturatedInto::::unique_saturated_into(balance)), + }, + T::DbWeight::get().reads(1).saturating_add(b_weight), + ); + weight = weight.saturating_add(inner_weight); + + let _ = fp_evm::CheckEvmTransaction::::new( + fp_evm::CheckEvmTransactionConfig { + evm_config, + block_gas_limit: T::BlockGasLimit::get(), + base_fee, + chain_id: T::ChainId::get(), + is_transactional, + }, + fp_evm::CheckEvmTransactionInput { + chain_id: Some(T::ChainId::get()), + to: target, + input, + nonce: nonce.unwrap_or(source_account.nonce), + gas_limit: gas_limit.into(), + gas_price: None, + max_fee_per_gas, + max_priority_fee_per_gas, + value, + access_list, + }, + weight_limit, + proof_size_base_cost, + ) + .validate_in_block_for(&source_account) + .and_then(|v| v.with_base_fee()) + .and_then(|v| v.with_balance_for(&source_account)) + .map_err(|error| RunnerError { error, weight })?; + Ok(()) + } + + fn call( + source: H160, + target: H160, + input: Vec, + value: U256, + gas_limit: u64, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + access_list: Vec<(H160, Vec)>, + is_transactional: bool, + validate: bool, + weight_limit: Option, + proof_size_base_cost: Option, + config: &evm::Config, + ) -> Result> { + if validate { + Self::validate( + source, + Some(target), + input.clone(), + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list.clone(), + is_transactional, + weight_limit, + proof_size_base_cost, + config, + )?; + } + // Validated, flag set to false + R::call( + source, + target, + input, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + is_transactional, + false, + weight_limit, + proof_size_base_cost, + config, + ) + } + + fn create( + source: H160, + init: Vec, + value: U256, + gas_limit: u64, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + access_list: Vec<(H160, Vec)>, + is_transactional: bool, + validate: bool, + weight_limit: Option, + proof_size_base_cost: Option, + config: &evm::Config, + ) -> Result> { + if validate { + Self::validate( + source, + None, + init.clone(), + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list.clone(), + is_transactional, + weight_limit, + proof_size_base_cost, + config, + )?; + } + // Validated, flag set to false + R::create( + source, + init, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + is_transactional, + false, + weight_limit, + proof_size_base_cost, + config, + ) + } + + fn create2( + source: H160, + init: Vec, + salt: H256, + value: U256, + gas_limit: u64, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + access_list: Vec<(H160, Vec)>, + is_transactional: bool, + validate: bool, + weight_limit: Option, + proof_size_base_cost: Option, + config: &evm::Config, + ) -> Result> { + if validate { + Self::validate( + source, + None, + init.clone(), + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list.clone(), + is_transactional, + weight_limit, + proof_size_base_cost, + config, + )?; + } + //Validated, flag set to false + R::create2( + source, + init, + salt, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + is_transactional, + false, + weight_limit, + proof_size_base_cost, + config, + ) + } +} diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 12c71594c..26db93153 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -36,6 +36,7 @@ mod assets; pub mod evm; mod governance; mod system; +pub mod types; pub mod xcm; pub use assets::*; @@ -107,7 +108,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 219, + spec_version: 220, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/hydradx/src/types.rs b/runtime/hydradx/src/types.rs new file mode 100644 index 000000000..822bfd395 --- /dev/null +++ b/runtime/hydradx/src/types.rs @@ -0,0 +1,18 @@ +use crate::*; +use frame_support::parameter_types; + +use hydradx_adapters::OraclePriceProvider; + +use hydradx_adapters::price::OraclePriceProviderUsingRoute; +use hydradx_traits::OraclePeriod; + +parameter_types! { + pub const LastBlockPeriod: OraclePeriod = OraclePeriod::LastBlock; + pub const ShortPeriod: OraclePeriod = OraclePeriod::Short; +} + +// Helper aliases for the OraclePriceProvider using the Router and EmaOracle +pub type LastBlockOraclePrice = + OraclePriceProviderUsingRoute, LastBlockPeriod>; +pub type ShortOraclePrice = + OraclePriceProviderUsingRoute, ShortPeriod>; diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 4d28a2019..fbe8b31e3 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-traits" -version = "3.0.0" +version = "3.0.1" description = "Shared traits" authors = ["GalacticCouncil"] edition = "2021" diff --git a/traits/src/lib.rs b/traits/src/lib.rs index b0dae6a27..670a3ab33 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -256,3 +256,15 @@ pub trait AMMPosition { shares_amount: Balance, ) -> Result<(Balance, Balance), Self::Error>; } + +/// Provides account's fee payment asset +pub trait AccountFeeCurrency { + type AssetId; + fn get(a: &AccountId) -> Self::AssetId; +} + +/// Provides account's balance of fee asset currency in a given currency +pub trait AccountFeeCurrencyBalanceInCurrency { + type Output; + fn get_balance_in_currency(to_currency: AssetId, account: &AccountId) -> Self::Output; +}