Skip to content
This repository has been archived by the owner on Mar 13, 2023. It is now read-only.

Integrate Ethereum::message_transact call from dispatch module #1267

Merged
merged 48 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
741f8f2
A copy-paste version
boundless-forest Jun 8, 2022
f47095b
Adapt for other two places
boundless-forest Jun 9, 2022
854fccc
Simplify the implementation
boundless-forest Jun 9, 2022
eb9b24f
Add evm support
boundless-forest Jun 9, 2022
e874faa
Fix
boundless-forest Jun 9, 2022
3852856
Test updated trait
boundless-forest Jun 9, 2022
bffa1b7
Get the matched value, Thanks Xavier
boundless-forest Jun 9, 2022
5c67cff
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
boundless-forest Jun 10, 2022
d3597b0
Fix compile
boundless-forest Jun 10, 2022
7435077
Impl call filter and into dispatch origin for pangoro
boundless-forest Jun 10, 2022
482a61a
That's a hard one to write
boundless-forest Jun 10, 2022
1c5a8bc
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
boundless-forest Jun 13, 2022
fe4b2d0
Code clean for pangoro mess
boundless-forest Jun 13, 2022
06c5bc6
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
boundless-forest Jun 13, 2022
3449c91
Adapt for pangolin runtime, compile is ok
boundless-forest Jun 13, 2022
b45c2f4
Fix tx validation check
boundless-forest Jun 13, 2022
3ae8454
Fix tx
boundless-forest Jun 13, 2022
2be2623
Fix tx
boundless-forest Jun 13, 2022
885653d
Use patch to dev
boundless-forest Jun 14, 2022
5c48a90
Revert changes
boundless-forest Jun 14, 2022
0d82220
A new version
boundless-forest Jun 14, 2022
1104a1c
Fix compile
boundless-forest Jun 14, 2022
9d3b3c3
Fix tests
boundless-forest Jun 14, 2022
1e39805
Remove useless imports
boundless-forest Jun 14, 2022
7bd2478
Optimal pre_dispatch
boundless-forest Jun 14, 2022
dff1ac9
Add weight check test
boundless-forest Jun 14, 2022
e1dddbc
Rename
boundless-forest Jun 14, 2022
c8618cb
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
boundless-forest Jun 16, 2022
dbf382c
Adapt for changes
boundless-forest Jun 16, 2022
07a8452
Code clean
boundless-forest Jun 16, 2022
8784a2c
Add more test
boundless-forest Jun 16, 2022
1f8e5e0
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
hackfisher Jun 16, 2022
a631b79
Merge branch 'main' into bear-eth-transact
boundless-forest Jun 16, 2022
f2d684d
Forbid payable call
boundless-forest Jun 16, 2022
62ad6e2
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
hackfisher Jun 16, 2022
45c5343
Merge branch 'main' into bear-move-FromBridgedChainMessageDispatch
hackfisher Jun 17, 2022
3315f11
Adapt new trait
boundless-forest Jun 17, 2022
313f6e3
Add todo
boundless-forest Jun 17, 2022
f738983
Add message transact dispatch all, only used by message layer (#1296)
hackfisher Jun 17, 2022
6a59c89
Fix gas limit, and change call from transact to message_transact
hackfisher Jun 17, 2022
ba7b13c
add missing changes
hackfisher Jun 17, 2022
65f210f
check_receiving_before_dispatch and update todos
hackfisher Jun 17, 2022
05ef8d5
Fix dispatch in pangoro runtime
boundless-forest Jun 19, 2022
f7d1e5d
Fix dispatch in pangolin runtime
boundless-forest Jun 19, 2022
af82891
Fix tests
boundless-forest Jun 19, 2022
1f4fad1
Add comments
boundless-forest Jun 19, 2022
e4349b4
Update lock file
boundless-forest Jun 19, 2022
57344c1
fix check (#1297)
hackfisher Jun 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 54 additions & 50 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions frame/dvm/ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ pallet-evm = { default-features = false, git = "https://github.com/darwin
array-bytes = { version = "1.5" }
libsecp256k1 = { version = "0.5", features = ["static-context", "hmac"] }
# darwinia-network
darwinia-balances = { path = "../../balances" }
darwinia-support = { features = ["testing"], path = "../../support" }
bp-message-dispatch = { git = "https://github.com/darwinia-network/darwinia-messages-substrate", branch = "darwinia-v0.12.3" }
bp-runtime = { git = "https://github.com/darwinia-network/darwinia-messages-substrate", branch = "darwinia-v0.12.3" }
darwinia-balances = { path = "../../balances" }
darwinia-support = { features = ["testing"], path = "../../support" }
pallet-bridge-dispatch = { git = "https://github.com/darwinia-network/darwinia-messages-substrate", branch = "darwinia-v0.12.3" }
# frontier
pallet-evm-precompile-simple = { git = "https://github.com/darwinia-network/frontier", branch = "darwinia-v0.12.3" }

Expand Down
43 changes: 43 additions & 0 deletions frame/dvm/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,45 @@ pub mod pallet {
Self::apply_validated_transaction(source, transaction)
}

/// This is message transact only for substrate to substrate LCMP to call
#[pallet::weight(<T as darwinia_evm::Config>::GasWeightMapping::gas_to_weight(
Pallet::<T>::transaction_data(transaction).gas_limit.unique_saturated_into()
))]
pub fn message_transact(
origin: OriginFor<T>,
transaction: Transaction,
) -> DispatchResultWithPostInfo {
// Source address supposed to be derived address generate from message layer
let source = ensure_ethereum_transaction(origin)?;

// Disable transact functionality if PreLog exist.
ensure!(
fp_consensus::find_pre_log(&frame_system::Pallet::<T>::digest()).is_err(),
Error::<T>::PreLogExists,
);

let extracted_transaction = match transaction {
Transaction::Legacy(t) => Ok(Transaction::Legacy(ethereum::LegacyTransaction {
nonce: <T as darwinia_evm::Config>::RingAccountBasic::account_basic(&source)
.nonce, // auto set
gas_price: T::FeeCalculator::min_gas_price(), // auto set
gas_limit: t.gas_limit,
action: t.action,
value: t.value,
input: t.input,
signature: t.signature, // not used.
})),
_ => Err(Error::<T>::MessageTransactionError),
}?;

ensure!(
Self::validate_transaction_in_block(source, &extracted_transaction).is_ok(),
Error::<T>::MessageValidateError
);

Self::apply_validated_transaction(source, extracted_transaction)
}

