From a0deabaeb1b38ab4cbd946d50483adf3199e6d49 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 23 Oct 2024 16:49:28 +0800 Subject: [PATCH] Seperate V1&V2 more clearly --- .../outbound-queue-v2/src/benchmarking.rs | 2 +- .../pallets/outbound-queue-v2/src/lib.rs | 2 +- .../pallets/outbound-queue-v2/src/mock.rs | 2 +- .../src/send_message_impl.rs | 6 +- .../pallets/outbound-queue-v2/src/test.rs | 2 +- .../pallets/outbound-queue-v2/src/types.rs | 2 +- .../outbound-queue/runtime-api/src/lib.rs | 2 +- .../pallets/outbound-queue/src/api.rs | 2 +- .../outbound-queue/src/benchmarking.rs | 2 +- .../pallets/outbound-queue/src/lib.rs | 2 +- .../pallets/outbound-queue/src/mock.rs | 2 +- .../outbound-queue/src/send_message_impl.rs | 2 +- .../pallets/outbound-queue/src/test.rs | 2 +- bridges/snowbridge/pallets/system/src/lib.rs | 5 +- bridges/snowbridge/primitives/core/src/lib.rs | 1 - .../primitives/core/src/outbound.rs | 475 ------------------ .../primitives/core/src/outbound/mod.rs | 24 + .../primitives/core/src/outbound/v1.rs | 456 +++++++++++++++++ .../primitives/core/src/outbound/v2.rs | 302 +++++++++++ .../primitives/core/src/outbound_v2.rs | 310 ------------ .../primitives/router-v2/src/outbound/mod.rs | 2 +- .../primitives/router/src/outbound/mod.rs | 2 +- .../runtime/runtime-common/src/lib.rs | 2 +- .../src/bridge_to_ethereum_config.rs | 4 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- 25 files changed, 807 insertions(+), 808 deletions(-) delete mode 100644 bridges/snowbridge/primitives/core/src/outbound.rs create mode 100644 bridges/snowbridge/primitives/core/src/outbound/mod.rs create mode 100644 bridges/snowbridge/primitives/core/src/outbound/v1.rs create mode 100644 bridges/snowbridge/primitives/core/src/outbound/v2.rs delete mode 100644 bridges/snowbridge/primitives/core/src/outbound_v2.rs diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs index 11ff1292b511..f6e02844a58d 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs @@ -6,7 +6,7 @@ use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; use frame_benchmarking::v2::*; use snowbridge_core::{ - outbound::{Command, Initializer, QueuedMessage}, + outbound::v1::{Command, Initializer, QueuedMessage}, ChannelId, }; use sp_core::{H160, H256}; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index be78d3e3271a..7430c886a699 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -114,7 +114,7 @@ use frame_support::{ }; use snowbridge_core::{ inbound::Message as DeliveryMessage, - outbound_v2::{CommandWrapper, Fee, GasMeter, Message}, + outbound::v2::{CommandWrapper, Fee, GasMeter, Message}, BasicOperatingMode, }; use snowbridge_merkle_tree::merkle_root; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs index 5fadc6c9c06b..8e47dba4ef42 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs @@ -14,7 +14,7 @@ use snowbridge_core::{ gwei, inbound::{Log, Proof, VerificationError, Verifier}, meth, - outbound_v2::*, + outbound::v2::*, pricing::{PricingParameters, Rewards}, primary_governance_origin, ParaId, }; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs index 4f14fa2942e7..0abe5cba14e6 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs @@ -9,11 +9,11 @@ use frame_support::{ traits::{EnqueueMessage, Get}, }; use snowbridge_core::{ - outbound_v2::{Message, SendError, SendMessage, SendMessageFeeProvider}, + outbound::v2::{Message, SendError, SendMessage, SendMessageFeeProvider}, primary_governance_origin, }; use sp_core::H256; -use sp_runtime::{traits::Zero, BoundedVec}; +use sp_runtime::BoundedVec; /// The maximal length of an enqueued message, as determined by the MessageQueue pallet pub type MaxEnqueuedMessageSizeOf = @@ -35,7 +35,7 @@ where SendError::MessageTooLarge ); - let fee = Fee::from((Self::calculate_local_fee(), T::Balance::zero())); + let fee = Fee::from(Self::calculate_local_fee()); Ok((message.clone(), fee)) } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs index ffed2f5b16b5..e4d942a08912 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -11,7 +11,7 @@ use frame_support::{ use codec::Encode; use snowbridge_core::{ - outbound_v2::{Command, SendError, SendMessage}, + outbound::v2::{Command, SendError, SendMessage}, primary_governance_origin, ChannelId, ParaId, }; use sp_core::H256; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs index 2eed5cd238bd..a4454bcc4c8e 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs @@ -10,7 +10,7 @@ use sp_std::prelude::*; use super::Pallet; -use snowbridge_core::outbound_v2::CommandWrapper; +use snowbridge_core::outbound::v2::CommandWrapper; pub use snowbridge_merkle_tree::MerkleProof; pub type ProcessMessageOriginOf = as ProcessMessage>::Origin; diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs index e6ddaa439352..3e25e481440d 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs @@ -4,7 +4,7 @@ use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_core::{ - outbound::{Command, Fee}, + outbound::v1::{Command, Fee}, PricingParameters, }; use snowbridge_outbound_queue_merkle_tree::MerkleProof; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/pallets/outbound-queue/src/api.rs index b904819b1b18..982d32add9ef 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/api.rs @@ -5,7 +5,7 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; use snowbridge_core::{ - outbound::{Command, Fee, GasMeter}, + outbound::v1::{Command, Fee, GasMeter}, PricingParameters, }; use snowbridge_outbound_queue_merkle_tree::{merkle_proof, MerkleProof}; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs index ee5754e86962..0eff490b1ae4 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs @@ -6,7 +6,7 @@ use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; use frame_benchmarking::v2::*; use snowbridge_core::{ - outbound::{Command, Initializer}, + outbound::v1::{Command, Initializer}, ChannelId, }; use sp_core::{H160, H256}; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs index 9b9dbe854a5e..75cecc28d720 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs @@ -111,7 +111,7 @@ use frame_support::{ weights::{Weight, WeightToFee}, }; use snowbridge_core::{ - outbound::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS}, + outbound::v1::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS}, BasicOperatingMode, ChannelId, }; use snowbridge_outbound_queue_merkle_tree::merkle_root; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs index 0b34893333e4..d7bc4a8bcb5d 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs @@ -10,7 +10,7 @@ use frame_support::{ use snowbridge_core::{ gwei, meth, - outbound::*, + outbound::v1::*, pricing::{PricingParameters, Rewards}, ParaId, PRIMARY_GOVERNANCE_CHANNEL, }; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs index 03be61819973..2c4e2a31e7a8 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs @@ -11,7 +11,7 @@ use frame_support::{ }; use frame_system::unique; use snowbridge_core::{ - outbound::{ + outbound::v1::{ Fee, Message, QueuedMessage, SendError, SendMessage, SendMessageFeeProvider, VersionedQueuedMessage, }, diff --git a/bridges/snowbridge/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/pallets/outbound-queue/src/test.rs index 4e9ea36e24bc..580f32a8b68f 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/test.rs @@ -10,7 +10,7 @@ use frame_support::{ use codec::Encode; use snowbridge_core::{ - outbound::{Command, SendError, SendMessage}, + outbound::v1::{Command, SendError, SendMessage}, ParaId, PricingParameters, Rewards, }; use sp_arithmetic::FixedU128; diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 1e8a788b7a5a..0a0672f3ef58 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -68,7 +68,10 @@ use frame_support::{ use frame_system::pallet_prelude::*; use snowbridge_core::{ meth, - outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage}, + outbound::{ + v1::{Command, Initializer, Message, SendError, SendMessage}, + OperatingMode, + }, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index b4bad1d1568c..ae1c56f978bf 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -12,7 +12,6 @@ pub mod inbound; pub mod location; pub mod operating_mode; pub mod outbound; -pub mod outbound_v2; pub mod pricing; pub mod ringbuffer; diff --git a/bridges/snowbridge/primitives/core/src/outbound.rs b/bridges/snowbridge/primitives/core/src/outbound.rs deleted file mode 100644 index 77770761822a..000000000000 --- a/bridges/snowbridge/primitives/core/src/outbound.rs +++ /dev/null @@ -1,475 +0,0 @@ -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 for QueuedMessage { - type Error = (); - fn try_from(x: VersionedQueuedMessage) -> Result { - use VersionedQueuedMessage::*; - match x { - V1(x) => Ok(x), - } - } -} - -impl> From 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, - /// 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, - }, - /// 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, - // Fee multiplier - multiplier: UD60x18, - }, - /// Transfer ERC20 tokens - TransferNativeToken { - /// 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, - /// Short symbol for the token - symbol: Vec, - /// 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::CreateAgent { .. } => 2, - Command::CreateChannel { .. } => 3, - Command::UpdateChannel { .. } => 4, - Command::SetOperatingMode { .. } => 5, - Command::TransferNativeFromAgent { .. } => 6, - Command::SetTokenTransferFees { .. } => 7, - Command::SetPricingParameters { .. } => 8, - Command::TransferNativeToken { .. } => 9, - Command::RegisterForeignToken { .. } => 10, - Command::MintForeignToken { .. } => 11, - } - } - - /// ABI-encode the Command. - pub fn abi_encode(&self) -> Vec { - 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, 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::TransferNativeToken { 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, PartialEq, RuntimeDebug, TypeInfo)] - pub struct Initializer { - /// ABI-encoded params of type `bytes` to pass to the initializer - pub params: Vec, - /// 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 { - 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 -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 Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - pub fn total(&self) -> Balance { - self.local.saturating_add(self.remote) - } -} - -impl From<(Balance, Balance)> for Fee -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<::Balance>), SendError>; - - /// Submit the message ticket for eventual delivery to Ethereum - fn deliver(ticket: Self::Ticket) -> Result; -} - -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; - - /// 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::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, - Command::TransferNativeToken { .. } => 100_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; diff --git a/bridges/snowbridge/primitives/core/src/outbound/mod.rs b/bridges/snowbridge/primitives/core/src/outbound/mod.rs new file mode 100644 index 000000000000..cdd2c6dece81 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/outbound/mod.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Outbound +//! +//! Common traits and types +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::RuntimeDebug; + +pub mod v1; +pub mod v2; + +/// 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, +} diff --git a/bridges/snowbridge/primitives/core/src/outbound/v1.rs b/bridges/snowbridge/primitives/core/src/outbound/v1.rs new file mode 100644 index 000000000000..6814a37019d0 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/outbound/v1.rs @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Outbound V1 primitives + +use crate::{outbound::OperatingMode, pricing::UD60x18, ChannelId}; +use codec::{Decode, Encode}; +use ethabi::Token; +use frame_support::PalletError; +use scale_info::TypeInfo; +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 for QueuedMessage { + type Error = (); + fn try_from(x: VersionedQueuedMessage) -> Result { + use VersionedQueuedMessage::*; + match x { + V1(x) => Ok(x), + } + } +} + +impl> From 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, + /// 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 + 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, + }, + /// 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, + // Fee multiplier + multiplier: UD60x18, + }, + /// Transfer ERC20 tokens + TransferNativeToken { + /// 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, + /// Short symbol for the token + symbol: Vec, + /// 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::CreateAgent { .. } => 2, + Command::CreateChannel { .. } => 3, + Command::UpdateChannel { .. } => 4, + Command::SetOperatingMode { .. } => 5, + Command::TransferNativeFromAgent { .. } => 6, + Command::SetTokenTransferFees { .. } => 7, + Command::SetPricingParameters { .. } => 8, + Command::TransferNativeToken { .. } => 9, + Command::RegisterForeignToken { .. } => 10, + Command::MintForeignToken { .. } => 11, + } + } + + /// ABI-encode the Command. + pub fn abi_encode(&self) -> Vec { + 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, 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::TransferNativeToken { 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, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Initializer { + /// ABI-encoded params of type `bytes` to pass to the initializer + pub params: Vec, + /// 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 { + 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 +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 Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + pub fn total(&self) -> Balance { + self.local.saturating_add(self.remote) + } +} + +impl From<(Balance, Balance)> for Fee +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<::Balance>), SendError>; + + /// Submit the message ticket for eventual delivery to Ethereum + fn deliver(ticket: Self::Ticket) -> Result; +} + +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; + + /// 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::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, + Command::TransferNativeToken { .. } => 100_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; diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/core/src/outbound/v2.rs new file mode 100644 index 000000000000..1ceea4c597cb --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/outbound/v2.rs @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Outbound V2 primitives + +use crate::outbound::OperatingMode; +use codec::{Decode, Encode}; +use ethabi::Token; +use frame_support::{pallet_prelude::ConstU32, BoundedVec, PalletError}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +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 { + /// Origin + pub origin: H256, + /// ID + pub id: H256, + /// Fee + pub fee: u128, + /// Commands + pub commands: BoundedVec>, +} + +/// 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 { + /// 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, + }, + /// 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, + }, + /// 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, + }, + /// Unlock 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, + /// Short symbol for the token + symbol: Vec, + /// 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::Upgrade { .. } => 1, + Command::CreateAgent { .. } => 2, + Command::SetOperatingMode { .. } => 5, + Command::TransferNativeFromAgent { .. } => 6, + Command::UnlockNativeToken { .. } => 9, + Command::RegisterForeignToken { .. } => 10, + Command::MintForeignToken { .. } => 11, + } + } + + /// ABI-encode the Command. + pub fn abi_encode(&self) -> Vec { + match self { + 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::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::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, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Initializer { + /// ABI-encoded params of type `bytes` to pass to the initializer + pub params: Vec, + /// The initializer is allowed to consume this much gas at most. + pub maximum_required_gas: u64, +} + +#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub struct CommandWrapper { + pub kind: u8, + pub max_dispatch_gas: u64, + pub command: Vec, +} + +/// ABI-encoded form for delivery to the Gateway contract on Ethereum +impl From for Token { + fn from(x: CommandWrapper) -> Token { + Token::Tuple(vec![ + Token::Uint(x.kind.into()), + Token::Uint(x.max_dispatch_gas.into()), + Token::Bytes(x.command), + ]) + } +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +/// Fee for delivering message +pub struct Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + /// Fee to cover cost of processing the message locally + pub local: Balance, +} + +impl Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + pub fn total(&self) -> Balance { + self.local + } +} + +impl From for Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + fn from(local: Balance) -> Self { + Self { local } + } +} + +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<::Balance>), SendError>; + + /// Submit the message ticket for eventual delivery to Ethereum + fn deliver(ticket: Self::Ticket) -> Result; +} + +/// 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; + + /// 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::CreateAgent { .. } => 275_000, + Command::TransferNativeFromAgent { .. } => 60_000, + Command::SetOperatingMode { .. } => 40_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::UnlockNativeToken { .. } => 100_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 + } +} diff --git a/bridges/snowbridge/primitives/core/src/outbound_v2.rs b/bridges/snowbridge/primitives/core/src/outbound_v2.rs deleted file mode 100644 index b65ebba3cac6..000000000000 --- a/bridges/snowbridge/primitives/core/src/outbound_v2.rs +++ /dev/null @@ -1,310 +0,0 @@ -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 v2::{Command, CommandWrapper, Initializer, Message}; - -mod v2 { - use crate::outbound::OperatingMode; - use codec::{Decode, Encode}; - use ethabi::Token; - use frame_support::{pallet_prelude::ConstU32, BoundedVec}; - 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 { - /// Origin - pub origin: H256, - /// ID - pub id: H256, - /// Fee - pub fee: u128, - /// Commands - pub commands: BoundedVec>, - } - - /// 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 { - /// 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, - }, - /// 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, - }, - /// 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, - }, - /// Unlock 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, - /// Short symbol for the token - symbol: Vec, - /// 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::Upgrade { .. } => 1, - Command::CreateAgent { .. } => 2, - Command::SetOperatingMode { .. } => 5, - Command::TransferNativeFromAgent { .. } => 6, - Command::UnlockNativeToken { .. } => 9, - Command::RegisterForeignToken { .. } => 10, - Command::MintForeignToken { .. } => 11, - } - } - - /// ABI-encode the Command. - pub fn abi_encode(&self) -> Vec { - match self { - 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::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::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, PartialEq, RuntimeDebug, TypeInfo)] - pub struct Initializer { - /// ABI-encoded params of type `bytes` to pass to the initializer - pub params: Vec, - /// The initializer is allowed to consume this much gas at most. - pub maximum_required_gas: u64, - } - - #[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo)] - #[cfg_attr(feature = "std", derive(PartialEq))] - pub struct CommandWrapper { - pub kind: u8, - pub max_dispatch_gas: u64, - pub command: Vec, - } - - /// ABI-encoded form for delivery to the Gateway contract on Ethereum - impl From for Token { - fn from(x: CommandWrapper) -> Token { - Token::Tuple(vec![ - Token::Uint(x.kind.into()), - Token::Uint(x.max_dispatch_gas.into()), - Token::Bytes(x.command), - ]) - } - } -} - -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(PartialEq))] -/// Fee for delivering message -pub struct Fee -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 Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - pub fn total(&self) -> Balance { - self.local.saturating_add(self.remote) - } -} - -impl From<(Balance, Balance)> for Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - fn from((local, remote): (Balance, Balance)) -> Self { - Self { local, remote } - } -} - -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<::Balance>), SendError>; - - /// Submit the message ticket for eventual delivery to Ethereum - fn deliver(ticket: Self::Ticket) -> Result; -} - -/// 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; - - /// 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::CreateAgent { .. } => 275_000, - Command::TransferNativeFromAgent { .. } => 60_000, - Command::SetOperatingMode { .. } => 40_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::UnlockNativeToken { .. } => 100_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 - } -} diff --git a/bridges/snowbridge/primitives/router-v2/src/outbound/mod.rs b/bridges/snowbridge/primitives/router-v2/src/outbound/mod.rs index 3356ff8728f7..175fad4a7c9c 100644 --- a/bridges/snowbridge/primitives/router-v2/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router-v2/src/outbound/mod.rs @@ -15,7 +15,7 @@ use frame_support::{ BoundedVec, }; use snowbridge_core::{ - outbound_v2::{Command, Message, SendMessage}, + outbound::v2::{Command, Message, SendMessage}, AgentId, TokenId, TokenIdOf, }; use sp_core::{H160, H256}; diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index aafa191cba8d..f51bf5f67375 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -11,7 +11,7 @@ use codec::{Decode, Encode}; use frame_support::{ensure, traits::Get}; use snowbridge_core::{ - outbound::{AgentExecuteCommand, Command, Message, SendMessage}, + outbound::v1::{AgentExecuteCommand, Command, Message, SendMessage}, AgentId, ChannelId, ParaId, TokenId, TokenIdOf, }; use sp_core::{H160, H256}; diff --git a/bridges/snowbridge/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs index 0b1a74b232a0..26630e61d604 100644 --- a/bridges/snowbridge/runtime/runtime-common/src/lib.rs +++ b/bridges/snowbridge/runtime/runtime-common/src/lib.rs @@ -11,7 +11,7 @@ mod tests; use codec::FullCodec; use core::marker::PhantomData; use frame_support::traits::Get; -use snowbridge_core::outbound::SendMessageFeeProvider; +use snowbridge_core::outbound::v1::SendMessageFeeProvider; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_std::fmt::Debug; use xcm::prelude::*; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index e4c4962c05e6..6b8706a714d7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -151,7 +151,7 @@ impl snowbridge_pallet_outbound_queue::Config for Runtime { type Decimals = ConstU8<12>; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound::ConstantGasMeter; + type GasMeter = snowbridge_core::outbound::v1::ConstantGasMeter; type Balance = Balance; type WeightToFee = WeightToFee; type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; @@ -165,7 +165,7 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime { type MessageQueue = MessageQueue; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound_v2::ConstantGasMeter; + type GasMeter = snowbridge_core::outbound::v2::ConstantGasMeter; type Balance = Balance; type WeightToFee = WeightToFee; type Verifier = snowbridge_pallet_ethereum_client::Pallet; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 260257eacf16..d34e37a0ecfa 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -96,7 +96,7 @@ use parachains_common::{ AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use snowbridge_core::{ - outbound::{Command, Fee}, + outbound::v1::{Command, Fee}, AgentId, PricingParameters, }; use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*};