mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 09:51:02 +00:00
Adds Snowbridge to Rococo runtime (#2522)
# Description Adds Snowbridge to the Rococo bridge hub runtime. Includes config changes required in Rococo asset hub. --------- Co-authored-by: Alistair Singh <alistair.singh7@gmail.com> Co-authored-by: ron <yrong1997@gmail.com> Co-authored-by: Vincent Geddes <vincent.geddes@hey.com> Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
[package]
|
||||
name = "snowbridge-core"
|
||||
description = "Snowbridge Core"
|
||||
version = "0.1.1"
|
||||
authors = ["Snowfork <contact@snowfork.com>"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.188", optional = true, features = ["alloc", "derive"], default-features = false }
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
|
||||
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
|
||||
hex-literal = { version = "0.4.1" }
|
||||
|
||||
polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false }
|
||||
xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false }
|
||||
xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false }
|
||||
|
||||
frame-support = { path = "../../../../../substrate/frame/support", default-features = false }
|
||||
frame-system = { path = "../../../../../substrate/frame/system", default-features = false }
|
||||
sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false }
|
||||
sp-std = { path = "../../../../../substrate/primitives/std", default-features = false }
|
||||
sp-io = { path = "../../../../../substrate/primitives/io", default-features = false }
|
||||
sp-core = { path = "../../../../../substrate/primitives/core", default-features = false }
|
||||
sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false }
|
||||
|
||||
snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false }
|
||||
|
||||
ethabi = { git = "https://github.com/Snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { version = "0.4.3" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"ethabi/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"polkadot-parachain-primitives/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"snowbridge-beacon-primitives/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"xcm-builder/std",
|
||||
"xcm/std",
|
||||
]
|
||||
serde = ["dep:serde", "scale-info/serde"]
|
||||
runtime-benchmarks = [
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"polkadot-parachain-primitives/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"xcm-builder/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! Types for representing inbound messages
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::PalletError;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{H160, H256};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
/// A trait for verifying inbound messages from Ethereum.
|
||||
pub trait Verifier {
|
||||
fn verify(event: &Log, proof: &Proof) -> Result<(), VerificationError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, PalletError, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub enum VerificationError {
|
||||
/// Execution header is missing
|
||||
HeaderNotFound,
|
||||
/// Event log was not found in the verified transaction receipt
|
||||
LogNotFound,
|
||||
/// Event log has an invalid format
|
||||
InvalidLog,
|
||||
/// Unable to verify the transaction receipt with the provided proof
|
||||
InvalidProof,
|
||||
}
|
||||
|
||||
pub type MessageNonce = u64;
|
||||
|
||||
/// A bridge message from the Gateway contract on Ethereum
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Message {
|
||||
/// Event log emitted by Gateway contract
|
||||
pub event_log: Log,
|
||||
/// Inclusion proof for a transaction receipt containing the event log
|
||||
pub proof: Proof,
|
||||
}
|
||||
|
||||
const MAX_TOPICS: usize = 4;
|
||||
|
||||
#[derive(Clone, RuntimeDebug)]
|
||||
pub enum LogValidationError {
|
||||
TooManyTopics,
|
||||
}
|
||||
|
||||
/// Event log
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Log {
|
||||
pub address: H160,
|
||||
pub topics: Vec<H256>,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Log {
|
||||
pub fn validate(&self) -> Result<(), LogValidationError> {
|
||||
if self.topics.len() > MAX_TOPICS {
|
||||
return Err(LogValidationError::TooManyTopics)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Inclusion proof for a transaction receipt
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Proof {
|
||||
// The block hash of the block in which the receipt was included.
|
||||
pub block_hash: H256,
|
||||
// The index of the transaction (and receipt) within the block.
|
||||
pub tx_index: u32,
|
||||
// Proof keys and values (receipts tree)
|
||||
pub data: (Vec<Vec<u8>>, Vec<Vec<u8>>),
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
//! # Core
|
||||
//!
|
||||
//! Common traits and types
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod inbound;
|
||||
pub mod operating_mode;
|
||||
pub mod outbound;
|
||||
pub mod pricing;
|
||||
pub mod ringbuffer;
|
||||
|
||||
pub use polkadot_parachain_primitives::primitives::{
|
||||
Id as ParaId, IsSystem, Sibling as SiblingParaId,
|
||||
};
|
||||
pub use ringbuffer::{RingBufferMap, RingBufferMapImpl};
|
||||
pub use sp_core::U256;
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::traits::Contains;
|
||||
use hex_literal::hex;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::H256;
|
||||
use sp_io::hashing::keccak_256;
|
||||
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
|
||||
use sp_std::prelude::*;
|
||||
use xcm::prelude::{
|
||||
Junction::Parachain,
|
||||
Junctions::{Here, X1},
|
||||
MultiLocation,
|
||||
};
|
||||
use xcm_builder::{DescribeAllTerminal, DescribeFamily, DescribeLocation, HashedDescription};
|
||||
|
||||
/// The ID of an agent contract
|
||||
pub type AgentId = H256;
|
||||
pub use operating_mode::BasicOperatingMode;
|
||||
|
||||
pub use pricing::{PricingParameters, Rewards};
|
||||
|
||||
pub fn sibling_sovereign_account<T>(para_id: ParaId) -> T::AccountId
|
||||
where
|
||||
T: frame_system::Config,
|
||||
{
|
||||
SiblingParaId::from(para_id).into_account_truncating()
|
||||
}
|
||||
|
||||
pub fn sibling_sovereign_account_raw(para_id: ParaId) -> [u8; 32] {
|
||||
SiblingParaId::from(para_id).into_account_truncating()
|
||||
}
|
||||
|
||||
pub struct AllowSiblingsOnly;
|
||||
impl Contains<MultiLocation> for AllowSiblingsOnly {
|
||||
fn contains(location: &MultiLocation) -> bool {
|
||||
matches!(location, MultiLocation { parents: 1, interior: X1(Parachain(_)) })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gwei(x: u128) -> U256 {
|
||||
U256::from(1_000_000_000u128).saturating_mul(x.into())
|
||||
}
|
||||
|
||||
pub fn meth(x: u128) -> U256 {
|
||||
U256::from(1_000_000_000_000_000u128).saturating_mul(x.into())
|
||||
}
|
||||
|
||||
pub fn eth(x: u128) -> U256 {
|
||||
U256::from(1_000_000_000_000_000_000u128).saturating_mul(x.into())
|
||||
}
|
||||
|
||||
pub const ROC: u128 = 1_000_000_000_000;
|
||||
|
||||
/// Identifier for a message channel
|
||||
#[derive(
|
||||
Clone, Copy, Encode, Decode, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
pub struct ChannelId([u8; 32]);
|
||||
|
||||
/// Deterministically derive a ChannelId for a sibling parachain
|
||||
/// Generator: keccak256("para" + big_endian_bytes(para_id))
|
||||
///
|
||||
/// The equivalent generator on the Solidity side is in
|
||||
/// contracts/src/Types.sol:into().
|
||||
fn derive_channel_id_for_sibling(para_id: ParaId) -> ChannelId {
|
||||
let para_id: u32 = para_id.into();
|
||||
let para_id_bytes: [u8; 4] = para_id.to_be_bytes();
|
||||
let prefix: [u8; 4] = *b"para";
|
||||
let preimage: Vec<u8> = prefix.into_iter().chain(para_id_bytes).collect();
|
||||
keccak_256(&preimage).into()
|
||||
}
|
||||
|
||||
impl ChannelId {
|
||||
pub const fn new(id: [u8; 32]) -> Self {
|
||||
ChannelId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParaId> for ChannelId {
|
||||
fn from(value: ParaId) -> Self {
|
||||
derive_channel_id_for_sibling(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for ChannelId {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
ChannelId(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelId> for [u8; 32] {
|
||||
fn from(value: ChannelId) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8; 32]> for ChannelId {
|
||||
fn from(value: &'a [u8; 32]) -> Self {
|
||||
ChannelId(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<H256> for ChannelId {
|
||||
fn from(value: H256) -> Self {
|
||||
ChannelId(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for ChannelId {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct Channel {
|
||||
/// ID of the agent contract deployed on Ethereum
|
||||
pub agent_id: AgentId,
|
||||
/// ID of the parachain who will receive or send messages using this channel
|
||||
pub para_id: ParaId,
|
||||
}
|
||||
|
||||
pub trait StaticLookup {
|
||||
/// Type to lookup from.
|
||||
type Source;
|
||||
/// Type to lookup into.
|
||||
type Target;
|
||||
/// Attempt a lookup.
|
||||
fn lookup(s: Self::Source) -> Option<Self::Target>;
|
||||
}
|
||||
|
||||
/// Channel for high-priority governance commands
|
||||
pub const PRIMARY_GOVERNANCE_CHANNEL: ChannelId =
|
||||
ChannelId::new(hex!("0000000000000000000000000000000000000000000000000000000000000001"));
|
||||
|
||||
/// Channel for lower-priority governance commands
|
||||
pub const SECONDARY_GOVERNANCE_CHANNEL: ChannelId =
|
||||
ChannelId::new(hex!("0000000000000000000000000000000000000000000000000000000000000002"));
|
||||
|
||||
pub struct DescribeHere;
|
||||
impl DescribeLocation for DescribeHere {
|
||||
fn describe_location(l: &MultiLocation) -> Option<Vec<u8>> {
|
||||
match (l.parents, l.interior) {
|
||||
(0, Here) => Some(Vec::<u8>::new().encode()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an AgentId from a MultiLocation. An AgentId is a unique mapping to a Agent contract on
|
||||
/// Ethereum which acts as the sovereign account for the MultiLocation.
|
||||
pub type AgentIdOf = HashedDescription<H256, (DescribeHere, DescribeFamily<DescribeAllTerminal>)>;
|
||||
@@ -0,0 +1,25 @@
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
/// Basic operating modes for a bridges module (Normal/Halted).
|
||||
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum BasicOperatingMode {
|
||||
/// Normal mode, when all operations are allowed.
|
||||
Normal,
|
||||
/// The pallet is halted. All non-governance operations are disabled.
|
||||
Halted,
|
||||
}
|
||||
|
||||
impl Default for BasicOperatingMode {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicOperatingMode {
|
||||
pub fn is_halted(&self) -> bool {
|
||||
*self == BasicOperatingMode::Halted
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::PalletError;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
|
||||
use sp_core::{RuntimeDebug, H256};
|
||||
pub use v1::{AgentExecuteCommand, Command, Initializer, Message, OperatingMode, QueuedMessage};
|
||||
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
|
||||
mod v1 {
|
||||
use crate::{pricing::UD60x18, ChannelId};
|
||||
use codec::{Decode, Encode};
|
||||
use ethabi::Token;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{RuntimeDebug, H160, H256, U256};
|
||||
use sp_std::{borrow::ToOwned, vec, vec::Vec};
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// The operating mode of Channels and Gateway contract on Ethereum.
|
||||
#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub enum OperatingMode {
|
||||
/// Normal operations. Allow sending and receiving messages.
|
||||
Normal,
|
||||
/// Reject outbound messages. This allows receiving governance messages but does now allow
|
||||
/// enqueuing of new messages from the Ethereum side. This can be used to close off an
|
||||
/// deprecated channel or pause the bridge for upgrade operations.
|
||||
RejectingOutboundMessages,
|
||||
}
|
||||
|
||||
/// 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
|
||||
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>,
|
||||
},
|
||||
/// Create an agent representing a consensus system on Polkadot
|
||||
CreateAgent {
|
||||
/// The ID of the agent, derived from the `MultiLocation` of the consensus system on
|
||||
/// Polkadot
|
||||
agent_id: H256,
|
||||
},
|
||||
/// Create bidirectional messaging channel to a parachain
|
||||
CreateChannel {
|
||||
/// The ID of the channel
|
||||
channel_id: ChannelId,
|
||||
/// The agent ID of the parachain
|
||||
agent_id: H256,
|
||||
/// Initial operating mode
|
||||
mode: OperatingMode,
|
||||
},
|
||||
/// Update the configuration of a channel
|
||||
UpdateChannel {
|
||||
/// The ID of the channel
|
||||
channel_id: ChannelId,
|
||||
/// The new operating mode
|
||||
mode: OperatingMode,
|
||||
},
|
||||
/// Set the global operating mode of the Gateway contract
|
||||
SetOperatingMode {
|
||||
/// The new operating mode
|
||||
mode: OperatingMode,
|
||||
},
|
||||
/// Transfer ether from an agent contract to a recipient account
|
||||
TransferNativeFromAgent {
|
||||
/// The agent ID
|
||||
agent_id: H256,
|
||||
/// The recipient of the ether
|
||||
recipient: H160,
|
||||
/// The amount to transfer
|
||||
amount: u128,
|
||||
},
|
||||
/// 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,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Compute the enum variant index
|
||||
pub fn index(&self) -> u8 {
|
||||
match self {
|
||||
Command::AgentExecute { .. } => 0,
|
||||
Command::Upgrade { .. } => 1,
|
||||
Command::CreateAgent { .. } => 2,
|
||||
Command::CreateChannel { .. } => 3,
|
||||
Command::UpdateChannel { .. } => 4,
|
||||
Command::SetOperatingMode { .. } => 5,
|
||||
Command::TransferNativeFromAgent { .. } => 6,
|
||||
Command::SetTokenTransferFees { .. } => 7,
|
||||
Command::SetPricingParameters { .. } => 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::CreateAgent { agent_id } =>
|
||||
ethabi::encode(&[Token::Tuple(vec![Token::FixedBytes(
|
||||
agent_id.as_bytes().to_owned(),
|
||||
)])]),
|
||||
Command::CreateChannel { channel_id, agent_id, mode } =>
|
||||
ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(channel_id.as_ref().to_owned()),
|
||||
Token::FixedBytes(agent_id.as_bytes().to_owned()),
|
||||
Token::Uint(U256::from((*mode) as u64)),
|
||||
])]),
|
||||
Command::UpdateChannel { channel_id, mode } =>
|
||||
ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(channel_id.as_ref().to_owned()),
|
||||
Token::Uint(U256::from((*mode) as u64)),
|
||||
])]),
|
||||
Command::SetOperatingMode { mode } =>
|
||||
ethabi::encode(&[Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])]),
|
||||
Command::TransferNativeFromAgent { agent_id, recipient, amount } =>
|
||||
ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::FixedBytes(agent_id.as_bytes().to_owned()),
|
||||
Token::Address(*recipient),
|
||||
Token::Uint(U256::from(*amount)),
|
||||
])]),
|
||||
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 } =>
|
||||
ethabi::encode(&[Token::Tuple(vec![
|
||||
Token::Uint(exchange_rate.clone().into_inner()),
|
||||
Token::Uint(U256::from(*delivery_cost)),
|
||||
])]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a call to the initializer of an implementation contract.
|
||||
/// The initializer has the following ABI signature: `initialize(bytes)`.
|
||||
#[derive(Clone, Encode, Decode, 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,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// A trait for getting the local costs associated with sending a message.
|
||||
pub trait SendMessageFeeProvider {
|
||||
type Balance: BaseArithmetic + Unsigned + Copy;
|
||||
|
||||
/// The local component of the message processing fees in native currency
|
||||
fn local_fee() -> Self::Balance;
|
||||
}
|
||||
|
||||
/// Reasons why sending to Ethereum could not be initiated
|
||||
#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, PalletError, TypeInfo)]
|
||||
pub enum SendError {
|
||||
/// Message is too large to be safely executed on Ethereum
|
||||
MessageTooLarge,
|
||||
/// The bridge has been halted for maintenance
|
||||
Halted,
|
||||
/// Invalid Channel
|
||||
InvalidChannel,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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, AFTER
|
||||
/// 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::CreateAgent { .. } => 275_000,
|
||||
Command::CreateChannel { .. } => 100_000,
|
||||
Command::UpdateChannel { .. } => 50_000,
|
||||
Command::TransferNativeFromAgent { .. } => 60_000,
|
||||
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 { .. } => 100_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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,67 @@
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_arithmetic::traits::{BaseArithmetic, Unsigned, Zero};
|
||||
use sp_core::U256;
|
||||
use sp_runtime::{FixedU128, RuntimeDebug};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct PricingParameters<Balance> {
|
||||
/// ETH/DOT exchange rate
|
||||
pub exchange_rate: FixedU128,
|
||||
/// Relayer rewards
|
||||
pub rewards: Rewards<Balance>,
|
||||
/// Ether (wei) fee per gas unit
|
||||
pub fee_per_gas: U256,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct Rewards<Balance> {
|
||||
/// Local reward in DOT
|
||||
pub local: Balance,
|
||||
/// Remote reward in ETH (wei)
|
||||
pub remote: U256,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct InvalidPricingParameters;
|
||||
|
||||
impl<Balance> PricingParameters<Balance>
|
||||
where
|
||||
Balance: BaseArithmetic + Unsigned + Copy,
|
||||
{
|
||||
pub fn validate(&self) -> Result<(), InvalidPricingParameters> {
|
||||
if self.exchange_rate == FixedU128::zero() {
|
||||
return Err(InvalidPricingParameters)
|
||||
}
|
||||
if self.fee_per_gas == U256::zero() {
|
||||
return Err(InvalidPricingParameters)
|
||||
}
|
||||
if self.rewards.local.is_zero() {
|
||||
return Err(InvalidPricingParameters)
|
||||
}
|
||||
if self.rewards.remote.is_zero() {
|
||||
return Err(InvalidPricingParameters)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Holder for fixed point number implemented in <https://github.com/PaulRBerg/prb-math>
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub struct UD60x18(U256);
|
||||
|
||||
impl From<FixedU128> for UD60x18 {
|
||||
fn from(value: FixedU128) -> Self {
|
||||
// Both FixedU128 and UD60x18 have 18 decimal places
|
||||
let inner: u128 = value.into_inner();
|
||||
UD60x18(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl UD60x18 {
|
||||
pub fn into_inner(self) -> U256 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
||||
use codec::FullCodec;
|
||||
use core::{cmp::Ord, marker::PhantomData, ops::Add};
|
||||
use frame_support::storage::{types::QueryKindTrait, StorageMap, StorageValue};
|
||||
use sp_core::{Get, GetDefault};
|
||||
use sp_runtime::traits::{One, Zero};
|
||||
|
||||
/// Trait object presenting the ringbuffer interface.
|
||||
pub trait RingBufferMap<Key, Value, QueryKind>
|
||||
where
|
||||
Key: FullCodec,
|
||||
Value: FullCodec,
|
||||
QueryKind: QueryKindTrait<Value, GetDefault>,
|
||||
{
|
||||
/// Insert a map entry.
|
||||
fn insert(k: Key, v: Value);
|
||||
|
||||
/// Check if map contains a key
|
||||
fn contains_key(k: Key) -> bool;
|
||||
|
||||
/// Get the value of the key
|
||||
fn get(k: Key) -> QueryKind::Query;
|
||||
}
|
||||
|
||||
pub struct RingBufferMapImpl<Index, B, CurrentIndex, Intermediate, M, QueryKind>(
|
||||
PhantomData<(Index, B, CurrentIndex, Intermediate, M, QueryKind)>,
|
||||
);
|
||||
|
||||
/// Ringbuffer implementation based on `RingBufferTransient`
|
||||
impl<Key, Value, Index, B, CurrentIndex, Intermediate, M, QueryKind>
|
||||
RingBufferMap<Key, Value, QueryKind>
|
||||
for RingBufferMapImpl<Index, B, CurrentIndex, Intermediate, M, QueryKind>
|
||||
where
|
||||
Key: FullCodec + Clone,
|
||||
Value: FullCodec,
|
||||
Index: Ord + One + Zero + Add<Output = Index> + Copy + FullCodec + Eq,
|
||||
B: Get<Index>,
|
||||
CurrentIndex: StorageValue<Index, Query = Index>,
|
||||
Intermediate: StorageMap<Index, Key, Query = Key>,
|
||||
M: StorageMap<Key, Value, Query = QueryKind::Query>,
|
||||
QueryKind: QueryKindTrait<Value, GetDefault>,
|
||||
{
|
||||
/// Insert a map entry.
|
||||
fn insert(k: Key, v: Value) {
|
||||
let bound = B::get();
|
||||
let mut current_index = CurrentIndex::get();
|
||||
|
||||
// Adding one here as bound denotes number of items but our index starts with zero.
|
||||
if (current_index + Index::one()) >= bound {
|
||||
current_index = Index::zero();
|
||||
} else {
|
||||
current_index = current_index + Index::one();
|
||||
}
|
||||
|
||||
// Deleting earlier entry if it exists
|
||||
if Intermediate::contains_key(current_index) {
|
||||
let older_key = Intermediate::get(current_index);
|
||||
M::remove(older_key);
|
||||
}
|
||||
|
||||
Intermediate::insert(current_index, k.clone());
|
||||
CurrentIndex::set(current_index);
|
||||
M::insert(k, v);
|
||||
}
|
||||
|
||||
/// Check if map contains a key
|
||||
fn contains_key(k: Key) -> bool {
|
||||
M::contains_key(k)
|
||||
}
|
||||
|
||||
/// Get the value associated with key
|
||||
fn get(k: Key) -> M::Query {
|
||||
M::get(k)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::{ChannelId, ParaId};
|
||||
use hex_literal::hex;
|
||||
|
||||
const EXPECT_CHANNEL_ID: [u8; 32] =
|
||||
hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539");
|
||||
|
||||
// The Solidity equivalent code is tested in Gateway.t.sol:testDeriveChannelID
|
||||
#[test]
|
||||
fn generate_channel_id() {
|
||||
let para_id: ParaId = 1000.into();
|
||||
let channel_id: ChannelId = para_id.into();
|
||||
assert_eq!(channel_id, EXPECT_CHANNEL_ID.into());
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use frame_support::traits::Contains;
|
||||
use snowbridge_core::AllowSiblingsOnly;
|
||||
use xcm::prelude::{Junction::Parachain, Junctions::X1, MultiLocation};
|
||||
|
||||
#[test]
|
||||
fn allow_siblings_predicate_only_allows_siblings() {
|
||||
let sibling = MultiLocation::new(1, X1(Parachain(1000)));
|
||||
let child = MultiLocation::new(0, X1(Parachain(1000)));
|
||||
assert!(AllowSiblingsOnly::contains(&sibling), "Sibling returns true.");
|
||||
assert!(!AllowSiblingsOnly::contains(&child), "Child returns false.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user