/// Internal transaction only for root.
#[pallet::weight(10_000_000)]
pub fn root_transact(
Expand Down Expand Up @@ -320,6 +359,10 @@ pub mod pallet {
InternalTransactionFatalError,
/// The internal call failed.
ReadyOnlyCall,
/// Message transaction invalid
MessageTransactionError,
/// Message validate invalid
MessageValidateError,
}

/// Current building block's transactions and receipts.
Expand Down
217 changes: 210 additions & 7 deletions frame/dvm/ethereum/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use sha3::{Digest, Keccak256};
// --- paritytech ---
use fp_evm::{Context, Precompile, PrecompileResult, PrecompileSet};
use frame_support::{
traits::{Everything, FindAuthor, GenesisBuild},
traits::{Everything, FindAuthor, GenesisBuild, OriginTrait, WithdrawReasons},
weights::GetDispatchInfo,
ConsensusEngineId, PalletId,
};
Expand All @@ -37,20 +37,23 @@ use pallet_evm_precompile_simple::{ECRecover, Identity, Ripemd160, Sha256};
use sp_core::{H160, H256, U256};
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
AccountId32, Perbill, RuntimeDebug,
};
use sp_std::prelude::*;
use sp_std::{cmp, prelude::*};
// --- darwinia-network ---
use crate::{self as darwinia_ethereum, account_basic::*, *};
use bp_message_dispatch::{CallValidate, IntoDispatchOrigin as IntoDispatchOriginT};
use darwinia_evm::{runner::stack::Runner, EVMCurrencyAdapter, EnsureAddressTruncated};
use darwinia_support::evm::DeriveSubstrateAddress;
use darwinia_support::evm::{
decimal_convert, DeriveEthereumAddress, DeriveSubstrateAddress, POW_9,
};

type Block = MockBlock<Test>;
pub type SignedExtra = (frame_system::CheckSpecVersion<Test>,);
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test, (), SignedExtra>;
type Balance = u64;

pub type SignedExtra = (frame_system::CheckSpecVersion<Test>,);
pub type EthereumTransactCall = darwinia_ethereum::Call<Test>;
pub type TestRuntimeCall = <Test as frame_system::Config>::Call;

