From da8445c508e25b014a9c06ae30f48fa1151084a8 Mon Sep 17 00:00:00 2001 From: Freddy Li Date: Thu, 3 Aug 2023 03:17:28 -0400 Subject: [PATCH] feat: Add percentage fee handler pallet (#118) Add percentage fee handler pallet --- .github/actions/install_toolchain/action.yml | 2 +- Cargo.lock | 23 + Cargo.toml | 1 + Dockerfile_e2e | 2 +- Makefile | 10 +- README.md | 2 +- basic-fee-handler/src/lib.rs | 6 +- bridge/Cargo.toml | 2 + bridge/src/lib.rs | 546 ++++++++++++++++--- bridge/src/mock.rs | 29 +- fee-handler-router/Cargo.toml | 2 + fee-handler-router/src/lib.rs | 26 +- fee-handler-router/src/mock.rs | 13 +- parachain-info/src/lib.rs | 2 +- percentage-fee-handler/Cargo.toml | 54 ++ percentage-fee-handler/src/benchmarking.rs | 38 ++ percentage-fee-handler/src/lib.rs | 263 +++++++++ percentage-fee-handler/src/mock.rs | 170 ++++++ percentage-fee-handler/src/weights.rs | 51 ++ scripts/e2e_setup.sh | 2 +- scripts/js/setup.js | 21 +- scripts/js/util.js | 36 ++ substrate-node/runtime/Cargo.toml | 4 + substrate-node/runtime/src/lib.rs | 19 +- traits/src/lib.rs | 6 +- 25 files changed, 1222 insertions(+), 108 deletions(-) create mode 100644 percentage-fee-handler/Cargo.toml create mode 100644 percentage-fee-handler/src/benchmarking.rs create mode 100644 percentage-fee-handler/src/lib.rs create mode 100644 percentage-fee-handler/src/mock.rs create mode 100644 percentage-fee-handler/src/weights.rs diff --git a/.github/actions/install_toolchain/action.yml b/.github/actions/install_toolchain/action.yml index fe67410..7475edf 100644 --- a/.github/actions/install_toolchain/action.yml +++ b/.github/actions/install_toolchain/action.yml @@ -9,7 +9,7 @@ runs: - name: Install latest nightly uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2023-02-03 + toolchain: nightly-2023-06-01 override: true target: wasm32-unknown-unknown components: rustfmt diff --git a/Cargo.lock b/Cargo.lock index b07cd53..06d6378 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4615,6 +4615,7 @@ dependencies = [ "sygma-basic-feehandler", "sygma-bridge", "sygma-fee-handler-router", + "sygma-percentage-feehandler", "sygma-runtime-api", "sygma-traits", "xcm", @@ -8968,6 +8969,7 @@ dependencies = [ "sygma-access-segregator", "sygma-basic-feehandler", "sygma-fee-handler-router", + "sygma-percentage-feehandler", "sygma-traits", "xcm", "xcm-builder", @@ -8990,6 +8992,27 @@ dependencies = [ "sp-std", "sygma-access-segregator", "sygma-basic-feehandler", + "sygma-percentage-feehandler", + "sygma-traits", + "xcm", + "xcm-builder", +] + +[[package]] +name = "sygma-percentage-feehandler" +version = "0.3.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", + "sygma-access-segregator", "sygma-traits", "xcm", "xcm-builder", diff --git a/Cargo.toml b/Cargo.toml index 85ed732..e700606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "access-segregator", "basic-fee-handler", + "percentage-fee-handler", "bridge", "fee-handler-router", "traits", diff --git a/Dockerfile_e2e b/Dockerfile_e2e index eb57472..e76d737 100644 --- a/Dockerfile_e2e +++ b/Dockerfile_e2e @@ -34,4 +34,4 @@ ENV MPCADDR $mpc_address RUN ["./scripts/e2e_setup.sh"] -ENTRYPOINT ["./node-template", "--dev", "--ws-external", "--base-path", "./db/"] +ENTRYPOINT ["./node-template", "--dev", "--rpc-external", "--base-path", "./db/"] diff --git a/Makefile b/Makefile index c02771c..1648004 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ fmt: test: cargo test +# run unit test with benchmark +test-benchmark: + cargo test --features runtime-benchmarks + # license-check Checks for missing license crates license-check: @echo " > \033[Checking for license headers...\033[0m " @@ -20,10 +24,14 @@ license-check: build: cargo build --release +# build the binary locally with benchmark +build-benchmark: + cargo build --release --features runtime-benchmarks + ############################## Local node ############################ # launch the local node in dev mode start-dev: - ./target/release/node-template --dev --ws-external + ./target/release/node-template --dev --rpc-external # run setup js script to setup the local substrate node # substrate node is required, run make start-dev first diff --git a/README.md b/README.md index 14af317..8c5ad74 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ This repo contains several substrate pallet implementation for Sygma protocol. - Run docker container as local testnet ```sh - $ docker run -p 9944:9944 -it sygma-substrate-pallet --dev --ws-external + $ docker run -p 9944:9944 -it sygma-substrate-pallet --dev --rpc-external ``` ## Interact via Polkadot JS App diff --git a/basic-fee-handler/src/lib.rs b/basic-fee-handler/src/lib.rs index ba92129..5737175 100644 --- a/basic-fee-handler/src/lib.rs +++ b/basic-fee-handler/src/lib.rs @@ -21,7 +21,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_std::boxed::Box; use sygma_traits::{DomainID, FeeHandler}; - use xcm::latest::AssetId; + use xcm::latest::{AssetId, MultiAsset}; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -96,8 +96,8 @@ pub mod pallet { } impl FeeHandler for Pallet { - fn get_fee(domain: DomainID, asset: &AssetId) -> Option { - AssetFees::::get((domain, asset)) + fn get_fee(domain: DomainID, asset: MultiAsset) -> Option { + AssetFees::::get((domain, &asset.id)) } } diff --git a/bridge/Cargo.toml b/bridge/Cargo.toml index 380f6c6..2e3e201 100644 --- a/bridge/Cargo.toml +++ b/bridge/Cargo.toml @@ -37,6 +37,7 @@ xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "relea sygma-traits = { path = "../traits", default-features = false } sygma-access-segregator = { path = "../access-segregator", default-features = false } sygma-basic-feehandler = { path = "../basic-fee-handler", default-features = false } +sygma-percentage-feehandler = { path = "../percentage-fee-handler", default-features = false } sygma-fee-handler-router = { path = "../fee-handler-router", default-features = false } [dev-dependencies] @@ -94,6 +95,7 @@ std = [ "sygma-traits/std", "sygma-access-segregator/std", "sygma-basic-feehandler/std", + "sygma-percentage-feehandler/std", "sygma-fee-handler-router/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/bridge/src/lib.rs b/bridge/src/lib.rs index 73b9017..13ca199 100644 --- a/bridge/src/lib.rs +++ b/bridge/src/lib.rs @@ -170,6 +170,14 @@ pub mod pallet { RegisterDestDomain { sender: T::AccountId, domain_id: DomainID, chain_id: ChainID }, /// When unregistering a dest domainID with its corresponding chainID UnregisterDestDomain { sender: T::AccountId, domain_id: DomainID, chain_id: ChainID }, + /// When bridge fee is collected + FeeCollected { + fee_payer: T::AccountId, + dest_domain_id: DomainID, + resource_id: ResourceId, + fee_amount: u128, + fee_asset_id: AssetId, + }, } #[pallet::error] @@ -435,9 +443,9 @@ pub mod pallet { // Extract asset (MultiAsset) to get corresponding ResourceId, transfer amount and the // transfer type let (resource_id, amount, transfer_type) = - Self::extract_asset(&asset).ok_or(Error::::AssetNotBound)?; + Self::extract_asset(&asset.clone()).ok_or(Error::::AssetNotBound)?; // Return error if no fee handler set - let fee = T::FeeHandler::get_fee(dest_domain_id, &asset.id) + let fee = T::FeeHandler::get_fee(dest_domain_id, *asset.clone()) .ok_or(Error::::MissingFeeConfig)?; ensure!(amount > fee, Error::::FeeTooExpensive); @@ -495,12 +503,21 @@ pub mod pallet { dest_domain_id, resource_id, deposit_nonce, - sender, + sender: sender.clone(), transfer_type, deposit_data: Self::create_deposit_data(decimal_converted_amount, recipient), handler_response: vec![], }); + // Emit FeeCollected event + Self::deposit_event(Event::FeeCollected { + fee_payer: sender, + dest_domain_id, + resource_id, + fee_amount: fee, + fee_asset_id: asset.id, + }); + Ok(()) } @@ -850,8 +867,8 @@ pub mod pallet { assert_events, new_test_ext, slice_to_generalkey, AccessSegregator, Assets, Balances, BridgeAccount, BridgePalletIndex, NativeLocation, NativeResourceId, Runtime, RuntimeEvent, RuntimeOrigin as Origin, SygmaBasicFeeHandler, SygmaBridge, - TreasuryAccount, UsdcAssetId, UsdcLocation, UsdcResourceId, ALICE, ASSET_OWNER, BOB, - DEST_DOMAIN_ID, ENDOWED_BALANCE, + SygmaFeeHandlerRouter, SygmaPercentageFeeHandler, TreasuryAccount, UsdcAssetId, + UsdcLocation, UsdcResourceId, ALICE, ASSET_OWNER, BOB, DEST_DOMAIN_ID, ENDOWED_BALANCE, }; use codec::{self, Encode}; use frame_support::{ @@ -861,6 +878,7 @@ pub mod pallet { use primitive_types::U256; use sp_core::{ecdsa, Pair}; use sp_std::{boxed::Box, vec}; + use sygma_fee_handler_router::FeeHandlerType; use sygma_traits::{MpcAddress, TransferType}; use xcm::latest::prelude::*; @@ -1118,16 +1136,22 @@ pub mod pallet { let amount = 200_000_000_000_000u128; // 200 with 12 decimals let final_amount_in_deposit_event = 199_000_000_000_000_000_000; // 200 - 1 then adjust to 18 decimals + assert_ok!(SygmaBridge::register_domain( + Origin::root(), + DEST_DOMAIN_ID, + U256::from(1) + )); assert_ok!(SygmaBasicFeeHandler::set_fee( Origin::root(), DEST_DOMAIN_ID, Box::new(NativeLocation::get().into()), fee )); - assert_ok!(SygmaBridge::register_domain( + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( Origin::root(), DEST_DOMAIN_ID, - U256::from(1) + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, )); assert_ok!(SygmaBridge::set_mpc_address(Origin::root(), test_mpc_addr)); @@ -1147,18 +1171,27 @@ pub mod pallet { assert_eq!(Balances::free_balance(BridgeAccount::get()), amount - fee); assert_eq!(Balances::free_balance(TreasuryAccount::get()), fee); // Check event - assert_events(vec![RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { - dest_domain_id: DEST_DOMAIN_ID, - resource_id: NativeResourceId::get(), - deposit_nonce: 0, - sender: ALICE, - transfer_type: TransferType::FungibleTransfer, - deposit_data: SygmaBridge::create_deposit_data( - final_amount_in_deposit_event, - b"ethereum recipient".to_vec(), - ), - handler_response: vec![], - })]); + assert_events(vec![ + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { + dest_domain_id: DEST_DOMAIN_ID, + resource_id: NativeResourceId::get(), + deposit_nonce: 0, + sender: ALICE, + transfer_type: TransferType::FungibleTransfer, + deposit_data: SygmaBridge::create_deposit_data( + final_amount_in_deposit_event, + b"ethereum recipient".to_vec(), + ), + handler_response: vec![], + }), + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::FeeCollected { + fee_payer: ALICE, + dest_domain_id: DEST_DOMAIN_ID, + resource_id: NativeResourceId::get(), + fee_amount: fee, + fee_asset_id: NativeLocation::get().into(), + }), + ]); }) } @@ -1216,6 +1249,12 @@ pub mod pallet { Box::new(UsdcLocation::get().into()), fee )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(UsdcLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); assert_ok!(SygmaBridge::register_domain( Origin::root(), DEST_DOMAIN_ID, @@ -1250,21 +1289,30 @@ pub mod pallet { )); // Check balances assert_eq!(Assets::balance(UsdcAssetId::get(), &ALICE), ENDOWED_BALANCE - amount); - assert_eq!(Assets::balance(UsdcAssetId::get(), &BridgeAccount::get()), 0); - assert_eq!(Assets::balance(UsdcAssetId::get(), &TreasuryAccount::get()), fee); + assert_eq!(Assets::balance(UsdcAssetId::get(), BridgeAccount::get()), 0); + assert_eq!(Assets::balance(UsdcAssetId::get(), TreasuryAccount::get()), fee); // Check event - assert_events(vec![RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { - dest_domain_id: DEST_DOMAIN_ID, - resource_id: UsdcResourceId::get(), - deposit_nonce: 0, - sender: ALICE, - transfer_type: TransferType::FungibleTransfer, - deposit_data: SygmaBridge::create_deposit_data( - amount - fee, - b"ethereum recipient".to_vec(), - ), - handler_response: vec![], - })]); + assert_events(vec![ + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { + dest_domain_id: DEST_DOMAIN_ID, + resource_id: UsdcResourceId::get(), + deposit_nonce: 0, + sender: ALICE, + transfer_type: TransferType::FungibleTransfer, + deposit_data: SygmaBridge::create_deposit_data( + amount - fee, + b"ethereum recipient".to_vec(), + ), + handler_response: vec![], + }), + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::FeeCollected { + fee_payer: ALICE, + dest_domain_id: DEST_DOMAIN_ID, + resource_id: UsdcResourceId::get(), + fee_amount: fee, + fee_asset_id: UsdcLocation::get().into(), + }), + ]); }) } @@ -1383,6 +1431,12 @@ pub mod pallet { Box::new(NativeLocation::get().into()), fee )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); assert_ok!(SygmaBridge::register_domain( Origin::root(), DEST_DOMAIN_ID, @@ -1418,6 +1472,12 @@ pub mod pallet { Box::new(NativeLocation::get().into()), fee )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); // register domain assert_ok!(SygmaBridge::register_domain( Origin::root(), @@ -1575,6 +1635,12 @@ pub mod pallet { Box::new(NativeLocation::get().into()), fee )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); assert_ok!(SygmaBridge::deposit( Origin::signed(ALICE), Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), @@ -1940,6 +2006,24 @@ pub mod pallet { Box::new(AstrLocation::get().into()), fee_astr_asset )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(UsdcLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(AstrLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); assert_ok!(SygmaBridge::register_domain( Origin::root(), @@ -1971,18 +2055,27 @@ pub mod pallet { // TreasuryAccount is collecting the bridging fee assert_eq!(Balances::free_balance(TreasuryAccount::get()), fee_native_asset); // Check event - assert_events(vec![RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { - dest_domain_id: DEST_DOMAIN_ID, - resource_id: NativeResourceId::get(), - deposit_nonce: 0, - sender: ALICE, - transfer_type: TransferType::FungibleTransfer, - deposit_data: SygmaBridge::create_deposit_data( - adjusted_amount_native_asset, - b"ethereum recipient".to_vec(), - ), - handler_response: vec![], - })]); + assert_events(vec![ + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { + dest_domain_id: DEST_DOMAIN_ID, + resource_id: NativeResourceId::get(), + deposit_nonce: 0, + sender: ALICE, + transfer_type: TransferType::FungibleTransfer, + deposit_data: SygmaBridge::create_deposit_data( + adjusted_amount_native_asset, + b"ethereum recipient".to_vec(), + ), + handler_response: vec![], + }), + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::FeeCollected { + fee_payer: ALICE, + dest_domain_id: DEST_DOMAIN_ID, + resource_id: NativeResourceId::get(), + fee_amount: fee_native_asset, + fee_asset_id: NativeLocation::get().into(), + }), + ]); // deposit usdc asset which has 18 decimal // Register foreign asset (usdc) with asset id 0 @@ -2017,7 +2110,7 @@ pub mod pallet { ENDOWED_BALANCE - amount_usdc_asset ); // usdc asset should not be reserved so that BridgeAccount should not hold it - assert_eq!(Assets::balance(UsdcAssetId::get(), &BridgeAccount::get()), 0); + assert_eq!(Assets::balance(UsdcAssetId::get(), BridgeAccount::get()), 0); // TreasuryAccount is collecting the bridging fee assert_eq!( Assets::balance(UsdcAssetId::get(), TreasuryAccount::get()), @@ -2025,18 +2118,27 @@ pub mod pallet { ); // Check event - assert_events(vec![RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { - dest_domain_id: DEST_DOMAIN_ID, - resource_id: UsdcResourceId::get(), - deposit_nonce: 1, - sender: ALICE, - transfer_type: TransferType::FungibleTransfer, - deposit_data: SygmaBridge::create_deposit_data( - adjusted_amount_usdc_asset, - b"ethereum recipient".to_vec(), - ), - handler_response: vec![], - })]); + assert_events(vec![ + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { + dest_domain_id: DEST_DOMAIN_ID, + resource_id: UsdcResourceId::get(), + deposit_nonce: 1, + sender: ALICE, + transfer_type: TransferType::FungibleTransfer, + deposit_data: SygmaBridge::create_deposit_data( + adjusted_amount_usdc_asset, + b"ethereum recipient".to_vec(), + ), + handler_response: vec![], + }), + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::FeeCollected { + fee_payer: ALICE, + dest_domain_id: DEST_DOMAIN_ID, + resource_id: UsdcResourceId::get(), + fee_amount: fee_usdc_asset, + fee_asset_id: UsdcLocation::get().into(), + }), + ]); // deposit astr asset which has 24 decimal // Register foreign asset (astr) with asset id 1 @@ -2073,7 +2175,7 @@ pub mod pallet { // astr asset should be reserved so that BridgeAccount should hold it(Astr is not // defined in ConcrateSygmaAsset) assert_eq!( - Assets::balance(AstrAssetId::get(), &BridgeAccount::get()), + Assets::balance(AstrAssetId::get(), BridgeAccount::get()), amount_astr_asset - fee_astr_asset ); // TreasuryAccount is collecting the bridging fee @@ -2083,18 +2185,27 @@ pub mod pallet { ); // Check event - assert_events(vec![RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { - dest_domain_id: DEST_DOMAIN_ID, - resource_id: AstrResourceId::get(), - deposit_nonce: 2, - sender: ALICE, - transfer_type: TransferType::FungibleTransfer, - deposit_data: SygmaBridge::create_deposit_data( - adjusted_amount_astr_asset, - b"ethereum recipient".to_vec(), - ), - handler_response: vec![], - })]); + assert_events(vec![ + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { + dest_domain_id: DEST_DOMAIN_ID, + resource_id: AstrResourceId::get(), + deposit_nonce: 2, + sender: ALICE, + transfer_type: TransferType::FungibleTransfer, + deposit_data: SygmaBridge::create_deposit_data( + adjusted_amount_astr_asset, + b"ethereum recipient".to_vec(), + ), + handler_response: vec![], + }), + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::FeeCollected { + fee_payer: ALICE, + dest_domain_id: DEST_DOMAIN_ID, + resource_id: AstrResourceId::get(), + fee_amount: fee_astr_asset, + fee_asset_id: AstrLocation::get().into(), + }), + ]); // deposit astr asset which has 24 decimal, extreme small amount edge case let amount_astr_asset_extreme_small_amount = 100_000; // 0.000000000000000000100000 astr @@ -2152,6 +2263,12 @@ pub mod pallet { Box::new(NativeLocation::get().into()), fee )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); // deposit in advance to make sure the native asset has enough funds in // TransferReserveAccount by doing this, Alice will deposit (half of her native // asset - fee) into TransferReserveAccount @@ -2493,6 +2610,12 @@ pub mod pallet { Box::new(NativeLocation::get().into()), fee )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); assert_ok!(SygmaBridge::deposit( Origin::signed(ALICE), Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), @@ -2534,5 +2657,286 @@ pub mod pallet { assert_eq!(Balances::free_balance(&BOB), ENDOWED_BALANCE + 200000000); }) } + + #[test] + fn deposit_native_asset_with_percentage_fee() { + new_test_ext().execute_with(|| { + let test_mpc_addr: MpcAddress = MpcAddress([1u8; 20]); + let amount = 200_000_000_000_000u128; // 200 with 12 decimals + + // test cases + let fee_rate_1 = 500u32; // 5% + let fee_rate_2 = 10000u32; // 100% + let fee_rate_3 = 9999u32; // 99.99% + let fee_rate_4 = 0u32; // 0% + let fee_rate_5 = 15000u32; // 150% + + assert_ok!(SygmaBridge::register_domain( + Origin::root(), + DEST_DOMAIN_ID, + U256::from(1) + )); + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::PercentageFeeHandler, + )); + assert_ok!(SygmaBridge::set_mpc_address(Origin::root(), test_mpc_addr)); + + // test 5% + assert_ok!(SygmaPercentageFeeHandler::set_fee_rate( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + fee_rate_1 + )); + assert_ok!(SygmaBridge::deposit( + Origin::signed(ALICE), + Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), + Box::new(MultiLocation { + parents: 0, + interior: X2( + slice_to_generalkey(b"ethereum recipient"), + slice_to_generalkey(&[1]), + ) + }), + )); + // Check balances of Alice after deposit 200 native token + assert_eq!(Balances::free_balance(ALICE), ENDOWED_BALANCE - amount); + // Check reserved native token + assert_eq!(Balances::free_balance(BridgeAccount::get()), 190_000_000_000_000u128); + // Check fee collected + assert_eq!(Balances::free_balance(TreasuryAccount::get()), 10_000_000_000_000u128); + // Check event + let final_amount_in_deposit_event_1 = 190_000_000_000_000_000_000; // 200 cut 5% then adjust to 18 decimals + assert_events(vec![ + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::Deposit { + dest_domain_id: DEST_DOMAIN_ID, + resource_id: NativeResourceId::get(), + deposit_nonce: 0, + sender: ALICE, + transfer_type: TransferType::FungibleTransfer, + deposit_data: SygmaBridge::create_deposit_data( + final_amount_in_deposit_event_1, + b"ethereum recipient".to_vec(), + ), + handler_response: vec![], + }), + RuntimeEvent::SygmaBridge(SygmaBridgeEvent::FeeCollected { + fee_payer: ALICE, + dest_domain_id: DEST_DOMAIN_ID, + resource_id: NativeResourceId::get(), + fee_amount: 10_000_000_000_000u128, + fee_asset_id: NativeLocation::get().into(), + }), + ]); + + // test 100% + // should not work because 100% is out of fee rate + assert_noop!( + SygmaPercentageFeeHandler::set_fee_rate( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + fee_rate_2 + ), + sygma_percentage_feehandler::Error::::FeeRateOutOfRange + ); + + // test 99.99% + // override 5%% to 99.99% + assert_ok!(SygmaPercentageFeeHandler::set_fee_rate( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + fee_rate_3 + )); + assert_ok!(SygmaBridge::deposit( + Origin::signed(ALICE), + Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), + Box::new(MultiLocation { + parents: 0, + interior: X2( + slice_to_generalkey(b"ethereum recipient"), + slice_to_generalkey(&[1]), + ) + }), + )); + // Check reserved native token, should increase by 0.02 to 190.020000000000 + assert_eq!(Balances::free_balance(BridgeAccount::get()), 190_020_000_000_000u128); + // Check fee collected, should increase by 199.98 to 209.980000000000 + assert_eq!(Balances::free_balance(TreasuryAccount::get()), 209_980_000_000_000u128); + + // test 0% + // override 99.99% to 0% + assert_ok!(SygmaPercentageFeeHandler::set_fee_rate( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + fee_rate_4 + )); + assert_ok!(SygmaBridge::deposit( + Origin::signed(ALICE), + Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), + Box::new(MultiLocation { + parents: 0, + interior: X2( + slice_to_generalkey(b"ethereum recipient"), + slice_to_generalkey(&[1]), + ) + }), + )); + // Check reserved native token, should increase by 200 to 390.020000000000 + assert_eq!(Balances::free_balance(BridgeAccount::get()), 390_020_000_000_000u128); + // Check fee collected, should increase by 0 to 209.980000000000 + assert_eq!(Balances::free_balance(TreasuryAccount::get()), 209_980_000_000_000u128); + + // test 150% + // should not work because 150% is out of fee rate + assert_noop!( + SygmaPercentageFeeHandler::set_fee_rate( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + fee_rate_5 + ), + sygma_percentage_feehandler::Error::::FeeRateOutOfRange + ); + + // Check reserved native token, should remain as 390.020000000000 + assert_eq!(Balances::free_balance(BridgeAccount::get()), 390_020_000_000_000u128); + // Check fee collected, should remain as 209.980000000000 + assert_eq!(Balances::free_balance(TreasuryAccount::get()), 209_980_000_000_000u128); + }) + } + + #[test] + fn percentage_fee_rate_not_set_for_domain_and_asset() { + new_test_ext().execute_with(|| { + let test_mpc_addr: MpcAddress = MpcAddress([1u8; 20]); + let amount = 200_000_000_000_000u128; // 200 with 12 decimals + + assert_ok!(SygmaBridge::register_domain( + Origin::root(), + DEST_DOMAIN_ID, + U256::from(1) + )); + assert_ok!(SygmaBridge::set_mpc_address(Origin::root(), test_mpc_addr)); + + // only set fee handler but not set fee rate for domain and asset + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::PercentageFeeHandler, + )); + + // deposit should go through, 200 native token all goes into TokenReservedAccount, + // no fee collected + assert_ok!(SygmaBridge::deposit( + Origin::signed(ALICE), + Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), + Box::new(MultiLocation { + parents: 0, + interior: X2( + slice_to_generalkey(b"ethereum recipient"), + slice_to_generalkey(&[1]), + ) + }), + )); + + // Check balances + assert_eq!(Balances::free_balance(ALICE), ENDOWED_BALANCE - amount); + // Check reserved native token, should increase by 200 + assert_eq!(Balances::free_balance(BridgeAccount::get()), amount); + // Check fee collected, should increase by 0 + assert_eq!(Balances::free_balance(TreasuryAccount::get()), 0); + }) + } + + #[test] + fn deposit_native_asset_with_percentage_fee_override_basic_fee_handler() { + new_test_ext().execute_with(|| { + let test_mpc_addr: MpcAddress = MpcAddress([1u8; 20]); + let amount = 200_000_000_000_000u128; // 200 with 12 decimals + let fee = 1_000_000_000_000u128; // 1 with 12 decimals + + assert_ok!(SygmaBridge::register_domain( + Origin::root(), + DEST_DOMAIN_ID, + U256::from(1) + )); + assert_ok!(SygmaBridge::set_mpc_address(Origin::root(), test_mpc_addr)); + + // set fee handler with basic fee handler and fixed fee + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::BasicFeeHandler, + )); + assert_ok!(SygmaBasicFeeHandler::set_fee( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + fee + )); + + assert_ok!(SygmaBridge::deposit( + Origin::signed(ALICE), + Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), + Box::new(MultiLocation { + parents: 0, + interior: X2( + slice_to_generalkey(b"ethereum recipient"), + slice_to_generalkey(&[1]), + ) + }), + )); + // Check balances + assert_eq!(Balances::free_balance(ALICE), ENDOWED_BALANCE - amount); + assert_eq!(Balances::free_balance(BridgeAccount::get()), amount - fee); + assert_eq!(Balances::free_balance(TreasuryAccount::get()), fee); + + // Override Basic fee handler to Percentage fee handler with 5% fee rate + assert_ok!(SygmaFeeHandlerRouter::set_fee_handler( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + FeeHandlerType::PercentageFeeHandler, + )); + assert_ok!(SygmaPercentageFeeHandler::set_fee_rate( + Origin::root(), + DEST_DOMAIN_ID, + Box::new(NativeLocation::get().into()), + 500u32 + )); + + assert_ok!(SygmaBridge::deposit( + Origin::signed(ALICE), + Box::new((Concrete(NativeLocation::get()), Fungible(amount)).into()), + Box::new(MultiLocation { + parents: 0, + interior: X2( + slice_to_generalkey(b"ethereum recipient"), + slice_to_generalkey(&[1]), + ) + }), + )); + // Check balances + assert_eq!(Balances::free_balance(ALICE), ENDOWED_BALANCE - amount * 2); + // Check reserved native token, should increase by 190 + assert_eq!( + Balances::free_balance(BridgeAccount::get()), + amount - fee + 190_000_000_000_000u128 + ); + // Check fee collected, should increase by 10 + assert_eq!( + Balances::free_balance(TreasuryAccount::get()), + fee + 10_000_000_000_000u128 + ); + }) + } } } diff --git a/bridge/src/mock.rs b/bridge/src/mock.rs index 1aaf48b..8b97e00 100644 --- a/bridge/src/mock.rs +++ b/bridge/src/mock.rs @@ -48,6 +48,8 @@ frame_support::construct_runtime!( AccessSegregator: sygma_access_segregator::{Pallet, Call, Storage, Event} = 4, SygmaBasicFeeHandler: sygma_basic_feehandler::{Pallet, Call, Storage, Event} = 5, SygmaBridge: sygma_bridge::{Pallet, Call, Storage, Event} = 6, + SygmaPercentageFeeHandler: sygma_percentage_feehandler::{Pallet, Call, Storage, Event} = 7, + SygmaFeeHandlerRouter: sygma_fee_handler_router::{Pallet, Call, Storage, Event} = 8, } ); @@ -156,11 +158,15 @@ impl pallet_timestamp::Config for Runtime { parameter_types! { // Make sure put same value with `construct_runtime` pub const AccessSegregatorPalletIndex: u8 = 4; - pub const FeeHandlerPalletIndex: u8 = 5; + pub const BaiscFeeHandlerPalletIndex: u8 = 5; pub const BridgePalletIndex: u8 = 6; + pub const PercentageFeeHandlerPalletIndex: u8 = 7; + pub const FeeHandlerRouterPalletIndex: u8 = 8; pub RegisteredExtrinsics: Vec<(u8, Vec)> = [ (AccessSegregatorPalletIndex::get(), b"grant_access".to_vec()), - (FeeHandlerPalletIndex::get(), b"set_fee".to_vec()), + (BaiscFeeHandlerPalletIndex::get(), b"set_fee".to_vec()), + (PercentageFeeHandlerPalletIndex::get(), b"set_fee_rate".to_vec()), + (FeeHandlerRouterPalletIndex::get(), b"set_fee_handler".to_vec()), (BridgePalletIndex::get(), b"set_mpc_address".to_vec()), (BridgePalletIndex::get(), b"pause_bridge".to_vec()), (BridgePalletIndex::get(), b"unpause_bridge".to_vec()), @@ -178,12 +184,27 @@ impl sygma_access_segregator::Config for Runtime { type WeightInfo = sygma_access_segregator::weights::SygmaWeightInfo; } +impl sygma_fee_handler_router::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BasicFeeHandler = SygmaBasicFeeHandler; + type DynamicFeeHandler = (); + type PercentageFeeHandler = SygmaPercentageFeeHandler; + type PalletIndex = FeeHandlerRouterPalletIndex; + type WeightInfo = sygma_fee_handler_router::weights::SygmaWeightInfo; +} + impl sygma_basic_feehandler::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type PalletIndex = FeeHandlerPalletIndex; + type PalletIndex = BaiscFeeHandlerPalletIndex; type WeightInfo = sygma_basic_feehandler::weights::SygmaWeightInfo; } +impl sygma_percentage_feehandler::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletIndex = PercentageFeeHandlerPalletIndex; + type WeightInfo = sygma_percentage_feehandler::weights::SygmaWeightInfo; +} + parameter_types! { pub TreasuryAccount: AccountId32 = AccountId32::new([100u8; 32]); pub EIP712ChainID: ChainID = primitive_types::U256([1u64; 4]); @@ -474,7 +495,7 @@ impl sygma_bridge::Config for Runtime { type FeeReserveAccount = TreasuryAccount; type EIP712ChainID = EIP712ChainID; type DestVerifyingContractAddress = DestVerifyingContractAddress; - type FeeHandler = SygmaBasicFeeHandler; + type FeeHandler = SygmaFeeHandlerRouter; type AssetTransactor = AssetTransactors; type ResourcePairs = ResourcePairs; type IsReserve = ReserveChecker; diff --git a/fee-handler-router/Cargo.toml b/fee-handler-router/Cargo.toml index 9355e98..1e7a787 100644 --- a/fee-handler-router/Cargo.toml +++ b/fee-handler-router/Cargo.toml @@ -21,6 +21,7 @@ xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "releas # Local sygma-traits = { path = "../traits", default-features = false } sygma-basic-feehandler = { path = "../basic-fee-handler", default-features = false } +sygma-percentage-feehandler = { path = "../percentage-fee-handler", default-features = false } sygma-access-segregator = { path = "../access-segregator", default-features = false } [dev-dependencies] @@ -49,6 +50,7 @@ std = [ "xcm-builder/std", "sygma-traits/std", "sygma-basic-feehandler/std", + "sygma-percentage-feehandler/std", "sygma-access-segregator/std", ] runtime-benchmarks = [ diff --git a/fee-handler-router/src/lib.rs b/fee-handler-router/src/lib.rs index 4086f40..a8406e6 100644 --- a/fee-handler-router/src/lib.rs +++ b/fee-handler-router/src/lib.rs @@ -21,11 +21,12 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_std::boxed::Box; use sygma_traits::{DomainID, FeeHandler}; - use xcm::latest::AssetId; + use xcm::latest::{AssetId, MultiAsset}; #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug, MaxEncodedLen)] pub enum FeeHandlerType { BasicFeeHandler, + PercentageFeeHandler, DynamicFeeHandler, } @@ -40,12 +41,15 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + sygma_basic_feehandler::Config { + pub trait Config: + frame_system::Config + sygma_basic_feehandler::Config + sygma_percentage_feehandler::Config + { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Fee handlers type BasicFeeHandler: FeeHandler; type DynamicFeeHandler: FeeHandler; + type PercentageFeeHandler: FeeHandler; /// Current pallet index defined in runtime type PalletIndex: Get; @@ -107,11 +111,13 @@ pub mod pallet { } impl FeeHandler for Pallet { - fn get_fee(domain: DomainID, asset: &AssetId) -> Option { - if let Some(handler_type) = HandlerType::::get((&domain, asset)) { + fn get_fee(domain: DomainID, asset: MultiAsset) -> Option { + if let Some(handler_type) = HandlerType::::get((&domain, asset.id)) { match handler_type { FeeHandlerType::BasicFeeHandler => sygma_basic_feehandler::Pallet::::get_fee(domain, asset), + FeeHandlerType::PercentageFeeHandler => + sygma_percentage_feehandler::Pallet::::get_fee(domain, asset), FeeHandlerType::DynamicFeeHandler => { // TODO: Support dynamic fee handler None @@ -215,13 +221,19 @@ pub mod pallet { )); assert_eq!( - FeeHandlerRouter::get_fee(EthereumDomainID::get(), &PhaLocation::get().into()) - .unwrap(), + FeeHandlerRouter::get_fee( + EthereumDomainID::get(), + (PhaLocation::get(), 10000u128).into() + ) + .unwrap(), 10000 ); // We don't support dynamic fee handler, return None assert_eq!( - FeeHandlerRouter::get_fee(MoonbeamDomainID::get(), &PhaLocation::get().into()), + FeeHandlerRouter::get_fee( + MoonbeamDomainID::get(), + (PhaLocation::get(), 10000u128).into() + ), None ); assert_events(vec![ diff --git a/fee-handler-router/src/mock.rs b/fee-handler-router/src/mock.rs index bce0157..cbbdf44 100644 --- a/fee-handler-router/src/mock.rs +++ b/fee-handler-router/src/mock.rs @@ -38,6 +38,7 @@ frame_support::construct_runtime!( AccessSegregator: sygma_access_segregator::{Pallet, Call, Storage, Event} = 3, SygmaBasicFeeHandler: sygma_basic_feehandler::{Pallet, Call, Storage, Event} = 4, FeeHandlerRouter: fee_handler_router::{Pallet, Call, Storage, Event} = 5, + SygamPercenrageFeeHandler: sygma_percentage_feehandler::{Pallet, Call, Storage, Event} = 6, } ); @@ -132,8 +133,9 @@ parameter_types! { pub const MoonbeamDomainID: DomainID = 1; // Make sure put same value with `construct_runtime` pub const AccessSegregatorPalletIndex: u8 = 3; - pub const FeeHandlerPalletIndex: u8 = 4; + pub const BasicFeeHandlerPalletIndex: u8 = 4; pub const FeeHandlerRouterPalletIndex: u8 = 5; + pub const PercentageFeeHandlerPalletIndex: u8 = 6; pub RegisteredExtrinsics: Vec<(u8, Vec)> = [ (AccessSegregatorPalletIndex::get(), b"grant_access".to_vec()), (FeeHandlerRouterPalletIndex::get(), b"set_fee_handler".to_vec()), @@ -143,7 +145,7 @@ parameter_types! { impl sygma_basic_feehandler::Config for Test { type RuntimeEvent = RuntimeEvent; - type PalletIndex = FeeHandlerPalletIndex; + type PalletIndex = BasicFeeHandlerPalletIndex; type WeightInfo = sygma_basic_feehandler::weights::SygmaWeightInfo; } @@ -159,10 +161,17 @@ impl fee_handler_router::Config for Test { type RuntimeEvent = RuntimeEvent; type BasicFeeHandler = SygmaBasicFeeHandler; type DynamicFeeHandler = (); + type PercentageFeeHandler = SygamPercenrageFeeHandler; type PalletIndex = FeeHandlerRouterPalletIndex; type WeightInfo = fee_handler_router::weights::SygmaWeightInfo; } +impl sygma_percentage_feehandler::Config for Test { + type RuntimeEvent = RuntimeEvent; + type PalletIndex = PercentageFeeHandlerPalletIndex; + type WeightInfo = sygma_percentage_feehandler::weights::SygmaWeightInfo; +} + pub fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/parachain-info/src/lib.rs b/parachain-info/src/lib.rs index 28451ab..cdbd91f 100644 --- a/parachain-info/src/lib.rs +++ b/parachain-info/src/lib.rs @@ -54,7 +54,7 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - >::put(&self.parachain_id); + >::put(self.parachain_id); } } diff --git a/percentage-fee-handler/Cargo.toml b/percentage-fee-handler/Cargo.toml new file mode 100644 index 0000000..74f4040 --- /dev/null +++ b/percentage-fee-handler/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "sygma-percentage-feehandler" +version = "0.3.0" +edition = "2021" +license = "LGPL-3.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde", "decode"] } + +# Substrate +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43", default-features = false, optional = true } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.43", default-features = false } + +# Polkadot +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43", default-features = false } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.43", default-features = false } + +# Local +sygma-traits = { path = "../traits", default-features = false } +sygma-access-segregator = { path = "../access-segregator", default-features = false } + +[dev-dependencies] +# Substrate +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "sp-std/std", + "xcm/std", + "xcm-builder/std", + "sygma-traits/std", + "sygma-access-segregator/std", +] +runtime-benchmarks = [ + 'frame-benchmarking', + 'frame-support/runtime-benchmarks', + 'frame-system/runtime-benchmarks', +] +try-runtime = ["frame-support/try-runtime"] + diff --git a/percentage-fee-handler/src/benchmarking.rs b/percentage-fee-handler/src/benchmarking.rs new file mode 100644 index 0000000..56264c1 --- /dev/null +++ b/percentage-fee-handler/src/benchmarking.rs @@ -0,0 +1,38 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +//! Sygma percentage-fee-handler pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] +use super::*; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin as SystemOrigin; + +use sp_std::vec; +use sygma_traits::DomainID; +use xcm::latest::prelude::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_fee_rate() { + let dest_domain_id: DomainID = 1; + let native_location: MultiLocation = MultiLocation::here(); + let fee_rate = 500u32; // 5% + + #[extrinsic_call] + set_fee_rate( + SystemOrigin::Root, + dest_domain_id, + Box::new(native_location.clone().into()), + fee_rate, + ); + + assert_eq!( + AssetFeeRate::::get(&(dest_domain_id, native_location.into())), + Some(fee_rate), + ); + } +} diff --git a/percentage-fee-handler/src/lib.rs b/percentage-fee-handler/src/lib.rs new file mode 100644 index 0000000..1606538 --- /dev/null +++ b/percentage-fee-handler/src/lib.rs @@ -0,0 +1,263 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use self::pallet::*; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; +pub use weights::*; + +#[cfg(test)] +mod mock; + +#[allow(unused_variables)] +#[allow(clippy::large_enum_variant)] +#[frame_support::pallet] +pub mod pallet { + use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::StorageVersion}; + use frame_system::pallet_prelude::*; + use sp_std::boxed::Box; + use sygma_traits::{DomainID, FeeHandler}; + use xcm::latest::{AssetId, Fungibility::Fungible, MultiAsset}; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + /// Mapping fungible asset id with domain id to fee rate + #[pallet::storage] + pub type AssetFeeRate = StorageMap<_, Twox64Concat, (DomainID, AssetId), u32>; + + pub trait WeightInfo { + fn set_fee_rate() -> Weight; + } + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + sygma_access_segregator::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Current pallet index defined in runtime + type PalletIndex: Get; + + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + /// Fee set rate for a specific asset and domain + /// args: [domain, asset, rate_basis_point] + FeeRateSet { domain: DomainID, asset: AssetId, rate_basis_point: u32 }, + } + + #[pallet::error] + pub enum Error { + /// Function unimplemented + Unimplemented, + /// Account has not gained access permission + AccessDenied, + /// Fee rate is out of range [0, 10000) + FeeRateOutOfRange, + } + + #[pallet::call] + impl Pallet { + /// Set bridge fee rate for a specific asset and domain. Note the fee rate is in Basis Point + /// representation, and the valid fee rate is [0, 10000) + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::set_fee_rate())] + pub fn set_fee_rate( + origin: OriginFor, + domain: DomainID, + asset: Box, + fee_rate_basis_point: u32, + ) -> DispatchResult { + let asset: AssetId = *asset; + ensure!( + >::has_access( + ::PalletIndex::get(), + b"set_fee_rate".to_vec(), + origin + ), + Error::::AccessDenied + ); + + // Make sure fee rate is valid + ensure!(fee_rate_basis_point < 10_000u32, Error::::FeeRateOutOfRange); + + // Update asset fee rate + AssetFeeRate::::insert((domain, &asset), fee_rate_basis_point); + + // Emit FeeRateSet event + Self::deposit_event(Event::FeeRateSet { + domain, + asset, + rate_basis_point: fee_rate_basis_point, + }); + Ok(()) + } + } + + impl FeeHandler for Pallet { + fn get_fee(domain: DomainID, asset: MultiAsset) -> Option { + match (asset.fun, asset.id) { + (Fungible(amount), _) => { + // return fee rate as 0 when it is not set in the storage + let fee_rate_basis_point = + AssetFeeRate::::get((domain, asset.id)).unwrap_or_default(); + Some(amount.saturating_mul(fee_rate_basis_point as u128).saturating_div(10000)) + }, + _ => None, + } + } + } + + #[cfg(test)] + mod test { + use crate as percentage_fee_handler; + use crate::{AssetFeeRate, Event as PercentageFeeHandlerEvent}; + use frame_support::{assert_noop, assert_ok}; + use percentage_fee_handler::mock::{ + assert_events, new_test_ext, AccessSegregator, PercentageFeeHandler, + PercentageFeeHandlerPalletIndex, RuntimeEvent as Event, RuntimeOrigin as Origin, Test, + ALICE, + }; + use sp_std::boxed::Box; + use sygma_traits::DomainID; + use xcm::latest::{prelude::*, MultiLocation}; + + #[test] + fn set_get_fee() { + new_test_ext().execute_with(|| { + let dest_domain_id: DomainID = 0; + let another_dest_domain_id: DomainID = 1; + let asset_id_a = Concrete(MultiLocation::new(1, Here)); + let asset_id_b = Concrete(MultiLocation::new(2, Here)); + let asset_a_deposit: MultiAsset = (asset_id_a, 100u128).into(); + let asset_b_deposit: MultiAsset = (asset_id_b, 200u128).into(); + + // if not set fee rate, return None + assert_eq!(AssetFeeRate::::get((dest_domain_id, asset_id_a)), None); + + // test the min fee rate case: 0u32 => 0%, should work + // set fee rate as 0 basis point aka 0% with assetId asset_id_a for one domain + assert_ok!(PercentageFeeHandler::set_fee_rate( + Origin::root(), + dest_domain_id, + Box::new(asset_id_a), + 0u32 + )); + + // test the max fee rate case: 10000 => 100%, should raise FeeRateOutOfRange error + assert_noop!( + PercentageFeeHandler::set_fee_rate( + Origin::root(), + dest_domain_id, + Box::new(asset_id_a), + 10_000u32 + ), + percentage_fee_handler::Error::::FeeRateOutOfRange + ); + + // set fee rate as 50 basis point aka 0.5% with assetId asset_id_a for one domain + assert_ok!(PercentageFeeHandler::set_fee_rate( + Origin::root(), + dest_domain_id, + Box::new(asset_id_a), + 50u32 + )); + // set fee rate as 200 basis point aka 2% with assetId asset_id_a for another domain + assert_ok!(PercentageFeeHandler::set_fee_rate( + Origin::root(), + another_dest_domain_id, + Box::new(asset_id_b), + 200u32 + )); + + assert_eq!(AssetFeeRate::::get((dest_domain_id, asset_id_a)).unwrap(), 50); + assert_eq!( + AssetFeeRate::::get((another_dest_domain_id, asset_id_b)).unwrap(), + 200 + ); + + // permission test: unauthorized account should not be able to set fee + let unauthorized_account = Origin::from(Some(ALICE)); + assert_noop!( + PercentageFeeHandler::set_fee_rate( + unauthorized_account, + dest_domain_id, + Box::new(asset_id_a), + 100u32 + ), + percentage_fee_handler::Error::::AccessDenied + ); + + assert_events(vec![ + Event::PercentageFeeHandler(PercentageFeeHandlerEvent::FeeRateSet { + domain: dest_domain_id, + asset: asset_id_a, + rate_basis_point: 50u32, + }), + Event::PercentageFeeHandler(PercentageFeeHandlerEvent::FeeRateSet { + domain: another_dest_domain_id, + asset: asset_id_b, + rate_basis_point: 200u32, + }), + ]); + }) + } + + #[test] + fn access_control() { + new_test_ext().execute_with(|| { + let dest_domain_id: DomainID = 0; + let asset_id = Concrete(MultiLocation::new(0, Here)); + + assert_ok!(PercentageFeeHandler::set_fee_rate( + Origin::root(), + dest_domain_id, + Box::new(asset_id), + 100u32 + ),); + assert_noop!( + PercentageFeeHandler::set_fee_rate( + Some(ALICE).into(), + dest_domain_id, + Box::new(asset_id), + 200u32 + ), + percentage_fee_handler::Error::::AccessDenied + ); + assert!(!AccessSegregator::has_access( + PercentageFeeHandlerPalletIndex::get(), + b"set_fee_rate".to_vec(), + Some(ALICE).into() + )); + assert_ok!(AccessSegregator::grant_access( + Origin::root(), + PercentageFeeHandlerPalletIndex::get(), + b"set_fee_rate".to_vec(), + ALICE + )); + assert!(AccessSegregator::has_access( + PercentageFeeHandlerPalletIndex::get(), + b"set_fee_rate".to_vec(), + Some(ALICE).into() + )); + assert_ok!(PercentageFeeHandler::set_fee_rate( + Some(ALICE).into(), + dest_domain_id, + Box::new(asset_id), + 200u32 + ),); + assert_eq!(AssetFeeRate::::get((dest_domain_id, asset_id)).unwrap(), 200u32); + }) + } + } +} diff --git a/percentage-fee-handler/src/mock.rs b/percentage-fee-handler/src/mock.rs new file mode 100644 index 0000000..1cb3284 --- /dev/null +++ b/percentage-fee-handler/src/mock.rs @@ -0,0 +1,170 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: LGPL-3.0-only + +#![cfg(test)] + +use frame_support::{ + pallet_prelude::ConstU32, + parameter_types, + sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, Perbill, + }, + traits::{AsEnsureOriginWithArg, ConstU128}, +}; +use frame_system::{self as system, EnsureRoot, EnsureSigned}; + +use crate as percentage_fee_handler; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub(crate) type Balance = u128; + +pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Assets: pallet_assets::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + AccessSegregator: sygma_access_segregator::{Pallet, Call, Storage, Event} = 3, + PercentageFeeHandler: percentage_fee_handler::{Pallet, Call, Storage, Event} = 4, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub const MaxLocks: u32 = 100; + pub const MinimumPeriod: u64 = 1; +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + 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 = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<2>; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type HoldIdentifier = (); + type MaxHolds = (); +} + +parameter_types! { + pub const AssetDeposit: Balance = 1; // 1 Unit deposit to create asset + pub const ApprovalDeposit: Balance = 1; + pub const AssetsStringLimit: u32 = 50; + pub const MetadataDepositBase: Balance = 1; + pub const MetadataDepositPerByte: Balance = 1; +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = u32; + type AssetIdParameter = codec::Compact; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type RemoveItemsLimit = ConstU32<1000>; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + // Make sure put same value with `construct_runtime` + pub const AccessSegregatorPalletIndex: u8 = 3; + pub const PercentageFeeHandlerPalletIndex: u8 = 4; + pub RegisteredExtrinsics: Vec<(u8, Vec)> = [ + (AccessSegregatorPalletIndex::get(), b"grant_access".to_vec()), + (PercentageFeeHandlerPalletIndex::get(), b"set_fee_rate".to_vec()), + ].to_vec(); +} + +impl sygma_access_segregator::Config for Test { + type RuntimeEvent = RuntimeEvent; + type BridgeCommitteeOrigin = EnsureRoot; + type PalletIndex = AccessSegregatorPalletIndex; + type Extrinsics = RegisteredExtrinsics; + type WeightInfo = sygma_access_segregator::weights::SygmaWeightInfo; +} + +impl percentage_fee_handler::Config for Test { + type RuntimeEvent = RuntimeEvent; + type PalletIndex = PercentageFeeHandlerPalletIndex; + type WeightInfo = percentage_fee_handler::weights::SygmaWeightInfo; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +// Checks events against the latest. A contiguous set of events must be provided. They must +// include the most recent event, but do not have to include every past event. +pub fn assert_events(mut expected: Vec) { + let mut actual: Vec = + system::Pallet::::events().iter().map(|e| e.event.clone()).collect(); + + expected.reverse(); + + for evt in expected { + let next = actual.pop().expect("event expected"); + assert_eq!(next, evt, "Events don't match"); + } +} diff --git a/percentage-fee-handler/src/weights.rs b/percentage-fee-handler/src/weights.rs new file mode 100644 index 0000000..cb9e977 --- /dev/null +++ b/percentage-fee-handler/src/weights.rs @@ -0,0 +1,51 @@ + +//! Autogenerated weights for `sygma_percentage_feehandler` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Freddys-MacBook-Pro-15.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/node-template +// benchmark +// pallet +// --chain +// dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// sygma_percentage_feehandler +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --output +// percentage_feehandler_weight.rs + +#![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 `sygma_percentage_feehandler`. +pub struct SygmaWeightInfo(PhantomData); +impl super::WeightInfo for SygmaWeightInfo { + /// Storage: SygmaPercentageFeeHandler AssetFeeRate (r:0 w:1) + /// Proof: SygmaPercentageFeeHandler AssetFeeRate (max_values: None, max_size: Some(616), added: 3091, mode: MaxEncodedLen) + fn set_fee_rate() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 22_000_000 picoseconds. + Weight::from_parts(23_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/scripts/e2e_setup.sh b/scripts/e2e_setup.sh index 280f905..8ea9634 100755 --- a/scripts/e2e_setup.sh +++ b/scripts/e2e_setup.sh @@ -8,7 +8,7 @@ NODE_DB_DIR=${PWD}/db # Run dev node echo "start the dev node up..." -./node-template --dev --ws-external --base-path "$NODE_DB_DIR" > substrate_node.log 2>&1 & +./node-template --dev --rpc-external --base-path "$NODE_DB_DIR" > substrate_node.log 2>&1 & echo "waiting for dev nodes start..." sleep 60 diff --git a/scripts/js/setup.js b/scripts/js/setup.js index 4460997..9e7f80a 100644 --- a/scripts/js/setup.js +++ b/scripts/js/setup.js @@ -11,6 +11,7 @@ const { setMpcAddress, registerDomain, setFee, + setFeeRate, getNativeAssetId, getERC20TSTAssetId, getERC20TSTD20AssetId, @@ -28,6 +29,7 @@ const bn1e20 = new BN(10).pow(new BN(20)); const feeHandlerType = { BasicFeeHandler: "BasicFeeHandler", + PercentageFeeHandler: "PercentageFeeHandler", DynamicFeeHandler: "DynamicFeeHandler" } @@ -57,6 +59,7 @@ async function main() { const keyring = new Keyring({type: 'sr25519'}); const sudo = keyring.addFromUri('//Alice'); const basicFeeAmount = bn1e12.mul(new BN(1)); // 1 * 10 ** 12 + const percentageFeeRate = 500; // 5% const mpcAddr = process.env.MPCADDR; // register dest domains @@ -64,10 +67,10 @@ async function main() { await registerDomain(api, domain.domainID, domain.chainID, true, sudo); } - // set fee for native asset for domains + // set fee rate for native asset for domains for (const domain of supportedDestDomains) { - await setFeeHandler(api, domain.domainID, getNativeAssetId(api), feeHandlerType.BasicFeeHandler, true, sudo) - await setFee(api, domain.domainID, getNativeAssetId(api), basicFeeAmount, true, sudo); + await setFeeHandler(api, domain.domainID, getNativeAssetId(api), feeHandlerType.PercentageFeeHandler, true, sudo) + await setFeeRate(api, domain.domainID, getNativeAssetId(api), percentageFeeRate, true, sudo); } // create USDC test asset (foreign asset) @@ -106,14 +109,14 @@ async function main() { // set fee for tokens with domains for (const domain of supportedDestDomains) { - await setFeeHandler(api, domain.domainID, getUSDCAssetId(api), feeHandlerType.BasicFeeHandler, true, sudo) - await setFee(api, domain.domainID, getUSDCAssetId(api), basicFeeAmount, true, sudo); + await setFeeHandler(api, domain.domainID, getUSDCAssetId(api), feeHandlerType.PercentageFeeHandler, true, sudo) + await setFeeRate(api, domain.domainID, getUSDCAssetId(api), percentageFeeRate, true, sudo); - await setFeeHandler(api, domain.domainID, getERC20TSTAssetId(api), feeHandlerType.BasicFeeHandler, true, sudo) - await setFee(api, domain.domainID, getERC20TSTAssetId(api), basicFeeAmount, true, sudo); + await setFeeHandler(api, domain.domainID, getERC20TSTAssetId(api), feeHandlerType.PercentageFeeHandler, true, sudo) + await setFeeRate(api, domain.domainID, getERC20TSTAssetId(api), percentageFeeRate, true, sudo); - await setFeeHandler(api, domain.domainID, getERC20TSTD20AssetId(api), feeHandlerType.BasicFeeHandler, true, sudo) - await setFee(api, domain.domainID, getERC20TSTD20AssetId(api), basicFeeAmount, true, sudo); + await setFeeHandler(api, domain.domainID, getERC20TSTD20AssetId(api), feeHandlerType.PercentageFeeHandler, true, sudo) + await setFeeRate(api, domain.domainID, getERC20TSTD20AssetId(api), percentageFeeRate, true, sudo); } // transfer some native asset to FeeReserveAccount and TransferReserveAccount as Existential Deposit(aka ED) diff --git a/scripts/js/util.js b/scripts/js/util.js index 61d5c3a..8e0d428 100644 --- a/scripts/js/util.js +++ b/scripts/js/util.js @@ -108,6 +108,41 @@ async function setFee(api, domainID, asset, amount, finalization, sudo) { }); } +async function setFeeRate(api, domainID, asset, feeRate, finalization, sudo) { + return new Promise(async (resolve, reject) => { + const nonce = Number((await api.query.system.account(sudo.address)).nonce); + + console.log( + `--- Submitting extrinsic to set percentage fee rate on domainID ${domainID}. (nonce: ${nonce}) ---` + ); + const unsub = await api.tx.sudo + .sudo(api.tx.sygmaPercentageFeeHandler.setFeeRate(domainID, asset, feeRate)) + .signAndSend(sudo, {nonce: nonce, era: 0}, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + if (finalization) { + console.log('Waiting for finalization...'); + } else { + unsub(); + resolve(); + } + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + reject(`Transaction Error`); + } + }); + }); +} + async function setMpcAddress(api, mpcAddr, finalization, sudo) { return new Promise(async (resolve, reject) => { const nonce = Number((await api.query.system.account(sudo.address)).nonce); @@ -441,6 +476,7 @@ module.exports = { queryBridgePauseStatus, setMpcAddress, setFee, + setFeeRate, setFeeHandler, setBalance, executeProposal, diff --git a/substrate-node/runtime/Cargo.toml b/substrate-node/runtime/Cargo.toml index 1e0904e..f966950 100644 --- a/substrate-node/runtime/Cargo.toml +++ b/substrate-node/runtime/Cargo.toml @@ -63,6 +63,7 @@ parachains-common = { git = "https://github.com/paritytech/cumulus", branch = "p # Local Dependencies sygma-basic-feehandler = { path = "../../basic-fee-handler", default-features = false } +sygma-percentage-feehandler = { path = "../../percentage-fee-handler", default-features = false } sygma-traits = { path = "../../traits", default-features = false } sygma-bridge = { path = "../../bridge", default-features = false } sygma-access-segregator = { path = "../../access-segregator", default-features = false } @@ -93,6 +94,7 @@ std = [ "pallet-insecure-randomness-collective-flip/std", "pallet-sudo/std", "sygma-basic-feehandler/std", + "sygma-percentage-feehandler/std", "sygma-traits/std", "sygma-bridge/std", "sygma-access-segregator/std", @@ -133,6 +135,7 @@ runtime-benchmarks = [ "sygma-bridge/runtime-benchmarks", "sygma-access-segregator/runtime-benchmarks", "sygma-basic-feehandler/runtime-benchmarks", + "sygma-percentage-feehandler/runtime-benchmarks", "sygma-fee-handler-router/runtime-benchmarks", ] try-runtime = [ @@ -149,6 +152,7 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "pallet-assets/try-runtime", "sygma-basic-feehandler/try-runtime", + "sygma-percentage-feehandler/try-runtime", "sygma-bridge/try-runtime", "sygma-access-segregator/try-runtime", "sygma-fee-handler-router/try-runtime", diff --git a/substrate-node/runtime/src/lib.rs b/substrate-node/runtime/src/lib.rs index 6c728e1..f1bef4a 100644 --- a/substrate-node/runtime/src/lib.rs +++ b/substrate-node/runtime/src/lib.rs @@ -339,14 +339,15 @@ impl pallet_assets::Config for Runtime { parameter_types! { // Make sure put same value with `construct_runtime` pub const AccessSegregatorPalletIndex: u8 = 9; - pub const FeeHandlerPalletIndex: u8 = 10; + pub const BasicFeeHandlerPalletIndex: u8 = 10; pub const BridgePalletIndex: u8 = 11; pub const FeeHandlerRouterPalletIndex: u8 = 12; + pub const PercentageFeeHandlerRouterPalletIndex: u8 = 13; // RegisteredExtrinsics here registers all valid (pallet index, extrinsic_name) paris // make sure to update this when adding new access control extrinsic pub RegisteredExtrinsics: Vec<(u8, Vec)> = [ (AccessSegregatorPalletIndex::get(), b"grant_access".to_vec()), - (FeeHandlerPalletIndex::get(), b"set_fee".to_vec()), + (BasicFeeHandlerPalletIndex::get(), b"set_fee".to_vec()), (BridgePalletIndex::get(), b"set_mpc_address".to_vec()), (BridgePalletIndex::get(), b"pause_bridge".to_vec()), (BridgePalletIndex::get(), b"unpause_bridge".to_vec()), @@ -354,6 +355,7 @@ parameter_types! { (BridgePalletIndex::get(), b"unregister_domain".to_vec()), (BridgePalletIndex::get(), b"retry".to_vec()), (FeeHandlerRouterPalletIndex::get(), b"set_fee_handler".to_vec()), + (PercentageFeeHandlerRouterPalletIndex::get(), b"set_fee_rate".to_vec()), ].to_vec(); } @@ -367,14 +369,21 @@ impl sygma_access_segregator::Config for Runtime { impl sygma_basic_feehandler::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type PalletIndex = FeeHandlerPalletIndex; + type PalletIndex = BasicFeeHandlerPalletIndex; type WeightInfo = sygma_basic_feehandler::weights::SygmaWeightInfo; } +impl sygma_percentage_feehandler::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletIndex = PercentageFeeHandlerRouterPalletIndex; + type WeightInfo = sygma_percentage_feehandler::weights::SygmaWeightInfo; +} + impl sygma_fee_handler_router::Config for Runtime { type RuntimeEvent = RuntimeEvent; type BasicFeeHandler = SygmaBasicFeeHandler; type DynamicFeeHandler = (); + type PercentageFeeHandler = SygmaPercentageFeeHandler; type PalletIndex = FeeHandlerRouterPalletIndex; type WeightInfo = sygma_fee_handler_router::weights::SygmaWeightInfo; } @@ -751,6 +760,7 @@ construct_runtime!( SygmaBasicFeeHandler: sygma_basic_feehandler::{Pallet, Call, Storage, Event} = 10, SygmaBridge: sygma_bridge::{Pallet, Call, Storage, Event} = 11, SygmaFeeHandlerRouter: sygma_fee_handler_router::{Pallet, Call, Storage, Event} = 12, + SygmaPercentageFeeHandler: sygma_percentage_feehandler::{Pallet, Call, Storage, Event} = 13, ParachainInfo: pallet_parachain_info::{Pallet, Storage, Config} = 20, } ); @@ -805,6 +815,7 @@ mod benches { [sygma_bridge, SygmaBridge::] [sygma_access_segregator, SygmaAccessSegregator::] [sygma_basic_feehandler, SygmaBasicFeeHandler::] + [sygma_percentage_feehandler, SygmaPercentageFeeHandler::] [sygma_fee_handler_router, SygmaFeeHandlerRouter::] ); } @@ -997,6 +1008,7 @@ impl_runtime_apis! { use sygma_access_segregator::Pallet as SygmaAccessSegregator; use sygma_basic_feehandler::Pallet as SygmaBasicFeeHandler; use sygma_fee_handler_router::Pallet as SygmaFeeHandlerRouter; + use sygma_percentage_feehandler::Pallet as SygmaPercentageFeeHandler; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1016,6 +1028,7 @@ impl_runtime_apis! { use sygma_bridge::Pallet as SygmaBridge; use sygma_access_segregator::Pallet as SygmaAccessSegregator; use sygma_basic_feehandler::Pallet as SygmaBasicFeeHandler; + use sygma_percentage_feehandler::Pallet as SygmaPercentageFeeHandler; use sygma_fee_handler_router::Pallet as SygmaFeeHandlerRouter; impl frame_system_benchmarking::Config for Runtime {} diff --git a/traits/src/lib.rs b/traits/src/lib.rs index e9f41aa..60ea32c 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -7,7 +7,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use primitive_types::{H160, U256}; use scale_info::TypeInfo; use sp_std::vec::Vec; -use xcm::latest::{prelude::*, AssetId, MultiLocation}; +use xcm::latest::{prelude::*, MultiLocation}; pub type DomainID = u8; pub type DepositNonce = u64; @@ -44,11 +44,11 @@ pub trait ExtractDestinationData { pub trait FeeHandler { // Return fee represent by a specific asset - fn get_fee(domain: DomainID, asset: &AssetId) -> Option; + fn get_fee(domain: DomainID, asset: MultiAsset) -> Option; } impl FeeHandler for () { - fn get_fee(_domain: DomainID, _asset: &AssetId) -> Option { + fn get_fee(_domain: DomainID, _asset: MultiAsset) -> Option { None } }