diff --git a/Cargo.lock b/Cargo.lock index c6892f3f6..7ac303fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1349,6 +1349,7 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "darwinia-common-runtime", + "darwinia-message-transact", "darwinia-precompile-assets", "darwinia-precompile-bls12-381", "darwinia-precompile-state-storage", @@ -2297,6 +2298,36 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "darwinia-message-transact" +version = "6.0.0" +dependencies = [ + "array-bytes", + "bp-message-dispatch", + "bp-runtime", + "ethereum", + "fp-ethereum", + "fp-evm", + "fp-self-contained", + "frame-support", + "frame-system", + "libsecp256k1 0.5.0", + "pallet-balances", + "pallet-bridge-dispatch", + "pallet-ethereum", + "pallet-evm", + "pallet-timestamp", + "parity-scale-codec", + "precompile-utils", + "rlp", + "scale-info", + "sha3 0.9.1", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "darwinia-precompile-assets" version = "6.0.0" @@ -2377,6 +2408,7 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "darwinia-common-runtime", + "darwinia-message-transact", "darwinia-precompile-assets", "darwinia-precompile-bls12-381", "darwinia-precompile-state-storage", @@ -5189,6 +5221,25 @@ dependencies = [ "tikv-jemalloc-sys", ] +[[package]] +name = "libsecp256k1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd1137239ab33b41aa9637a88a28249e5e70c40a42ccc92db7f12cc356c1fcd7" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.2.2", + "libsecp256k1-gen-ecmult 0.2.1", + "libsecp256k1-gen-genmult 0.2.1", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + [[package]] name = "libsecp256k1" version = "0.6.0" @@ -7183,6 +7234,7 @@ dependencies = [ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "darwinia-common-runtime", + "darwinia-message-transact", "darwinia-precompile-assets", "darwinia-precompile-bls12-381", "darwinia-precompile-state-storage", diff --git a/Cargo.toml b/Cargo.toml index 17c5b7891..e63373346 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ panic = "unwind" members = [ "core/*", "node", + "pallets/*", "precompiles/*", "runtime/*", ] diff --git a/pallets/message-transact/Cargo.toml b/pallets/message-transact/Cargo.toml new file mode 100644 index 000000000..a8bc117f1 --- /dev/null +++ b/pallets/message-transact/Cargo.toml @@ -0,0 +1,73 @@ +[package] +authors = ["Darwinia Network "] +description = "State storage precompiles for EVM pallet." +edition = "2021" +homepage = "https://darwinia.network" +license = "GPL-3.0" +name = "darwinia-message-transact" +readme = "README.md" +repository = "https://github.com/darwinia-network/darwinia" +version = "6.0.0" + +[dependencies] +# crates.io +codec = { default-features = false, package = "parity-scale-codec", version = "3.2.1", features = ["derive"] } +ethereum = { default-features = false, version = "0.12.0", features = ["with-codec"] } +scale-info = { default-features = false, version = "2.3.0", features = ["derive"] } + +# frontier +fp-ethereum = { default-features = false, git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" } +fp-evm = { default-features = false, git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" } +pallet-evm = { default-features = false, git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" } + +# paritytech +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } + +[dev-dependencies] +array-bytes = { version = "4.1" } +libsecp256k1 = { version = "0.5", features = ["static-context", "hmac"] } +rlp = { version = "0.5" } +sha3 = { version = "0.9" } + +# darwinia +pallet-bridge-dispatch = { git = "https://github.com/darwinia-network/darwinia-messages-substrate", branch = "polkadot-v0.9.30" } +bp-message-dispatch = { git = "https://github.com/darwinia-network/darwinia-messages-substrate", branch = "polkadot-v0.9.30" } +bp-runtime = { git = "https://github.com/darwinia-network/darwinia-messages-substrate", branch = "polkadot-v0.9.30" } + +# frontier +fp-self-contained = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" } + +# moonbeam +precompile-utils = { git = "https://github.com/darwinia-network/moonbeam.git", branch = "polkadot-v0.9.30", features = ["testing"] } + +# substrate +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +pallet-ethereum = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.30" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" } + +[features] +default = ["std"] +std = [ + # crates.io + "codec/std", + "ethereum/std", + "scale-info/std", + + # frontier + "fp-evm/std", + "fp-ethereum/std", + "pallet-evm/std", + "pallet-ethereum/std", + + # paritytech + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/pallets/message-transact/src/lib.rs b/pallets/message-transact/src/lib.rs new file mode 100644 index 000000000..b6c097fe4 --- /dev/null +++ b/pallets/message-transact/src/lib.rs @@ -0,0 +1,201 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +// crates.io +use codec::{Decode, Encode, MaxEncodedLen}; +use ethereum::TransactionV2 as Transaction; +use frame_support::sp_runtime::traits::UniqueSaturatedInto; +use scale_info::TypeInfo; +// frontier +use fp_ethereum::{TransactionData, ValidatedTransaction}; +use fp_evm::{CheckEvmTransaction, CheckEvmTransactionConfig, InvalidEvmTransactionError}; +use pallet_evm::{FeeCalculator, GasWeightMapping}; +// substrate +use frame_support::{traits::EnsureOrigin, PalletError, RuntimeDebug}; +use sp_core::{H160, U256}; + +pub use pallet::*; + +#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub enum LcmpEthOrigin { + MessageTransact(H160), +} + +pub fn ensure_message_transact(o: OuterOrigin) -> Result +where + OuterOrigin: Into>, +{ + match o.into() { + Ok(LcmpEthOrigin::MessageTransact(n)) => Ok(n), + _ => Err("bad origin: expected to be an Lcmp Ethereum transaction"), + } +} + +pub struct EnsureLcmpEthOrigin; +impl> + From> EnsureOrigin + for EnsureLcmpEthOrigin +{ + type Success = H160; + + fn try_origin(o: O) -> Result { + o.into().map(|o| match o { + LcmpEthOrigin::MessageTransact(id) => id, + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(LcmpEthOrigin::MessageTransact(Default::default())) + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::origin] + pub type Origin = LcmpEthOrigin; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_evm::Config { + /// Handler for applying an already validated transaction + type ValidatedTransaction: ValidatedTransaction; + /// Origin for message transact + type LcmpEthOrigin: EnsureOrigin; + } + + #[pallet::error] + pub enum Error { + /// Evm validation errors. + MessageTransactError(EvmTxErrorWrapper), + } + + #[pallet::call] + impl Pallet + where + OriginFor: Into>>, + { + /// This call can only be called by the lcmp message layer and is not available to normal + /// users. + #[pallet::weight({ + let without_base_extrinsic_weight = true; + ::GasWeightMapping::gas_to_weight({ + let transaction_data: TransactionData = transaction.into(); + transaction_data.gas_limit.unique_saturated_into() + }, without_base_extrinsic_weight) + })] + pub fn message_transact( + origin: OriginFor, + transaction: Transaction, + ) -> DispatchResultWithPostInfo { + let source = ensure_message_transact(origin)?; + let (who, _) = pallet_evm::Pallet::::account_basic(&source); + let base_fee = T::FeeCalculator::min_gas_price().0; + + let mut transaction_mut = transaction; + match transaction_mut { + Transaction::Legacy(ref mut tx) => { + tx.nonce = who.nonce; + tx.gas_price = base_fee; + }, + Transaction::EIP2930(ref mut tx) => { + tx.nonce = who.nonce; + tx.gas_price = base_fee; + }, + Transaction::EIP1559(ref mut tx) => { + tx.nonce = who.nonce; + tx.max_fee_per_gas = base_fee; + tx.max_priority_fee_per_gas = U256::zero(); + }, + }; + + let transaction_data: TransactionData = (&transaction_mut).into(); + let _ = CheckEvmTransaction::::new( + CheckEvmTransactionConfig { + evm_config: T::config(), + block_gas_limit: T::BlockGasLimit::get(), + base_fee, + chain_id: T::ChainId::get(), + is_transactional: true, + }, + transaction_data.clone().into(), + ) + .validate_in_block_for(&who) + .and_then(|v| v.with_chain_id()) + .and_then(|v| v.with_base_fee()) + .and_then(|v| v.with_balance_for(&who)) + .map_err(|e| >::MessageTransactError(e))?; + + T::ValidatedTransaction::apply(source, transaction_mut) + } + } +} + +#[derive(Encode, Decode, TypeInfo, PalletError)] +pub enum EvmTxErrorWrapper { + GasLimitTooLow, + GasLimitTooHigh, + GasPriceTooLow, + PriorityFeeTooHigh, + BalanceTooLow, + TxNonceTooLow, + TxNonceTooHigh, + InvalidPaymentInput, + InvalidChainId, +} + +impl From for EvmTxErrorWrapper { + fn from(validation_error: InvalidEvmTransactionError) -> Self { + match validation_error { + InvalidEvmTransactionError::GasLimitTooLow => EvmTxErrorWrapper::GasLimitTooLow, + InvalidEvmTransactionError::GasLimitTooHigh => EvmTxErrorWrapper::GasLimitTooHigh, + InvalidEvmTransactionError::GasPriceTooLow => EvmTxErrorWrapper::GasPriceTooLow, + InvalidEvmTransactionError::PriorityFeeTooHigh => EvmTxErrorWrapper::PriorityFeeTooHigh, + InvalidEvmTransactionError::BalanceTooLow => EvmTxErrorWrapper::BalanceTooLow, + InvalidEvmTransactionError::TxNonceTooLow => EvmTxErrorWrapper::TxNonceTooLow, + InvalidEvmTransactionError::TxNonceTooHigh => EvmTxErrorWrapper::TxNonceTooHigh, + InvalidEvmTransactionError::InvalidPaymentInput => + EvmTxErrorWrapper::InvalidPaymentInput, + InvalidEvmTransactionError::InvalidChainId => EvmTxErrorWrapper::InvalidChainId, + } + } +} + +/// Calculates the fee for a relayer to submit an LCMP Evm transaction. +/// +/// The gas_price of an LCMP Evm transaction is always the min_gas_price(), which is a fixed value. +/// Therefore, only the gas_limit and value of the transaction should be considered in the +/// calculation of the fee, and the gas_price of the transaction itself can be ignored. +pub fn total_payment(tx_data: TransactionData) -> U256 { + let base_fee = ::FeeCalculator::min_gas_price().0; + let fee = base_fee.saturating_mul(tx_data.gas_limit); + + tx_data.value.saturating_add(fee) +} diff --git a/pallets/message-transact/src/mock.rs b/pallets/message-transact/src/mock.rs new file mode 100644 index 000000000..6bdd4cde4 --- /dev/null +++ b/pallets/message-transact/src/mock.rs @@ -0,0 +1,369 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +//! Test utilities + +// crates.io +use codec::{Decode, Encode}; +use sha3::{Digest, Keccak256}; +// frontier +use pallet_ethereum::IntermediateStateRoot; +use pallet_evm::IdentityAddressMapping; +// substrate +use frame_support::{ + dispatch::RawOrigin, + ensure, + pallet_prelude::Weight, + traits::{ConstU32, Everything}, +}; +use sp_core::{H256, U256}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, DispatchInfoOf, IdentifyAccount, IdentityLookup, Verify}, + transaction_validity::{InvalidTransaction, TransactionValidity, TransactionValidityError}, +}; +use sp_std::prelude::*; +// darwinia +use crate::*; +use bp_message_dispatch::{CallValidate, IntoDispatchOrigin as IntoDispatchOriginT}; + +pub type Block = frame_system::mocking::MockBlock; +pub type Balance = u64; +pub type AccountId = H160; +pub type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::parameter_types! { + pub const BlockHashCount: u64 = 250; +} +impl frame_system::Config for TestRuntime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountId; + type BaseCallFilter = Everything; + type BlockHashCount = (); + type BlockLength = (); + type BlockNumber = u64; + type BlockWeights = (); + type DbWeight = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = u64; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = (); + type SystemWeightInfo = (); + type Version = (); +} + +frame_support::parameter_types! { + pub const MaxLocks: u32 = 10; + pub const ExistentialDeposit: u64 = 0; +} +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +frame_support::parameter_types! { + pub const MinimumPeriod: u64 = 6000 / 2; +} +impl pallet_timestamp::Config for TestRuntime { + type MinimumPeriod = MinimumPeriod; + type Moment = u64; + type OnTimestampSet = (); + type WeightInfo = (); +} + +frame_support::parameter_types! { + pub const TransactionByteFee: u64 = 1; + pub const ChainId: u64 = 42; + pub const BlockGasLimit: U256 = U256::MAX; + pub const WeightPerGas: Weight = Weight::from_ref_time(20_000); +} + +pub struct FixedGasPrice; +impl FeeCalculator for FixedGasPrice { + fn min_gas_price() -> (U256, Weight) { + (U256::from(5), Weight::zero()) + } +} + +impl pallet_evm::Config for TestRuntime { + type AddressMapping = IdentityAddressMapping; + type BlockGasLimit = BlockGasLimit; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = pallet_evm::EnsureAddressRoot; + type ChainId = ChainId; + type Currency = Balances; + type FeeCalculator = FixedGasPrice; + type FindAuthor = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type OnChargeTransaction = (); + type PrecompilesType = (); + type PrecompilesValue = (); + type Runner = pallet_evm::runner::stack::Runner; + type RuntimeEvent = RuntimeEvent; + type WeightPerGas = WeightPerGas; + type WithdrawOrigin = pallet_evm::EnsureAddressNever; +} + +impl pallet_ethereum::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type StateRoot = IntermediateStateRoot; +} + +pub struct MockAccountIdConverter; +impl sp_runtime::traits::Convert for MockAccountIdConverter { + fn convert(hash: H256) -> AccountId { + hash.into() + } +} + +#[derive(Decode, Encode, Clone)] +pub struct MockEncodedCall(pub Vec); +impl From for Result { + fn from(call: MockEncodedCall) -> Result { + RuntimeCall::decode(&mut &call.0[..]).map_err(drop) + } +} + +pub struct MockCallValidator; +impl CallValidate for MockCallValidator { + fn check_receiving_before_dispatch( + relayer_account: &AccountId, + call: &RuntimeCall, + ) -> Result<(), &'static str> { + match call { + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: tx }) => { + let total_payment = crate::total_payment::(tx.into()); + let relayer = pallet_evm::Pallet::::account_basic(&relayer_account).0; + + ensure!(relayer.balance >= total_payment, "Insufficient balance"); + Ok(()) + }, + _ => Ok(()), + } + } + + fn call_validate( + relayer_account: &AccountId, + origin: &RuntimeOrigin, + call: &RuntimeCall, + ) -> Result<(), TransactionValidityError> { + match call { + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: tx }) => + match origin.caller { + OriginCaller::MessageTransact(LcmpEthOrigin::MessageTransact(id)) => { + let total_payment = crate::total_payment::(tx.into()); + pallet_balances::Pallet::::transfer( + RawOrigin::Signed(*relayer_account).into(), + id, + total_payment.as_u64(), + ) + .map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Payment) + })?; + + Ok(()) + }, + _ => Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner)), + }, + _ => Ok(()), + } + } +} +pub struct MockIntoDispatchOrigin; +impl IntoDispatchOriginT for MockIntoDispatchOrigin { + fn into_dispatch_origin(id: &AccountId, call: &RuntimeCall) -> RuntimeOrigin { + match call { + RuntimeCall::MessageTransact(crate::Call::message_transact { .. }) => + crate::LcmpEthOrigin::MessageTransact(*id).into(), + _ => frame_system::RawOrigin::Signed(*id).into(), + } + } +} +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct MockAccountPublic(AccountId); +impl IdentifyAccount for MockAccountPublic { + type AccountId = AccountId; + + fn into_account(self) -> AccountId { + self.0 + } +} +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +pub struct MockSignature(AccountId); +impl Verify for MockSignature { + type Signer = MockAccountPublic; + + fn verify>(&self, _msg: L, signer: &AccountId) -> bool { + self.0 == *signer + } +} + +pub(crate) type MockBridgeMessageId = [u8; 4]; + +impl pallet_bridge_dispatch::Config for TestRuntime { + type AccountIdConverter = MockAccountIdConverter; + type BridgeMessageId = MockBridgeMessageId; + type CallValidator = MockCallValidator; + type EncodedCall = MockEncodedCall; + type IntoDispatchOrigin = MockIntoDispatchOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type SourceChainAccountId = AccountId; + type TargetChainAccountPublic = MockAccountPublic; + type TargetChainSignature = MockSignature; +} + +impl crate::Config for TestRuntime { + type LcmpEthOrigin = crate::EnsureLcmpEthOrigin; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; +} + +frame_support::construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, + Ethereum: pallet_ethereum::{Pallet, Call, Storage, Event, Origin}, + MessageTransact: crate::{Pallet, Call, Origin}, + Dispatch: pallet_bridge_dispatch::{Pallet, Call, Event}, + } +} + +impl fp_self_contained::SelfContainedCall for RuntimeCall { + type SignedInfo = H160; + + fn is_self_contained(&self) -> bool { + match self { + RuntimeCall::Ethereum(call) => call.is_self_contained(), + _ => false, + } + } + + fn check_self_contained(&self) -> Option> { + match self { + RuntimeCall::Ethereum(call) => call.check_self_contained(), + _ => None, + } + } + + fn validate_self_contained( + &self, + info: &Self::SignedInfo, + dispatch_info: &DispatchInfoOf, + len: usize, + ) -> Option { + match self { + RuntimeCall::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len), + _ => None, + } + } + + fn pre_dispatch_self_contained( + &self, + info: &Self::SignedInfo, + dispatch_info: &DispatchInfoOf, + len: usize, + ) -> Option> { + match self { + RuntimeCall::Ethereum(call) => + call.pre_dispatch_self_contained(info, dispatch_info, len), + _ => None, + } + } + + fn apply_self_contained( + self, + info: Self::SignedInfo, + ) -> Option>> { + use sp_runtime::traits::Dispatchable as _; + match self { + call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => + Some(call.dispatch(RuntimeOrigin::from( + pallet_ethereum::RawOrigin::EthereumTransaction(info), + ))), + _ => None, + } + } +} + +pub(crate) struct AccountInfo { + pub address: H160, + pub private_key: H256, +} + +pub(crate) fn address_build(seed: u8) -> AccountInfo { + let raw_private_key = [seed + 1; 32]; + let secret_key = libsecp256k1::SecretKey::parse_slice(&raw_private_key).unwrap(); + let raw_public_key = &libsecp256k1::PublicKey::from_secret_key(&secret_key).serialize()[1..65]; + let raw_address = { + let mut s = [0; 20]; + s.copy_from_slice(&Keccak256::digest(raw_public_key)[12..]); + s + }; + + AccountInfo { private_key: raw_private_key.into(), address: raw_address.into() } +} + +#[derive(Default)] +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl ExtBuilder { + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { balances: self.balances } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/pallets/message-transact/src/tests/eip1559.rs b/pallets/message-transact/src/tests/eip1559.rs new file mode 100644 index 000000000..f13e65a02 --- /dev/null +++ b/pallets/message-transact/src/tests/eip1559.rs @@ -0,0 +1,249 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +// crates +use array_bytes::hex2bytes; +// darwinia +use crate::{mock::*, tests::*}; +// frontier +use fp_evm::FeeCalculator; +// substrate +use frame_support::pallet_prelude::Weight; +use sp_core::U256; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; + +pub fn eip1559_erc20_creation_unsigned_transaction() -> EIP1559UnsignedTransaction { + EIP1559UnsignedTransaction { + nonce: U256::zero(), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from(1), + gas_limit: U256::from(1_000_000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: hex2bytes(ERC20_CONTRACT_BYTECODE).unwrap(), + } +} + +#[test] +fn test_dispatch_eip1559_transaction_works() { + let alice = address_build(1); + let relayer_account = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer_account.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let unsigned_tx = eip1559_erc20_creation_unsigned_transaction(); + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer_account.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageDispatched( + SOURCE_CHAIN_ID, + mock_message_id, + Ok(()), + ), + )); + }); +} + +#[test] +fn test_dispatch_eip1559_transaction_weight_mismatch() { + let alice = address_build(1); + let relayer_account = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer_account.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = eip1559_erc20_creation_unsigned_transaction(); + // 62500001 * 16000 > 1_000_000_000_000 + unsigned_tx.gas_limit = U256::from(62500001); + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer_account.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(!result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageWeightMismatch( + SOURCE_CHAIN_ID, + mock_message_id, + Weight::from_ref_time(1249913722000), + Weight::from_ref_time(1000000000000), + ), + )); + }); +} + +#[test] +fn test_dispatch_eip1559_transaction_with_autoset_nonce() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = eip1559_erc20_creation_unsigned_transaction(); + unsigned_tx.nonce = U256::MAX; + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + }); +} + +#[test] +fn test_dispatch_eip1559_transaction_with_autoset_gas_price() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = eip1559_erc20_creation_unsigned_transaction(); + unsigned_tx.max_fee_per_gas = + ::FeeCalculator::min_gas_price().0 - 1; + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + }); +} + +#[test] +fn test_dispatch_eip1559_transaction_with_insufficient_relayer_balance() { + let alice = address_build(1); + let relayer1 = address_build(2); + let relayer2 = address_build(3); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer1.address, 1_000), + (relayer2.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let unsigned_tx = eip1559_erc20_creation_unsigned_transaction(); + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + // Failed in pre-dispatch balance check + let before_dispatch = + pallet_evm::Pallet::::account_basic(&relayer1.address).0.balance; + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer1.address, + mock_message_id, + Ok(message.clone()), + |_, _| Ok(()), + ); + assert!(!result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageCallValidateFailed( + SOURCE_CHAIN_ID, + mock_message_id, + TransactionValidityError::Invalid(InvalidTransaction::Payment), + ), + )); + let after_dispatch = + pallet_evm::Pallet::::account_basic(&relayer1.address).0.balance; + assert_eq!(before_dispatch, after_dispatch); + + let before_dispatch = + pallet_evm::Pallet::::account_basic(&relayer2.address).0.balance; + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer2.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + assert!(result.dispatch_result); + let after_dispatch = + pallet_evm::Pallet::::account_basic(&relayer2.address).0.balance; + assert!(before_dispatch > after_dispatch); + }); +} diff --git a/pallets/message-transact/src/tests/eip2930.rs b/pallets/message-transact/src/tests/eip2930.rs new file mode 100644 index 000000000..6cc99aafa --- /dev/null +++ b/pallets/message-transact/src/tests/eip2930.rs @@ -0,0 +1,248 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +// crates.io +use array_bytes::hex2bytes; +// darwinia +use crate::{mock::*, tests::*}; +// frontier +use fp_evm::FeeCalculator; +// substrate +use frame_support::pallet_prelude::Weight; +use sp_core::U256; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; + +fn eip2930_erc20_creation_unsigned_transaction() -> EIP2930UnsignedTransaction { + EIP2930UnsignedTransaction { + nonce: U256::zero(), + gas_price: U256::from(1), + gas_limit: U256::from(1_000_000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: hex2bytes(ERC20_CONTRACT_BYTECODE).unwrap(), + } +} + +#[test] +fn test_dispatch_eip2930_transaction_works() { + let alice = address_build(1); + let relayer_account = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer_account.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let unsigned_tx = eip2930_erc20_creation_unsigned_transaction(); + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer_account.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageDispatched( + SOURCE_CHAIN_ID, + mock_message_id, + Ok(()), + ), + )); + }); +} + +#[test] +fn test_dispatch_eip2930_transaction_weight_mismatch() { + let alice = address_build(1); + let relayer_account = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer_account.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = eip2930_erc20_creation_unsigned_transaction(); + // 62500001 * 16000 > 1_000_000_000_000 + unsigned_tx.gas_limit = U256::from(62500001); + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer_account.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(!result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageWeightMismatch( + SOURCE_CHAIN_ID, + mock_message_id, + Weight::from_ref_time(1249913722000), + Weight::from_ref_time(1000000000000), + ), + )); + }); +} + +#[test] +fn test_dispatch_eip2930_transaction_with_autoset_nonce() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = eip2930_erc20_creation_unsigned_transaction(); + unsigned_tx.nonce = U256::MAX; + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + }); +} + +#[test] +fn test_dispatch_eip2930_transaction_with_autoset_gas_price() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = eip2930_erc20_creation_unsigned_transaction(); + unsigned_tx.gas_price = + ::FeeCalculator::min_gas_price().0 - 1; + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + }); +} + +#[test] +fn test_dispatch_eip2930_transaction_with_insufficient_relayer_balance() { + let alice = address_build(1); + let relayer1 = address_build(2); + let relayer2 = address_build(3); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer1.address, 1_000), + (relayer2.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let unsigned_tx = eip2930_erc20_creation_unsigned_transaction(); + let t = unsigned_tx.sign(&alice.private_key, None); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + // Failed in pre-dispatch balance check + let before_dispatch = + pallet_evm::Pallet::::account_basic(&relayer1.address).0.balance; + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer1.address, + mock_message_id, + Ok(message.clone()), + |_, _| Ok(()), + ); + assert!(!result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageCallValidateFailed( + SOURCE_CHAIN_ID, + mock_message_id, + TransactionValidityError::Invalid(InvalidTransaction::Payment), + ), + )); + let after_dispatch = + pallet_evm::Pallet::::account_basic(&relayer1.address).0.balance; + assert_eq!(before_dispatch, after_dispatch); + + let before_dispatch = + pallet_evm::Pallet::::account_basic(&relayer2.address).0.balance; + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer2.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + assert!(result.dispatch_result); + let after_dispatch = + pallet_evm::Pallet::::account_basic(&relayer2.address).0.balance; + assert!(before_dispatch > after_dispatch); + }); +} diff --git a/pallets/message-transact/src/tests/legacy.rs b/pallets/message-transact/src/tests/legacy.rs new file mode 100644 index 000000000..2dd375e43 --- /dev/null +++ b/pallets/message-transact/src/tests/legacy.rs @@ -0,0 +1,245 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +// crates.io +use array_bytes::hex2bytes; +// darwinia +use crate::{mock::*, tests::*}; +// frontier +use fp_evm::FeeCalculator; +// substrate +use frame_support::pallet_prelude::Weight; +use sp_core::U256; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; + +pub fn legacy_erc20_creation_unsigned_transaction() -> LegacyUnsignedTransaction { + LegacyUnsignedTransaction { + nonce: U256::zero(), + gas_price: U256::from(1), + gas_limit: U256::from(1_000_000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: hex2bytes(ERC20_CONTRACT_BYTECODE).unwrap(), + } +} + +#[test] +fn test_dispatch_legacy_transaction_works() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let t = legacy_erc20_creation_unsigned_transaction().sign(&alice.private_key); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + assert!(result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageDispatched( + SOURCE_CHAIN_ID, + mock_message_id, + Ok(()), + ), + )); + }); +} + +#[test] +fn test_dispatch_legacy_transaction_weight_mismatch() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = legacy_erc20_creation_unsigned_transaction(); + unsigned_tx.gas_limit = U256::from(62500001); + let t = unsigned_tx.sign(&alice.private_key); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(!result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageWeightMismatch( + SOURCE_CHAIN_ID, + mock_message_id, + Weight::from_ref_time(1249913722000), + Weight::from_ref_time(1000000000000), + ), + )); + }); +} + +#[test] +fn test_dispatch_legacy_transaction_with_autoset_nonce() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = legacy_erc20_creation_unsigned_transaction(); + unsigned_tx.nonce = U256::MAX; + let t = unsigned_tx.sign(&alice.private_key); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + }); +} + +#[test] +fn test_dispatch_legacy_transaction_with_autoset_gas_price() { + let alice = address_build(1); + let relayer = address_build(2); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let mut unsigned_tx = legacy_erc20_creation_unsigned_transaction(); + unsigned_tx.gas_price = + ::FeeCalculator::min_gas_price().0 - 1; + let t = unsigned_tx.sign(&alice.private_key); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + + assert!(result.dispatch_result); + }); +} + +#[test] +fn test_dispatch_legacy_transaction_with_insufficient_relayer_balance() { + let alice = address_build(1); + let relayer1 = address_build(2); + let relayer2 = address_build(3); + + ExtBuilder::default() + .with_balances(vec![ + (alice.address, 1_000_000_000_000), + (relayer1.address, 1_000), + (relayer2.address, 1_000_000_000_000), + ]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let unsigned_tx = legacy_erc20_creation_unsigned_transaction(); + let t = unsigned_tx.sign(&alice.private_key); + let call = + RuntimeCall::MessageTransact(crate::Call::message_transact { transaction: t }); + let message = prepare_message(call); + + // Failed in pre-dispatch balance check + let before_dispatch = + pallet_evm::Pallet::::account_basic(&relayer1.address).0.balance; + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer1.address, + mock_message_id, + Ok(message.clone()), + |_, _| Ok(()), + ); + assert!(!result.dispatch_result); + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageCallValidateFailed( + SOURCE_CHAIN_ID, + mock_message_id, + TransactionValidityError::Invalid(InvalidTransaction::Payment), + ), + )); + let after_dispatch = + pallet_evm::Pallet::::account_basic(&relayer1.address).0.balance; + assert_eq!(before_dispatch, after_dispatch); + + let before_dispatch = + pallet_evm::Pallet::::account_basic(&relayer2.address).0.balance; + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer2.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + assert!(result.dispatch_result); + let after_dispatch = + pallet_evm::Pallet::::account_basic(&relayer2.address).0.balance; + assert!(before_dispatch > after_dispatch); + }); +} diff --git a/pallets/message-transact/src/tests/mod.rs b/pallets/message-transact/src/tests/mod.rs new file mode 100644 index 000000000..96480153c --- /dev/null +++ b/pallets/message-transact/src/tests/mod.rs @@ -0,0 +1,257 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +mod eip1559; +mod eip2930; +mod legacy; + +// crates.io +use codec::Encode; +use ethereum::{TransactionAction, TransactionSignature}; +use rlp::RlpStream; +use sha3::{Digest, Keccak256}; +// frontier +use fp_ethereum::Transaction; +// darwinia +use crate::mock::*; +use bp_message_dispatch::{CallOrigin, MessageDispatch, MessagePayload, SpecVersion}; +use bp_runtime::messages::DispatchFeePayment; +// substrate +use frame_support::pallet_prelude::Weight; +use sp_core::{H256, U256}; + +pub(crate) const TEST_SPEC_VERSION: SpecVersion = 0; +pub(crate) type SubChainId = [u8; 4]; +pub(crate) const SOURCE_CHAIN_ID: SubChainId = *b"srce"; +pub(crate) const TARGET_CHAIN_ID: SubChainId = *b"trgt"; +pub(crate) const TEST_WEIGHT: Weight = Weight::from_ref_time(1_000_000_000_000); + +// This ERC-20 contract mints the maximum amount of tokens to the contract creator. +// pragma solidity ^0.5.0; +// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/token/ERC20/ERC20.sol"; +// contract MyToken is ERC20 { +// constructor() public { _mint(msg.sender, 2**256 - 1); } +// } +pub const ERC20_CONTRACT_BYTECODE: &str = "608060405234801561001057600080fd5b50610041337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61004660201b60201c565b610291565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156100e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b6101028160025461020960201b610c7c1790919060201c565b60028190555061015d816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461020960201b610c7c1790919060201c565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b600080828401905083811015610287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b610e3a806102a06000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a08231146101fd578063a457c2d714610255578063a9059cbb146102bb578063dd62ed3e1461032157610088565b8063095ea7b31461008d57806318160ddd146100f357806323b872dd146101115780633950935114610197575b600080fd5b6100d9600480360360408110156100a357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610399565b604051808215151515815260200191505060405180910390f35b6100fb6103b7565b6040518082815260200191505060405180910390f35b61017d6004803603606081101561012757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506103c1565b604051808215151515815260200191505060405180910390f35b6101e3600480360360408110156101ad57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061049a565b604051808215151515815260200191505060405180910390f35b61023f6004803603602081101561021357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061054d565b6040518082815260200191505060405180910390f35b6102a16004803603604081101561026b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610595565b604051808215151515815260200191505060405180910390f35b610307600480360360408110156102d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610662565b604051808215151515815260200191505060405180910390f35b6103836004803603604081101561033757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610680565b6040518082815260200191505060405180910390f35b60006103ad6103a6610707565b848461070f565b6001905092915050565b6000600254905090565b60006103ce848484610906565b61048f846103da610707565b61048a85604051806060016040528060288152602001610d7060289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610440610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b600190509392505050565b60006105436104a7610707565b8461053e85600160006104b8610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b61070f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60006106586105a2610707565b8461065385604051806060016040528060258152602001610de160259139600160006105cc610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b6001905092915050565b600061067661066f610707565b8484610906565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610795576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180610dbd6024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561081b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610d286022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180610d986025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a12576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180610d056023913960400191505060405180910390fd5b610a7d81604051806060016040528060268152602001610d4a602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b10816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610c69576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610c2e578082015181840152602081019050610c13565b50505050905090810190601f168015610c5b5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610cfa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b809150509291505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa265627a7a72315820c7a5ffabf642bda14700b2de42f8c57b36621af020441df825de45fd2b3e1c5c64736f6c63430005100032"; + +fn prepare_message( + call: RuntimeCall, +) -> as MessageDispatch< + AccountId, + ::BridgeMessageId, +>>::Message { + MessagePayload { + spec_version: TEST_SPEC_VERSION, + weight: TEST_WEIGHT, + origin: CallOrigin::SourceAccount(AccountId::from_low_u64_be(1)), + dispatch_fee_payment: DispatchFeePayment::AtSourceChain, + call: MockEncodedCall(call.encode()), + } +} + +pub struct LegacyUnsignedTransaction { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub action: TransactionAction, + pub value: U256, + pub input: Vec, +} +impl LegacyUnsignedTransaction { + fn signing_rlp_append(&self, s: &mut RlpStream) { + s.begin_list(9); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas_limit); + s.append(&self.action); + s.append(&self.value); + s.append(&self.input); + s.append(&ChainId::get()); + s.append(&0u8); + s.append(&0u8); + } + + fn signing_hash(&self) -> H256 { + let mut stream = RlpStream::new(); + self.signing_rlp_append(&mut stream); + H256::from_slice(&Keccak256::digest(&stream.out()).as_slice()) + } + + pub fn sign(&self, key: &H256) -> Transaction { + self.sign_with_chain_id(key, ChainId::get()) + } + + pub fn sign_with_chain_id(&self, key: &H256, chain_id: u64) -> Transaction { + let hash = self.signing_hash(); + let msg = libsecp256k1::Message::parse(hash.as_fixed_bytes()); + let s = libsecp256k1::sign(&msg, &libsecp256k1::SecretKey::parse_slice(&key[..]).unwrap()); + let sig = s.0.serialize(); + + let sig = TransactionSignature::new( + s.1.serialize() as u64 % 2 + chain_id * 2 + 35, + H256::from_slice(&sig[0..32]), + H256::from_slice(&sig[32..64]), + ) + .unwrap(); + + Transaction::Legacy(ethereum::LegacyTransaction { + nonce: self.nonce, + gas_price: self.gas_price, + gas_limit: self.gas_limit, + action: self.action, + value: self.value, + input: self.input.clone(), + signature: sig, + }) + } +} + +pub struct EIP2930UnsignedTransaction { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub action: TransactionAction, + pub value: U256, + pub input: Vec, +} + +impl EIP2930UnsignedTransaction { + pub fn sign(&self, secret: &H256, chain_id: Option) -> Transaction { + let secret = { + let mut sk: [u8; 32] = [0u8; 32]; + sk.copy_from_slice(&secret[0..]); + libsecp256k1::SecretKey::parse(&sk).unwrap() + }; + let chain_id = chain_id.unwrap_or(ChainId::get()); + let msg = ethereum::EIP2930TransactionMessage { + chain_id, + nonce: self.nonce, + gas_price: self.gas_price, + gas_limit: self.gas_limit, + action: self.action, + value: self.value, + input: self.input.clone(), + access_list: vec![], + }; + let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..]).unwrap(); + + let (signature, recid) = libsecp256k1::sign(&signing_message, &secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + Transaction::EIP2930(ethereum::EIP2930Transaction { + chain_id: msg.chain_id, + nonce: msg.nonce, + gas_price: msg.gas_price, + gas_limit: msg.gas_limit, + action: msg.action, + value: msg.value, + input: msg.input.clone(), + access_list: msg.access_list, + odd_y_parity: recid.serialize() != 0, + r, + + s, + }) + } +} + +pub struct EIP1559UnsignedTransaction { + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub action: TransactionAction, + pub value: U256, + pub input: Vec, +} + +impl EIP1559UnsignedTransaction { + pub fn sign(&self, secret: &H256, chain_id: Option) -> Transaction { + let secret = { + let mut sk: [u8; 32] = [0u8; 32]; + sk.copy_from_slice(&secret[0..]); + libsecp256k1::SecretKey::parse(&sk).unwrap() + }; + let chain_id = chain_id.unwrap_or(ChainId::get()); + let msg = ethereum::EIP1559TransactionMessage { + chain_id, + nonce: self.nonce, + max_priority_fee_per_gas: self.max_priority_fee_per_gas, + max_fee_per_gas: self.max_fee_per_gas, + gas_limit: self.gas_limit, + action: self.action, + value: self.value, + input: self.input.clone(), + access_list: vec![], + }; + let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..]).unwrap(); + + let (signature, recid) = libsecp256k1::sign(&signing_message, &secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: msg.chain_id, + nonce: msg.nonce, + max_priority_fee_per_gas: msg.max_priority_fee_per_gas, + max_fee_per_gas: msg.max_fee_per_gas, + gas_limit: msg.gas_limit, + action: msg.action, + value: msg.value, + input: msg.input.clone(), + access_list: msg.access_list, + odd_y_parity: recid.serialize() != 0, + r, + s, + }) + } +} + +#[test] +fn test_dispatch_basic_system_call_works() { + let relayer_account = address_build(1); + + ExtBuilder::default() + .with_balances(vec![(relayer_account.address, 1000)]) + .build() + .execute_with(|| { + let mock_message_id = [0; 4]; + let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + let message = prepare_message(call); + System::set_block_number(1); + let result = Dispatch::dispatch( + SOURCE_CHAIN_ID, + TARGET_CHAIN_ID, + &relayer_account.address, + mock_message_id, + Ok(message), + |_, _| Ok(()), + ); + assert!(!result.dispatch_fee_paid_during_dispatch); + assert!(result.dispatch_result); + + System::assert_has_event(RuntimeEvent::Dispatch( + pallet_bridge_dispatch::Event::MessageDispatched( + SOURCE_CHAIN_ID, + mock_message_id, + Ok(()), + ), + )); + }); +} diff --git a/precompiles/state-storage/src/mock.rs b/precompiles/state-storage/src/mock.rs index cc8f2ad0f..5d24429ee 100644 --- a/precompiles/state-storage/src/mock.rs +++ b/precompiles/state-storage/src/mock.rs @@ -23,7 +23,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; // frontier use fp_evm::{Precompile, PrecompileSet}; use pallet_evm::IdentityAddressMapping; -// parity +// substrate use frame_support::{ pallet_prelude::Weight, traits::{ConstU32, Everything}, diff --git a/runtime/crab/Cargo.toml b/runtime/crab/Cargo.toml index 307e2945c..68f73e061 100644 --- a/runtime/crab/Cargo.toml +++ b/runtime/crab/Cargo.toml @@ -37,6 +37,7 @@ cumulus-pallet-session-benchmarking = { optional = true, default-features = fals # darwinia darwinia-common-runtime = { default-features = false, path = "../common" } +darwinia-message-transact = { default-features = false, path = "../../pallets/message-transact" } darwinia-precompile-assets = { default-features = false, path = "../../precompiles/assets" } darwinia-precompile-bls12-381 = { default-features = false, path = "../../precompiles/bls12-381" } darwinia-precompile-state-storage = { default-features = false, path = "../../precompiles/state-storage" } @@ -144,6 +145,7 @@ std = [ # darwinia "darwinia-common-runtime/std", + "darwinia-message-transact/std", "darwinia-precompile-assets/std", "darwinia-precompile-bls12-381/std", "darwinia-precompile-state-storage/std", diff --git a/runtime/crab/src/lib.rs b/runtime/crab/src/lib.rs index 93d3c69fa..d9ff10911 100644 --- a/runtime/crab/src/lib.rs +++ b/runtime/crab/src/lib.rs @@ -313,6 +313,7 @@ frame_support::construct_runtime! { Ethereum: pallet_ethereum = 31, Evm: pallet_evm = 32, BaseFee: pallet_base_fee = 33, + MessageTransact: darwinia_message_transact = 39, // S2S stuff BridgeDarwiniaGrandpa: pallet_bridge_grandpa:: = 35, diff --git a/runtime/crab/src/pallets/bridge_dispatch.rs b/runtime/crab/src/pallets/bridge_dispatch.rs index d5a919a78..963e98b74 100644 --- a/runtime/crab/src/pallets/bridge_dispatch.rs +++ b/runtime/crab/src/pallets/bridge_dispatch.rs @@ -20,21 +20,79 @@ pub use pallet_bridge_dispatch::Instance1 as WithDarwiniaDispatch; // darwinia use crate::*; -use bp_message_dispatch::{Everything, IntoDispatchOrigin as IntoDispatchOriginT}; +use bp_message_dispatch::{CallValidate, IntoDispatchOrigin as IntoDispatchOriginT}; use bp_messages::{LaneId, MessageNonce}; +use darwinia_message_transact::{total_payment, Call as MessageTransactCall, LcmpEthOrigin}; use pallet_bridge_dispatch::Config; +// substrate +use frame_support::ensure; +use frame_system::RawOrigin; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; + +pub struct CallValidator; +impl CallValidate for CallValidator { + fn check_receiving_before_dispatch( + relayer_account: &AccountId, + call: &RuntimeCall, + ) -> Result<(), &'static str> { + match call { + RuntimeCall::MessageTransact(MessageTransactCall::message_transact { + transaction: tx, + }) => { + let total_payment = total_payment::(tx.into()); + let relayer = + pallet_evm::Pallet::::account_basic(&H160(relayer_account.0)).0; + + ensure!(relayer.balance >= total_payment, "Insufficient balance"); + Ok(()) + }, + _ => Ok(()), + } + } + + fn call_validate( + relayer_account: &AccountId, + origin: &RuntimeOrigin, + call: &RuntimeCall, + ) -> Result<(), TransactionValidityError> { + match call { + RuntimeCall::MessageTransact(MessageTransactCall::message_transact { + transaction: tx, + }) => match origin.caller { + OriginCaller::MessageTransact(LcmpEthOrigin::MessageTransact(id)) => { + let total_payment = total_payment::(tx.into()); + pallet_balances::Pallet::::transfer( + RawOrigin::Signed(*relayer_account).into(), + id.into(), + total_payment.as_u128(), + ) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?; + + Ok(()) + }, + _ => Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner)), + }, + _ => Ok(()), + } + } +} pub struct IntoDispatchOrigin; -impl IntoDispatchOriginT for IntoDispatchOrigin { - fn into_dispatch_origin(id: &bp_crab::AccountId, _: &RuntimeCall) -> RuntimeOrigin { - frame_system::RawOrigin::Signed(id.clone()).into() +impl IntoDispatchOriginT for IntoDispatchOrigin { + fn into_dispatch_origin(id: &AccountId, call: &RuntimeCall) -> RuntimeOrigin { + match call { + RuntimeCall::MessageTransact(darwinia_message_transact::Call::message_transact { + .. + }) => darwinia_message_transact::LcmpEthOrigin::MessageTransact(H160(id.0)).into(), + _ => frame_system::RawOrigin::Signed(id.clone()).into(), + } } } impl Config for Runtime { type AccountIdConverter = bp_crab::AccountIdConverter; type BridgeMessageId = (LaneId, MessageNonce); - type CallValidator = Everything; + type CallValidator = CallValidator; type EncodedCall = bm_darwinia::FromDarwiniaEncodedCall; type IntoDispatchOrigin = IntoDispatchOrigin; type RuntimeCall = RuntimeCall; diff --git a/runtime/crab/src/pallets/message_transact.rs b/runtime/crab/src/pallets/message_transact.rs new file mode 100644 index 000000000..3ab8d65fa --- /dev/null +++ b/runtime/crab/src/pallets/message_transact.rs @@ -0,0 +1,25 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +// darwinia +use crate::*; + +impl darwinia_message_transact::Config for Runtime { + type LcmpEthOrigin = darwinia_message_transact::EnsureLcmpEthOrigin; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; +} diff --git a/runtime/crab/src/pallets/mod.rs b/runtime/crab/src/pallets/mod.rs index 6db8976d6..1df2a00f4 100644 --- a/runtime/crab/src/pallets/mod.rs +++ b/runtime/crab/src/pallets/mod.rs @@ -101,6 +101,8 @@ pub use evm::*; mod base_fee; +mod message_transact; + // S2S stuff mod bridge_dispatch; pub use bridge_dispatch::*; diff --git a/runtime/darwinia/Cargo.toml b/runtime/darwinia/Cargo.toml index 658f294a6..7d2958e03 100644 --- a/runtime/darwinia/Cargo.toml +++ b/runtime/darwinia/Cargo.toml @@ -37,6 +37,7 @@ cumulus-pallet-session-benchmarking = { optional = true, default-features = fals # darwinia darwinia-common-runtime = { default-features = false, path = "../common" } +darwinia-message-transact = { default-features = false, path = "../../pallets/message-transact" } darwinia-precompile-assets = { default-features = false, path = "../../precompiles/assets" } darwinia-precompile-bls12-381 = { default-features = false, path = "../../precompiles/bls12-381" } darwinia-precompile-state-storage = { default-features = false, path = "../../precompiles/state-storage" } @@ -144,6 +145,7 @@ std = [ # darwinia "darwinia-common-runtime/std", + "darwinia-message-transact/std", "darwinia-precompile-assets/std", "darwinia-precompile-bls12-381/std", "darwinia-precompile-state-storage/std", diff --git a/runtime/darwinia/src/lib.rs b/runtime/darwinia/src/lib.rs index a6ef11a7d..15ecc0e06 100644 --- a/runtime/darwinia/src/lib.rs +++ b/runtime/darwinia/src/lib.rs @@ -313,6 +313,7 @@ frame_support::construct_runtime! { Ethereum: pallet_ethereum = 31, Evm: pallet_evm = 32, BaseFee: pallet_base_fee = 33, + MessageTransact: darwinia_message_transact = 39, // S2S stuff BridgeCrabGrandpa: pallet_bridge_grandpa:: = 35, diff --git a/runtime/darwinia/src/pallets/bridge_dispatch.rs b/runtime/darwinia/src/pallets/bridge_dispatch.rs index 696eb8679..361c23f64 100644 --- a/runtime/darwinia/src/pallets/bridge_dispatch.rs +++ b/runtime/darwinia/src/pallets/bridge_dispatch.rs @@ -20,23 +20,79 @@ pub use pallet_bridge_dispatch::Instance1 as WithCrabDispatch; // darwinia use crate::*; -use bp_message_dispatch::{Everything, IntoDispatchOrigin as IntoDispatchOriginT}; +use bp_message_dispatch::{CallValidate, IntoDispatchOrigin as IntoDispatchOriginT}; use bp_messages::{LaneId, MessageNonce}; +use darwinia_message_transact::{total_payment, Call as MessageTransactCall, LcmpEthOrigin}; use pallet_bridge_dispatch::Config; +// substrate +use frame_support::ensure; +use frame_system::RawOrigin; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; + +pub struct CallValidator; +impl CallValidate for CallValidator { + fn check_receiving_before_dispatch( + relayer_account: &AccountId, + call: &RuntimeCall, + ) -> Result<(), &'static str> { + match call { + RuntimeCall::MessageTransact(MessageTransactCall::message_transact { + transaction: tx, + }) => { + let total_payment = total_payment::(tx.into()); + let relayer = + pallet_evm::Pallet::::account_basic(&H160(relayer_account.0)).0; + + ensure!(relayer.balance >= total_payment, "Insufficient balance"); + Ok(()) + }, + _ => Ok(()), + } + } + + fn call_validate( + relayer_account: &AccountId, + origin: &RuntimeOrigin, + call: &RuntimeCall, + ) -> Result<(), TransactionValidityError> { + match call { + RuntimeCall::MessageTransact(MessageTransactCall::message_transact { + transaction: tx, + }) => match origin.caller { + OriginCaller::MessageTransact(LcmpEthOrigin::MessageTransact(id)) => { + let total_payment = total_payment::(tx.into()); + pallet_balances::Pallet::::transfer( + RawOrigin::Signed(*relayer_account).into(), + id.into(), + total_payment.as_u128(), + ) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?; + + Ok(()) + }, + _ => Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner)), + }, + _ => Ok(()), + } + } +} pub struct IntoDispatchOrigin; -impl IntoDispatchOriginT - for IntoDispatchOrigin -{ - fn into_dispatch_origin(id: &bp_darwinia::AccountId, _: &RuntimeCall) -> RuntimeOrigin { - frame_system::RawOrigin::Signed(id.clone()).into() +impl IntoDispatchOriginT for IntoDispatchOrigin { + fn into_dispatch_origin(id: &AccountId, call: &RuntimeCall) -> RuntimeOrigin { + match call { + RuntimeCall::MessageTransact(darwinia_message_transact::Call::message_transact { + .. + }) => darwinia_message_transact::LcmpEthOrigin::MessageTransact(H160(id.0)).into(), + _ => frame_system::RawOrigin::Signed(id.clone()).into(), + } } } impl Config for Runtime { type AccountIdConverter = bp_darwinia::AccountIdConverter; type BridgeMessageId = (LaneId, MessageNonce); - type CallValidator = Everything; + type CallValidator = CallValidator; type EncodedCall = bm_crab::FromCrabEncodedCall; type IntoDispatchOrigin = IntoDispatchOrigin; type RuntimeCall = RuntimeCall; diff --git a/runtime/darwinia/src/pallets/message_transact.rs b/runtime/darwinia/src/pallets/message_transact.rs new file mode 100644 index 000000000..3ab8d65fa --- /dev/null +++ b/runtime/darwinia/src/pallets/message_transact.rs @@ -0,0 +1,25 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +// darwinia +use crate::*; + +impl darwinia_message_transact::Config for Runtime { + type LcmpEthOrigin = darwinia_message_transact::EnsureLcmpEthOrigin; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; +} diff --git a/runtime/darwinia/src/pallets/mod.rs b/runtime/darwinia/src/pallets/mod.rs index 6db8976d6..1df2a00f4 100644 --- a/runtime/darwinia/src/pallets/mod.rs +++ b/runtime/darwinia/src/pallets/mod.rs @@ -101,6 +101,8 @@ pub use evm::*; mod base_fee; +mod message_transact; + // S2S stuff mod bridge_dispatch; pub use bridge_dispatch::*; diff --git a/runtime/pangolin/Cargo.toml b/runtime/pangolin/Cargo.toml index a2c8db8b4..f4e4c58a8 100644 --- a/runtime/pangolin/Cargo.toml +++ b/runtime/pangolin/Cargo.toml @@ -37,6 +37,7 @@ cumulus-pallet-session-benchmarking = { optional = true, default-features = fals # darwinia darwinia-common-runtime = { default-features = false, path = "../common" } +darwinia-message-transact = { default-features = false, path = "../../pallets/message-transact" } darwinia-precompile-assets = { default-features = false, path = "../../precompiles/assets" } darwinia-precompile-bls12-381 = { default-features = false, path = "../../precompiles/bls12-381" } darwinia-precompile-state-storage = { default-features = false, path = "../../precompiles/state-storage" } @@ -144,6 +145,7 @@ std = [ # darwinia "darwinia-common-runtime/std", + "darwinia-message-transact/std", "darwinia-precompile-assets/std", "darwinia-precompile-bls12-381/std", "darwinia-precompile-state-storage/std", diff --git a/runtime/pangolin/src/lib.rs b/runtime/pangolin/src/lib.rs index 219e0d039..cf5aea755 100644 --- a/runtime/pangolin/src/lib.rs +++ b/runtime/pangolin/src/lib.rs @@ -309,6 +309,7 @@ frame_support::construct_runtime! { Ethereum: pallet_ethereum = 31, Evm: pallet_evm = 32, BaseFee: pallet_base_fee = 33, + MessageTransact: darwinia_message_transact = 35, } } diff --git a/runtime/pangolin/src/pallets/message_transact.rs b/runtime/pangolin/src/pallets/message_transact.rs new file mode 100644 index 000000000..3ab8d65fa --- /dev/null +++ b/runtime/pangolin/src/pallets/message_transact.rs @@ -0,0 +1,25 @@ +// This file is part of Darwinia. +// +// Copyright (C) 2018-2022 Darwinia Network +// SPDX-License-Identifier: GPL-3.0 +// +// Darwinia is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Darwinia is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Darwinia. If not, see . + +// darwinia +use crate::*; + +impl darwinia_message_transact::Config for Runtime { + type LcmpEthOrigin = darwinia_message_transact::EnsureLcmpEthOrigin; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; +} diff --git a/runtime/pangolin/src/pallets/mod.rs b/runtime/pangolin/src/pallets/mod.rs index dc7dbb37c..ea7d4188f 100644 --- a/runtime/pangolin/src/pallets/mod.rs +++ b/runtime/pangolin/src/pallets/mod.rs @@ -100,3 +100,5 @@ mod evm; pub use evm::*; mod base_fee; + +mod message_transact;