mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 02:21:14 +00:00
Removes Snowbridge parachain directory (#3186)
Removes the `bridges/snowbridge/parachain` directory and moves everything up to under `snowbridge` directly. We are cleaning up our local dev env after merging our crates into the polkadot-sdk. --------- Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Helpers for implementing runtime api
|
||||
|
||||
use crate::{Config, MessageLeaves};
|
||||
use frame_support::storage::StorageStreamIter;
|
||||
use snowbridge_core::outbound::{Message, SendMessage};
|
||||
use snowbridge_outbound_queue_merkle_tree::{merkle_proof, MerkleProof};
|
||||
|
||||
pub fn prove_message<T>(leaf_index: u64) -> Option<MerkleProof>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
if !MessageLeaves::<T>::exists() {
|
||||
return None
|
||||
}
|
||||
let proof =
|
||||
merkle_proof::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter(), leaf_index);
|
||||
Some(proof)
|
||||
}
|
||||
|
||||
pub fn calculate_fee<T>(message: Message) -> Option<T::Balance>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
match crate::Pallet::<T>::validate(&message) {
|
||||
Ok((_, fees)) => Some(fees.total()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use bridge_hub_common::AggregateMessageOrigin;
|
||||
use codec::Encode;
|
||||
use frame_benchmarking::v2::*;
|
||||
use snowbridge_core::{
|
||||
outbound::{Command, Initializer},
|
||||
ChannelId,
|
||||
};
|
||||
use sp_core::{H160, H256};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::Pallet as OutboundQueue;
|
||||
|
||||
#[benchmarks(
|
||||
where
|
||||
<T as Config>::MaxMessagePayloadSize: Get<u32>,
|
||||
)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
/// Benchmark for processing a message.
|
||||
#[benchmark]
|
||||
fn do_process_message() -> Result<(), BenchmarkError> {
|
||||
let enqueued_message = QueuedMessage {
|
||||
id: H256::zero(),
|
||||
channel_id: ChannelId::from([1; 32]),
|
||||
command: Command::Upgrade {
|
||||
impl_address: H160::zero(),
|
||||
impl_code_hash: H256::zero(),
|
||||
initializer: Some(Initializer {
|
||||
params: [7u8; 256].into_iter().collect(),
|
||||
maximum_required_gas: 200_000,
|
||||
}),
|
||||
},
|
||||
};
|
||||
let origin = AggregateMessageOrigin::Snowbridge([1; 32].into());
|
||||
let encoded_enqueued_message = enqueued_message.encode();
|
||||
|
||||
#[block]
|
||||
{
|
||||
let _ = OutboundQueue::<T>::do_process_message(origin, &encoded_enqueued_message);
|
||||
}
|
||||
|
||||
assert_eq!(MessageLeaves::<T>::decode_len().unwrap(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark for producing final messages commitment
|
||||
#[benchmark]
|
||||
fn commit() -> 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];
|
||||
let leaf = <T as Config>::Hashing::hash(&leaf_data);
|
||||
MessageLeaves::<T>::append(leaf);
|
||||
}
|
||||
|
||||
#[block]
|
||||
{
|
||||
OutboundQueue::<T>::commit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark for producing commitment for a single message
|
||||
#[benchmark]
|
||||
fn commit_single() -> Result<(), BenchmarkError> {
|
||||
let leaf = <T as Config>::Hashing::hash(&[100; 1]);
|
||||
MessageLeaves::<T>::append(leaf);
|
||||
|
||||
#[block]
|
||||
{
|
||||
OutboundQueue::<T>::commit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(OutboundQueue, crate::mock::new_tester(), crate::mock::Test,);
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Pallet for committing outbound messages for delivery to Ethereum
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Messages come either from sibling parachains via XCM, or BridgeHub itself
|
||||
//! via the `snowbridge-pallet-system`:
|
||||
//!
|
||||
//! 1. `snowbridge_router_primitives::outbound::EthereumBlobExporter::deliver`
|
||||
//! 2. `snowbridge_pallet_system::Pallet::send`
|
||||
//!
|
||||
//! The message submission pipeline works like this:
|
||||
//! 1. The message is first validated via the implementation for
|
||||
//! [`snowbridge_core::outbound::SendMessage::validate`]
|
||||
//! 2. The message is then enqueued for later processing via the implementation for
|
||||
//! [`snowbridge_core::outbound::SendMessage::deliver`]
|
||||
//! 3. The underlying message queue is implemented by [`Config::MessageQueue`]
|
||||
//! 4. The message queue delivers messages back to this pallet via the implementation for
|
||||
//! [`frame_support::traits::ProcessMessage::process_message`]
|
||||
//! 5. The message is processed in `Pallet::do_process_message`: a. Assigned a nonce b. ABI-encoded,
|
||||
//! hashed, and stored in the `MessageLeaves` vector
|
||||
//! 6. At the end of the block, a merkle root is constructed from all the leaves in `MessageLeaves`.
|
||||
//! 7. This merkle root is inserted into the parachain header as a digest item
|
||||
//! 8. Offchain relayers are able to relay the message to Ethereum after: a. Generating a merkle
|
||||
//! proof for the committed message using the `prove_message` runtime API b. Reading the actual
|
||||
//! message content from the `Messages` vector in storage
|
||||
//!
|
||||
//! On the Ethereum side, the message root is ultimately the thing being
|
||||
//! verified by the Polkadot light client.
|
||||
//!
|
||||
//! # Message Priorities
|
||||
//!
|
||||
//! The processing of governance commands can never be halted. This effectively
|
||||
//! allows us to pause processing of normal user messages while still allowing
|
||||
//! governance commands to be sent to Ethereum.
|
||||
//!
|
||||
//! # Fees
|
||||
//!
|
||||
//! An upfront fee must be paid for delivering a message. This fee covers several
|
||||
//! components:
|
||||
//! 1. The weight of processing the message locally
|
||||
//! 2. The gas refund paid out to relayers for message submission
|
||||
//! 3. An additional reward paid out to relayers for message submission
|
||||
//!
|
||||
//! Messages are weighed to determine the maximum amount of gas they could
|
||||
//! consume on Ethereum. Using this upper bound, a final fee can be calculated.
|
||||
//!
|
||||
//! The fee calculation also requires the following parameters:
|
||||
//! * ETH/DOT exchange rate
|
||||
//! * Ether fee per unit of gas
|
||||
//!
|
||||
//! By design, it is expected that governance should manually update these
|
||||
//! parameters every few weeks using the `set_pricing_parameters` extrinsic in the
|
||||
//! system pallet.
|
||||
//!
|
||||
//! ## Fee Computation Function
|
||||
//!
|
||||
//! ```text
|
||||
//! LocalFee(Message) = WeightToFee(ProcessMessageWeight(Message))
|
||||
//! RemoteFee(Message) = MaxGasRequired(Message) * FeePerGas + Reward
|
||||
//! Fee(Message) = LocalFee(Message) + (RemoteFee(Message) / Ratio("ETH/DOT"))
|
||||
//! ```
|
||||
//!
|
||||
//! By design, the computed fee is always going to conservative, to cover worst-case
|
||||
//! costs of dispatch on Ethereum. In future iterations of the design, we will optimize
|
||||
//! this, or provide a mechanism to asynchronously refund a portion of collected fees.
|
||||
//!
|
||||
//! # Extrinsics
|
||||
//!
|
||||
//! * [`Call::set_operating_mode`]: Set the operating mode
|
||||
//!
|
||||
//! # Runtime API
|
||||
//!
|
||||
//! * `prove_message`: Generate a merkle proof for a committed message
|
||||
//! * `calculate_fee`: Calculate the delivery fee for a message
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
pub mod api;
|
||||
pub mod process_message_impl;
|
||||
pub mod send_message_impl;
|
||||
pub mod types;
|
||||
pub mod weights;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem};
|
||||
use codec::Decode;
|
||||
use frame_support::{
|
||||
storage::StorageStreamIter,
|
||||
traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError},
|
||||
weights::{Weight, WeightToFee},
|
||||
};
|
||||
use snowbridge_core::{
|
||||
outbound::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS},
|
||||
BasicOperatingMode, ChannelId,
|
||||
};
|
||||
use snowbridge_outbound_queue_merkle_tree::merkle_root;
|
||||
pub use snowbridge_outbound_queue_merkle_tree::MerkleProof;
|
||||
use sp_core::{H256, U256};
|
||||
use sp_runtime::{
|
||||
traits::{CheckedDiv, Hash},
|
||||
DigestItem,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
pub use types::{CommittedMessage, ProcessMessageOriginOf};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use snowbridge_core::PricingParameters;
|
||||
use sp_arithmetic::FixedU128;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
type Hashing: Hash<Output = H256>;
|
||||
|
||||
type MessageQueue: EnqueueMessage<AggregateMessageOrigin>;
|
||||
|
||||
/// Measures the maximum gas used to execute a command on Ethereum
|
||||
type GasMeter: GasMeter;
|
||||
|
||||
type Balance: Balance + From<u128>;
|
||||
|
||||
/// Number of decimal places in native currency
|
||||
#[pallet::constant]
|
||||
type Decimals: Get<u8>;
|
||||
|
||||
/// Max bytes in a message payload
|
||||
#[pallet::constant]
|
||||
type MaxMessagePayloadSize: Get<u32>;
|
||||
|
||||
/// Max number of messages processed per block
|
||||
#[pallet::constant]
|
||||
type MaxMessagesPerBlock: Get<u32>;
|
||||
|
||||
/// Check whether a channel exists
|
||||
type Channels: Contains<ChannelId>;
|
||||
|
||||
type PricingParameters: Get<PricingParameters<Self::Balance>>;
|
||||
|
||||
/// Convert a weight value into a deductible fee based.
|
||||
type WeightToFee: WeightToFee<Balance = Self::Balance>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// Message has been queued and will be processed in the future
|
||||
MessageQueued {
|
||||
/// ID of the message. Usually the XCM message hash or a SetTopic.
|
||||
id: H256,
|
||||
},
|
||||
/// Message will be committed at the end of current block. From now on, to track the
|
||||
/// progress the message, use the `nonce` of `id`.
|
||||
MessageAccepted {
|
||||
/// ID of the message
|
||||
id: H256,
|
||||
/// The nonce assigned to this message
|
||||
nonce: u64,
|
||||
},
|
||||
/// Some messages have been committed
|
||||
MessagesCommitted {
|
||||
/// Merkle root of the committed messages
|
||||
root: H256,
|
||||
/// number of committed messages
|
||||
count: u64,
|
||||
},
|
||||
/// Set OperatingMode
|
||||
OperatingModeChanged { mode: BasicOperatingMode },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The message is too large
|
||||
MessageTooLarge,
|
||||
/// The pallet is halted
|
||||
Halted,
|
||||
/// Invalid Channel
|
||||
InvalidChannel,
|
||||
}
|
||||
|
||||
/// Messages to be committed in the current block. This storage value is killed in
|
||||
/// `on_initialize`, so should never go into block PoV.
|
||||
///
|
||||
/// Is never read in the runtime, only by offchain message relayers.
|
||||
///
|
||||
/// Inspired by the `frame_system::Pallet::Events` storage value
|
||||
#[pallet::storage]
|
||||
#[pallet::unbounded]
|
||||
pub(super) type Messages<T: Config> = StorageValue<_, Vec<CommittedMessage>, ValueQuery>;
|
||||
|
||||
/// Hashes of the ABI-encoded messages in the [`Messages`] storage value. Used to generate a
|
||||
/// merkle root during `on_finalize`. This storage value is killed in
|
||||
/// `on_initialize`, so should never go into block PoV.
|
||||
#[pallet::storage]
|
||||
#[pallet::unbounded]
|
||||
#[pallet::getter(fn message_leaves)]
|
||||
pub(super) type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
|
||||
|
||||
/// The current nonce for each message origin
|
||||
#[pallet::storage]
|
||||
pub type Nonce<T: Config> = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>;
|
||||
|
||||
/// The current operating mode of the pallet.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn operating_mode)]
|
||||
pub type OperatingMode<T: Config> = StorageValue<_, BasicOperatingMode, ValueQuery>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T>
|
||||
where
|
||||
T::AccountId: AsRef<[u8]>,
|
||||
{
|
||||
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
|
||||
// Remove storage from previous block
|
||||
Messages::<T>::kill();
|
||||
MessageLeaves::<T>::kill();
|
||||
// Reserve some weight for the `on_finalize` handler
|
||||
T::WeightInfo::commit()
|
||||
}
|
||||
|
||||
fn on_finalize(_: BlockNumberFor<T>) {
|
||||
Self::commit();
|
||||
}
|
||||
|
||||
fn integrity_test() {
|
||||
let decimals = T::Decimals::get();
|
||||
assert!(decimals == 10 || decimals == 12, "Decimals should be 10 or 12");
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Halt or resume all pallet operations. May only be called by root.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(
|
||||
origin: OriginFor<T>,
|
||||
mode: BasicOperatingMode,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
OperatingMode::<T>::put(mode);
|
||||
Self::deposit_event(Event::OperatingModeChanged { mode });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Generate a messages commitment and insert it into the header digest
|
||||
pub(crate) fn commit() {
|
||||
let count = MessageLeaves::<T>::decode_len().unwrap_or_default() as u64;
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Create merkle root of messages
|
||||
let root = merkle_root::<<T as Config>::Hashing, _>(MessageLeaves::<T>::stream_iter());
|
||||
|
||||
let digest_item: DigestItem = CustomDigestItem::Snowbridge(root).into();
|
||||
|
||||
// Insert merkle root into the header digest
|
||||
<frame_system::Pallet<T>>::deposit_log(digest_item);
|
||||
|
||||
Self::deposit_event(Event::MessagesCommitted { root, count });
|
||||
}
|
||||
|
||||
/// Process a message delivered by the MessageQueue pallet
|
||||
pub(crate) fn do_process_message(
|
||||
_: ProcessMessageOriginOf<T>,
|
||||
mut message: &[u8],
|
||||
) -> Result<bool, ProcessMessageError> {
|
||||
use ProcessMessageError::*;
|
||||
|
||||
// Yield if the maximum number of messages has been processed this block.
|
||||
// This ensures that the weight of `on_finalize` has a known maximum bound.
|
||||
ensure!(
|
||||
MessageLeaves::<T>::decode_len().unwrap_or(0) <
|
||||
T::MaxMessagesPerBlock::get() as usize,
|
||||
Yield
|
||||
);
|
||||
|
||||
// Decode bytes into versioned message
|
||||
let versioned_queued_message: VersionedQueuedMessage =
|
||||
VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?;
|
||||
|
||||
// Convert versioned message into latest supported message version
|
||||
let queued_message: QueuedMessage =
|
||||
versioned_queued_message.try_into().map_err(|_| Unsupported)?;
|
||||
|
||||
// Obtain next nonce
|
||||
let nonce = <Nonce<T>>::try_mutate(
|
||||
queued_message.channel_id,
|
||||
|nonce| -> Result<u64, ProcessMessageError> {
|
||||
*nonce = nonce.checked_add(1).ok_or(Unsupported)?;
|
||||
Ok(*nonce)
|
||||
},
|
||||
)?;
|
||||
|
||||
let pricing_params = T::PricingParameters::get();
|
||||
let command = queued_message.command.index();
|
||||
let params = queued_message.command.abi_encode();
|
||||
let max_dispatch_gas =
|
||||
T::GasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
|
||||
let reward = pricing_params.rewards.remote;
|
||||
|
||||
// Construct the final committed message
|
||||
let message = CommittedMessage {
|
||||
channel_id: queued_message.channel_id,
|
||||
nonce,
|
||||
command,
|
||||
params,
|
||||
max_dispatch_gas,
|
||||
max_fee_per_gas: pricing_params
|
||||
.fee_per_gas
|
||||
.try_into()
|
||||
.defensive_unwrap_or(u128::MAX),
|
||||
reward: reward.try_into().defensive_unwrap_or(u128::MAX),
|
||||
id: queued_message.id,
|
||||
};
|
||||
|
||||
// ABI-encode and hash the prepared message
|
||||
let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
|
||||
let message_abi_encoded_hash = <T as Config>::Hashing::hash(&message_abi_encoded);
|
||||
|
||||
Messages::<T>::append(Box::new(message));
|
||||
MessageLeaves::<T>::append(message_abi_encoded_hash);
|
||||
|
||||
Self::deposit_event(Event::MessageAccepted { id: queued_message.id, nonce });
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Calculate total fee in native currency to cover all costs of delivering a message to the
|
||||
/// remote destination. See module-level documentation for more details.
|
||||
pub(crate) fn calculate_fee(
|
||||
gas_used_at_most: u64,
|
||||
params: PricingParameters<T::Balance>,
|
||||
) -> Fee<T::Balance> {
|
||||
// Remote fee in ether
|
||||
let fee = Self::calculate_remote_fee(
|
||||
gas_used_at_most,
|
||||
params.fee_per_gas,
|
||||
params.rewards.remote,
|
||||
);
|
||||
|
||||
// downcast to u128
|
||||
let fee: u128 = fee.try_into().defensive_unwrap_or(u128::MAX);
|
||||
|
||||
// convert to local currency
|
||||
let fee = FixedU128::from_inner(fee)
|
||||
.checked_div(¶ms.exchange_rate)
|
||||
.expect("exchange rate is not zero; qed")
|
||||
.into_inner();
|
||||
|
||||
// adjust fixed point to match local currency
|
||||
let fee = Self::convert_from_ether_decimals(fee);
|
||||
|
||||
Fee::from((Self::calculate_local_fee(), fee))
|
||||
}
|
||||
|
||||
/// Calculate fee in remote currency for dispatching a message on Ethereum
|
||||
pub(crate) fn calculate_remote_fee(
|
||||
gas_used_at_most: u64,
|
||||
fee_per_gas: U256,
|
||||
reward: U256,
|
||||
) -> U256 {
|
||||
fee_per_gas.saturating_mul(gas_used_at_most.into()).saturating_add(reward)
|
||||
}
|
||||
|
||||
/// The local component of the message processing fees in native currency
|
||||
pub(crate) fn calculate_local_fee() -> T::Balance {
|
||||
T::WeightToFee::weight_to_fee(
|
||||
&T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()),
|
||||
)
|
||||
}
|
||||
|
||||
// 1 DOT has 10 digits of precision
|
||||
// 1 KSM has 12 digits of precision
|
||||
// 1 ETH has 18 digits of precision
|
||||
pub(crate) fn convert_from_ether_decimals(value: u128) -> T::Balance {
|
||||
let decimals = ETHER_DECIMALS.saturating_sub(T::Decimals::get()) as u32;
|
||||
let denom = 10u128.saturating_pow(decimals);
|
||||
value.checked_div(denom).expect("divisor is non-zero; qed").into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::{Everything, Hooks},
|
||||
weights::IdentityFee,
|
||||
};
|
||||
|
||||
use snowbridge_core::{
|
||||
gwei, meth,
|
||||
outbound::*,
|
||||
pricing::{PricingParameters, Rewards},
|
||||
ParaId, PRIMARY_GOVERNANCE_CHANNEL,
|
||||
};
|
||||
use sp_core::{ConstU32, ConstU8, H160, H256};
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup, Keccak256},
|
||||
AccountId32, BuildStorage, FixedU128,
|
||||
};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
type AccountId = AccountId32;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Storage, Event<T>},
|
||||
MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event<T>},
|
||||
OutboundQueue: crate::{Pallet, Storage, Event<T>},
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
}
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type RuntimeTask = RuntimeTask;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type DbWeight = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
type Nonce = u64;
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const HeapSize: u32 = 32 * 1024;
|
||||
pub const MaxStale: u32 = 32;
|
||||
pub static ServiceWeight: Option<Weight> = 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;
|
||||
type QueuePausedQuery = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const OwnParaId: ParaId = ParaId::new(1013);
|
||||
pub Parameters: PricingParameters<u128> = PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 400),
|
||||
fee_per_gas: gwei(20),
|
||||
rewards: Rewards { local: DOT, remote: meth(1) }
|
||||
};
|
||||
}
|
||||
|
||||
pub const DOT: u128 = 10_000_000_000;
|
||||
|
||||
impl crate::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Hashing = Keccak256;
|
||||
type MessageQueue = MessageQueue;
|
||||
type Decimals = ConstU8<12>;
|
||||
type MaxMessagePayloadSize = ConstU32<1024>;
|
||||
type MaxMessagesPerBlock = ConstU32<20>;
|
||||
type GasMeter = ConstantGasMeter;
|
||||
type Balance = u128;
|
||||
type PricingParameters = Parameters;
|
||||
type Channels = Everything;
|
||||
type WeightToFee = IdentityFee<u128>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
fn setup() {
|
||||
System::set_block_number(1);
|
||||
}
|
||||
|
||||
pub fn new_tester() -> sp_io::TestExternalities {
|
||||
let storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
let mut ext: sp_io::TestExternalities = storage.into();
|
||||
ext.execute_with(setup);
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn run_to_end_of_next_block() {
|
||||
// finish current block
|
||||
MessageQueue::on_finalize(System::block_number());
|
||||
OutboundQueue::on_finalize(System::block_number());
|
||||
System::on_finalize(System::block_number());
|
||||
// start next block
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
System::on_initialize(System::block_number());
|
||||
OutboundQueue::on_initialize(System::block_number());
|
||||
MessageQueue::on_initialize(System::block_number());
|
||||
// finish next block
|
||||
MessageQueue::on_finalize(System::block_number());
|
||||
OutboundQueue::on_finalize(System::block_number());
|
||||
System::on_finalize(System::block_number());
|
||||
}
|
||||
|
||||
pub fn mock_governance_message<T>() -> Message
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
let _marker = PhantomData::<T>; // for clippy
|
||||
|
||||
Message {
|
||||
id: None,
|
||||
channel_id: PRIMARY_GOVERNANCE_CHANNEL,
|
||||
command: Command::Upgrade {
|
||||
impl_address: H160::zero(),
|
||||
impl_code_hash: H256::zero(),
|
||||
initializer: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Message should fail validation as it is too large
|
||||
pub fn mock_invalid_governance_message<T>() -> Message
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
let _marker = PhantomData::<T>; // for clippy
|
||||
|
||||
Message {
|
||||
id: None,
|
||||
channel_id: PRIMARY_GOVERNANCE_CHANNEL,
|
||||
command: Command::Upgrade {
|
||||
impl_address: H160::zero(),
|
||||
impl_code_hash: H256::zero(),
|
||||
initializer: Some(Initializer {
|
||||
params: (0..1000).map(|_| 1u8).collect::<Vec<u8>>(),
|
||||
maximum_required_gas: 0,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_message(sibling_para_id: u32) -> Message {
|
||||
Message {
|
||||
id: None,
|
||||
channel_id: ParaId::from(sibling_para_id).into(),
|
||||
command: Command::AgentExecute {
|
||||
agent_id: Default::default(),
|
||||
command: AgentExecuteCommand::TransferToken {
|
||||
token: Default::default(),
|
||||
recipient: Default::default(),
|
||||
amount: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Implementation for [`frame_support::traits::ProcessMessage`]
|
||||
use super::*;
|
||||
use crate::weights::WeightInfo;
|
||||
use frame_support::{
|
||||
traits::{ProcessMessage, ProcessMessageError},
|
||||
weights::WeightMeter,
|
||||
};
|
||||
|
||||
impl<T: Config> ProcessMessage for Pallet<T> {
|
||||
type Origin = AggregateMessageOrigin;
|
||||
fn process_message(
|
||||
message: &[u8],
|
||||
origin: Self::Origin,
|
||||
meter: &mut WeightMeter,
|
||||
_: &mut [u8; 32],
|
||||
) -> Result<bool, ProcessMessageError> {
|
||||
let weight = T::WeightInfo::do_process_message();
|
||||
if meter.try_consume(weight).is_err() {
|
||||
return Err(ProcessMessageError::Overweight(weight))
|
||||
}
|
||||
Self::do_process_message(origin, message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Implementation for [`snowbridge_core::outbound::SendMessage`]
|
||||
use super::*;
|
||||
use bridge_hub_common::AggregateMessageOrigin;
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{EnqueueMessage, Get},
|
||||
CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use frame_system::unique;
|
||||
use snowbridge_core::{
|
||||
outbound::{
|
||||
Fee, Message, QueuedMessage, SendError, SendMessage, SendMessageFeeProvider,
|
||||
VersionedQueuedMessage,
|
||||
},
|
||||
ChannelId, PRIMARY_GOVERNANCE_CHANNEL,
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::BoundedVec;
|
||||
|
||||
/// The maximal length of an enqueued message, as determined by the MessageQueue pallet
|
||||
pub type MaxEnqueuedMessageSizeOf<T> =
|
||||
<<T as Config>::MessageQueue as EnqueueMessage<AggregateMessageOrigin>>::MaxMessageLen;
|
||||
|
||||
#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound)]
|
||||
pub struct Ticket<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
pub message_id: H256,
|
||||
pub channel_id: ChannelId,
|
||||
pub message: BoundedVec<u8, MaxEnqueuedMessageSizeOf<T>>,
|
||||
}
|
||||
|
||||
impl<T> SendMessage for Pallet<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
type Ticket = Ticket<T>;
|
||||
|
||||
fn validate(
|
||||
message: &Message,
|
||||
) -> Result<(Self::Ticket, Fee<<Self as SendMessageFeeProvider>::Balance>), SendError> {
|
||||
// The inner payload should not be too large
|
||||
let payload = message.command.abi_encode();
|
||||
ensure!(
|
||||
payload.len() < T::MaxMessagePayloadSize::get() as usize,
|
||||
SendError::MessageTooLarge
|
||||
);
|
||||
|
||||
// Ensure there is a registered channel we can transmit this message on
|
||||
ensure!(T::Channels::contains(&message.channel_id), SendError::InvalidChannel);
|
||||
|
||||
// Generate a unique message id unless one is provided
|
||||
let message_id: H256 = message
|
||||
.id
|
||||
.unwrap_or_else(|| unique((message.channel_id, &message.command)).into());
|
||||
|
||||
let gas_used_at_most = T::GasMeter::maximum_gas_used_at_most(&message.command);
|
||||
let fee = Self::calculate_fee(gas_used_at_most, T::PricingParameters::get());
|
||||
|
||||
let queued_message: VersionedQueuedMessage = QueuedMessage {
|
||||
id: message_id,
|
||||
channel_id: message.channel_id,
|
||||
command: message.command.clone(),
|
||||
}
|
||||
.into();
|
||||
// The whole message should not be too large
|
||||
let encoded = queued_message.encode().try_into().map_err(|_| SendError::MessageTooLarge)?;
|
||||
|
||||
let ticket = Ticket { message_id, channel_id: message.channel_id, message: encoded };
|
||||
|
||||
Ok((ticket, fee))
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<H256, SendError> {
|
||||
let origin = AggregateMessageOrigin::Snowbridge(ticket.channel_id);
|
||||
|
||||
if ticket.channel_id != PRIMARY_GOVERNANCE_CHANNEL {
|
||||
ensure!(!Self::operating_mode().is_halted(), SendError::Halted);
|
||||
}
|
||||
|
||||
let message = ticket.message.as_bounded_slice();
|
||||
|
||||
T::MessageQueue::enqueue_message(message, origin);
|
||||
Self::deposit_event(Event::MessageQueued { id: ticket.message_id });
|
||||
Ok(ticket.message_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SendMessageFeeProvider for Pallet<T> {
|
||||
type Balance = T::Balance;
|
||||
|
||||
/// The local component of the message processing fees in native currency
|
||||
fn local_fee() -> Self::Balance {
|
||||
Self::calculate_local_fee()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use crate::{mock::*, *};
|
||||
|
||||
use frame_support::{
|
||||
assert_err, assert_noop, assert_ok,
|
||||
traits::{Hooks, ProcessMessage, ProcessMessageError},
|
||||
weights::WeightMeter,
|
||||
};
|
||||
|
||||
use codec::Encode;
|
||||
use snowbridge_core::{
|
||||
outbound::{Command, SendError, SendMessage},
|
||||
ParaId, PricingParameters, Rewards,
|
||||
};
|
||||
use sp_arithmetic::FixedU128;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::FixedPointNumber;
|
||||
|
||||
#[test]
|
||||
fn submit_messages_and_commit() {
|
||||
new_tester().execute_with(|| {
|
||||
for para_id in 1000..1004 {
|
||||
let message = mock_message(para_id);
|
||||
let (ticket, _) = OutboundQueue::validate(&message).unwrap();
|
||||
assert_ok!(OutboundQueue::deliver(ticket));
|
||||
}
|
||||
|
||||
ServiceWeight::set(Some(Weight::MAX));
|
||||
run_to_end_of_next_block();
|
||||
|
||||
for para_id in 1000..1004 {
|
||||
let origin: ParaId = (para_id as u32).into();
|
||||
let channel_id: ChannelId = origin.into();
|
||||
assert_eq!(Nonce::<Test>::get(channel_id), 1);
|
||||
}
|
||||
|
||||
let digest = System::digest();
|
||||
let digest_items = digest.logs();
|
||||
assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some());
|
||||
assert_eq!(Messages::<Test>::decode_len(), Some(4));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_message_fail_too_large() {
|
||||
new_tester().execute_with(|| {
|
||||
let message = mock_invalid_governance_message::<Test>();
|
||||
assert_err!(OutboundQueue::validate(&message), SendError::MessageTooLarge);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_from_ether_decimals() {
|
||||
assert_eq!(
|
||||
OutboundQueue::convert_from_ether_decimals(1_000_000_000_000_000_000),
|
||||
1_000_000_000_000
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_exits_early_if_no_processed_messages() {
|
||||
new_tester().execute_with(|| {
|
||||
// on_finalize should do nothing, nor should it panic
|
||||
OutboundQueue::on_finalize(System::block_number());
|
||||
|
||||
let digest = System::digest();
|
||||
let digest_items = digest.logs();
|
||||
assert_eq!(digest_items.len(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_message_yields_on_max_messages_per_block() {
|
||||
new_tester().execute_with(|| {
|
||||
for _ in 0..<Test as Config>::MaxMessagesPerBlock::get() {
|
||||
MessageLeaves::<Test>::append(H256::zero())
|
||||
}
|
||||
|
||||
let channel_id: ChannelId = ParaId::from(1000).into();
|
||||
let origin = AggregateMessageOrigin::Snowbridge(channel_id);
|
||||
let message = QueuedMessage {
|
||||
id: Default::default(),
|
||||
channel_id,
|
||||
command: Command::Upgrade {
|
||||
impl_address: Default::default(),
|
||||
impl_code_hash: Default::default(),
|
||||
initializer: None,
|
||||
},
|
||||
}
|
||||
.encode();
|
||||
|
||||
let mut meter = WeightMeter::new();
|
||||
|
||||
assert_noop!(
|
||||
OutboundQueue::process_message(message.as_slice(), origin, &mut meter, &mut [0u8; 32]),
|
||||
ProcessMessageError::Yield
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_message_fails_on_max_nonce_reached() {
|
||||
new_tester().execute_with(|| {
|
||||
let sibling_id = 1000;
|
||||
let channel_id: ChannelId = ParaId::from(sibling_id).into();
|
||||
let origin = AggregateMessageOrigin::Snowbridge(channel_id);
|
||||
let message: QueuedMessage = QueuedMessage {
|
||||
id: H256::zero(),
|
||||
channel_id,
|
||||
command: mock_message(sibling_id).command,
|
||||
};
|
||||
let versioned_queued_message: VersionedQueuedMessage = message.try_into().unwrap();
|
||||
let encoded = versioned_queued_message.encode();
|
||||
let mut meter = WeightMeter::with_limit(Weight::MAX);
|
||||
|
||||
Nonce::<Test>::set(channel_id, u64::MAX);
|
||||
|
||||
assert_noop!(
|
||||
OutboundQueue::process_message(encoded.as_slice(), origin, &mut meter, &mut [0u8; 32]),
|
||||
ProcessMessageError::Unsupported
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_message_fails_on_overweight_message() {
|
||||
new_tester().execute_with(|| {
|
||||
let sibling_id = 1000;
|
||||
let channel_id: ChannelId = ParaId::from(sibling_id).into();
|
||||
let origin = AggregateMessageOrigin::Snowbridge(channel_id);
|
||||
let message: QueuedMessage = QueuedMessage {
|
||||
id: H256::zero(),
|
||||
channel_id,
|
||||
command: mock_message(sibling_id).command,
|
||||
};
|
||||
let versioned_queued_message: VersionedQueuedMessage = message.try_into().unwrap();
|
||||
let encoded = versioned_queued_message.encode();
|
||||
let mut meter = WeightMeter::with_limit(Weight::from_parts(1, 1));
|
||||
assert_noop!(
|
||||
OutboundQueue::process_message(encoded.as_slice(), origin, &mut meter, &mut [0u8; 32]),
|
||||
ProcessMessageError::Overweight(<Test as Config>::WeightInfo::do_process_message())
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
// Governance messages should be able to bypass a halted operating mode
|
||||
// Other message sends should fail when halted
|
||||
#[test]
|
||||
fn submit_upgrade_message_success_when_queue_halted() {
|
||||
new_tester().execute_with(|| {
|
||||
// halt the outbound queue
|
||||
OutboundQueue::set_operating_mode(RuntimeOrigin::root(), BasicOperatingMode::Halted)
|
||||
.unwrap();
|
||||
|
||||
// submit a high priority message from bridge_hub should success
|
||||
let message = mock_governance_message::<Test>();
|
||||
let (ticket, _) = OutboundQueue::validate(&message).unwrap();
|
||||
assert_ok!(OutboundQueue::deliver(ticket));
|
||||
|
||||
// submit a low priority message from asset_hub will fail as pallet is halted
|
||||
let message = mock_message(1000);
|
||||
let (ticket, _) = OutboundQueue::validate(&message).unwrap();
|
||||
assert_noop!(OutboundQueue::deliver(ticket), SendError::Halted);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn governance_message_does_not_get_the_chance_to_processed_in_same_block_when_congest_of_low_priority_sibling_messages(
|
||||
) {
|
||||
use snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;
|
||||
use AggregateMessageOrigin::*;
|
||||
|
||||
let sibling_id: u32 = 1000;
|
||||
let sibling_channel_id: ChannelId = ParaId::from(sibling_id).into();
|
||||
|
||||
new_tester().execute_with(|| {
|
||||
// submit a lot of low priority messages from asset_hub which will need multiple blocks to
|
||||
// execute(20 messages for each block so 40 required at least 2 blocks)
|
||||
let max_messages = 40;
|
||||
for _ in 0..max_messages {
|
||||
// submit low priority message
|
||||
let message = mock_message(sibling_id);
|
||||
let (ticket, _) = OutboundQueue::validate(&message).unwrap();
|
||||
OutboundQueue::deliver(ticket).unwrap();
|
||||
}
|
||||
|
||||
let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id));
|
||||
assert_eq!(footprint.storage.count, (max_messages) as u64);
|
||||
|
||||
let message = mock_governance_message::<Test>();
|
||||
let (ticket, _) = OutboundQueue::validate(&message).unwrap();
|
||||
OutboundQueue::deliver(ticket).unwrap();
|
||||
|
||||
// move to next block
|
||||
ServiceWeight::set(Some(Weight::MAX));
|
||||
run_to_end_of_next_block();
|
||||
|
||||
// first process 20 messages from sibling channel
|
||||
let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id));
|
||||
assert_eq!(footprint.storage.count, 40 - 20);
|
||||
|
||||
// and governance message does not have the chance to execute in same block
|
||||
let footprint = MessageQueue::footprint(Snowbridge(PRIMARY_GOVERNANCE_CHANNEL));
|
||||
assert_eq!(footprint.storage.count, 1);
|
||||
|
||||
// move to next block
|
||||
ServiceWeight::set(Some(Weight::MAX));
|
||||
run_to_end_of_next_block();
|
||||
|
||||
// now governance message get executed in this block
|
||||
let footprint = MessageQueue::footprint(Snowbridge(PRIMARY_GOVERNANCE_CHANNEL));
|
||||
assert_eq!(footprint.storage.count, 0);
|
||||
|
||||
// and this time process 19 messages from sibling channel so we have 1 message left
|
||||
let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id));
|
||||
assert_eq!(footprint.storage.count, 1);
|
||||
|
||||
// move to the next block, the last 1 message from sibling channel get executed
|
||||
ServiceWeight::set(Some(Weight::MAX));
|
||||
run_to_end_of_next_block();
|
||||
let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id));
|
||||
assert_eq!(footprint.storage.count, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_local_currency() {
|
||||
new_tester().execute_with(|| {
|
||||
let fee: u128 = 1_000_000;
|
||||
let fee1 = FixedU128::from_inner(fee).into_inner();
|
||||
let fee2 = FixedU128::from(fee)
|
||||
.into_inner()
|
||||
.checked_div(FixedU128::accuracy())
|
||||
.expect("accuracy is not zero; qed");
|
||||
assert_eq!(fee, fee1);
|
||||
assert_eq!(fee, fee2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_digest_item_with_correct_index() {
|
||||
new_tester().execute_with(|| {
|
||||
let digest_item: DigestItem = CustomDigestItem::Snowbridge(H256::default()).into();
|
||||
let enum_prefix = match digest_item {
|
||||
DigestItem::Other(data) => data[0],
|
||||
_ => u8::MAX,
|
||||
};
|
||||
assert_eq!(enum_prefix, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_digest_item() {
|
||||
new_tester().execute_with(|| {
|
||||
let digest_item: DigestItem = CustomDigestItem::Snowbridge([5u8; 32].into()).into();
|
||||
let digest_item_raw = digest_item.encode();
|
||||
assert_eq!(digest_item_raw[0], 0); // DigestItem::Other
|
||||
assert_eq!(digest_item_raw[2], 0); // CustomDigestItem::Snowbridge
|
||||
assert_eq!(
|
||||
digest_item_raw,
|
||||
[
|
||||
0, 132, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_messages_with_fees() {
|
||||
new_tester().execute_with(|| {
|
||||
let message = mock_message(1000);
|
||||
let (_, fee) = OutboundQueue::validate(&message).unwrap();
|
||||
assert_eq!(fee.local, 698000000);
|
||||
assert_eq!(fee.remote, 2680000000000);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_fees() {
|
||||
new_tester().execute_with(|| {
|
||||
let gas_used: u64 = 250000;
|
||||
let illegal_price_params: PricingParameters<<Test as Config>::Balance> =
|
||||
PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 400),
|
||||
fee_per_gas: 10000_u32.into(),
|
||||
rewards: Rewards { local: 1_u32.into(), remote: 1_u32.into() },
|
||||
};
|
||||
let fee = OutboundQueue::calculate_fee(gas_used, illegal_price_params);
|
||||
assert_eq!(fee.local, 698000000);
|
||||
assert_eq!(fee.remote, 1000000);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_fees_with_valid_exchange_rate_but_remote_fee_calculated_as_zero() {
|
||||
new_tester().execute_with(|| {
|
||||
let gas_used: u64 = 250000;
|
||||
let illegal_price_params: PricingParameters<<Test as Config>::Balance> =
|
||||
PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 1),
|
||||
fee_per_gas: 1_u32.into(),
|
||||
rewards: Rewards { local: 1_u32.into(), remote: 1_u32.into() },
|
||||
};
|
||||
let fee = OutboundQueue::calculate_fee(gas_used, illegal_price_params.clone());
|
||||
assert_eq!(fee.local, 698000000);
|
||||
// Though none zero pricing params the remote fee calculated here is invalid
|
||||
// which should be avoided
|
||||
assert_eq!(fee.remote, 0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use codec::{Decode, Encode};
|
||||
use ethabi::Token;
|
||||
use frame_support::traits::ProcessMessage;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use super::Pallet;
|
||||
|
||||
use snowbridge_core::ChannelId;
|
||||
pub use snowbridge_outbound_queue_merkle_tree::MerkleProof;
|
||||
|
||||
pub type ProcessMessageOriginOf<T> = <Pallet<T> as ProcessMessage>::Origin;
|
||||
|
||||
pub const LOG_TARGET: &str = "snowbridge-outbound-queue";
|
||||
|
||||
/// Message which has been assigned a nonce and will be committed at the end of a block
|
||||
#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct CommittedMessage {
|
||||
/// Message channel
|
||||
pub channel_id: ChannelId,
|
||||
/// Unique nonce to prevent replaying messages
|
||||
#[codec(compact)]
|
||||
pub nonce: u64,
|
||||
/// Command to execute in the Gateway contract
|
||||
pub command: u8,
|
||||
/// Params for the command
|
||||
pub params: Vec<u8>,
|
||||
/// Maximum gas allowed for message dispatch
|
||||
#[codec(compact)]
|
||||
pub max_dispatch_gas: u64,
|
||||
/// Maximum fee per gas
|
||||
#[codec(compact)]
|
||||
pub max_fee_per_gas: u128,
|
||||
/// Reward in ether for delivering this message, in addition to the gas refund
|
||||
#[codec(compact)]
|
||||
pub reward: u128,
|
||||
/// Message ID (Used for tracing messages across route, has no role in consensus)
|
||||
pub id: H256,
|
||||
}
|
||||
|
||||
/// Convert message into an ABI-encoded form for delivery to the InboundQueue contract on Ethereum
|
||||
impl From<CommittedMessage> for Token {
|
||||
fn from(x: CommittedMessage) -> Token {
|
||||
Token::Tuple(vec![
|
||||
Token::FixedBytes(Vec::from(x.channel_id.as_ref())),
|
||||
Token::Uint(x.nonce.into()),
|
||||
Token::Uint(x.command.into()),
|
||||
Token::Bytes(x.params.to_vec()),
|
||||
Token::Uint(x.max_dispatch_gas.into()),
|
||||
Token::Uint(x.max_fee_per_gas.into()),
|
||||
Token::Uint(x.reward.into()),
|
||||
Token::FixedBytes(Vec::from(x.id.as_ref())),
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
//! Autogenerated weights for `snowbridge-pallet-outbound-queue`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-10-19, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `192.168.1.7`, CPU: `<UNKNOWN>`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// target/release/polkadot-parachain
|
||||
// benchmark
|
||||
// pallet
|
||||
// --chain=bridge-hub-rococo-dev
|
||||
// --pallet=snowbridge-pallet-outbound-queue
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --template
|
||||
// ../parachain/templates/module-weight-template.hbs
|
||||
// --output
|
||||
// ../parachain/pallets/outbound-queue/src/weights.rs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `snowbridge-pallet-outbound-queue`.
|
||||
pub trait WeightInfo {
|
||||
fn do_process_message() -> Weight;
|
||||
fn commit() -> Weight;
|
||||
fn commit_single() -> Weight;
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1)
|
||||
/// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured)
|
||||
/// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1)
|
||||
/// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue Nonce (r:1 w:1)
|
||||
/// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue Messages (r:1 w:1)
|
||||
/// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured)
|
||||
fn do_process_message() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `42`
|
||||
// Estimated: `3485`
|
||||
// Minimum execution time: 39_000_000 picoseconds.
|
||||
Weight::from_parts(39_000_000, 3485)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0)
|
||||
/// 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 commit() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1094`
|
||||
// Estimated: `2579`
|
||||
// Minimum execution time: 28_000_000 picoseconds.
|
||||
Weight::from_parts(28_000_000, 2579)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
|
||||
fn commit_single() -> 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user