From 82d54ff5986149c07a604ae8d119685d77e6533f Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Sun, 28 Nov 2021 16:59:41 +0800 Subject: [PATCH] Modify eth verify (#1644) * modify eth verify * add tests * Apply suggestions from code review Co-authored-by: Xiliang Chen * fix srtool build * checked_div instead of saturating_div Co-authored-by: Xiliang Chen --- modules/asset-registry/src/mock.rs | 2 + modules/currencies/src/mock.rs | 2 + modules/evm-bridge/src/mock.rs | 2 + modules/evm/src/lib.rs | 33 ++++++++-- modules/evm/src/mock.rs | 4 +- modules/evm/src/tests.rs | 44 ++++++------- primitives/src/unchecked_extrinsic.rs | 74 +++++++++++++++------ runtime/acala/src/lib.rs | 26 +++++++- runtime/common/src/precompile/mock.rs | 2 + runtime/integration-tests/src/evm.rs | 14 +--- runtime/karura/src/lib.rs | 26 +++++++- runtime/mandala/src/lib.rs | 33 ++++++++-- ts-tests/tests/test-sign-eth.ts | 92 +++++++++++++++++++-------- 13 files changed, 259 insertions(+), 95 deletions(-) diff --git a/modules/asset-registry/src/mock.rs b/modules/asset-registry/src/mock.rs index a13f3590f1..7592c279e4 100644 --- a/modules/asset-registry/src/mock.rs +++ b/modules/asset-registry/src/mock.rs @@ -98,6 +98,7 @@ ord_parameter_types! { pub const TreasuryAccount: AccountId = AccountId::from([2u8; 32]); pub const NetworkContractAccount: AccountId = AccountId::from([0u8; 32]); pub const StorageDepositPerByte: u128 = 10; + pub const TxFeePerGas: u128 = 10; pub const DeveloperDeposit: u64 = 1000; pub const DeploymentFee: u64 = 200; } @@ -108,6 +109,7 @@ impl module_evm::Config for Runtime { type TransferAll = (); type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = (); type ChainId = (); diff --git a/modules/currencies/src/mock.rs b/modules/currencies/src/mock.rs index 6e46dbed53..f600928ef3 100644 --- a/modules/currencies/src/mock.rs +++ b/modules/currencies/src/mock.rs @@ -149,6 +149,7 @@ ord_parameter_types! { pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); pub const StorageDepositPerByte: u128 = 10; + pub const TxFeePerGas: u128 = 10; pub const DeveloperDeposit: u64 = 1000; pub const DeploymentFee: u64 = 200; } @@ -159,6 +160,7 @@ impl module_evm::Config for Runtime { type TransferAll = (); type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = (); type ChainId = (); diff --git a/modules/evm-bridge/src/mock.rs b/modules/evm-bridge/src/mock.rs index 1c30208530..19d04b1c8f 100644 --- a/modules/evm-bridge/src/mock.rs +++ b/modules/evm-bridge/src/mock.rs @@ -105,6 +105,7 @@ ord_parameter_types! { pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); pub const StorageDepositPerByte: u128 = 10; + pub const TxFeePerGas: u128 = 10; pub const DeveloperDeposit: u64 = 1000; pub const DeploymentFee: u64 = 200; } @@ -115,6 +116,7 @@ impl module_evm::Config for Runtime { type TransferAll = (); type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = (); type ChainId = (); diff --git a/modules/evm/src/lib.rs b/modules/evm/src/lib.rs index 62e7a611cb..a191ed3cb0 100644 --- a/modules/evm/src/lib.rs +++ b/modules/evm/src/lib.rs @@ -75,7 +75,8 @@ use sha3::{Digest, Keccak256}; use sp_io::KillStorageResult::{AllRemoved, SomeRemaining}; use sp_runtime::{ traits::{ - Convert, DispatchInfoOf, One, PostDispatchInfoOf, Saturating, SignedExtension, UniqueSaturatedInto, Zero, + Convert, DispatchInfoOf, One, PostDispatchInfoOf, Saturating, SignedExtension, UniqueSaturatedFrom, + UniqueSaturatedInto, Zero, }, transaction_validity::TransactionValidityError, Either, TransactionOutcome, @@ -185,6 +186,11 @@ pub mod module { #[pallet::constant] type StorageDepositPerByte: Get>; + /// Tx fee required for per gas. + /// Provide to the client + #[pallet::constant] + type TxFeePerGas: Get>; + /// The overarching event type. type Event: From> + IsType<::Event>; @@ -860,6 +866,25 @@ pub mod module { } impl Pallet { + pub fn pad_zero(b: BalanceOf, num: u32) -> BalanceOf { + BalanceOf::::unique_saturated_from( + UniqueSaturatedInto::::unique_saturated_into(b).saturating_mul(10u128.saturating_pow(num)), + ) + } + + pub fn truncate_zero(b: BalanceOf, num: u32) -> BalanceOf { + BalanceOf::::unique_saturated_from( + UniqueSaturatedInto::::unique_saturated_into(b) + .checked_div(10u128.saturating_pow(num)) + .expect("divisor is non-zero; qed"), + ) + } + + /// Get StorageDepositPerByte of actual decimals + pub fn get_storage_deposit_per_byte() -> BalanceOf { + Self::truncate_zero(T::StorageDepositPerByte::get(), 6) + } + /// Check whether an account is empty. pub fn is_account_empty(address: &H160) -> bool { let account_id = T::AddressMapping::get_account_id(address); @@ -1244,7 +1269,7 @@ impl Pallet { } let user = T::AddressMapping::get_account_id(caller); - let amount = T::StorageDepositPerByte::get().saturating_mul(limit.into()); + let amount = Self::get_storage_deposit_per_byte().saturating_mul(limit.into()); log::debug!( target: "evm", @@ -1263,7 +1288,7 @@ impl Pallet { } let user = T::AddressMapping::get_account_id(caller); - let amount = T::StorageDepositPerByte::get().saturating_mul(unused.into()); + let amount = Self::get_storage_deposit_per_byte().saturating_mul(unused.into()); log::debug!( target: "evm", @@ -1285,7 +1310,7 @@ impl Pallet { let user = T::AddressMapping::get_account_id(caller); let contract_acc = T::AddressMapping::get_account_id(contract); - let amount = T::StorageDepositPerByte::get().saturating_mul((storage.abs() as u32).into()); + let amount = Self::get_storage_deposit_per_byte().saturating_mul((storage.abs() as u32).into()); log::debug!( target: "evm", diff --git a/modules/evm/src/mock.rs b/modules/evm/src/mock.rs index 1fc2f93fec..7a74e5b1c4 100644 --- a/modules/evm/src/mock.rs +++ b/modules/evm/src/mock.rs @@ -173,7 +173,8 @@ ord_parameter_types! { pub const TreasuryAccount: AccountId32 = AccountId32::from([2u8; 32]); pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); pub const NewContractExtraBytes: u32 = 100; - pub const StorageDepositPerByte: u64 = 10; + pub const StorageDepositPerByte: u64 = 10_000_000; + pub const TxFeePerGas: u64 = 20_000_000; pub const DeveloperDeposit: u64 = 1000; pub const DeploymentFee: u64 = 200; pub const ChainId: u64 = 1; @@ -185,6 +186,7 @@ impl Config for Runtime { type TransferAll = Currencies; type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = (); diff --git a/modules/evm/src/tests.rs b/modules/evm/src/tests.rs index d2fc43481c..93218dfc1c 100644 --- a/modules/evm/src/tests.rs +++ b/modules/evm/src/tests.rs @@ -223,7 +223,7 @@ fn call_reverts_with_message() { assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - let alice_balance = INITIAL_BALANCE - 323 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 323 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); @@ -301,7 +301,7 @@ fn should_deploy_payable_contract() { assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); assert_eq!(result.used_storage, 287); - let alice_balance = INITIAL_BALANCE - amount - 287 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - amount - 287 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); assert_eq!(balance(contract_address), amount); @@ -377,7 +377,7 @@ fn should_transfer_from_contract() { assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); assert_eq!(result.used_storage, 892); - let alice_balance = INITIAL_BALANCE - 892 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 892 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); let contract_address = result.value; @@ -477,7 +477,7 @@ fn contract_should_deploy_contracts() { assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); assert_eq!(result.used_storage, 467); - let alice_balance = INITIAL_BALANCE - 467 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 467 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); let factory_contract_address = result.value; @@ -488,7 +488,7 @@ fn contract_should_deploy_contracts() { assert_eq!(balance(factory_contract_address), 0); assert_eq!( reserved_balance(factory_contract_address), - 467 * ::StorageDepositPerByte::get() + 467 * EVM::get_storage_deposit_per_byte() ); // Factory.createContract @@ -510,18 +510,18 @@ fn contract_should_deploy_contracts() { assert_eq!( balance(alice()), - alice_balance - amount - 281 * ::StorageDepositPerByte::get() + alice_balance - amount - 281 * EVM::get_storage_deposit_per_byte() ); assert_eq!(balance(factory_contract_address), amount); assert_eq!( reserved_balance(factory_contract_address), - (467 + 128) * ::StorageDepositPerByte::get() + (467 + 128) * EVM::get_storage_deposit_per_byte() ); let contract_address = H160::from_str("7b8f8ca099f6e33cf1817cf67d0556429cfc54e4").unwrap(); assert_eq!(balance(contract_address), 0); assert_eq!( reserved_balance(contract_address), - 153 * ::StorageDepositPerByte::get() + 153 * EVM::get_storage_deposit_per_byte() ); }); } @@ -555,7 +555,7 @@ fn contract_should_deploy_contracts_without_payable() { .unwrap(); assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); - let alice_balance = INITIAL_BALANCE - 464 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 464 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); let factory_contract_address = result.value; @@ -582,12 +582,12 @@ fn contract_should_deploy_contracts_without_payable() { assert_eq!(result.used_storage, 290); assert_eq!( balance(alice()), - alice_balance - (result.used_storage as u64 * ::StorageDepositPerByte::get()) + alice_balance - (result.used_storage as u64 * EVM::get_storage_deposit_per_byte()) ); assert_eq!(balance(factory_contract_address), 0); assert_eq!( reserved_balance(factory_contract_address), - (464 + 128) * ::StorageDepositPerByte::get() + (464 + 128) * EVM::get_storage_deposit_per_byte() ); }); } @@ -621,7 +621,7 @@ fn deploy_factory() { assert_eq!(result.used_storage, 461); assert_eq!( balance(alice()), - INITIAL_BALANCE - (result.used_storage as u64 * ::StorageDepositPerByte::get()) + INITIAL_BALANCE - (result.used_storage as u64 * EVM::get_storage_deposit_per_byte()) ); }); } @@ -788,7 +788,7 @@ fn should_transfer_maintainer() { .unwrap(); assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); assert_eq!(result.used_storage, 461); - let alice_balance = INITIAL_BALANCE - 461 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 461 * EVM::get_storage_deposit_per_byte(); let contract_address = result.value; assert_eq!(balance(alice()), alice_balance); @@ -857,7 +857,7 @@ fn should_deploy() { let contract_address = result.value; assert_eq!(result.used_storage, 284); - let alice_balance = INITIAL_BALANCE - 284 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 284 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); @@ -908,7 +908,7 @@ fn should_deploy() { let code_size = Accounts::::get(contract_address).map_or(0, |account_info| -> u32 { account_info.contract_info.map_or(0, |contract_info| CodeInfos::::get(contract_info.code_hash).map_or(0, |code_info| code_info.code_size)) }); - assert_eq!(balance(alice()), INITIAL_BALANCE - DeploymentFee::get() - ((NewContractExtraBytes::get() + code_size) as u64 * StorageDepositPerByte::get())); + assert_eq!(balance(alice()), INITIAL_BALANCE - DeploymentFee::get() - ((NewContractExtraBytes::get() + code_size) as u64 * EVM::get_storage_deposit_per_byte())); assert_eq!(Balances::free_balance(TreasuryAccount::get()), INITIAL_BALANCE + DeploymentFee::get()); // call method `multiply` will work @@ -1065,7 +1065,7 @@ fn should_set_code() { .unwrap(); let contract_address = result.value; assert_eq!(result.used_storage, 284); - let alice_balance = INITIAL_BALANCE - 284 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 284 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); assert_eq!(reserved_balance(contract_address), 2840); @@ -1210,7 +1210,7 @@ fn should_selfdestruct() { let contract_address = result.value; assert_eq!(result.used_storage, 287); - let alice_balance = INITIAL_BALANCE - 287 * ::StorageDepositPerByte::get() - amount; + let alice_balance = INITIAL_BALANCE - 287 * EVM::get_storage_deposit_per_byte() - amount; assert_eq!(balance(alice()), alice_balance); @@ -1250,7 +1250,7 @@ fn should_selfdestruct() { assert_eq!(balance(contract_address), 1000); assert_eq!( reserved_balance(contract_address), - 287 * ::StorageDepositPerByte::get() + 287 * EVM::get_storage_deposit_per_byte() ); // can't deploy at the same address until everything is wiped out @@ -1327,7 +1327,7 @@ fn storage_limit_should_work() { .unwrap(); assert_eq!(result.exit_reason, ExitReason::Succeed(ExitSucceed::Returned)); assert_eq!(result.used_storage, 516); - let alice_balance = INITIAL_BALANCE - 516 * ::StorageDepositPerByte::get(); + let alice_balance = INITIAL_BALANCE - 516 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); let factory_contract_address = result.value; @@ -1338,7 +1338,7 @@ fn storage_limit_should_work() { assert_eq!(balance(factory_contract_address), 0); assert_eq!( reserved_balance(factory_contract_address), - 516 * ::StorageDepositPerByte::get() + 516 * EVM::get_storage_deposit_per_byte() ); // Factory.createContract(1) @@ -1445,7 +1445,7 @@ fn evm_execute_mode_should_work() { ).unwrap(); new_test_ext().execute_with(|| { - let mut alice_balance = INITIAL_BALANCE - 516 * ::StorageDepositPerByte::get(); + let mut alice_balance = INITIAL_BALANCE - 516 * EVM::get_storage_deposit_per_byte(); let result = ::Runner::create( alice(), @@ -1557,7 +1557,7 @@ fn evm_execute_mode_should_work() { } ); - alice_balance -= 290 * ::StorageDepositPerByte::get(); + alice_balance -= 290 * EVM::get_storage_deposit_per_byte(); assert_eq!(balance(alice()), alice_balance); diff --git a/primitives/src/unchecked_extrinsic.rs b/primitives/src/unchecked_extrinsic.rs index efd349ad0c..d427fc55c2 100644 --- a/primitives/src/unchecked_extrinsic.rs +++ b/primitives/src/unchecked_extrinsic.rs @@ -16,10 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{evm::EthereumTransactionMessage, signature::AcalaMultiSignature, Address}; +use crate::{evm::EthereumTransactionMessage, signature::AcalaMultiSignature, Address, Balance}; use codec::{Decode, Encode}; use frame_support::{ - traits::ExtrinsicCall, + log, + traits::{ExtrinsicCall, Get}, weights::{DispatchInfo, GetDispatchInfo}, }; use module_evm_utiltity::ethereum::{LegacyTransactionMessage, TransactionAction}; @@ -33,17 +34,18 @@ use sp_runtime::{ transaction_validity::{InvalidTransaction, TransactionValidityError}, AccountId32, RuntimeDebug, }; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::{convert::TryInto, marker::PhantomData, prelude::*}; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(ConvertTx))] -pub struct AcalaUncheckedExtrinsic( +pub struct AcalaUncheckedExtrinsic( pub UncheckedExtrinsic, - PhantomData, + PhantomData<(ConvertTx, StorageDepositPerByte, TxFeePerGas)>, ); #[cfg(feature = "std")] -impl parity_util_mem::MallocSizeOf for AcalaUncheckedExtrinsic +impl parity_util_mem::MallocSizeOf + for AcalaUncheckedExtrinsic where Extra: SignedExtension, { @@ -53,7 +55,9 @@ where } } -impl Extrinsic for AcalaUncheckedExtrinsic { +impl Extrinsic + for AcalaUncheckedExtrinsic +{ type Call = Call; type SignaturePayload = (Address, AcalaMultiSignature, Extra); @@ -74,12 +78,16 @@ impl Extrinsic for AcalaUncheckedExtrin } } -impl ExtrinsicMetadata for AcalaUncheckedExtrinsic { +impl ExtrinsicMetadata + for AcalaUncheckedExtrinsic +{ const VERSION: u8 = UncheckedExtrinsic::::VERSION; type SignedExtensions = Extra; } -impl ExtrinsicCall for AcalaUncheckedExtrinsic { +impl ExtrinsicCall + for AcalaUncheckedExtrinsic +{ fn call(&self) -> &Self::Call { self.0.call() } @@ -89,11 +97,14 @@ fn to_bytes>(value: T) -> [u8; 32] { Into::<[u8; 32]>::into(value.into()) } -impl Checkable for AcalaUncheckedExtrinsic +impl Checkable + for AcalaUncheckedExtrinsic where Call: Encode + Member, Extra: SignedExtension, ConvertTx: Convert<(Call, Extra), Result>, + StorageDepositPerByte: Get, + TxFeePerGas: Get, Lookup: traits::Lookup, { type Checked = CheckedExtrinsic; @@ -109,13 +120,37 @@ where return Err(InvalidTransaction::BadProof.into()); } - // we merge storage_limit and valid_until into gas_price - let gas_price = (eth_msg.storage_limit as u64) << 32 | eth_msg.valid_until as u64; + // tx_gas_price = tx_fee_per_gas + block_period << 16 + storage_entry_limit + // tx_gas_limit = gas_limit + storage_entry_deposit / tx_fee_per_gas * storage_entry_limit + let block_period = eth_msg.valid_until.checked_div(30).expect("divisor is non-zero; qed"); + // u16: max value 0xffff * 64 = 4194240 bytes = 4MB + let storage_entry_limit: u16 = eth_msg + .storage_limit + .checked_div(64) + .expect("divisor is non-zero; qed") + .try_into() + .map_err(|_| InvalidTransaction::BadProof)?; + let storage_entry_deposit = StorageDepositPerByte::get().saturating_mul(64); + let tx_gas_price = TxFeePerGas::get() + .saturating_add((block_period << 16).into()) + .saturating_add(storage_entry_limit.into()); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + let tx_gas_limit = storage_entry_deposit + .checked_div(TxFeePerGas::get()) + .expect("divisor is non-zero; qed") + .saturating_mul(storage_entry_limit.into()) + .saturating_add(eth_msg.gas_limit.into()); + + log::trace!( + target: "evm", "eth_msg.gas_limit: {:?}, eth_msg.storage_limit: {:?}, tx_gas_limit: {:?}, tx_gas_price: {:?}", + eth_msg.storage_limit, eth_msg.gas_limit, tx_gas_limit, tx_gas_price + ); let msg = LegacyTransactionMessage { nonce: eth_msg.nonce.into(), - gas_price: gas_price.into(), - gas_limit: eth_msg.gas_limit.into(), + gas_price: tx_gas_price.into(), + gas_limit: tx_gas_limit.into(), action: eth_msg.action, value: eth_msg.value.into(), input: eth_msg.input, @@ -161,7 +196,8 @@ where } } -impl GetDispatchInfo for AcalaUncheckedExtrinsic +impl GetDispatchInfo + for AcalaUncheckedExtrinsic where Call: GetDispatchInfo, Extra: SignedExtension, @@ -172,8 +208,8 @@ where } #[cfg(feature = "std")] -impl serde::Serialize - for AcalaUncheckedExtrinsic +impl serde::Serialize + for AcalaUncheckedExtrinsic { fn serialize(&self, seq: S) -> Result where @@ -184,8 +220,8 @@ impl serde::Serialize } #[cfg(feature = "std")] -impl<'a, Call: Decode, Extra: SignedExtension, ConvertTx> serde::Deserialize<'a> - for AcalaUncheckedExtrinsic +impl<'a, Call: Decode, Extra: SignedExtension, ConvertTx, StorageDepositPerByte, TxFeePerGas> serde::Deserialize<'a> + for AcalaUncheckedExtrinsic { fn deserialize(de: D) -> Result where diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index aa08d472d4..c264bba997 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1268,18 +1268,37 @@ impl pallet_proxy::Config for Runtime { parameter_types! { pub const ChainId: u64 = 787; pub const NewContractExtraBytes: u32 = 10_000; - pub StorageDepositPerByte: Balance = deposit(0, 1); pub NetworkContractSource: H160 = H160::from_low_u64_be(0); pub DeveloperDeposit: Balance = 100 * dollar(ACA); pub DeploymentFee: Balance = 10000 * dollar(ACA); } +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct StorageDepositPerByte; +impl> frame_support::traits::Get for StorageDepositPerByte { + fn get() -> I { + // NOTE: use 18 decimals + I::from(100 * dollar(ACA)) + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct TxFeePerGas; +impl> frame_support::traits::Get for TxFeePerGas { + fn get() -> I { + // NOTE: 200 GWei + // ensure suffix is 0x0000 + I::from(200u128.saturating_mul(10u128.saturating_pow(9)) & !0xffff) + } +} + impl module_evm::Config for Runtime { type AddressMapping = EvmAddressMapping; type Currency = Balances; type TransferAll = Currencies; type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = runtime_common::AllPrecompiles; type ChainId = ChainId; @@ -2252,8 +2271,9 @@ mod tests { // Otherwise, the creation of the contract account will fail because it is less than // ExistentialDeposit. assert!( - Balance::from(NewContractExtraBytes::get()) * StorageDepositPerByte::get() - >= NativeTokenExistentialDeposit::get() + Balance::from(NewContractExtraBytes::get()).saturating_mul( + >::get() / 10u128.saturating_pow(6) + ) >= NativeTokenExistentialDeposit::get() ); } diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 76ef6c1ba3..590b429789 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -379,6 +379,7 @@ ord_parameter_types! { pub const NetworkContractAccount: AccountId32 = AccountId32::from([0u8; 32]); pub const NewContractExtraBytes: u32 = 100; pub const StorageDepositPerByte: u64 = 10; + pub const TxFeePerGas: u64 = 10; pub const DeveloperDeposit: u64 = 1000; pub const DeploymentFee: u64 = 200; pub const ChainId: u64 = 1; @@ -397,6 +398,7 @@ impl module_evm::Config for Test { type TransferAll = Currencies; type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = AllPrecompiles; type ChainId = ChainId; diff --git a/runtime/integration-tests/src/evm.rs b/runtime/integration-tests/src/evm.rs index 8f2fe918af..9b1265d64e 100644 --- a/runtime/integration-tests/src/evm.rs +++ b/runtime/integration-tests/src/evm.rs @@ -312,10 +312,7 @@ fn test_evm_module() { ))); // test EvmAccounts Lookup - #[cfg(feature = "with-mandala-runtime")] assert_eq!(Balances::free_balance(alice()), 998_963_300_000_000); - #[cfg(feature = "with-karura-runtime")] - assert_eq!(Balances::free_balance(alice()), 996_889_900_000_000); assert_eq!(Balances::free_balance(bob()), 1_000 * dollar(NATIVE_CURRENCY)); let to = EvmAccounts::eth_address(&alice_key()); assert_ok!(Currencies::transfer( @@ -324,10 +321,7 @@ fn test_evm_module() { NATIVE_CURRENCY, 10 * dollar(NATIVE_CURRENCY) )); - #[cfg(feature = "with-mandala-runtime")] assert_eq!(Balances::free_balance(alice()), 1_008_963_300_000_000); - #[cfg(feature = "with-karura-runtime")] - assert_eq!(Balances::free_balance(alice()), 1_006_889_900_000_000); assert_eq!( Balances::free_balance(bob()), 1_000 * dollar(NATIVE_CURRENCY) - 10 * dollar(NATIVE_CURRENCY) @@ -494,10 +488,8 @@ fn should_not_kill_contract_on_transfer_all() { assert_eq!(Balances::free_balance(EvmAddressMapping::::get_account_id(&contract)), 2 * dollar(NATIVE_CURRENCY)); - #[cfg(all(not(feature = "with-ethereum-compatibility"), feature = "with-mandala-runtime"))] + #[cfg(not(feature = "with-ethereum-compatibility"))] assert_eq!(Balances::free_balance(alice()), 1_996_993_800_000_000); - #[cfg(all(not(feature = "with-ethereum-compatibility"), feature = "with-karura-runtime"))] - assert_eq!(Balances::free_balance(alice()), 1_994_981_400_000_000); #[cfg(feature = "with-ethereum-compatibility")] assert_eq!(Balances::free_balance(alice()), 1_998 * dollar(NATIVE_CURRENCY)); @@ -511,10 +503,8 @@ fn should_not_kill_contract_on_transfer_all() { assert_eq!(Balances::free_balance(EvmAddressMapping::::get_account_id(&contract)), 0); - #[cfg(all(not(feature = "with-ethereum-compatibility"), feature = "with-mandala-runtime"))] + #[cfg(not(feature = "with-ethereum-compatibility"))] assert_eq!(Balances::free_balance(alice()), 1_998_993_800_000_000); - #[cfg(all(not(feature = "with-ethereum-compatibility"), feature = "with-karura-runtime"))] - assert_eq!(Balances::free_balance(alice()), 1_996_981_400_000_000); #[cfg(feature = "with-ethereum-compatibility")] assert_eq!(Balances::free_balance(alice()), 2000 * dollar(NATIVE_CURRENCY)); diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index f5705ba922..935eec6fec 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1284,18 +1284,37 @@ impl pallet_proxy::Config for Runtime { parameter_types! { pub const ChainId: u64 = 686; pub const NewContractExtraBytes: u32 = 10_000; - pub StorageDepositPerByte: Balance = deposit(0, 1); pub NetworkContractSource: H160 = H160::from_low_u64_be(0); pub DeveloperDeposit: Balance = 100 * dollar(KAR); pub DeploymentFee: Balance = 10000 * dollar(KAR); } +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct StorageDepositPerByte; +impl> frame_support::traits::Get for StorageDepositPerByte { + fn get() -> I { + // NOTE: use 18 decimals + I::from(100 * dollar(KAR)) + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct TxFeePerGas; +impl> frame_support::traits::Get for TxFeePerGas { + fn get() -> I { + // NOTE: 200 GWei + // ensure suffix is 0x0000 + I::from(200u128.saturating_mul(10u128.saturating_pow(9)) & !0xffff) + } +} + impl module_evm::Config for Runtime { type AddressMapping = EvmAddressMapping; type Currency = Balances; type TransferAll = Currencies; type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = runtime_common::AllPrecompiles; type ChainId = ChainId; @@ -2327,8 +2346,9 @@ mod tests { // Otherwise, the creation of the contract account will fail because it is less than // ExistentialDeposit. assert!( - Balance::from(NewContractExtraBytes::get()) * StorageDepositPerByte::get() - >= NativeTokenExistentialDeposit::get() + Balance::from(NewContractExtraBytes::get()).saturating_mul( + >::get() / 10u128.saturating_pow(6) + ) >= NativeTokenExistentialDeposit::get() ); } diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a4c17ca59b..4f88eed886 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1487,7 +1487,6 @@ parameter_types! { parameter_types! { pub NativeTokenExistentialDeposit: Balance = 10 * cent(ACA); pub const NewContractExtraBytes: u32 = 0; - pub const StorageDepositPerByte: Balance = 0; pub const DeveloperDeposit: Balance = 0; pub const DeploymentFee: Balance = 0; } @@ -1496,11 +1495,32 @@ parameter_types! { parameter_types! { pub NativeTokenExistentialDeposit: Balance = 10 * cent(ACA); pub const NewContractExtraBytes: u32 = 10_000; - pub StorageDepositPerByte: Balance = deposit(0, 1); pub DeveloperDeposit: Balance = dollar(ACA); pub DeploymentFee: Balance = dollar(ACA); } +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct StorageDepositPerByte; +impl> frame_support::traits::Get for StorageDepositPerByte { + fn get() -> I { + #[cfg(not(feature = "with-ethereum-compatibility"))] + // NOTE: use 18 decimals + return I::from(100 * dollar(ACA)); + #[cfg(feature = "with-ethereum-compatibility")] + return I::from(0); + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct TxFeePerGas; +impl> frame_support::traits::Get for TxFeePerGas { + fn get() -> I { + // NOTE: 200 GWei + // ensure suffix is 0x0000 + I::from(200u128.saturating_mul(10u128.saturating_pow(9)) & !0xffff) + } +} + #[cfg(feature = "with-ethereum-compatibility")] static ISTANBUL_CONFIG: module_evm_utiltity::evm::Config = module_evm_utiltity::evm::Config::istanbul(); @@ -1510,6 +1530,7 @@ impl module_evm::Config for Runtime { type TransferAll = Currencies; type NewContractExtraBytes = NewContractExtraBytes; type StorageDepositPerByte = StorageDepositPerByte; + type TxFeePerGas = TxFeePerGas; type Event = Event; type Precompiles = runtime_common::AllPrecompiles; type ChainId = ChainId; @@ -1984,7 +2005,8 @@ pub type SignedExtra = ( module_evm::SetEvmOrigin, ); /// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = AcalaUncheckedExtrinsic; +pub type UncheckedExtrinsic = + AcalaUncheckedExtrinsic; /// The payload being signed in transactions. pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. @@ -2533,8 +2555,9 @@ mod tests { // Otherwise, the creation of the contract account will fail because it is less than // ExistentialDeposit. assert!( - Balance::from(NewContractExtraBytes::get()) * StorageDepositPerByte::get() - >= NativeTokenExistentialDeposit::get() + Balance::from(NewContractExtraBytes::get()).saturating_mul( + >::get() / 10u128.saturating_pow(6) + ) >= NativeTokenExistentialDeposit::get() ); } diff --git a/ts-tests/tests/test-sign-eth.ts b/ts-tests/tests/test-sign-eth.ts index 19ddda5f27..54f27dd431 100644 --- a/ts-tests/tests/test-sign-eth.ts +++ b/ts-tests/tests/test-sign-eth.ts @@ -39,23 +39,39 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { factory = new ethers.ContractFactory(Erc20DemoContract.abi, Erc20DemoContract.bytecode); }); + const bigNumDiv = (x: BigNumber, y: BigNumber) => { + const res = x.div(y); + return res.mul(y) === x + ? res + : res.add(1) + } + it("create should sign and verify", async function () { this.timeout(150000); const chanid = +context.provider.api.consts.evm.chainId.toString() const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 - const storageLimit = 20000 - - const gasPrice = '0x' + (BigInt(storageLimit) << BigInt(32) | BigInt(validUntil)).toString(16); + const storageLimit = 20000; + const gasLimit = 2100000; + + const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); + const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); + const storage_byte_deposit = BigNumber.from(context.provider.api.consts.evm.storageDepositPerByte.toString()); + const storage_entry_deposit = storage_byte_deposit.mul(64); + const tx_fee_per_gas = BigNumber.from(context.provider.api.consts.evm.txFeePerGas.toString()); + const tx_gas_price = tx_fee_per_gas.add(block_period.toNumber() << 16).add(storage_entry_limit); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + const tx_gas_limit = storage_entry_deposit.div(tx_fee_per_gas).mul(storage_entry_limit).add(gasLimit); const deploy = factory.getDeployTransaction(100000); const value = { // to: "0x0000000000000000000000000000000000000000", nonce, - gasLimit: 2100000, - gasPrice, + gasLimit: tx_gas_limit.toNumber(), + gasPrice: tx_gas_price.toHexString(), data: deploy.data, value: 0, chainId: chanid, @@ -66,8 +82,8 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { expect(rawtx).to.deep.include({ nonce: 0, - gasPrice: BigNumber.from('0x4e2000000069'), - gasLimit: BigNumber.from(2100000), + gasPrice: BigNumber.from(200000209209), + gasLimit: BigNumber.from(12116000), // to: '0x0000000000000000000000000000000000000000', value: BigNumber.from(0), data: deploy.data, @@ -80,13 +96,20 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { type: null }); + // tx data to user input + const input_storage_entry_limit = tx_gas_price.and(0xffff); + const input_storage_limit = input_storage_entry_limit.mul(64); + const input_block_period = (tx_gas_price.sub(input_storage_entry_limit).sub(tx_fee_per_gas).toNumber()) >> 16; + const input_valid_until = input_block_period * 30; + const input_gas_limit = tx_gas_limit.sub(storage_entry_deposit.div(tx_fee_per_gas).mul(input_storage_entry_limit)); + const tx = context.provider.api.tx.evm.ethCall( { Create: null }, value.data, value.value, - value.gasLimit, - storageLimit, - validUntil + input_gas_limit.toNumber(), + input_storage_limit.toNumber(), + input_valid_until ); const sig = ethers.utils.joinSignature({ r: rawtx.r!, s: rawtx.s, v: rawtx.v }) @@ -126,8 +149,8 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { "input": "${deploy.data}", "value": 0, "gas_limit": 2100000, - "storage_limit": 20000, - "valid_until": 105 + "storage_limit": 20032, + "valid_until": 120 } } }`.toString().replace(/\s/g, '') @@ -157,20 +180,30 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { it("call should sign and verify", async function () { this.timeout(150000); - const chanid = +context.provider.api.consts.evm.chainId.toString() - const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber() - const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100 - const storageLimit = 1000 + const chanid = +context.provider.api.consts.evm.chainId.toString(); + const nonce = (await context.provider.api.query.system.account(subAddr)).nonce.toNumber(); + const validUntil = (await context.provider.api.rpc.chain.getHeader()).number.toNumber() + 100; + const storageLimit = 1000; + const gasLimit = 210000; + + const block_period = bigNumDiv(BigNumber.from(validUntil), BigNumber.from(30)); + const storage_entry_limit = bigNumDiv(BigNumber.from(storageLimit), BigNumber.from(64)); + const storage_byte_deposit = BigNumber.from(context.provider.api.consts.evm.storageDepositPerByte.toString()); + const storage_entry_deposit = storage_byte_deposit.mul(64); + const tx_fee_per_gas = BigNumber.from(context.provider.api.consts.evm.txFeePerGas.toString()); + const tx_gas_price = tx_fee_per_gas.add(block_period.toNumber() << 16).add(storage_entry_limit); + // There is a loss of precision here, so the order of calculation must be guaranteed + // must ensure storage_deposit / tx_fee_per_gas * storage_limit + const tx_gas_limit = storage_entry_deposit.div(tx_fee_per_gas).mul(storage_entry_limit).add(gasLimit); - const gasPrice = '0x' + (BigInt(storageLimit) << BigInt(32) | BigInt(validUntil)).toString(16); const receiver = '0x1111222233334444555566667777888899990000'; const input = await factory.attach(contract).populateTransaction.transfer(receiver, 100); const value = { to: contract, nonce, - gasLimit: 210000, - gasPrice, + gasLimit: tx_gas_limit.toNumber(), + gasPrice: tx_gas_price.toHexString(), data: input.data, value: 0, chainId: chanid, @@ -181,8 +214,8 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { expect(rawtx).to.deep.include({ nonce: 1, - gasPrice: BigNumber.from('0x03e80000006a'), - gasLimit: BigNumber.from(210000), + gasPrice: BigNumber.from(200000208912), + gasLimit: BigNumber.from(722000), to: ethers.utils.getAddress(contract), value: BigNumber.from(0), data: input.data, @@ -195,13 +228,20 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { type: null }); + // tx data to user input + const input_storage_entry_limit = tx_gas_price.and(0xffff); + const input_storage_limit = input_storage_entry_limit.mul(64); + const input_block_period = (tx_gas_price.sub(input_storage_entry_limit).sub(tx_fee_per_gas).toNumber()) >> 16; + const input_valid_until = input_block_period * 30; + const input_gas_limit = tx_gas_limit.sub(storage_entry_deposit.div(tx_fee_per_gas).mul(input_storage_entry_limit)); + const tx = context.provider.api.tx.evm.ethCall( { Call: value.to }, value.data, value.value, - value.gasLimit, - storageLimit, - validUntil + input_gas_limit.toNumber(), + input_storage_limit.toNumber(), + input_valid_until ); const sig = ethers.utils.joinSignature({ r: rawtx.r!, s: rawtx.s, v: rawtx.v }) @@ -241,8 +281,8 @@ describeWithAcala("Acala RPC (Sign eth)", (context) => { "input": "${input.data}", "value": 0, "gas_limit": 210000, - "storage_limit": 1000, - "valid_until": 106 + "storage_limit": 1024, + "valid_until": 120 } } }`.toString().replace(/\s/g, '')