Expand Down Expand Up @@ -92,7 +95,7 @@ frame_support::parameter_types! {
// For weight estimation, we assume that the most locks on an individual account will be 50.
// This number may need to be adjusted in the future if this assumption no longer holds true.
pub const MaxLocks: u32 = 10;
pub const ExistentialDeposit: u64 = 500;
pub const ExistentialDeposit: u64 = 0;
}
impl darwinia_balances::Config<RingInstance> for Test {
type AccountStore = System;
Expand Down Expand Up @@ -239,6 +242,206 @@ impl darwinia_ethereum::Config for Test {
type StateRoot = IntermediateStateRoot;
}

// --- pallet-bridge-dispatch config start ---
pub(crate) type BridgeMessageId = [u8; 4];
pub(crate) type SubChainId = [u8; 4];
pub(crate) const SOURCE_CHAIN_ID: SubChainId = *b"srce";
pub(crate) const TARGET_CHAIN_ID: SubChainId = *b"trgt";

frame_support::parameter_types! {
pub const MaxUsableBalanceFromRelayer: Balance = 100;
}

fn evm_ensure_can_withdraw(
who: &AccountId32,
amount: U256,
reasons: WithdrawReasons,
) -> Result<(), TransactionValidityError> {
// Ensure the account's evm account has enough balance to withdraw.
let old_evm_balance = <Test as darwinia_evm::Config>::RingAccountBasic::account_balance(who);
let (_old_sub, old_remaining) = old_evm_balance.div_mod(U256::from(POW_9));
ensure!(
old_evm_balance > amount,
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);

let (mut amount_sub, amount_remaining) = amount.div_mod(U256::from(POW_9));
if old_remaining < amount_remaining {
amount_sub = amount_sub.saturating_add(U256::from(1));
}

let new_evm_balance = old_evm_balance.saturating_sub(amount);
let (new_sub, _new_remaining) = new_evm_balance.div_mod(U256::from(POW_9));

// Ensure the account underlying substrate account has no liquidity restrictions.
ensure!(
Ring::ensure_can_withdraw(
who,
amount_sub.low_u128().unique_saturated_into(),
reasons,
new_sub.low_u128().unique_saturated_into(),
)
.is_ok(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);

Ok(())
}

pub struct AccountIdConverter;
impl sp_runtime::traits::Convert<H256, AccountId32> for AccountIdConverter {
fn convert(hash: H256) -> AccountId32 {
AccountId32::new(hash.0)
}
}

#[derive(Decode, Encode, Clone)]
pub struct EncodedCall(pub Vec<u8>);
impl From<EncodedCall> for Result<Call, ()> {
fn from(call: EncodedCall) -> Result<Call, ()> {
Call::decode(&mut &call.0[..]).map_err(drop)
}
}

pub struct CallValidator;
impl CallValidate<AccountId32, Origin, Call> for CallValidator {
fn check_receiving_before_dispatch(
relayer_account: &AccountId32,
call: &Call,
) -> Result<(), &'static str> {
match call {
Call::Ethereum(darwinia_ethereum::Call::message_transact { transaction: tx }) =>
match tx {
Transaction::Legacy(t) => {
// Use fixed gas price now.
let gas_price =
<Test as darwinia_evm::Config>::FeeCalculator::min_gas_price();
let fee = t.gas_limit.saturating_mul(gas_price);

// Ensure the relayer's account has enough balance to withdraw.
ensure!(
evm_ensure_can_withdraw(
relayer_account,
cmp::min(
fee,
decimal_convert(
MaxUsableBalanceFromRelayer::get().into(),
None
)
),
WithdrawReasons::TRANSFER
)
.is_ok(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);
Ok(())
},
_ => Ok(()),
},
_ => Ok(()),
}
}

fn call_validate(
relayer_account: &AccountId32,
origin: &Origin,
call: &Call,
) -> Result<(), TransactionValidityError> {
match call {
// Note: Only support Ethereum::message_transact(LegacyTransaction)
Call::Ethereum(crate::Call::message_transact { transaction: tx }) => {
match origin.caller() {
OriginCaller::Ethereum(RawOrigin::EthereumTransaction(id)) => match tx {
Transaction::Legacy(t) => {
// Only non-payable call supported.
boundless-forest marked this conversation as resolved.
Show resolved Hide resolved
if t.value != U256::zero() {
return Err(TransactionValidityError::Invalid(
InvalidTransaction::Payment,
));
}
let gas_price =
<Test as darwinia_evm::Config>::FeeCalculator::min_gas_price();
let fee = t.gas_limit.saturating_mul(gas_price);

// MaxUsableBalanceFromRelayer is the cap limitation for fee in case gas_limit is too large for relayer
if fee > decimal_convert(MaxUsableBalanceFromRelayer::get().into(), None) {
return Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(2u8)));
}

// Already done `evm_ensure_can_withdraw` in check_receiving_before_dispatch
let derived_substrate_address =
<Test as darwinia_evm::Config>::IntoAccountId::derive_substrate_address(*id);

let result = <Test as darwinia_evm::Config>::RingAccountBasic::transfer(
&relayer_account,
&derived_substrate_address,
fee,
);

if result.is_err() {
return Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(3u8)));
}

Ok(())
},
_ =>
Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1u8))),
},
_ => Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0u8))),
}
},
_ => Ok(()),
}
}
}

