mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-23 17:28:23 +00:00
feat: Add Snowbridge ethereum system v2 pallet (#57)
This PR introduces the Snowbridge `system-v2` pallet and associated runtime components **Key Changes:** * **Added `system-v2` Pallet:** Integrated the `snowbridge-pallet-system-v2` pallet, providing functionalities for the Ethereum side of the bridge. * **Runtime API Integration:** * Implemented the `ControlV2Api` trait in the runtime (`operator/runtime/src/apis.rs`) to allow looking up the `AgentId` associated with a `VersionedLocation`. * **System V1 Compatibility:** Added the `system-v1` pallet (`snowbridge-pallet-system`) and related configuration/code references in various locations. **Important:** This `system-v1` is included *solely* because the `system-v2` pallet requires it for compilation and compatibility. It is **not functionally used** in this runtime. --------- Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
This commit is contained in:
parent
6c8c91b736
commit
ca9eb0f813
33 changed files with 3656 additions and 221 deletions
594
operator/Cargo.lock
generated
594
operator/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,8 @@ members = [
|
|||
"pallets/ethereum-client",
|
||||
"pallets/inbound-queue-v2",
|
||||
"pallets/outbound-queue-v2",
|
||||
"pallets/system",
|
||||
"pallets/system-v2",
|
||||
"pallets/validator-set",
|
||||
"primitives/bridge",
|
||||
"runtime",
|
||||
|
|
@ -18,6 +20,8 @@ members = [
|
|||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.lints]
|
||||
|
||||
[workspace.dependencies]
|
||||
# Local
|
||||
datahaven-runtime = { path = "./runtime", default-features = false }
|
||||
|
|
@ -156,18 +160,22 @@ xcm-executor = { git = "https://github.com/paritytech/polkadot-sdk", branch = "s
|
|||
# Snowbridge
|
||||
bp-relayers = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
|
||||
bridge-hub-common = { path = "primitives/snowbridge/bridge-hub-common", default-features = false }
|
||||
snowbridge-merkle-tree = { path = "primitives/snowbridge/merkle-tree", default-features = false }
|
||||
snowbridge-pallet-system = { path = "pallets/system", default-features = false }
|
||||
snowbridge-beacon-primitives = { path = "primitives/snowbridge/beacon", default-features = false }
|
||||
snowbridge-core = { path = "primitives/snowbridge/core", default-features = false }
|
||||
snowbridge-ethereum = { path = "primitives/snowbridge/ethereum", default-features = false }
|
||||
snowbridge-inbound-queue-primitives = { path = "primitives/snowbridge/inbound-queue", default-features = false }
|
||||
snowbridge-merkle-tree = { path = "primitives/snowbridge/merkle-tree", default-features = false }
|
||||
snowbridge-outbound-queue-primitives = { path = "primitives/snowbridge/outbound-queue", default-features = false }
|
||||
snowbridge-outbound-queue-v2-runtime-api = { path = "pallets/outbound-queue-v2/runtime-api", default-features = false }
|
||||
snowbridge-pallet-ethereum-client = { path = "pallets/ethereum-client", default-features = false }
|
||||
snowbridge-pallet-ethereum-client-fixtures = { path = "pallets/ethereum-client/fixtures", default-features = false }
|
||||
snowbridge-pallet-outbound-queue = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2412", default-features = false }
|
||||
snowbridge-pallet-inbound-queue-v2 = { path = "pallets/inbound-queue-v2", default-features = false }
|
||||
snowbridge-pallet-inbound-queue-v2-fixtures = { path = "pallets/inbound-queue-v2/fixtures", default-features = false }
|
||||
snowbridge-pallet-outbound-queue-v2 = { path = "pallets/outbound-queue-v2", default-features = false }
|
||||
snowbridge-pallet-system-v2 = { path = "pallets/system-v2", default-features = false }
|
||||
snowbridge-system-v2-runtime-api = { path = "pallets/system-v2/runtime-api", default-features = false }
|
||||
snowbridge-test-utils = { path = "primitives/snowbridge/test-utils", default-features = false }
|
||||
snowbridge-verification-primitives = { path = "primitives/snowbridge/verification", default-features = false }
|
||||
|
||||
|
|
|
|||
85
operator/pallets/system-v2/Cargo.toml
Normal file
85
operator/pallets/system-v2/Cargo.toml
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
[package]
|
||||
name = "snowbridge-pallet-system-v2"
|
||||
description = "Snowbridge System Pallet V2"
|
||||
version = "0.2.0"
|
||||
authors = ["Snowfork <contact@snowfork.com>"]
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license = "Apache-2.0"
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[package.metadata.polkadot-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
frame-benchmarking = { optional = true, workspace = true }
|
||||
frame-support.workspace = true
|
||||
frame-system.workspace = true
|
||||
log = { workspace = true }
|
||||
pallet-xcm.workspace = true
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
snowbridge-core.workspace = true
|
||||
snowbridge-outbound-queue-primitives.workspace = true
|
||||
snowbridge-pallet-system.workspace = true
|
||||
sp-core.workspace = true
|
||||
sp-io.workspace = true
|
||||
sp-runtime.workspace = true
|
||||
sp-std.workspace = true
|
||||
xcm-executor.workspace = true
|
||||
xcm.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
pallet-balances = { default-features = true, workspace = true }
|
||||
polkadot-primitives = { default-features = true, workspace = true }
|
||||
snowbridge-pallet-outbound-queue-v2 = { default-features = true, workspace = true }
|
||||
snowbridge-test-utils = { workspace = true }
|
||||
sp-keyring = { default-features = true, workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking?/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"pallet-xcm/std",
|
||||
"scale-info/std",
|
||||
"snowbridge-core/std",
|
||||
"snowbridge-outbound-queue-primitives/std",
|
||||
"snowbridge-pallet-system/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"xcm-executor/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"pallet-xcm/runtime-benchmarks",
|
||||
"polkadot-primitives/runtime-benchmarks",
|
||||
"snowbridge-core/runtime-benchmarks",
|
||||
"snowbridge-pallet-outbound-queue-v2/runtime-benchmarks",
|
||||
"snowbridge-pallet-system/runtime-benchmarks",
|
||||
"snowbridge-test-utils/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"xcm-executor/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-xcm/try-runtime",
|
||||
"snowbridge-pallet-outbound-queue-v2/try-runtime",
|
||||
"snowbridge-pallet-system/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
4
operator/pallets/system-v2/README.md
Normal file
4
operator/pallets/system-v2/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Ethereum System V2
|
||||
|
||||
This pallet is part of BridgeHub. Certain extrinsics in this pallet (like `register_token` and `add_tip`) will be called
|
||||
from the System Frontend pallet on AssetHub.
|
||||
37
operator/pallets/system-v2/runtime-api/Cargo.toml
Normal file
37
operator/pallets/system-v2/runtime-api/Cargo.toml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "snowbridge-system-v2-runtime-api"
|
||||
description = "Snowbridge System 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.polkadot-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = [
|
||||
"derive",
|
||||
], workspace = true }
|
||||
snowbridge-core.workspace = true
|
||||
sp-api.workspace = true
|
||||
sp-std.workspace = true
|
||||
xcm.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"snowbridge-core/std",
|
||||
"sp-api/std",
|
||||
"sp-std/std",
|
||||
"xcm/std",
|
||||
]
|
||||
4
operator/pallets/system-v2/runtime-api/README.md
Normal file
4
operator/pallets/system-v2/runtime-api/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Ethereum System Runtime API V2
|
||||
|
||||
Provides an API for looking up an agent ID on Ethereum. An agent ID is a unique mapping to an Agent contract on Ethereum
|
||||
which acts as the sovereign account for the Location.
|
||||
14
operator/pallets/system-v2/runtime-api/src/lib.rs
Normal file
14
operator/pallets/system-v2/runtime-api/src/lib.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use snowbridge_core::AgentId;
|
||||
use xcm::VersionedLocation;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
pub trait ControlV2Api
|
||||
{
|
||||
/// Provides the Agent ID on Ethereum for the specified location.
|
||||
fn agent_id(location: VersionedLocation) -> Option<AgentId>;
|
||||
}
|
||||
}
|
||||
15
operator/pallets/system-v2/src/api.rs
Normal file
15
operator/pallets/system-v2/src/api.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Helpers for implementing runtime api
|
||||
|
||||
use crate::Config;
|
||||
use sp_core::H256;
|
||||
use xcm::{prelude::*, VersionedLocation};
|
||||
|
||||
pub fn agent_id<Runtime>(location: VersionedLocation) -> Option<H256>
|
||||
where
|
||||
Runtime: Config,
|
||||
{
|
||||
let location: Location = location.try_into().ok()?;
|
||||
crate::Pallet::<Runtime>::location_to_message_origin(location).ok()
|
||||
}
|
||||
39
operator/pallets/system-v2/src/benchmarking.rs
Normal file
39
operator/pallets/system-v2/src/benchmarking.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Benchmarking setup for pallet-template
|
||||
use super::*;
|
||||
|
||||
#[allow(unused)]
|
||||
use crate::Pallet as SnowbridgeControl;
|
||||
use frame_benchmarking::v2::*;
|
||||
use xcm::prelude::*;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn register_token() -> Result<(), BenchmarkError> {
|
||||
let origin_location = Location::new(1, [Parachain(1000), PalletInstance(36)]);
|
||||
let origin = <T as Config>::Helper::make_xcm_origin(origin_location.clone());
|
||||
let creator = Box::new(VersionedLocation::from(origin_location.clone()));
|
||||
let relay_token_asset_id: Location = Location::parent();
|
||||
let asset = Box::new(VersionedLocation::from(relay_token_asset_id));
|
||||
let asset_metadata = AssetMetadata {
|
||||
name: "wnd".as_bytes().to_vec().try_into().unwrap(),
|
||||
symbol: "wnd".as_bytes().to_vec().try_into().unwrap(),
|
||||
decimals: 12,
|
||||
};
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, creator, asset, asset_metadata);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
SnowbridgeControl,
|
||||
crate::mock::new_test_ext(true),
|
||||
crate::mock::Test
|
||||
);
|
||||
}
|
||||
278
operator/pallets/system-v2/src/lib.rs
Normal file
278
operator/pallets/system-v2/src/lib.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Governance API for controlling the Ethereum side of the bridge
|
||||
//!
|
||||
//! # Extrinsics
|
||||
//!
|
||||
//! ## Governance
|
||||
//!
|
||||
//! * [`Call::upgrade`]: Upgrade the Gateway contract on Ethereum.
|
||||
//! * [`Call::set_operating_mode`]: Set the operating mode of the Gateway contract
|
||||
//!
|
||||
//! ## Polkadot-native tokens on Ethereum
|
||||
//!
|
||||
//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a
|
||||
//! prerequisite, the token should be registered first.
|
||||
//!
|
||||
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
|
||||
pub mod api;
|
||||
pub mod weights;
|
||||
pub use weights::*;
|
||||
|
||||
use frame_support::{pallet_prelude::*, traits::EnsureOrigin};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use snowbridge_core::{AgentIdOf as LocationHashOf, AssetMetadata, TokenId, TokenIdOf};
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v2::{Command, Initializer, Message, SendMessage},
|
||||
OperatingMode, SendError,
|
||||
};
|
||||
use snowbridge_pallet_system::{ForeignToNativeId, NativeToForeignId};
|
||||
use sp_core::{H160, H256};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::traits::MaybeEquivalence;
|
||||
use sp_std::prelude::*;
|
||||
use xcm::prelude::*;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use frame_support::traits::OriginTrait;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub trait BenchmarkHelper<O>
|
||||
where
|
||||
O: OriginTrait,
|
||||
{
|
||||
fn make_xcm_origin(location: Location) -> O;
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + snowbridge_pallet_system::Config {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Send messages to Ethereum
|
||||
type OutboundQueue: SendMessage;
|
||||
|
||||
/// Origin check for XCM locations that transact with this pallet
|
||||
type FrontendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
|
||||
|
||||
/// Origin for governance calls
|
||||
type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// An Upgrade message was sent to the Gateway
|
||||
Upgrade {
|
||||
impl_address: H160,
|
||||
impl_code_hash: H256,
|
||||
initializer_params_hash: H256,
|
||||
},
|
||||
/// An SetOperatingMode message was sent to the Gateway
|
||||
SetOperatingMode { mode: OperatingMode },
|
||||
/// Register Polkadot-native token as a wrapped ERC20 token on Ethereum
|
||||
RegisterToken {
|
||||
/// Location of Polkadot-native token
|
||||
location: VersionedLocation,
|
||||
/// ID of Polkadot-native token on Ethereum
|
||||
foreign_token_id: H256,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Location could not be reachored
|
||||
LocationReanchorFailed,
|
||||
/// A token location could not be converted to a token ID.
|
||||
LocationConversionFailed,
|
||||
/// A `VersionedLocation` could not be converted into a `Location`.
|
||||
UnsupportedLocationVersion,
|
||||
/// An XCM could not be sent, due to a `SendError`.
|
||||
Send(SendError),
|
||||
/// The gateway contract upgrade message could not be sent due to invalid upgrade
|
||||
/// parameters.
|
||||
InvalidUpgradeParameters,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Sends command to the Gateway contract to upgrade itself with a new implementation
|
||||
/// contract
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be `Root`.
|
||||
/// - `impl_address`: The address of the implementation contract.
|
||||
/// - `impl_code_hash`: The codehash of the implementation contract.
|
||||
/// - `initializer`: Optionally call an initializer on the implementation contract.
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight((<T as pallet::Config>::WeightInfo::upgrade(), DispatchClass::Operational))]
|
||||
pub fn upgrade(
|
||||
origin: OriginFor<T>,
|
||||
impl_address: H160,
|
||||
impl_code_hash: H256,
|
||||
initializer: Initializer,
|
||||
) -> DispatchResult {
|
||||
let origin_location = T::GovernanceOrigin::ensure_origin(origin)?;
|
||||
let origin = Self::location_to_message_origin(origin_location)?;
|
||||
|
||||
ensure!(
|
||||
!impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
|
||||
Error::<T>::InvalidUpgradeParameters
|
||||
);
|
||||
|
||||
let initializer_params_hash: H256 = blake2_256(initializer.params.as_ref()).into();
|
||||
|
||||
let command = Command::Upgrade {
|
||||
impl_address,
|
||||
impl_code_hash,
|
||||
initializer,
|
||||
};
|
||||
Self::send(origin, command, 0)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::Upgrade {
|
||||
impl_address,
|
||||
impl_code_hash,
|
||||
initializer_params_hash,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a message to the Gateway contract to change its operating mode
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be `GovernanceOrigin`
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight((<T as pallet::Config>::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
|
||||
let origin_location = T::GovernanceOrigin::ensure_origin(origin)?;
|
||||
let origin = Self::location_to_message_origin(origin_location)?;
|
||||
|
||||
let command = Command::SetOperatingMode { mode };
|
||||
Self::send(origin, command, 0)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::SetOperatingMode { mode });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum.
|
||||
///
|
||||
/// The system frontend pallet on AH proxies this call to BH.
|
||||
///
|
||||
/// - `sender`: The original sender initiating the call on AH
|
||||
/// - `asset_id`: Location of the asset (relative to this chain)
|
||||
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
|
||||
/// - `fee`: Ether to pay for the execution cost on Ethereum
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as pallet::Config>::WeightInfo::register_token())]
|
||||
pub fn register_token(
|
||||
origin: OriginFor<T>,
|
||||
sender: Box<VersionedLocation>,
|
||||
asset_id: Box<VersionedLocation>,
|
||||
metadata: AssetMetadata,
|
||||
) -> DispatchResult {
|
||||
T::FrontendOrigin::ensure_origin(origin)?;
|
||||
|
||||
let sender_location: Location = (*sender)
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
|
||||
let asset_location: Location = (*asset_id)
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
|
||||
|
||||
let location = Self::reanchor(asset_location)?;
|
||||
let token_id = TokenIdOf::convert_location(&location)
|
||||
.ok_or(Error::<T>::LocationConversionFailed)?;
|
||||
|
||||
if !ForeignToNativeId::<T>::contains_key(token_id) {
|
||||
NativeToForeignId::<T>::insert(location.clone(), token_id);
|
||||
ForeignToNativeId::<T>::insert(token_id, location.clone());
|
||||
}
|
||||
|
||||
let command = Command::RegisterForeignToken {
|
||||
token_id,
|
||||
name: metadata.name.into_inner(),
|
||||
symbol: metadata.symbol.into_inner(),
|
||||
decimals: metadata.decimals,
|
||||
};
|
||||
|
||||
let message_origin = Self::location_to_message_origin(sender_location)?;
|
||||
Self::send(message_origin, command, 0)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::RegisterToken {
|
||||
location: location.into(),
|
||||
foreign_token_id: token_id,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Send `command` to the Gateway from a specific origin/agent
|
||||
fn send(origin: H256, command: Command, fee: u128) -> DispatchResult {
|
||||
let mut message = Message {
|
||||
origin,
|
||||
id: Default::default(),
|
||||
fee,
|
||||
commands: BoundedVec::try_from(vec![command]).unwrap(),
|
||||
};
|
||||
let hash = sp_io::hashing::blake2_256(&message.encode());
|
||||
message.id = hash.into();
|
||||
|
||||
let ticket = <T as pallet::Config>::OutboundQueue::validate(&message)
|
||||
.map_err(|err| Error::<T>::Send(err))?;
|
||||
|
||||
<T as pallet::Config>::OutboundQueue::deliver(ticket)
|
||||
.map_err(|err| Error::<T>::Send(err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reanchor the `location` in context of ethereum
|
||||
pub fn reanchor(location: Location) -> Result<Location, Error<T>> {
|
||||
location
|
||||
.reanchored(&T::EthereumLocation::get(), &T::UniversalLocation::get())
|
||||
.map_err(|_| Error::<T>::LocationReanchorFailed)
|
||||
}
|
||||
|
||||
pub fn location_to_message_origin(location: Location) -> Result<H256, Error<T>> {
|
||||
let reanchored_location = Self::reanchor(location)?;
|
||||
LocationHashOf::convert_location(&reanchored_location)
|
||||
.ok_or(Error::<T>::LocationConversionFailed)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
|
||||
fn convert(foreign_id: &TokenId) -> Option<Location> {
|
||||
ForeignToNativeId::<T>::get(foreign_id)
|
||||
}
|
||||
fn convert_back(location: &Location) -> Option<TokenId> {
|
||||
NativeToForeignId::<T>::get(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
170
operator/pallets/system-v2/src/mock.rs
Normal file
170
operator/pallets/system-v2/src/mock.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{tokens::fungible::Mutate, ConstU128, Contains},
|
||||
PalletId,
|
||||
};
|
||||
use sp_core::H256;
|
||||
|
||||
use crate as snowbridge_system_v2;
|
||||
use frame_system::EnsureRootWithSuccess;
|
||||
use snowbridge_core::{
|
||||
gwei, meth, sibling_sovereign_account, AllowSiblingsOnly, ParaId, PricingParameters, Rewards,
|
||||
};
|
||||
|
||||
pub use snowbridge_test_utils::{mock_origin::pallet_xcm_origin, mock_outbound_queue::*};
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, BlakeTwo256, IdentityLookup},
|
||||
AccountId32, BuildStorage, FixedU128,
|
||||
};
|
||||
use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*};
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use crate::BenchmarkHelper;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
type Balance = u128;
|
||||
|
||||
pub type AccountId = AccountId32;
|
||||
|
||||
// Configure a mock runtime to test the pallet.
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
XcmOrigin: pallet_xcm_origin::{Pallet, Origin},
|
||||
EthereumSystem: snowbridge_pallet_system,
|
||||
EthereumSystemV2: snowbridge_system_v2,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::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 AccountData = pallet_balances::AccountData<u128>;
|
||||
type Nonce = u64;
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Test {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pallet_xcm_origin::Config for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const AnyNetwork: Option<NetworkId> = None;
|
||||
pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH));
|
||||
pub const RelayLocation: Location = Location::parent();
|
||||
pub UniversalLocation: InteriorLocation =
|
||||
[GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)].into();
|
||||
pub EthereumNetwork: NetworkId = Ethereum { chain_id: 11155111 };
|
||||
pub EthereumDestination: Location = Location::new(2,[GlobalConsensus(EthereumNetwork::get())]);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const InitialFunding: u128 = 1_000_000_000_000;
|
||||
pub BridgeHubParaId: ParaId = ParaId::new(1002);
|
||||
pub AssetHubParaId: ParaId = ParaId::new(1000);
|
||||
pub TestParaId: u32 = 2000;
|
||||
pub RootLocation: Location = Location::parent();
|
||||
pub FrontendLocation: Location = Location::new(1, [Parachain(1000), PalletInstance(36)]);
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl BenchmarkHelper<RuntimeOrigin> for () {
|
||||
fn make_xcm_origin(location: Location) -> RuntimeOrigin {
|
||||
RuntimeOrigin::from(pallet_xcm_origin::Origin(location))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllowFromAssetHub;
|
||||
impl Contains<Location> for AllowFromAssetHub {
|
||||
fn contains(location: &Location) -> bool {
|
||||
FrontendLocation::get() == *location
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = MockOkOutboundQueue;
|
||||
type FrontendOrigin = pallet_xcm_origin::EnsureXcm<AllowFromAssetHub>;
|
||||
type GovernanceOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub TreasuryAccount: AccountId = PalletId(*b"py/trsry").into_account_truncating();
|
||||
pub Parameters: PricingParameters<u128> = PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 400),
|
||||
fee_per_gas: gwei(20),
|
||||
rewards: Rewards { local: 10_000_000_000, remote: meth(1) },
|
||||
multiplier: FixedU128::from_rational(4, 3)
|
||||
};
|
||||
pub const InboundDeliveryCost: u128 = 1_000_000_000;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl snowbridge_pallet_system::BenchmarkHelper<RuntimeOrigin> for () {
|
||||
fn make_xcm_origin(location: Location) -> RuntimeOrigin {
|
||||
RuntimeOrigin::from(pallet_xcm_origin::Origin(location))
|
||||
}
|
||||
}
|
||||
|
||||
impl snowbridge_pallet_system::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = MockOkOutboundQueueV1;
|
||||
type SiblingOrigin = pallet_xcm_origin::EnsureXcm<AllowSiblingsOnly>;
|
||||
type AgentIdOf = snowbridge_core::AgentIdOf;
|
||||
type Token = Balances;
|
||||
type TreasuryAccount = TreasuryAccount;
|
||||
type DefaultPricingParameters = Parameters;
|
||||
type InboundDeliveryCost = InboundDeliveryCost;
|
||||
type WeightInfo = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type EthereumLocation = EthereumDestination;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = ();
|
||||
}
|
||||
|
||||
// Build genesis storage according to the mock runtime.
|
||||
pub fn new_test_ext(_genesis_build: bool) -> sp_io::TestExternalities {
|
||||
let storage = frame_system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
let mut ext: sp_io::TestExternalities = storage.into();
|
||||
let initial_amount = InitialFunding::get();
|
||||
let test_para_id = TestParaId::get();
|
||||
let sovereign_account = sibling_sovereign_account::<Test>(test_para_id.into());
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
Balances::mint_into(&AccountId32::from([0; 32]), initial_amount).unwrap();
|
||||
Balances::mint_into(&sovereign_account, initial_amount).unwrap();
|
||||
});
|
||||
ext
|
||||
}
|
||||
|
||||
// Test helpers
|
||||
|
||||
pub fn make_xcm_origin(location: Location) -> RuntimeOrigin {
|
||||
pallet_xcm_origin::Origin(location).into()
|
||||
}
|
||||
201
operator/pallets/system-v2/src/tests.rs
Normal file
201
operator/pallets/system-v2/src/tests.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use crate::{mock::*, DispatchError::BadOrigin, *};
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
use sp_keyring::sr25519::Keyring;
|
||||
use xcm::{latest::WESTEND_GENESIS_HASH, prelude::*};
|
||||
|
||||
#[test]
|
||||
fn register_tokens_succeeds() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = make_xcm_origin(FrontendLocation::get());
|
||||
let versioned_location: VersionedLocation = Location::parent().into();
|
||||
|
||||
assert_ok!(EthereumSystemV2::register_token(
|
||||
origin,
|
||||
Box::new(versioned_location.clone()),
|
||||
Box::new(versioned_location),
|
||||
Default::default(),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_id_from_location() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let bob: AccountId = Keyring::Bob.into();
|
||||
let origin = Location::new(
|
||||
1,
|
||||
[
|
||||
Parachain(1000),
|
||||
AccountId32 {
|
||||
network: Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)),
|
||||
id: bob.into(),
|
||||
},
|
||||
],
|
||||
);
|
||||
let agent_id = EthereumSystemV2::location_to_message_origin(origin.clone()).unwrap();
|
||||
let expected_agent_id =
|
||||
hex_literal::hex!("fa2d646322a1c6db25dd004f44f14f3d39a9556bed9655f372942a84a5b3d93b")
|
||||
.into();
|
||||
assert_eq!(agent_id, expected_agent_id);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_as_root() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let address: H160 = [1_u8; 20].into();
|
||||
let code_hash: H256 = [1_u8; 32].into();
|
||||
let initializer = Initializer {
|
||||
params: [0; 256].into(),
|
||||
maximum_required_gas: 10000,
|
||||
};
|
||||
let initializer_params_hash: H256 = blake2_256(initializer.params.as_ref()).into();
|
||||
assert_ok!(EthereumSystemV2::upgrade(
|
||||
origin,
|
||||
address,
|
||||
code_hash,
|
||||
initializer
|
||||
));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::EthereumSystemV2(crate::Event::Upgrade {
|
||||
impl_address: address,
|
||||
impl_code_hash: code_hash,
|
||||
initializer_params_hash,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_as_signed_fails() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::signed(sp_runtime::AccountId32::new([0; 32]));
|
||||
let address: H160 = Default::default();
|
||||
let code_hash: H256 = Default::default();
|
||||
let initializer = Initializer {
|
||||
params: [0; 256].into(),
|
||||
maximum_required_gas: 10000,
|
||||
};
|
||||
assert_noop!(
|
||||
EthereumSystemV2::upgrade(origin, address, code_hash, initializer),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_with_params() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let address: H160 = [1_u8; 20].into();
|
||||
let code_hash: H256 = [1_u8; 32].into();
|
||||
let initializer = Initializer {
|
||||
params: [0; 256].into(),
|
||||
maximum_required_gas: 10000,
|
||||
};
|
||||
assert_ok!(EthereumSystemV2::upgrade(
|
||||
origin,
|
||||
address,
|
||||
code_hash,
|
||||
initializer
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_operating_mode() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let mode = OperatingMode::RejectingOutboundMessages;
|
||||
|
||||
assert_ok!(EthereumSystemV2::set_operating_mode(origin, mode));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::EthereumSystemV2(
|
||||
crate::Event::SetOperatingMode { mode },
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
pub struct RegisterTokenTestCase {
|
||||
/// Input: Location of Polkadot-native token relative to BH
|
||||
pub native: Location,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_all_tokens_succeeds() {
|
||||
let test_cases = vec![
|
||||
// DOT
|
||||
RegisterTokenTestCase {
|
||||
native: Location::parent(),
|
||||
},
|
||||
// GLMR (Some Polkadot parachain currency)
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(1, [Parachain(2004)]),
|
||||
},
|
||||
// USDT
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(1, [Parachain(1000), PalletInstance(50), GeneralIndex(1984)]),
|
||||
},
|
||||
// KSM
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(2, [GlobalConsensus(Kusama)]),
|
||||
},
|
||||
// KAR (Some Kusama parachain currency)
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]),
|
||||
},
|
||||
];
|
||||
for tc in test_cases.iter() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = make_xcm_origin(FrontendLocation::get());
|
||||
let versioned_location: VersionedLocation = tc.native.clone().into();
|
||||
|
||||
assert_ok!(EthereumSystemV2::register_token(
|
||||
origin,
|
||||
Box::new(versioned_location.clone()),
|
||||
Box::new(versioned_location),
|
||||
Default::default()
|
||||
));
|
||||
|
||||
let reanchored_location = EthereumSystemV2::reanchor(tc.native.clone()).unwrap();
|
||||
let foreign_token_id =
|
||||
EthereumSystemV2::location_to_message_origin(tc.native.clone()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
NativeToForeignId::<Test>::get(reanchored_location.clone()),
|
||||
Some(foreign_token_id)
|
||||
);
|
||||
assert_eq!(
|
||||
ForeignToNativeId::<Test>::get(foreign_token_id),
|
||||
Some(reanchored_location.clone())
|
||||
);
|
||||
|
||||
System::assert_last_event(RuntimeEvent::EthereumSystemV2(
|
||||
Event::<Test>::RegisterToken {
|
||||
location: reanchored_location.into(),
|
||||
foreign_token_id,
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_ethereum_native_token_fails() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = make_xcm_origin(FrontendLocation::get());
|
||||
let location = Location::new(2, [GlobalConsensus(Ethereum { chain_id: 11155111 })]);
|
||||
let versioned_location: Box<VersionedLocation> = Box::new(location.clone().into());
|
||||
assert_noop!(
|
||||
EthereumSystemV2::register_token(
|
||||
origin,
|
||||
versioned_location.clone(),
|
||||
versioned_location.clone(),
|
||||
Default::default()
|
||||
),
|
||||
Error::<Test>::LocationConversionFailed
|
||||
);
|
||||
});
|
||||
}
|
||||
89
operator/pallets/system-v2/src/weights.rs
Normal file
89
operator/pallets/system-v2/src/weights.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
|
||||
//! Autogenerated weights for `snowbridge_system`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `crake.local`, 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_system
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --template
|
||||
// ../parachain/templates/module-weight-template.hbs
|
||||
// --output
|
||||
// ../parachain/pallets/control/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_system`.
|
||||
pub trait WeightInfo {
|
||||
fn register_token() -> Weight;
|
||||
fn upgrade() -> Weight;
|
||||
fn set_operating_mode() -> Weight;
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
fn register_token() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `256`
|
||||
// Estimated: `6044`
|
||||
// Minimum execution time: 45_000_000 picoseconds.
|
||||
Weight::from_parts(45_000_000, 6044)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: ParachainInfo ParachainId (r:1 w:0)
|
||||
/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
|
||||
/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue BookStateFor (r:1 w:1)
|
||||
/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue ServiceHead (r:1 w:1)
|
||||
/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue Pages (r:0 w:1)
|
||||
/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
|
||||
fn upgrade() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 44_000_000 picoseconds.
|
||||
Weight::from_parts(44_000_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: ParachainInfo ParachainId (r:1 w:0)
|
||||
/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
|
||||
/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue BookStateFor (r:1 w:1)
|
||||
/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue ServiceHead (r:1 w:1)
|
||||
/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue Pages (r:0 w:1)
|
||||
/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
|
||||
fn set_operating_mode() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 31_000_000 picoseconds.
|
||||
Weight::from_parts(31_000_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
80
operator/pallets/system/Cargo.toml
Normal file
80
operator/pallets/system/Cargo.toml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
[package]
|
||||
name = "snowbridge-pallet-system"
|
||||
description = "Snowbridge System Pallet"
|
||||
version = "0.13.1"
|
||||
authors = ["Snowfork <contact@snowfork.com>"]
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license = "Apache-2.0"
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[package.metadata.polkadot-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
frame-benchmarking = { optional = true, workspace = true }
|
||||
frame-support.workspace = true
|
||||
frame-system.workspace = true
|
||||
log = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
snowbridge-core.workspace = true
|
||||
snowbridge-outbound-queue-primitives.workspace = true
|
||||
sp-core.workspace = true
|
||||
sp-io.workspace = true
|
||||
sp-runtime.workspace = true
|
||||
sp-std.workspace = true
|
||||
xcm-executor.workspace = true
|
||||
xcm.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { workspace = true, default-features = true }
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
pallet-balances = { default-features = true, workspace = true }
|
||||
pallet-message-queue = { default-features = true, workspace = true }
|
||||
polkadot-primitives = { default-features = true, workspace = true }
|
||||
snowbridge-pallet-outbound-queue = { default-features = true, workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking?/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"scale-info/std",
|
||||
"snowbridge-core/std",
|
||||
"snowbridge-outbound-queue-primitives/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"xcm-executor/std",
|
||||
"xcm/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"pallet-message-queue/runtime-benchmarks",
|
||||
"polkadot-primitives/runtime-benchmarks",
|
||||
"snowbridge-core/runtime-benchmarks",
|
||||
"snowbridge-pallet-outbound-queue/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"xcm-executor/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-message-queue/try-runtime",
|
||||
"snowbridge-pallet-outbound-queue/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
3
operator/pallets/system/README.md
Normal file
3
operator/pallets/system/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Ethereum System
|
||||
|
||||
Contains management functions to manage functions on Ethereum. For example, creating agents and channels.
|
||||
37
operator/pallets/system/runtime-api/Cargo.toml
Normal file
37
operator/pallets/system/runtime-api/Cargo.toml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "snowbridge-system-runtime-api"
|
||||
description = "Snowbridge System Runtime API"
|
||||
version = "0.13.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.polkadot-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = [
|
||||
"derive",
|
||||
], workspace = true }
|
||||
snowbridge-core.workspace = true
|
||||
sp-api.workspace = true
|
||||
sp-std.workspace = true
|
||||
xcm.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"snowbridge-core/std",
|
||||
"sp-api/std",
|
||||
"sp-std/std",
|
||||
"xcm/std",
|
||||
]
|
||||
3
operator/pallets/system/runtime-api/README.md
Normal file
3
operator/pallets/system/runtime-api/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Ethereum System Runtime API
|
||||
|
||||
Provides an API for looking up an agent ID on Ethereum.
|
||||
13
operator/pallets/system/runtime-api/src/lib.rs
Normal file
13
operator/pallets/system/runtime-api/src/lib.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use snowbridge_core::AgentId;
|
||||
use xcm::VersionedLocation;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
pub trait ControlApi
|
||||
{
|
||||
fn agent_id(location: VersionedLocation) -> Option<AgentId>;
|
||||
}
|
||||
}
|
||||
16
operator/pallets/system/src/api.rs
Normal file
16
operator/pallets/system/src/api.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Helpers for implementing runtime api
|
||||
|
||||
use snowbridge_core::AgentId;
|
||||
use xcm::{prelude::*, VersionedLocation};
|
||||
|
||||
use crate::{agent_id_of, Config};
|
||||
|
||||
pub fn agent_id<Runtime>(location: VersionedLocation) -> Option<AgentId>
|
||||
where
|
||||
Runtime: Config,
|
||||
{
|
||||
let location: Location = location.try_into().ok()?;
|
||||
agent_id_of::<Runtime>(&location).ok()
|
||||
}
|
||||
96
operator/pallets/system/src/benchmarking.rs
Normal file
96
operator/pallets/system/src/benchmarking.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Benchmarking setup for pallet-template
|
||||
use super::*;
|
||||
|
||||
#[allow(unused)]
|
||||
use crate::Pallet as SnowbridgeControl;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
use snowbridge_core::eth;
|
||||
use snowbridge_outbound_queue_primitives::OperatingMode;
|
||||
use sp_runtime::SaturatedConversion;
|
||||
use xcm::prelude::*;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn upgrade() -> Result<(), BenchmarkError> {
|
||||
let impl_address = H160::repeat_byte(1);
|
||||
let impl_code_hash = H256::repeat_byte(1);
|
||||
|
||||
// Assume 256 bytes passed to initializer
|
||||
let params: Vec<u8> = (0..256).map(|_| 1u8).collect();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
RawOrigin::Root,
|
||||
impl_address,
|
||||
impl_code_hash,
|
||||
Some(Initializer {
|
||||
params,
|
||||
maximum_required_gas: 100000,
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_operating_mode() -> Result<(), BenchmarkError> {
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, OperatingMode::RejectingOutboundMessages);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_pricing_parameters() -> Result<(), BenchmarkError> {
|
||||
let params = T::DefaultPricingParameters::get();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, params);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_token_transfer_fees() -> Result<(), BenchmarkError> {
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, 1, 1, eth(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_token() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
let amount: BalanceOf<T> = (10_000_000_000_000_u128)
|
||||
.saturated_into::<u128>()
|
||||
.saturated_into();
|
||||
|
||||
T::Token::mint_into(&caller, amount)?;
|
||||
|
||||
let relay_token_asset_id: Location = Location::parent();
|
||||
let asset = Box::new(VersionedLocation::from(relay_token_asset_id));
|
||||
let asset_metadata = AssetMetadata {
|
||||
name: "wnd".as_bytes().to_vec().try_into().unwrap(),
|
||||
symbol: "wnd".as_bytes().to_vec().try_into().unwrap(),
|
||||
decimals: 12,
|
||||
};
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, asset, asset_metadata);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
SnowbridgeControl,
|
||||
crate::mock::new_test_ext(true),
|
||||
crate::mock::Test
|
||||
);
|
||||
}
|
||||
566
operator/pallets/system/src/lib.rs
Normal file
566
operator/pallets/system/src/lib.rs
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Governance API for controlling the Ethereum side of the bridge
|
||||
//!
|
||||
//! # Extrinsics
|
||||
//!
|
||||
//! ## Governance
|
||||
//!
|
||||
//! Only Polkadot governance itself can call these extrinsics. Delivery fees are waived.
|
||||
//!
|
||||
//! * [`Call::upgrade`]`: Upgrade the gateway contract
|
||||
//! * [`Call::set_operating_mode`]: Update the operating mode of the gateway contract
|
||||
//!
|
||||
//! ## Polkadot-native tokens on Ethereum
|
||||
//!
|
||||
//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a
|
||||
//! prerequisite, the token should be registered first.
|
||||
//!
|
||||
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod migration;
|
||||
|
||||
pub mod api;
|
||||
pub mod weights;
|
||||
pub use weights::*;
|
||||
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate},
|
||||
tokens::Preservation,
|
||||
Contains, EnsureOrigin,
|
||||
},
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use snowbridge_core::{
|
||||
meth, AgentId, AssetMetadata, Channel, ChannelId, ParaId,
|
||||
PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL,
|
||||
SECONDARY_GOVERNANCE_CHANNEL,
|
||||
};
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v1::{Command, Initializer, Message, SendMessage},
|
||||
OperatingMode, SendError,
|
||||
};
|
||||
use sp_core::{RuntimeDebug, H160, H256};
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::{traits::MaybeEquivalence, DispatchError, SaturatedConversion};
|
||||
use sp_std::prelude::*;
|
||||
use xcm::prelude::*;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use frame_support::traits::OriginTrait;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
pub type BalanceOf<T> =
|
||||
<<T as pallet::Config>::Token as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
|
||||
pub type PricingParametersOf<T> = PricingParametersRecord<BalanceOf<T>>;
|
||||
|
||||
/// Hash the location to produce an agent id
|
||||
pub fn agent_id_of<T: Config>(location: &Location) -> Result<H256, DispatchError> {
|
||||
T::AgentIdOf::convert_location(location).ok_or(Error::<T>::LocationConversionFailed.into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub trait BenchmarkHelper<O>
|
||||
where
|
||||
O: OriginTrait,
|
||||
{
|
||||
fn make_xcm_origin(location: Location) -> O;
|
||||
}
|
||||
|
||||
/// Whether a fee should be withdrawn to an account for sending an outbound message
|
||||
#[derive(Clone, PartialEq, RuntimeDebug)]
|
||||
pub enum PaysFee<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
/// Fully charge includes (local + remote fee)
|
||||
Yes(AccountIdOf<T>),
|
||||
/// Partially charge includes local fee only
|
||||
Partial(AccountIdOf<T>),
|
||||
/// No charge
|
||||
No,
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use frame_support::dispatch::PostDispatchInfo;
|
||||
use snowbridge_core::StaticLookup;
|
||||
use sp_core::U256;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::storage_version(migration::STORAGE_VERSION)]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Send messages to Ethereum
|
||||
type OutboundQueue: SendMessage<Balance = BalanceOf<Self>>;
|
||||
|
||||
/// Origin check for XCM locations that can create agents
|
||||
type SiblingOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
|
||||
|
||||
/// Converts Location to AgentId
|
||||
type AgentIdOf: ConvertLocation<AgentId>;
|
||||
|
||||
/// Token reserved for control operations
|
||||
type Token: Mutate<Self::AccountId>;
|
||||
|
||||
/// TreasuryAccount to collect fees
|
||||
#[pallet::constant]
|
||||
type TreasuryAccount: Get<Self::AccountId>;
|
||||
|
||||
/// Number of decimal places of local currency
|
||||
type DefaultPricingParameters: Get<PricingParametersOf<Self>>;
|
||||
|
||||
/// Cost of delivering a message from Ethereum
|
||||
#[pallet::constant]
|
||||
type InboundDeliveryCost: Get<BalanceOf<Self>>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// This chain's Universal Location.
|
||||
type UniversalLocation: Get<InteriorLocation>;
|
||||
|
||||
// The bridges configured Ethereum location
|
||||
type EthereumLocation: Get<Location>;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// An Upgrade message was sent to the Gateway
|
||||
Upgrade {
|
||||
impl_address: H160,
|
||||
impl_code_hash: H256,
|
||||
initializer_params_hash: Option<H256>,
|
||||
},
|
||||
/// An CreateAgent message was sent to the Gateway
|
||||
CreateAgent {
|
||||
location: Box<Location>,
|
||||
agent_id: AgentId,
|
||||
},
|
||||
/// An CreateChannel message was sent to the Gateway
|
||||
CreateChannel {
|
||||
channel_id: ChannelId,
|
||||
agent_id: AgentId,
|
||||
},
|
||||
/// An UpdateChannel message was sent to the Gateway
|
||||
UpdateChannel {
|
||||
channel_id: ChannelId,
|
||||
mode: OperatingMode,
|
||||
},
|
||||
/// An SetOperatingMode message was sent to the Gateway
|
||||
SetOperatingMode {
|
||||
mode: OperatingMode,
|
||||
},
|
||||
/// An TransferNativeFromAgent message was sent to the Gateway
|
||||
TransferNativeFromAgent {
|
||||
agent_id: AgentId,
|
||||
recipient: H160,
|
||||
amount: u128,
|
||||
},
|
||||
/// A SetTokenTransferFees message was sent to the Gateway
|
||||
SetTokenTransferFees {
|
||||
create_asset_xcm: u128,
|
||||
transfer_asset_xcm: u128,
|
||||
register_token: U256,
|
||||
},
|
||||
PricingParametersChanged {
|
||||
params: PricingParametersOf<T>,
|
||||
},
|
||||
/// Register Polkadot-native token as a wrapped ERC20 token on Ethereum
|
||||
RegisterToken {
|
||||
/// Location of Polkadot-native token
|
||||
location: VersionedLocation,
|
||||
/// ID of Polkadot-native token on Ethereum
|
||||
foreign_token_id: H256,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
LocationConversionFailed,
|
||||
AgentAlreadyCreated,
|
||||
NoAgent,
|
||||
ChannelAlreadyCreated,
|
||||
NoChannel,
|
||||
UnsupportedLocationVersion,
|
||||
InvalidLocation,
|
||||
Send(SendError),
|
||||
InvalidTokenTransferFees,
|
||||
InvalidPricingParameters,
|
||||
InvalidUpgradeParameters,
|
||||
}
|
||||
|
||||
/// The set of registered agents
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn agents)]
|
||||
pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;
|
||||
|
||||
/// The set of registered channels
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn channels)]
|
||||
pub type Channels<T: Config> = StorageMap<_, Twox64Concat, ChannelId, Channel, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn parameters)]
|
||||
pub type PricingParameters<T: Config> =
|
||||
StorageValue<_, PricingParametersOf<T>, ValueQuery, T::DefaultPricingParameters>;
|
||||
|
||||
/// Lookup table for foreign token ID to native location relative to ethereum
|
||||
#[pallet::storage]
|
||||
pub type ForeignToNativeId<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>;
|
||||
|
||||
/// Lookup table for native location relative to ethereum to foreign token ID
|
||||
#[pallet::storage]
|
||||
pub type NativeToForeignId<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, xcm::v5::Location, TokenId, OptionQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(frame_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
// Own parachain id
|
||||
pub para_id: ParaId,
|
||||
// AssetHub's parachain id
|
||||
pub asset_hub_para_id: ParaId,
|
||||
#[serde(skip)]
|
||||
pub _config: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
Pallet::<T>::initialize(self.para_id, self.asset_hub_para_id).expect("infallible; qed");
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Sends command to the Gateway contract to upgrade itself with a new implementation
|
||||
/// contract
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be `Root`.
|
||||
/// - `impl_address`: The address of the implementation contract.
|
||||
/// - `impl_code_hash`: The codehash of the implementation contract.
|
||||
/// - `initializer`: Optionally call an initializer on the implementation contract.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((T::WeightInfo::upgrade(), DispatchClass::Operational))]
|
||||
pub fn upgrade(
|
||||
origin: OriginFor<T>,
|
||||
impl_address: H160,
|
||||
impl_code_hash: H256,
|
||||
initializer: Option<Initializer>,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
|
||||
ensure!(
|
||||
!impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
|
||||
Error::<T>::InvalidUpgradeParameters
|
||||
);
|
||||
|
||||
let initializer_params_hash: Option<H256> = initializer
|
||||
.as_ref()
|
||||
.map(|i| H256::from(blake2_256(i.params.as_ref())));
|
||||
let command = Command::Upgrade {
|
||||
impl_address,
|
||||
impl_code_hash,
|
||||
initializer,
|
||||
};
|
||||
Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::Upgrade {
|
||||
impl_address,
|
||||
impl_code_hash,
|
||||
initializer_params_hash,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a message to the Gateway contract to change its operating mode
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be `Location`
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((T::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
|
||||
pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
|
||||
let command = Command::SetOperatingMode { mode };
|
||||
Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::SetOperatingMode { mode });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set pricing parameters on both sides of the bridge
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be root
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight((T::WeightInfo::set_pricing_parameters(), DispatchClass::Operational))]
|
||||
pub fn set_pricing_parameters(
|
||||
origin: OriginFor<T>,
|
||||
params: PricingParametersOf<T>,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
params
|
||||
.validate()
|
||||
.map_err(|_| Error::<T>::InvalidPricingParameters)?;
|
||||
PricingParameters::<T>::put(params.clone());
|
||||
|
||||
let command = Command::SetPricingParameters {
|
||||
exchange_rate: params.exchange_rate.into(),
|
||||
delivery_cost: T::InboundDeliveryCost::get().saturated_into::<u128>(),
|
||||
multiplier: params.multiplier.into(),
|
||||
};
|
||||
Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
|
||||
|
||||
Self::deposit_event(Event::PricingParametersChanged { params });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a message to the Gateway contract to update fee related parameters for
|
||||
/// token transfers.
|
||||
///
|
||||
/// Privileged. Can only be called by root.
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be root
|
||||
/// - `create_asset_xcm`: The XCM execution cost for creating a new asset class on AssetHub,
|
||||
/// in DOT
|
||||
/// - `transfer_asset_xcm`: The XCM execution cost for performing a reserve transfer on
|
||||
/// AssetHub, in DOT
|
||||
/// - `register_token`: The Ether fee for registering a new token, to discourage spamming
|
||||
#[pallet::call_index(9)]
|
||||
#[pallet::weight((T::WeightInfo::set_token_transfer_fees(), DispatchClass::Operational))]
|
||||
pub fn set_token_transfer_fees(
|
||||
origin: OriginFor<T>,
|
||||
create_asset_xcm: u128,
|
||||
transfer_asset_xcm: u128,
|
||||
register_token: U256,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
|
||||
// Basic validation of new costs. Particularly for token registration, we want to ensure
|
||||
// its relatively expensive to discourage spamming. Like at least 100 USD.
|
||||
ensure!(
|
||||
create_asset_xcm > 0 && transfer_asset_xcm > 0 && register_token > meth(100),
|
||||
Error::<T>::InvalidTokenTransferFees
|
||||
);
|
||||
|
||||
let command = Command::SetTokenTransferFees {
|
||||
create_asset_xcm,
|
||||
transfer_asset_xcm,
|
||||
register_token,
|
||||
};
|
||||
Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::<T>::No)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::SetTokenTransferFees {
|
||||
create_asset_xcm,
|
||||
transfer_asset_xcm,
|
||||
register_token,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum.
|
||||
/// Privileged. Can only be called by root.
|
||||
///
|
||||
/// Fee required: No
|
||||
///
|
||||
/// - `origin`: Must be root
|
||||
/// - `location`: Location of the asset (relative to this chain)
|
||||
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
|
||||
#[pallet::call_index(10)]
|
||||
#[pallet::weight(T::WeightInfo::register_token())]
|
||||
pub fn register_token(
|
||||
origin: OriginFor<T>,
|
||||
location: Box<VersionedLocation>,
|
||||
metadata: AssetMetadata,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_root(origin)?;
|
||||
|
||||
let location: Location = (*location)
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
|
||||
|
||||
Self::do_register_token(&location, metadata, PaysFee::<T>::No)?;
|
||||
|
||||
Ok(PostDispatchInfo {
|
||||
actual_weight: Some(T::WeightInfo::register_token()),
|
||||
pays_fee: Pays::No,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Send `command` to the Gateway on the Channel identified by `channel_id`
|
||||
fn send(channel_id: ChannelId, command: Command, pays_fee: PaysFee<T>) -> DispatchResult {
|
||||
let message = Message {
|
||||
id: None,
|
||||
channel_id,
|
||||
command,
|
||||
};
|
||||
let (ticket, fee) =
|
||||
T::OutboundQueue::validate(&message).map_err(|err| Error::<T>::Send(err))?;
|
||||
|
||||
let payment = match pays_fee {
|
||||
PaysFee::Yes(account) => Some((account, fee.total())),
|
||||
PaysFee::Partial(account) => Some((account, fee.local)),
|
||||
PaysFee::No => None,
|
||||
};
|
||||
|
||||
if let Some((payer, fee)) = payment {
|
||||
T::Token::transfer(
|
||||
&payer,
|
||||
&T::TreasuryAccount::get(),
|
||||
fee,
|
||||
Preservation::Preserve,
|
||||
)?;
|
||||
}
|
||||
|
||||
T::OutboundQueue::deliver(ticket).map_err(|err| Error::<T>::Send(err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initializes agents and channels.
|
||||
pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> {
|
||||
// Asset Hub
|
||||
let asset_hub_location: Location =
|
||||
ParentThen(Parachain(asset_hub_para_id.into()).into()).into();
|
||||
let asset_hub_agent_id = agent_id_of::<T>(&asset_hub_location)?;
|
||||
let asset_hub_channel_id: ChannelId = asset_hub_para_id.into();
|
||||
Agents::<T>::insert(asset_hub_agent_id, ());
|
||||
Channels::<T>::insert(
|
||||
asset_hub_channel_id,
|
||||
Channel {
|
||||
agent_id: asset_hub_agent_id,
|
||||
para_id: asset_hub_para_id,
|
||||
},
|
||||
);
|
||||
|
||||
// Governance channels
|
||||
let bridge_hub_agent_id = agent_id_of::<T>(&Location::here())?;
|
||||
// Agent for BridgeHub
|
||||
Agents::<T>::insert(bridge_hub_agent_id, ());
|
||||
|
||||
// Primary governance channel
|
||||
Channels::<T>::insert(
|
||||
PRIMARY_GOVERNANCE_CHANNEL,
|
||||
Channel {
|
||||
agent_id: bridge_hub_agent_id,
|
||||
para_id,
|
||||
},
|
||||
);
|
||||
|
||||
// Secondary governance channel
|
||||
Channels::<T>::insert(
|
||||
SECONDARY_GOVERNANCE_CHANNEL,
|
||||
Channel {
|
||||
agent_id: bridge_hub_agent_id,
|
||||
para_id,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if the pallet has been initialized.
|
||||
pub(crate) fn is_initialized() -> bool {
|
||||
let primary_exists = Channels::<T>::contains_key(PRIMARY_GOVERNANCE_CHANNEL);
|
||||
let secondary_exists = Channels::<T>::contains_key(SECONDARY_GOVERNANCE_CHANNEL);
|
||||
primary_exists && secondary_exists
|
||||
}
|
||||
|
||||
pub(crate) fn do_register_token(
|
||||
location: &Location,
|
||||
metadata: AssetMetadata,
|
||||
pays_fee: PaysFee<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let ethereum_location = T::EthereumLocation::get();
|
||||
// reanchor to Ethereum context
|
||||
let location = location
|
||||
.clone()
|
||||
.reanchored(ðereum_location, &T::UniversalLocation::get())
|
||||
.map_err(|_| Error::<T>::LocationConversionFailed)?;
|
||||
|
||||
let token_id = TokenIdOf::convert_location(&location)
|
||||
.ok_or(Error::<T>::LocationConversionFailed)?;
|
||||
|
||||
if !ForeignToNativeId::<T>::contains_key(token_id) {
|
||||
NativeToForeignId::<T>::insert(location.clone(), token_id);
|
||||
ForeignToNativeId::<T>::insert(token_id, location.clone());
|
||||
}
|
||||
|
||||
let command = Command::RegisterForeignToken {
|
||||
token_id,
|
||||
name: metadata.name.into_inner(),
|
||||
symbol: metadata.symbol.into_inner(),
|
||||
decimals: metadata.decimals,
|
||||
};
|
||||
Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?;
|
||||
|
||||
Self::deposit_event(Event::<T>::RegisterToken {
|
||||
location: location.clone().into(),
|
||||
foreign_token_id: token_id,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> StaticLookup for Pallet<T> {
|
||||
type Source = ChannelId;
|
||||
type Target = Channel;
|
||||
fn lookup(channel_id: Self::Source) -> Option<Self::Target> {
|
||||
Channels::<T>::get(channel_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Contains<ChannelId> for Pallet<T> {
|
||||
fn contains(channel_id: &ChannelId) -> bool {
|
||||
Channels::<T>::get(channel_id).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Get<PricingParametersOf<T>> for Pallet<T> {
|
||||
fn get() -> PricingParametersOf<T> {
|
||||
PricingParameters::<T>::get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> MaybeEquivalence<TokenId, Location> for Pallet<T> {
|
||||
fn convert(foreign_id: &TokenId) -> Option<Location> {
|
||||
ForeignToNativeId::<T>::get(foreign_id)
|
||||
}
|
||||
fn convert_back(location: &Location) -> Option<TokenId> {
|
||||
NativeToForeignId::<T>::get(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
227
operator/pallets/system/src/migration.rs
Normal file
227
operator/pallets/system/src/migration.rs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Governance API for controlling the Ethereum side of the bridge
|
||||
use super::*;
|
||||
use frame_support::{
|
||||
migrations::VersionedMigration,
|
||||
pallet_prelude::*,
|
||||
traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade},
|
||||
weights::Weight,
|
||||
};
|
||||
use log;
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use sp_runtime::TryRuntimeError;
|
||||
|
||||
const LOG_TARGET: &str = "ethereum_system::migration";
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
pub mod v0 {
|
||||
use super::*;
|
||||
|
||||
pub struct InitializeOnUpgrade<T, BridgeHubParaId, AssetHubParaId>(
|
||||
PhantomData<(T, BridgeHubParaId, AssetHubParaId)>,
|
||||
);
|
||||
|
||||
impl<T, BridgeHubParaId, AssetHubParaId> OnRuntimeUpgrade
|
||||
for InitializeOnUpgrade<T, BridgeHubParaId, AssetHubParaId>
|
||||
where
|
||||
T: Config,
|
||||
BridgeHubParaId: Get<u32>,
|
||||
AssetHubParaId: Get<u32>,
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
if !Pallet::<T>::is_initialized() {
|
||||
Pallet::<T>::initialize(
|
||||
BridgeHubParaId::get().into(),
|
||||
AssetHubParaId::get().into(),
|
||||
)
|
||||
.expect("infallible; qed");
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Ethereum system initialized."
|
||||
);
|
||||
T::DbWeight::get().reads_writes(2, 5)
|
||||
} else {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Ethereum system already initialized. Skipping."
|
||||
);
|
||||
T::DbWeight::get().reads(2)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
if !Pallet::<T>::is_initialized() {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Agents and channels not initialized. Initialization will run."
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Agents and channels are initialized. Initialization will not run."
|
||||
);
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(_: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
frame_support::ensure!(
|
||||
Pallet::<T>::is_initialized(),
|
||||
"Agents and channels were not initialized."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use sp_core::U256;
|
||||
|
||||
/// Descreases the fee per gas.
|
||||
pub struct FeePerGasMigration<T>(PhantomData<T>);
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
impl<T> FeePerGasMigration<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
/// Calculate the fee required to pay for gas on Ethereum.
|
||||
fn calculate_remote_fee_v1(params: &PricingParametersOf<T>) -> U256 {
|
||||
use snowbridge_outbound_queue_primitives::v1::{
|
||||
AgentExecuteCommand, Command, ConstantGasMeter, GasMeter,
|
||||
};
|
||||
let command = Command::AgentExecute {
|
||||
agent_id: H256::zero(),
|
||||
command: AgentExecuteCommand::TransferToken {
|
||||
token: H160::zero(),
|
||||
recipient: H160::zero(),
|
||||
amount: 0,
|
||||
},
|
||||
};
|
||||
let gas_used_at_most = ConstantGasMeter::maximum_gas_used_at_most(&command);
|
||||
params
|
||||
.fee_per_gas
|
||||
.saturating_mul(gas_used_at_most.into())
|
||||
.saturating_add(params.rewards.remote)
|
||||
}
|
||||
|
||||
/// Calculate the fee required to pay for gas on Ethereum.
|
||||
fn calculate_remote_fee_v2(params: &PricingParametersOf<T>) -> U256 {
|
||||
use snowbridge_outbound_queue_primitives::v2::{Command, ConstantGasMeter, GasMeter};
|
||||
let command = Command::UnlockNativeToken {
|
||||
token: H160::zero(),
|
||||
recipient: H160::zero(),
|
||||
amount: 0,
|
||||
};
|
||||
let gas_used_at_most = ConstantGasMeter::maximum_dispatch_gas_used_at_most(&command);
|
||||
params
|
||||
.fee_per_gas
|
||||
.saturating_mul(gas_used_at_most.into())
|
||||
.saturating_add(params.rewards.remote)
|
||||
}
|
||||
}
|
||||
|
||||
/// The percentage gas increase. We must adjust the fee per gas by this percentage.
|
||||
const GAS_INCREASE_PERCENTAGE: u64 = 70;
|
||||
|
||||
impl<T> UncheckedOnRuntimeUpgrade for FeePerGasMigration<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut params = Pallet::<T>::parameters();
|
||||
|
||||
let old_fee_per_gas = params.fee_per_gas;
|
||||
|
||||
// Fee per gas can be set based on a percentage in order to keep the remote fee the
|
||||
// same.
|
||||
params.fee_per_gas = params.fee_per_gas * GAS_INCREASE_PERCENTAGE / 100;
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Fee per gas migrated from {old_fee_per_gas:?} to {0:?}.",
|
||||
params.fee_per_gas,
|
||||
);
|
||||
|
||||
PricingParameters::<T>::put(params);
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
use codec::Encode;
|
||||
|
||||
let params = Pallet::<T>::parameters();
|
||||
let remote_fee_v1 = Self::calculate_remote_fee_v1(¶ms);
|
||||
let remote_fee_v2 = Self::calculate_remote_fee_v2(¶ms);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Pre fee per gas migration: pricing parameters = {params:?}, remote_fee_v1 = {remote_fee_v1:?}, remote_fee_v2 = {remote_fee_v2:?}"
|
||||
);
|
||||
Ok((params, remote_fee_v1, remote_fee_v2).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
use codec::Decode;
|
||||
|
||||
let (old_params, old_remote_fee_v1, old_remote_fee_v2): (
|
||||
PricingParametersOf<T>,
|
||||
U256,
|
||||
U256,
|
||||
) = Decode::decode(&mut &state[..]).unwrap();
|
||||
|
||||
let params = Pallet::<T>::parameters();
|
||||
ensure!(
|
||||
old_params.exchange_rate == params.exchange_rate,
|
||||
"Exchange rate unchanged."
|
||||
);
|
||||
ensure!(old_params.rewards == params.rewards, "Rewards unchanged.");
|
||||
ensure!(
|
||||
(old_params.fee_per_gas * GAS_INCREASE_PERCENTAGE / 100) == params.fee_per_gas,
|
||||
"Fee per gas decreased."
|
||||
);
|
||||
ensure!(
|
||||
old_params.multiplier == params.multiplier,
|
||||
"Multiplier unchanged."
|
||||
);
|
||||
|
||||
let remote_fee_v1 = Self::calculate_remote_fee_v1(¶ms);
|
||||
let remote_fee_v2 = Self::calculate_remote_fee_v2(¶ms);
|
||||
ensure!(
|
||||
remote_fee_v1 <= old_remote_fee_v1,
|
||||
"The v1 remote fee can cover the cost of the previous fee."
|
||||
);
|
||||
ensure!(
|
||||
remote_fee_v2 <= old_remote_fee_v2,
|
||||
"The v2 remote fee can cover the cost of the previous fee."
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Post fee per gas migration: pricing parameters = {params:?} remote_fee_v1 = {remote_fee_v1:?} remote_fee_v2 = {remote_fee_v2:?}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the migration of the gas price and increment the pallet version so it cannot be re-run.
|
||||
pub type FeePerGasMigrationV0ToV1<T> = VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
v1::FeePerGasMigration<T>,
|
||||
Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
>;
|
||||
254
operator/pallets/system/src/mock.rs
Normal file
254
operator/pallets/system/src/mock.rs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use crate as snowbridge_system;
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{tokens::fungible::Mutate, ConstU128, ConstU8},
|
||||
weights::IdentityFee,
|
||||
PalletId,
|
||||
};
|
||||
use sp_core::H256;
|
||||
use xcm_executor::traits::ConvertLocation;
|
||||
|
||||
use snowbridge_core::{
|
||||
gwei, meth, sibling_sovereign_account, AgentId, AllowSiblingsOnly, ParaId, PricingParameters,
|
||||
Rewards,
|
||||
};
|
||||
use snowbridge_outbound_queue_primitives::v1::ConstantGasMeter;
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Keccak256},
|
||||
AccountId32, BuildStorage, FixedU128,
|
||||
};
|
||||
use xcm::prelude::*;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use crate::BenchmarkHelper;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
type Balance = u128;
|
||||
|
||||
pub type AccountId = AccountId32;
|
||||
|
||||
// A stripped-down version of pallet-xcm that only inserts an XCM origin into the runtime
|
||||
#[allow(dead_code)]
|
||||
#[frame_support::pallet]
|
||||
mod pallet_xcm_origin {
|
||||
use frame_support::{
|
||||
pallet_prelude::*,
|
||||
traits::{Contains, OriginTrait},
|
||||
};
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
type RuntimeOrigin: From<Origin> + From<<Self as frame_system::Config>::RuntimeOrigin>;
|
||||
}
|
||||
|
||||
// Insert this custom Origin into the aggregate RuntimeOrigin
|
||||
#[pallet::origin]
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct Origin(pub Location);
|
||||
|
||||
impl From<Location> for Origin {
|
||||
fn from(location: Location) -> Origin {
|
||||
Origin(location)
|
||||
}
|
||||
}
|
||||
|
||||
/// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and
|
||||
/// filter the contained location
|
||||
pub struct EnsureXcm<F>(PhantomData<F>);
|
||||
impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureXcm<F>
|
||||
where
|
||||
for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
|
||||
{
|
||||
type Success = Location;
|
||||
|
||||
fn try_origin(outer: O) -> Result<Self::Success, O> {
|
||||
match outer.caller().try_into() {
|
||||
Ok(Origin(ref location)) if F::contains(location) => return Ok(location.clone()),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Err(outer)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn try_successful_origin() -> Result<O, ()> {
|
||||
Ok(O::from(Origin(Location::new(1, [Parachain(2000)]))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure a mock runtime to test the pallet.
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
XcmOrigin: pallet_xcm_origin::{Pallet, Origin},
|
||||
OutboundQueue: snowbridge_pallet_outbound_queue::{Pallet, Call, Storage, Event<T>},
|
||||
EthereumSystem: snowbridge_system,
|
||||
MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event<T>}
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::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 AccountData = pallet_balances::AccountData<u128>;
|
||||
type Nonce = u64;
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Test {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pallet_xcm_origin::Config for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
}
|
||||
|
||||
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 = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxMessagePayloadSize: u32 = 1024;
|
||||
pub const MaxMessagesPerBlock: u32 = 20;
|
||||
pub const OwnParaId: ParaId = ParaId::new(1013);
|
||||
}
|
||||
|
||||
impl snowbridge_pallet_outbound_queue::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Hashing = Keccak256;
|
||||
type MessageQueue = MessageQueue;
|
||||
type Decimals = ConstU8<10>;
|
||||
type MaxMessagePayloadSize = MaxMessagePayloadSize;
|
||||
type MaxMessagesPerBlock = MaxMessagesPerBlock;
|
||||
type GasMeter = ConstantGasMeter;
|
||||
type Balance = u128;
|
||||
type PricingParameters = EthereumSystem;
|
||||
type Channels = EthereumSystem;
|
||||
type WeightToFee = IdentityFee<u128>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SS58Prefix: u8 = 42;
|
||||
pub const AnyNetwork: Option<NetworkId> = None;
|
||||
pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::Polkadot);
|
||||
pub const RelayLocation: Location = Location::parent();
|
||||
pub UniversalLocation: InteriorLocation =
|
||||
[GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)].into();
|
||||
pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
|
||||
pub EthereumDestination: Location = Location::new(2,[GlobalConsensus(EthereumNetwork::get())]);
|
||||
}
|
||||
|
||||
pub const DOT: u128 = 10_000_000_000;
|
||||
|
||||
parameter_types! {
|
||||
pub TreasuryAccount: AccountId = PalletId(*b"py/trsry").into_account_truncating();
|
||||
pub Fee: u64 = 1000;
|
||||
pub const InitialFunding: u128 = 1_000_000_000_000;
|
||||
pub BridgeHubParaId: ParaId = ParaId::new(1002);
|
||||
pub AssetHubParaId: ParaId = ParaId::new(1000);
|
||||
pub TestParaId: u32 = 2000;
|
||||
pub Parameters: PricingParameters<u128> = PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 400),
|
||||
fee_per_gas: gwei(20),
|
||||
rewards: Rewards { local: DOT, remote: meth(1) },
|
||||
multiplier: FixedU128::from_rational(4, 3)
|
||||
};
|
||||
pub const InboundDeliveryCost: u128 = 1_000_000_000;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl BenchmarkHelper<RuntimeOrigin> for () {
|
||||
fn make_xcm_origin(location: Location) -> RuntimeOrigin {
|
||||
RuntimeOrigin::from(pallet_xcm_origin::Origin(location))
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = OutboundQueue;
|
||||
type SiblingOrigin = pallet_xcm_origin::EnsureXcm<AllowSiblingsOnly>;
|
||||
type AgentIdOf = snowbridge_core::AgentIdOf;
|
||||
type TreasuryAccount = TreasuryAccount;
|
||||
type Token = Balances;
|
||||
type DefaultPricingParameters = Parameters;
|
||||
type WeightInfo = ();
|
||||
type InboundDeliveryCost = InboundDeliveryCost;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type EthereumLocation = EthereumDestination;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = ();
|
||||
}
|
||||
|
||||
// Build genesis storage according to the mock runtime.
|
||||
pub fn new_test_ext(genesis_build: bool) -> sp_io::TestExternalities {
|
||||
let mut storage = frame_system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
if genesis_build {
|
||||
crate::GenesisConfig::<Test> {
|
||||
para_id: OwnParaId::get(),
|
||||
asset_hub_para_id: AssetHubParaId::get(),
|
||||
_config: Default::default(),
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut ext: sp_io::TestExternalities = storage.into();
|
||||
let initial_amount = InitialFunding::get();
|
||||
let test_para_id = TestParaId::get();
|
||||
let sovereign_account = sibling_sovereign_account::<Test>(test_para_id.into());
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
Balances::mint_into(&AccountId32::from([0; 32]), initial_amount).unwrap();
|
||||
Balances::mint_into(&sovereign_account, initial_amount).unwrap();
|
||||
Balances::mint_into(&treasury_account, initial_amount).unwrap();
|
||||
});
|
||||
ext
|
||||
}
|
||||
|
||||
// Test helpers
|
||||
|
||||
pub fn make_agent_id(location: Location) -> AgentId {
|
||||
<Test as snowbridge_system::Config>::AgentIdOf::convert_location(&location)
|
||||
.expect("convert location")
|
||||
}
|
||||
330
operator/pallets/system/src/tests.rs
Normal file
330
operator/pallets/system/src/tests.rs
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use crate::{mock::*, *};
|
||||
use frame_support::{assert_noop, assert_ok};
|
||||
use hex_literal::hex;
|
||||
use snowbridge_core::eth;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{AccountId32, DispatchError::BadOrigin};
|
||||
|
||||
#[test]
|
||||
fn test_agent_for_here() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin_location = Location::here();
|
||||
let agent_id = make_agent_id(origin_location);
|
||||
assert_eq!(
|
||||
agent_id,
|
||||
hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_as_root() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let address: H160 = [1_u8; 20].into();
|
||||
let code_hash: H256 = [1_u8; 32].into();
|
||||
|
||||
assert_ok!(EthereumSystem::upgrade(origin, address, code_hash, None));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::EthereumSystem(crate::Event::Upgrade {
|
||||
impl_address: address,
|
||||
impl_code_hash: code_hash,
|
||||
initializer_params_hash: None,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_as_signed_fails() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::signed(AccountId32::new([0; 32]));
|
||||
let address: H160 = Default::default();
|
||||
let code_hash: H256 = Default::default();
|
||||
|
||||
assert_noop!(
|
||||
EthereumSystem::upgrade(origin, address, code_hash, None),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_with_params() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let address: H160 = [1_u8; 20].into();
|
||||
let code_hash: H256 = [1_u8; 32].into();
|
||||
let initializer: Option<Initializer> = Some(Initializer {
|
||||
params: [0; 256].into(),
|
||||
maximum_required_gas: 10000,
|
||||
});
|
||||
assert_ok!(EthereumSystem::upgrade(
|
||||
origin,
|
||||
address,
|
||||
code_hash,
|
||||
initializer
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_operating_mode() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let mode = OperatingMode::RejectingOutboundMessages;
|
||||
|
||||
assert_ok!(EthereumSystem::set_operating_mode(origin, mode));
|
||||
|
||||
System::assert_last_event(RuntimeEvent::EthereumSystem(
|
||||
crate::Event::SetOperatingMode { mode },
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_operating_mode_as_signed_fails() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::signed([14; 32].into());
|
||||
let mode = OperatingMode::RejectingOutboundMessages;
|
||||
|
||||
assert_noop!(EthereumSystem::set_operating_mode(origin, mode), BadOrigin);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_pricing_parameters() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let mut params = Parameters::get();
|
||||
params.rewards.local = 7;
|
||||
|
||||
assert_ok!(EthereumSystem::set_pricing_parameters(origin, params));
|
||||
|
||||
assert_eq!(PricingParameters::<Test>::get().rewards.local, 7);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_pricing_parameters_as_signed_fails() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::signed([14; 32].into());
|
||||
let params = Parameters::get();
|
||||
|
||||
assert_noop!(
|
||||
EthereumSystem::set_pricing_parameters(origin, params),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_pricing_parameters_invalid() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let mut params = Parameters::get();
|
||||
params.rewards.local = 0;
|
||||
|
||||
assert_noop!(
|
||||
EthereumSystem::set_pricing_parameters(origin.clone(), params),
|
||||
Error::<Test>::InvalidPricingParameters
|
||||
);
|
||||
|
||||
let mut params = Parameters::get();
|
||||
params.exchange_rate = 0u128.into();
|
||||
assert_noop!(
|
||||
EthereumSystem::set_pricing_parameters(origin.clone(), params),
|
||||
Error::<Test>::InvalidPricingParameters
|
||||
);
|
||||
params = Parameters::get();
|
||||
params.fee_per_gas = sp_core::U256::zero();
|
||||
assert_noop!(
|
||||
EthereumSystem::set_pricing_parameters(origin.clone(), params),
|
||||
Error::<Test>::InvalidPricingParameters
|
||||
);
|
||||
params = Parameters::get();
|
||||
params.rewards.local = 0;
|
||||
assert_noop!(
|
||||
EthereumSystem::set_pricing_parameters(origin.clone(), params),
|
||||
Error::<Test>::InvalidPricingParameters
|
||||
);
|
||||
params = Parameters::get();
|
||||
params.rewards.remote = sp_core::U256::zero();
|
||||
assert_noop!(
|
||||
EthereumSystem::set_pricing_parameters(origin, params),
|
||||
Error::<Test>::InvalidPricingParameters
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_token_transfer_fees() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
|
||||
assert_ok!(EthereumSystem::set_token_transfer_fees(
|
||||
origin,
|
||||
1,
|
||||
1,
|
||||
eth(1)
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_token_transfer_fees_root_only() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::signed([14; 32].into());
|
||||
|
||||
assert_noop!(
|
||||
EthereumSystem::set_token_transfer_fees(origin, 1, 1, 1.into()),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_token_transfer_fees_invalid() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
|
||||
assert_noop!(
|
||||
EthereumSystem::set_token_transfer_fees(origin, 0, 0, 0.into()),
|
||||
Error::<Test>::InvalidTokenTransferFees
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genesis_build_initializes_correctly() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
assert!(EthereumSystem::is_initialized(), "Ethereum uninitialized.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_genesis_build_is_uninitialized() {
|
||||
new_test_ext(false).execute_with(|| {
|
||||
assert!(!EthereumSystem::is_initialized(), "Ethereum initialized.");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_token_with_signed_yields_bad_origin() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::signed([14; 32].into());
|
||||
let location = Location::new(1, [Parachain(2000)]);
|
||||
let versioned_location: Box<VersionedLocation> = Box::new(location.clone().into());
|
||||
assert_noop!(
|
||||
EthereumSystem::register_token(origin, versioned_location, Default::default()),
|
||||
BadOrigin
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub struct RegisterTokenTestCase {
|
||||
/// Input: Location of Polkadot-native token relative to BH
|
||||
pub native: Location,
|
||||
/// Output: Reanchored, canonicalized location
|
||||
pub reanchored: Location,
|
||||
/// Output: Stable hash of reanchored location
|
||||
pub foreign: TokenId,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_all_tokens_succeeds() {
|
||||
let test_cases = vec![
|
||||
// DOT
|
||||
RegisterTokenTestCase {
|
||||
native: Location::parent(),
|
||||
reanchored: Location::new(1, GlobalConsensus(Polkadot)),
|
||||
foreign: hex!("4e241583d94b5d48a27a22064cd49b2ed6f5231d2d950e432f9b7c2e0ade52b2")
|
||||
.into(),
|
||||
},
|
||||
// GLMR (Some Polkadot parachain currency)
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(1, [Parachain(2004)]),
|
||||
reanchored: Location::new(1, [GlobalConsensus(Polkadot), Parachain(2004)]),
|
||||
foreign: hex!("34c08fc90409b6924f0e8eabb7c2aaa0c749e23e31adad9f6d217b577737fafb")
|
||||
.into(),
|
||||
},
|
||||
// USDT
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(1, [Parachain(1000), PalletInstance(50), GeneralIndex(1984)]),
|
||||
reanchored: Location::new(
|
||||
1,
|
||||
[
|
||||
GlobalConsensus(Polkadot),
|
||||
Parachain(1000),
|
||||
PalletInstance(50),
|
||||
GeneralIndex(1984),
|
||||
],
|
||||
),
|
||||
foreign: hex!("14b0579be12d7d7f9971f1d4b41f0e88384b9b74799b0150d4aa6cd01afb4444")
|
||||
.into(),
|
||||
},
|
||||
// KSM
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(2, [GlobalConsensus(Kusama)]),
|
||||
reanchored: Location::new(1, [GlobalConsensus(Kusama)]),
|
||||
foreign: hex!("03b6054d0c576dd8391e34e1609cf398f68050c23009d19ce93c000922bcd852")
|
||||
.into(),
|
||||
},
|
||||
// KAR (Some Kusama parachain currency)
|
||||
RegisterTokenTestCase {
|
||||
native: Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]),
|
||||
reanchored: Location::new(1, [GlobalConsensus(Kusama), Parachain(2000)]),
|
||||
foreign: hex!("d3e39ad6ea4cee68c9741181e94098823b2ea34a467577d0875c036f0fce5be0")
|
||||
.into(),
|
||||
},
|
||||
];
|
||||
for tc in test_cases.iter() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let versioned_location: VersionedLocation = tc.native.clone().into();
|
||||
|
||||
assert_ok!(EthereumSystem::register_token(
|
||||
origin,
|
||||
Box::new(versioned_location),
|
||||
Default::default()
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
NativeToForeignId::<Test>::get(tc.reanchored.clone()),
|
||||
Some(tc.foreign)
|
||||
);
|
||||
assert_eq!(
|
||||
ForeignToNativeId::<Test>::get(tc.foreign),
|
||||
Some(tc.reanchored.clone())
|
||||
);
|
||||
|
||||
System::assert_last_event(RuntimeEvent::EthereumSystem(Event::<Test>::RegisterToken {
|
||||
location: tc.reanchored.clone().into(),
|
||||
foreign_token_id: tc.foreign,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_ethereum_native_token_fails() {
|
||||
new_test_ext(true).execute_with(|| {
|
||||
let origin = RuntimeOrigin::root();
|
||||
let location = Location::new(
|
||||
2,
|
||||
[
|
||||
GlobalConsensus(Ethereum { chain_id: 11155111 }),
|
||||
AccountKey20 {
|
||||
network: None,
|
||||
key: hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"),
|
||||
},
|
||||
],
|
||||
);
|
||||
let versioned_location: Box<VersionedLocation> = Box::new(location.clone().into());
|
||||
assert_noop!(
|
||||
EthereumSystem::register_token(origin, versioned_location, Default::default()),
|
||||
Error::<Test>::LocationConversionFailed
|
||||
);
|
||||
});
|
||||
}
|
||||
131
operator/pallets/system/src/weights.rs
Normal file
131
operator/pallets/system/src/weights.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
//! Autogenerated weights for `snowbridge_system`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `crake.local`, 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_system
|
||||
// --extrinsic=*
|
||||
// --execution=wasm
|
||||
// --wasm-execution=compiled
|
||||
// --template
|
||||
// ../parachain/templates/module-weight-template.hbs
|
||||
// --output
|
||||
// ../parachain/pallets/control/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_system`.
|
||||
pub trait WeightInfo {
|
||||
fn upgrade() -> Weight;
|
||||
fn set_operating_mode() -> Weight;
|
||||
fn set_token_transfer_fees() -> Weight;
|
||||
fn set_pricing_parameters() -> Weight;
|
||||
fn register_token() -> Weight;
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: ParachainInfo ParachainId (r:1 w:0)
|
||||
/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
|
||||
/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue BookStateFor (r:1 w:1)
|
||||
/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue ServiceHead (r:1 w:1)
|
||||
/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue Pages (r:0 w:1)
|
||||
/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
|
||||
fn upgrade() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 44_000_000 picoseconds.
|
||||
Weight::from_parts(44_000_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: ParachainInfo ParachainId (r:1 w:0)
|
||||
/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
|
||||
/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue BookStateFor (r:1 w:1)
|
||||
/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue ServiceHead (r:1 w:1)
|
||||
/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue Pages (r:0 w:1)
|
||||
/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
|
||||
fn set_operating_mode() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 31_000_000 picoseconds.
|
||||
Weight::from_parts(31_000_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: ParachainInfo ParachainId (r:1 w:0)
|
||||
/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
|
||||
/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue BookStateFor (r:1 w:1)
|
||||
/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue ServiceHead (r:1 w:1)
|
||||
/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue Pages (r:0 w:1)
|
||||
/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
|
||||
fn set_token_transfer_fees() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 31_000_000 picoseconds.
|
||||
Weight::from_parts(42_000_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
|
||||
/// Storage: ParachainInfo ParachainId (r:1 w:0)
|
||||
/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
|
||||
/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
|
||||
/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue BookStateFor (r:1 w:1)
|
||||
/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue ServiceHead (r:1 w:1)
|
||||
/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
|
||||
/// Storage: MessageQueue Pages (r:0 w:1)
|
||||
/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
|
||||
fn set_pricing_parameters() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `3517`
|
||||
// Minimum execution time: 31_000_000 picoseconds.
|
||||
Weight::from_parts(42_000_000, 3517)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
|
||||
fn register_token() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `256`
|
||||
// Estimated: `6044`
|
||||
// Minimum execution time: 45_000_000 picoseconds.
|
||||
Weight::from_parts(45_000_000, 6044)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
//! # Outbound
|
||||
//!
|
||||
//! Common traits and types
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
|
|
|
|||
408
operator/primitives/snowbridge/outbound-queue/src/v1/message.rs
Normal file
408
operator/primitives/snowbridge/outbound-queue/src/v1/message.rs
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! # Outbound V1 primitives
|
||||
|
||||
use crate::{OperatingMode, SendError, SendMessageFeeProvider};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use ethabi::Token;
|
||||
use scale_info::TypeInfo;
|
||||
use snowbridge_core::{pricing::UD60x18, ChannelId};
|
||||
use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
|
||||
use sp_core::{RuntimeDebug, H160, H256, U256};
|
||||
use sp_std::{borrow::ToOwned, vec, vec::Vec};
|
||||
|
||||
/// Enqueued outbound messages need to be versioned to prevent data corruption
|
||||
/// or loss after forkless runtime upgrades
|
||||
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub enum VersionedQueuedMessage {
|
||||
V1(QueuedMessage),
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedQueuedMessage> for QueuedMessage {
|
||||
type Error = ();
|
||||
fn try_from(x: VersionedQueuedMessage) -> Result<Self, Self::Error> {
|
||||
use VersionedQueuedMessage::*;
|
||||
match x {
|
||||
V1(x) => Ok(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<QueuedMessage>> From<T> for VersionedQueuedMessage {
|
||||
fn from(x: T) -> Self {
|
||||
VersionedQueuedMessage::V1(x.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A message which can be accepted by implementations of `/[`SendMessage`\]`
|
||||
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub struct Message {
|
||||
/// ID for this message. One will be automatically generated if not provided.
|
||||
///
|
||||
/// When this message is created from an XCM message, the ID should be extracted
|
||||
/// from the `SetTopic` instruction.
|
||||
///
|
||||
/// The ID plays no role in bridge consensus, and is purely meant for message tracing.
|
||||
pub id: Option<H256>,
|
||||
/// The message channel ID
|
||||
pub channel_id: ChannelId,
|
||||
/// The stable ID for a receiving gateway contract
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
/// A command which is executable by the Gateway contract on Ethereum
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub enum Command {
|
||||
/// Execute a sub-command within an agent for a consensus system in Polkadot
|
||||
/// DEPRECATED in favour of `UnlockNativeToken`. We still have to keep it around in
|
||||
/// case buffered and uncommitted messages are using this variant.
|
||||
AgentExecute {
|
||||
/// The ID of the agent
|
||||
agent_id: H256,
|
||||
/// The sub-command to be executed
|
||||
command: AgentExecuteCommand,
|
||||
},
|
||||
/// Upgrade the Gateway contract
|
||||
Upgrade {
|
||||
/// Address of the new implementation contract
|
||||
impl_address: H160,
|
||||
/// Codehash of the implementation contract
|
||||
impl_code_hash: H256,
|
||||
/// Optionally invoke an initializer in the implementation contract
|
||||
initializer: Option<Initializer>,
|
||||
},
|
||||
/// Set the global operating mode of the Gateway contract
|
||||
SetOperatingMode {
|
||||
/// The new operating mode
|
||||
mode: OperatingMode,
|
||||
},
|
||||
/// Set token fees of the Gateway contract
|
||||
SetTokenTransferFees {
|
||||
/// The fee(DOT) for the cost of creating asset on AssetHub
|
||||
create_asset_xcm: u128,
|
||||
/// The fee(DOT) for the cost of sending asset on AssetHub
|
||||
transfer_asset_xcm: u128,
|
||||
/// The fee(Ether) for register token to discourage spamming
|
||||
register_token: U256,
|
||||
},
|
||||
/// Set pricing parameters
|
||||
SetPricingParameters {
|
||||
// ETH/DOT exchange rate
|
||||
exchange_rate: UD60x18,
|
||||
// Cost of delivering a message from Ethereum to BridgeHub, in ROC/KSM/DOT
|
||||
delivery_cost: u128,
|
||||
// Fee multiplier
|
||||
multiplier: UD60x18,
|
||||
},
|
||||
/// Transfer ERC20 tokens
|
||||
UnlockNativeToken {
|
||||
/// ID of the agent
|
||||
agent_id: H256,
|
||||
/// Address of the ERC20 token
|
||||
token: H160,
|
||||
/// The recipient of the tokens
|
||||
recipient: H160,
|
||||
/// The amount of tokens to transfer
|
||||
amount: u128,
|
||||
},
|
||||
/// Register foreign token from Polkadot
|
||||
RegisterForeignToken {
|
||||
/// ID for the token
|
||||
token_id: H256,
|
||||
/// Name of the token
|
||||
name: Vec<u8>,
|
||||
/// Short symbol for the token
|
||||
symbol: Vec<u8>,
|
||||
/// Number of decimal places
|
||||
decimals: u8,
|
||||
},
|
||||
/// Mint foreign token from Polkadot
|
||||
MintForeignToken {
|
||||
/// ID for the token
|
||||
token_id: H256,
|
||||
/// The recipient of the newly minted tokens
|
||||
recipient: H160,
|
||||
/// The amount of tokens to mint
|
||||
amount: u128,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Compute the enum variant index
|
||||
pub fn index(&self) -> u8 {
|
||||
match self {
|
||||
Command::AgentExecute { .. } => 0,
|
||||
Command::Upgrade { .. } => 1,
|
||||
Command::SetOperatingMode { .. } => 5,
|
||||
Command::SetTokenTransferFees { .. } => 7,
|
||||
Command::SetPricingParameters { .. } => 8,
|
||||
Command::UnlockNativeToken { .. } => 9,
|
||||
Command::RegisterForeignToken { .. } => 10,
|
||||
Command::MintForeignToken { .. } => 11,
|
||||
}
|
||||
}
|
||||
|
||||
/// ABI-encode the Command.
|
||||
pub fn abi_encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Command::AgentExecute { agent_id, command } => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(agent_id.as_bytes().to_owned()),
|
||||
Token::Bytes(command.abi_encode()),
|
||||
])]),
|
||||
Command::Upgrade {
|
||||
impl_address,
|
||||
impl_code_hash,
|
||||
initializer,
|
||||
..
|
||||
} => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::Address(*impl_address),
|
||||
Token::FixedBytes(impl_code_hash.as_bytes().to_owned()),
|
||||
initializer
|
||||
.clone()
|
||||
.map_or(Token::Bytes(vec![]), |i| Token::Bytes(i.params)),
|
||||
])]),
|
||||
Command::SetOperatingMode { mode } => {
|
||||
ethabi::encode(&[Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])])
|
||||
}
|
||||
Command::SetTokenTransferFees {
|
||||
create_asset_xcm,
|
||||
transfer_asset_xcm,
|
||||
register_token,
|
||||
} => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::Uint(U256::from(*create_asset_xcm)),
|
||||
Token::Uint(U256::from(*transfer_asset_xcm)),
|
||||
Token::Uint(*register_token),
|
||||
])]),
|
||||
Command::SetPricingParameters {
|
||||
exchange_rate,
|
||||
delivery_cost,
|
||||
multiplier,
|
||||
} => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::Uint(exchange_rate.clone().into_inner()),
|
||||
Token::Uint(U256::from(*delivery_cost)),
|
||||
Token::Uint(multiplier.clone().into_inner()),
|
||||
])]),
|
||||
Command::UnlockNativeToken {
|
||||
agent_id,
|
||||
token,
|
||||
recipient,
|
||||
amount,
|
||||
} => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(agent_id.as_bytes().to_owned()),
|
||||
Token::Address(*token),
|
||||
Token::Address(*recipient),
|
||||
Token::Uint(U256::from(*amount)),
|
||||
])]),
|
||||
Command::RegisterForeignToken {
|
||||
token_id,
|
||||
name,
|
||||
symbol,
|
||||
decimals,
|
||||
} => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(token_id.as_bytes().to_owned()),
|
||||
Token::String(name.to_owned()),
|
||||
Token::String(symbol.to_owned()),
|
||||
Token::Uint(U256::from(*decimals)),
|
||||
])]),
|
||||
Command::MintForeignToken {
|
||||
token_id,
|
||||
recipient,
|
||||
amount,
|
||||
} => ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(token_id.as_bytes().to_owned()),
|
||||
Token::Address(*recipient),
|
||||
Token::Uint(U256::from(*amount)),
|
||||
])]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a call to the initializer of an implementation contract.
|
||||
/// The initializer has the following ABI signature: `initialize(bytes)`.
|
||||
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Initializer {
|
||||
/// ABI-encoded params of type `bytes` to pass to the initializer
|
||||
pub params: Vec<u8>,
|
||||
/// The initializer is allowed to consume this much gas at most.
|
||||
pub maximum_required_gas: u64,
|
||||
}
|
||||
|
||||
/// A Sub-command executable within an agent
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub enum AgentExecuteCommand {
|
||||
/// Transfer ERC20 tokens
|
||||
TransferToken {
|
||||
/// Address of the ERC20 token
|
||||
token: H160,
|
||||
/// The recipient of the tokens
|
||||
recipient: H160,
|
||||
/// The amount of tokens to transfer
|
||||
amount: u128,
|
||||
},
|
||||
}
|
||||
|
||||
impl AgentExecuteCommand {
|
||||
fn index(&self) -> u8 {
|
||||
match self {
|
||||
AgentExecuteCommand::TransferToken { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// ABI-encode the sub-command
|
||||
pub fn abi_encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
AgentExecuteCommand::TransferToken {
|
||||
token,
|
||||
recipient,
|
||||
amount,
|
||||
} => ethabi::encode(&[
|
||||
Token::Uint(self.index().into()),
|
||||
Token::Bytes(ethabi::encode(&[
|
||||
Token::Address(*token),
|
||||
Token::Address(*recipient),
|
||||
Token::Uint(U256::from(*amount)),
|
||||
])),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Message which is awaiting processing in the MessageQueue pallet
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub struct QueuedMessage {
|
||||
/// Message ID
|
||||
pub id: H256,
|
||||
/// Channel ID
|
||||
pub channel_id: ChannelId,
|
||||
/// Command to execute in the Gateway contract
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
/// Fee for delivering message
|
||||
pub struct Fee<Balance>
|
||||
where
|
||||
Balance: BaseArithmetic + Unsigned + Copy,
|
||||
{
|
||||
/// Fee to cover cost of processing the message locally
|
||||
pub local: Balance,
|
||||
/// Fee to cover cost processing the message remotely
|
||||
pub remote: Balance,
|
||||
}
|
||||
|
||||
impl<Balance> Fee<Balance>
|
||||
where
|
||||
Balance: BaseArithmetic + Unsigned + Copy,
|
||||
{
|
||||
pub fn total(&self) -> Balance {
|
||||
self.local.saturating_add(self.remote)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Balance> From<(Balance, Balance)> for Fee<Balance>
|
||||
where
|
||||
Balance: BaseArithmetic + Unsigned + Copy,
|
||||
{
|
||||
fn from((local, remote): (Balance, Balance)) -> Self {
|
||||
Self { local, remote }
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for sending messages to Ethereum
|
||||
pub trait SendMessage: SendMessageFeeProvider {
|
||||
type Ticket: Clone + Encode + Decode;
|
||||
|
||||
/// Validate an outbound message and return a tuple:
|
||||
/// 1. Ticket for submitting the message
|
||||
/// 2. Delivery fee
|
||||
fn validate(
|
||||
message: &Message,
|
||||
) -> Result<(Self::Ticket, Fee<<Self as SendMessageFeeProvider>::Balance>), SendError>;
|
||||
|
||||
/// Submit the message ticket for eventual delivery to Ethereum
|
||||
fn deliver(ticket: Self::Ticket) -> Result<H256, SendError>;
|
||||
}
|
||||
|
||||
pub trait Ticket: Encode + Decode + Clone {
|
||||
fn message_id(&self) -> H256;
|
||||
}
|
||||
|
||||
pub trait GasMeter {
|
||||
/// All the gas used for submitting a message to Ethereum, minus the cost of dispatching
|
||||
/// the command within the message
|
||||
const MAXIMUM_BASE_GAS: u64;
|
||||
|
||||
/// Total gas consumed at most, including verification & dispatch
|
||||
fn maximum_gas_used_at_most(command: &Command) -> u64 {
|
||||
Self::MAXIMUM_BASE_GAS + Self::maximum_dispatch_gas_used_at_most(command)
|
||||
}
|
||||
|
||||
/// Measures the maximum amount of gas a command payload will require to *dispatch*, NOT
|
||||
/// including validation & verification.
|
||||
fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64;
|
||||
}
|
||||
|
||||
/// A meter that assigns a constant amount of gas for the execution of a command
|
||||
///
|
||||
/// The gas figures are extracted from this report:
|
||||
/// > forge test --match-path test/Gateway.t.sol --gas-report
|
||||
///
|
||||
/// A healthy buffer is added on top of these figures to account for:
|
||||
/// * The EIP-150 63/64 rule
|
||||
/// * Future EVM upgrades that may increase gas cost
|
||||
pub struct ConstantGasMeter;
|
||||
|
||||
impl GasMeter for ConstantGasMeter {
|
||||
// The base transaction cost, which includes:
|
||||
// 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000
|
||||
// for message verification
|
||||
const MAXIMUM_BASE_GAS: u64 = 185_000;
|
||||
|
||||
fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 {
|
||||
match command {
|
||||
Command::SetOperatingMode { .. } => 40_000,
|
||||
Command::AgentExecute { command, .. } => match command {
|
||||
// Execute IERC20.transferFrom
|
||||
//
|
||||
// Worst-case assumptions are important:
|
||||
// * No gas refund for clearing storage slot of source account in ERC20 contract
|
||||
// * Assume dest account in ERC20 contract does not yet have a storage slot
|
||||
// * ERC20.transferFrom possibly does other business logic besides updating balances
|
||||
AgentExecuteCommand::TransferToken { .. } => 200_000,
|
||||
},
|
||||
Command::Upgrade { initializer, .. } => {
|
||||
let initializer_max_gas = match *initializer {
|
||||
Some(Initializer {
|
||||
maximum_required_gas,
|
||||
..
|
||||
}) => maximum_required_gas,
|
||||
None => 0,
|
||||
};
|
||||
// total maximum gas must also include the gas used for updating the proxy before
|
||||
// the the initializer is called.
|
||||
50_000 + initializer_max_gas
|
||||
}
|
||||
Command::SetTokenTransferFees { .. } => 60_000,
|
||||
Command::SetPricingParameters { .. } => 60_000,
|
||||
Command::UnlockNativeToken { .. } => 200_000,
|
||||
Command::RegisterForeignToken { .. } => 1_200_000,
|
||||
Command::MintForeignToken { .. } => 100_000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GasMeter for () {
|
||||
const MAXIMUM_BASE_GAS: u64 = 1;
|
||||
|
||||
fn maximum_dispatch_gas_used_at_most(_: &Command) -> u64 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
pub const ETHER_DECIMALS: u8 = 18;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
pub mod message;
|
||||
|
||||
pub use message::*;
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v1::{Fee, Message as MessageV1, SendMessage as SendMessageV1},
|
||||
v2::{Message, SendMessage},
|
||||
SendMessageFeeProvider,
|
||||
};
|
||||
|
|
@ -29,3 +30,29 @@ impl SendMessageFeeProvider for MockOkOutboundQueue {
|
|||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockOkOutboundQueueV1;
|
||||
impl SendMessageV1 for MockOkOutboundQueueV1 {
|
||||
type Ticket = ();
|
||||
|
||||
fn validate(
|
||||
_: &MessageV1,
|
||||
) -> Result<
|
||||
(Self::Ticket, Fee<<Self as SendMessageFeeProvider>::Balance>),
|
||||
snowbridge_outbound_queue_primitives::SendError,
|
||||
> {
|
||||
Ok(((), Fee::from((0, 0))))
|
||||
}
|
||||
|
||||
fn deliver(_: Self::Ticket) -> Result<H256, snowbridge_outbound_queue_primitives::SendError> {
|
||||
Ok(H256::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl SendMessageFeeProvider for MockOkOutboundQueueV1 {
|
||||
type Balance = u128;
|
||||
|
||||
fn local_fee() -> Self::Balance {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ polkadot-runtime-common = { workspace = true }
|
|||
scale-info = { features = ["derive", "serde"], workspace = true }
|
||||
serde_json = { workspace = true, default-features = false, features = ["alloc"] }
|
||||
snowbridge-beacon-primitives = { workspace = true }
|
||||
snowbridge-core = { workspace = true }
|
||||
snowbridge-inbound-queue-primitives = { workspace = true }
|
||||
snowbridge-merkle-tree = { workspace = true }
|
||||
snowbridge-outbound-queue-primitives = { workspace = true }
|
||||
|
|
@ -68,6 +69,9 @@ snowbridge-outbound-queue-v2-runtime-api = { workspace = true }
|
|||
snowbridge-pallet-ethereum-client = { workspace = true }
|
||||
snowbridge-pallet-inbound-queue-v2 = { workspace = true }
|
||||
snowbridge-pallet-outbound-queue-v2 = { workspace = true }
|
||||
snowbridge-pallet-system = { workspace = true }
|
||||
snowbridge-pallet-system-v2 = { workspace = true }
|
||||
snowbridge-system-v2-runtime-api = { workspace = true }
|
||||
snowbridge-verification-primitives = { workspace = true }
|
||||
sp-api = { workspace = true }
|
||||
sp-block-builder = { workspace = true }
|
||||
|
|
@ -144,11 +148,15 @@ std = [
|
|||
"snowbridge-pallet-outbound-queue-v2/std",
|
||||
"snowbridge-merkle-tree/std",
|
||||
"snowbridge-outbound-queue-v2-runtime-api/std",
|
||||
"snowbridge-pallet-system/std",
|
||||
"snowbridge-pallet-system-v2/std",
|
||||
"snowbridge-system-v2-runtime-api/std",
|
||||
"dhp-bridge/std",
|
||||
"snowbridge-verification-primitives/std",
|
||||
"sp-api/std",
|
||||
"sp-block-builder/std",
|
||||
"sp-consensus-babe/std",
|
||||
"sp-consensus-beefy/std",
|
||||
"sp-consensus-grandpa/std",
|
||||
"sp-core/std",
|
||||
"sp-genesis-builder/std",
|
||||
|
|
@ -191,12 +199,13 @@ runtime-benchmarks = [
|
|||
"pallet-utility/runtime-benchmarks",
|
||||
"polkadot-primitives/runtime-benchmarks",
|
||||
"polkadot-runtime-common/runtime-benchmarks",
|
||||
"polkadot-primitives/runtime-benchmarks",
|
||||
"snowbridge-inbound-queue-primitives/runtime-benchmarks",
|
||||
"snowbridge-pallet-ethereum-client/runtime-benchmarks",
|
||||
"snowbridge-pallet-inbound-queue-v2/runtime-benchmarks",
|
||||
"snowbridge-pallet-system-v2/runtime-benchmarks",
|
||||
"snowbridge-pallet-outbound-queue-v2/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"snowbridge-pallet-system/runtime-benchmarks",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
|
|
@ -230,8 +239,10 @@ try-runtime = [
|
|||
"polkadot-runtime-common/try-runtime",
|
||||
"snowbridge-pallet-ethereum-client/try-runtime",
|
||||
"snowbridge-pallet-inbound-queue-v2/try-runtime",
|
||||
"snowbridge-pallet-system-v2/try-runtime",
|
||||
"snowbridge-pallet-outbound-queue-v2/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
"snowbridge-pallet-system/try-runtime",
|
||||
]
|
||||
|
||||
fast-runtime = [
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ use pallet_evm::FeeCalculator;
|
|||
use pallet_evm::Runner;
|
||||
use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
|
||||
use polkadot_primitives::Hash;
|
||||
use snowbridge_core::AgentId;
|
||||
use sp_api::impl_runtime_apis;
|
||||
use sp_consensus_beefy::{
|
||||
ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature},
|
||||
|
|
@ -65,6 +66,7 @@ use sp_runtime::{
|
|||
ApplyExtrinsicResult, Permill,
|
||||
};
|
||||
use sp_version::RuntimeVersion;
|
||||
use xcm::VersionedLocation;
|
||||
/// MMR helper types.
|
||||
mod mmr {
|
||||
use super::Runtime;
|
||||
|
|
@ -493,6 +495,12 @@ impl_runtime_apis! {
|
|||
}
|
||||
}
|
||||
|
||||
impl snowbridge_system_v2_runtime_api::ControlV2Api<Block> for Runtime {
|
||||
fn agent_id(location: VersionedLocation) -> Option<AgentId> {
|
||||
snowbridge_pallet_system_v2::api::agent_id::<Runtime>(location)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl frame_benchmarking::Benchmark<Block> for Runtime {
|
||||
fn benchmark_metadata(extra: bool) -> (
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@
|
|||
|
||||
mod runtime_params;
|
||||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundQueueV2, PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent,
|
||||
RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys,
|
||||
Signature, System, Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT, SLOT_DURATION,
|
||||
STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
gas::WEIGHT_PER_GAS,
|
||||
|
|
@ -47,7 +55,7 @@ use frame_support::{
|
|||
};
|
||||
use frame_system::{
|
||||
limits::{BlockLength, BlockWeights},
|
||||
EnsureRoot,
|
||||
EnsureRoot, EnsureRootWithSuccess,
|
||||
};
|
||||
use pallet_ethereum::PostLogContent;
|
||||
use pallet_evm::{
|
||||
|
|
@ -61,14 +69,22 @@ use pallet_transaction_payment::{
|
|||
ConstFeeMultiplier, FungibleAdapter, Multiplier, Pallet as TransactionPayment,
|
||||
};
|
||||
use polkadot_primitives::Moment;
|
||||
use runtime_params::RuntimeParameters;
|
||||
use snowbridge_beacon_primitives::{Fork, ForkVersions};
|
||||
use snowbridge_core::{gwei, meth, AgentIdOf, PricingParameters, Rewards};
|
||||
use snowbridge_inbound_queue_primitives::RewardLedger;
|
||||
use snowbridge_outbound_queue_primitives::v2::ConstantGasMeter;
|
||||
use snowbridge_outbound_queue_primitives::{
|
||||
v1::{Fee, Message, SendMessage},
|
||||
v2::ConstantGasMeter,
|
||||
SendError, SendMessageFeeProvider,
|
||||
};
|
||||
use snowbridge_pallet_system::BalanceOf;
|
||||
use sp_consensus_beefy::{
|
||||
ecdsa_crypto::AuthorityId as BeefyId,
|
||||
mmr::{BeefyDataProvider, MmrLeafVersion},
|
||||
};
|
||||
use sp_core::{crypto::KeyTypeId, Get, H160, H256, U256};
|
||||
use sp_runtime::FixedU128;
|
||||
use sp_runtime::{
|
||||
traits::{ConvertInto, IdentityLookup, Keccak256, One, OpaqueKeys, UniqueSaturatedInto},
|
||||
FixedPointNumber, Perbill,
|
||||
|
|
@ -80,16 +96,7 @@ use sp_std::{
|
|||
};
|
||||
use sp_version::RuntimeVersion;
|
||||
use xcm::latest::NetworkId;
|
||||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundQueueV2, PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent,
|
||||
RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys,
|
||||
Signature, System, Timestamp, ValidatorSet, EXISTENTIAL_DEPOSIT, SLOT_DURATION,
|
||||
STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use runtime_params::RuntimeParameters;
|
||||
use xcm::prelude::*;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use bridge_hub_common::AggregateMessageOrigin;
|
||||
|
|
@ -619,6 +626,72 @@ impl pallet_evm_chain_id::Config for Runtime {}
|
|||
//║ SNOWBRIDGE PALLETS ║
|
||||
//╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
// --- Snowbridge Config Constants & Parameter Types ---
|
||||
parameter_types! {
|
||||
pub UniversalLocation: InteriorLocation = Here.into();
|
||||
pub InboundDeliveryCost: BalanceOf<Runtime> = 0;
|
||||
pub RootLocation: Location = Location::here();
|
||||
pub Parameters: PricingParameters<u128> = PricingParameters {
|
||||
exchange_rate: FixedU128::from_rational(1, 400),
|
||||
fee_per_gas: gwei(20),
|
||||
rewards: Rewards { local: 1 * UNIT, remote: meth(1) },
|
||||
multiplier: FixedU128::from_rational(1, 1),
|
||||
};
|
||||
pub EthereumLocation: Location = Location::new(1, EthereumNetwork::get());
|
||||
pub TreasuryAccountId: AccountId = AccountId::from([0u8; 20]);
|
||||
}
|
||||
|
||||
pub struct DoNothingOutboundQueue;
|
||||
impl SendMessage for DoNothingOutboundQueue {
|
||||
type Ticket = ();
|
||||
|
||||
fn validate(
|
||||
_: &Message,
|
||||
) -> Result<(Self::Ticket, Fee<<Self as SendMessageFeeProvider>::Balance>), SendError> {
|
||||
Ok(((), Fee::from((0, 0))))
|
||||
}
|
||||
|
||||
fn deliver(_: Self::Ticket) -> Result<H256, snowbridge_outbound_queue_primitives::SendError> {
|
||||
Ok(H256::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl SendMessageFeeProvider for DoNothingOutboundQueue {
|
||||
type Balance = u128;
|
||||
|
||||
fn local_fee() -> Self::Balance {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the Snowbridge System V1 config trait
|
||||
impl snowbridge_pallet_system::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = DoNothingOutboundQueue;
|
||||
type SiblingOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type AgentIdOf = AgentIdOf;
|
||||
type Token = Balances;
|
||||
type TreasuryAccount = TreasuryAccountId;
|
||||
type DefaultPricingParameters = Parameters;
|
||||
type InboundDeliveryCost = InboundDeliveryCost;
|
||||
type WeightInfo = ();
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type EthereumLocation = EthereumLocation;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = ();
|
||||
}
|
||||
|
||||
// Implement the Snowbridge System v2 config trait
|
||||
impl snowbridge_pallet_system_v2::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = OutboundQueueV2;
|
||||
type FrontendOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type GovernanceOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type Helper = ();
|
||||
}
|
||||
|
||||
// For tests, benchmarks and fast-runtime configurations we use the mocked fork versions
|
||||
#[cfg(any(
|
||||
feature = "std",
|
||||
|
|
@ -742,6 +815,7 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime {
|
|||
type MaxMessagesPerBlock = ConstU32<32>;
|
||||
type OnNewCommitment = ();
|
||||
type WeightToFee = IdentityFee<Balance>;
|
||||
type WeightInfo = ();
|
||||
type Verifier = EthereumBeaconClient;
|
||||
type GatewayAddress = runtime_params::dynamic_params::runtime_config::EthereumGatewayAddress;
|
||||
type RewardKind = ();
|
||||
|
|
@ -749,7 +823,6 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime {
|
|||
type RewardPayment = DummyRewardPayment;
|
||||
type EthereumNetwork = EthereumNetwork;
|
||||
type ConvertAssetId = ();
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
//╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
|
|
@ -762,15 +835,28 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime {
|
|||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmark_helpers {
|
||||
use crate::RuntimeOrigin;
|
||||
use crate::{EthereumBeaconClient, Runtime};
|
||||
use snowbridge_beacon_primitives::BeaconHeader;
|
||||
use snowbridge_pallet_inbound_queue_v2::BenchmarkHelper as InboundQueueBenchmarkHelperV2;
|
||||
// use snowbridge_pallet_outbound_queue_v2::BenchmarkHelper as OutboundQueueBenchmarkHelperV2;
|
||||
use sp_core::H256;
|
||||
use xcm::opaque::latest::Location;
|
||||
|
||||
impl<T: snowbridge_pallet_inbound_queue_v2::Config> InboundQueueBenchmarkHelperV2<T> for Runtime {
|
||||
fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) {
|
||||
EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl snowbridge_pallet_system::BenchmarkHelper<RuntimeOrigin> for () {
|
||||
fn make_xcm_origin(_location: Location) -> RuntimeOrigin {
|
||||
RuntimeOrigin::root()
|
||||
}
|
||||
}
|
||||
|
||||
impl snowbridge_pallet_system_v2::BenchmarkHelper<RuntimeOrigin> for () {
|
||||
fn make_xcm_origin(_location: Location) -> RuntimeOrigin {
|
||||
RuntimeOrigin::root()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@ where
|
|||
UncheckedExtrinsic::new_bare(call)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the runtime by composing the FRAME pallets that were previously configured.
|
||||
#[frame_support::runtime]
|
||||
mod runtime {
|
||||
|
|
@ -332,6 +333,12 @@ mod runtime {
|
|||
|
||||
#[runtime::pallet_index(62)]
|
||||
pub type OutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(63)]
|
||||
pub type SnowbridgeSystem = snowbridge_pallet_system;
|
||||
|
||||
#[runtime::pallet_index(64)]
|
||||
pub type SnowbridgeSystemV2 = snowbridge_pallet_system_v2;
|
||||
// ╚══════════════════════ Snowbridge Pallets ═══════════════════════╝
|
||||
|
||||
// ╔══════════════════════ StorageHub Pallets ═══════════════════════╗
|
||||
|
|
|
|||
Loading…
Reference in a new issue