diff --git a/core/packages/contracts/src/Verification.sol b/core/packages/contracts/src/Verification.sol index a91099be45..c247a18b38 100644 --- a/core/packages/contracts/src/Verification.sol +++ b/core/packages/contracts/src/Verification.sol @@ -96,6 +96,7 @@ library Verification { /// @param encodedParaID The SCALE-encoded parachain ID of BridgeHub /// @param commitment The message commitment root expected to be contained within the /// digest of BridgeHub parachain header. + /// @param proof The chain of proofs described above function verifyCommitment(address beefyClient, bytes4 encodedParaID, bytes32 commitment, Proof calldata proof) external view @@ -109,12 +110,11 @@ library Verification { // Compute the merkle leaf hash of our parachain bytes32 parachainHeadHash = createParachainHeaderMerkleLeaf(encodedParaID, proof.header); - // Compute the merkle root hash of all parachain heads - // - // For reference, in the polkadot relay chain, this is where the merkle tree root is constructed: if (proof.headProof.pos >= proof.headProof.width) { return false; } + + // Compute the merkle root hash of all parachain heads bytes32 parachainHeadsRoot = SubstrateMerkleProof.computeRoot( parachainHeadHash, proof.headProof.pos, proof.headProof.width, proof.headProof.proof ); diff --git a/core/packages/contracts/src/interfaces/IGateway.sol b/core/packages/contracts/src/interfaces/IGateway.sol index 29ddf17f8a..42da8cf18f 100644 --- a/core/packages/contracts/src/interfaces/IGateway.sol +++ b/core/packages/contracts/src/interfaces/IGateway.sol @@ -65,14 +65,15 @@ interface IGateway { */ /// @dev Send a message to the AssetHub parachain to register a new fungible asset - /// in the `ForeignAssets` pallet. + /// in the `ForeignAssets` pallet. function registerToken(address token) external payable; - /// @dev Send ERC20 tokens to Polkadot. + /// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress` function sendToken(address token, ParaID destinationChain, bytes32 destinationAddress, uint128 amount) external payable; + /// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress` function sendToken(address token, ParaID destinationChain, address destinationAddress, uint128 amount) external payable; diff --git a/core/packages/contracts/src/storage/CoreStorage.sol b/core/packages/contracts/src/storage/CoreStorage.sol index ca1cdfe775..4053281818 100644 --- a/core/packages/contracts/src/storage/CoreStorage.sol +++ b/core/packages/contracts/src/storage/CoreStorage.sol @@ -12,11 +12,11 @@ library CoreStorage { OperatingMode mode; // Message channels mapping(ParaID paraID => Channel) channels; - // All agents + // Agents mapping(bytes32 agentID => address) agents; - // The fee charged to users for submitting outbound message to Polkadot + // The default fee charged to users for submitting outbound message to Polkadot uint256 defaultFee; - // The reward given to relayers for submitting inbound messages from Polkadot + // The default reward given to relayers for submitting inbound messages from Polkadot uint256 defaultReward; } diff --git a/core/packages/contracts/src/utils/ERC1967.sol b/core/packages/contracts/src/utils/ERC1967.sol index a048eeeaf6..1cba006be0 100644 --- a/core/packages/contracts/src/utils/ERC1967.sol +++ b/core/packages/contracts/src/utils/ERC1967.sol @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork pragma solidity 0.8.20; +/// @title Minimal implementation of ERC1967 storage slot library ERC1967 { // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 public constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; diff --git a/parachain/Cargo.lock b/parachain/Cargo.lock index b5afdccb5b..98adb20f28 100644 --- a/parachain/Cargo.lock +++ b/parachain/Cargo.lock @@ -2686,7 +2686,6 @@ dependencies = [ "serde", "serde-big-array", "serde_json", - "snowbridge-testutils", "sp-core", "sp-io", "sp-runtime", @@ -2714,7 +2713,6 @@ dependencies = [ "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", - "snowbridge-testutils", "sp-core", "sp-io", "sp-keyring", @@ -2825,16 +2823,6 @@ dependencies = [ "xcm-executor", ] -[[package]] -name = "snowbridge-testutils" -version = "0.1.0" -dependencies = [ - "ethereum-types", - "rustc-hex", - "serde", - "serde_json", -] - [[package]] name = "sp-api" version = "4.0.0-dev" diff --git a/parachain/Cargo.toml b/parachain/Cargo.toml index e4fec4827f..cdd61f8b83 100644 --- a/parachain/Cargo.toml +++ b/parachain/Cargo.toml @@ -2,7 +2,6 @@ members = [ "primitives/core", "primitives/ethereum", - "primitives/testutils", "primitives/router", "pallets/inbound-queue", "pallets/outbound-queue", diff --git a/parachain/pallets/control/src/lib.rs b/parachain/pallets/control/src/lib.rs index fe0dcb9ab0..ff7d4e8619 100644 --- a/parachain/pallets/control/src/lib.rs +++ b/parachain/pallets/control/src/lib.rs @@ -19,7 +19,7 @@ mod benchmarking; pub mod weights; pub use weights::*; -use snowbridge_core::{Command, OutboundMessage, OutboundQueue as OutboundQueueTrait, ParaId}; +use snowbridge_core::outbound::{Command, Message, OutboundQueue as OutboundQueueTrait, ParaId}; use sp_core::{H160, H256}; use sp_runtime::traits::Hash; use sp_std::prelude::*; @@ -73,6 +73,13 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Sends a message to the Gateway contract to upgrade itself. + /// + /// - `origin`: Must be `Root`. + /// - `impl_address`: The address of the new implementation contract. + /// - `impl_code_hash`: The codehash of `impl_address`. + /// - `params`: An optional list of ABI-encoded parameters for the implementation + /// contract's `initialize(bytes) function. If `None`, the initialization function is not called. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::upgrade(params.clone().map_or(0, |d| d.len() as u32)))] pub fn upgrade( @@ -90,7 +97,7 @@ pub mod pallet { let params_hash = params.as_ref().map(|p| T::MessageHasher::hash(p)); - let message = OutboundMessage { + let message = Message { origin: T::OwnParaId::get(), command: Command::Upgrade { impl_address, impl_code_hash, params }, }; @@ -105,6 +112,9 @@ pub mod pallet { Ok(()) } + /// Sends a message to the Gateway contract to create a new Agent representing `origin` + /// + /// - `origin`: Must be `MultiLocation` #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::create_agent())] pub fn create_agent(origin: OriginFor) -> DispatchResult { @@ -125,10 +135,8 @@ pub mod pallet { return Ok(()); } - let message = OutboundMessage { - origin: T::OwnParaId::get(), - command: Command::CreateAgent { agent_id }, - }; + let message = + Message { origin: T::OwnParaId::get(), command: Command::CreateAgent { agent_id } }; let ticket = T::OutboundQueue::validate(&message).map_err(|_| Error::::SubmissionFailed)?; diff --git a/parachain/pallets/control/src/mock.rs b/parachain/pallets/control/src/mock.rs index f0026b9b85..b39ef360f1 100644 --- a/parachain/pallets/control/src/mock.rs +++ b/parachain/pallets/control/src/mock.rs @@ -10,7 +10,7 @@ use frame_support::{ #[cfg(feature = "runtime-benchmarks")] use frame_benchmarking::v2::whitelisted_caller; -use snowbridge_core::{OutboundMessage, OutboundMessageHash, ParaId, SubmitError}; +use snowbridge_core::outbound::{Message, MessageHash, ParaId, SubmitError}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -174,14 +174,14 @@ impl EnsureOrigin for EnsureOriginFromTable { pub struct MockOutboundQueue; impl snowbridge_control::OutboundQueueTrait for MockOutboundQueue { - type Ticket = OutboundMessage; + type Ticket = Message; - fn validate(message: &OutboundMessage) -> Result { + fn validate(message: &Message) -> Result { Ok(message.clone()) } - fn submit(_ticket: Self::Ticket) -> Result { - Ok(OutboundMessageHash::zero()) + fn submit(_ticket: Self::Ticket) -> Result { + Ok(MessageHash::zero()) } } diff --git a/parachain/pallets/ethereum-beacon-client/Cargo.toml b/parachain/pallets/ethereum-beacon-client/Cargo.toml index e4d5ebc4c6..c0b338095c 100644 --- a/parachain/pallets/ethereum-beacon-client/Cargo.toml +++ b/parachain/pallets/ethereum-beacon-client/Cargo.toml @@ -37,7 +37,6 @@ bp-runtime = { git = "https://github.com/Snowfork/cumulus.git", branch = "snowbr rand = "0.8.5" sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "master" } sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "master" } -snowbridge-testutils = { path = "../../primitives/testutils" } serde_json = "1.0.96" hex-literal = { version = "0.4.1" } pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "master" } diff --git a/parachain/pallets/ethereum-beacon-client/src/lib.rs b/parachain/pallets/ethereum-beacon-client/src/lib.rs index b099838857..0771dbeedc 100644 --- a/parachain/pallets/ethereum-beacon-client/src/lib.rs +++ b/parachain/pallets/ethereum-beacon-client/src/lib.rs @@ -26,13 +26,14 @@ use primitives::{ CompactBeaconState, CompactExecutionHeader, ExecutionHeaderState, ForkData, ForkVersion, ForkVersions, PublicKeyPrepared, SigningData, }; -use snowbridge_core::{Message, RingBufferMap, Verifier}; +use snowbridge_core::{ + inbound::{Message, Proof, Verifier}, + RingBufferMap, +}; use sp_core::H256; use sp_std::prelude::*; pub use weights::WeightInfo; -use snowbridge_core::Proof; - use functions::{ compute_epoch, compute_period, decompress_sync_committee_bits, sync_committee_sum, }; @@ -346,9 +347,9 @@ pub mod pallet { // committee period. let max_latency = config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD * config::SLOTS_PER_EPOCH; ensure!( - latest_execution_state.beacon_slot == 0 || - latest_finalized_state.slot < - latest_execution_state.beacon_slot + max_latency as u64, + latest_execution_state.beacon_slot == 0 + || latest_finalized_state.slot + < latest_execution_state.beacon_slot + max_latency as u64, Error::::ExecutionHeaderTooFarBehind ); Ok(()) @@ -366,8 +367,8 @@ pub mod pallet { // Verify update does not skip a sync committee period. ensure!( - update.signature_slot > update.attested_header.slot && - update.attested_header.slot >= update.finalized_header.slot, + update.signature_slot > update.attested_header.slot + && update.attested_header.slot >= update.finalized_header.slot, Error::::InvalidUpdateSlot ); // Retrieve latest finalized state. @@ -387,12 +388,12 @@ pub mod pallet { // Verify update is relevant. let update_attested_period = compute_period(update.attested_header.slot); - let update_has_next_sync_committee = !>::exists() && - (update.next_sync_committee_update.is_some() && - update_attested_period == store_period); + let update_has_next_sync_committee = !>::exists() + && (update.next_sync_committee_update.is_some() + && update_attested_period == store_period); ensure!( - update.attested_header.slot > latest_finalized_state.slot || - update_has_next_sync_committee, + update.attested_header.slot > latest_finalized_state.slot + || update_has_next_sync_committee, Error::::IrrelevantUpdate ); @@ -549,9 +550,9 @@ pub mod pallet { // Checks that we don't skip execution headers, they need to be imported sequentially. let latest_execution_state: ExecutionHeaderState = Self::latest_execution_state(); ensure!( - latest_execution_state.block_number == 0 || - update.execution_header.block_number == - latest_execution_state.block_number + 1, + latest_execution_state.block_number == 0 + || update.execution_header.block_number + == latest_execution_state.block_number + 1, Error::::ExecutionHeaderSkippedSlot ); @@ -595,7 +596,7 @@ pub mod pallet { let state = >::get(block_root) .ok_or(Error::::ExpectedFinalizedHeaderNotStored)?; if update.header.slot != state.slot { - return Err(Error::::ExpectedFinalizedHeaderNotStored.into()) + return Err(Error::::ExpectedFinalizedHeaderNotStored.into()); } }, } @@ -782,13 +783,13 @@ pub mod pallet { let fork_versions = T::ForkVersions::get(); if epoch >= fork_versions.capella.epoch { - return fork_versions.capella.version + return fork_versions.capella.version; } if epoch >= fork_versions.bellatrix.epoch { - return fork_versions.bellatrix.version + return fork_versions.bellatrix.version; } if epoch >= fork_versions.altair.epoch { - return fork_versions.altair.version + return fork_versions.altair.version; } fork_versions.genesis.version diff --git a/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs b/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs index a71a47e307..ebce56ae10 100644 --- a/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs +++ b/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs @@ -1,7 +1,7 @@ use super::*; use hex_literal::hex; use snowbridge_beacon_primitives::CompactExecutionHeader; -use snowbridge_core::{Message, Proof}; +use snowbridge_core::inbound::{Message, Proof}; pub struct InboundQueueTest { pub execution_header: CompactExecutionHeader, diff --git a/parachain/pallets/inbound-queue/src/lib.rs b/parachain/pallets/inbound-queue/src/lib.rs index 7f77175dc2..da59aae764 100644 --- a/parachain/pallets/inbound-queue/src/lib.rs +++ b/parachain/pallets/inbound-queue/src/lib.rs @@ -32,7 +32,7 @@ use sp_runtime::traits::AccountIdConversion; use sp_std::convert::TryFrom; use envelope::Envelope; -use snowbridge_core::{Message, Verifier}; +use snowbridge_core::inbound::{Message, Verifier}; use snowbridge_router_primitives::inbound; use xcm::v3::{send_xcm, Junction::*, Junctions::*, MultiLocation, SendError}; @@ -119,10 +119,12 @@ pub mod pallet { BridgeModule(bp_runtime::OwnedBridgeModuleError), } + /// The address of the Gateway contract on Ethereum #[pallet::storage] #[pallet::getter(fn gateway)] pub type Gateway = StorageValue<_, H160, ValueQuery>; + /// The current nonce for each parachain #[pallet::storage] pub type Nonce = StorageMap<_, Twox64Concat, ParaId, u64, ValueQuery>; @@ -144,6 +146,7 @@ pub mod pallet { #[pallet::genesis_config] #[derive(DefaultNoBound)] pub struct GenesisConfig { + /// The address of the Gateway contract on Ethereum pub gateway: H160, } @@ -163,6 +166,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Submit an inbound message originating from the Gateway contract on Ethereum #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { diff --git a/parachain/pallets/inbound-queue/src/test.rs b/parachain/pallets/inbound-queue/src/test.rs index b5d7d04908..68228e70f2 100644 --- a/parachain/pallets/inbound-queue/src/test.rs +++ b/parachain/pallets/inbound-queue/src/test.rs @@ -18,7 +18,7 @@ use sp_runtime::{ use sp_std::convert::From; use snowbridge_beacon_primitives::{Fork, ForkVersions}; -use snowbridge_core::{Message, Proof}; +use snowbridge_core::inbound::{Message, Proof}; use snowbridge_ethereum::Log; use hex_literal::hex; diff --git a/parachain/pallets/outbound-queue/src/benchmarking.rs b/parachain/pallets/outbound-queue/src/benchmarking.rs index 7b7b1b50cb..28f2c4615c 100644 --- a/parachain/pallets/outbound-queue/src/benchmarking.rs +++ b/parachain/pallets/outbound-queue/src/benchmarking.rs @@ -4,7 +4,7 @@ use super::*; use codec::Encode; use frame_benchmarking::v2::*; -use snowbridge_core::Command; +use snowbridge_core::outbound::Command; #[allow(unused_imports)] use crate::Pallet as OutboundQueue; diff --git a/parachain/pallets/outbound-queue/src/lib.rs b/parachain/pallets/outbound-queue/src/lib.rs index 5a4de76f68..f99b800f32 100644 --- a/parachain/pallets/outbound-queue/src/lib.rs +++ b/parachain/pallets/outbound-queue/src/lib.rs @@ -26,8 +26,8 @@ use sp_core::{RuntimeDebug, H256}; use sp_runtime::traits::Hash; use sp_std::prelude::*; -use snowbridge_core::{ - Command, OutboundMessage, OutboundMessageHash, OutboundQueue as OutboundQueueTrait, SubmitError, +use snowbridge_core::outbound::{ + Command, Message, MessageHash, OutboundQueue as OutboundQueueTrait, SubmitError, }; use snowbridge_outbound_queue_merkle_tree::merkle_root; @@ -54,7 +54,7 @@ pub struct EnqueuedMessage { /// Message which has been assigned a nonce and will be committed at the end of a block #[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] -pub struct Message { +pub struct PreparedMessage { /// ID of source parachain origin: ParaId, /// Unique nonce to prevent replaying messages @@ -65,7 +65,7 @@ pub struct Message { } /// Convert message into an ABI-encoded form for delivery to the InboundQueue contract on Ethereum -impl Into for Message { +impl Into for PreparedMessage { fn into(self) -> Token { Token::Tuple(vec![ Token::Uint(u32::from(self.origin).into()), @@ -160,7 +160,7 @@ pub mod pallet { /// Inspired by the `frame_system::Pallet::Events` storage value #[pallet::storage] #[pallet::unbounded] - pub(super) type Messages = StorageValue<_, Vec, ValueQuery>; + pub(super) type Messages = StorageValue<_, Vec, ValueQuery>; /// Hashes of the ABI-encoded messages in the [`Messages`] storage value. Used to generate a /// merkle root during `on_finalize`. This storage value is killed in @@ -262,8 +262,12 @@ pub mod pallet { let (command, params) = enqueued_message.command.abi_encode(); - let message: Message = - Message { origin: enqueued_message.origin, nonce: next_nonce, command, params }; + let message: PreparedMessage = PreparedMessage { + origin: enqueued_message.origin, + nonce: next_nonce, + command, + params, + }; let message_abi_encoded = ethabi::encode(&vec![message.clone().into()]); let message_abi_encoded_hash = ::Hashing::hash(&message_abi_encoded); @@ -292,13 +296,12 @@ pub mod pallet { impl OutboundQueueTrait for Pallet { type Ticket = OutboundQueueTicket>; - fn validate(message: &OutboundMessage) -> Result { + fn validate(message: &Message) -> Result { // The inner payload should not be too large let (_, payload) = message.command.abi_encode(); // Create a message id for tracking progress in submission pipeline - let message_id: OutboundMessageHash = - sp_io::hashing::blake2_256(&(message.encode())).into(); + let message_id: MessageHash = sp_io::hashing::blake2_256(&(message.encode())).into(); ensure!( payload.len() < T::MaxMessagePayloadSize::get() as usize, @@ -317,7 +320,7 @@ pub mod pallet { Ok(ticket) } - fn submit(ticket: Self::Ticket) -> Result { + fn submit(ticket: Self::Ticket) -> Result { Self::ensure_not_halted().map_err(|_| SubmitError::BridgeHalted)?; T::MessageQueue::enqueue_message( ticket.message.as_bounded_slice(), diff --git a/parachain/pallets/outbound-queue/src/test.rs b/parachain/pallets/outbound-queue/src/test.rs index 7992413c13..6a268fa421 100644 --- a/parachain/pallets/outbound-queue/src/test.rs +++ b/parachain/pallets/outbound-queue/src/test.rs @@ -125,7 +125,7 @@ fn submit_messages_from_multiple_origins_and_commit() { //next_block(); for para_id in 1000..1004 { - let message = OutboundMessage { + let message = Message { origin: para_id.into(), command: Command::Upgrade { impl_address: H160::zero(), @@ -158,7 +158,7 @@ fn submit_messages_from_multiple_origins_and_commit() { #[test] fn submit_message_fail_too_large() { new_tester().execute_with(|| { - let message = OutboundMessage { + let message = Message { origin: 1000.into(), command: Command::Upgrade { impl_address: H160::zero(), diff --git a/parachain/primitives/core/src/types.rs b/parachain/primitives/core/src/inbound.rs similarity index 58% rename from parachain/primitives/core/src/types.rs rename to parachain/primitives/core/src/inbound.rs index 34c6dab56b..5527802099 100644 --- a/parachain/primitives/core/src/types.rs +++ b/parachain/primitives/core/src/inbound.rs @@ -1,23 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Types for representing messages +//! Types for representing inbound messages use codec::{Decode, Encode}; +use frame_support::dispatch::DispatchError; use frame_support::{scale_info::TypeInfo, RuntimeDebug}; -use sp_core::{H160, H256}; -use sp_runtime::DigestItem; +use snowbridge_ethereum::Log; +use sp_core::H256; use sp_std::vec::Vec; -#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct MessageId { - account: H160, - nonce: u64, -} - -impl MessageId { - pub fn new(account: H160, nonce: u64) -> MessageId { - MessageId { account, nonce } - } +/// A trait for verifying inbound messages from Ethereum. +/// +/// This trait should be implemented by runtime modules that wish to provide message verification +/// functionality. +pub trait Verifier { + fn verify(message: &Message) -> Result; } pub type MessageNonce = u64; @@ -41,19 +38,6 @@ pub struct Proof { pub block_hash: H256, // The index of the transaction (and receipt) within the block. pub tx_index: u32, - // Proof keys and values + // Proof keys and values (receipts tree) pub data: (Vec>, Vec>), } - -/// Auxiliary [`DigestItem`] to include in header digest. -#[derive(Encode, Decode, Copy, Clone, PartialEq, RuntimeDebug, TypeInfo)] -pub enum AuxiliaryDigestItem { - /// A batch of messages has been committed. - Commitment(H256), -} - -impl Into for AuxiliaryDigestItem { - fn into(self) -> DigestItem { - DigestItem::Other(self.encode()) - } -} diff --git a/parachain/primitives/core/src/lib.rs b/parachain/primitives/core/src/lib.rs index bc9cebbc4d..da410a5936 100644 --- a/parachain/primitives/core/src/lib.rs +++ b/parachain/primitives/core/src/lib.rs @@ -8,183 +8,9 @@ #![allow(unused_variables)] #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; -use frame_support::dispatch::DispatchError; -use snowbridge_ethereum::Log; -use sp_core::{RuntimeDebug, H160, H256, U256}; -use sp_std::{borrow::ToOwned, vec, vec::Vec}; - +pub mod inbound; +pub mod outbound; pub mod ringbuffer; -pub mod types; pub use polkadot_parachain::primitives::Id as ParaId; pub use ringbuffer::{RingBufferMap, RingBufferMapImpl}; -pub use types::{Message, MessageNonce, Proof}; - -/// A trait for verifying messages. -/// -/// This trait should be implemented by runtime modules that wish to provide message verification -/// functionality. -pub trait Verifier { - fn verify(message: &Message) -> Result; -} - -#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug)] -pub enum SubmitError { - MessageTooLarge, - BridgeHalted, -} - -/// A message which can be accepted by the [`OutboundQueue`] -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct OutboundMessage { - /// The parachain from which the message originated - pub origin: ParaId, - /// The stable ID for a receiving gateway contract - pub command: Command, -} - -use ethabi::Token; - -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] -pub enum OperatingMode { - Normal, - RejectingOutboundMessages, -} - -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Command { - AgentExecute { agent_id: H256, command: AgentExecuteCommand }, - Upgrade { impl_address: H160, impl_code_hash: H256, params: Option> }, - CreateAgent { agent_id: H256 }, - CreateChannel { para_id: ParaId, agent_id: H256 }, - UpdateChannel { para_id: ParaId, mode: OperatingMode, fee: u128, reward: u128 }, - SetOperatingMode { mode: OperatingMode }, - TransferNativeFromAgent { agent_id: H256, recipient: H160, amount: u128 }, -} - -impl Command { - 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, - } - } - - pub fn abi_encode(&self) -> (u8, Vec) { - match self { - Command::AgentExecute { agent_id, command } => ( - self.index(), - ethabi::encode(&vec![Token::Tuple(vec![ - Token::FixedBytes(agent_id.as_bytes().to_owned()), - Token::Bytes(command.abi_encode()), - ])]), - ), - Command::Upgrade { impl_address, impl_code_hash, params } => ( - self.index(), - ethabi::encode(&vec![Token::Tuple(vec![ - Token::Address(*impl_address), - Token::FixedBytes(impl_code_hash.as_bytes().to_owned()), - params.clone().map_or(Token::Bytes(vec![]), |p| Token::Bytes(p)), - ])]), - ), - Command::CreateAgent { agent_id } => ( - self.index(), - ethabi::encode(&vec![Token::Tuple(vec![Token::FixedBytes( - agent_id.as_bytes().to_owned(), - )])]), - ), - Command::CreateChannel { para_id, agent_id } => { - let para_id: u32 = (*para_id).into(); - ( - self.index(), - ethabi::encode(&vec![Token::Tuple(vec![ - Token::Uint(U256::from(para_id)), - Token::FixedBytes(agent_id.as_bytes().to_owned()), - ])]), - ) - }, - Command::UpdateChannel { para_id, mode, fee, reward } => { - let para_id: u32 = (*para_id).into(); - ( - self.index(), - ethabi::encode(&vec![Token::Tuple(vec![ - Token::Uint(U256::from(para_id)), - Token::Uint(U256::from((*mode) as u64)), - Token::Uint(U256::from(*fee)), - Token::Uint(U256::from(*reward)), - ])]), - ) - }, - Command::SetOperatingMode { mode } => ( - self.clone().index(), - ethabi::encode(&vec![Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])]), - ), - Command::TransferNativeFromAgent { agent_id, recipient, amount } => ( - self.clone().index(), - ethabi::encode(&vec![Token::Tuple(vec![ - Token::FixedBytes(agent_id.as_bytes().to_owned()), - Token::Address(*recipient), - Token::Uint(U256::from(*amount)), - ])]), - ), - } - } -} - -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] -pub enum AgentExecuteCommand { - TransferToken { token: H160, recipient: H160, amount: u128 }, -} - -impl AgentExecuteCommand { - fn index(&self) -> u8 { - match self { - AgentExecuteCommand::TransferToken { .. } => 0, - } - } - pub fn abi_encode(&self) -> Vec { - match self { - AgentExecuteCommand::TransferToken { token, recipient, amount } => { - ethabi::encode(&vec![ - Token::Uint(self.index().into()), - Token::Bytes(ethabi::encode(&vec![ - Token::Address(*token), - Token::Address(*recipient), - Token::Uint(U256::from(*amount)), - ])), - ]) - }, - } - } -} - -pub type OutboundMessageHash = H256; - -// A trait for enqueueing messages for delivery to Ethereum -pub trait OutboundQueue { - type Ticket; - - /// Validate a message destined for Ethereum - fn validate(message: &OutboundMessage) -> Result; - - /// Submit the message for eventual delivery to Ethereum - fn submit(ticket: Self::Ticket) -> Result; -} - -impl OutboundQueue for () { - type Ticket = u64; - - fn validate(message: &OutboundMessage) -> Result { - Ok(0) - } - - fn submit(ticket: Self::Ticket) -> Result { - Ok(OutboundMessageHash::zero()) - } -} diff --git a/parachain/primitives/core/src/outbound.rs b/parachain/primitives/core/src/outbound.rs new file mode 100644 index 0000000000..752548bf7d --- /dev/null +++ b/parachain/primitives/core/src/outbound.rs @@ -0,0 +1,231 @@ +use codec::{Decode, Encode}; +pub use polkadot_parachain::primitives::Id as ParaId; +use sp_core::{RuntimeDebug, H160, H256, U256}; +use sp_std::{borrow::ToOwned, vec, vec::Vec}; + +pub type MessageHash = H256; + +/// A trait for enqueueing messages for delivery to Ethereum +pub trait OutboundQueue { + type Ticket; + + /// Validate a message + fn validate(message: &Message) -> Result; + + /// Submit the message ticket for eventual delivery to Ethereum + fn submit(ticket: Self::Ticket) -> Result; +} + +/// Default implementation of `OutboundQueue` for tests +impl OutboundQueue for () { + type Ticket = u64; + + fn validate(message: &Message) -> Result { + Ok(0) + } + + fn submit(ticket: Self::Ticket) -> Result { + Ok(MessageHash::zero()) + } +} + +/// Errors returned by the [`OutboundQueue`] +#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug)] +pub enum SubmitError { + /// Message is too large to be safely executed on Ethereum + MessageTooLarge, + /// The bridge has been halted for maintenance + BridgeHalted, +} + +/// A message which can be accepted by the [`OutboundQueue`] +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct Message { + /// The parachain from which the message originated + pub origin: ParaId, + /// The stable ID for a receiving gateway contract + pub command: Command, +} + +use ethabi::Token; + +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] +pub enum OperatingMode { + Normal, + RejectingOutboundMessages, +} + +/// A command which is executable by the Gateway contract on Ethereum +#[derive(Clone, Encode, Decode, RuntimeDebug)] +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, + /// Optional list of parameters to pass to initializer in the implementation contract + params: 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 parachain + para_id: ParaId, + /// The agent ID of the parachain + agent_id: H256, + }, + /// Update the configuration of a channel + UpdateChannel { + /// The ID of the parachain to which the channel belongs. + para_id: ParaId, + /// The new operating mode + mode: OperatingMode, + /// The new fee to charge users for outbound messaging to Polkadot + fee: u128, + /// The new reward to give to relayers for submitting inbound messages from Polkadot + reward: u128, + }, + /// Set the global operating mode of the Gateway contract + SetOperatingMode { + /// The new operating mode + mode: OperatingMode, + }, + /// Transfer ether from an agent + TransferNativeFromAgent { + /// The agent ID + agent_id: H256, + /// The recipient of the ether + recipient: H160, + /// The amount to transfer + 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, + } + } + + /// ABI-encode the Command. + /// Returns a tuple of: + /// - Index of the command + /// - the ABI encoded command + pub fn abi_encode(&self) -> (u8, Vec) { + match self { + Command::AgentExecute { agent_id, command } => ( + self.index(), + ethabi::encode(&vec![Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Bytes(command.abi_encode()), + ])]), + ), + Command::Upgrade { impl_address, impl_code_hash, params } => ( + self.index(), + ethabi::encode(&vec![Token::Tuple(vec![ + Token::Address(*impl_address), + Token::FixedBytes(impl_code_hash.as_bytes().to_owned()), + params.clone().map_or(Token::Bytes(vec![]), |p| Token::Bytes(p)), + ])]), + ), + Command::CreateAgent { agent_id } => ( + self.index(), + ethabi::encode(&vec![Token::Tuple(vec![Token::FixedBytes( + agent_id.as_bytes().to_owned(), + )])]), + ), + Command::CreateChannel { para_id, agent_id } => { + let para_id: u32 = (*para_id).into(); + ( + self.index(), + ethabi::encode(&vec![Token::Tuple(vec![ + Token::Uint(U256::from(para_id)), + Token::FixedBytes(agent_id.as_bytes().to_owned()), + ])]), + ) + }, + Command::UpdateChannel { para_id, mode, fee, reward } => { + let para_id: u32 = (*para_id).into(); + ( + self.index(), + ethabi::encode(&vec![Token::Tuple(vec![ + Token::Uint(U256::from(para_id)), + Token::Uint(U256::from((*mode) as u64)), + Token::Uint(U256::from(*fee)), + Token::Uint(U256::from(*reward)), + ])]), + ) + }, + Command::SetOperatingMode { mode } => ( + self.clone().index(), + ethabi::encode(&vec![Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])]), + ), + Command::TransferNativeFromAgent { agent_id, recipient, amount } => ( + self.clone().index(), + ethabi::encode(&vec![Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), + ), + } + } +} + +/// A Sub-command executable within an agent +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +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(&vec![ + Token::Uint(self.index().into()), + Token::Bytes(ethabi::encode(&vec![ + Token::Address(*token), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])), + ]) + }, + } + } +} diff --git a/parachain/primitives/ethereum/Cargo.toml b/parachain/primitives/ethereum/Cargo.toml index dbc2890518..b0bcfffdc3 100644 --- a/parachain/primitives/ethereum/Cargo.toml +++ b/parachain/primitives/ethereum/Cargo.toml @@ -26,7 +26,6 @@ ethabi = { git = "https://github.com/snowfork/ethabi-decode.git", package = "eth [dev-dependencies] wasm-bindgen-test = "0.3.19" -snowbridge-testutils = { path = "../../primitives/testutils" } rand = "0.8.5" serde = { version = "1.0.164", features = [ "derive" ] } serde_json = "1.0.96" diff --git a/parachain/primitives/router/src/outbound/mod.rs b/parachain/primitives/router/src/outbound/mod.rs index 1d4c0a6ea0..a9bf6cb7c6 100644 --- a/parachain/primitives/router/src/outbound/mod.rs +++ b/parachain/primitives/router/src/outbound/mod.rs @@ -5,8 +5,8 @@ use core::slice::Iter; use codec::{Decode, Encode}; use frame_support::{ensure, log, traits::Get}; -use snowbridge_core::{ - AgentExecuteCommand, Command, OutboundMessage, OutboundQueue as OutboundQueueTrait, +use snowbridge_core::outbound::{ + AgentExecuteCommand, Command, Message, OutboundQueue as OutboundQueueTrait, }; use sp_core::{H160, H256}; use sp_std::{marker::PhantomData, prelude::*}; @@ -124,7 +124,7 @@ where }, }; - let outbound_message = OutboundMessage { + let outbound_message = Message { origin: para_id.into(), command: Command::AgentExecute { agent_id, command: agent_execute_command }, }; @@ -316,7 +316,7 @@ impl<'a, Call> XcmConverter<'a, Call> { mod tests { use frame_support::parameter_types; use hex_literal::hex; - use snowbridge_core::{OutboundMessageHash, SubmitError}; + use snowbridge_core::outbound::{MessageHash, SubmitError}; use super::*; @@ -335,23 +335,23 @@ mod tests { impl OutboundQueueTrait for MockOkOutboundQueue { type Ticket = (); - fn validate(_: &OutboundMessage) -> Result<(), SubmitError> { + fn validate(_: &Message) -> Result<(), SubmitError> { Ok(()) } - fn submit(_: Self::Ticket) -> Result { - Ok(OutboundMessageHash::zero()) + fn submit(_: Self::Ticket) -> Result { + Ok(MessageHash::zero()) } } struct MockErrOutboundQueue; impl OutboundQueueTrait for MockErrOutboundQueue { type Ticket = (); - fn validate(_: &OutboundMessage) -> Result<(), SubmitError> { + fn validate(_: &Message) -> Result<(), SubmitError> { Err(SubmitError::MessageTooLarge) } - fn submit(_: Self::Ticket) -> Result { + fn submit(_: Self::Ticket) -> Result { Err(SubmitError::MessageTooLarge) } } diff --git a/parachain/primitives/testutils/Cargo.toml b/parachain/primitives/testutils/Cargo.toml deleted file mode 100644 index ae708e74af..0000000000 --- a/parachain/primitives/testutils/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "snowbridge-testutils" -version = "0.1.0" -authors = [ "Snowfork " ] -edition = "2021" - -[dependencies] -ethereum-types = { version = "0.14.1", default-features = false } -hex = { package = "rustc-hex", version = "2.1.0", default-features = false } -serde = { version = "1.0.164", features = [ "derive" ] } -serde_json = "1.0.96" diff --git a/parachain/primitives/testutils/src/lib.rs b/parachain/primitives/testutils/src/lib.rs deleted file mode 100644 index e2ae97f12c..0000000000 --- a/parachain/primitives/testutils/src/lib.rs +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -use ethereum_types::{H128, H256, H512}; -use serde::{Deserialize, Deserializer}; -use std::{fs::File, path::Path}; - -/// The structs defined below are used to load Ethash merkle proofs -/// generated by https://github.com/talbaneth/ethashproof. -/// To generate proof JSON: -/// ./cmd/relayer/relayer $BLOCK_NUM -/// To load in test: -/// $proof_data = BlockWithProofs::from_file("path/to/proof.json") -/// .to_double_node_with_merkle_proof_vec(); - -#[derive(Clone)] -pub struct Hex(pub Vec); - -impl<'de> Deserialize<'de> for Hex { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - let mut s = ::deserialize(deserializer)?; - if s.starts_with("0x") { - s = s[2..].to_string(); - } - if s.len() % 2 == 1 { - s.insert_str(0, "0"); - } - let v: Vec = hex::FromHexIter::new(&s).map(|x| x.unwrap()).collect(); - Ok(Hex(v)) - } -} - -impl From<&Hex> for H256 { - fn from(item: &Hex) -> Self { - let mut data = [0u8; 32]; - let size = item.0.len(); - for i in 0..size { - data[31 - i] = item.0[size - 1 - i]; - } - data.into() - } -} - -impl From<&Hex> for H128 { - fn from(item: &Hex) -> Self { - let mut data = [0u8; 16]; - let size = item.0.len(); - for i in 0..size { - data[15 - i] = item.0[size - 1 - i]; - } - data.into() - } -} - -#[derive(Deserialize)] -struct BlockWithProofsRaw { - pub proof_length: u64, - pub header_rlp: Hex, - pub merkle_root: Hex, // H128 - pub elements: Vec, // H256 - pub merkle_proofs: Vec, // H128 -} - -pub struct BlockWithProofs { - pub proof_length: u64, - pub header_rlp: Hex, - pub merkle_root: H128, - pub elements: Vec, - pub merkle_proofs: Vec, -} - -impl From for BlockWithProofs { - fn from(item: BlockWithProofsRaw) -> Self { - Self { - proof_length: item.proof_length, - header_rlp: item.header_rlp, - merkle_root: (&item.merkle_root).into(), - elements: item.elements.iter().map(|e| e.into()).collect(), - merkle_proofs: item.merkle_proofs.iter().map(|e| e.into()).collect(), - } - } -} - -impl BlockWithProofs { - pub fn from_file(path: &Path) -> Self { - let raw: BlockWithProofsRaw = serde_json::from_reader(File::open(path).unwrap()).unwrap(); - raw.into() - } - - fn combine_dag_h256_to_h512(elements: Vec) -> Vec { - elements - .iter() - .zip(elements.iter().skip(1)) - .enumerate() - .filter(|(i, _)| i % 2 == 0) - .map(|(_, (a, b))| { - let mut buffer = [0u8; 64]; - buffer[..32].copy_from_slice(&(a.0)); - buffer[32..].copy_from_slice(&(b.0)); - buffer.into() - }) - .collect() - } - - pub fn to_double_node_with_merkle_proof_vec( - &self, - mapper: fn([H512; 2], Vec) -> T, - ) -> Vec { - let h512s = Self::combine_dag_h256_to_h512(self.elements.clone()); - h512s - .iter() - .zip(h512s.iter().skip(1)) - .enumerate() - .filter(|(i, _)| i % 2 == 0) - .map(|(i, (a, b))| { - mapper( - [*a, *b], - self.merkle_proofs[i / 2 * self.proof_length as usize.. - (i / 2 + 1) * self.proof_length as usize] - .to_vec(), - ) - }) - .collect() - } -}