pub struct IntoDispatchOrigin;
impl IntoDispatchOriginT<AccountId32, Call, Origin> for IntoDispatchOrigin {
fn into_dispatch_origin(id: &AccountId32, call: &Call) -> Origin {
match call {
Call::Ethereum(darwinia_ethereum::Call::message_transact { .. }) => {
let derive_eth_address = id.derive_ethereum_address();
darwinia_ethereum::RawOrigin::EthereumTransaction(derive_eth_address).into()
},
_ => frame_system::RawOrigin::Signed(id.clone()).into(),
}
}
}

#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct TestAccountPublic(AccountId32);
impl IdentifyAccount for TestAccountPublic {
type AccountId = AccountId32;

fn into_account(self) -> AccountId32 {
self.0
}
}

#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct TestSignature(AccountId32);
impl Verify for TestSignature {
type Signer = TestAccountPublic;

fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, _msg: L, signer: &AccountId32) -> bool {
self.0 == *signer
}
}

impl pallet_bridge_dispatch::Config for Test {
type AccountIdConverter = AccountIdConverter;
type BridgeMessageId = BridgeMessageId;
type Call = Call;
type CallValidator = CallValidator;
type EncodedCall = EncodedCall;
type Event = Event;
type IntoDispatchOrigin = IntoDispatchOrigin;
type SourceChainAccountId = AccountId32;
type TargetChainAccountPublic = TestAccountPublic;
type TargetChainSignature = TestSignature;
}
// --- pallet-bridge-dispatch config end ---

frame_support::construct_runtime! {
pub enum Test where
Block = Block,
Expand All @@ -251,6 +454,7 @@ frame_support::construct_runtime! {
Kton: darwinia_balances::<Instance2>::{Pallet, Call, Storage, Config<T>, Event<T>},
EVM: darwinia_evm::{Pallet, Call, Storage, Config, Event<T>},
Ethereum: darwinia_ethereum::{Pallet, Call, Storage, Config, Event<T>, Origin},
Dispatch: pallet_bridge_dispatch::{Pallet, Call, Event<T>},
}
}

Expand Down Expand Up @@ -521,7 +725,6 @@ fn address_build(seed: u8) -> AccountInfo {
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext(accounts_len: usize) -> (Vec<AccountInfo>, sp_io::TestExternalities) {
// sc_cli::init_logger("");
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();

let pairs = (0..accounts_len).map(|i| address_build(i as u8)).collect::<Vec<_>>();
Expand Down
13 changes: 0 additions & 13 deletions frame/dvm/ethereum/src/tests/account_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,3 @@ fn mutate_account_dec_balance_existential_by_90() {
assert_balance!(test_addr, new, 500, 0);
});
}
#[test]
fn mutate_account_dec_balance_existential_by_990() {
let (_, mut ext) = new_test_ext(1);
ext.execute_with(|| {
let test_addr = H160::from_str("1000000000000000000000000000000000000001").unwrap();
let origin = decimal_convert(500, Some(90));
RingAccount::mutate_account_basic_balance(&test_addr, origin);

let new = origin.saturating_sub(U256::from(990));
RingAccount::mutate_account_basic_balance(&test_addr, new);
assert_balance!(test_addr, U256::zero(), 0, 0);
});
}
Loading