feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
[package]
|
||||
name = "snowbridge-pallet-outbound-queue-v2"
|
||||
description = "Snowbridge Outbound Queue Pallet V2"
|
||||
version = "0.2.0"
|
||||
authors = ["Snowfork <contact@snowfork.com>"]
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license = "Apache-2.0"
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[package.metadata.pezkuwi-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[dependencies]
|
||||
alloy-core = { workspace = true, features = ["sol-types"] }
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
ethabi = { workspace = true }
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
frame-benchmarking = { optional = true, workspace = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
bp-relayers = { workspace = true }
|
||||
|
||||
snowbridge-beacon-primitives = { workspace = true }
|
||||
snowbridge-core = { workspace = true }
|
||||
snowbridge-merkle-tree = { workspace = true }
|
||||
snowbridge-outbound-queue-primitives = { workspace = true }
|
||||
snowbridge-verification-primitives = { workspace = true }
|
||||
|
||||
xcm = { workspace = true }
|
||||
xcm-builder = { workspace = true }
|
||||
xcm-executor = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-message-queue = { workspace = true }
|
||||
snowbridge-test-utils = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"alloy-core/std",
|
||||
"bp-relayers/std",
|
||||
"codec/std",
|
||||
"ethabi/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"pallet-message-queue/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"snowbridge-beacon-primitives/std",
|
||||
"snowbridge-core/std",
|
||||
"snowbridge-merkle-tree/std",
|
||||
"snowbridge-outbound-queue-primitives/std",
|
||||
"snowbridge-verification-primitives/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"xcm-builder/std",
|
||||
"xcm-executor/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"bp-relayers/runtime-benchmarks",
|
||||
"frame-benchmarking",
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-message-queue/runtime-benchmarks",
|
||||
"snowbridge-beacon-primitives/runtime-benchmarks",
|
||||
"snowbridge-core/runtime-benchmarks",
|
||||
"snowbridge-merkle-tree/runtime-benchmarks",
|
||||
"snowbridge-outbound-queue-primitives/runtime-benchmarks",
|
||||
"snowbridge-test-utils/runtime-benchmarks",
|
||||
"snowbridge-verification-primitives/runtime-benchmarks",
|
||||
"sp-io/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
"xcm-executor/runtime-benchmarks",
|
||||
"xcm/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-message-queue/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "snowbridge-outbound-queue-v2-runtime-api"
|
||||
description = "Snowbridge Outbound Queue Runtime API V2"
|
||||
version = "0.2.0"
|
||||
authors = ["Snowfork <contact@snowfork.com>"]
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license = "Apache-2.0"
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[package.metadata.pezkuwi-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
frame-support = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
snowbridge-merkle-tree = { workspace = true }
|
||||
sp-api = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"scale-info/std",
|
||||
"snowbridge-merkle-tree/std",
|
||||
"sp-api/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-support/runtime-benchmarks",
|
||||
"snowbridge-merkle-tree/runtime-benchmarks",
|
||||
"sp-api/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
|
||||
//! Ethereum Outbound Queue V2 Runtime API
|
||||
//!
|
||||
//! * `prove_message`: Generate a merkle proof for a committed message
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
use frame_support::traits::tokens::Balance as BalanceT;
|
||||
use snowbridge_merkle_tree::MerkleProof;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
pub trait OutboundQueueV2Api<Balance> where Balance: BalanceT
|
||||
{
|
||||
/// Generate a merkle proof for a committed message identified by `leaf_index`.
|
||||
fn prove_message(leaf_index: u64) -> Option<MerkleProof>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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_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)
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use crate::fixture::make_submit_delivery_receipt_message;
|
||||
use codec::Encode;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_support::{traits::Hooks, BoundedVec};
|
||||
use frame_system::RawOrigin;
|
||||
use snowbridge_outbound_queue_primitives::v2::{Command, Initializer, Message};
|
||||
use sp_core::{H160, H256};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::Pallet as OutboundQueue;
|
||||
|
||||
#[benchmarks(
|
||||
where
|
||||
<T as Config>::MaxMessagePayloadSize: Get<u32>,
|
||||
<T as frame_system::Config>::AccountId: From<[u8; 32]>,
|
||||
)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
use frame_support::assert_ok;
|
||||
|
||||
/// Build `Upgrade` message with `MaxMessagePayloadSize`, in the worst-case.
|
||||
fn build_message<T: Config>() -> (Message, OutboundMessage) {
|
||||
let commands = vec![Command::Upgrade {
|
||||
impl_address: H160::zero(),
|
||||
impl_code_hash: H256::zero(),
|
||||
initializer: Initializer {
|
||||
params: core::iter::repeat_with(|| 1_u8)
|
||||
.take(<T as Config>::MaxMessagePayloadSize::get() as usize)
|
||||
.collect(),
|
||||
maximum_required_gas: 200_000,
|
||||
},
|
||||
}];
|
||||
let message = Message {
|
||||
origin: Default::default(),
|
||||
id: H256::default(),
|
||||
fee: 0,
|
||||
commands: BoundedVec::try_from(commands.clone()).unwrap(),
|
||||
};
|
||||
let wrapped_commands: Vec<OutboundCommandWrapper> = commands
|
||||
.into_iter()
|
||||
.map(|command| OutboundCommandWrapper {
|
||||
kind: command.index(),
|
||||
gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
|
||||
payload: command.abi_encode(),
|
||||
})
|
||||
.collect();
|
||||
let outbound_message = OutboundMessage {
|
||||
origin: Default::default(),
|
||||
nonce: 1,
|
||||
topic: H256::default(),
|
||||
commands: wrapped_commands.clone().try_into().unwrap(),
|
||||
};
|
||||
(message, outbound_message)
|
||||
}
|
||||
|
||||
/// Initialize `MaxMessagesPerBlock` messages need to be committed, in the worst-case.
|
||||
fn initialize_worst_case<T: Config>() {
|
||||
for _ in 0..T::MaxMessagesPerBlock::get() {
|
||||
initialize_with_one_message::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize with a single message
|
||||
fn initialize_with_one_message<T: Config>() {
|
||||
let (message, outbound_message) = build_message::<T>();
|
||||
let leaf = <T as Config>::Hashing::hash(&message.encode());
|
||||
MessageLeaves::<T>::append(leaf);
|
||||
Messages::<T>::append(outbound_message);
|
||||
}
|
||||
|
||||
/// Benchmark for processing a message.
|
||||
#[benchmark]
|
||||
fn do_process_message() -> Result<(), BenchmarkError> {
|
||||
let (enqueued_message, _) = build_message::<T>();
|
||||
let origin = T::AggregateMessageOrigin::from([1; 32].into());
|
||||
let message = enqueued_message.encode();
|
||||
|
||||
#[block]
|
||||
{
|
||||
let _ = OutboundQueue::<T>::do_process_message(origin, &message).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(MessageLeaves::<T>::decode_len().unwrap(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark for producing final messages commitment, in the worst-case
|
||||
#[benchmark]
|
||||
fn commit() -> Result<(), BenchmarkError> {
|
||||
initialize_worst_case::<T>();
|
||||
|
||||
#[block]
|
||||
{
|
||||
OutboundQueue::<T>::commit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark for producing commitment for a single message, used to estimate the delivery
|
||||
/// cost. The assumption is that cost of commit a single message is even higher than the average
|
||||
/// cost of commit all messages.
|
||||
#[benchmark]
|
||||
fn commit_single() -> Result<(), BenchmarkError> {
|
||||
initialize_with_one_message::<T>();
|
||||
|
||||
#[block]
|
||||
{
|
||||
OutboundQueue::<T>::commit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark for `on_initialize` in the worst-case
|
||||
#[benchmark]
|
||||
fn on_initialize() -> Result<(), BenchmarkError> {
|
||||
initialize_worst_case::<T>();
|
||||
#[block]
|
||||
{
|
||||
OutboundQueue::<T>::on_initialize(1_u32.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark the entire process flow in the worst-case. This can be used to determine
|
||||
/// appropriate values for the configuration parameters `MaxMessagesPerBlock` and
|
||||
/// `MaxMessagePayloadSize`
|
||||
#[benchmark]
|
||||
fn process() -> Result<(), BenchmarkError> {
|
||||
initialize_worst_case::<T>();
|
||||
let origin = T::AggregateMessageOrigin::from([1; 32].into());
|
||||
let (enqueued_message, _) = build_message::<T>();
|
||||
let message = enqueued_message.encode();
|
||||
|
||||
#[block]
|
||||
{
|
||||
OutboundQueue::<T>::on_initialize(1_u32.into());
|
||||
for _ in 0..T::MaxMessagesPerBlock::get() {
|
||||
OutboundQueue::<T>::do_process_message(origin.clone(), &message).unwrap();
|
||||
}
|
||||
OutboundQueue::<T>::commit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn submit_delivery_receipt() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
let message = make_submit_delivery_receipt_message();
|
||||
|
||||
T::Helper::initialize_storage(message.finalized_header, message.block_roots_root);
|
||||
|
||||
let receipt = DeliveryReceipt::try_from(&message.event.event_log).unwrap();
|
||||
|
||||
let order = PendingOrder {
|
||||
nonce: receipt.nonce,
|
||||
fee: 0,
|
||||
block_number: frame_system::Pallet::<T>::current_block_number(),
|
||||
};
|
||||
<PendingOrders<T>>::insert(receipt.nonce, order);
|
||||
|
||||
#[block]
|
||||
{
|
||||
assert_ok!(OutboundQueue::<T>::submit_delivery_receipt(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(message.event),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(OutboundQueue, crate::mock::new_tester(), crate::mock::Test,);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
// Generated, do not edit!
|
||||
// See ethereum client README.md for instructions to generate
|
||||
|
||||
use hex_literal::hex;
|
||||
use snowbridge_beacon_primitives::{
|
||||
types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader,
|
||||
};
|
||||
use snowbridge_verification_primitives::{EventFixture, EventProof, Log, Proof};
|
||||
use sp_core::U256;
|
||||
use sp_std::vec;
|
||||
|
||||
pub fn make_submit_delivery_receipt_message() -> EventFixture {
|
||||
EventFixture {
|
||||
event: EventProof {
|
||||
event_log: Log {
|
||||
address: hex!("b1185ede04202fe62d38f5db72f71e38ff3e8305").into(),
|
||||
topics: vec![
|
||||
hex!("8856ab63954e6c2938803a4654fb704c8779757e7bfdbe94a578e341ec637a95").into(),
|
||||
hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
|
||||
],
|
||||
data: hex!("907b6ec7bf3f2496ef79238e0fb19e032bfe444c7ffe906bd340c6c4ffe8511f0000000000000000000000000000000000000000000000000000000000000001d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d").into(),
|
||||
},
|
||||
proof: Proof {
|
||||
receipt_proof: (vec![
|
||||
hex!("8a40611a32af2ad0ad63bf32e8c633ff209e6f701645b8f25e492327cd95d4e0").to_vec(),
|
||||
], vec![
|
||||
hex!("f9024e822080b9024802f9024401830d716eb9010000200000000000000000000000000000080000000000000000000000000000000000000000000004000000000000000000000000000000000000000000008000000000000000000801000000000000000000000000000000000000000000000000000000020000000008000000000800000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000800000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000020000000000200000000000000000000000000000000000000000000000000000000f90139f87a94b1185ede04202fe62d38f5db72f71e38ff3e8305f842a057f58171b8777633d03aff1e7408b96a3d910c93a7ce433a8cb7fb837dc306a6a09441dceeeffa7e032eedaccf9b7632e60e86711551a82ffbbb0dda8afd9e4ef7a0000000000000000000000000de45448ca2d57797c0bec0ee15a1e42334744219f8bb94b1185ede04202fe62d38f5db72f71e38ff3e8305f842a08856ab63954e6c2938803a4654fb704c8779757e7bfdbe94a578e341ec637a95a00000000000000000000000000000000000000000000000000000000000000000b860907b6ec7bf3f2496ef79238e0fb19e032bfe444c7ffe906bd340c6c4ffe8511f0000000000000000000000000000000000000000000000000000000000000001d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d").to_vec(),
|
||||
]),
|
||||
execution_proof: ExecutionProof {
|
||||
header: BeaconHeader {
|
||||
slot: 663,
|
||||
proposer_index: 4,
|
||||
parent_root: hex!("478651896411faa949cd0882bb6a82595b71d3eba74cbeb87b5eb8162c7e00f1").into(),
|
||||
state_root: hex!("4191b7c2a622b8cfa31684e5e06de3b36834e403a6ded2acd32156a488f829b0").into(),
|
||||
body_root: hex!("72765c81bbad6cd083314506936528719e03d33ee65927167a372febe1fbf734").into(),
|
||||
},
|
||||
ancestry_proof: Some(AncestryProof {
|
||||
header_branch: vec![
|
||||
hex!("478651896411faa949cd0882bb6a82595b71d3eba74cbeb87b5eb8162c7e00f1").into(),
|
||||
hex!("87314a5200d3a22749bd98ba32af7d0546d25d47cf012dfcc85adc19ad7adfe3").into(),
|
||||
hex!("e794355aa5b1743ea691e3f051f44a66956892621c0110a12980e66f70dc3d74").into(),
|
||||
hex!("fe0a3f7035e5d4cc83412939c9d343caa34889bd9655a05e9cc53a09e3aa7e8c").into(),
|
||||
hex!("0f472e1a66d039197fbe378845e848ec11d5bcde60ac650da812fa1f4461c603").into(),
|
||||
hex!("018f388291fbd20c15691e4b118a17b870eec7beb837e91fcc839adcb5fb21fc").into(),
|
||||
hex!("8a016b0d65b61e5026b9357df092bf82b52fa61af3e10d11ba7b24ae30b457ec").into(),
|
||||
hex!("73af6cacce9735d5576d1defc7fc39061776004ac7d3aba3abf6dfad7b1a3a36").into(),
|
||||
hex!("6116f4b671f0f26c224ab10c6c330d56c9b5e48fa6e2204035f333bc142f55d2").into(),
|
||||
hex!("a08d24b9120c5faeb98654664c4a324aaa509031193dbd74930acf26285b26a6").into(),
|
||||
hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(),
|
||||
hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(),
|
||||
hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(),
|
||||
],
|
||||
finalized_block_root: hex!("d5793913dc57d9f5b9d50fb8c693504201d6926649834ac90337b673e66f98e0").into(),
|
||||
}),
|
||||
execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader {
|
||||
parent_hash: hex!("35f64f37bea4538092ba578f4851d52375f7f3b2a52c1cb16f22fe512aead95d").into(),
|
||||
fee_recipient: hex!("0000000000000000000000000000000000000000").into(),
|
||||
state_root: hex!("dfa305877e67ab0caa827b15d573b58dfe360fe8d484d37228f8ae55ccced61c").into(),
|
||||
receipts_root: hex!("8a40611a32af2ad0ad63bf32e8c633ff209e6f701645b8f25e492327cd95d4e0").into(),
|
||||
logs_bloom: hex!("00200000000000000000000000000000080000000000000000000000000000000000000000000004000000000000000000000000000000000000000000008000000000000000000801000000000000000000000000000000000000000000000000000000020000000008000000000800000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000800000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000020000000000200000000000000000000000000000000000000000000000000000000").into(),
|
||||
prev_randao: hex!("ebdade0d95216bdba380fce14fad97c870d8553fcf0ca37df22331b3ed498db2").into(),
|
||||
block_number: 663,
|
||||
gas_limit: 41857321,
|
||||
gas_used: 881006,
|
||||
timestamp: 1742914413,
|
||||
extra_data: hex!("d983010e0c846765746888676f312e32332e348664617277696e").into(),
|
||||
base_fee_per_gas: U256::from(7u64),
|
||||
block_hash: hex!("c70c7e5b0a03fa9509e0b4598d65e6e0b6a2477f25064aa3221b39eee17583d4").into(),
|
||||
transactions_root: hex!("065ecaa208638c4a43d080cd78a1451e406895caaa254b1ad0585c6a9e3c7ac6").into(),
|
||||
withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(),
|
||||
blob_gas_used: 0,
|
||||
excess_blob_gas: 0,
|
||||
}),
|
||||
execution_branch: vec![
|
||||
hex!("ef46bf5d8bd654162c1a7f44949fe1f9cb2a3f356d593587a3f1f3f8da14b790").into(),
|
||||
hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(),
|
||||
hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(),
|
||||
hex!("b4e39d31736a4064a84ca49c4d372696e92e7c5c7bcbb7219671c24df1dfcafa").into(),
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
finalized_header: BeaconHeader {
|
||||
slot: 864,
|
||||
proposer_index: 4,
|
||||
parent_root: hex!("2839d32d7b5a1dbb9139c5fd11ea549abaac1ead425c79553b8424550fddd389").into(),
|
||||
state_root: hex!("452daf2471437939f4d65af921a1cfee860b11111771fd55164b37fe25d610de").into(),
|
||||
body_root: hex!("0bbd0377987d0984e7495bf61219342941e31a0b6fe790453fbc87ec92319097").into(),
|
||||
},
|
||||
block_roots_root: hex!("aca108d3e77ec6b010ca03df025e3b2e84f754d4642e5d8bf0ba9bdf58f42848").into(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
// 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 teyrchains via XCM, or BridgeHub itself
|
||||
//! via the `snowbridge-pallet-system-v2`:
|
||||
//!
|
||||
//! 1. `snowbridge_outbound_queue_primitives::v2::EthereumBlobExporter::deliver`
|
||||
//! 2. `snowbridge_pallet_system_v2::Pallet::send`
|
||||
//!
|
||||
//! The message submission pipeline works like this:
|
||||
//! 1. The message is first validated via the implementation for
|
||||
//! [`snowbridge_outbound_queue_primitives::v2::SendMessage::validate`]
|
||||
//! 2. The message is then enqueued for later processing via the implementation for
|
||||
//! [`snowbridge_outbound_queue_primitives::v2::SendMessage::deliver`]
|
||||
//! 3. The underlying message queue is implemented by [`Config::MessageQueue`]
|
||||
//! 4. The message queue delivers messages to this pallet via the implementation for
|
||||
//! [`frame_support::traits::ProcessMessage::process_message`]
|
||||
//! 5. The message is processed in `Pallet::do_process_message`:
|
||||
//! a. Convert to `OutboundMessage`, and stored into the `Messages` vector storage
|
||||
//! b. ABI-encode the `OutboundMessage` and store the committed Keccak256 hash in `MessageLeaves`
|
||||
//! c. Generate `PendingOrder` with assigned nonce and fee attached, stored into the
|
||||
//! `PendingOrders` map storage, with nonce as the key
|
||||
//! d. Increment nonce and update the `Nonce` storage
|
||||
//! 6. At the end of the block, a merkle root is constructed from all the leaves in `MessageLeaves`.
|
||||
//! At the beginning of the next block, both `Messages` and `MessageLeaves` are dropped so that
|
||||
//! state at each block only holds the messages processed in that block.
|
||||
//! 7. This merkle root is inserted into the teyrchain 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
|
||||
//! 9. On the Ethereum side, the message root is ultimately the thing being verified by the Beefy
|
||||
//! light client.
|
||||
//! 10. When the message has been verified and executed, the relayer will call the extrinsic
|
||||
//! `submit_delivery_receipt` to:
|
||||
//! a. Verify the message with proof for a transaction receipt containing the event log,
|
||||
//! same as the inbound queue verification flow
|
||||
//! b. Fetch the pending order by nonce of the message, pay reward with fee attached in the order
|
||||
//! c. Remove the order from `PendingOrders` map storage by nonce
|
||||
//!
|
||||
//!
|
||||
//! # Extrinsics
|
||||
//!
|
||||
//! * [`Call::submit_delivery_receipt`]: Submit delivery proof
|
||||
//!
|
||||
//! # Runtime API
|
||||
//!
|
||||
//! * `prove_message`: Generate a merkle proof for a committed 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;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod fixture;
|
||||
|
||||
use alloy_core::{
|
||||
primitives::{Bytes, FixedBytes},
|
||||
sol_types::SolValue,
|
||||
};
|
||||
use bp_relayers::RewardLedger;
|
||||
use codec::{Decode, FullCodec};
|
||||
use frame_support::{
|
||||
storage::StorageStreamIter,
|
||||
traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError},
|
||||
weights::{Weight, WeightToFee},
|
||||
};
|
||||
use snowbridge_core::{
|
||||
digest_item::SnowbridgeDigestItem,
|
||||
reward::{AddTip, AddTipError},
|
||||
BasicOperatingMode,
|
||||
};
|
||||
use snowbridge_merkle_tree::merkle_root;
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v2::{
|
||||
abi::{CommandWrapper, OutboundMessageWrapper},
|
||||
DeliveryReceipt, GasMeter, Message, OutboundCommandWrapper, OutboundMessage,
|
||||
},
|
||||
EventProof, VerificationError, Verifier,
|
||||
};
|
||||
use sp_core::{H160, H256};
|
||||
use sp_runtime::{
|
||||
traits::{BlockNumberProvider, Debug, Hash},
|
||||
DigestItem,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
pub use types::{OnNewCommitment, PendingOrder, ProcessMessageOriginOf};
|
||||
pub use weights::WeightInfo;
|
||||
use xcm::prelude::NetworkId;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use snowbridge_beacon_primitives::BeaconHeader;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
type Hashing: Hash<Output = H256>;
|
||||
|
||||
type AggregateMessageOrigin: FullCodec
|
||||
+ MaxEncodedLen
|
||||
+ Clone
|
||||
+ Eq
|
||||
+ PartialEq
|
||||
+ TypeInfo
|
||||
+ Debug
|
||||
+ From<H256>;
|
||||
|
||||
type MessageQueue: EnqueueMessage<Self::AggregateMessageOrigin>;
|
||||
|
||||
/// Measures the maximum gas used to execute a command on Ethereum
|
||||
type GasMeter: GasMeter;
|
||||
|
||||
type Balance: Balance + From<u128>;
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// Hook that is called whenever there is a new commitment.
|
||||
type OnNewCommitment: OnNewCommitment;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// The verifier for delivery proof from Ethereum
|
||||
type Verifier: Verifier;
|
||||
|
||||
/// Address of the Gateway contract
|
||||
#[pallet::constant]
|
||||
type GatewayAddress: Get<H160>;
|
||||
/// Reward discriminator type.
|
||||
type RewardKind: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
|
||||
/// The default RewardKind discriminator for rewards allocated to relayers from this pallet.
|
||||
#[pallet::constant]
|
||||
type DefaultRewardKind: Get<Self::RewardKind>;
|
||||
/// Relayer reward payment.
|
||||
type RewardPayment: RewardLedger<Self::AccountId, Self::RewardKind, u128>;
|
||||
/// Ethereum NetworkId
|
||||
type EthereumNetwork: Get<NetworkId>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper: BenchmarkHelper<Self>;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// Message has been queued and will be processed in the future
|
||||
MessageQueued {
|
||||
/// The message
|
||||
message: Message,
|
||||
},
|
||||
/// Message will be committed at the end of current block. From now on, to track the
|
||||
/// progress the message, use the `nonce` or the `id`.
|
||||
MessageAccepted {
|
||||
/// ID of the message
|
||||
id: H256,
|
||||
/// The nonce assigned to this message
|
||||
nonce: u64,
|
||||
},
|
||||
/// Message was not committed due to some failure condition, like an overweight message.
|
||||
MessageRejected {
|
||||
/// ID of the message, if known (e.g. if a message is corrupt, the ID will not be
|
||||
/// known).
|
||||
id: Option<H256>,
|
||||
/// The payload of the message. Useful for debugging purposes if the message
|
||||
/// cannot be decoded.
|
||||
payload: Vec<u8>,
|
||||
/// The error that was returned.
|
||||
error: ProcessMessageError,
|
||||
},
|
||||
/// Message was not committed due to being overweight or the current block is full.
|
||||
MessagePostponed {
|
||||
/// The payload of the message. Useful for debugging purposes if the message
|
||||
/// cannot be decoded.
|
||||
payload: Vec<u8>,
|
||||
/// The error that was returned.
|
||||
reason: ProcessMessageError,
|
||||
},
|
||||
/// 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 },
|
||||
/// Delivery Proof received
|
||||
MessageDelivered { nonce: u64 },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The message is too large
|
||||
MessageTooLarge,
|
||||
/// The pallet is halted
|
||||
Halted,
|
||||
/// Invalid Channel
|
||||
InvalidChannel,
|
||||
/// Invalid Envelope
|
||||
InvalidEnvelope,
|
||||
/// Message verification error
|
||||
Verification(VerificationError),
|
||||
/// Invalid Gateway
|
||||
InvalidGateway,
|
||||
/// Pending nonce does not exist
|
||||
InvalidPendingNonce,
|
||||
/// Reward payment failed
|
||||
RewardPaymentFailed,
|
||||
}
|
||||
|
||||
/// Messages to be committed in the current block. This storage value is killed in
|
||||
/// `on_initialize`, so will not end up bloating state.
|
||||
///
|
||||
/// Is never read in the runtime, only by offchain message relayers.
|
||||
/// Because of this, it will never go into the PoV of a block.
|
||||
///
|
||||
/// Inspired by the `frame_system::Pallet::Events` storage value
|
||||
#[pallet::storage]
|
||||
#[pallet::unbounded]
|
||||
pub type Messages<T: Config> = StorageValue<_, Vec<OutboundMessage>, 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 state
|
||||
/// at each block contains only root hash of messages processed in that block. This also means
|
||||
/// it doesn't have to be included in PoV.
|
||||
#[pallet::storage]
|
||||
#[pallet::unbounded]
|
||||
pub type MessageLeaves<T: Config> = StorageValue<_, Vec<H256>, ValueQuery>;
|
||||
|
||||
/// The current nonce for the messages
|
||||
#[pallet::storage]
|
||||
pub type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;
|
||||
|
||||
/// Pending orders to relay
|
||||
#[pallet::storage]
|
||||
pub type PendingOrders<T: Config> =
|
||||
StorageMap<_, Twox64Concat, u64, PendingOrder<BlockNumberFor<T>>, OptionQuery>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
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::on_initialize() + T::WeightInfo::commit()
|
||||
}
|
||||
|
||||
fn on_finalize(_: BlockNumberFor<T>) {
|
||||
Self::commit();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub trait BenchmarkHelper<T> {
|
||||
fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256);
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T>
|
||||
where
|
||||
<T as frame_system::Config>::AccountId: From<[u8; 32]>,
|
||||
{
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::submit_delivery_receipt())]
|
||||
pub fn submit_delivery_receipt(
|
||||
origin: OriginFor<T>,
|
||||
event: Box<EventProof>,
|
||||
) -> DispatchResult
|
||||
where
|
||||
<T as frame_system::Config>::AccountId: From<[u8; 32]>,
|
||||
{
|
||||
let relayer = ensure_signed(origin)?;
|
||||
|
||||
// submit message to verifier for verification
|
||||
T::Verifier::verify(&event.event_log, &event.proof)
|
||||
.map_err(|e| Error::<T>::Verification(e))?;
|
||||
|
||||
let receipt = DeliveryReceipt::try_from(&event.event_log)
|
||||
.map_err(|_| Error::<T>::InvalidEnvelope)?;
|
||||
|
||||
Self::process_delivery_receipt(relayer, receipt)
|
||||
}
|
||||
}
|
||||
|
||||
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 = SnowbridgeDigestItem::SnowbridgeV2(root).into();
|
||||
|
||||
// Insert merkle root into the header digest
|
||||
<frame_system::Pallet<T>>::deposit_log(digest_item);
|
||||
|
||||
T::OnNewCommitment::on_new_commitment(root);
|
||||
|
||||
Self::deposit_event(Event::MessagesCommitted { root, count });
|
||||
}
|
||||
|
||||
/// Process a message delivered by the MessageQueue pallet.
|
||||
/// IMPORTANT!! This method does not roll back storage changes on error.
|
||||
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.
|
||||
let current_len = MessageLeaves::<T>::decode_len().unwrap_or(0);
|
||||
if current_len >= T::MaxMessagesPerBlock::get() as usize {
|
||||
Self::deposit_event(Event::MessagePostponed {
|
||||
payload: message.to_vec(),
|
||||
reason: Yield,
|
||||
});
|
||||
return Err(Yield);
|
||||
}
|
||||
|
||||
// Decode bytes into Message
|
||||
let Message { origin, id, fee, commands } =
|
||||
Message::decode(&mut message).map_err(|_| {
|
||||
Self::deposit_event(Event::MessageRejected {
|
||||
id: None,
|
||||
payload: message.to_vec(),
|
||||
error: Corrupt,
|
||||
});
|
||||
Corrupt
|
||||
})?;
|
||||
|
||||
// Convert it to OutboundMessage and save into Messages storage
|
||||
let commands: Vec<OutboundCommandWrapper> = commands
|
||||
.into_iter()
|
||||
.map(|command| OutboundCommandWrapper {
|
||||
kind: command.index(),
|
||||
gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command),
|
||||
payload: command.abi_encode(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let nonce = <Nonce<T>>::get().checked_add(1).ok_or_else(|| {
|
||||
Self::deposit_event(Event::MessageRejected {
|
||||
id: None,
|
||||
payload: message.to_vec(),
|
||||
error: Unsupported,
|
||||
});
|
||||
Unsupported
|
||||
})?;
|
||||
|
||||
let outbound_message = OutboundMessage {
|
||||
origin,
|
||||
nonce,
|
||||
topic: id,
|
||||
commands: commands.clone().try_into().map_err(|_| {
|
||||
Self::deposit_event(Event::MessageRejected {
|
||||
id: Some(id),
|
||||
payload: message.to_vec(),
|
||||
error: Corrupt,
|
||||
});
|
||||
Corrupt
|
||||
})?,
|
||||
};
|
||||
Messages::<T>::append(outbound_message);
|
||||
|
||||
// Convert it to an OutboundMessageWrapper (in ABI format), hash it using Keccak256 to
|
||||
// generate a committed hash, and store it in MessageLeaves storage which can be
|
||||
// verified on Ethereum later.
|
||||
let abi_commands: Vec<CommandWrapper> = commands
|
||||
.into_iter()
|
||||
.map(|command| CommandWrapper {
|
||||
kind: command.kind,
|
||||
gas: command.gas,
|
||||
payload: Bytes::from(command.payload),
|
||||
})
|
||||
.collect();
|
||||
let committed_message = OutboundMessageWrapper {
|
||||
origin: FixedBytes::from(origin.as_fixed_bytes()),
|
||||
nonce,
|
||||
topic: FixedBytes::from(id.as_fixed_bytes()),
|
||||
commands: abi_commands,
|
||||
};
|
||||
let message_abi_encoded_hash =
|
||||
<T as Config>::Hashing::hash(&committed_message.abi_encode());
|
||||
MessageLeaves::<T>::append(message_abi_encoded_hash);
|
||||
|
||||
// Generate `PendingOrder` with fee attached in the message, stored
|
||||
// into the `PendingOrders` map storage, with assigned nonce as the key.
|
||||
// When the message is processed on ethereum side, the relayer will send the nonce
|
||||
// back with delivery proof, only after that the order can
|
||||
// be resolved and the fee will be rewarded to the relayer.
|
||||
let order = PendingOrder {
|
||||
nonce,
|
||||
fee,
|
||||
block_number: frame_system::Pallet::<T>::current_block_number(),
|
||||
};
|
||||
<PendingOrders<T>>::insert(nonce, order);
|
||||
|
||||
<Nonce<T>>::set(nonce);
|
||||
|
||||
Self::deposit_event(Event::MessageAccepted { id, nonce });
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Process a delivery receipt from a relayer, to allocate the relayer reward.
|
||||
pub fn process_delivery_receipt(
|
||||
relayer: <T as frame_system::Config>::AccountId,
|
||||
receipt: DeliveryReceipt,
|
||||
) -> DispatchResult
|
||||
where
|
||||
<T as frame_system::Config>::AccountId: From<[u8; 32]>,
|
||||
{
|
||||
// Verify that the message was submitted from the known Gateway contract
|
||||
ensure!(T::GatewayAddress::get() == receipt.gateway, Error::<T>::InvalidGateway);
|
||||
|
||||
let reward_account = if receipt.reward_address == [0u8; 32] {
|
||||
relayer
|
||||
} else {
|
||||
receipt.reward_address.into()
|
||||
};
|
||||
|
||||
let nonce = receipt.nonce;
|
||||
|
||||
let order = <PendingOrders<T>>::get(nonce).ok_or(Error::<T>::InvalidPendingNonce)?;
|
||||
|
||||
if order.fee > 0 {
|
||||
// Pay relayer reward
|
||||
T::RewardPayment::register_reward(
|
||||
&reward_account,
|
||||
T::DefaultRewardKind::get(),
|
||||
order.fee,
|
||||
);
|
||||
}
|
||||
|
||||
<PendingOrders<T>>::remove(nonce);
|
||||
|
||||
Self::deposit_event(Event::MessageDelivered { nonce });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> AddTip for Pallet<T> {
|
||||
fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError> {
|
||||
ensure!(amount > 0, AddTipError::AmountZero);
|
||||
PendingOrders::<T>::try_mutate_exists(nonce, |maybe_order| -> Result<(), AddTipError> {
|
||||
match maybe_order {
|
||||
Some(order) => {
|
||||
order.fee = order.fee.saturating_add(amount);
|
||||
Ok(())
|
||||
},
|
||||
None => Err(AddTipError::UnknownMessage),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::*;
|
||||
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{Everything, Hooks},
|
||||
weights::IdentityFee,
|
||||
BoundedVec,
|
||||
};
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use hex_literal::hex;
|
||||
use scale_info::TypeInfo;
|
||||
use snowbridge_core::{AgentId, AgentIdOf, ChannelId, ParaId};
|
||||
use snowbridge_outbound_queue_primitives::{v2::*, Log, Proof, VerificationError, Verifier};
|
||||
use snowbridge_test_utils::mock_rewards::{BridgeReward, MockRewardLedger};
|
||||
use sp_core::{ConstU32, H160, H256};
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup, Keccak256},
|
||||
AccountId32, BuildStorage,
|
||||
};
|
||||
use sp_std::marker::PhantomData;
|
||||
use xcm::prelude::Here;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
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>},
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = Everything;
|
||||
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 PalletInfo = PalletInfo;
|
||||
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 IdleMaxServiceWeight = ();
|
||||
type QueuePausedQuery = ();
|
||||
}
|
||||
|
||||
// Mock verifier
|
||||
pub struct MockVerifier;
|
||||
|
||||
impl Verifier for MockVerifier {
|
||||
fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const GATEWAY_ADDRESS: [u8; 20] = hex!["b1185ede04202fe62d38f5db72f71e38ff3e8305"];
|
||||
const WETH: [u8; 20] = hex!["C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"];
|
||||
|
||||
parameter_types! {
|
||||
pub const OwnParaId: ParaId = ParaId::new(1013);
|
||||
pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS);
|
||||
pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
|
||||
pub DefaultMyRewardKind: BridgeReward = BridgeReward::Snowbridge;
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Copy,
|
||||
MaxEncodedLen,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
TypeInfo,
|
||||
Debug,
|
||||
)]
|
||||
pub enum AggregateMessageOrigin {
|
||||
Snowbridge(ChannelId),
|
||||
SnowbridgeV2(H256),
|
||||
}
|
||||
|
||||
impl From<H256> for AggregateMessageOrigin {
|
||||
fn from(hash: H256) -> Self {
|
||||
Self::SnowbridgeV2(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Verifier = MockVerifier;
|
||||
type GatewayAddress = GatewayAddress;
|
||||
type Hashing = Keccak256;
|
||||
type MessageQueue = MessageQueue;
|
||||
type MaxMessagePayloadSize = ConstU32<1024>;
|
||||
type MaxMessagesPerBlock = ConstU32<20>;
|
||||
type GasMeter = ConstantGasMeter;
|
||||
type Balance = u128;
|
||||
type WeightToFee = IdentityFee<u128>;
|
||||
type WeightInfo = ();
|
||||
type RewardPayment = MockRewardLedger;
|
||||
type EthereumNetwork = EthereumNetwork;
|
||||
type RewardKind = BridgeReward;
|
||||
type DefaultRewardKind = DefaultMyRewardKind;
|
||||
type OnNewCommitment = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = Test;
|
||||
type AggregateMessageOrigin = AggregateMessageOrigin;
|
||||
}
|
||||
|
||||
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 bridge_hub_root_origin() -> AgentId {
|
||||
AgentIdOf::convert_location(&Here.into()).unwrap()
|
||||
}
|
||||
|
||||
pub fn mock_governance_message<T>() -> Message
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
let _marker = PhantomData::<T>; // for clippy
|
||||
|
||||
Message {
|
||||
origin: bridge_hub_root_origin(),
|
||||
id: Default::default(),
|
||||
fee: 0,
|
||||
commands: BoundedVec::try_from(vec![Command::Upgrade {
|
||||
impl_address: Default::default(),
|
||||
impl_code_hash: Default::default(),
|
||||
initializer: Initializer {
|
||||
params: (0..512).map(|_| 1u8).collect::<Vec<u8>>(),
|
||||
maximum_required_gas: 0,
|
||||
},
|
||||
}])
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
origin: Default::default(),
|
||||
id: Default::default(),
|
||||
fee: 0,
|
||||
commands: BoundedVec::try_from(vec![Command::Upgrade {
|
||||
impl_address: H160::zero(),
|
||||
impl_code_hash: H256::zero(),
|
||||
initializer: Initializer {
|
||||
params: (0..1000).map(|_| 1u8).collect::<Vec<u8>>(),
|
||||
maximum_required_gas: 0,
|
||||
},
|
||||
}])
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_message(sibling_para_id: u32) -> Message {
|
||||
Message {
|
||||
origin: H256::from_low_u64_be(sibling_para_id as u64),
|
||||
id: H256::from_low_u64_be(1),
|
||||
fee: 1_000,
|
||||
commands: BoundedVec::try_from(vec![Command::UnlockNativeToken {
|
||||
token: H160(WETH),
|
||||
recipient: H160(GATEWAY_ADDRESS),
|
||||
amount: 1_000_000,
|
||||
}])
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_register_token_message(sibling_para_id: u32) -> Message {
|
||||
Message {
|
||||
origin: H256::from_low_u64_be(sibling_para_id as u64),
|
||||
id: H256::from_low_u64_be(1),
|
||||
fee: 1_000,
|
||||
commands: BoundedVec::try_from(vec![Command::RegisterForeignToken {
|
||||
token_id: H256::from_low_u64_be(1),
|
||||
name: vec![],
|
||||
symbol: vec![],
|
||||
decimals: 12,
|
||||
}])
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<T: Config> BenchmarkHelper<T> for Test {
|
||||
// not implemented since the MockVerifier is used for tests
|
||||
fn initialize_storage(_: BeaconHeader, _: H256) {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 = T::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() {
|
||||
Self::deposit_event(Event::MessagePostponed {
|
||||
payload: message.to_vec(),
|
||||
reason: ProcessMessageError::Overweight(weight),
|
||||
});
|
||||
return Err(ProcessMessageError::Overweight(weight));
|
||||
}
|
||||
Self::do_process_message(origin, message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Implementation for [`snowbridge_outbound_queue_primitives::v2::SendMessage`]
|
||||
use super::*;
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{EnqueueMessage, Get},
|
||||
};
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v2::{Message, SendMessage},
|
||||
SendError,
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::BoundedVec;
|
||||
|
||||
impl<T> SendMessage for Pallet<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
type Ticket = Message;
|
||||
|
||||
fn validate(message: &Message) -> Result<Self::Ticket, SendError> {
|
||||
// The inner payload should not be too large
|
||||
let payload = message.encode();
|
||||
ensure!(
|
||||
payload.len() < T::MaxMessagePayloadSize::get() as usize,
|
||||
SendError::MessageTooLarge
|
||||
);
|
||||
|
||||
Ok(message.clone())
|
||||
}
|
||||
|
||||
fn deliver(ticket: Self::Ticket) -> Result<H256, SendError> {
|
||||
let origin = ticket.origin.into();
|
||||
|
||||
let message =
|
||||
BoundedVec::try_from(ticket.encode()).map_err(|_| SendError::MessageTooLarge)?;
|
||||
|
||||
T::MessageQueue::enqueue_message(message.as_bounded_slice(), origin);
|
||||
Self::deposit_event(Event::MessageQueued { message: ticket.clone() });
|
||||
Ok(ticket.id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use crate::{
|
||||
mock::{AggregateMessageOrigin::*, *},
|
||||
*,
|
||||
};
|
||||
use alloy_core::primitives::FixedBytes;
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
assert_err, assert_noop, assert_ok,
|
||||
traits::{Hooks, ProcessMessage, ProcessMessageError, QueueFootprintQuery},
|
||||
weights::WeightMeter,
|
||||
BoundedVec,
|
||||
};
|
||||
use hex_literal::hex;
|
||||
use snowbridge_core::{digest_item::SnowbridgeDigestItem, ChannelId, ParaId};
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v2::{abi::OutboundMessageWrapper, Command, Initializer, SendMessage},
|
||||
SendError,
|
||||
};
|
||||
use sp_core::{hexdisplay::HexDisplay, H256};
|
||||
|
||||
#[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();
|
||||
|
||||
assert_eq!(Nonce::<Test>::get(), 4);
|
||||
|
||||
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 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::SnowbridgeV2(H256::zero());
|
||||
let message = Message {
|
||||
origin: Default::default(),
|
||||
id: Default::default(),
|
||||
fee: 0,
|
||||
commands: BoundedVec::try_from(vec![Command::Upgrade {
|
||||
impl_address: Default::default(),
|
||||
impl_code_hash: Default::default(),
|
||||
initializer: Initializer {
|
||||
params: (0..512).map(|_| 1u8).collect::<Vec<u8>>(),
|
||||
maximum_required_gas: 0,
|
||||
},
|
||||
}])
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut meter = WeightMeter::new();
|
||||
|
||||
assert_err!(
|
||||
OutboundQueue::process_message(
|
||||
message.encode().as_slice(),
|
||||
origin,
|
||||
&mut meter,
|
||||
&mut [0u8; 32]
|
||||
),
|
||||
ProcessMessageError::Yield
|
||||
);
|
||||
let events = System::events();
|
||||
let last_event = events.last().expect("Expected at least one event").event.clone();
|
||||
|
||||
match last_event {
|
||||
mock::RuntimeEvent::OutboundQueue(Event::MessagePostponed {
|
||||
payload: _,
|
||||
reason: ProcessMessageError::Yield,
|
||||
}) => {},
|
||||
_ => {
|
||||
panic!("Expected Event::MessagePostponed(Yield) but got {:?}", last_event);
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[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::SnowbridgeV2(H256::zero());
|
||||
let message: Message = mock_message(sibling_id);
|
||||
|
||||
let mut meter = WeightMeter::with_limit(Weight::MAX);
|
||||
|
||||
Nonce::<Test>::set(u64::MAX);
|
||||
|
||||
let result = OutboundQueue::process_message(
|
||||
message.encode().as_slice(),
|
||||
origin,
|
||||
&mut meter,
|
||||
&mut [0u8; 32],
|
||||
);
|
||||
assert_err!(result, ProcessMessageError::Unsupported);
|
||||
|
||||
let events = System::events();
|
||||
let last_event = events.last().expect("Expected at least one event").event.clone();
|
||||
|
||||
match last_event {
|
||||
mock::RuntimeEvent::OutboundQueue(Event::MessageRejected {
|
||||
id: None,
|
||||
payload: _,
|
||||
error: ProcessMessageError::Unsupported,
|
||||
}) => {},
|
||||
_ => {
|
||||
panic!("Expected Event::MessageRejected(Unsupported) but got {:?}", last_event);
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[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::SnowbridgeV2(H256::zero());
|
||||
let message: Message = mock_message(sibling_id);
|
||||
let mut meter = WeightMeter::with_limit(Weight::from_parts(1, 1));
|
||||
assert_err!(
|
||||
OutboundQueue::process_message(
|
||||
message.encode().as_slice(),
|
||||
origin,
|
||||
&mut meter,
|
||||
&mut [0u8; 32]
|
||||
),
|
||||
ProcessMessageError::Overweight(<Test as Config>::WeightInfo::do_process_message())
|
||||
);
|
||||
let events = System::events();
|
||||
let last_event = events.last().expect("Expected at least one event").event.clone();
|
||||
|
||||
match last_event {
|
||||
mock::RuntimeEvent::OutboundQueue(Event::MessagePostponed {
|
||||
payload: _,
|
||||
reason: ProcessMessageError::Overweight(_),
|
||||
}) => {},
|
||||
_ => {
|
||||
panic!("Expected Event::MessagePostponed(Overweight(_)) but got {:?}", last_event);
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn governance_message_not_processed_in_same_block_when_queue_congested_with_low_priority_messages()
|
||||
{
|
||||
let sibling_id: u32 = 1000;
|
||||
|
||||
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(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
|
||||
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(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
|
||||
assert_eq!(footprint.storage.count, 40 - 20);
|
||||
|
||||
// and governance message does not have the chance to execute in same block
|
||||
let footprint = MessageQueue::footprint(SnowbridgeV2(bridge_hub_root_origin()));
|
||||
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(SnowbridgeV2(bridge_hub_root_origin()));
|
||||
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(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
|
||||
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(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64)));
|
||||
assert_eq!(footprint.storage.count, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_digest_item_with_correct_index() {
|
||||
new_tester().execute_with(|| {
|
||||
let digest_item: DigestItem = SnowbridgeDigestItem::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 = SnowbridgeDigestItem::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); // SnowbridgeDigestItem::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
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn encode_mock_message(message: Message) -> Vec<u8> {
|
||||
let commands: Vec<CommandWrapper> = message
|
||||
.commands
|
||||
.into_iter()
|
||||
.map(|command| CommandWrapper {
|
||||
kind: command.index(),
|
||||
gas: <Test as Config>::GasMeter::maximum_dispatch_gas_used_at_most(&command),
|
||||
payload: Bytes::from(command.abi_encode()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// print the abi-encoded message and decode with solidity test
|
||||
let committed_message = OutboundMessageWrapper {
|
||||
origin: FixedBytes::from(message.origin.as_fixed_bytes()),
|
||||
nonce: 1,
|
||||
topic: FixedBytes::from(message.id.as_fixed_bytes()),
|
||||
commands,
|
||||
};
|
||||
let message_abi_encoded = committed_message.abi_encode();
|
||||
message_abi_encoded
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_unlock_message() {
|
||||
let message: Message = mock_message(1000);
|
||||
let message_abi_encoded = encode_mock_message(message);
|
||||
println!("{}", HexDisplay::from(&message_abi_encoded));
|
||||
assert_eq!(hex!("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b1185ede04202fe62d38f5db72f71e38ff3e830500000000000000000000000000000000000000000000000000000000000f4240").to_vec(), message_abi_encoded)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_register_pna() {
|
||||
let message: Message = mock_register_token_message(1000);
|
||||
let message_abi_encoded = encode_mock_message(message);
|
||||
println!("{}", HexDisplay::from(&message_abi_encoded));
|
||||
assert_eq!(hex!("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000124f80000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec(), message_abi_encoded)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_tip_cumulative() {
|
||||
new_tester().execute_with(|| {
|
||||
let nonce = 1;
|
||||
let initial_fee = 1000;
|
||||
let additional_fee = 500;
|
||||
let current_block = System::block_number();
|
||||
let order = PendingOrder { nonce, fee: initial_fee, block_number: current_block };
|
||||
PendingOrders::<Test>::insert(nonce, order);
|
||||
assert_ok!(OutboundQueue::add_tip(nonce, additional_fee));
|
||||
let order_after = PendingOrders::<Test>::get(nonce).unwrap();
|
||||
assert_eq!(order_after.fee, initial_fee + additional_fee);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_tip_fails_no_pending_order() {
|
||||
new_tester().execute_with(|| {
|
||||
let nonce = 42;
|
||||
let amount = 1000;
|
||||
assert_noop!(OutboundQueue::add_tip(nonce, amount), AddTipError::UnknownMessage);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_tip_fails_amount_zero() {
|
||||
new_tester().execute_with(|| {
|
||||
let nonce = 1;
|
||||
let initial_fee = 1000;
|
||||
let zero_amount = 0;
|
||||
let current_block = System::block_number();
|
||||
let order = PendingOrder { nonce, fee: initial_fee, block_number: current_block };
|
||||
PendingOrders::<Test>::insert(nonce, order);
|
||||
|
||||
assert_noop!(OutboundQueue::add_tip(nonce, zero_amount), AddTipError::AmountZero);
|
||||
|
||||
// Verify the original fee is unchanged
|
||||
let order_after = PendingOrders::<Test>::get(nonce).unwrap();
|
||||
assert_eq!(order_after.fee, initial_fee);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use super::Pallet;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::traits::ProcessMessage;
|
||||
use scale_info::TypeInfo;
|
||||
pub use snowbridge_merkle_tree::MerkleProof;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub type ProcessMessageOriginOf<T> = <Pallet<T> as ProcessMessage>::Origin;
|
||||
|
||||
/// Pending order
|
||||
#[derive(Encode, Decode, TypeInfo, Clone, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)]
|
||||
pub struct PendingOrder<BlockNumber> {
|
||||
/// The nonce used to identify the message
|
||||
pub nonce: u64,
|
||||
/// The block number in which the message was committed
|
||||
pub block_number: BlockNumber,
|
||||
/// The fee in Ether provided by the user to incentivize message delivery
|
||||
#[codec(compact)]
|
||||
pub fee: u128,
|
||||
}
|
||||
|
||||
/// Hook that will be called when a new message commitment is constructed.
|
||||
pub trait OnNewCommitment {
|
||||
fn on_new_commitment(commitment: H256);
|
||||
}
|
||||
|
||||
impl OnNewCommitment for () {
|
||||
fn on_new_commitment(_commitment: H256) {}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
//! 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-pezkuwichain-dev")`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// target/release/pezkuwi-teyrchain
|
||||
// benchmark
|
||||
// pallet
|
||||
// --chain=bridge-hub-pezkuwichain-dev
|
||||
// --pallet=snowbridge-pallet-outbound-queue
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --template
|
||||
// ../teyrchain/templates/module-weight-template.hbs
|
||||
// --output
|
||||
// ../teyrchain/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;
|
||||
fn submit_delivery_receipt() -> Weight;
|
||||
fn on_initialize() -> Weight;
|
||||
fn process() -> 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))
|
||||
}
|
||||
|
||||
fn submit_delivery_receipt() -> Weight {
|
||||
Weight::from_parts(70_000_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3601))
|
||||
.saturating_add(RocksDbWeight::get().reads(2))
|
||||
.saturating_add(RocksDbWeight::get().writes(2))
|
||||
}
|
||||
|
||||
fn on_initialize() -> Weight {
|
||||
Weight::from_parts(5_000_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().reads(2))
|
||||
.saturating_add(RocksDbWeight::get().writes(5))
|
||||
}
|
||||
|
||||
fn process() -> Weight {
|
||||
Weight::from_parts(506_000_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1493))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(35))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user