diff --git a/Cargo.lock b/Cargo.lock index ac8f91865..08ea0b48e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4621,7 +4621,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "215.0.0" +version = "216.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -4671,6 +4671,7 @@ dependencies = [ "pallet-dca", "pallet-democracy 4.1.0", "pallet-duster", + "pallet-dynamic-evm-fee", "pallet-dynamic-fees", "pallet-elections-phragmen", "pallet-ema-oracle", @@ -7498,6 +7499,33 @@ dependencies = [ "substrate-wasm-builder", ] +[[package]] +name = "pallet-dynamic-evm-fee" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hydra-dx-math", + "hydradx-traits", + "log", + "orml-tokens", + "orml-traits", + "pallet-balances", + "pallet-currencies", + "pallet-evm", + "pallet-transaction-payment", + "parity-scale-codec", + "primitives", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "test-utils", +] + [[package]] name = "pallet-dynamic-fees" version = "1.0.2" @@ -11333,7 +11361,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.19.1" +version = "1.19.2" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -11377,6 +11405,7 @@ dependencies = [ "pallet-dca", "pallet-democracy 4.1.0", "pallet-duster", + "pallet-dynamic-evm-fee", "pallet-dynamic-fees", "pallet-elections-phragmen", "pallet-ema-oracle", @@ -11435,6 +11464,7 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", + "test-utils", "tokio", "xcm-emulator", ] diff --git a/Cargo.toml b/Cargo.toml index 77d585a91..ded90d71a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ 'runtime/hydradx/src/evm/evm-utility/macro', 'pallets/referrals', 'pallets/evm-accounts', + 'pallets/dynamic-evm-fee' ] [workspace.dependencies] @@ -62,6 +63,7 @@ pallet-currencies = { path = "pallets/currencies", default-features = false } pallet-dca = { path = "pallets/dca", default-features = false } pallet-duster = { path = "pallets/duster", default-features = false } pallet-dynamic-fees = { path = "pallets/dynamic-fees", default-features = false } +pallet-dynamic-evm-fee = { path = "pallets/dynamic-evm-fee", default-features = false } pallet-ema-oracle = { path = "pallets/ema-oracle", default-features = false } pallet-genesis-history = { path = "pallets/genesis-history", default-features = false } pallet-liquidity-mining = { path = "pallets/liquidity-mining", default-features = false } diff --git a/Makefile b/Makefile index 84973e399..d51132178 100644 --- a/Makefile +++ b/Makefile @@ -65,4 +65,4 @@ release: build checksum all: clippy build-benchmarks test-benchmarks test build checksum chopstics: release - npx @acala-network/chopsticks xcm --parachain=launch-configs/chopsticks/hydradx.yml --parachain=launch-configs/chopsticks/assethub.yml + npx @acala-network/chopsticks xcm --parachain=launch-configs/chopsticks/hydradx.yml --parachain=launch-configs/chopsticks/assethub.yml \ No newline at end of file diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index c8bf8e541..02046ce6a 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.19.1" +version = "1.19.2" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" @@ -34,6 +34,7 @@ pallet-relaychain-info = { workspace = true } pallet-route-executor = { workspace = true} pallet-dca = { workspace = true} pallet-dynamic-fees = { workspace = true } +pallet-dynamic-evm-fee = { workspace = true } pallet-staking = { workspace = true} pallet-lbp = { workspace = true} pallet-xyk = { workspace = true} @@ -127,8 +128,9 @@ polkadot-runtime = { workspace = true } [dev-dependencies] pretty_assertions = "1.2.1" +pallet-relaychain-info = { workspace = true } xcm-emulator = { workspace = true } - +test-utils = { workspace = true } [features] default = ["std"] std = [ @@ -194,6 +196,7 @@ std = [ "hydradx-runtime/std", "pallet-staking/std", "scraper/std", + "pallet-dynamic-evm-fee/std" ] # we don't include integration tests when benchmarking feature is enabled diff --git a/integration-tests/src/cross_chain_transfer.rs b/integration-tests/src/cross_chain_transfer.rs index 523fc63b6..00abde8a5 100644 --- a/integration-tests/src/cross_chain_transfer.rs +++ b/integration-tests/src/cross_chain_transfer.rs @@ -53,15 +53,13 @@ fn hydra_should_receive_asset_when_transferred_from_polkadot_relay_chain() { ); }); - let fees = 401884032343; Hydra::execute_with(|| { + let fee = hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()); + assert!(fee > 0, "Fees is not sent to treasury"); + assert_eq!( hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), - BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fees - ); - assert_eq!( - hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()), - fees + BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fee ); }); } @@ -141,16 +139,13 @@ fn hydra_should_receive_asset_when_transferred_from_acala() { ); }); - let fee = 321507225875; Hydra::execute_with(|| { + let fee = hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()); + assert!(fee > 0, "Fees is not sent to treasury"); assert_eq!( hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 30 * UNITS - fee ); - assert_eq!( - hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()), - fee // fees should go to treasury - ); }); } @@ -331,7 +326,7 @@ fn claim_trapped_asset_should_work() { Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), - 1000 * UNITS + 29_758_869_580_594 + 1_029_939_717_395_149 //1000 * UNITS + 30 * UNITS - fee ); let origin = MultiLocation::new(1, X1(Parachain(ACALA_PARA_ID))); diff --git a/integration-tests/src/dca.rs b/integration-tests/src/dca.rs index 7a34902fa..75f0c3009 100644 --- a/integration-tests/src/dca.rs +++ b/integration-tests/src/dca.rs @@ -442,7 +442,7 @@ mod omnipool { run_to_block(11, 40); //Assert - assert_balance!(ALICE.into(), DAI, ALICE_INITIAL_DAI_BALANCE + 600 * UNITS); + assert_balance!(ALICE.into(), DAI, ALICE_INITIAL_DAI_BALANCE + 700 * UNITS); //Because the last trade is not enough for a whole trade, it is returned to the user let amount_in = 140_421_094_431_120; diff --git a/integration-tests/src/evm.rs b/integration-tests/src/evm.rs index d73b5ea08..d5434af38 100644 --- a/integration-tests/src/evm.rs +++ b/integration-tests/src/evm.rs @@ -24,7 +24,7 @@ use sp_runtime::{traits::SignedExtension, FixedU128, Permill}; use std::borrow::Cow; use xcm_emulator::TestExt; -const TREASURY_ACCOUNT_INIT_BALANCE: Balance = 1000 * UNITS; +pub const TREASURY_ACCOUNT_INIT_BALANCE: Balance = 1000 * UNITS; mod account_conversion { use super::*; @@ -244,8 +244,8 @@ mod account_conversion { let fee_raw = hydradx_runtime::TransactionPayment::compute_fee_details(len, &info, 0); let fee = fee_raw.final_fee(); - // simple test that the fee is approximately 10 HDX - assert!(fee / UNITS == 10); + // simple test that the fee is approximately 10/4 HDX (it was originally 10 HDX, but we divided the fee by 4 in the config) + assert_eq!(fee / UNITS, 10 / 4); }); } @@ -1174,12 +1174,16 @@ fn dispatch_should_respect_call_filter() { ); }); } - #[test] fn compare_fee_between_evm_and_native_omnipool_calls() { 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(), + ); + //Set alice with as fee currency and fund it assert_ok!(hydradx_runtime::MultiTransactionPayment::set_currency( hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), @@ -1189,7 +1193,7 @@ fn compare_fee_between_evm_and_native_omnipool_calls() { hydradx_runtime::RuntimeOrigin::root(), ALICE.into(), WETH, - 100 * UNITS as i128, + 1000000000 * UNITS as i128, )); //Fund evm account with HDX to dispatch omnipool sell @@ -1214,6 +1218,8 @@ fn compare_fee_between_evm_and_native_omnipool_calls() { }); let gas_limit = 1000000; + let gas_price = hydradx_runtime::DynamicEvmFee::min_gas_price(); + //Execute omnipool via EVM assert_ok!(EVM::call( evm_signed_origin(evm_address()), @@ -1222,7 +1228,7 @@ fn compare_fee_between_evm_and_native_omnipool_calls() { omni_sell.encode(), U256::from(0), gas_limit, - gas_price(), + gas_price.0 * 10, None, Some(U256::zero()), [].into(), @@ -1230,13 +1236,12 @@ fn compare_fee_between_evm_and_native_omnipool_calls() { //Pre dispatch the native omnipool call - so withdrawring only the fees for the execution let info = omni_sell.get_dispatch_info(); - let len: usize = 1; assert_ok!( pallet_transaction_payment::ChargeTransactionPayment::::from(0).pre_dispatch( &AccountId::from(ALICE), &omni_sell, &info, - len, + crate::fee_calculation::SWAP_ENCODED_LEN as usize, ) ); @@ -1260,7 +1265,7 @@ fn compare_fee_between_evm_and_native_omnipool_calls() { }) } -fn init_omnipool_with_oracle_for_block_10() { +pub fn init_omnipool_with_oracle_for_block_10() { init_omnipol(); //do_trade_to_populate_oracle(DAI, HDX, UNITS); set_relaychain_block_number(10); @@ -1313,10 +1318,10 @@ pub fn init_omnipol() { // TODO: test that we charge approximatelly same fee on evm as with extrinsics directly -const DISPATCH_ADDR: H160 = addr(1025); +pub const DISPATCH_ADDR: H160 = addr(1025); -fn gas_price() -> U256 { - U256::from(8 * 10_u128.pow(7)) +pub fn gas_price() -> U256 { + U256::from(hydradx_runtime::evm::DEFAULT_BASE_FEE_PER_GAS) } fn create_dispatch_handle(data: Vec) -> MockHandle { diff --git a/integration-tests/src/fee_calculation.rs b/integration-tests/src/fee_calculation.rs new file mode 100644 index 000000000..c9f5ad059 --- /dev/null +++ b/integration-tests/src/fee_calculation.rs @@ -0,0 +1,246 @@ +#![cfg(test)] + +use crate::evm::DISPATCH_ADDR; +use crate::{oracle::hydradx_run_to_block, polkadot_test_net::*}; +use frame_support::assert_ok; +use frame_support::dispatch::DispatchClass; +use frame_support::dispatch::GetDispatchInfo; +use hydradx_runtime::Tokens; +use hydradx_runtime::TransactionPayment; +use hydradx_runtime::EVM; +use orml_traits::MultiCurrency; +use pallet_evm::FeeCalculator; +use primitives::constants::currency::UNITS; +use primitives::constants::time::HOURS; +use sp_core::Encode; +use sp_core::U256; +use sp_runtime::{FixedU128, Permill}; +use test_utils::assert_eq_approx; +use xcm_emulator::TestExt; + +pub const SWAP_ENCODED_LEN: u32 = 146; //We use this as this is what the UI send as length when omnipool swap is executed +const HDX_USD_SPOT_PRICE: f64 = 0.0266; //Current HDX price in USD on CoinGecko on 22th Feb, 2024 +pub const ETH_USD_SPOT_PRICE: f64 = 2907.92; //Current HDX price in USD on CoinGecko on 22th Feb, 2024 + +#[ignore] +#[test] +fn min_swap_fee() { + TestNet::reset(); + Hydra::execute_with(|| { + pallet_transaction_payment::pallet::NextFeeMultiplier::::put( + hydradx_runtime::MinimumMultiplier::get(), + ); + + let call = hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: DOT, + asset_out: 2, + amount: UNITS, + min_buy_amount: 0, + }); + + let info = call.get_dispatch_info(); + let info_len = 146; + let fee = TransactionPayment::compute_fee(info_len, &info, 0); + let fee_in_cent = FixedU128::from_float(fee as f64 * HDX_USD_SPOT_PRICE).div(UNITS.into()); + let tolerance = FixedU128::from((2, (UNITS / 10_000))); + println!("Swap tx fee in cents: {fee_in_cent:?}"); + + assert_eq_approx!( + fee_in_cent, + FixedU128::from_float(0.009_909_846_329_778), + tolerance, + "The min fee should be ~0.01$ (1 cent)" + ); + }); +} + +#[ignore] +#[test] +fn max_swap_fee() { + TestNet::reset(); + Hydra::execute_with(|| { + pallet_transaction_payment::pallet::NextFeeMultiplier::::put( + hydradx_runtime::MaximumMultiplier::get(), + ); + + let call = hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: DOT, + asset_out: 2, + amount: UNITS, + min_buy_amount: 0, + }); + + let info = call.get_dispatch_info(); + let info_len = 146; //We use this as this is what the UI send as length when omnipool swap is executed + let fee = TransactionPayment::compute_fee(info_len, &info, 0); + let fee_in_cent = FixedU128::from_float(fee as f64 * HDX_USD_SPOT_PRICE).div(UNITS.into()); + let tolerance = FixedU128::from((2, (UNITS / 10_000))); + assert_eq_approx!( + fee_in_cent, + FixedU128::from_float(10.008_401_718_494_405), + tolerance, + "The max fee should be ~1000 cent (10$)" + ); + }); +} + +#[ignore] +#[test] +fn substrate_and_evm_fee_growth_simulator_with_genesis_chain() { + TestNet::reset(); + + Hydra::execute_with(|| { + let prod_init_multiplier = FixedU128::from_u32(1); + + pallet_transaction_payment::pallet::NextFeeMultiplier::::put(prod_init_multiplier); + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + evm_account(), + HDX, + 1000000 * UNITS as i128, + )); + + init_omnipool(); + let block_weight = hydradx_runtime::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap(); + + for (nonce, b) in (2..HOURS).enumerate() { + //=HOURS { + hydradx_run_to_block(b); + hydradx_runtime::System::set_block_consumed_resources(block_weight, 0); + let call = + hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: HDX, + asset_out: 2, + amount: 10 * UNITS, + min_buy_amount: 10000, + }); + + let info = call.get_dispatch_info(); + let fee = TransactionPayment::compute_fee(SWAP_ENCODED_LEN, &info, 0); + + let fee_in_cent = (fee as f64 * HDX_USD_SPOT_PRICE) / 1000000000000.0; + let fee_in_cent = round(fee_in_cent); + + let evm_fee_in_cent = round(get_evm_fee_in_cent(nonce as u128)); + let next = TransactionPayment::next_fee_multiplier(); + + let gas_price = hydradx_runtime::DynamicEvmFee::min_gas_price(); + + println!("{b:?} - fee: ${fee_in_cent:?} - evm_fee: ${evm_fee_in_cent:?} - multiplier: {next:?} - gas {gas_price:?}"); + } + }); +} + +#[ignore] +#[test] +fn substrate_and_evm_fee_growth_simulator_with_idle_chain() { + TestNet::reset(); + + Hydra::execute_with(|| { + //We simulate that the chain has no activity so the MinimumMultiplier kept diverged to absolute minimum + pallet_transaction_payment::pallet::NextFeeMultiplier::::put( + hydradx_runtime::MinimumMultiplier::get(), + ); + assert_ok!(hydradx_runtime::Currencies::update_balance( + hydradx_runtime::RuntimeOrigin::root(), + evm_account(), + HDX, + 1000000 * UNITS as i128, + )); + + init_omnipool(); + let block_weight = hydradx_runtime::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap(); + + for (nonce, b) in (2..HOURS).enumerate() { + //=HOURS { + hydradx_run_to_block(b); + hydradx_runtime::System::set_block_consumed_resources(block_weight, 0); + let call = + hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: HDX, + asset_out: 2, + amount: 10 * UNITS, + min_buy_amount: 10000, + }); + + let info = call.get_dispatch_info(); + let fee = TransactionPayment::compute_fee(SWAP_ENCODED_LEN, &info, 0); + + let fee_in_cent = (fee as f64 * HDX_USD_SPOT_PRICE) / 1000000000000.0; + let fee_in_cent = round(fee_in_cent); + + let evm_fee_in_cent = round(get_evm_fee_in_cent(nonce as u128)); + let next = TransactionPayment::next_fee_multiplier(); + + let gas_price = hydradx_runtime::DynamicEvmFee::min_gas_price(); + + println!("{b:?} - fee: ${fee_in_cent:?} - evm_fee: ${evm_fee_in_cent:?} - multiplier: {next:?} - gas {gas_price:?}"); + } + }); +} + +pub fn get_evm_fee_in_cent(nonce: u128) -> f64 { + let treasury_eth_balance = Tokens::free_balance(WETH, &Treasury::account_id()); + + let omni_sell = hydradx_runtime::RuntimeCall::Omnipool(pallet_omnipool::Call::::sell { + asset_in: HDX, + asset_out: DAI, + amount: UNITS, + 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( + evm_signed_origin(evm_address()), + evm_address(), + DISPATCH_ADDR, + omni_sell.encode(), + U256::from(0), + gas_limit, + gas_price.0 * 10, + None, + Some(U256::from(nonce)), + [].into(), + )); + + let new_treasury_eth_balance = Tokens::free_balance(WETH, &Treasury::account_id()); + let fee_weth_evm = new_treasury_eth_balance - treasury_eth_balance; + + let fee_in_cents = ETH_USD_SPOT_PRICE * fee_weth_evm as f64 / 1000000000000000000.0; + round(fee_in_cents) +} + +fn round(fee_in_cent: f64) -> f64 { + let decimal_places = 6; + let rounder = 10_f64.powi(decimal_places); + (fee_in_cent * rounder).round() / rounder +} + +fn init_omnipool() { + let native_price = FixedU128::from_inner(1201500000000000); + let stable_price = FixedU128::from_inner(45_000_000_000); + + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + HDX, + native_price, + Permill::from_percent(10), + hydradx_runtime::Omnipool::protocol_account(), + )); + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + DAI, + stable_price, + Permill::from_percent(100), + hydradx_runtime::Omnipool::protocol_account(), + )); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index af37c2859..d634a9e2d 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -11,6 +11,7 @@ mod dust_removal_whitelist; mod dynamic_fees; mod evm; mod exchange_asset; +mod fee_calculation; mod insufficient_assets_ed; mod non_native_fee; mod omnipool_init; diff --git a/integration-tests/src/non_native_fee.rs b/integration-tests/src/non_native_fee.rs index 6a21217a2..8eb455bbe 100644 --- a/integration-tests/src/non_native_fee.rs +++ b/integration-tests/src/non_native_fee.rs @@ -46,7 +46,7 @@ fn non_native_fee_payment_works_with_oracle_price_based_on_onchain_route() { ) ); let bob_balance = hydradx_runtime::Tokens::free_balance(BTC, &AccountId::from(BOB)); - assert_eq!(bob_balance, 999_962); + assert_eq!(bob_balance, 999991); assert_ok!(hydradx_runtime::Balances::force_set_balance( hydradx_runtime::RuntimeOrigin::root(), @@ -75,7 +75,7 @@ fn non_native_fee_payment_works_with_oracle_price_based_on_onchain_route() { ); let dave_balance = hydradx_runtime::Tokens::free_balance(DAI, &AccountId::from(DAVE)); - assert_eq!(dave_balance, 999_992_364_637_822_103_500); //Price based on oracle with onchain route + assert_eq!(dave_balance, 999_998_091_159_455_519_200); }); } diff --git a/integration-tests/src/oracle.rs b/integration-tests/src/oracle.rs index df5835839..5a668548b 100644 --- a/integration-tests/src/oracle.rs +++ b/integration-tests/src/oracle.rs @@ -2,6 +2,8 @@ use crate::polkadot_test_net::*; +use frame_support::traits::OnFinalize; +use frame_support::traits::OnInitialize; use frame_support::{ assert_ok, sp_runtime::{FixedU128, Permill}, @@ -12,10 +14,27 @@ use hydradx_traits::{ AggregatedPriceOracle, OraclePeriod::{self, *}, }; + use pallet_ema_oracle::OracleError; use primitives::constants::chain::OMNIPOOL_SOURCE; use xcm_emulator::TestExt; +pub fn hydradx_run_to_block(to: BlockNumber) { + while hydradx_runtime::System::block_number() < to { + let b = hydradx_runtime::System::block_number(); + + hydradx_runtime::System::on_finalize(b); + hydradx_runtime::EmaOracle::on_finalize(b); + hydradx_runtime::TransactionPayment::on_finalize(b); + + hydradx_runtime::System::on_initialize(b + 1); + hydradx_runtime::EmaOracle::on_initialize(b + 1); + hydradx_runtime::DynamicEvmFee::on_initialize(b + 1); + + hydradx_runtime::System::set_block_number(b + 1); + } +} + const HDX: AssetId = CORE_ASSET_ID; const SUPPORTED_PERIODS: &[OraclePeriod] = &[LastBlock, Short, TenMinutes]; diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index b9f3df5a7..8bea6fb73 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -529,7 +529,7 @@ pub mod hydra { (DAI, Price::from(1)), (ACA, Price::from(1)), (BTC, Price::from_inner(134_000_000)), - (WETH, Price::from_inner(3_666_754_716_981_130_000)), + (WETH, pallet_dynamic_evm_fee::ETH_HDX_REFERENCE_PRICE), ], account_currencies: vec![], }, @@ -676,6 +676,7 @@ pub fn polkadot_run_to_block(to: BlockNumber) { hydradx_runtime::EmaOracle::on_initialize(b + 1); hydradx_runtime::DCA::on_initialize(b + 1); hydradx_runtime::CircuitBreaker::on_initialize(b + 1); + hydradx_runtime::DynamicEvmFee::on_initialize(b + 1); hydradx_runtime::System::set_block_number(b + 1); } diff --git a/pallets/dynamic-evm-fee/Cargo.toml b/pallets/dynamic-evm-fee/Cargo.toml new file mode 100644 index 000000000..4a778781f --- /dev/null +++ b/pallets/dynamic-evm-fee/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "pallet-dynamic-evm-fee" +version = "1.0.0" +description = "Storing and mutating the dynamic fee for EVM transactions." +authors = ["GalacticCoucil"] +edition = "2021" +license = "Apache 2.0" +repository = "/~https://github.com/galacticcouncil/warehouse" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +log = { workspace = true } + +# ORML dependencies +orml-traits = { workspace = true } + +# HydraDX traits +hydradx-traits = { workspace = true } +primitives = { workspace = true } +hydra-dx-math = { workspace = true } + +# Substrate dependencies +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-api = { workspace = true } +sp-std = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true} + +# Evm dependencies +pallet-evm = { workspace = true } + +# Optional imports for benchmarking +frame-benchmarking = { workspace = true, optional = true } +sp-io = { workspace = true, optional = true } + +[dev-dependencies] +pallet-currencies = { workspace = true } +orml-tokens = { workspace = true, features=["std"] } +pallet-balances = { workspace = true, features=["std"] } +test-utils = { workspace = true } +pallet-transaction-payment = { workspace = true } +frame-benchmarking = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-api/std", + "sp-runtime/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "pallet-transaction-payment/std", + "orml-traits/std", + "hydradx-traits/std", + "primitives/std", + "frame-benchmarking/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-io", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/dynamic-evm-fee/README.md b/pallets/dynamic-evm-fee/README.md new file mode 100644 index 000000000..9b520080a --- /dev/null +++ b/pallets/dynamic-evm-fee/README.md @@ -0,0 +1,31 @@ +# Dynamic EVM Fee + +## Overview + +The goal of this pallet to have EVM transaction fees in tandem with Substrate fees. + +This pallet enables dynamic adjustment of the EVM transaction fee, leveraging two primary metrics: +- Current network congestion +- The oracle price difference between ETH and HDX + +Fees are calculated with the production of each new block, ensuring responsiveness to changing network conditions. + +### Fee Adjustment Based on Network Congestion +The formula for adjusting fees in response to network congestion is as follows: +``` +BaseFeePerGas = DefaultBaseFeePerGas + (DefaultBaseFeePerGas * Multiplier * 3) +``` +- `DefaultBaseFeePerGas`: This represents the minimum fee payable for a transaction, set in pallet configuration. +- `Multiplier`: Derived from current network congestion levels, this multiplier is computed within the `pallet-transaction-payment`. + +### Fee Adjustment Based on ETH-HDX Price Fluctuations + +The transaction fee is also adjusted in accordance with in ETH-HDX oracle price change: +- When HDX increases in value against ETH, the evm fee is increased accordingly. +- When HDX decreases in value against ETH, the evm fee is decreased accordingly. + +This dual-criteria approach ensures that transaction fees remain fair and reflective of both market conditions and network demand. + + +### FeeCalculator +The pallet implements `FeeCalculator` trait of pallet_evm to be used for EVM transactions \ No newline at end of file diff --git a/pallets/dynamic-evm-fee/src/lib.rs b/pallets/dynamic-evm-fee/src/lib.rs new file mode 100644 index 000000000..489a89fb8 --- /dev/null +++ b/pallets/dynamic-evm-fee/src/lib.rs @@ -0,0 +1,175 @@ +// This file is part of pallet-relaychain-info. + +// Copyright (C) 2020-2022 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Dynamic EVM Fee +//! +//! ## Overview +//! +//! The goal of this pallet to have EVM transaction fees in tandem with Substrate fees. +//! +//! This pallet enables dynamic adjustment of the EVM transaction fee, leveraging two primary metrics: +//! - Current network congestion +//! - The oracle price difference between ETH and HDX +//! +//! Fees are calculated with the production of each new block, ensuring responsiveness to changing network conditions. +//! +//! ### Fee Adjustment Based on Network Congestion +//! The formula for adjusting fees in response to network congestion is as follows: +//! +//! BaseFeePerGas = DefaultBaseFeePerGas + (DefaultBaseFeePerGas * Multiplier * 3 +//! +//! - `DefaultBaseFeePerGas`: This represents the minimum fee payable for a transaction, set in pallet configuration. +//! - `Multiplier`: Derived from current network congestion levels, this multiplier is computed within the `pallet-transaction-payment`. +//! +//! ### Fee Adjustment Based on ETH-HDX Price Fluctuations +//! +//! The transaction fee is also adjusted in accordance with in ETH-HDX oracle price change: +//! - When HDX increases in value against ETH, the evm fee is increased accordingly. +//! - When HDX decreases in value against ETH, the evm fee is decreased accordingly. +//! +//! This dual-criteria approach ensures that transaction fees remain fair and reflective of both market conditions and network demand. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod tests; + +pub mod weights; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; +pub use weights::WeightInfo; + +use codec::HasCompact; +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::BlockNumberFor; +use hydra_dx_math::ema::EmaPrice; +use hydradx_traits::NativePriceOracle; +use sp_core::U256; +use sp_runtime::FixedPointNumber; +use sp_runtime::FixedU128; + +pub const ETH_HDX_REFERENCE_PRICE: FixedU128 = FixedU128::from_inner(8945857934143137845); //Current onchain ETH price on at block #4,534,103 + +#[frame_support::pallet] +pub mod pallet { + use crate::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Identifier for the class of asset. + type AssetId: Member + + Parameter + + Default + + Copy + + HasCompact + + MaybeSerializeDeserialize + + MaxEncodedLen + + TypeInfo; + + /// Minimum base fee per gas value. Used to bound the base fee per gas in min direction. + type MinBaseFeePerGas: Get; + + /// Maximum base fee per gas value. Used to bound the base fee per gas in max direction. + type MaxBaseFeePerGas: Get; + + /// Default base fee per gas value. Used in genesis if no other value specified explicitly. + type DefaultBaseFeePerGas: Get; + + /// Transaction fee multiplier provider + type FeeMultiplier: Get; + + /// Native price oracle + type NativePriceOracle: NativePriceOracle; + + /// WETH Asset Id + #[pallet::constant] + type WethAssetId: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::type_value] + pub fn DefaultBaseFeePerGas() -> U256 { + U256::from(T::DefaultBaseFeePerGas::get()) + } + + /// Base fee per gas + #[pallet::storage] + #[pallet::getter(fn base_evm_fee)] + pub type BaseFeePerGas = StorageValue<_, U256, ValueQuery, DefaultBaseFeePerGas>; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_: BlockNumberFor) -> Weight { + BaseFeePerGas::::mutate(|old_base_fee_per_gas| { + let multiplier = T::FeeMultiplier::get(); + + let mut new_base_fee_per_gas = T::DefaultBaseFeePerGas::get().saturating_add( + multiplier + .saturating_mul_int(T::DefaultBaseFeePerGas::get()) + .saturating_mul(3), + ); + + let Some(eth_hdx_price) = T::NativePriceOracle::price(T::WethAssetId::get()) else { + log::warn!(target: "runtime::dynamic-evm-fee", "Could not get ETH-HDX price from oracle"); + return; + }; + let Some(eth_hdx_price) = FixedU128::checked_from_rational(eth_hdx_price.n, eth_hdx_price.d) else { + log::warn!(target: "runtime::dynamic-evm-fee", "Could not get rational of eth-hdx price, n: {}, d: {}", eth_hdx_price.n, eth_hdx_price.d); + return; + }; + + let Some(price_diff) = FixedU128::checked_from_rational(eth_hdx_price.into_inner(), ETH_HDX_REFERENCE_PRICE.into_inner()) else { + log::warn!(target: "runtime::dynamic-evm-fee", "Could not get rational of eth-hdx price, current price: {}, reference price: {}", eth_hdx_price, ETH_HDX_REFERENCE_PRICE); + return; + }; + + new_base_fee_per_gas = price_diff.saturating_mul_int(new_base_fee_per_gas); + + new_base_fee_per_gas = + new_base_fee_per_gas.clamp(T::MinBaseFeePerGas::get(), T::MaxBaseFeePerGas::get()); + + *old_base_fee_per_gas = U256::from(new_base_fee_per_gas); + }); + + T::WeightInfo::on_initialize() + } + + fn integrity_test() { + assert!( + T::MinBaseFeePerGas::get() < T::MaxBaseFeePerGas::get(), + "MinBaseFeePerGas should be less than MaxBaseFeePerGas, otherwise it fails when we clamp for bounding the base fee per gas." + ); + } + } +} +impl pallet_evm::FeeCalculator for Pallet { + fn min_gas_price() -> (U256, Weight) { + let base_fee_per_gas = Self::base_evm_fee(); + + (base_fee_per_gas, T::WeightInfo::on_initialize()) + } +} diff --git a/pallets/dynamic-evm-fee/src/tests/mock.rs b/pallets/dynamic-evm-fee/src/tests/mock.rs new file mode 100644 index 000000000..8f900b042 --- /dev/null +++ b/pallets/dynamic-evm-fee/src/tests/mock.rs @@ -0,0 +1,294 @@ +// This file is part of Basilisk-node. + +// Copyright (C) 2020-2022 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use crate as multi_payment; +use crate::Config; +use hydra_dx_math::types::Ratio; + +use crate as dynamic_evm_fee; +use frame_support::{ + parameter_types, + traits::{Everything, Get, Nothing}, + weights::Weight, +}; +use frame_system as system; +use hydra_dx_math::ema::EmaPrice; +use hydradx_traits::router::RouteProvider; +use hydradx_traits::NativePriceOracle; +use orml_traits::parameter_type_with_key; +use pallet_currencies::BasicCurrencyAdapter; +use pallet_transaction_payment::Multiplier; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, FixedPointNumber, FixedU128, +}; +use sp_std::cell::RefCell; +pub type AccountId = u64; +pub type Balance = u128; +pub type AssetId = u32; +pub type Amount = i128; + +pub const INITIAL_BALANCE: Balance = 1_000_000_000_000_000u128; + +pub const ALICE: AccountId = 1; +pub const FEE_RECEIVER: AccountId = 300; + +pub const HDX: AssetId = 0; +pub const WETH: AssetId = 1; +pub const SUPPORTED_CURRENCY: AssetId = 2000; +pub const SUPPORTED_CURRENCY_WITH_PRICE: AssetId = 3000; +pub const HIGH_ED_CURRENCY: AssetId = 6000; +pub const HIGH_VALUE_CURRENCY: AssetId = 7000; + +pub const HIGH_ED: Balance = 5; + +pub const DEFAULT_ETH_HDX_ORACLE_PRICE: Ratio = Ratio::new(8945857934143137845, FixedU128::DIV); + +thread_local! { + static EXTRINSIC_BASE_WEIGHT: RefCell = RefCell::new(Weight::zero()); + static MULTIPLIER: RefCell = RefCell::new(Multiplier::from_rational(1,1000)); + static ETH_HDX_ORACLE_PRICE: RefCell = RefCell::new(DEFAULT_ETH_HDX_ORACLE_PRICE); +} + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Currencies: pallet_currencies, + Tokens: orml_tokens, + DynamicEvmFee: dynamic_evm_fee, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 63; + + pub const HdxAssetId: u32 = HDX; + pub const WethAssetId: u32 = WETH; + pub const ExistentialDeposit: u128 = 2; + pub const MaxLocks: u32 = 50; + pub const RegistryStringLimit: u32 = 100; + pub const FeeReceiver: AccountId = FEE_RECEIVER; + + + + pub ExchangeFeeRate: (u32, u32) = (2, 1_000); +} + +impl system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +pub struct MultiplierProviderMock; + +impl Get for MultiplierProviderMock { + fn get() -> Multiplier { + MULTIPLIER.with(|v| *v.borrow()) + } +} + +pub struct NativePriceOracleMock; + +impl NativePriceOracle for NativePriceOracleMock { + fn price(_: AssetId) -> Option { + Some(ETH_HDX_ORACLE_PRICE.with(|v| *v.borrow())) + } +} + +pub struct DefaultBaseDFeePerGas; + +impl Get for DefaultBaseDFeePerGas { + fn get() -> u128 { + 15_000_000 + } +} + +pub struct MinBaseFeePerGas; +impl Get for MinBaseFeePerGas { + fn get() -> u128 { + 15_000_000 / 10 + } +} + +pub struct MaxBaseFeePerGas; +impl Get for MaxBaseFeePerGas { + fn get() -> u128 { + 14415000000 + } +} + +impl Config for Test { + type AssetId = AssetId; + type MinBaseFeePerGas = MinBaseFeePerGas; + type MaxBaseFeePerGas = MaxBaseFeePerGas; + type DefaultBaseFeePerGas = DefaultBaseDFeePerGas; + type FeeMultiplier = MultiplierProviderMock; + type NativePriceOracle = NativePriceOracleMock; + type WethAssetId = HdxAssetId; + type WeightInfo = (); +} + +pub struct DefaultRouteProvider; + +impl RouteProvider for DefaultRouteProvider {} + +impl pallet_balances::Config for Test { + type MaxLocks = MaxLocks; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type MaxHolds = (); + type RuntimeHoldReason = (); +} + +parameter_types! { + pub const MaxReserves: u32 = 50; +} +parameter_type_with_key! { + pub ExistentialDeposits: |currency_id: AssetId| -> Balance { + match *currency_id { + HIGH_ED_CURRENCY => HIGH_ED, + HIGH_VALUE_CURRENCY => 1u128, + _ => 2u128 + } + }; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = AssetId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type DustRemovalWhitelist = Nothing; + type ReserveIdentifier = (); + type MaxReserves = MaxReserves; + type CurrencyHooks = (); +} + +impl pallet_currencies::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type GetNativeCurrencyId = HdxAssetId; + type WeightInfo = (); +} + +pub struct ExtBuilder { + base_weight: Weight, + native_balances: Vec<(AccountId, Balance)>, + endowed_accounts: Vec<(AccountId, AssetId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + base_weight: Weight::zero(), + native_balances: vec![(ALICE, INITIAL_BALANCE)], + endowed_accounts: vec![ + (ALICE, HDX, INITIAL_BALANCE), + (ALICE, SUPPORTED_CURRENCY, INITIAL_BALANCE), // used for fallback price test + (ALICE, SUPPORTED_CURRENCY_WITH_PRICE, INITIAL_BALANCE), + ], + } + } +} + +impl ExtBuilder { + fn set_constants(&self) { + EXTRINSIC_BASE_WEIGHT.with(|v| *v.borrow_mut() = self.base_weight); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_constants(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self.native_balances, + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self.endowed_accounts, + } + .assimilate_storage(&mut t) + .unwrap(); + + let core_asset: u32 = 0; + let mut buf: Vec = Vec::new(); + + buf.extend_from_slice(&core_asset.to_le_bytes()); + buf.extend_from_slice(b"HDT"); + buf.extend_from_slice(&core_asset.to_le_bytes()); + + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| { + System::set_block_number(1); + // Make sure the prices are up-to-date. + }); + ext + } +} + +pub fn set_multiplier(multiplier: Multiplier) { + MULTIPLIER.with(|v| { + *v.borrow_mut() = multiplier; + }); +} +pub fn set_oracle_price(price: Ratio) { + ETH_HDX_ORACLE_PRICE.with(|v| { + *v.borrow_mut() = price; + }); +} diff --git a/pallets/dynamic-evm-fee/src/tests/mod.rs b/pallets/dynamic-evm-fee/src/tests/mod.rs new file mode 100644 index 000000000..744c15954 --- /dev/null +++ b/pallets/dynamic-evm-fee/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod mock; +mod on_initialize; diff --git a/pallets/dynamic-evm-fee/src/tests/on_initialize.rs b/pallets/dynamic-evm-fee/src/tests/on_initialize.rs new file mode 100644 index 000000000..81e4376b4 --- /dev/null +++ b/pallets/dynamic-evm-fee/src/tests/on_initialize.rs @@ -0,0 +1,122 @@ +use crate::tests::mock::DynamicEvmFee; +use crate::tests::mock::*; +use frame_support::traits::OnInitialize; +use hydra_dx_math::types::Ratio; +use pallet_transaction_payment::Multiplier; +use sp_core::U256; + +#[test] +fn should_return_default_base_fee_when_min_multiplier() { + ExtBuilder::default().build().execute_with(|| { + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(15045000)); + }); +} + +#[test] +fn should_increase_evm_fee_with_max_multiplier() { + ExtBuilder::default().build().execute_with(|| { + set_multiplier(Multiplier::from_rational(320, 1)); + + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(14415000000u128)); + }); +} + +#[test] +fn should_increase_evm_fee_when_hdx_pumping_10percent_against_eth() { + ExtBuilder::default().build().execute_with(|| { + set_oracle_price(Ratio::new( + DEFAULT_ETH_HDX_ORACLE_PRICE.n * 110 / 100, + DEFAULT_ETH_HDX_ORACLE_PRICE.d, + )); + + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(16549499)); + }); +} + +#[test] +fn should_not_change_when_price_pumps_then_remains_same_in_consquent_block() { + ExtBuilder::default().build().execute_with(|| { + //Arrange + set_oracle_price(Ratio::new( + DEFAULT_ETH_HDX_ORACLE_PRICE.n * 110 / 100, + DEFAULT_ETH_HDX_ORACLE_PRICE.d, + )); + + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(16549499)); + + //Act + DynamicEvmFee::on_initialize(2); + + //Assert + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(16549499)); + }); +} + +#[test] +fn should_increase_evm_fee_when_hdx_pumping_1percent_against_eth() { + ExtBuilder::default().build().execute_with(|| { + set_oracle_price(Ratio::new( + DEFAULT_ETH_HDX_ORACLE_PRICE.n * 101 / 100, + DEFAULT_ETH_HDX_ORACLE_PRICE.d, + )); + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(15195449)); + }); +} + +#[test] +fn should_decrease_evm_fee_when_hdx_dumping_10percent_against_eth() { + ExtBuilder::default().build().execute_with(|| { + set_oracle_price(Ratio::new( + DEFAULT_ETH_HDX_ORACLE_PRICE.n * 90 / 100, + DEFAULT_ETH_HDX_ORACLE_PRICE.d, + )); + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(13540499)); + }); +} + +#[test] +fn evm_fee_should_go_to_minimum_when_hdx_dumps_a_alot() { + ExtBuilder::default().build().execute_with(|| { + set_oracle_price(Ratio::new( + DEFAULT_ETH_HDX_ORACLE_PRICE.n / 100, + DEFAULT_ETH_HDX_ORACLE_PRICE.d, + )); + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(1500000)); + }); +} + +#[test] +fn evm_fee_should_go_to_maximum_when_hdx_pumps_a_lot() { + ExtBuilder::default().build().execute_with(|| { + set_oracle_price(Ratio::new( + DEFAULT_ETH_HDX_ORACLE_PRICE.n * 1000, + DEFAULT_ETH_HDX_ORACLE_PRICE.d, + )); + DynamicEvmFee::on_initialize(1); + + let new_base_fee = DynamicEvmFee::base_evm_fee(); + assert_eq!(new_base_fee, U256::from(14415000000u128)); + }); +} diff --git a/pallets/dynamic-evm-fee/src/weights.rs b/pallets/dynamic-evm-fee/src/weights.rs new file mode 100644 index 000000000..838a95c3f --- /dev/null +++ b/pallets/dynamic-evm-fee/src/weights.rs @@ -0,0 +1,107 @@ +// This file is part of HydraDX. + +// Copyright (C) 2020-2023 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +//! Autogenerated weights for `pallet_dynamic_evm_fee` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-02-06, STEPS: `10`, REPEAT: `30`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dmoka-msi-pc`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/release/hydradx +// benchmark +// pallet +// --chain=dev +// --steps=10 +// --repeat=30 +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=.maintain/pallet-weight-template.hbs +// --pallet=pallet-dynamic-evm-fee +// --output=dynamic-evm-fee2.rs +// --extrinsic=* + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_dynamic_evm_fee`. +pub trait WeightInfo { + fn on_initialize() -> Weight; +} + +/// Weights for `pallet_dynamic_evm_fee` using the HydraDX node and recommended hardware. +pub struct HydraWeight(PhantomData); +impl WeightInfo for HydraWeight { + /// Storage: `DynamicEvmFee::BaseFeePerGas` (r:1 w:1) + /// Proof: `DynamicEvmFee::BaseFeePerGas` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::NextAssetId` (r:1 w:0) + /// Proof: `AssetRegistry::NextAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::LocationAssets` (r:1 w:0) + /// Proof: `AssetRegistry::LocationAssets` (`max_values`: None, `max_size`: Some(622), added: 3097, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Router::Routes` (r:1 w:0) + /// Proof: `Router::Routes` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(177), added: 2652, mode: `MaxEncodedLen`) + fn on_initialize() -> Weight { + // Proof Size summary in bytes: + // Measured: `2709` + // Estimated: `11598` + // Minimum execution time: 64_849_000 picoseconds. + Weight::from_parts(66_099_000, 11598) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `DynamicEvmFee::BaseFeePerGas` (r:1 w:1) + /// Proof: `DynamicEvmFee::BaseFeePerGas` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::NextAssetId` (r:1 w:0) + /// Proof: `AssetRegistry::NextAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::LocationAssets` (r:1 w:0) + /// Proof: `AssetRegistry::LocationAssets` (`max_values`: None, `max_size`: Some(622), added: 3097, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Router::Routes` (r:1 w:0) + /// Proof: `Router::Routes` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(177), added: 2652, mode: `MaxEncodedLen`) + fn on_initialize() -> Weight { + // Proof Size summary in bytes: + // Measured: `2709` + // Estimated: `11598` + // Minimum execution time: 64_849_000 picoseconds. + Weight::from_parts(66_099_000, 11598) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/rococo-local/config-zombienet.json b/rococo-local/config-zombienet.json index befb4e394..f9f8f4447 100644 --- a/rococo-local/config-zombienet.json +++ b/rococo-local/config-zombienet.json @@ -41,7 +41,7 @@ { "name": "alice", "command": "../target/release/hydradx", - "args": ["--pruning=archive"], + "args": ["--pruning=archive", "--log=info"], "ws_port": 9988, "rpc_port": 9999 }, diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index c4e036fed..5f91aa3d8 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "215.0.0" +version = "216.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" @@ -31,6 +31,7 @@ pallet-omnipool-liquidity-mining = { workspace = true } pallet-dca = { workspace = true } hydra-dx-math = { workspace = true } pallet-dynamic-fees = { workspace = true } +pallet-dynamic-evm-fee = { workspace = true } pallet-stableswap = { workspace = true } pallet-bonds = { workspace = true } pallet-lbp = { workspace = true } @@ -288,6 +289,7 @@ std = [ "fp-rpc/std", "fp-self-contained/std", "pallet-ethereum/std", + "pallet-dynamic-evm-fee/std", "pallet-evm/std", "pallet-evm-chain-id/std", "pallet-evm-precompile-dispatch/std", @@ -361,6 +363,7 @@ try-runtime= [ "pallet-xyk/try-runtime", "fp-self-contained/try-runtime", "pallet-ethereum/try-runtime", + "pallet-dynamic-evm-fee/try-runtime", "pallet-evm/try-runtime", "pallet-evm-chain-id/try-runtime", "pallet-xyk/try-runtime", diff --git a/runtime/hydradx/src/benchmarking/dca.rs b/runtime/hydradx/src/benchmarking/dca.rs index 9ca9ef5d3..9f5153d84 100644 --- a/runtime/hydradx/src/benchmarking/dca.rs +++ b/runtime/hydradx/src/benchmarking/dca.rs @@ -41,8 +41,8 @@ use pallet_route_executor::Trade; use pallet_route_executor::MAX_NUMBER_OF_TRADES; use scale_info::prelude::vec::Vec; use sp_runtime::traits::ConstU32; -use sp_runtime::FixedU128; use sp_runtime::{DispatchError, Permill}; +use sp_runtime::{DispatchResult, FixedU128}; use sp_std::vec; pub const HDX: AssetId = 0; @@ -172,6 +172,13 @@ fn create_account_with_native_balance() -> Result { Ok(caller) } +fn fund_treasury() -> DispatchResult { + let treasury = ::FeeReceiver::get(); + >::update_balance(HDX, &treasury, 500_000_000_000_000i128)?; + + Ok(()) +} + runtime_benchmarks! { {Runtime, pallet_dca} @@ -184,9 +191,11 @@ runtime_benchmarks! { let amount_buy = 200 * ONE; >::update_balance(HDX, &seller, 500_000_000_000_000i128)?; - >::update_balance(HDX, &other_seller, 20_000_000_000_000_000_000_000i128)?; + //Fund treasury with some HDX to prevent BelowMinimum issue due to low fee + fund_treasury()?; + let schedule1 = schedule_buy_fake(seller.clone(), HDX, DAI, amount_buy); let execution_block = 1001u32; @@ -225,9 +234,10 @@ runtime_benchmarks! { let amount_sell = 100 * ONE; >::update_balance(HDX, &seller, 20_000_000_000_000_000i128)?; - >::update_balance(HDX, &other_seller, 20_000_000_000_000_000_000_000i128)?; + fund_treasury()?; //Fund treasury with some HDX to prevent BelowMinimum issue due to low fee + fund_treasury()?; //Fund treasury with some HDX to prevent BelowMinimum issue due to low fee let schedule1 = schedule_sell_fake(seller.clone(), HDX, DAI, amount_sell); let execution_block = 1001u32; @@ -259,6 +269,7 @@ runtime_benchmarks! { on_initialize_with_empty_block{ let seller: AccountId = account("seller", 3, 1); + fund_treasury()?; //Fund treasury with some HDX to prevent BelowMinimum issue due to low fee let execution_block = 100u32; assert_eq!(DCA::schedules::(execution_block), None); @@ -274,6 +285,7 @@ runtime_benchmarks! { schedule{ let caller: AccountId = create_account_with_native_balance()?; + fund_treasury()?; //Fund treasury with some HDX to prevent BelowMinimum issue due to low fee let asset_1 = register_asset(b"AS1".to_vec(), 1u128).map_err(|_| BenchmarkError::Stop("Failed to register asset"))?; let asset_2 = register_asset(b"AS2".to_vec(), 1u128).map_err(|_| BenchmarkError::Stop("Failed to register asset"))?; @@ -373,6 +385,7 @@ runtime_benchmarks! { terminate { let caller: AccountId = create_account_with_native_balance()?; + fund_treasury()?; //Fund treasury with some HDX to prevent BelowMinimum issue due to low fee >::update_balance(HDX, &caller, 100_000_000_000_000_000i128)?; diff --git a/runtime/hydradx/src/benchmarking/dynamic_evm_fee.rs b/runtime/hydradx/src/benchmarking/dynamic_evm_fee.rs new file mode 100644 index 000000000..e0a16db54 --- /dev/null +++ b/runtime/hydradx/src/benchmarking/dynamic_evm_fee.rs @@ -0,0 +1,145 @@ +// This file is part of Basilisk-node + +// Copyright (C) 2020-2021 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::{AccountId, AssetId, Balance, Currencies, EmaOracle, Runtime, System}; +use frame_benchmarking::account; +use frame_benchmarking::BenchmarkError; +use frame_support::assert_ok; +use frame_support::traits::{OnFinalize, OnInitialize}; +use frame_system::RawOrigin; +use orml_benchmarking::runtime_benchmarks; +use orml_traits::MultiCurrencyExtended; +use primitives::BlockNumber; +use sp_runtime::traits::SaturatedConversion; +use sp_runtime::FixedU128; + +type DynamicEvmFeePallet = pallet_dynamic_evm_fee::Pallet; +use crate::evm::WETH_ASSET_LOCATION; +use pallet_dynamic_evm_fee::BaseFeePerGas; + +pub fn update_balance(currency_id: AssetId, who: &AccountId, balance: Balance) { + assert_ok!(>::update_balance( + currency_id, + who, + balance.saturated_into() + )); +} + +runtime_benchmarks! { + { Runtime, pallet_dynamic_evm_fee} + + + on_initialize{ + //let maker: AccountId = account("maker", 0, SEED); + + crate::benchmarking::omnipool::init()?; + + let acc = Omnipool::protocol_account(); + // Register new asset in asset registry + let token_id = register_asset(b"AS1".to_vec(), 1u128).map_err(|_| BenchmarkError::Stop("Failed to register asset"))?; + assert_eq!(token_id, 1000001, "Token ID should be 1000001"); + set_location(token_id, WETH_ASSET_LOCATION).map_err(|_| BenchmarkError::Stop("Failed to set location for weth"))?; + add_as_accepted_currency(token_id, FixedU128::from_inner(16420844565569051996)).map_err(|_| BenchmarkError::Stop("Failed to add token as accepted currency"))?; + // Create account for token provider and set balance + let owner: AccountId = account("owner", 0, 1); + + let token_price = FixedU128::from((1,5)); + let token_amount = 200_000_000_000_000_u128; + + update_balance(token_id, &acc, token_amount); + update_balance(0, &owner, 1_000_000_000_000_000_u128); + + // Add the token to the pool + Omnipool::add_token(RawOrigin::Root.into(), token_id, token_price, Permill::from_percent(100), owner)?; + let seller: AccountId = account("seller", 3, 1); + update_balance(0, &seller, 500_000_000_000_000_u128); + Omnipool::sell(RawOrigin::Signed(seller).into(), 0, token_id, 10000000000000, 0)?; + + set_period(10); + let base_fee_per_gas = >::get(); + + }: { + DynamicEvmFeePallet::::on_initialize(1u32) + } + verify{ + assert!(>::get() != base_fee_per_gas); + } +} +use crate::Omnipool; +use sp_runtime::Permill; + +fn set_period(to: u32) { + while System::block_number() < Into::::into(to) { + let b = System::block_number(); + + System::on_finalize(b); + EmaOracle::on_finalize(b); + + System::on_initialize(b + 1_u32); + EmaOracle::on_initialize(b + 1_u32); + + System::set_block_number(b + 1_u32); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::NativeExistentialDeposit; + use orml_benchmarking::impl_benchmark_test_suite; + use sp_runtime::BuildStorage; + + fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_asset_registry::GenesisConfig:: { + registered_assets: vec![ + ( + Some(1), + Some(b"LRNA".to_vec().try_into().unwrap()), + 1_000u128, + None, + None, + None, + true, + ), + ( + Some(2), + Some(b"DAI".to_vec().try_into().unwrap()), + 1_000u128, + None, + None, + None, + true, + ), + ], + native_asset_name: b"HDX".to_vec().try_into().unwrap(), + native_existential_deposit: NativeExistentialDeposit::get(), + native_decimals: 12, + native_symbol: b"HDX".to_vec().try_into().unwrap(), + } + .assimilate_storage(&mut t) + .unwrap(); + + sp_io::TestExternalities::new(t) + } + + impl_benchmark_test_suite!(new_test_ext(),); +} diff --git a/runtime/hydradx/src/benchmarking/mod.rs b/runtime/hydradx/src/benchmarking/mod.rs index f380d1188..946955e43 100644 --- a/runtime/hydradx/src/benchmarking/mod.rs +++ b/runtime/hydradx/src/benchmarking/mod.rs @@ -3,6 +3,7 @@ pub mod currencies; pub mod dca; pub mod duster; +pub mod dynamic_evm_fee; pub mod multi_payment; pub mod omnipool; pub mod route_executor; @@ -10,10 +11,11 @@ pub mod tokens; pub mod vesting; pub mod xyk; -use crate::AssetRegistry; +use crate::{AssetLocation, AssetRegistry, MultiTransactionPayment}; use frame_system::RawOrigin; use hydradx_traits::{registry::Create, AssetKind}; +use pallet_transaction_multi_payment::Price; use primitives::{AssetId, Balance}; use sp_runtime::traits::One; use sp_std::vec; @@ -22,8 +24,8 @@ use sp_std::vec::Vec; pub const BSX: Balance = primitives::constants::currency::UNITS; use frame_support::storage::with_transaction; +use hydradx_traits::Mutate; use sp_runtime::TransactionOutcome; - pub fn register_asset(name: Vec, deposit: Balance) -> Result { let n = name.try_into().map_err(|_| ())?; with_transaction(|| { @@ -58,6 +60,14 @@ pub fn register_external_asset(name: Vec) -> Result { .map_err(|_| ()) } +pub fn set_location(asset_id: AssetId, location: AssetLocation) -> Result<(), ()> { + AssetRegistry::set_location(asset_id, location).map_err(|_| ()) +} + +pub fn add_as_accepted_currency(asset_id: AssetId, price: Price) -> Result<(), ()> { + MultiTransactionPayment::add_currency(RawOrigin::Root.into(), asset_id, price).map_err(|_| ()) +} + #[allow(dead_code)] pub fn update_asset(asset_id: AssetId, name: Option>, deposit: Balance) -> Result<(), ()> { let nm = if let Some(n) = name { diff --git a/runtime/hydradx/src/evm/mod.rs b/runtime/hydradx/src/evm/mod.rs index 6fd468e20..10457b067 100644 --- a/runtime/hydradx/src/evm/mod.rs +++ b/runtime/hydradx/src/evm/mod.rs @@ -19,11 +19,11 @@ // you may not use this file except in compliance with the License. // http://www.apache.org/licenses/LICENSE-2.0 -use crate::TreasuryAccount; pub use crate::{ evm::accounts_conversion::{ExtendedAddressMapping, FindAuthorTruncated}, AssetLocation, Aura, NORMAL_DISPATCH_RATIO, }; +use crate::{DCAOraclePeriod, NativeAssetId, TreasuryAccount, LRNA}; use frame_support::{ parameter_types, traits::{Defensive, FindAuthor, Imbalance, OnUnbalanced}, @@ -31,16 +31,17 @@ use frame_support::{ ConsensusEngineId, }; use hex_literal::hex; +use hydradx_adapters::{AssetFeeOraclePriceProvider, OraclePriceProvider}; use orml_tokens::CurrencyAdapter; -use pallet_evm::{EnsureAddressTruncated, FeeCalculator}; +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 sp_core::{Get, U256}; - mod accounts_conversion; pub mod precompiles; @@ -53,9 +54,8 @@ pub const GAS_PER_SECOND: u64 = 40_000_000; // Approximate ratio of the amount of Weight per Gas. const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND; -// Fixed gas price of 0.08 gwei per gas -// pallet-base-fee to be implemented after migration to polkadot-v1.1.0 -const DEFAULT_BASE_FEE_PER_GAS: u128 = 80_000_000; +// Fixed gas price of 0.015 gwei per gas +pub const DEFAULT_BASE_FEE_PER_GAS: u128 = 15_000_000; parameter_types! { // We allow for a 75% fullness of a 0.5s block @@ -108,11 +108,15 @@ impl OnUnbalanced for DealWithFees { } } -pub struct FixedGasPrice; -impl FeeCalculator for FixedGasPrice { - fn min_gas_price() -> (U256, Weight) { - // Return some meaningful gas price and weight - (DEFAULT_BASE_FEE_PER_GAS.into(), Weight::from_parts(7u64, 0)) +parameter_types! { + pub PostLogContent: pallet_ethereum::PostLogContent = pallet_ethereum::PostLogContent::BlockAndTxnHashes; +} + +pub struct TransactionPaymentMultiplier; + +impl Get for TransactionPaymentMultiplier { + fn get() -> Multiplier { + crate::TransactionPayment::next_fee_multiplier() } } @@ -135,7 +139,7 @@ impl pallet_evm::Config for crate::Runtime { type CallOrigin = EnsureAddressTruncated; type ChainId = crate::EVMChainId; type Currency = WethCurrency; - type FeeCalculator = FixedGasPrice; + type FeeCalculator = crate::DynamicEvmFee; type FindAuthor = FindAuthorTruncated; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type OnChargeTransaction = TransferEvmFees; @@ -154,10 +158,6 @@ impl pallet_evm::Config for crate::Runtime { impl pallet_evm_chain_id::Config for crate::Runtime {} -parameter_types! { - pub PostLogContent: pallet_ethereum::PostLogContent = pallet_ethereum::PostLogContent::BlockAndTxnHashes; -} - impl pallet_ethereum::Config for crate::Runtime { type RuntimeEvent = crate::RuntimeEvent; type StateRoot = pallet_ethereum::IntermediateStateRoot; @@ -179,3 +179,27 @@ impl pallet_evm_accounts::Config for crate::Runtime { type ControllerOrigin = crate::SuperMajorityTechCommittee; type WeightInfo = crate::weights::evm_accounts::HydraWeight; } + +parameter_types! { + pub const DefaultBaseFeePerGas: u128 = DEFAULT_BASE_FEE_PER_GAS; + pub const MinBaseFeePerGas: u128 = DEFAULT_BASE_FEE_PER_GAS.saturating_div(10); + pub const MaxBaseFeePerGas: u128 = 14415000000; //To reach 10 dollar per omnipool trade +} + +impl pallet_dynamic_evm_fee::Config for crate::Runtime { + type AssetId = AssetId; + type DefaultBaseFeePerGas = DefaultBaseFeePerGas; + type MinBaseFeePerGas = MinBaseFeePerGas; + type MaxBaseFeePerGas = MaxBaseFeePerGas; + type FeeMultiplier = TransactionPaymentMultiplier; + type NativePriceOracle = AssetFeeOraclePriceProvider< + NativeAssetId, + crate::MultiTransactionPayment, + crate::Router, + OraclePriceProvider, + crate::MultiTransactionPayment, + DCAOraclePeriod, + >; + type WethAssetId = WethAssetId; + type WeightInfo = crate::weights::dynamic_evm_fee::HydraWeight; +} diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 6e0c7d189..f27f0473f 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -107,7 +107,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 215, + spec_version: 216, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -187,6 +187,7 @@ construct_runtime!( EVMChainId: pallet_evm_chain_id = 91, Ethereum: pallet_ethereum = 92, EVMAccounts: pallet_evm_accounts = 93, + DynamicEvmFee: pallet_dynamic_evm_fee = 94, // Parachain ParachainSystem: cumulus_pallet_parachain_system exclude_parts { Config } = 103, @@ -730,6 +731,7 @@ impl_runtime_apis! { orml_list_benchmark!(list, extra, pallet_route_executor, benchmarking::route_executor); orml_list_benchmark!(list, extra, pallet_dca, benchmarking::dca); orml_list_benchmark!(list, extra, pallet_xyk, benchmarking::xyk); + orml_list_benchmark!(list, extra, pallet_dynamic_evm_fee, benchmarking::dynamic_evm_fee); let storage_info = AllPalletsWithSystem::storage_info(); @@ -813,6 +815,7 @@ impl_runtime_apis! { orml_add_benchmark!(params, batches, pallet_route_executor, benchmarking::route_executor); orml_add_benchmark!(params, batches, pallet_dca, benchmarking::dca); orml_add_benchmark!(params, batches, pallet_xyk, benchmarking::xyk); + orml_add_benchmark!(params, batches, pallet_dynamic_evm_fee, benchmarking::dynamic_evm_fee); if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) diff --git a/runtime/hydradx/src/system.rs b/runtime/hydradx/src/system.rs index d17300846..c218f2cc0 100644 --- a/runtime/hydradx/src/system.rs +++ b/runtime/hydradx/src/system.rs @@ -423,6 +423,8 @@ pub type SlowAdjustingFeeUpdate = pub struct WeightToFee; +pub const SUBSTRATE_FEE_DIVIDER: u128 = 4; // We use this to divide fee related constant as HDX price is high (~0.26$), but we want to reduce the fee price + impl WeightToFeePolynomial for WeightToFee { type Balance = Balance; @@ -438,7 +440,7 @@ impl WeightToFeePolynomial for WeightToFee { /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. fn polynomial() -> WeightToFeeCoefficients { // extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT - let p = CENTS; // 1_000_000_000_000 + let p = CENTS / SUBSTRATE_FEE_DIVIDER; // 1_000_000_000_000 / SUBSTRATE_FEE_DIVIDER let q = 10 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); // 7_919_840_000 smallvec::smallvec![WeightToFeeCoefficient { degree: 1, @@ -450,18 +452,18 @@ impl WeightToFeePolynomial for WeightToFee { } parameter_types! { - pub const TransactionByteFee: Balance = 10 * MILLICENTS; + pub const TransactionByteFee: Balance = 10 * MILLICENTS / SUBSTRATE_FEE_DIVIDER; /// The portion of the `NORMAL_DISPATCH_RATIO` that we adjust the fees with. Blocks filled less /// than this will decrease the weight and more will increase. pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); /// The adjustment variable of the runtime. Higher values will cause `TargetBlockFullness` to /// change the fees more rapidly. - pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(6, 100_000); + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(10, 113); /// Minimum amount of the multiplier. This value cannot be too low. A test case should ensure /// that combined with `AdjustmentVariable`, we can recover from the minimum. - pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128); + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1000u128); /// The maximum amount of the multiplier. - pub MaximumMultiplier: Multiplier = Multiplier::saturating_from_integer(4); + pub MaximumMultiplier: Multiplier = Multiplier::saturating_from_integer(320); } impl pallet_transaction_payment::Config for Runtime { diff --git a/runtime/hydradx/src/tests.rs b/runtime/hydradx/src/tests.rs index 330bad8e3..0367d2097 100644 --- a/runtime/hydradx/src/tests.rs +++ b/runtime/hydradx/src/tests.rs @@ -4,14 +4,13 @@ use primitives::constants::{ time::{DAYS, HOURS}, }; -use pallet_transaction_payment::Multiplier; - use codec::Encode; use frame_support::{ dispatch::{DispatchClass, GetDispatchInfo}, sp_runtime::{traits::Convert, FixedPointNumber}, weights::WeightToFee, }; +use pallet_transaction_payment::Multiplier; use sp_runtime::BuildStorage; #[test] @@ -91,7 +90,6 @@ where } #[test] -#[ignore] fn multiplier_can_grow_from_zero() { let minimum_multiplier = MinimumMultiplier::get(); let target = TargetBlockFullness::get() * BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap(); @@ -102,24 +100,64 @@ fn multiplier_can_grow_from_zero() { assert!(next > minimum_multiplier, "{next:?} !>= {minimum_multiplier:?}"); }) } - -#[test] #[ignore] +#[test] fn multiplier_growth_simulator() { // calculate the value of the fee multiplier after one hour of operation with fully loaded blocks + let max_multiplier = MaximumMultiplier::get(); + println!("max multiplier = {max_multiplier:?}"); + let mut multiplier = Multiplier::saturating_from_integer(1); let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap(); for _block_num in 1..=HOURS { run_with_system_weight(block_weight, || { let next = SlowAdjustingFeeUpdate::::convert(multiplier); // ensure that it is growing as well. - assert!(next > multiplier, "{next:?} !>= {multiplier:?}"); + //assert!(next > multiplier, "{next:?} !>= {multiplier:?}"); + println!("multiplier = {multiplier:?}"); multiplier = next; }); } println!("multiplier = {multiplier:?}"); } +#[ignore] +#[test] +fn fee_growth_simulator() { + use frame_support::traits::OnFinalize; + // calculate the value of the fee multiplier after one hour of operation with fully loaded blocks + let max_multiplier = MaximumMultiplier::get(); + println!("--- FEE GROWTH SIMULATOR STARTS ---"); + + println!("With max multiplier = {max_multiplier:?}"); + let mut multiplier = Multiplier::saturating_from_integer(1); + let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap(); + for _block_num in 1..=HOURS { + run_with_system_weight(block_weight, || { + let b = crate::System::block_number(); + + let call = pallet_omnipool::Call::::sell { + asset_in: 2, + asset_out: 0, + amount: 1_000_000_000_000, + min_buy_amount: 0, + }; + let call_len = call.encoded_size() as u32; + let info = call.get_dispatch_info(); + + let next = TransactionPayment::next_fee_multiplier(); + let call_fee = TransactionPayment::compute_fee(call_len, &info, 0); + + as OnFinalize>::on_finalize(b + 1); + crate::System::set_block_number(b + 1); + + //let next = SlowAdjustingFeeUpdate::::convert(multiplier); + println!("Trade fee = {call_fee:?} with multiplier = {multiplier:?}"); + multiplier = next; + }); + } + println!("multiplier = {multiplier:?}"); +} #[test] #[ignore] fn max_multiplier() { diff --git a/runtime/hydradx/src/weights/dynamic_evm_fee.rs b/runtime/hydradx/src/weights/dynamic_evm_fee.rs new file mode 100644 index 000000000..dad1ab154 --- /dev/null +++ b/runtime/hydradx/src/weights/dynamic_evm_fee.rs @@ -0,0 +1,74 @@ +// This file is part of HydraDX. + +// Copyright (C) 2020-2023 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_dynamic_evm_fee` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-02-23, STEPS: `10`, REPEAT: `30`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bench-bot`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/hydradx +// benchmark +// pallet +// --chain=dev +// --steps=10 +// --repeat=30 +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=.maintain/pallet-weight-template-no-back.hbs +// --pallet=pallet-dynamic-evm-fee +// --output=dyn.rs +// --extrinsic=* + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_dynamic_evm_fee`. +pub struct HydraWeight(PhantomData); +impl pallet_dynamic_evm_fee::WeightInfo for HydraWeight { + /// Storage: `DynamicEvmFee::BaseFeePerGas` (r:1 w:1) + /// Proof: `DynamicEvmFee::BaseFeePerGas` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::NextAssetId` (r:1 w:0) + /// Proof: `AssetRegistry::NextAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::LocationAssets` (r:1 w:0) + /// Proof: `AssetRegistry::LocationAssets` (`max_values`: None, `max_size`: Some(622), added: 3097, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Router::Routes` (r:1 w:0) + /// Proof: `Router::Routes` (`max_values`: None, `max_size`: Some(90), added: 2565, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(177), added: 2652, mode: `MaxEncodedLen`) + fn on_initialize() -> Weight { + // Proof Size summary in bytes: + // Measured: `2709` + // Estimated: `11598` + // Minimum execution time: 73_589_000 picoseconds. + Weight::from_parts(74_453_000, 11598) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} \ No newline at end of file diff --git a/runtime/hydradx/src/weights/mod.rs b/runtime/hydradx/src/weights/mod.rs index 263f9b6e2..b4c47676c 100644 --- a/runtime/hydradx/src/weights/mod.rs +++ b/runtime/hydradx/src/weights/mod.rs @@ -8,6 +8,7 @@ pub mod currencies; pub mod dca; pub mod democracy; pub mod duster; +pub mod dynamic_evm_fee; pub mod elections; pub mod ema_oracle; pub mod evm_accounts; diff --git a/scripts/init-testnet/ddos/ddos_init.ts b/scripts/init-testnet/ddos/ddos_init.ts new file mode 100644 index 000000000..5e81d04f8 --- /dev/null +++ b/scripts/init-testnet/ddos/ddos_init.ts @@ -0,0 +1,157 @@ +// Required imports +const { ApiPromise, WsProvider } = require('@polkadot/api'); +const { hexToU8a } = require('@polkadot/util'); + +const { Keyring } = require('@polkadot/keyring'); + +async function main () { + // Initialise the provider to connect to the local node + //const provider = new WsProvider('wss://rpc.nice.hydration.cloud'); + const provider = new WsProvider('ws://127.0.0.1:9988'); + + // Create the API and wait until ready + const api = await ApiPromise.create({ provider }); + + // Retrieve the chain & node information information via rpc calls + const [chain, nodeName, nodeVersion] = await Promise.all([ + api.rpc.system.chain(), + api.rpc.system.name(), + api.rpc.system.version() + ]); + + console.log(`You are connected to chain ${chain} using ${nodeName} v${nodeVersion}`); + + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + let transactions = []; + assetRegistry(api, transactions); + mintForAlice(api, transactions); + mintUsersWithHDX(api, transactions); + initOmnipool(api, transactions) + + let batch = api.tx.utility.batchAll(transactions); + await api.tx.preimage.notePreimage(batch.method.toHex()).signAndSend(alice); +} + +main().catch(console.error).finally(() => process.exit()); + +function mintForTreasuryDca(api, txs) { + let treasury = "7KQx4f7yU3hqZHfvDVnSfe6mpgAT8Pxyr67LXHV6nsbZo3Tm"; + //weth + txs.push( + api.tx.currencies.updateBalance(treasury, 5, "5000000000000000") + ); + +} + +function mintForAlice(api, txs) { + let alice = "7NPoMQbiA6trJKkjB35uk96MeJD4PGWkLQLH7k7hXEkZpiba"; + txs.push( + api.tx.currencies.updateBalance(alice, 0, "5000000000000000000000000000") + ); + + txs.push(api.tx.currencies.updateBalance(alice, 1, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 2, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 3, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 4, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 5, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 6, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 7, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 8, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 9, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 10, "5000000000000000000")); +} + +function mintUsersWithHDX(api, txs) { + const keyring = new Keyring({ type: 'sr25519' }); + const user1 = keyring.addFromUri('//User1'); + const user2 = keyring.addFromUri('//User2'); + const user3 = keyring.addFromUri('//User3'); + const user4 = keyring.addFromUri('//User4'); + const user5 = keyring.addFromUri('//User5'); + const user6 = keyring.addFromUri('//User6'); + const user7 = keyring.addFromUri('//User7'); + const user8 = keyring.addFromUri('//User8'); + const user9 = keyring.addFromUri('//User9'); + const user10 = keyring.addFromUri('//User10'); + + txs.push(api.tx.currencies.updateBalance(user1.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user2.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user3.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user4.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user5.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user6.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user7.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user8.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user9.publicKey, 0, "5000000000000000000000000000")); + txs.push(api.tx.currencies.updateBalance(user10.publicKey, 0, "5000000000000000000000000000")); +} + +function assetRegistry(api, txs) { + const assets = require('../assets.json'); + let keys = Object.keys(assets); + + for (let i = 0, l = keys.length; i < l; i++) { + let k = keys[i]; + let a = assets[k]; + let tx; + + if (k == "0") { + tx = api.tx.assetRegistry.setMetadata(k, a.metadata.symbol, a.metadata.decimals); + txs.push(tx); + + continue; + } + + if (k == "1" || k == "2") { + let aType = {}; + aType[a.asset.assetType] = 0; + + tx = api.tx.assetRegistry.update(k, a.asset.name, aType, 100, null); + txs.push(tx); + + tx = api.tx.assetRegistry.setMetadata(k, a.metadata.symbol, a.metadata.decimals); + txs.push(tx); + continue; + } + + let aType = {}; + aType[a.asset.assetType] = 0; + + a.metadata.decimals = Number(a.metadata.decimals); + + tx = api.tx.assetRegistry.register(a.asset.name, aType, 100, k, a.metadata, null, null); + txs.push(tx); + }; + + return txs; +} + +function initOmnipool(api, txs) { + let omniAccount = "7L53bUTBbfuj14UpdCNPwmgzzHSsrsTWBHX5pys32mVWM3C1"; + //hdx + txs.push( + api.tx.currencies.updateBalance(omniAccount, 0, "936329588000000000") + ); + + //dai + txs.push( + api.tx.currencies.updateBalance(omniAccount, 2, "50000000000000000000000") + ); + + txs.push( + api.tx.currencies.updateBalance(omniAccount, 5, "936329588000000000") + ); + + txs.push( + api.tx.omnipool.addToken(0, "1201500000000000", 1000000, omniAccount) + ); + txs.push( + api.tx.omnipool.addToken(2, "1501500000000000", 1000000, omniAccount) + ); + + txs.push( + api.tx.omnipool.addToken(5, "1701500000000000", 1000000, omniAccount) + ); +} \ No newline at end of file diff --git a/scripts/init-testnet/ddos/ddos_run.ts b/scripts/init-testnet/ddos/ddos_run.ts new file mode 100644 index 000000000..7a5689e61 --- /dev/null +++ b/scripts/init-testnet/ddos/ddos_run.ts @@ -0,0 +1,123 @@ +// Required imports +const { ApiPromise, WsProvider } = require('@polkadot/api'); +const { hexToU8a } = require('@polkadot/util'); + +const { Keyring } = require('@polkadot/keyring'); + +async function main () { + // Initialise the provider to connect to the local node + //const provider = new WsProvider('wss://rpc.nice.hydration.cloud'); + const provider = new WsProvider('ws://127.0.0.1:9988'); + + // Create the API and wait until ready + const api = await ApiPromise.create({ provider }); + + // Retrieve the chain & node information information via rpc calls + const [chain, nodeName, nodeVersion] = await Promise.all([ + api.rpc.system.chain(), + api.rpc.system.name(), + api.rpc.system.version() + ]); + + console.log(`You are connected to chain ${chain} using ${nodeName} v${nodeVersion}`); + + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + let balance = await api.query.system.account(alice.publicKey); + console.log(`Alice's HDX balance before DDOS ${balance.data.free}`); + + const blockNumber = await api.rpc.chain.getBlock(); + + await createDcaSchedules(api, alice, blockNumber.block.header.number); + + let balance_after = await api.query.system.account(alice.publicKey); + console.log(`Alice's HDX balance after DDOS ${balance_after.data.free}`); + + let balance_diff = balance.data.free - balance_after.data.free; + + console.log(`Alice's spent balance ${balance_diff}`); + +} + +main().catch(console.error).finally(() => process.exit()); + +async function createDcaSchedules(api, user, block) { + let counter = 0; + let prev_block = block; + let prev_balance = await api.query.system.account(user.publicKey); + + while (1) { + const blockInfo = await api.rpc.chain.getBlock(); + const blockNumber = blockInfo.block.header.number; + + //Change this `ddos_run_duration` variable to define how long (in blocks) the DDOS should take + let ddos_run_duration = 1000; + let block_spent = blockNumber - block; + if (block_spent == ddos_run_duration) { + console.log(`The specified blocktime ${block_spent} passed`); + break; + } + + //If there is a new block + if (!(Math.abs(blockNumber - prev_block) < Number.EPSILON)) { + ///Change this `dcas_per_block` variable to reach different extrinsic weight utilization + ///10 (10%) + ///20 (20%) + ///28 (30%) + ///38 (40%) + ///48 (50%) + ///56 (60%) + ///64 (70%) + ///72 (80%) + ///85 (90%) + ///100 (100%) + let dcas_per_block = 100; + for (let i = 0; i < dcas_per_block; i++) { + let user_pub_key = user.publicKey; + const nonce = await api.rpc.system.accountNextIndex(user_pub_key); + const tip = 1; + + await createDca(user_pub_key, nonce, tip); + } + + //Print out block feeresult + let balance = await api.query.system.account(user.publicKey); + let balance_diff = prev_balance.data.free - balance.data.free; + const blockWeight = await api.query.system.blockWeight(); + + console.log(`${balance_diff} HDX fee spent in block ${blockNumber} with weight ${blockWeight.normal.refTime}`); + + prev_block = blockNumber; + prev_balance = balance; + } + } + + async function createDca(user_pub_key, nonce, tip) { + try { + await api.tx.dca + .schedule( + { + owner: user_pub_key, + period: 1, + totalAmount: 1000000000000000, + maxRetries: null, + stabilityThreshold: null, + slippage: null, + order: { + Sell: { + assetIn: 5, + assetOut: 2, + amountIn: 100000000000000, + minAmountOut: 0, + route: null + } + } + }, + null) + .signAndSend(user, { nonce, tip }); + } catch (error) { + console.log("Error while sending DCA - Sent transaction counter when signing fails: ", counter); + } + } +} \ No newline at end of file diff --git a/scripts/init-testnet/ddos/ddos_tx.ts b/scripts/init-testnet/ddos/ddos_tx.ts new file mode 100644 index 000000000..79c22759a --- /dev/null +++ b/scripts/init-testnet/ddos/ddos_tx.ts @@ -0,0 +1,135 @@ +// Required imports +const { ApiPromise, WsProvider } = require('@polkadot/api'); +const { hexToU8a } = require('@polkadot/util'); + +const { Keyring } = require('@polkadot/keyring'); + +async function main () { + // Initialise the provider to connect to the local node + //const provider = new WsProvider('wss://rpc.nice.hydration.cloud'); + const provider = new WsProvider('ws://127.0.0.1:9988'); + + // Create the API and wait until ready + const api = await ApiPromise.create({ provider }); + + // Retrieve the chain & node information information via rpc calls + const [chain, nodeName, nodeVersion] = await Promise.all([ + api.rpc.system.chain(), + api.rpc.system.name(), + api.rpc.system.version() + ]); + + console.log(`You are connected to chain ${chain} using ${nodeName} v${nodeVersion}`); + + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + let balance = await api.query.system.account(alice.publicKey); + console.log(`Alice's HDX balance before DDOS ${balance.data.free}`); + + const blockNumber = await api.rpc.chain.getBlock(); + + let alice_pub_key = alice.publicKey; + + await doOmnipoolSells(api, alice, blockNumber.block.header.number); + + let balance_after = await api.query.system.account(alice.publicKey); + console.log(`Alice's HDX balance after DDOS ${balance_after.data.free}`); + + let balance_diff = balance.data.free - balance_after.data.free; + + console.log(`Alice's spent balance ${balance_diff}`); + +} + +main().catch(console.error).finally(() => process.exit()); + +async function doOmnipoolSells(api, user, block) { + const start = Date.now(); + + let counter = 0; + let prev_block = block; + let prev_balance = await api.query.system.account(user.publicKey); + let multiplier = await api.query.transactionPayment.nextFeeMultiplier(); + + + while (1) { + const blockInfo = await api.rpc.chain.getBlock(); + const blockNumber = blockInfo.block.header.number; + + //Change this `ddos_run_duration` variable to define how long (in blocks) the DDOS should take + let ddos_run_duration = 350; + let block_spent = blockNumber - block; + if (block_spent == ddos_run_duration) { + console.log(`The specified blocktime ${block_spent} passed`); + break; + } + + //If there is a new block + if (!(Math.abs(blockNumber - prev_block) < Number.EPSILON)) { + let sells_per_block = 62; + for (let i = 0; i < sells_per_block; i++) { + let user_pub_key = user.publicKey; + const nonce = await api.rpc.system.accountNextIndex(user_pub_key); + const tip = 1; + + await omniSell(api, user, nonce, tip) + } + + //Print out block feeresult + let balance = await api.query.system.account(user.publicKey); + let balance_diff = prev_balance.data.free - balance.data.free; + const blockWeight = await api.query.system.blockWeight(); + + let fee_per_tx = balance_diff / sells_per_block/1000000000000; + + const info = await api.tx.omnipool.sell(5, 2, 100000000000000, 0).paymentInfo(user); + const minute_passed = Math.round((Date.now() - start)/1000/60); + let multiplier = await api.query.transactionPayment.nextFeeMultiplier(); + + console.log(`Fee: ${info.partialFee.toHuman()} with multiplier ${multiplier}| Analyses: ${minute_passed} mins - ${balance_diff} HDX fee (${fee_per_tx} per tx) spent in block ${blockNumber} with weight ${blockWeight.normal.refTime}`); + + prev_block = blockNumber; + prev_balance = balance; + } + } + +async function createDca(user_pub_key, nonce, tip) { + try { + await api.tx.dca + .schedule( + { + owner: user_pub_key, + period: 1, + totalAmount: 1000000000000000, + maxRetries: null, + stabilityThreshold: null, + slippage: null, + order: { + Sell: { + assetIn: 5, + assetOut: 2, + amountIn: 100000000000000, + minAmountOut: 0, + route: null + } + } + }, + null) + .signAndSend(user, { nonce, tip }); + } catch (error) { + console.log("Error while sending DCA - Sent transaction counter when signing fails: ", counter); + } + } +} + + +async function omniSell(api, user, nonce, tip) { + try { + await api.tx.omnipool + .sell(5, 2, 100000000000000, 0) + .signAndSend(user, { nonce, tip }); + } catch (error) { + console.log("Error while sending DCA - Sent transaction counter when signing fails: ", error); + } +} \ No newline at end of file diff --git a/scripts/init-testnet/evm/assets.json b/scripts/init-testnet/evm/assets.json new file mode 100644 index 000000000..65d53cef6 --- /dev/null +++ b/scripts/init-testnet/evm/assets.json @@ -0,0 +1,254 @@ +{ + "0": { + "asset": { + "name": "HydraDX", + "assetType": "Token", + "existentialDeposit": "1,000,000,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "HDX", + "decimals": "12" + } + }, + "1": { + "asset": { + "name": "Lerna", + "assetType": "Token", + "existentialDeposit": "400,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "LRNA", + "decimals": "12" + } + }, + "2": { + "asset": { + "name": "DAI Stablecoin (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "10,000,000,000,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "DAI", + "decimals": "18" + } + }, + "3": { + "asset": { + "name": "Wrapped Bitcoin (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "44", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "WBTC", + "decimals": "8" + } + }, + "4": { + "asset": { + "name": "Ethereum (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "7,000,000,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "WEX", + "decimals": "18" + } + }, + "5": { + "asset": { + "name": "Polkadot", + "assetType": "Token", + "existentialDeposit": "17,540,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "DOT", + "decimals": "10" + } + }, + "6": { + "asset": { + "name": "ApeCoin (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "2,518,891,687,657,430", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "APE", + "decimals": "18" + } + }, + "7": { + "asset": { + "name": "USD Coin (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "10,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "USDC", + "decimals": "6" + } + }, + "8": { + "asset": { + "name": "Phala", + "assetType": "Token", + "existentialDeposit": "54,945,054,945", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "PHA", + "decimals": "12" + } + }, + "9": { + "asset": { + "name": "Astar", + "assetType": "Token", + "existentialDeposit": "147,058,823,529,412,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "ASTR", + "decimals": "18" + } + }, + "10": { + "asset": { + "name": "Statemint USDT", + "assetType": "Token", + "existentialDeposit": "10,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "USDT", + "decimals": "6" + } + }, + "11": { + "asset": { + "name": "interBTC", + "assetType": "Token", + "existentialDeposit": "36", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "iBTC", + "decimals": "8" + } + }, + "12": { + "asset": { + "name": "Zeitgeist", + "assetType": "Token", + "existentialDeposit": "1,204,151,916", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "ZTG", + "decimals": "10" + } + }, + "13": { + "asset": { + "name": "Centrifuge", + "assetType": "Token", + "existentialDeposit": "32,467,532,467,532,500", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "CFG", + "decimals": "18" + } + }, + "14": { + "asset": { + "name": "Bifrost Native Coin", + "assetType": "Token", + "existentialDeposit": "68,795,189,840", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "BNC", + "decimals": "12" + } + }, + "15": { + "asset": { + "name": "Bifrost Voucher DOT", + "assetType": "Token", + "existentialDeposit": "18,761,726", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "vDOT", + "decimals": "10" + } + }, + "16": { + "asset": { + "name": "Glimmer", + "assetType": "Token", + "existentialDeposit": "34,854,864,344,868,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "GLMR", + "decimals": "18" + } + }, + "17": { + "asset": { + "name": "Interlay", + "assetType": "Token", + "existentialDeposit": "6,164,274,209", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "INTR", + "decimals": "10" + } + }, + "18": { + "asset": { + "name": "Random token 1 (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "7,000,000,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "RAND1", + "decimals": "18" + } + }, + "19": { + "asset": { + "name": "Random token 2 (via Wormhole)", + "assetType": "Token", + "existentialDeposit": "7,000,000,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "RAND2", + "decimals": "18" + } + }, + "20": { + "asset": { + "name": "Ethereum (via Moonbeam)", + "assetType": "Token", + "existentialDeposit": "7,000,000,000,000", + "xcmRateLimit": null + }, + "metadata": { + "symbol": "WETH", + "decimals": "18" + } + } + } \ No newline at end of file diff --git a/scripts/init-testnet/evm/dispatch.ts b/scripts/init-testnet/evm/dispatch.ts new file mode 100644 index 000000000..98cff0395 --- /dev/null +++ b/scripts/init-testnet/evm/dispatch.ts @@ -0,0 +1,56 @@ +const { ethers } = require('ethers'); + +async function main() { + // Define the Ethereum RPC URL + //const ethereumRpcUrl = "https://rpc.nice.hydration.cloud"; // Replace with your Ethereum RPC URL + + const ethereumRpcUrl = "http://127.0.0.1:9988"; // Replace with your Ethereum RPC URL + + // Create a provider using the Ethereum RPC URL + const provider = new ethers.JsonRpcProvider(ethereumRpcUrl); + + try { + // Example: Fetch the Ethereum block number + const blockNumber = await provider.getBlockNumber(); + console.log("Ethereum Block Number:", blockNumber); + + // Example: Fetch the balance of an Ethereum address + const address = "0x222222ff7Be76052e023Ec1a306fCca8F9659D80"; // Replace with the Ethereum address you want to check + const balance = await provider.getBalance(address); + console.log("Ethereum Balance (Wei):", balance.toString()); + + + const privateKey = '42d8d953e4f9246093a33e9ca6daa078501012f784adfe4bbed57918ff13be14'; + const wallet = new ethers.Wallet(privateKey, provider); + + const dispatchContractAddress = '0x0000000000000000000000000000000000000401'; + + try { + // Omnipool sell call encoded + const extrinsicData = '0x3b05000000000500000000a0724e18090000000000000000000000000000000000000000000000000000'; // Replace with your extrinsic data + const nonce = await provider.getTransactionCount(wallet.address, 'latest'); + + const tx = { + to: dispatchContractAddress, + value: 0, + nonce: nonce, + gasLimit: 100000, // Adjust the gas limit accordingly + gasPrice: ethers.parseUnits('80000000', 'wei'), // Adjust the gas price accordingly + data: extrinsicData, + }; + + const signer= wallet.connect(provider); // Connect the wallet to the provider + await signer.sendTransaction(tx); + + } catch (error) { + console.error('Error:', error); + } + + + + } catch (error) { + console.error("Error:", error); + } +} + +main(); \ No newline at end of file diff --git a/scripts/init-testnet/evm/init_chain.ts b/scripts/init-testnet/evm/init_chain.ts new file mode 100644 index 000000000..2566e5c2d --- /dev/null +++ b/scripts/init-testnet/evm/init_chain.ts @@ -0,0 +1,215 @@ +// Required imports +const { ApiPromise, WsProvider } = require('@polkadot/api'); +const { hexToU8a } = require('@polkadot/util'); +const { encodeAddress, decodeAddress } = require('@polkadot/util-crypto'); +const { Keyring } = require('@polkadot/keyring'); + +async function main () { + // Initialise the provider to connect to the local node + //const provider = new WsProvider('wss://rpc.nice.hydration.cloud'); + const provider = new WsProvider('ws://127.0.0.1:9988'); + + // Create the API and wait until ready + const api = await ApiPromise.create({ provider }); + + // Retrieve the chain & node information information via rpc calls + const [chain, nodeName, nodeVersion] = await Promise.all([ + api.rpc.system.chain(), + api.rpc.system.name(), + api.rpc.system.version() + ]); + + console.log(`You are connected to chain ${chain} using ${nodeName} v${nodeVersion}`); + + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + let transactions = []; + + assetRegistry(api, transactions); + mintForAlice(api, transactions); + initOmnipool(api, transactions) + setWethLocation(api,transactions); + mintMetamaskWeth(api,transactions); + let tx = api.tx.multiTransactionPayment.addCurrency(20, '16420844565569051996'); + transactions.push(tx); + + let batch = api.tx.utility.batchAll(transactions); + + await api.tx.preimage.notePreimage(batch.method.toHex()).signAndSend(alice); +} + +main().catch(console.error).finally(() => process.exit()); + +function mintForTreasuryDca(api, txs) { + let treasury = "7KQx4f7yU3hqZHfvDVnSfe6mpgAT8Pxyr67LXHV6nsbZo3Tm"; + //weth + txs.push( + api.tx.currencies.updateBalance(treasury, 5, "5000000000000000") + ); + +} + +function mintForAlice(api, txs) { + let alice = "7NPoMQbiA6trJKkjB35uk96MeJD4PGWkLQLH7k7hXEkZpiba"; + txs.push( + api.tx.currencies.updateBalance(alice, 0, "5000000000000000000") + ); + + txs.push(api.tx.currencies.updateBalance(alice, 1, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 2, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 3, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 4, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 5, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 6, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 7, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 8, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 9, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 10, "5000000000000000000")); + txs.push(api.tx.currencies.updateBalance(alice, 20, "5000000000000000000")); + +} + +function assetRegistry(api, txs) { + const assets = require('./assets.json'); + let keys = Object.keys(assets); + + for (let i = 0, l = keys.length; i < l; i++) { + let k = keys[i]; + let a = assets[k]; + let tx; + + if (k == "0") { + tx = api.tx.assetRegistry.setMetadata(k, a.metadata.symbol, a.metadata.decimals); + txs.push(tx); + + continue; + } + + if (k == "1" || k == "2") { + let aType = {}; + aType[a.asset.assetType] = 0; + + tx = api.tx.assetRegistry.update(k, a.asset.name, aType, 100, null); + txs.push(tx); + + tx = api.tx.assetRegistry.setMetadata(k, a.metadata.symbol, a.metadata.decimals); + txs.push(tx); + continue; + } + + let aType = {}; + aType[a.asset.assetType] = 0; + + a.metadata.decimals = Number(a.metadata.decimals); + + tx = api.tx.assetRegistry.register(a.asset.name, aType, 100, k, a.metadata, null, null); + txs.push(tx); + }; + + return txs; +} + +function setWethLocation(api, txs) { + const multiLocation = { + parents: 1, + interior: { + X3: [ + {Parachain: 2004}, + {PalletInstance: 110}, + {AccountKey20: {key: "0xab3f0245b83feb11d15aaffefd7ad465a59817ed"}} + ] + } + }; + + let tx = api.tx.assetRegistry.setLocation(20, multiLocation); + txs.push(tx); + + return txs; +} + + +function mintMetamaskWeth(api, txs) { + let my_original_metamask = "7KATdGbFsc58BDyfV9ZtxHEYPt5icvS5itHcJh3yWYmpwG8k"; + let new_metamask = "7KATdGauM2GcuFoZ91PPA8gp1BxWVLBEor7h8TJT3xDm2f5Y"; + let test_acc = "7KATdGakyhfBGnAt3XVgXTL7cYjzRXeSZHezKNtENcbwWibb" + //hdx + /*txs.push( + api.tx.currencies.updateBalance(new_metamask, 0, "226329588000000000") + );*/ + + //weth + txs.push( + api.tx.currencies.updateBalance(my_original_metamask, 20, "1936329588000000000") + ); + txs.push( + api.tx.currencies.updateBalance(new_metamask, 20, "1936329588000000000") + ); + + txs.push( + api.tx.currencies.updateBalance(test_acc, 20, "1936329588000000000") + ); + + txs.push( + api.tx.currencies.updateBalance(my_original_metamask, 5, "1936329588000000000") + ); + txs.push( + api.tx.currencies.updateBalance(new_metamask, 5, "1936329588000000000") + ); + + txs.push( + api.tx.currencies.updateBalance(test_acc, 5, "1936329588000000000") + ); +} + +/* +function mintStaking(api, txs) { + let stakingPot = "7L53bUTCbRAv4KC8NGQC17Cv8VDWYgbeCnLkT3tShzisck4C"; + //hdx + txs.push( + api.tx.currencies.updateBalance(stakingPot, 0, "1000000000000000") + ); +} + +function initStaking(api, txs) { + txs.push( + api.tx.staking.initializeStaking() + ); +}*/ + + +function initOmnipool(api, txs) { + let omniAccount = "7L53bUTBbfuj14UpdCNPwmgzzHSsrsTWBHX5pys32mVWM3C1"; + //hdx + txs.push( + api.tx.currencies.updateBalance(omniAccount, 0, "936329588000000000") + ); + + //dai + txs.push( + api.tx.currencies.updateBalance(omniAccount, 2, "50000000000000000000000") + ); + + txs.push( + api.tx.currencies.updateBalance(omniAccount, 5, "936329588000000000") + ); + + txs.push( + api.tx.currencies.updateBalance(omniAccount, 20, "1434000000000000000000") + ); + + txs.push( + api.tx.omnipool.addToken(0, "1201500000000000", 1000000, omniAccount) + ); + txs.push( + api.tx.omnipool.addToken(2, "1501500000000000", 1000000, omniAccount) + ); + + txs.push( + api.tx.omnipool.addToken(5, "1701500000000000", 1000000, omniAccount) + ); + + txs.push( + api.tx.omnipool.addToken(20, "16420844565569051996", 1000000, omniAccount) + ); +} \ No newline at end of file diff --git a/scripts/init-testnet/index.js b/scripts/init-testnet/index.js index 0b12c8e9c..f1ba3d055 100644 --- a/scripts/init-testnet/index.js +++ b/scripts/init-testnet/index.js @@ -3,7 +3,7 @@ const { ApiPromise, WsProvider } = require('@polkadot/api'); const { Keyring } = require('@polkadot/keyring'); async function main () { - let rpcAddr = process.argv[2] || 'ws://localhost:9946'; + let rpcAddr = process.argv[2] || 'ws://localhost:9988'; console.log(`\nConnecting to RPC node: ${rpcAddr}\n`); diff --git a/scripts/init-testnet/package.json b/scripts/init-testnet/package.json index f5ea954cd..374d5f066 100644 --- a/scripts/init-testnet/package.json +++ b/scripts/init-testnet/package.json @@ -12,6 +12,7 @@ "dependencies": { "@polkadot/api": "^9.3.3", "@polkadot/keyring": "^12.3.2", - "@acala-network/chopsticks-utils": "^0.9.8" + "@acala-network/chopsticks-utils": "^0.9.8", + "ethers": "^6.10.0" } }