diff --git a/cumulus b/cumulus index 179da41296eaf..154dc0edcaf5c 160000 --- a/cumulus +++ b/cumulus @@ -1 +1 @@ -Subproject commit 179da41296eaf838febe46b01e7cacd6c1803bec +Subproject commit 154dc0edcaf5c0479853847b03fdc81047b0fb3e diff --git a/parachain/Cargo.lock b/parachain/Cargo.lock index b822e93a3b156..50b00b70eb507 100644 --- a/parachain/Cargo.lock +++ b/parachain/Cargo.lock @@ -1858,6 +1858,7 @@ name = "pallet-message-queue" version = "7.0.0-dev" source = "git+https://github.com/paritytech/substrate.git?branch=master#e976964644d951a1eefaaa85f1d7c7d58211ddf7" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "log", @@ -2639,10 +2640,12 @@ dependencies = [ "hex", "hex-literal", "pallet-balances", + "pallet-message-queue", "parity-scale-codec", "polkadot-parachain", "scale-info", "snowbridge-core", + "snowbridge-outbound-queue", "sp-core", "sp-io", "sp-keyring", @@ -2813,6 +2816,7 @@ dependencies = [ name = "snowbridge-outbound-queue-runtime-api" version = "0.1.0" dependencies = [ + "frame-support", "parity-scale-codec", "snowbridge-core", "snowbridge-outbound-queue-merkle-tree", diff --git a/parachain/pallets/control/Cargo.toml b/parachain/pallets/control/Cargo.toml index aa6e7f9b8a29d..cb18b50f10684 100644 --- a/parachain/pallets/control/Cargo.toml +++ b/parachain/pallets/control/Cargo.toml @@ -37,8 +37,10 @@ ethabi = { git = "https://github.com/Snowfork/ethabi-decode.git", package = "eth hex = "0.4.1" hex-literal = { version = "0.4.1" } pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "master" } -sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master"} +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" } polkadot-parachain = { git = "https://github.com/paritytech/polkadot.git", branch = "master" } +pallet-message-queue = { git = "https://github.com/paritytech/substrate.git", branch = "master" } +snowbridge-outbound-queue = { path = "../outbound-queue" } [features] default = ["std"] @@ -60,6 +62,6 @@ std = [ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", - "xcm-executor/runtime-benchmarks" + "xcm-executor/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/parachain/pallets/control/src/benchmarking.rs b/parachain/pallets/control/src/benchmarking.rs index 3838c100eef7f..ee52ea0bc7428 100644 --- a/parachain/pallets/control/src/benchmarking.rs +++ b/parachain/pallets/control/src/benchmarking.rs @@ -8,10 +8,11 @@ use crate::Pallet as SnowbridgeControl; use frame_benchmarking::v2::*; use frame_system::RawOrigin; use snowbridge_core::outbound::OperatingMode; +use sp_runtime::SaturatedConversion; use xcm::prelude::*; fn fund_sovereign_account(para_id: ParaId) -> Result<(), BenchmarkError> { - let amount: BalanceOf = u16::MAX.into(); + let amount: BalanceOf = (1_000_000_000_000_u64).saturated_into::().saturated_into(); let sovereign_account = sibling_sovereign_account::(para_id); T::Token::mint_into(&sovereign_account, amount)?; Ok(()) diff --git a/parachain/pallets/control/src/lib.rs b/parachain/pallets/control/src/lib.rs index 33c1f1a82309b..25aaa0edd2a5f 100644 --- a/parachain/pallets/control/src/lib.rs +++ b/parachain/pallets/control/src/lib.rs @@ -37,12 +37,18 @@ use sp_std::prelude::*; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; +use frame_support::{ + pallet_prelude::*, + traits::{tokens::Preservation, EnsureOrigin}, +}; +use frame_system::pallet_prelude::*; use snowbridge_core::{ outbound::{ Command, Initializer, Message, OperatingMode, OutboundQueue as OutboundQueueTrait, ParaId, }, sibling_sovereign_account, AgentId, }; +use sp_runtime::Saturating; #[cfg(feature = "runtime-benchmarks")] use frame_support::traits::OriginTrait; @@ -82,22 +88,21 @@ where /// Whether a fee should be withdrawn to an account for sending an outbound message #[derive(Clone, PartialEq, RuntimeDebug)] -enum PaysFee +pub enum PaysFee where T: Config, { + /// Fully charge includes (base_fee + delivery_fee) Yes(AccountIdOf), + /// Partially charge includes base_fee only + Partial(AccountIdOf), + /// No charge No, } #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{tokens::Preservation, EnsureOrigin}, - }; - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -289,7 +294,8 @@ pub mod pallet { ensure!(Channels::::contains_key(para_id), Error::::NoChannel); let command = Command::UpdateChannel { para_id, mode, fee }; - Self::send(para_id, command, PaysFee::::No)?; + let pays_fee = PaysFee::::Partial(sibling_sovereign_account::(para_id)); + Self::send(para_id, command, pays_fee)?; Self::deposit_event(Event::::UpdateChannel { para_id, mode, fee }); Ok(()) @@ -361,7 +367,9 @@ pub mod pallet { // Ensure that origin location is some consensus system on a sibling parachain let (para_id, agent_id) = ensure_sibling::(&origin_location)?; - Self::do_transfer_native_from_agent(agent_id, para_id, recipient, amount) + let pays_fee = PaysFee::::Partial(sibling_sovereign_account::(para_id)); + + Self::do_transfer_native_from_agent(agent_id, para_id, recipient, amount, pays_fee) } /// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`. @@ -390,7 +398,9 @@ pub mod pallet { let (para_id, agent_id) = ensure_sibling::(&location).map_err(|_| Error::::InvalidLocation)?; - Self::do_transfer_native_from_agent(agent_id, para_id, recipient, amount) + let pays_fee = PaysFee::::No; + + Self::do_transfer_native_from_agent(agent_id, para_id, recipient, amount, pays_fee) } } @@ -398,14 +408,20 @@ pub mod pallet { /// Send `command` to the Gateway on the channel identified by `origin`. fn send(origin: ParaId, command: Command, pays_fee: PaysFee) -> DispatchResult { let message = Message { origin, command }; - let (ticket, delivery_fee) = + let (ticket, fee) = T::OutboundQueue::validate(&message).map_err(|_| Error::::SubmissionFailed)?; - if let PaysFee::Yes(sovereign_account) = pays_fee { + let payment = match pays_fee { + PaysFee::Yes(account) => Some((account, fee.base.saturating_add(fee.delivery))), + PaysFee::Partial(account) => Some((account, fee.base)), + PaysFee::No => None, + }; + + if let Some((payer, fee)) = payment { T::Token::transfer( - &sovereign_account, + &payer, &T::TreasuryAccount::get(), - delivery_fee, + fee, Preservation::Preserve, )?; } @@ -421,11 +437,12 @@ pub mod pallet { para_id: ParaId, recipient: H160, amount: u128, + pays_fee: PaysFee, ) -> DispatchResult { ensure!(Agents::::contains_key(agent_id), Error::::NoAgent); let command = Command::TransferNativeFromAgent { agent_id, recipient, amount }; - Self::send(para_id, command, PaysFee::::No)?; + Self::send(para_id, command, pays_fee)?; Self::deposit_event(Event::::TransferNativeFromAgent { agent_id, diff --git a/parachain/pallets/control/src/mock.rs b/parachain/pallets/control/src/mock.rs index 916012368ca1b..4c660360bd341 100644 --- a/parachain/pallets/control/src/mock.rs +++ b/parachain/pallets/control/src/mock.rs @@ -4,18 +4,19 @@ use crate as snowbridge_control; use frame_support::{ parameter_types, traits::{tokens::fungible::Mutate, ConstU128, ConstU16, ConstU64, Contains}, + weights::IdentityFee, PalletId, }; use sp_core::H256; use xcm_executor::traits::ConvertLocation; use snowbridge_core::{ - outbound::{Message, MessageHash, ParaId, SubmitError}, - AgentId, + outbound::{ConstantGasMeter, ParaId}, + sibling_sovereign_account, AgentId, }; use sp_runtime::{ testing::Header, - traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Keccak256}, AccountId32, }; use xcm::prelude::*; @@ -94,7 +95,9 @@ frame_support::construct_runtime!( System: frame_system, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, XcmOrigin: pallet_xcm_origin::{Pallet, Origin}, + OutboundQueue: snowbridge_outbound_queue::{Pallet, Call, Storage, Event}, EthereumControl: snowbridge_control, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} } ); @@ -146,7 +149,45 @@ impl pallet_xcm_origin::Config for Test { } parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl pallet_message_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MessageProcessor = OutboundQueue; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; +} + +parameter_types! { + pub const MaxMessagePayloadSize: u32 = 1024; + pub const MaxMessagesPerBlock: u32 = 20; pub const OwnParaId: ParaId = ParaId::new(1013); +} + +impl snowbridge_outbound_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type MaxMessagePayloadSize = MaxMessagePayloadSize; + type MaxMessagesPerBlock = MaxMessagesPerBlock; + type OwnParaId = OwnParaId; + type GasMeter = ConstantGasMeter; + type Balance = u128; + type DeliveryFeePerGas = ConstU128<1>; + type DeliveryRefundPerGas = ConstU128<1>; + type DeliveryReward = ConstU128<1>; + type WeightToFee = IdentityFee; + type WeightInfo = (); +} + +parameter_types! { pub const SS58Prefix: u8 = 42; pub const AnyNetwork: Option = None; pub const RelayNetwork: Option = Some(NetworkId::Kusama); @@ -155,24 +196,12 @@ parameter_types! { X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)); } -pub struct MockOutboundQueue; -impl snowbridge_control::OutboundQueueTrait for MockOutboundQueue { - type Ticket = Message; - type Balance = Balance; - - fn validate(message: &Message) -> Result<(Self::Ticket, Self::Balance), SubmitError> { - Ok((message.clone(), 10)) - } - - fn submit(_ticket: Self::Ticket) -> Result { - Ok(MessageHash::zero()) - } -} - parameter_types! { pub TreasuryAccount: AccountId = PalletId(*b"py/trsry").into_account_truncating(); pub Fee: u64 = 1000; pub const RococoNetwork: NetworkId = NetworkId::Rococo; + pub const InitialFunding: u128 = 1_000_000_000_000; + pub TestParaId: u32 = 2000; } #[cfg(feature = "runtime-benchmarks")] @@ -196,7 +225,7 @@ impl Contains for AllowSiblingsOnly { impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type OwnParaId = OwnParaId; - type OutboundQueue = MockOutboundQueue; + type OutboundQueue = OutboundQueue; type MessageHasher = BlakeTwo256; type SiblingOrigin = pallet_xcm_origin::EnsureXcm; type AgentIdOf = HashedDescription>; @@ -211,9 +240,15 @@ impl crate::Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext: sp_io::TestExternalities = storage.into(); + let initial_amount = InitialFunding::get().into(); + let test_para_id = TestParaId::get(); + let sovereign_account = sibling_sovereign_account::(test_para_id.into()); + let treasury_account = TreasuryAccount::get(); ext.execute_with(|| { System::set_block_number(1); - let _ = Balances::mint_into(&AccountId32::from([0; 32]), 1_000_000_000_000); + Balances::mint_into(&AccountId32::from([0; 32]), initial_amount).unwrap(); + Balances::mint_into(&sovereign_account, initial_amount).unwrap(); + Balances::mint_into(&treasury_account, initial_amount).unwrap(); }); ext } diff --git a/parachain/pallets/control/src/tests.rs b/parachain/pallets/control/src/tests.rs index 9b649d13228e9..3e54ad39094ce 100644 --- a/parachain/pallets/control/src/tests.rs +++ b/parachain/pallets/control/src/tests.rs @@ -29,8 +29,10 @@ fn create_agent() { fn create_agent_fails_on_funds_unavailable() { new_test_ext().execute_with(|| { let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(2000)) }; - let origin = make_xcm_origin(origin_location); + // Reset balance of sovereign_account to zero so to trigger the FundsUnavailable error + let sovereign_account = sibling_sovereign_account::(2000.into()); + Balances::set_balance(&sovereign_account, 0); assert_noop!(EthereumControl::create_agent(origin), TokenError::FundsUnavailable); }); } @@ -506,3 +508,76 @@ fn check_sibling_sovereign_account() { ); }); } + +#[test] +fn charge_fee_for_create_agent() { + new_test_ext().execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(para_id)) }; + let origin = make_xcm_origin(origin_location); + let sovereign_account = sibling_sovereign_account::(para_id.into()); + let (_, agent_id) = ensure_sibling::(&origin_location).unwrap(); + + assert_ok!(EthereumControl::create_agent(origin.clone())); + // assert sovereign_balance decreased by (fee.base_fee + fee.delivery_fee) + let message = + Message { origin: para_id.into(), command: Command::CreateAgent { agent_id } }; + let (_, fee) = OutboundQueue::validate(&message).unwrap(); + let sovereign_balance = Balances::balance(&sovereign_account); + assert_eq!(sovereign_balance + fee.base + fee.delivery, InitialFunding::get()); + + // and treasury_balance increased + let treasury_balance = Balances::balance(&TreasuryAccount::get()); + assert_eq!(treasury_balance > InitialFunding::get(), true); + + // (sovereign_balance + treasury_balance) keeps the same + assert_eq!(sovereign_balance + treasury_balance, (InitialFunding::get() * 2) as u128); + }); +} + +#[test] +fn charge_fee_for_transfer_native_from_agent() { + new_test_ext().execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(para_id)) }; + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + let origin = make_xcm_origin(origin_location); + let (_, agent_id) = ensure_sibling::(&origin_location).unwrap(); + + let sovereign_account = sibling_sovereign_account::(para_id.into()); + + // create_agent & create_channel first + assert_ok!(EthereumControl::create_agent(origin.clone())); + assert_ok!(EthereumControl::create_channel(origin.clone())); + + // assert sovereign_balance decreased by only the base_fee + let sovereign_balance_before = Balances::balance(&sovereign_account); + assert_ok!(EthereumControl::transfer_native_from_agent(origin.clone(), recipient, amount)); + let message = Message { + origin: para_id.into(), + command: Command::TransferNativeFromAgent { agent_id, recipient, amount }, + }; + let (_, fee) = OutboundQueue::validate(&message).unwrap(); + let sovereign_balance_after = Balances::balance(&sovereign_account); + assert_eq!(sovereign_balance_after + fee.base, sovereign_balance_before); + }); +} + +#[test] +fn charge_fee_for_upgrade() { + new_test_ext().execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin = RuntimeOrigin::root(); + let address: H160 = Default::default(); + let code_hash: H256 = Default::default(); + let initializer: Option = + Some(Initializer { params: [0; 256].into(), maximum_required_gas: 10000 }); + assert_ok!(EthereumControl::upgrade(origin, address, code_hash, initializer.clone())); + + // assert sovereign_balance does not change as we do not charge for sudo operations + let sovereign_account = sibling_sovereign_account::(para_id.into()); + let sovereign_balance = Balances::balance(&sovereign_account); + assert_eq!(sovereign_balance, InitialFunding::get()); + }); +} diff --git a/parachain/pallets/outbound-queue/Cargo.toml b/parachain/pallets/outbound-queue/Cargo.toml index e0d07672f38f4..ae32eaa3964aa 100644 --- a/parachain/pallets/outbound-queue/Cargo.toml +++ b/parachain/pallets/outbound-queue/Cargo.toml @@ -3,16 +3,16 @@ name = "snowbridge-outbound-queue" description = "Snowbridge Outbound Queue" version = "0.1.1" edition = "2021" -authors = [ "Snowfork " ] +authors = ["Snowfork "] repository = "https://github.com/Snowfork/snowbridge" [package.metadata.docs.rs] -targets = [ "x86_64-unknown-linux-gnu" ] +targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde = { version = "1.0.164", optional = true } -codec = { version = "3.1.5", package = "parity-scale-codec", default-features = false, features = [ "derive" ] } -scale-info = { version = "2.7.0", default-features = false, features = [ "derive" ] } +codec = { version = "3.1.5", package = "parity-scale-codec", default-features = false, features = ["derive"] } +scale-info = { version = "2.7.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } rlp = { version = "0.5", default-features = false, optional = true } @@ -34,13 +34,13 @@ bp-runtime = { git = "https://github.com/Snowfork/cumulus.git", branch = "snowbr [dev-dependencies] frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "master" } -pallet-message-queue = { git = "https://github.com/paritytech/substrate.git", branch = "master", default-features = false } +pallet-message-queue = { git = "https://github.com/paritytech/substrate.git", branch = "master" } sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" } hex-literal = { version = "0.4.1" } rlp = { version = "0.5" } [features] -default = [ "std" ] +default = ["std"] std = [ "serde", "codec/std", @@ -65,5 +65,5 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "hex-literal", - "rlp" + "rlp", ] diff --git a/parachain/pallets/outbound-queue/runtime-api/Cargo.toml b/parachain/pallets/outbound-queue/runtime-api/Cargo.toml index 73cf375a90bd1..0fbd3a48f1d47 100644 --- a/parachain/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/parachain/pallets/outbound-queue/runtime-api/Cargo.toml @@ -13,6 +13,7 @@ codec = { version = "3.1.5", package = "parity-scale-codec", features = [ "deriv sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master", default-features = false} sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master", default-features = false} sp-api = { git = "https://github.com/paritytech/substrate.git", branch = "master", default-features = false} +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master", default-features = false } xcm = { git = "https://github.com/paritytech/polkadot.git", branch = "master", default-features = false } snowbridge-outbound-queue-merkle-tree = { path = "../merkle-tree", default-features = false} snowbridge-core = { path = "../../../primitives/core", default-features = false } @@ -24,6 +25,7 @@ std = [ "sp-core/std", "sp-api/std", "sp-std/std", + "frame-support/std", "xcm/std", "snowbridge-outbound-queue-merkle-tree/std", "snowbridge-core/std", diff --git a/parachain/pallets/outbound-queue/runtime-api/src/lib.rs b/parachain/pallets/outbound-queue/runtime-api/src/lib.rs index 906db434f327e..f3504e150103e 100644 --- a/parachain/pallets/outbound-queue/runtime-api/src/lib.rs +++ b/parachain/pallets/outbound-queue/runtime-api/src/lib.rs @@ -2,11 +2,15 @@ // SPDX-FileCopyrightText: 2023 Snowfork #![cfg_attr(not(feature = "std"), no_std)] +use frame_support::traits::tokens::Balance as BalanceT; +use snowbridge_core::outbound::{Fees, Message, SubmitError}; use snowbridge_outbound_queue_merkle_tree::MerkleProof; sp_api::decl_runtime_apis! { - pub trait OutboundQueueApi + pub trait OutboundQueueApi where Balance: BalanceT { fn prove_message(leaf_index: u64) -> Option; + + fn calculate_fee(message: Message) -> Result, SubmitError>; } } diff --git a/parachain/pallets/outbound-queue/src/api.rs b/parachain/pallets/outbound-queue/src/api.rs index f1eb48776dd2c..ce352027663dd 100644 --- a/parachain/pallets/outbound-queue/src/api.rs +++ b/parachain/pallets/outbound-queue/src/api.rs @@ -4,18 +4,24 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; +use snowbridge_core::outbound::{Fees, Message, OutboundQueue, SubmitError}; use snowbridge_outbound_queue_merkle_tree::{merkle_proof, MerkleProof}; -pub fn prove_message(leaf_index: u64) -> Option +pub fn prove_message(leaf_index: u64) -> Option where - Runtime: Config, + T: Config, { - if !MessageLeaves::::exists() { + if !MessageLeaves::::exists() { return None } - let proof = merkle_proof::<::Hashing, _>( - MessageLeaves::::stream_iter(), - leaf_index, - ); + let proof = + merkle_proof::<::Hashing, _>(MessageLeaves::::stream_iter(), leaf_index); Some(proof) } + +pub fn calculate_fee(message: Message) -> Result, SubmitError> +where + T: Config, +{ + Ok(crate::Pallet::::validate(&message)?.1) +} diff --git a/parachain/pallets/outbound-queue/src/benchmarking.rs b/parachain/pallets/outbound-queue/src/benchmarking.rs index ec73384a448e2..97e50a7677d3e 100644 --- a/parachain/pallets/outbound-queue/src/benchmarking.rs +++ b/parachain/pallets/outbound-queue/src/benchmarking.rs @@ -47,7 +47,7 @@ mod benchmarks { /// Benchmark for producing final messages commitment #[benchmark] - fn on_finalize() -> Result<(), BenchmarkError> { + fn do_commit_messages() -> Result<(), BenchmarkError> { // Assume worst case, where `MaxMessagesPerBlock` messages need to be committed. for i in 0..T::MaxMessagesPerBlock::get() { let leaf_data: [u8; 1] = [i as u8]; @@ -63,5 +63,19 @@ mod benchmarks { Ok(()) } + /// Benchmark for producing commitment for a single message + #[benchmark] + fn do_commit_one_message() -> Result<(), BenchmarkError> { + let leaf = ::Hashing::hash(&[100; 1]); + MessageLeaves::::append(leaf); + + #[block] + { + OutboundQueue::::commit_messages(); + } + + Ok(()) + } + impl_benchmark_test_suite!(OutboundQueue, crate::test::new_tester(), crate::test::Test,); } diff --git a/parachain/pallets/outbound-queue/src/lib.rs b/parachain/pallets/outbound-queue/src/lib.rs index bba0920dae407..8ffb1276b4e74 100644 --- a/parachain/pallets/outbound-queue/src/lib.rs +++ b/parachain/pallets/outbound-queue/src/lib.rs @@ -41,7 +41,7 @@ use frame_support::{ ensure, storage::StorageStreamIter, traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessage, ProcessMessageError}, - weights::Weight, + weights::{Weight, WeightToFee}, }; use snowbridge_core::ParaId; use sp_core::H256; @@ -49,8 +49,9 @@ use sp_runtime::traits::{Hash, Saturating}; use sp_std::prelude::*; use snowbridge_core::outbound::{ - AggregateMessageOrigin, Command, EnqueuedMessage, ExportOrigin, GasMeter, Message, MessageHash, - OutboundQueue as OutboundQueueTrait, OutboundQueueTicket, PreparedMessage, SubmitError, + AggregateMessageOrigin, Command, EnqueuedMessage, ExportOrigin, Fees, GasMeter, Message, + MessageHash, OutboundQueue as OutboundQueueTrait, OutboundQueueTicket, PreparedMessage, + SubmitError, }; use snowbridge_outbound_queue_merkle_tree::merkle_root; pub use snowbridge_outbound_queue_merkle_tree::MerkleProof; @@ -114,6 +115,9 @@ pub mod pallet { #[pallet::constant] type DeliveryReward: Get; + /// Convert a weight value into a deductible fee based. + type WeightToFee: WeightToFee; + /// Weight information for extrinsics in this pallet type WeightInfo: WeightInfo; } @@ -200,7 +204,7 @@ pub mod pallet { Messages::::kill(); MessageLeaves::::kill(); // Reserve some weight for the `on_finalize` handler - T::WeightInfo::on_finalize() + T::WeightInfo::commit_messages() } fn on_finalize(_: BlockNumberFor) { @@ -343,7 +347,7 @@ pub mod pallet { type Ticket = OutboundQueueTicket>; type Balance = T::Balance; - fn validate(message: &Message) -> Result<(Self::Ticket, Self::Balance), SubmitError> { + fn validate(message: &Message) -> Result<(Self::Ticket, Fees), SubmitError> { // The inner payload should not be too large let payload = message.command.abi_encode(); @@ -354,8 +358,16 @@ pub mod pallet { payload.len() < T::MaxMessagePayloadSize::get() as usize, SubmitError::MessageTooLarge ); + + let base_fee = T::WeightToFee::weight_to_fee( + &T::WeightInfo::do_process_message() + .saturating_add(T::WeightInfo::commit_one_message()), + ); let delivery_fee = Self::delivery_fee(&message.command); + let command = message.command.clone(); + let fee = Fees { base: base_fee, delivery: delivery_fee }; + let enqueued_message: EnqueuedMessage = EnqueuedMessage { id: message_id, origin: message.origin, command }; // The whole message should not be too large @@ -365,7 +377,7 @@ pub mod pallet { let ticket = OutboundQueueTicket { id: message_id, origin: message.origin, message: encoded }; - Ok((ticket, delivery_fee)) + Ok((ticket, fee)) } fn submit(ticket: Self::Ticket) -> Result { diff --git a/parachain/pallets/outbound-queue/src/test.rs b/parachain/pallets/outbound-queue/src/test.rs index de78e5e51353e..3c83102e140a4 100644 --- a/parachain/pallets/outbound-queue/src/test.rs +++ b/parachain/pallets/outbound-queue/src/test.rs @@ -6,7 +6,7 @@ use super::*; use frame_support::{ assert_err, assert_noop, assert_ok, parameter_types, traits::{Everything, Hooks, ProcessMessageError}, - weights::WeightMeter, + weights::{IdentityFee, WeightMeter}, }; use snowbridge_core::outbound::{AgentExecuteCommand, Command, ExportOrigin, Initializer}; @@ -99,6 +99,7 @@ impl crate::Config for Test { type DeliveryFeePerGas = ConstU128<1>; type DeliveryRefundPerGas = ConstU128<1>; type DeliveryReward = ConstU128<1>; + type WeightToFee = IdentityFee; type WeightInfo = (); } diff --git a/parachain/pallets/outbound-queue/src/weights.rs b/parachain/pallets/outbound-queue/src/weights.rs index 928227a8128fc..be6a0ba296ff1 100644 --- a/parachain/pallets/outbound-queue/src/weights.rs +++ b/parachain/pallets/outbound-queue/src/weights.rs @@ -32,7 +32,8 @@ use core::marker::PhantomData; /// Weight functions needed for `snowbridge_outbound_queue`. pub trait WeightInfo { fn do_process_message() -> Weight; - fn on_finalize() -> Weight; + fn commit_messages() -> Weight; + fn commit_one_message() -> Weight; } // For backwards compatibility and tests. @@ -58,7 +59,7 @@ impl WeightInfo for () { /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) /// Storage: System Digest (r:1 w:1) /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) - fn on_finalize() -> Weight { + fn commit_messages() -> Weight { // Proof Size summary in bytes: // Measured: `1094` // Estimated: `2579` @@ -67,4 +68,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + + fn commit_one_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 1586) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } diff --git a/parachain/primitives/core/src/outbound.rs b/parachain/primitives/core/src/outbound.rs index 132ae8c85b294..9eae0ee062241 100644 --- a/parachain/primitives/core/src/outbound.rs +++ b/parachain/primitives/core/src/outbound.rs @@ -2,7 +2,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use derivative::Derivative; use ethabi::Token; use frame_support::{ - traits::{tokens::Balance, Get}, + traits::{tokens::Balance as BalanceT, Get}, BoundedVec, CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; pub use polkadot_parachain::primitives::Id as ParaId; @@ -16,34 +16,35 @@ pub type FeeAmount = u128; pub type GasAmount = u128; pub type GasPriceInWei = u128; +/// OutboundFee which covers the cost of execution on both BridgeHub and Ethereum +#[derive(Copy, Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Fees { + /// Fee for processing the message locally + pub base: Balance, + /// Fee for processing the message remotely + pub delivery: Balance, +} + +impl Fees { + pub fn total(&self) -> Balance { + self.base.saturating_add(self.delivery) + } +} + /// A trait for enqueueing messages for delivery to Ethereum pub trait OutboundQueue { - type Ticket: Clone; - type Balance: Balance; + type Ticket: Clone + Encode + Decode; + type Balance: BalanceT; /// Validate an outbound message and return a tuple: /// 1. A ticket for submitting the message - /// 2. The delivery fee in DOT which covers the cost of execution on Ethereum - fn validate(message: &Message) -> Result<(Self::Ticket, Self::Balance), SubmitError>; + /// 2. The OutboundFee + fn validate(message: &Message) -> Result<(Self::Ticket, Fees), SubmitError>; /// Submit the message ticket for eventual delivery to Ethereum fn submit(ticket: Self::Ticket) -> Result; } -/// Default implementation of `OutboundQueue` for tests -impl OutboundQueue for () { - type Ticket = u64; - type Balance = u64; - - fn validate(message: &Message) -> Result<(Self::Ticket, Self::Balance), SubmitError> { - Ok((0, 0)) - } - - fn submit(ticket: Self::Ticket) -> Result { - Ok(MessageHash::zero()) - } -} - /// SubmitError returned #[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum SubmitError { diff --git a/parachain/primitives/router/src/outbound/mod.rs b/parachain/primitives/router/src/outbound/mod.rs index 081037af52974..233367bdcd850 100644 --- a/parachain/primitives/router/src/outbound/mod.rs +++ b/parachain/primitives/router/src/outbound/mod.rs @@ -126,13 +126,13 @@ where }; // validate the message - let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { + let (ticket, fees) = OutboundQueue::validate(&outbound_message).map_err(|err| { log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}"); SendError::Unroutable })?; // convert fee to MultiAsset - let fee = MultiAsset::from((MultiLocation::parent(), fee)).into(); + let fee = MultiAsset::from((MultiLocation::parent(), fees.total())).into(); Ok((ticket.encode(), fee)) } @@ -309,7 +309,7 @@ impl<'a, Call> XcmConverter<'a, Call> { mod tests { use frame_support::parameter_types; use hex_literal::hex; - use snowbridge_core::outbound::{MessageHash, SubmitError}; + use snowbridge_core::outbound::{Fees, MessageHash, SubmitError}; use xcm_builder::{DescribeAllTerminal, DescribeFamily, HashedDescription}; pub type AgentIdOf = HashedDescription>; @@ -332,8 +332,8 @@ mod tests { type Ticket = (); type Balance = u128; - fn validate(_: &Message) -> Result<((), Self::Balance), SubmitError> { - Ok(((), 1)) + fn validate(_: &Message) -> Result<((), Fees), SubmitError> { + Ok(((), Fees { base: 1, delivery: 1 })) } fn submit(_: Self::Ticket) -> Result { @@ -345,7 +345,7 @@ mod tests { type Ticket = (); type Balance = u128; - fn validate(_: &Message) -> Result<((), Self::Balance), SubmitError> { + fn validate(_: &Message) -> Result<((), Fees), SubmitError> { Err(SubmitError::MessageTooLarge) }