diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index e5913e01039ac..4cc49626bf1ff 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -36,14 +36,14 @@ jobs: - name: script run: | - forklift cargo build --locked --profile production -p pallet-revive-eth-rpc --bin eth-rpc - forklift cargo build -p staging-node-cli --bin substrate-node + forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc + forklift cargo build --locked --release -p revive-dev-node --bin revive-dev-node - name: Checkout evm-tests uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: paritytech/evm-test-suite - ref: 72d1dace20c8fea4c2404383cc422299b26f1961 + ref: 79144d85626f97e481b84a16d2b8f0813d03548b path: evm-test-suite - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -64,8 +64,8 @@ jobs: resolc --version echo "Check that binaries are in place" - export NODE_BIN_PATH=$(readlink -f ../target/debug/substrate-node) - export ETH_RPC_PATH=$(readlink -f ../target/production/eth-rpc) + export NODE_BIN_PATH=$(readlink -f ../target/release/revive-dev-node) + export ETH_RPC_PATH=$(readlink -f ../target/release/eth-rpc) export RESOLC_PATH=/usr/local/bin/resolc echo $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH @@ -77,11 +77,12 @@ jobs: wget https://github.com/ethereum/solidity/releases/download/v0.8.30/solc-static-linux -q chmod +x solc-static-linux mv solc-static-linux /usr/local/bin/solc - echo "Run the tests" - echo "bash init.sh --kitchensink -- --matter-labs -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH" - bash init.sh --kitchensink -- --matter-labs -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH + # TODO restore once tests can be run against the revive-dev-node + # echo "Run the tests" + # echo "bash init.sh --kitchensink -- --matter-labs -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH" + # bash init.sh --kitchensink -- --matter-labs -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH echo "Run eth-rpc tests" - bash init.sh --kitchensink http://localhost:9944 --eth-rpc -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH + bash init.sh --kitchensink http://localhost:9944 --eth-rpc -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH - name: Collect tests results if: always() diff --git a/Cargo.lock b/Cargo.lock index 671f499eaaa5b..02dadfdda27f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10870,6 +10870,7 @@ dependencies = [ "node-primitives", "pallet-asset-conversion", "pallet-asset-conversion-tx-payment", + "pallet-revive", "pallet-skip-feeless-payment", "parity-scale-codec", "sc-block-builder", diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs index 3493082dbbb56..696b013bf5c4a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound_edge_case.rs @@ -355,7 +355,7 @@ fn transfer_from_penpal_to_ethereum_trapped_on_ah_and_then_claim_can_work() { remote_xcm: Xcm(vec![ ClaimAsset { assets: vec![ - Asset { id: AssetId(ethereum()), fun: Fungible(600422871584) }, + Asset { id: AssetId(ethereum()), fun: Fungible(600914043236) }, Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) }, ] .into(), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 4ee1920724c34..b42fc1c244806 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -83,13 +83,13 @@ use sp_runtime::{ generic, impl_opaque_keys, traits::{AccountIdConversion, BlakeTwo256, Block as BlockT, ConvertInto, Saturating, Verify}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, Perbill, Permill, RuntimeDebug, + ApplyExtrinsicResult, FixedU128, Perbill, Permill, RuntimeDebug, }; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; use testnet_parachains_constants::westend::{ - consensus::*, currency::*, fee::WeightToFee, snowbridge::EthereumNetwork, time::*, + consensus::*, currency::*, snowbridge::EthereumNetwork, time::*, }; use westend_runtime_constants::time::DAYS as RC_DAYS; use xcm_config::{ @@ -248,6 +248,18 @@ parameter_types! { pub const TransactionByteFee: Balance = MILLICENTS; } +/// `pallet_revive` requires this specific `WeightToFee` implementation. +/// +/// This is needed because we make certain assumptions about how weight +/// is mapped to fees. Enforced at compile time. +pub type WeightToFee = pallet_revive::evm::fees::BlockRatioFee< + // p + CENTS, + // q + { 100 * ExtrinsicBaseWeight::get().ref_time() as u128 }, + Runtime, +>; + impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = @@ -1161,16 +1173,18 @@ parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); + pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(1,2); } impl pallet_revive::Config for Runtime { type Time = Timestamp; + type Balance = Balance; type Currency = Balances; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; - type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_revive::weights::SubstrateWeight; type Precompiles = ( ERC20, TrustBackedAssetsInstance>, @@ -1191,8 +1205,9 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = (); type FindAuthor = ::FindAuthor; + type FeeInfo = pallet_revive::evm::fees::Info; + type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; } parameter_types! { @@ -1402,6 +1417,7 @@ pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< frame_system::CheckWeight, pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, frame_metadata_hash_extension::CheckMetadataHash, + pallet_revive::evm::tx_extension::SetOrigin, ), >; @@ -1425,6 +1441,7 @@ impl EthExtra for EthExtraImpl { frame_system::CheckWeight::::new(), pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), ) .into() } @@ -1698,8 +1715,9 @@ mod benches { ); } -pallet_revive::impl_runtime_apis_plus_revive!( +pallet_revive::impl_runtime_apis_plus_revive_traits!( Runtime, + Revive, Executive, EthExtraImpl, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 1674e02f1c1cb..908168957d065 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -479,6 +479,7 @@ where frame_system::CheckWeight::::new(), pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None), frame_metadata_hash_extension::CheckMetadataHash::::new(true), + pallet_revive::evm::tx_extension::SetOrigin::::default(), )); let raw_payload = SignedPayload::new(call, tx_ext) .map_err(|e| { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 52bb4a74cdb6d..54cac811d1495 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -31,7 +31,7 @@ use asset_hub_westend_runtime::{ AllPalletsWithoutSystem, Assets, Balances, Block, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, PolkadotXcm, Revive, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - ToRococoXcmRouterInstance, TrustBackedAssetsInstance, Uniques, XcmpQueue, + ToRococoXcmRouterInstance, TrustBackedAssetsInstance, Uniques, WeightToFee, XcmpQueue, }; pub use asset_hub_westend_runtime::{AssetConversion, AssetDeposit, CollatorSelection, System}; use asset_test_utils::{ @@ -57,7 +57,7 @@ use frame_support::{ use hex_literal::hex; use pallet_revive::{ test_utils::builder::{BareInstantiateBuilder, Contract}, - Code, DepositLimit, + Code, }; use pallet_revive_fixtures::compile_module; use pallet_uniques::{asset_ops::Item, asset_strategies::Attribute}; @@ -66,7 +66,7 @@ use sp_consensus_aura::SlotDuration; use sp_core::crypto::Ss58Codec; use sp_runtime::{traits::MaybeEquivalence, Either, MultiAddress}; use std::convert::Into; -use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::UNITS}; use xcm::{ latest::{ prelude::{Assets as XcmAssets, *}, @@ -1689,7 +1689,7 @@ fn withdraw_and_deposit_erc20s() { let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); let Contract { addr: erc20_address, .. } = bare_instantiate(&sender, code) .gas_limit(Weight::from_parts(2_000_000_000, 200_000)) - .storage_deposit_limit(DepositLimit::Balance(Balance::MAX)) + .storage_deposit_limit(Balance::MAX) .data(constructor_data) .build_and_unwrap_contract(); @@ -1802,7 +1802,7 @@ fn smart_contract_not_erc20_will_error() { let Contract { addr: non_erc20_address, .. } = bare_instantiate(&sender, code) .gas_limit(Weight::from_parts(2_000_000_000, 200_000)) - .storage_deposit_limit(DepositLimit::Balance(Balance::MAX)) + .storage_deposit_limit(Balance::MAX) .build_and_unwrap_contract(); let wnd_amount_for_fees = 1_000_000_000_000u128; @@ -1860,7 +1860,7 @@ fn smart_contract_does_not_return_bool_fails() { let Contract { addr: non_erc20_address, .. } = bare_instantiate(&sender, code) .gas_limit(Weight::from_parts(2_000_000_000, 200_000)) - .storage_deposit_limit(DepositLimit::Balance(Balance::MAX)) + .storage_deposit_limit(Balance::MAX) .data(constructor_data) .build_and_unwrap_contract(); @@ -1916,7 +1916,7 @@ fn expensive_erc20_runs_out_of_gas() { let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); let Contract { addr: non_erc20_address, .. } = bare_instantiate(&sender, code) .gas_limit(Weight::from_parts(2_000_000_000, 200_000)) - .storage_deposit_limit(DepositLimit::Balance(Balance::MAX)) + .storage_deposit_limit(Balance::MAX) .data(constructor_data) .build_and_unwrap_contract(); diff --git a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs index 29e40d1ab1cf8..75244bd122af5 100644 --- a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs +++ b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs @@ -19,12 +19,13 @@ use core::marker::PhantomData; use ethereum_standards::IERC20; use frame_support::traits::{fungible::Inspect, OriginTrait}; +use frame_system::pallet_prelude::OriginFor; use pallet_revive::{ precompiles::alloy::{ primitives::{Address, U256 as EU256}, sol_types::SolCall, }, - AddressMapper, ContractResult, DepositLimit, MomentOf, + AddressMapper, ContractResult, ExecConfig, MomentOf, }; use sp_core::{Get, H160, H256, U256}; use sp_runtime::Weight; @@ -122,12 +123,13 @@ where IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode(); let ContractResult { result, gas_consumed, storage_deposit, .. } = pallet_revive::Pallet::::bare_call( - T::RuntimeOrigin::signed(who.clone()), + OriginFor::::signed(who.clone()), asset_id, U256::zero(), gas_limit, - DepositLimit::Balance(StorageDepositLimit::get()), + StorageDepositLimit::get(), data, + ExecConfig::new_substrate_tx(), ); // We need to return this surplus for the executor to allow refunding it. let surplus = gas_limit.saturating_sub(gas_consumed); @@ -180,12 +182,13 @@ where let gas_limit = GasLimit::get(); let ContractResult { result, gas_consumed, storage_deposit, .. } = pallet_revive::Pallet::::bare_call( - T::RuntimeOrigin::signed(TransfersCheckingAccount::get()), + OriginFor::::signed(TransfersCheckingAccount::get()), asset_id, U256::zero(), gas_limit, - DepositLimit::Balance(StorageDepositLimit::get()), + StorageDepositLimit::get(), data, + ExecConfig::new_substrate_tx(), ); // We need to return this surplus for the executor to allow refunding it. let surplus = gas_limit.saturating_sub(gas_consumed); diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 17795f5f95c3a..7dff9d74ea8db 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -91,7 +91,7 @@ use sp_runtime::{ generic, impl_opaque_keys, traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, FixedU128, }; pub use sp_runtime::{traits::ConvertInto, MultiAddress, Perbill, Permill}; #[cfg(feature = "std")] @@ -144,6 +144,7 @@ pub type TxExtension = ( frame_system::CheckWeight, pallet_asset_tx_payment::ChargeAssetTxPayment, frame_metadata_hash_extension::CheckMetadataHash, + pallet_revive::evm::tx_extension::SetOrigin, frame_system::WeightReclaim, ); @@ -167,6 +168,7 @@ impl EthExtra for EthExtraImpl { frame_system::CheckWeight::::new(), pallet_asset_tx_payment::ChargeAssetTxPayment::::from(tip, None), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), frame_system::WeightReclaim::::new(), ) .into() @@ -441,7 +443,7 @@ parameter_types! { impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = WeightToFee; + type WeightToFee = pallet_revive::evm::fees::BlockRatioFee<1, 1, Self>; type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; type OperationalFeeMultiplier = ConstU8<5>; @@ -806,16 +808,18 @@ parameter_types! { pub const DepositPerItem: Balance = 0; pub const DepositPerByte: Balance = 0; pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); + pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(1,2); } impl pallet_revive::Config for Runtime { type Time = Timestamp; + type Balance = Balance; type Currency = Balances; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; - type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_revive::weights::SubstrateWeight; type Precompiles = (); type AddressMapper = pallet_revive::AccountId32Mapper; @@ -829,8 +833,9 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type ChainId = ConstU64<420_420_999>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = (); type FindAuthor = ::FindAuthor; + type FeeInfo = pallet_revive::evm::fees::Info; + type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; } impl pallet_sudo::Config for Runtime { @@ -906,7 +911,12 @@ mod benches { ); } -impl_runtime_apis! { +pallet_revive::impl_runtime_apis_plus_revive_traits!( + Runtime, + Revive, + Executive, + EthExtraImpl, + impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) @@ -1211,7 +1221,7 @@ impl_runtime_apis! { ConsensusHook::can_build_upon(included_hash, slot) } } -} +); cumulus_pallet_parachain_system::register_validate_block! { Runtime = Runtime, diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 6d159013cf8ca..be6d3ddb8dd6f 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -337,6 +337,7 @@ impl pallet_assets::Config for Test { #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] impl pallet_revive::Config for Test { type AddressMapper = pallet_revive::AccountId32Mapper; + type Balance = Balance; type Currency = Balances; type Precompiles = (XcmPrecompile,); type Time = Timestamp; diff --git a/polkadot/xcm/pallet-xcm/src/precompiles.rs b/polkadot/xcm/pallet-xcm/src/precompiles.rs index b673a1183055e..af126623f1456 100644 --- a/polkadot/xcm/pallet-xcm/src/precompiles.rs +++ b/polkadot/xcm/pallet-xcm/src/precompiles.rs @@ -22,7 +22,7 @@ use pallet_revive::{ alloy::{self, sol_types::SolValue}, AddressMatcher, Error, Ext, Precompile, }, - DispatchInfo, Origin, + DispatchInfo, ExecOrigin as Origin, }; use tracing::error; use xcm::{v5, IdentifyVersion, MAX_XCM_DECODE_DEPTH}; @@ -184,7 +184,7 @@ mod test { }, H160, }, - DepositLimit, U256, + ExecConfig, U256, }; use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::traits::AccountIdConversion; @@ -231,8 +231,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); assert!(result.result.is_ok()); let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap())) @@ -279,8 +280,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); assert!(result.result.is_ok()); @@ -327,8 +329,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { Ok(value) => value, @@ -376,8 +379,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { Ok(value) => value, @@ -402,8 +406,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { Ok(value) => value, @@ -451,8 +456,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { Ok(value) => value, @@ -478,8 +484,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { Ok(value) => value, @@ -520,8 +527,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_weight_call, + ExecConfig::new_substrate_tx(), ); let weight_result = match xcm_weight_results.result { @@ -542,8 +550,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); assert!(result.result.is_ok()); @@ -580,8 +589,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_weight_call, + ExecConfig::new_substrate_tx(), ); let weight_result = match xcm_weight_results.result { @@ -602,8 +612,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { @@ -648,8 +659,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_weight_call, + ExecConfig::new_substrate_tx(), ); let weight_result = match xcm_weight_results.result { @@ -670,8 +682,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { Ok(value) => value, @@ -715,8 +728,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_weight_call, + ExecConfig::new_substrate_tx(), ); let weight_result = match xcm_weight_results.result { @@ -744,8 +758,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { @@ -770,8 +785,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_call, + ExecConfig::new_substrate_tx(), ); let return_value = match result.result { @@ -818,8 +834,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_weight_call, + ExecConfig::new_substrate_tx(), ); let result = match xcm_weight_results.result { @@ -842,8 +859,9 @@ mod test { xcm_precompile_addr, U256::zero(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u128::MAX, encoded_weight_call, + ExecConfig::new_substrate_tx(), ); let result = match xcm_weight_results.result { diff --git a/prdoc/pr_9803.prdoc b/prdoc/pr_9803.prdoc new file mode 100644 index 0000000000000..d921d45c785a6 --- /dev/null +++ b/prdoc/pr_9803.prdoc @@ -0,0 +1,42 @@ +title: Rework gas mapping +doc: + - audience: Runtime Dev + description: |- + Did some breaking changes to the `Config` trait and `impl_runtime_apis_plus_revive` macro. + `Config::Balance`: Set to the runtimes balance typ + `Config::FeeInfo`: Set to pallet_revive::evm::fees::Info` + Renamed `impl_runtime_apis_plus_revive` to `impl_runtime_apis_plus_revive_traits` and added + another parameter (set to the name of the revive Pallet "Revive"). + + + - audience: Runtime User + description: | + Fixes those `InvalidTransaction` errors. Gas mapping is now works even when the wallet does tamper + with the weight estimate. +crates: +- name: pallet-origin-restriction + bump: major +- name: frame-support + bump: major +- name: pallet-transaction-payment + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: assets-common + bump: major +- name: penpal-runtime + bump: major +- name: pallet-xcm + bump: major +- name: pallet-assets + bump: major +- name: pallet-revive-eth-rpc + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major +- name: pallet-assets-precompiles + bump: major +- name: pallet-revive-fixtures + bump: major diff --git a/substrate/bin/node/bench/src/import.rs b/substrate/bin/node/bench/src/import.rs index ec112cfee80df..34b3f975ad61f 100644 --- a/substrate/bin/node/bench/src/import.rs +++ b/substrate/bin/node/bench/src/import.rs @@ -35,7 +35,6 @@ use std::borrow::Cow; use node_primitives::Block; use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes}; use sc_client_api::backend::Backend; -use sp_state_machine::InspectState; use crate::{ common::SizeType, @@ -52,7 +51,6 @@ pub struct ImportBenchmarkDescription { pub struct ImportBenchmark { database: BenchDb, block: Block, - block_type: BlockType, } impl core::BenchmarkDescription for ImportBenchmarkDescription { @@ -83,7 +81,7 @@ impl core::BenchmarkDescription for ImportBenchmarkDescription { fn setup(self: Box) -> Box { let mut bench_db = BenchDb::with_key_types(self.database_type, 50_000, self.key_types); let block = bench_db.generate_block(self.block_type.to_content(self.size.transactions())); - Box::new(ImportBenchmark { database: bench_db, block_type: self.block_type, block }) + Box::new(ImportBenchmark { database: bench_db, block }) } fn name(&self) -> Cow<'static, str> { @@ -113,49 +111,6 @@ impl core::Benchmark for ImportBenchmark { context.import_block(self.block.clone()); let elapsed = start.elapsed(); - // Sanity checks. - context - .client - .state_at(self.block.header.hash()) - .expect("state_at failed for block#1") - .inspect_state(|| { - match self.block_type { - BlockType::RandomTransfersKeepAlive => { - // should be 9 per signed extrinsic + 1 per unsigned - // we have 2 unsigned (timestamp and glutton bloat) while the rest are - // signed in the block. - // those 9 events per signed are: - // - transaction paid for the transaction payment - // - withdraw (Balances::Withdraw) for charging the transaction fee - // - new account (System::NewAccount) as we always transfer fund to - // non-existent account - // - endowed (Balances::Endowed) for this new account - // - issued (Balances::Issued) as total issuance is increased - // - successful transfer (Event::Transfer) for this transfer operation - // - 2x deposit (Balances::Deposit and Treasury::Deposit) for depositing - // the transaction fee into the treasury - // - extrinsic success - assert_eq!( - kitchensink_runtime::System::events().len(), - (self.block.extrinsics.len() - 2) * 9 + 2, - ); - }, - BlockType::Noop => { - assert_eq!( - kitchensink_runtime::System::events().len(), - // should be 2 per signed extrinsic + 1 per unsigned - // we have 2 unsigned and the rest are signed in the block - // those 2 events per signed are: - // - deposit event for charging transaction fee - // - extrinsic success - // +3 Bags List events from on_idle hook - (self.block.extrinsics.len() - 2) * 2 + 2 + 3, - ); - }, - _ => {}, - } - }); - if mode == Mode::Profile { std::thread::park_timeout(std::time::Duration::from_secs(1)); } diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 1ef506ddd9c77..8db5cf24ea22a 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -140,6 +140,7 @@ pub fn create_extrinsic( >::from(tip, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + pallet_revive::evm::tx_extension::SetOrigin::::default(), frame_system::WeightReclaim::::new(), ); @@ -158,6 +159,7 @@ pub fn create_extrinsic( (), None, (), + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); @@ -1077,6 +1079,7 @@ mod tests { let tx_payment = pallet_skip_feeless_payment::SkipCheckIfFeeless::from( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None), ); + let set_eth_origin = pallet_revive::evm::tx_extension::SetOrigin::default(); let weight_reclaim = frame_system::WeightReclaim::new(); let metadata_hash = frame_metadata_hash_extension::CheckMetadataHash::new(false); let tx_ext: TxExtension = ( @@ -1090,6 +1093,7 @@ mod tests { check_weight, tx_payment, metadata_hash, + set_eth_origin, weight_reclaim, ); let raw_payload = SignedPayload::from_raw( @@ -1107,6 +1111,7 @@ mod tests { (), None, (), + (), ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); diff --git a/substrate/bin/node/cli/tests/basic.rs b/substrate/bin/node/cli/tests/basic.rs index 4e0b9b9afc886..fc93294a6a3a6 100644 --- a/substrate/bin/node/cli/tests/basic.rs +++ b/substrate/bin/node/cli/tests/basic.rs @@ -382,21 +382,7 @@ fn full_native_block_import_works() { phase: Phase::ApplyExtrinsic(1), event: RuntimeEvent::Balances(pallet_balances::Event::Deposit { who: pallet_treasury::Pallet::::account_id(), - amount: fees_after_refund * 8 / 10, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: RuntimeEvent::Treasury(pallet_treasury::Event::Deposit { - value: fees_after_refund * 8 / 10, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: RuntimeEvent::Balances(pallet_balances::Event::Rescinded { - amount: fees_after_refund * 2 / 10, + amount: fees_after_refund, }), topics: vec![], }, @@ -493,21 +479,7 @@ fn full_native_block_import_works() { phase: Phase::ApplyExtrinsic(1), event: RuntimeEvent::Balances(pallet_balances::Event::Deposit { who: pallet_treasury::Pallet::::account_id(), - amount: fees_after_refund * 8 / 10, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: RuntimeEvent::Treasury(pallet_treasury::Event::Deposit { - value: fees_after_refund * 8 / 10, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: RuntimeEvent::Balances(pallet_balances::Event::Rescinded { - amount: fees_after_refund - fees_after_refund * 8 / 10, + amount: fees_after_refund, }), topics: vec![], }, @@ -554,21 +526,7 @@ fn full_native_block_import_works() { phase: Phase::ApplyExtrinsic(2), event: RuntimeEvent::Balances(pallet_balances::Event::Deposit { who: pallet_treasury::Pallet::::account_id(), - amount: fees_after_refund * 8 / 10, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(2), - event: RuntimeEvent::Treasury(pallet_treasury::Event::Deposit { - value: fees_after_refund * 8 / 10, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(2), - event: RuntimeEvent::Balances(pallet_balances::Event::Rescinded { - amount: fees_after_refund - fees_after_refund * 8 / 10, + amount: fees_after_refund, }), topics: vec![], }, diff --git a/substrate/bin/node/cli/tests/common.rs b/substrate/bin/node/cli/tests/common.rs index 389c872e4fce1..3e499c6bfbb4b 100644 --- a/substrate/bin/node/cli/tests/common.rs +++ b/substrate/bin/node/cli/tests/common.rs @@ -122,6 +122,7 @@ pub fn executor_call( } pub fn new_test_ext(code: &[u8]) -> TestExternalities { + sp_tracing::try_init_simple(); let ext = TestExternalities::new_with_code( code, node_testing::genesis::config().build_storage().unwrap(), diff --git a/substrate/bin/node/cli/tests/submit_transaction.rs b/substrate/bin/node/cli/tests/submit_transaction.rs index 86774e29ad63a..a71adf3d24754 100644 --- a/substrate/bin/node/cli/tests/submit_transaction.rs +++ b/substrate/bin/node/cli/tests/submit_transaction.rs @@ -18,7 +18,7 @@ use codec::Decode; use frame_system::offchain::{SendSignedTransaction, Signer, SubmitTransaction}; -use kitchensink_runtime::{Executive, Indices, Runtime, UncheckedExtrinsic}; +use kitchensink_runtime::{Executive, ExistentialDeposit, Indices, Runtime, UncheckedExtrinsic}; use polkadot_sdk::*; use sp_application_crypto::AppCrypto; use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt}; @@ -240,7 +240,10 @@ fn submitted_transaction_should_be_valid() { // add balance to the account let author = extrinsic.0.preamble.clone().to_signed().clone().unwrap().0; let address = Indices::lookup(author).unwrap(); - let data = pallet_balances::AccountData { free: 5_000_000_000_000, ..Default::default() }; + let data = pallet_balances::AccountData { + free: ExistentialDeposit::get() * 10, + ..Default::default() + }; let account = frame_system::AccountInfo { providers: 1, data, ..Default::default() }; >::insert(&address, account); diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index aa5563f0d58bf..58c447d248a3f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -59,14 +59,14 @@ use frame_support::{ }, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstantStoragePrice, Contains, Currency, EitherOfDiverse, EnsureOriginWithArg, - EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, - LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, + EqualPrivilegeOnly, InsideBoth, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, + LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, }, weights::{ constants::{ BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, }, - ConstantMultiplier, IdentityFee, Weight, + ConstantMultiplier, Weight, }, BoundedVec, PalletId, }; @@ -79,7 +79,7 @@ use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce}; use pallet_asset_conversion::{AccountIdConverter, Ascending, Chain, WithFirstAsset}; use pallet_asset_conversion_tx_payment::SwapAssetAdapter; use pallet_assets_precompiles::{InlineIdConfig, ERC20}; -use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600}; +use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, TaskId}; use pallet_election_provider_multi_phase::{GeometricDepositBase, SolutionAccuracyOf}; use pallet_identity::legacy::IdentityInfo; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; @@ -88,12 +88,8 @@ use pallet_nis::WithMaximumOf; use pallet_nomination_pools::PoolId; use pallet_revive::evm::runtime::EthExtra; use pallet_session::historical as pallet_session_historical; -// Can't use `FungibleAdapter` here until Treasury pallet migrates to fungibles -// -use pallet_broker::TaskId; -#[allow(deprecated)] -pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; +pub use pallet_transaction_payment::{FungibleAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_tx_pause::RuntimeCallNameOf; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; @@ -136,7 +132,7 @@ pub use pallet_staking::StakerStatus; pub mod impls; #[cfg(not(feature = "runtime-benchmarks"))] use impls::AllianceIdentityVerifier; -use impls::{AllianceProposalProvider, Author}; +use impls::AllianceProposalProvider; /// Constant values used within the runtime. pub mod constants; @@ -203,22 +199,6 @@ pub fn native_version() -> NativeVersion { type NegativeImbalance = >::NegativeImbalance; -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(fees) = fees_then_tips.next() { - // for fees, 80% to treasury, 20% to author - let mut split = fees.ration(80, 20); - if let Some(tips) = fees_then_tips.next() { - // for tips, if any, 80% to treasury, 20% to author (though this can be anything) - tips.ration_merge_into(80, 20, &mut split); - } - Treasury::on_unbalanced(split.0); - Author::on_unbalanced(split.1); - } - } -} - /// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. /// This is used to limit the maximal weight of a single extrinsic. const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); @@ -604,18 +584,15 @@ parameter_types! { pub const OperationalFeeMultiplier: u8 = 5; pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000); - pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128); + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 10u128); pub MaximumMultiplier: Multiplier = Bounded::max_value(); } -// Can't use `FungibleAdapter` here until Treasury pallet migrates to fungibles -// -#[allow(deprecated)] impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CurrencyAdapter; + type OnChargeTransaction = FungibleAdapter>; type OperationalFeeMultiplier = OperationalFeeMultiplier; - type WeightToFee = IdentityFee; + type WeightToFee = pallet_revive::evm::fees::BlockRatioFee<1, 1, Self>; type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = TargetedFeeAdjustment< Self, @@ -1424,6 +1401,7 @@ parameter_types! { pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); + pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(1,2); } impl pallet_contracts::Config for Runtime { @@ -1470,12 +1448,13 @@ impl pallet_contracts::Config for Runtime { impl pallet_revive::Config for Runtime { type Time = Timestamp; + type Balance = Balance; type Currency = Balances; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; + type RuntimeOrigin = RuntimeOrigin; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; - type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_revive::weights::SubstrateWeight; type Precompiles = (ERC20, Instance1>, ERC20, Instance2>); @@ -1489,9 +1468,10 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = (); type FindAuthor = ::FindAuthor; type AllowEVMBytecode = ConstBool; + type FeeInfo = pallet_revive::evm::fees::Info; + type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; } impl pallet_sudo::Config for Runtime { @@ -1557,6 +1537,7 @@ where ), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + pallet_revive::evm::tx_extension::SetOrigin::::default(), frame_system::WeightReclaim::::new(), ); @@ -1614,6 +1595,7 @@ where pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(0, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + pallet_revive::evm::tx_extension::SetOrigin::::default(), frame_system::WeightReclaim::::new(), ) } @@ -2834,6 +2816,7 @@ pub type TxExtension = ( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, >, frame_metadata_hash_extension::CheckMetadataHash, + pallet_revive::evm::tx_extension::SetOrigin, frame_system::WeightReclaim, ); @@ -2857,6 +2840,7 @@ impl EthExtra for EthExtraImpl { pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None) .into(), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), frame_system::WeightReclaim::::new(), ) } @@ -3117,8 +3101,9 @@ mod benches { ); } -pallet_revive::impl_runtime_apis_plus_revive!( +pallet_revive::impl_runtime_apis_plus_revive_traits!( Runtime, + Revive, Executive, EthExtraImpl, diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index a958a7da80612..ac74856deba37 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -27,6 +27,7 @@ node-cli = { workspace = true } node-primitives = { workspace = true, default-features = true } pallet-asset-conversion = { workspace = true, default-features = true } pallet-asset-conversion-tx-payment = { workspace = true, default-features = true } +pallet-revive = { workspace = true, default-features = true } pallet-skip-feeless-payment = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index 6b100a6477970..485323bf6132b 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -87,6 +87,7 @@ pub fn tx_ext(nonce: Nonce, extra_fee: Balance) -> TxExtension { pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + pallet_revive::evm::tx_extension::SetOrigin::default(), frame_system::WeightReclaim::new(), ) } diff --git a/substrate/frame/assets/precompiles/src/mock.rs b/substrate/frame/assets/precompiles/src/mock.rs index 941726a148167..e763f2364668f 100644 --- a/substrate/frame/assets/precompiles/src/mock.rs +++ b/substrate/frame/assets/precompiles/src/mock.rs @@ -68,6 +68,7 @@ impl pallet_assets::Config for Test { #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] impl pallet_revive::Config for Test { type AddressMapper = pallet_revive::TestAccountMapper; + type Balance = u64; type Currency = Balances; type Precompiles = (ERC20>,); } diff --git a/substrate/frame/assets/precompiles/src/tests.rs b/substrate/frame/assets/precompiles/src/tests.rs index 0da75a49c60c7..3ff71b3b2104a 100644 --- a/substrate/frame/assets/precompiles/src/tests.rs +++ b/substrate/frame/assets/precompiles/src/tests.rs @@ -22,7 +22,7 @@ use crate::{ }; use alloy::primitives::U256; use frame_support::{assert_ok, traits::Currency}; -use pallet_revive::DepositLimit; +use pallet_revive::ExecConfig; use sp_core::H160; use sp_runtime::Weight; @@ -77,8 +77,9 @@ fn precompile_transfer_works() { H160::from(asset_addr), 0u32.into(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u64::MAX, data, + ExecConfig::new_substrate_tx(), ); assert_contract_event( @@ -115,8 +116,9 @@ fn total_supply_works() { H160::from(asset_addr), 0u32.into(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u64::MAX, data, + ExecConfig::new_substrate_tx(), ) .result .unwrap() @@ -147,8 +149,9 @@ fn balance_of_works() { H160::from(asset_addr), 0u32.into(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u64::MAX, data, + ExecConfig::new_substrate_tx(), ) .result .unwrap() @@ -192,8 +195,9 @@ fn approval_works() { H160::from(asset_addr), 0u32.into(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u64::MAX, data, + ExecConfig::new_substrate_tx(), ); assert_contract_event( @@ -214,8 +218,9 @@ fn approval_works() { H160::from(asset_addr), 0u32.into(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u64::MAX, data, + ExecConfig::new_substrate_tx(), ) .result .unwrap() @@ -236,8 +241,9 @@ fn approval_works() { H160::from(asset_addr), 0u32.into(), Weight::MAX, - DepositLimit::UnsafeOnlyForDryRun, + u64::MAX, data, + ExecConfig::new_substrate_tx(), ); assert_eq!(Assets::balance(asset_id, owner), 90); assert_eq!(Assets::allowance(asset_id, &owner, &spender), 15); diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 9a743170c8ac0..f949211d2ae71 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -19,6 +19,7 @@ polkadot-sdk = { workspace = true, features = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parachains-common", + "polkadot-runtime-common", "runtime", "with-tracing", ] } diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index fcfc12afc66e7..34a18790a987c 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -30,7 +30,13 @@ use frame_support::weights::{ Weight, }; use frame_system::limits::BlockWeights; -use pallet_revive::{evm::runtime::EthExtra, AccountId32Mapper}; +use pallet_revive::{ + evm::{ + fees::{BlockRatioFee, Info as FeeInfo}, + runtime::EthExtra, + }, + AccountId32Mapper, +}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use polkadot_sdk::{ polkadot_sdk_frame::{ @@ -39,7 +45,7 @@ use polkadot_sdk::{ }, *, }; -use sp_weights::{ConstantMultiplier, IdentityFee}; +use sp_weights::ConstantMultiplier; pub use polkadot_sdk::{ parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, @@ -48,9 +54,9 @@ pub use polkadot_sdk::{ pub mod currency { use super::Balance; - pub const MILLICENTS: Balance = 1_000_000_000; - pub const CENTS: Balance = 1_000 * MILLICENTS; - pub const DOLLARS: Balance = 100 * CENTS; + pub const DOLLARS: Balance = 1_000_000_000_000; + pub const CENTS: Balance = DOLLARS / 100; + pub const MILLICENTS: Balance = CENTS / 1_000; } /// Provides getters for genesis configuration presets. @@ -156,6 +162,8 @@ type TxExtension = ( // Ensures that the sender has enough funds to pay for the transaction // and deducts the fee from the sender's account. pallet_transaction_payment::ChargeTransactionPayment, + // Needs to be done after all extensions that rely on a signed origin. + pallet_revive::evm::tx_extension::SetOrigin, // Reclaim the unused weight from the block using post dispatch information. // It must be last in the pipeline in order to catch the refund in previous transaction // extensions @@ -180,6 +188,7 @@ impl EthExtra for EthExtraImpl { frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + pallet_revive::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), frame_system::WeightReclaim::::new(), ) } @@ -284,7 +293,7 @@ impl frame_system::Config for Runtime { } parameter_types! { - pub const ExistentialDeposit: Balance = DOLLARS; + pub const ExistentialDeposit: Balance = CENTS; } // Implements the types required for the balances pallet. @@ -311,8 +320,9 @@ parameter_types! { #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = IdentityFee; + type WeightToFee = BlockRatioFee<1, 1, Self>; type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = polkadot_sdk::polkadot_runtime_common::SlowAdjustingFeeUpdate; } parameter_types! { @@ -324,15 +334,18 @@ impl pallet_revive::Config for Runtime { type AddressMapper = AccountId32Mapper; type ChainId = ConstU64<420_420_420>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Balance = Balance; type Currency = Balances; type NativeToEthRatio = ConstU32<1_000_000>; type UploadOrigin = EnsureSigned; type InstantiateOrigin = EnsureSigned; type Time = Timestamp; + type FeeInfo = FeeInfo; } -pallet_revive::impl_runtime_apis_plus_revive!( +pallet_revive::impl_runtime_apis_plus_revive_traits!( Runtime, + Revive, Executive, EthExtraImpl, diff --git a/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol b/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol index f3da8fcf801d0..8ae8199f62fac 100644 --- a/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol +++ b/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol @@ -6,7 +6,7 @@ contract TransactionInfo { return tx.origin; } - function gasprice() public view returns (uint64) { + function gasprice() public view returns (uint256) { return uint64(tx.gasprice); } diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 691477539f181..44e712ba7ea7e 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -20,8 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use crate::{ call_builder::{caller_funding, default_deposit_limit, CallSetup, Contract, VmBinaryModule}, - evm::runtime::GAS_PRICE, - exec::{Key, MomentOf, PrecompileExt}, + exec::{Key, PrecompileExt}, limits, precompiles::{ self, @@ -113,12 +112,11 @@ fn whitelisted_pallet_account() -> T::AccountId { #[benchmarks( where - BalanceOf: Into + TryFrom, T: Config, - MomentOf: Into, ::RuntimeEvent: From>, ::RuntimeCall: From>, ::Hash: frame_support::traits::IsType, + OriginFor: From>, )] mod benchmarks { use super::*; @@ -294,12 +292,11 @@ mod benchmarks { let dust = 42u32 * d; let evm_value = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(value, dust)); - let caller = whitelisted_caller(); T::Currency::set_balance(&caller, caller_funding::()); let VmBinaryModule { code, .. } = VmBinaryModule::sized(c); - let origin = RawOrigin::Signed(caller.clone()); - Contracts::::map_account(origin.clone().into()).unwrap(); + let origin = Origin::EthTransaction(caller.clone()); + Contracts::::map_account(OriginFor::::signed(caller.clone())).unwrap(); let deployer = T::AddressMapper::to_address(&caller); let nonce = System::::account_nonce(&caller).try_into().unwrap_or_default(); let addr = crate::address::create1(&deployer, nonce); @@ -308,8 +305,12 @@ mod benchmarks { assert!(AccountInfoOf::::get(&deployer).is_none()); + ::FeeInfo::deposit_txfee( + ::Currency::issue(caller_funding::()), + ); + #[extrinsic_call] - _(origin, evm_value, Weight::MAX, storage_deposit, code, input); + _(origin, evm_value, Weight::MAX, storage_deposit, code, input, 0u32.into(), 0); let deposit = T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id); @@ -321,14 +322,14 @@ mod benchmarks { let mapping_deposit = T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &caller); + assert_eq!( + ::FeeInfo::remaining_txfee(), + caller_funding::() - deposit - code_deposit - Pallet::::min_balance(), + ); assert_eq!( Pallet::::evm_balance(&deployer), Pallet::::convert_native_to_evm( - caller_funding::() - - Pallet::::min_balance() - - Pallet::::min_balance() - - value - deposit - code_deposit - - mapping_deposit, + caller_funding::() - Pallet::::min_balance() - value - mapping_deposit, ) - dust, ); @@ -439,12 +440,17 @@ mod benchmarks { let evm_value = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(value, dust)); + // need to pass the overdraw check + ::FeeInfo::deposit_txfee( + ::Currency::issue(caller_funding::()), + ); + let caller_addr = T::AddressMapper::to_address(&instance.caller); - let origin = RawOrigin::Signed(instance.caller.clone()); + let origin = Origin::EthTransaction(instance.caller.clone()); let before = Pallet::::evm_balance(&instance.address); let storage_deposit = default_deposit_limit::(); #[extrinsic_call] - _(origin, instance.address, evm_value, Weight::MAX, storage_deposit, data); + _(origin, instance.address, evm_value, Weight::MAX, storage_deposit, data, 0u32.into(), 0); let deposit = T::Currency::balance_on_hold( &HoldReason::StorageDepositReserve.into(), &instance.account_id, @@ -678,7 +684,7 @@ mod benchmarks { let mut call_setup = CallSetup::::default(); let contract_acc = call_setup.contract().account_id.clone(); let caller = call_setup.contract().address; - call_setup.set_origin(Origin::from_account_id(contract_acc)); + call_setup.set_origin(ExecOrigin::from_account_id(contract_acc)); let (mut ext, _) = call_setup.ext(); let result; @@ -737,7 +743,7 @@ mod benchmarks { ISystem::ISystemCalls::callerIsRoot(ISystem::callerIsRootCall {}).abi_encode(); let mut setup = CallSetup::::default(); - setup.set_origin(Origin::Root); + setup.set_origin(ExecOrigin::Root); let (mut ext, _) = setup.ext(); let result; @@ -994,7 +1000,7 @@ mod benchmarks { { result = runtime.bench_gas_price(memory.as_mut_slice()); } - assert_eq!(result.unwrap(), u64::from(GAS_PRICE)); + assert_eq!(U256::from(result.unwrap()), >::evm_gas_price()); } #[benchmark(pov_mode = Measured)] @@ -1107,24 +1113,6 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().now()); } - #[benchmark(pov_mode = Measured)] - fn seal_weight_to_fee() { - build_runtime!(runtime, memory: [[0u8;32], ]); - let weight = Weight::from_parts(500_000, 300_000); - let result; - #[block] - { - result = runtime.bench_weight_to_fee( - memory.as_mut_slice(), - weight.ref_time(), - weight.proof_size(), - 0, - ); - } - assert_ok!(result); - assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().get_weight_price(weight)); - } - #[benchmark(pov_mode = Measured)] fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { let mut setup = CallSetup::::default(); @@ -1828,7 +1816,7 @@ mod benchmarks { // We benchmark the overhead of cloning the input. Not passing it to the contract. // This is why we set the input here instead of passig it as pointer to the `bench_call`. setup.set_data(vec![42; i as usize]); - setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone())); setup.set_balance(value + 1u32.into() + Pallet::::min_balance()); let (mut ext, _) = setup.ext(); @@ -1932,7 +1920,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); setup.set_storage_deposit_limit(deposit); - setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone())); let (mut ext, _) = setup.ext(); let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); @@ -1981,7 +1969,7 @@ mod benchmarks { let deposit_len = deposit_bytes.len() as u32; let mut setup = CallSetup::::default(); - setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone())); setup.set_balance(value + 1u32.into() + (Pallet::::min_balance() * 2u32.into())); let account_id = &setup.contract().account_id.clone(); diff --git a/substrate/frame/revive/src/call_builder.rs b/substrate/frame/revive/src/call_builder.rs index 75b002f70f5fa..e176c62e73811 100644 --- a/substrate/frame/revive/src/call_builder.rs +++ b/substrate/frame/revive/src/call_builder.rs @@ -32,8 +32,8 @@ use crate::{ storage::meter::Meter, transient_storage::MeterEntry, vm::pvm::{PreparedCall, Runtime}, - AccountInfo, BalanceOf, BalanceWithDust, BumpNonce, Code, CodeInfoOf, Config, ContractBlob, - ContractInfo, DepositLimit, Error, GasMeter, MomentOf, Origin, Pallet as Contracts, + AccountInfo, BalanceOf, BalanceWithDust, Code, CodeInfoOf, Config, ContractBlob, ContractInfo, + Error, ExecConfig, ExecOrigin as Origin, GasMeter, OriginFor, Pallet as Contracts, PristineCode, Weight, }; use alloc::{vec, vec::Vec}; @@ -56,14 +56,12 @@ pub struct CallSetup { value: BalanceOf, data: Vec, transient_storage_size: u32, + exec_config: ExecConfig, } impl Default for CallSetup where T: Config, - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, { fn default() -> Self { Self::new(VmBinaryModule::dummy()) @@ -73,9 +71,6 @@ where impl CallSetup where T: Config, - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, { /// Setup a new call for the given module. pub fn new(module: VmBinaryModule) -> Self { @@ -111,6 +106,7 @@ where value: 0u32.into(), data: vec![], transient_storage_size: 0, + exec_config: ExecConfig::new_substrate_tx(), } } @@ -157,6 +153,7 @@ where &mut self.gas_meter, &mut self.storage_meter, self.value, + &self.exec_config, ); if self.transient_storage_size > 0 { Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap(); @@ -224,9 +221,6 @@ pub struct Contract { impl Contract where T: Config, - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, { /// Create new contract and use a default account id as instantiator. pub fn new(module: VmBinaryModule, data: Vec) -> Result, &'static str> { @@ -252,7 +246,7 @@ where ) -> Result, &'static str> { T::Currency::set_balance(&caller, caller_funding::()); let salt = Some([0xffu8; 32]); - let origin: T::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); + let origin: OriginFor = RawOrigin::Signed(caller.clone()).into(); // We ignore the error since we might also pass an already mapped account here. Contracts::::map_account(origin.clone()).ok(); @@ -266,11 +260,11 @@ where origin, U256::zero(), Weight::MAX, - DepositLimit::Balance(default_deposit_limit::()), + default_deposit_limit::(), Code::Upload(module.code), data, salt, - BumpNonce::Yes, + ExecConfig::new_substrate_tx(), ); let address = outcome.result?.addr; diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index f340474f472e0..dd5cd6596e124 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,9 +19,13 @@ mod api; pub use api::*; +mod call; +pub(crate) use call::*; mod tracing; pub use tracing::*; -mod gas_encoder; -pub use gas_encoder::*; +pub mod fees; pub mod runtime; +pub mod tx_extension; pub use alloy_core::sol_types::decode_revert_reason; + +type OnChargeTransactionBalanceOf = <::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction>::Balance; diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 09b9f67271504..a1605d569b3b7 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -209,6 +209,20 @@ impl GenericTransaction { Self::from_unsigned(tx.into(), base_gas_price, from) } + /// The gas price that is actually paid (including priority fee). + pub fn effective_gas_price(&self, base_gas_price: U256) -> Option { + let effective_gas_price = if let Some(prio_price) = self.max_priority_fee_per_gas { + let max_price = self.max_fee_per_gas?; + Some(max_price.min(base_gas_price.saturating_add(prio_price))) + } else { + self.gas_price + }; + + // we do not implement priority fee as it does not map to tip well + // hence the effective gas price cannot be higher than the base price + effective_gas_price.map(|e| e.min(base_gas_price)) + } + /// Create a new [`GenericTransaction`] from a unsigned transaction. pub fn from_unsigned( tx: TransactionUnsigned, @@ -216,7 +230,7 @@ impl GenericTransaction { from: Option, ) -> Self { use TransactionUnsigned::*; - match tx { + let mut tx = match tx { TransactionLegacyUnsigned(tx) => GenericTransaction { from, r#type: Some(tx.r#type.as_byte()), @@ -238,11 +252,6 @@ impl GenericTransaction { value: Some(tx.value), to: Some(tx.to), gas: Some(tx.gas), - gas_price: Some( - base_gas_price - .saturating_add(tx.max_priority_fee_per_gas) - .min(tx.max_fee_per_blob_gas), - ), access_list: Some(tx.access_list), blob_versioned_hashes: tx.blob_versioned_hashes, max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), @@ -259,11 +268,6 @@ impl GenericTransaction { value: Some(tx.value), to: tx.to, gas: Some(tx.gas), - gas_price: Some( - base_gas_price - .saturating_add(tx.max_priority_fee_per_gas) - .min(tx.max_fee_per_gas), - ), access_list: Some(tx.access_list), max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), @@ -291,18 +295,15 @@ impl GenericTransaction { value: Some(tx.value), to: tx.to, gas: Some(tx.gas), - gas_price: Some( - base_gas_price - .saturating_add(tx.max_priority_fee_per_gas) - .min(tx.max_fee_per_gas), - ), access_list: Some(tx.access_list), authorization_list: tx.authorization_list, max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), ..Default::default() }, - } + }; + tx.gas_price = tx.effective_gas_price(base_gas_price); + tx } /// Convert to a [`TransactionUnsigned`]. @@ -390,12 +391,12 @@ fn from_unsigned_works_for_legacy() { value: U256::from(1), to: Some(H160::zero()), gas: U256::from(1), - gas_price: U256::from(11), + gas_price: U256::from(10), ..Default::default() }); let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None); - assert_eq!(generic.gas_price, Some(U256::from(11))); + assert_eq!(generic.gas_price, Some(U256::from(10))); let tx2 = generic.try_into_unsigned().unwrap(); assert_eq!(tx, tx2); @@ -418,7 +419,7 @@ fn from_unsigned_works_for_1559() { }); let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None); - assert_eq!(generic.gas_price, Some(U256::from(11))); + assert_eq!(generic.gas_price, Some(U256::from(10))); let tx2 = generic.try_into_unsigned().unwrap(); assert_eq!(tx, tx2); @@ -449,7 +450,7 @@ fn from_unsigned_works_for_7702() { }); let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None); - assert_eq!(generic.gas_price, Some(U256::from(11))); + assert_eq!(generic.gas_price, Some(U256::from(10))); let tx2 = generic.try_into_unsigned().unwrap(); assert_eq!(tx, tx2); diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 7ca087ff53fe4..f1162592ae833 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -718,26 +718,6 @@ impl Default for TransactionSigned { } } -impl TransactionSigned { - /// Get the effective gas price. - pub fn effective_gas_price(&self, base_gas_price: U256) -> U256 { - match &self { - TransactionSigned::TransactionLegacySigned(tx) => - tx.transaction_legacy_unsigned.gas_price, - TransactionSigned::Transaction7702Signed(tx) => base_gas_price - .saturating_add(tx.transaction_7702_unsigned.max_priority_fee_per_gas) - .min(tx.transaction_7702_unsigned.max_fee_per_gas), - TransactionSigned::Transaction4844Signed(tx) => base_gas_price - .saturating_add(tx.transaction_4844_unsigned.max_priority_fee_per_gas) - .min(tx.transaction_4844_unsigned.max_fee_per_blob_gas), - TransactionSigned::Transaction1559Signed(tx) => base_gas_price - .saturating_add(tx.transaction_1559_unsigned.max_priority_fee_per_gas) - .min(tx.transaction_1559_unsigned.max_fee_per_gas), - TransactionSigned::Transaction2930Signed(tx) => tx.transaction_2930_unsigned.gas_price, - } - } -} - /// Validator withdrawal #[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs new file mode 100644 index 0000000000000..420068a70d594 --- /dev/null +++ b/substrate/frame/revive/src/evm/call.rs @@ -0,0 +1,198 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functionality to decode an eth transaction into an dispatchable call. + +use crate::{ + evm::{fees::InfoT, runtime::SetWeightLimit}, + extract_code_and_data, BalanceOf, CallOf, Config, GenericTransaction, Pallet, Weight, Zero, + LOG_TARGET, RUNTIME_PALLETS_ADDR, +}; +use codec::DecodeLimit; +use frame_support::MAX_EXTRINSIC_DEPTH; +use num_traits::Bounded; +use sp_core::Get; +use sp_runtime::{ + transaction_validity::InvalidTransaction, FixedPointNumber, SaturatedConversion, Saturating, +}; + +/// Result of decoding an eth transaction into a dispatchable call. +pub struct CallInfo { + /// The dispatchable call with the correct weights assigned. + /// + /// This will be either `eth_call` or `eth_instantiate_with_code`. + pub call: CallOf, + /// The weight that was set inside [`Self::call`]. + pub weight_limit: Weight, + /// The encoded length of the bare transaction carrying the ethereum payload. + pub encoded_len: u32, + /// The adjusted transaction fee of [`Self::call`]. + pub tx_fee: BalanceOf, + /// The additional storage deposit to be deposited into the txhold. + pub storage_deposit: BalanceOf, +} + +/// Decode `tx` into a dispatchable call. +pub fn create_call( + tx: GenericTransaction, + encoded_len: Option, +) -> Result, InvalidTransaction> +where + T: Config, + CallOf: SetWeightLimit, +{ + let base_fee = >::evm_gas_price(); + + let Some(gas) = tx.gas else { + log::debug!(target: LOG_TARGET, "No gas provided"); + return Err(InvalidTransaction::Call); + }; + + let Some(effective_gas_price) = tx.gas_price else { + log::debug!(target: LOG_TARGET, "No gas_price provided."); + return Err(InvalidTransaction::Payment); + }; + + let chain_id = tx.chain_id.unwrap_or_default(); + + if chain_id != ::ChainId::get().into() { + log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); + return Err(InvalidTransaction::Call); + } + + if effective_gas_price < base_fee { + log::debug!( + target: LOG_TARGET, + "Specified gas_price is too low. effective_gas_price={effective_gas_price} base_fee={base_fee}" + ); + return Err(InvalidTransaction::Payment); + } + + let encoded_len = if let Some(encoded_len) = encoded_len { + encoded_len + } else { + let unsigned_tx = tx.clone().try_into_unsigned().map_err(|_| { + log::debug!(target: LOG_TARGET, "Invalid transaction type."); + InvalidTransaction::Call + })?; + let eth_transact_call = + crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; + ::FeeInfo::encoded_len(eth_transact_call.into()) + }; + + let value = tx.value.unwrap_or_default(); + let data = tx.input.to_vec(); + + let mut call = if let Some(dest) = tx.to { + if dest == RUNTIME_PALLETS_ADDR { + let call = + CallOf::::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &data[..]) + .map_err(|_| { + log::debug!(target: LOG_TARGET, "Failed to decode data as Call"); + InvalidTransaction::Call + })?; + + if !value.is_zero() { + log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value"); + return Err(InvalidTransaction::Call) + } + + call + } else { + let call = crate::Call::eth_call:: { + dest, + value, + gas_limit: Zero::zero(), + storage_deposit_limit: BalanceOf::::max_value(), + data, + effective_gas_price, + encoded_len, + } + .into(); + call + } + } else { + let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) { + let Some((code, data)) = extract_code_and_data(&data) else { + log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data"); + return Err(InvalidTransaction::Call); + }; + (code, data) + } else { + (data, Default::default()) + }; + + let call = crate::Call::eth_instantiate_with_code:: { + value, + gas_limit: Zero::zero(), + storage_deposit_limit: BalanceOf::::max_value(), + code, + data, + effective_gas_price, + encoded_len, + } + .into(); + + call + }; + + // the fee as signed off by the eth wallet. we cannot consume more. + let eth_fee = effective_gas_price.saturating_mul(gas) / ::NativeToEthRatio::get(); + + let weight_limit = { + let info = ::FeeInfo::dispatch_info(&call); + let fixed_fee = ::FeeInfo::weight_to_fee( + ::BlockWeights::get().get(info.class).base_extrinsic, + ) + .saturating_add(::FeeInfo::length_to_fee(encoded_len as u32)); + + let remaining_fee = { + let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}"); + InvalidTransaction::Payment + })?; + let unadjusted = ::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(>::saturated_from(adjusted)); + unadjusted + }; + + let weight_limit = ::FeeInfo::fee_to_weight(remaining_fee) + .checked_sub(&info.total_weight()).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover the weight of the extrinsic."); + InvalidTransaction::Payment + })?; + + call.set_weight_limit(weight_limit); + let info = ::FeeInfo::dispatch_info(&call); + let max_weight = >::evm_max_extrinsic_weight(); + let overweight_by = info.total_weight().saturating_sub(max_weight); + let capped_weight = weight_limit.saturating_sub(overweight_by); + call.set_weight_limit(capped_weight); + capped_weight + }; + + // the overall fee of the extrinsic including the gas limit + let tx_fee = ::FeeInfo::tx_fee(encoded_len, &call); + + // the leftover we make available to the deposit collection system + let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| { + log::error!(target: LOG_TARGET, "The eth_fee={eth_fee:?} is smaller than the tx_fee={tx_fee:?}. This is a bug."); + InvalidTransaction::Payment + })?.saturated_into(); + + Ok(CallInfo { call, weight_limit, encoded_len, tx_fee, storage_deposit }) +} diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs new file mode 100644 index 0000000000000..50bb1450dfb59 --- /dev/null +++ b/substrate/frame/revive/src/evm/fees.rs @@ -0,0 +1,309 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the fee types that need to be configured for `pallet-transaction-payment`. + +use crate::{ + evm::{runtime::EthExtra, OnChargeTransactionBalanceOf}, + BalanceOf, CallOf, Config, DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Error, + PostDispatchInfo, LOG_TARGET, +}; +use codec::Encode; +use core::marker::PhantomData; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo}, + pallet_prelude::Weight, + traits::{fungible::Credit, Get, SuppressedDrop}, + weights::WeightToFee, +}; +use frame_system::Config as SysConfig; +use num_traits::Zero; +use pallet_transaction_payment::{ + Config as TxConfig, MultiplierUpdate, NextFeeMultiplier, Pallet as TxPallet, TxCreditHold, +}; +use sp_runtime::{ + generic::UncheckedExtrinsic, + traits::{Block as BlockT, Dispatchable, TransactionExtension}, + FixedPointNumber, FixedU128, SaturatedConversion, Saturating, +}; + +type CreditOf = Credit<::AccountId, ::Currency>; + +/// The only [`WeightToFee`] implementation that is supported by this pallet. +/// +/// `P,Q`: Rational number that defines the ref_time to fee mapping. +/// +/// This enforces a ratio of ref_time and proof_time that is proportional +/// to their distribution in the block limits. We enforce the usage of this fee +/// structure because our gas mapping depends on it. +/// +/// # Panics +/// +/// If either `P` or `Q` is zero. +pub struct BlockRatioFee(PhantomData); + +/// The only [`InfoT`] implementation valid for [`Config::FeeInfo`]. +/// +/// The reason for this type is to avoid coupling the rest of pallet_revive to +/// pallet_transaction_payment. This way we bundle all the trait bounds in once place. +pub struct Info(PhantomData<(Address, Signature, Extra)>); + +/// A trait that signals that [`BlockRatioFee`] is used by the runtime. +/// +/// This trait is sealed. Use [`BlockRatioFee`]. +pub trait BlockRatioWeightToFee: seal::Sealed { + /// The runtime. + type T: Config; + /// The ref_time to fee coefficient. + const REF_TIME_TO_FEE: FixedU128; + + /// The proof_size to fee coefficient. + fn proof_size_to_fee() -> FixedU128 { + let max_weight = ::BlockWeights::get().max_block; + let ratio = + FixedU128::from_rational(max_weight.ref_time().into(), max_weight.proof_size().into()); + Self::REF_TIME_TO_FEE.saturating_mul(ratio) + } +} + +/// A trait that exposes all the transaction payment details to `pallet_revive`. +/// +/// This trait is sealed. Use [`Info`]. +pub trait InfoT: seal::Sealed { + /// Check that the fee configuration of the chain is valid. + /// + /// This is being called by the pallets `integrity_check`. + fn integrity_test() {} + + /// Exposes the current fee multiplier of the chain. + fn next_fee_multiplier() -> FixedU128 { + FixedU128::from_rational(1, 1) + } + + /// The reciprocal of the next fee multiplier. + /// + /// Needed when dividing a fee by the multiplier before presenting + /// it to the eth wallet as gas. Needed because the wallet will multiply + /// it with the gas_price which includes this multiplicator. + fn next_fee_multiplier_reciprocal() -> FixedU128 { + Self::next_fee_multiplier() + .reciprocal() + .expect("The minimum multiplier is not 0. We check that in `integrity_test`; qed") + } + + /// Calculate the fee of a transaction including the next fee multiplier adjustment. + fn tx_fee(_len: u32, _call: &CallOf) -> BalanceOf { + Zero::zero() + } + + /// Makes sure that not too much storage deposit was withdrawn. + fn ensure_not_overdrawn( + _encoded_len: u32, + _info: &DispatchInfo, + result: DispatchResultWithPostInfo, + ) -> DispatchResultWithPostInfo { + result + } + + /// Get the dispatch info of a call with the proper extension weight set. + fn dispatch_info(_call: &CallOf) -> DispatchInfo { + Default::default() + } + + /// Calculate the encoded length of a call. + fn encoded_len(_eth_transact_call: CallOf) -> u32 { + 0 + } + + /// Convert a weight to an unadjusted fee. + fn weight_to_fee(_weight: Weight) -> BalanceOf { + Zero::zero() + } + + /// Convert an unadjusted fee back to a weight. + fn fee_to_weight(_fee: BalanceOf) -> Weight { + Zero::zero() + } + + /// Convert the length of a transaction to an unadjusted weight. + fn length_to_fee(_len: u32) -> BalanceOf { + Zero::zero() + } + + /// Add some additional fee to the `pallet_transaction_payment` credit. + fn deposit_txfee(_credit: CreditOf) {} + + /// Withdraw some fee to pay for storage deposits. + fn withdraw_txfee(_amount: BalanceOf) -> Option> { + Default::default() + } + + /// Return the remaining transaction fee. + fn remaining_txfee() -> BalanceOf { + Default::default() + } +} + +impl BlockRatioWeightToFee for BlockRatioFee { + type T = T; + const REF_TIME_TO_FEE: FixedU128 = { + assert!(P > 0 && Q > 0); + FixedU128::from_rational(P, Q) + }; +} + +impl WeightToFee for BlockRatioFee { + type Balance = BalanceOf; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + let ref_time_fee = Self::REF_TIME_TO_FEE + .saturating_mul_int(Self::Balance::saturated_from(weight.ref_time())); + let proof_size_fee = Self::proof_size_to_fee() + .saturating_mul_int(Self::Balance::saturated_from(weight.proof_size())); + ref_time_fee.max(proof_size_fee) + } +} + +impl InfoT for Info +where + BalanceOf: From>, + ::RuntimeCall: + Dispatchable, + <::Block as BlockT>::Extrinsic: + From, Signature, E::Extension>>, + ::WeightToFee: BlockRatioWeightToFee, + <::OnChargeTransaction as TxCreditHold>::Credit: + SuppressedDrop>, +{ + fn integrity_test() { + let min_multiplier = ::FeeMultiplierUpdate::min(); + assert!(!min_multiplier.is_zero(), "The multiplier is never allowed to be zero."); + assert!( + min_multiplier.saturating_mul_int(::NativeToEthRatio::get()) > 0, + "The gas price needs to be greater zero." + ); + assert!( + !::WeightToFee::REF_TIME_TO_FEE.is_zero(), + "ref_time to fee is not allowed to be zero." + ); + assert!( + !::WeightToFee::proof_size_to_fee().is_zero(), + "proof_size to fee is not allowed to be zero." + ); + } + + fn next_fee_multiplier() -> FixedU128 { + >::get() + } + + fn tx_fee(len: u32, call: &CallOf) -> BalanceOf { + let dispatch_info = Self::dispatch_info(call); + TxPallet::::compute_fee(len, &dispatch_info, 0u32.into()).into() + } + + fn ensure_not_overdrawn( + encoded_len: u32, + info: &DispatchInfo, + result: DispatchResultWithPostInfo, + ) -> DispatchResultWithPostInfo { + // if tx is already failing we can ignore + // as it will be rolled back anyways + let Ok(post_info) = result else { + return result; + }; + + let fee: BalanceOf = + >::compute_actual_fee(encoded_len, info, &post_info, Zero::zero()) + .into(); + let available = Self::remaining_txfee(); + if fee > available { + log::debug!(target: LOG_TARGET, "Drew too much from the txhold. \ + fee={fee:?} \ + available={available:?} \ + overdrawn_by={:?}", + fee.saturating_sub(available), + ); + Err(DispatchErrorWithPostInfo { + post_info, + error: >::TxFeeOverdraw.into(), + }) + } else { + log::trace!(target: LOG_TARGET, "Enough left in the txhold. \ + fee={fee:?} \ + available={available:?} \ + refund={:?}", + available.saturating_sub(fee), + ); + result + } + } + + fn dispatch_info(call: &CallOf) -> DispatchInfo { + let mut dispatch_info = call.get_dispatch_info(); + dispatch_info.extension_weight = + E::get_eth_extension(0u32.into(), 0u32.into()).weight(call); + dispatch_info + } + + fn encoded_len(eth_transact_call: CallOf) -> u32 { + let uxt: <::Block as BlockT>::Extrinsic = + UncheckedExtrinsic::new_bare(eth_transact_call).into(); + uxt.encoded_size() as u32 + } + + fn weight_to_fee(weight: Weight) -> BalanceOf { + TxPallet::::weight_to_fee(weight).into() + } + + /// Convert an unadjusted fee back to a weight. + fn fee_to_weight(fee: BalanceOf) -> Weight { + let ref_time = ::WeightToFee::REF_TIME_TO_FEE + .reciprocal() + .expect("This is not zero. Enforced in `integrity_test`; qed") + .saturating_mul_int(fee); + let proof_size = ::WeightToFee::proof_size_to_fee() + .reciprocal() + .expect("This is not zero. Enforced in `integrity_test`; qed") + .saturating_mul_int(fee); + Weight::from_parts(ref_time.saturated_into(), proof_size.saturated_into()) + } + + fn length_to_fee(len: u32) -> BalanceOf { + TxPallet::::length_to_fee(len).into() + } + + fn deposit_txfee(credit: CreditOf) { + TxPallet::::deposit_txfee(credit) + } + + fn withdraw_txfee(amount: BalanceOf) -> Option> { + TxPallet::::withdraw_txfee(amount) + } + + fn remaining_txfee() -> BalanceOf { + TxPallet::::remaining_txfee() + } +} + +impl InfoT for () {} + +mod seal { + pub trait Sealed {} + impl Sealed for super::BlockRatioFee {} + impl Sealed for super::Info {} + impl Sealed for () {} +} diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs deleted file mode 100644 index 4ea822f306962..0000000000000 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ /dev/null @@ -1,216 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//! Encodes/Decodes EVM gas values. - -use crate::Weight; -use core::ops::{Div, Rem}; -use frame_support::pallet_prelude::CheckedShl; -use sp_arithmetic::traits::{One, Zero}; -use sp_core::U256; - -// We use 3 digits to store each component. -const SCALE: u128 = 100; - -/// Rounds up the given value to the nearest multiple of the mask. -/// -/// # Panics -/// Panics if the `mask` is zero. -fn round_up(value: T, mask: T) -> T -where - T: One + Zero + Copy + Rem + Div, - ::Output: PartialEq, -{ - let rest = if value % mask == T::zero() { T::zero() } else { T::one() }; - value / mask + rest -} - -/// Rounds up the log2 of the given value to the nearest integer. -fn log2_round_up(val: T) -> u128 -where - T: Into, -{ - let val = val.into(); - val.checked_ilog2() - .map(|v| if 1u128 << v == val { v } else { v + 1 }) - .unwrap_or(0) as u128 -} - -mod private { - pub trait Sealed {} - impl Sealed for () {} -} - -/// Encodes/Decodes EVM gas values. -/// -/// # Note -/// -/// This is defined as a trait rather than standalone functions to allow -/// it to be added as an associated type to [`crate::Config`]. This way, -/// it can be invoked without requiring the implementation bounds to be -/// explicitly specified. -/// -/// This trait is sealed and cannot be implemented by downstream crates. -pub trait GasEncoder: private::Sealed { - /// Encodes all components (deposit limit, weight reference time, and proof size) into a single - /// gas value. - fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256; - - /// Decodes the weight and deposit from the encoded gas value. - /// Returns `None` if the gas value is invalid - fn decode(gas: U256) -> Option<(Weight, Balance)>; - - /// Returns the encoded values of the specified weight and deposit. - fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) { - let encoded = Self::encode(U256::zero(), weight, deposit); - Self::decode(encoded).expect("encoded values should be decodable; qed") - } -} - -impl GasEncoder for () -where - Balance: Zero + One + CheckedShl + Into, -{ - /// The encoding follows the pattern `g...grrppdd`, where: - /// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. - /// - `pp`: log2 Proof size, encoded in the next 2 digits. - /// - `rr`: log2 Reference time, encoded in the next 2 digits. - /// - `g...g`: Gas limit, encoded in the highest digits. - /// - /// # Note - /// - The deposit value is maxed by 2^99 for u128 balance, and 2^63 for u64 balance. - fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 { - let deposit: u128 = deposit.into(); - let deposit_component = log2_round_up(deposit); - - let proof_size = weight.proof_size(); - let proof_size_component = SCALE * log2_round_up(proof_size); - - let ref_time = weight.ref_time(); - let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); - - let components = U256::from(deposit_component + proof_size_component + ref_time_component); - - let raw_gas_mask = U256::from(SCALE).pow(3.into()); - let raw_gas_component = if gas_limit <= components { - U256::zero() - } else { - round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) - }; - - components.saturating_add(raw_gas_component) - } - - fn decode(gas: U256) -> Option<(Weight, Balance)> { - let deposit = gas % SCALE; - - // Casting with as_u32 is safe since all values are maxed by `SCALE`. - let deposit = deposit.as_u32(); - let proof_time = ((gas / SCALE) % SCALE).as_u32(); - let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); - - let ref_weight = match ref_time { - 0 => 0, - 64 => u64::MAX, - _ => 1u64.checked_shl(ref_time)?, - }; - - let proof_weight = match proof_time { - 0 => 0, - 64 => u64::MAX, - _ => 1u64.checked_shl(proof_time)?, - }; - - let weight = Weight::from_parts(ref_weight, proof_weight); - - let deposit = match deposit { - 0 => Balance::zero(), - _ => Balance::one().checked_shl(deposit)?, - }; - - Some((weight, deposit)) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_gas_encoding_decoding_works() { - let raw_gas_limit = 111_111_999_999_999u128; - let weight = Weight::from_parts(222_999_999, 333_999_999); - let deposit = 444_999_999u64; - - let encoded_gas = <() as GasEncoder>::encode(raw_gas_limit.into(), weight, deposit); - assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); - assert!(encoded_gas > raw_gas_limit.into()); - - let (decoded_weight, decoded_deposit) = - <() as GasEncoder>::decode(encoded_gas).unwrap(); - assert!(decoded_weight.all_gte(weight)); - assert!(weight.mul(2).all_gte(weight)); - - assert!(decoded_deposit >= deposit); - assert!(deposit * 2 >= decoded_deposit); - - assert_eq!( - (decoded_weight, decoded_deposit), - <() as GasEncoder>::as_encoded_values(weight, deposit) - ); - } - - #[test] - fn test_encoding_zero_values_work() { - let encoded_gas = <() as GasEncoder>::encode( - Default::default(), - Default::default(), - Default::default(), - ); - - assert_eq!(encoded_gas, U256::from(0)); - - let (decoded_weight, decoded_deposit) = - <() as GasEncoder>::decode(encoded_gas).unwrap(); - assert_eq!(Weight::default(), decoded_weight); - assert_eq!(0u64, decoded_deposit); - - let encoded_gas = - <() as GasEncoder>::encode(U256::from(1), Default::default(), Default::default()); - assert_eq!(encoded_gas, U256::from(1000000)); - } - - #[test] - fn test_encoding_max_values_work() { - let max_weight = Weight::from_parts(u64::MAX, u64::MAX); - let max_deposit = 1u64 << 63; - let encoded_gas = - <() as GasEncoder>::encode(Default::default(), max_weight, max_deposit); - - assert_eq!(encoded_gas, U256::from(646463)); - - let (decoded_weight, decoded_deposit) = - <() as GasEncoder>::decode(encoded_gas).unwrap(); - assert_eq!(max_weight, decoded_weight); - assert_eq!(max_deposit, decoded_deposit); - } - - #[test] - fn test_overflow() { - assert_eq!(None, <() as GasEncoder>::decode(65_00u128.into()), "Invalid proof size"); - assert_eq!(None, <() as GasEncoder>::decode(65_00_00u128.into()), "Invalid ref_time"); - } -} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index b443dd07d89f6..9d34d65a7856a 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -18,45 +18,36 @@ use crate::{ evm::{ api::{GenericTransaction, TransactionSigned}, - GasEncoder, + create_call, + fees::InfoT, }, - vm::pvm::extract_code_and_data, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, OnChargeTransactionBalanceOf, Pallet, - LOG_TARGET, RUNTIME_PALLETS_ADDR, + AccountIdOf, AddressMapper, BalanceOf, CallOf, Config, Pallet, Zero, LOG_TARGET, }; use alloc::vec::Vec; -use codec::{Decode, DecodeLimit, DecodeWithMemTracking, Encode}; +use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, - traits::{InherentBuilder, IsSubType, SignedTransactionBuilder}, - MAX_EXTRINSIC_DEPTH, + traits::{ + fungible::Balanced, + tokens::{Fortitude, Precision, Preservation}, + InherentBuilder, IsSubType, SignedTransactionBuilder, + }, }; +use pallet_transaction_payment::Config as TxConfig; use scale_info::{StaticTypeInfo, TypeInfo}; -use sp_core::{Get, H256, U256}; +use sp_core::U256; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, - traits::{ - Checkable, Dispatchable, ExtrinsicCall, ExtrinsicLike, ExtrinsicMetadata, - TransactionExtension, - }, + traits::{Checkable, ExtrinsicCall, ExtrinsicLike, ExtrinsicMetadata, TransactionExtension}, transaction_validity::{InvalidTransaction, TransactionValidityError}, - OpaqueExtrinsic, RuntimeDebug, + OpaqueExtrinsic, RuntimeDebug, Weight, }; -type CallOf = ::RuntimeCall; - -/// The EVM gas price. -/// This constant is used by the proxy to advertise it via the eth_gas_price RPC. -/// -/// We use a fixed value for the gas price. -/// This let us calculate the gas estimate for a transaction with the formula: -/// `estimate_gas = substrate_fee / gas_price`. -/// -/// The chosen constant value is: -/// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the -/// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. -/// - Not too low, enabling users to adjust the gas price to define a tip. -pub(crate) const GAS_PRICE: u64 = 1_000_000u64; +/// Used to set the weight limit argument of a `eth_call` or `eth_instantiate_with_code` call. +pub trait SetWeightLimit { + /// Set the weight limit of this call. + fn set_weight_limit(&mut self, weight_limit: Weight); +} /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. @@ -125,13 +116,7 @@ where E: EthExtra, Self: Encode, ::Nonce: TryFrom, - ::RuntimeCall: Dispatchable, - OnChargeTransactionBalanceOf: Into>, - BalanceOf: Into + TryFrom, - MomentOf: Into, - CallOf: From> + IsSubType>, - ::Hash: frame_support::traits::IsType, - + CallOf: SetWeightLimit, // required by Checkable for `generic::UncheckedExtrinsic` generic::UncheckedExtrinsic, Signature, E::Extension>: Checkable< @@ -160,9 +145,8 @@ where } } -impl GetDispatchInfo for UncheckedExtrinsic -where - CallOf: GetDispatchInfo + Dispatchable, +impl GetDispatchInfo + for UncheckedExtrinsic { fn get_dispatch_info(&self) -> DispatchInfo { self.0.get_dispatch_info() @@ -197,7 +181,6 @@ impl SignedTransactionBuilder for UncheckedExtrinsic where Address: TypeInfo, - CallOf: TypeInfo, Signature: TypeInfo, E::Extension: TypeInfo, { @@ -218,7 +201,6 @@ where impl InherentBuilder for UncheckedExtrinsic where Address: TypeInfo, - CallOf: TypeInfo, Signature: TypeInfo, E::Extension: TypeInfo, { @@ -232,7 +214,6 @@ impl From: Encode, E::Extension: Encode, { fn from(extrinsic: UncheckedExtrinsic) -> Self { @@ -246,7 +227,7 @@ where /// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`]. pub trait EthExtra { /// The Runtime configuration. - type Config: Config + pallet_transaction_payment::Config; + type Config: Config + TxConfig; /// The Runtime's transaction extension. /// It should include at least: @@ -271,8 +252,6 @@ pub trait EthExtra { /// /// # Parameters /// - `payload`: The RLP-encoded Ethereum transaction. - /// - `gas_limit`: The gas limit for the extrinsic - /// - `storage_deposit_limit`: The storage deposit limit for the extrinsic, /// - `encoded_len`: The encoded length of the extrinsic. fn try_into_checked_extrinsic( payload: Vec, @@ -283,12 +262,7 @@ pub trait EthExtra { > where ::Nonce: TryFrom, - BalanceOf: Into + TryFrom, - MomentOf: Into, - ::RuntimeCall: Dispatchable, - OnChargeTransactionBalanceOf: Into>, - CallOf: From>, - ::Hash: frame_support::traits::IsType, + CallOf: SetWeightLimit, { let tx = TransactionSigned::decode(&payload).map_err(|err| { log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}"); @@ -316,120 +290,49 @@ pub trait EthExtra { log::debug!(target: LOG_TARGET, "Failed to recover signer: {err:?}"); InvalidTransaction::BadProof })?; - let signer = ::AddressMapper::to_fallback_account_id(&signer_addr); - let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = - GenericTransaction::from_signed(tx, crate::GAS_PRICE.into(), None); - - let Some(gas) = gas else { - log::debug!(target: LOG_TARGET, "No gas provided"); - return Err(InvalidTransaction::Call); - }; - - if chain_id.unwrap_or_default() != ::ChainId::get().into() { - log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); - return Err(InvalidTransaction::Call); - } - - let value = value.unwrap_or_default(); - let data = input.to_vec(); - - let (gas_limit, storage_deposit_limit) = - ::EthGasEncoder::decode(gas).ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); - InvalidTransaction::Call - })?; - - let call = if let Some(dest) = to { - if dest == RUNTIME_PALLETS_ADDR { - let call = CallOf::::decode_all_with_depth_limit( - MAX_EXTRINSIC_DEPTH, - &mut &data[..], - ) - .map_err(|_| { - log::debug!(target: LOG_TARGET, "Failed to decode data as Call"); - InvalidTransaction::Call - })?; - - if !value.is_zero() { - log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value"); - return Err(InvalidTransaction::Call) - } - - call - } else { - crate::Call::eth_call:: { - dest, - value, - gas_limit, - storage_deposit_limit, - data, - } - .into() - } - } else { - let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) { - let Some((code, data)) = extract_code_and_data(&data) else { - log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data"); - return Err(InvalidTransaction::Call); - }; - (code, data) - } else { - (data, Default::default()) - }; - - crate::Call::eth_instantiate_with_code:: { - value, - gas_limit, - storage_deposit_limit, - code, - data, - } - .into() - }; - - let mut info = call.get_dispatch_info(); - let nonce = nonce.unwrap_or_default().try_into().map_err(|_| { + let base_fee = >::evm_gas_price(); + let tx = GenericTransaction::from_signed(tx, base_fee, None); + let nonce = tx.nonce.unwrap_or_default().try_into().map_err(|_| { log::debug!(target: LOG_TARGET, "Failed to convert nonce"); InvalidTransaction::Call })?; - let gas_price = gas_price.unwrap_or_default(); - - let eth_fee = Pallet::::evm_gas_to_fee(gas, gas_price) - .map_err(|_| InvalidTransaction::Call)?; - - // Fees calculated from the extrinsic, without the tip. - info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&call); - let actual_fee: BalanceOf = - pallet_transaction_payment::Pallet::::compute_fee( - encoded_len as u32, - &info, - Default::default(), - ) - .into(); - log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: gas_price: {gas_price:?}, encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); - - // The fees from the Ethereum transaction should be greater or equal to the actual fees paid - // by the account. - if eth_fee < actual_fee { - log::debug!(target: LOG_TARGET, "eth fees {eth_fee:?} too low, actual fees: {actual_fee:?}"); - return Err(InvalidTransaction::Payment.into()) - } - - let tip = - Pallet::::evm_gas_to_fee(gas, gas_price.saturating_sub(GAS_PRICE.into())) - .unwrap_or_default() - .min(actual_fee); + let call_info = create_call::(tx, Some(encoded_len as u32))?; + let storage_credit = ::Currency::withdraw( + &signer, + call_info.storage_deposit, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ).map_err(|_| { + log::debug!(target: LOG_TARGET, "Not enough balance to hold additional storage deposit of {:?}", call_info.storage_deposit); + InvalidTransaction::Payment + })?; + ::FeeInfo::deposit_txfee(storage_credit); crate::tracing::if_tracing(|tracer| { tracer.watch_address(&Pallet::::block_author().unwrap_or_default()); tracer.watch_address(&signer_addr); }); - log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); + log::debug!(target: LOG_TARGET, "\ + Created checked Ethereum transaction with: \ + weight_limit={} \ + additional_storage_deposit_held={:?} \ + nonce={nonce:?} + ", + call_info.weight_limit, + call_info.storage_deposit, + ); + + // We can't calculate a tip because it needs to be based on the actual gas used which we + // cannot know pre-dispatch. Hence we never supply a tip here or it would be way too high. Ok(CheckedExtrinsic { - format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)), - function: call, + format: ExtrinsicFormat::Signed( + signer.into(), + Self::get_eth_extension(nonce, Zero::zero()), + ), + function: call_info.call, }) } } @@ -440,40 +343,25 @@ mod test { use crate::{ evm::*, test_utils::*, - tests::{ExtBuilder, RuntimeCall, RuntimeOrigin, Test}, - Weight, + tests::{ + Address, ExtBuilder, RuntimeCall, RuntimeOrigin, SignedExtra, Test, UncheckedExtrinsic, + }, + EthTransactInfo, Weight, RUNTIME_PALLETS_ADDR, }; use frame_support::{error::LookupError, traits::fungible::Mutate}; use pallet_revive_fixtures::compile_module; - use sp_runtime::{ - traits::{self, Checkable, DispatchTransaction}, - MultiAddress, MultiSignature, - }; - type AccountIdOf = ::AccountId; + use sp_runtime::traits::{self, Checkable, DispatchTransaction, Get}; - #[derive(Clone, PartialEq, Eq, Debug)] - pub struct Extra; - type SignedExtra = (frame_system::CheckNonce, ChargeTransactionPayment); - - use pallet_transaction_payment::ChargeTransactionPayment; - impl EthExtra for Extra { - type Config = Test; - type Extension = SignedExtra; - - fn get_eth_extension(nonce: u32, tip: BalanceOf) -> Self::Extension { - (frame_system::CheckNonce::from(nonce), ChargeTransactionPayment::from(tip)) - } - } + type AccountIdOf = ::AccountId; - type Ex = UncheckedExtrinsic, MultiSignature, Extra>; struct TestContext; impl traits::Lookup for TestContext { - type Source = MultiAddress; + type Source = Address; type Target = AccountIdOf; fn lookup(&self, s: Self::Source) -> Result { match s { - MultiAddress::Id(id) => Ok(id), + Self::Source::Id(id) => Ok(id), _ => Err(LookupError), } } @@ -484,6 +372,7 @@ mod test { struct UncheckedExtrinsicBuilder { tx: GenericTransaction, before_validate: Option>, + dry_run: Option>>, } impl UncheckedExtrinsicBuilder { @@ -493,10 +382,10 @@ mod test { tx: GenericTransaction { from: Some(Account::default().address()), chain_id: Some(::ChainId::get().into()), - gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, before_validate: None, + dry_run: None, } } @@ -505,29 +394,24 @@ mod test { self } - fn estimate_gas(&mut self) { - let dry_run = crate::Pallet::::dry_run_eth_transact( - self.tx.clone(), - Weight::MAX, - |eth_call, dispatch_call| { - let mut info = dispatch_call.get_dispatch_info(); - - info.extension_weight = - Extra::get_eth_extension(0, 0u32.into()).weight(&dispatch_call); - let uxt: Ex = - sp_runtime::generic::UncheckedExtrinsic::new_bare(eth_call).into(); - pallet_transaction_payment::Pallet::::compute_fee( - uxt.encoded_size() as u32, - &info, - Default::default(), - ) - }, + fn fund_account(account: &Account) { + let _ = ::Currency::set_balance( + &account.substrate_account(), + 100_000_000_000_000, ); + } + + fn estimate_gas(&mut self) { + let account = Account::default(); + Self::fund_account(&account); + + let dry_run = crate::Pallet::::dry_run_eth_transact(self.tx.clone()); + self.tx.gas_price = Some(>::evm_gas_price()); match dry_run { Ok(dry_run) => { - log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); self.tx.gas = Some(dry_run.eth_gas); + self.dry_run = Some(dry_run); }, Err(err) => { log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err); @@ -557,7 +441,10 @@ mod test { fn check( self, - ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + ) -> Result< + (u32, RuntimeCall, SignedExtra, GenericTransaction, Weight), + TransactionValidityError, + > { self.mutate_estimate_and_check(Box::new(|_| ())) } @@ -565,26 +452,26 @@ mod test { fn mutate_estimate_and_check( mut self, f: Box ()>, - ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + ) -> Result< + (u32, RuntimeCall, SignedExtra, GenericTransaction, Weight), + TransactionValidityError, + > { ExtBuilder::default().build().execute_with(|| self.estimate_gas()); - f(&mut self.tx); ExtBuilder::default().build().execute_with(|| { + f(&mut self.tx); let UncheckedExtrinsicBuilder { tx, before_validate, .. } = self.clone(); // Fund the account. let account = Account::default(); - let _ = ::Currency::set_balance( - &account.substrate_account(), - 100_000_000_000_000, - ); + Self::fund_account(&account); let payload = account .sign_transaction(tx.clone().try_into_unsigned().unwrap()) .signed_payload(); let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload }); - let encoded_len = call.encoded_size(); - let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); + let uxt: UncheckedExtrinsic = generic::UncheckedExtrinsic::new_bare(call).into(); + let encoded_len = uxt.encoded_size(); let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; let (account_id, extra): (AccountId32, SignedExtra) = match result.format { ExtrinsicFormat::Signed(signer, extra) => (signer, extra), @@ -600,7 +487,13 @@ mod test { 0, )?; - Ok((result.function, extra, tx)) + Ok(( + encoded_len as u32, + result.function, + extra, + tx, + self.dry_run.unwrap().gas_required, + )) }) } } @@ -608,43 +501,69 @@ mod test { #[test] fn check_eth_transact_call_works() { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - let (call, _, tx) = builder.check().unwrap(); - let (gas_limit, storage_deposit_limit) = - <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + let (expected_encoded_len, call, _, tx, gas_required) = builder.check().unwrap(); + let expected_effective_gas_price: u32 = ::NativeToEthRatio::get(); - assert_eq!( - call, - crate::Call::eth_call:: { - dest: tx.to.unwrap(), - value: tx.value.unwrap_or_default().as_u64().into(), - data: tx.input.to_vec(), + match call { + RuntimeCall::Contracts(crate::Call::eth_call:: { + dest, + value, + data, gas_limit, - storage_deposit_limit - } - .into() - ); + storage_deposit_limit, + effective_gas_price, + encoded_len, + }) if dest == tx.to.unwrap() && + value == tx.value.unwrap_or_default().as_u64().into() && + data == tx.input.to_vec() && + storage_deposit_limit == >::max_value() && + effective_gas_price == expected_effective_gas_price.into() => + { + assert_eq!(encoded_len, expected_encoded_len); + assert!( + gas_limit.all_gte(gas_required), + "Assert failed: gas_limit={gas_limit:?} >= gas_required={gas_required:?}" + ); + }, + _ => panic!("Call does not match."), + } } #[test] fn check_eth_transact_instantiate_works() { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - let (call, _, tx) = builder.check().unwrap(); - let (gas_limit, storage_deposit_limit) = - <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + let (expected_code, _) = compile_module("dummy").unwrap(); + let expected_data = vec![]; + let builder = UncheckedExtrinsicBuilder::instantiate_with( + expected_code.clone(), + expected_data.clone(), + ); + let (expected_encoded_len, call, _, tx, gas_required) = builder.check().unwrap(); + let expected_effective_gas_price: u32 = ::NativeToEthRatio::get(); + let expected_value = tx.value.unwrap_or_default().as_u64().into(); - assert_eq!( - call, - crate::Call::eth_instantiate_with_code:: { - value: tx.value.unwrap_or_default().as_u64().into(), + match call { + RuntimeCall::Contracts(crate::Call::eth_instantiate_with_code:: { + value, code, data, gas_limit, - storage_deposit_limit - } - .into() - ); + storage_deposit_limit, + effective_gas_price, + encoded_len, + }) if value == expected_value && + code == expected_code && + data == expected_data && + storage_deposit_limit == >::max_value() && + effective_gas_price == expected_effective_gas_price.into() => + { + assert_eq!(encoded_len, expected_encoded_len); + assert!( + gas_limit.all_gte(gas_required), + "Assert failed: gas_limit={gas_limit:?} >= gas_required={gas_required:?}" + ); + }, + _ => panic!("Call does not match."), + } } #[test] @@ -726,17 +645,15 @@ mod test { let (code, _) = compile_module("dummy").unwrap(); // create some dummy data to increase the gas fee let data = vec![42u8; crate::limits::CALLDATA_BYTES as usize]; - let (_, extra, tx) = + let (_, _, extra, _tx, _gas_required) = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) .mutate_estimate_and_check(Box::new(|tx| { tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); })) .unwrap(); - let diff = tx.gas_price.unwrap() - U256::from(GAS_PRICE); - let expected_tip = crate::Pallet::::evm_gas_to_fee(tx.gas.unwrap(), diff).unwrap(); - assert_eq!(extra.1.tip(), expected_tip); + assert_eq!(U256::from(extra.1.tip()), 0u32.into()); } #[test] @@ -746,7 +663,7 @@ mod test { let builder = UncheckedExtrinsicBuilder::call_with(RUNTIME_PALLETS_ADDR).data(remark.encode()); - let (call, _, _) = builder.check().unwrap(); + let (_, call, _, _, _) = builder.check().unwrap(); assert_eq!(call, remark); } diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index dc235ede4784e..9a28c0c6b293d 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -17,9 +17,9 @@ use crate::{ evm::{CallTrace, Trace}, tracing::Tracing, - BalanceOf, Bounded, Config, MomentOf, Weight, + Config, Weight, }; -use sp_core::{H256, U256}; +use sp_core::U256; mod call_tracing; pub use call_tracing::*; @@ -38,9 +38,6 @@ pub enum Tracer { impl Tracer where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, T::Nonce: Into, { /// Returns an empty trace. diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index d3f2157775857..e6bdf843be223 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -17,11 +17,10 @@ use crate::{ evm::{Bytes, PrestateTrace, PrestateTraceInfo, PrestateTracerConfig}, tracing::Tracing, - AccountInfo, BalanceOf, Bounded, Code, Config, ExecReturnValue, Key, MomentOf, Pallet, - PristineCode, Weight, + AccountInfo, Code, Config, ExecReturnValue, Key, Pallet, PristineCode, Weight, }; use alloc::{collections::BTreeMap, vec::Vec}; -use sp_core::{H160, H256, U256}; +use sp_core::{H160, U256}; /// A tracer that traces the prestate. #[derive(frame_support::DefaultNoBound, Debug, Clone, PartialEq)] @@ -43,9 +42,6 @@ pub struct PrestateTracer { impl PrestateTracer where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, T::Nonce: Into, { /// Create a new [`PrestateTracer`] instance. @@ -132,9 +128,6 @@ where impl PrestateTracer where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, T::Nonce: Into, { /// Get the code of the contract. @@ -165,9 +158,6 @@ where impl Tracing for PrestateTracer where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, T::Nonce: Into, { fn watch_address(&mut self, addr: &H160) { diff --git a/substrate/frame/revive/src/evm/tx_extension.rs b/substrate/frame/revive/src/evm/tx_extension.rs new file mode 100644 index 0000000000000..607d8a76c10e5 --- /dev/null +++ b/substrate/frame/revive/src/evm/tx_extension.rs @@ -0,0 +1,100 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains transaction extensions needed for ethereum compatability. + +use crate::{CallOf, Config, Origin, OriginFor}; +use codec::{Decode, DecodeWithMemTracking, Encode}; +use frame_support::{ + pallet_prelude::{InvalidTransaction, TransactionSource}, + DebugNoBound, DefaultNoBound, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + impl_tx_ext_default, + traits::{DispatchInfoOf, TransactionExtension, ValidateResult}, + Weight, +}; + +/// An extension that sets the origin to [`Origin::EthTransaction`] in case it originated from an +/// eth transaction. +/// +/// This extension needs to be put behind any other extension that relies on a signed origin. +#[derive( + Encode, + Decode, + DecodeWithMemTracking, + Clone, + Eq, + PartialEq, + DefaultNoBound, + TypeInfo, + DebugNoBound, +)] +#[scale_info(skip_type_params(T))] +pub struct SetOrigin { + /// Skipped as can only be set by runtime code. + #[codec(skip)] + is_eth_transaction: bool, + _phantom: core::marker::PhantomData, +} + +impl SetOrigin { + /// Create the extension so that it will transform the origin. + /// + /// If the extension is default constructed it will do nothing. + pub fn new_from_eth_transaction() -> Self { + Self { is_eth_transaction: true, _phantom: Default::default() } + } +} + +impl TransactionExtension> for SetOrigin +where + T: Config + Send + Sync, + OriginFor: From>, +{ + const IDENTIFIER: &'static str = "EthSetOrigin"; + type Implicit = (); + type Pre = (); + type Val = (); + + fn weight(&self, _: &CallOf) -> Weight { + Default::default() + } + + fn validate( + &self, + origin: OriginFor, + _call: &CallOf, + _info: &DispatchInfoOf>, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> ValidateResult> { + let origin = if self.is_eth_transaction { + let signer = + frame_system::ensure_signed(origin).map_err(|_| InvalidTransaction::BadProof)?; + Origin::EthTransaction(signer).into() + } else { + origin + }; + Ok((Default::default(), Default::default(), origin)) + } + + impl_tx_ext_default!(CallOf; prepare); +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 6547a15386439..15217c4ae8fdc 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -20,7 +20,7 @@ use crate::{ gas::GasMeter, limits, precompiles::{All as AllPrecompiles, Instance as PrecompileInstance, Precompiles}, - primitives::{BumpNonce, ExecReturnValue, StorageDeposit}, + primitives::{ExecConfig, ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, AccountIdOrAddress, WriteOutcome}, tracing::if_tracing, @@ -54,7 +54,7 @@ use sp_core::{ }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ - traits::{BadOrigin, Bounded, Convert, Saturating, Zero}, + traits::{BadOrigin, Bounded, Saturating, TrailingZeroInput, Zero}, DispatchError, SaturatedConversion, }; @@ -399,9 +399,6 @@ pub trait PrecompileExt: sealing::Sealed { /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; - /// Returns the price for the specified amount of weight. - fn get_weight_price(&self, weight: Weight) -> U256; - /// Get an immutable reference to the nested gas meter. fn gas_meter(&self) -> &GasMeter; @@ -444,6 +441,9 @@ pub trait PrecompileExt: sealing::Sealed { /// - If `code_offset + buf.len()` extends beyond code: Available code copied, remaining bytes /// are filled with zeros fn copy_code_slice(&mut self, buf: &mut [u8], address: &H160, code_offset: usize); + + /// Returns the effective gas price of this transaction. + fn effective_gas_price(&self) -> U256; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -535,9 +535,8 @@ pub struct Stack<'a, T: Config, E> { first_frame: Frame, /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, - /// Whether or not actual transfer of funds should be performed. - /// This is set to `true` exclusively when we simulate a call through eth_transact. - skip_transfer: bool, + /// Global behavior determined by the creater of this stack. + exec_config: &'a ExecConfig, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } @@ -773,10 +772,7 @@ impl CachedContract { impl<'a, T, E> Stack<'a, T, E> where T: Config, - BalanceOf: Into + TryFrom, - MomentOf: Into, E: Executable, - T::Hash: frame_support::traits::IsType, { /// Create and run a new call stack by calling into `dest`. /// @@ -790,7 +786,7 @@ where storage_meter: &mut storage::meter::Meter, value: U256, input_data: Vec, - skip_transfer: bool, + exec_config: &ExecConfig, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); if let Some((mut stack, executable)) = Stack::<'_, T, E>::new( @@ -799,11 +795,9 @@ where gas_meter, storage_meter, value, - skip_transfer, + exec_config, )? { - stack - .run(executable, input_data, BumpNonce::Yes) - .map(|_| stack.first_frame.last_frame_output) + stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { if_tracing(|t| { t.enter_child_span( @@ -817,13 +811,22 @@ where ); }); - let result = Self::transfer_from_origin(&origin, &origin, &dest, value, storage_meter); + let result = Self::transfer_from_origin( + &origin, + &origin, + &dest, + value, + storage_meter, + exec_config, + ); if_tracing(|t| match result { Ok(ref output) => t.exit_child_span(&output, Weight::zero()), Err(e) => t.exit_child_span_with_error(e.error.into(), Weight::zero()), }); + log::trace!(target: LOG_TARGET, "call finished with: {result:?}"); + result } } @@ -841,8 +844,7 @@ where value: U256, input_data: Vec, salt: Option<&[u8; 32]>, - skip_transfer: bool, - bump_nonce: BumpNonce, + exec_config: &ExecConfig, ) -> Result<(H160, ExecReturnValue), ExecError> { let deployer = T::AddressMapper::to_address(&origin); let (mut stack, executable) = Stack::<'_, T, E>::new( @@ -856,18 +858,19 @@ where gas_meter, storage_meter, value, - skip_transfer, + exec_config, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); let result = stack - .run(executable, input_data, bump_nonce) + .run(executable, input_data) .map(|_| (address, stack.first_frame.last_frame_output)); if let Ok((contract, ref output)) = result { if !output.did_revert() { Contracts::::deposit_event(Event::Instantiated { deployer, contract }); } } + log::trace!(target: LOG_TARGET, "instantiate finished with: {result:?}"); result } @@ -878,6 +881,7 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: BalanceOf, + exec_config: &'a ExecConfig, ) -> (Self, E) { let call = Self::new( FrameArgs::Call { @@ -889,7 +893,7 @@ where gas_meter, storage_meter, value.into(), - false, + exec_config, ) .unwrap() .unwrap(); @@ -906,7 +910,7 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: U256, - skip_transfer: bool, + exec_config: &'a ExecConfig, ) -> Result)>, ExecError> { origin.ensure_mapped()?; let Some((first_frame, executable)) = Self::new_frame( @@ -932,7 +936,7 @@ where first_frame, frames: Default::default(), transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), - skip_transfer, + exec_config, _phantom: Default::default(), }; @@ -1119,7 +1123,6 @@ where &mut self, executable: ExecutableOrPrecompile, input_data: Vec, - bump_nonce: BumpNonce, ) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; @@ -1153,7 +1156,8 @@ where let do_transaction = || -> ExecResult { let caller = self.caller(); - let skip_transfer = self.skip_transfer; + let is_first_frame = self.frames.len() == 0; + let bump_nonce = self.exec_config.bump_nonce; let frame = top_frame_mut!(self); let account_id = &frame.account_id.clone(); @@ -1173,12 +1177,8 @@ where let ed = >::min_balance(); frame.nested_storage.record_charge(&StorageDeposit::Charge(ed))?; - if self.skip_transfer { - T::Currency::set_balance(account_id, ed); - } else { - T::Currency::transfer(origin, account_id, ed, Preservation::Preserve) - .map_err(|_| >::StorageDepositNotEnoughFunds)?; - } + >::charge_deposit(None, origin, account_id, ed, self.exec_config) + .map_err(|_| >::StorageDepositNotEnoughFunds)?; // A consumer is added at account creation and removed it on termination, otherwise // the runtime could remove the account. As long as a contract exists its @@ -1189,7 +1189,7 @@ where // Contracts nonce starts at 1 >::inc_account_nonce(account_id); - if matches!(bump_nonce, BumpNonce::Yes) { + if bump_nonce || !is_first_frame { // Needs to be incremented before calling into the code so that it is visible // in case of recursion. >::inc_account_nonce(caller.account_id()?); @@ -1215,6 +1215,7 @@ where account_id, frame.value_transferred, &mut frame.nested_storage, + self.exec_config, )?; } @@ -1268,8 +1269,7 @@ where // Hence we need to delay charging the base deposit after execution. let frame = if entry_point == ExportedFunction::Constructor { let origin = self.origin.account_id()?.clone(); - let frame = self.top_frame_mut(); - let contract_info = frame.contract_info(); + let frame = top_frame_mut!(self); // if we are dealing with EVM bytecode // We upload the new runtime code, and update the code if !is_pvm { @@ -1281,14 +1281,15 @@ where }; let mut module = crate::ContractBlob::::from_evm_runtime_code(data, origin)?; - module.store_code(skip_transfer)?; + module.store_code(&self.exec_config, Some(&mut frame.nested_storage))?; code_deposit = module.code_info().deposit(); - contract_info.code_hash = *module.code_hash(); + let contract_info = frame.contract_info(); + contract_info.code_hash = *module.code_hash(); >::increment_refcount(contract_info.code_hash)?; } - let deposit = contract_info.update_base_deposit(code_deposit); + let deposit = frame.contract_info().update_base_deposit(code_deposit); frame .nested_storage .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); @@ -1458,6 +1459,7 @@ where to: &T::AccountId, value: U256, storage_meter: &mut storage::meter::GenericMeter, + exec_config: &ExecConfig, ) -> DispatchResult { fn transfer_with_dust( from: &AccountIdOf, @@ -1550,8 +1552,9 @@ where match storage_meter .record_charge(&StorageDeposit::Charge(ed)) .and_then(|_| { - T::Currency::transfer(origin, to, ed, Preservation::Preserve) - .map_err(|_| Error::::StorageDepositNotEnoughFunds.into()) + >::charge_deposit(None, origin, to, ed, exec_config) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + Ok(()) }) .and_then(|_| transfer_with_dust::(from, to, value)) { @@ -1568,6 +1571,7 @@ where to: &T::AccountId, value: U256, storage_meter: &mut storage::meter::GenericMeter, + exec_config: &ExecConfig, ) -> ExecResult { // If the from address is root there is no account to transfer from, and therefore we can't // take any `value` other than 0. @@ -1576,7 +1580,7 @@ where Origin::Root if value.is_zero() => return Ok(Default::default()), Origin::Root => return Err(DispatchError::RootNotAllowed.into()), }; - Self::transfer(origin, from, to, value, storage_meter) + Self::transfer(origin, from, to, value, storage_meter, exec_config) .map(|_| Default::default()) .map_err(Into::into) } @@ -1642,7 +1646,8 @@ where if block_number < self.block_number.saturating_sub(256u32.into()) { return None; } - Some(System::::block_hash(&block_number).into()) + let block_hash = System::::block_hash(&block_number); + Decode::decode(&mut TrailingZeroInput::new(block_hash.as_ref())).ok() } } @@ -1650,9 +1655,6 @@ impl<'a, T, E> Ext for Stack<'a, T, E> where T: Config, E: Executable, - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, { fn delegate_call( &mut self, @@ -1683,7 +1685,7 @@ where deposit_limit.saturated_into::>(), self.is_read_only(), )? { - self.run(executable, input_data, BumpNonce::Yes) + self.run(executable, input_data) } else { // Delegate-calls to non-contract accounts are considered success. Ok(()) @@ -1786,9 +1788,6 @@ impl<'a, T, E> PrecompileWithInfoExt for Stack<'a, T, E> where T: Config, E: Executable, - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, { fn get_storage(&mut self, key: &Key) -> Option> { self.top_frame_mut().contract_info().read(key) @@ -1857,7 +1856,7 @@ where let executable = executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&self.top_frame().account_id); if_tracing(|t| t.instantiate_code(&code, salt)); - self.run(executable, input_data, BumpNonce::Yes).map(|_| address) + self.run(executable, input_data).map(|_| address) } } @@ -1865,9 +1864,6 @@ impl<'a, T, E> PrecompileExt for Stack<'a, T, E> where T: Config, E: Executable, - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, { type T = T; @@ -1923,7 +1919,7 @@ where deposit_limit.saturated_into::>(), is_read_only, )? { - self.run(executable, input_data, BumpNonce::Yes) + self.run(executable, input_data) } else { if_tracing(|t| { t.enter_child_span( @@ -1950,6 +1946,7 @@ where &dest, value, &mut frame.nested_storage, + self.exec_config, ) }; @@ -2121,10 +2118,6 @@ where limits::PAYLOAD_BYTES } - fn get_weight_price(&self, weight: Weight) -> U256 { - T::WeightPrice::convert(weight).into() - } - fn gas_meter(&self) -> &GasMeter { &self.top_frame().nested_gas } @@ -2187,15 +2180,16 @@ where buf[len..].fill(0); } + + fn effective_gas_price(&self) -> U256 { + self.exec_config + .effective_gas_price + .unwrap_or_else(|| >::evm_gas_price()) + } } /// Returns true if the address has a precompile contract, else false. -pub fn is_precompile>(address: &H160) -> bool -where - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, -{ +pub fn is_precompile>(address: &H160) -> bool { >::get::>(address.as_fixed_bytes()).is_some() } diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index 91a3e1c1c43f5..d9af8d9eb9b8c 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -156,10 +156,6 @@ impl PrecompileExt for MockExt { panic!("MockExt::max_value_size") } - fn get_weight_price(&self, _weight: Weight) -> U256 { - panic!("MockExt::get_weight_price") - } - fn gas_meter(&self) -> &GasMeter { &self.gas_meter } @@ -213,6 +209,10 @@ impl PrecompileExt for MockExt { fn to_account_id(&self, _address: &H160) -> AccountIdOf { panic!("MockExt::to_account_id") } + + fn effective_gas_price(&self) -> U256 { + panic!("MockExt::effective_gas_price") + } } impl PrecompileWithInfoExt for MockExt { diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index 3be69bc619cc6..88d06265dfbb6 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -225,7 +225,7 @@ fn it_works() { &mut storage_meter, value.into(), vec![], - false, + &ExecConfig::new_substrate_tx(), ), Ok(_) ); @@ -252,6 +252,7 @@ fn transfer_works() { &BOB, Pallet::::convert_native_to_evm(value), &mut storage_meter, + &ExecConfig::new_substrate_tx(), ) .unwrap(); @@ -260,7 +261,9 @@ fn transfer_works() { assert_eq!(get_balance(&ALICE), 100 - value - min_balance); assert_eq!(get_balance(&BOB), min_balance + value); assert_eq!( - storage_meter.try_into_deposit(&Origin::from_account_id(ALICE), false).unwrap(), + storage_meter + .try_into_deposit(&Origin::from_account_id(ALICE), &ExecConfig::new_substrate_tx()) + .unwrap(), StorageDeposit::Charge(min_balance) ); }); @@ -287,6 +290,7 @@ fn transfer_to_nonexistent_account_works() { &CHARLIE, evm_value, &mut storage_meter, + &ExecConfig::new_substrate_tx(), )); assert_eq!(get_balance(&ALICE), ed); assert_eq!(get_balance(&BOB), ed); @@ -301,7 +305,8 @@ fn transfer_to_nonexistent_account_works() { &BOB, &DJANGO, evm_value, - &mut storage_meter + &mut storage_meter, + &ExecConfig::new_substrate_tx(), ), >::StorageDepositNotEnoughFunds, ); @@ -315,7 +320,8 @@ fn transfer_to_nonexistent_account_works() { &BOB, &EVE, evm_value, - &mut storage_meter + &mut storage_meter, + &ExecConfig::new_substrate_tx(), ), >::TransferFailed ); @@ -348,7 +354,7 @@ fn correct_transfer_on_call() { &mut storage_meter, evm_value.as_u64().into(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap(); @@ -388,7 +394,7 @@ fn correct_transfer_on_delegate_call() { &mut storage_meter, evm_value.as_u64().into(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); assert_eq!(get_balance(&ALICE), 100 - value); @@ -422,7 +428,7 @@ fn delegate_call_missing_contract() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); // add missing contract code @@ -434,7 +440,7 @@ fn delegate_call_missing_contract() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -462,7 +468,7 @@ fn changes_are_reverted_on_failing_call() { &mut storage_meter, 55u64.into(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap(); @@ -491,6 +497,7 @@ fn balance_too_low() { &dest, Pallet::::convert_native_to_evm(100u64).as_u64().into(), &mut storage_meter, + &ExecConfig::new_substrate_tx(), ); assert_eq!(result, Err(Error::::TransferFailed.into())); @@ -520,7 +527,7 @@ fn output_is_returned_on_success() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); let output = result.unwrap(); @@ -549,7 +556,7 @@ fn output_is_returned_on_failure() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); let output = result.unwrap(); @@ -578,7 +585,7 @@ fn input_data_to_call() { &mut storage_meter, U256::zero(), vec![1, 2, 3, 4], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -610,8 +617,7 @@ fn input_data_to_instantiate() { min_balance.into(), vec![1, 2, 3, 4], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -665,7 +671,7 @@ fn max_depth() { &mut storage_meter, value.into(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); @@ -727,7 +733,7 @@ fn caller_returns_proper_values() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); @@ -790,7 +796,7 @@ fn origin_returns_proper_values() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); @@ -828,7 +834,7 @@ fn to_account_id_returns_proper_values() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -865,7 +871,7 @@ fn code_hash_returns_proper_values() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -891,7 +897,7 @@ fn own_code_hash_returns_proper_values() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -927,7 +933,7 @@ fn caller_is_origin_returns_proper_values() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -953,7 +959,7 @@ fn root_caller_succeeds() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -979,7 +985,7 @@ fn root_caller_does_not_succeed_when_value_not_zero() { &mut storage_meter, 1u64.into(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Err(_)); }); @@ -1015,7 +1021,7 @@ fn root_caller_succeeds_with_consecutive_calls() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -1060,7 +1066,7 @@ fn address_returns_proper_values() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); @@ -1085,8 +1091,7 @@ fn refuse_instantiate_with_value_below_existential_deposit() { U256::zero(), // <- zero value vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ), Err(_) ); @@ -1119,8 +1124,7 @@ fn instantiation_work_with_success_output() { Pallet::::convert_native_to_evm(min_balance), vec![], Some(&[0 ;32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); @@ -1171,8 +1175,7 @@ fn instantiation_fails_with_failing_output() { Pallet::::convert_native_to_evm(min_balance), vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); @@ -1234,7 +1237,7 @@ fn instantiation_from_contract() { &mut storage_meter, Pallet::::convert_native_to_evm(min_balance * 10), vec![], - false, + &ExecConfig::new_substrate_tx(), ), Ok(_) ); @@ -1303,7 +1306,7 @@ fn instantiation_traps() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ), Ok(_) ); @@ -1336,8 +1339,7 @@ fn termination_from_instantiate_fails() { Pallet::::convert_native_to_evm(100u64), vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ), Err(ExecError { error: Error::::TerminatedInConstructor.into(), @@ -1404,7 +1406,7 @@ fn in_memory_changes_not_discarded() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -1463,8 +1465,7 @@ fn recursive_call_during_constructor_is_balance_transfer() { 10u64.into(), vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -1510,7 +1511,7 @@ fn cannot_send_more_balance_than_available_to_self() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap(); }); @@ -1542,7 +1543,7 @@ fn call_reentry_direct_recursion() { &mut storage_meter, U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), - false, + &ExecConfig::new_substrate_tx(), )); // Calling into oneself fails @@ -1554,7 +1555,7 @@ fn call_reentry_direct_recursion() { &mut storage_meter, U256::zero(), BOB_ADDR.as_bytes().to_vec(), - false, + &ExecConfig::new_substrate_tx(), ) .map_err(|e| e.error), >::ReentranceDenied, @@ -1604,7 +1605,7 @@ fn call_deny_reentry() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ) .map_err(|e| e.error), >::ReentranceDenied, @@ -1642,8 +1643,7 @@ fn minimum_balance_must_return_converted_balance() { min_balance_evm_value, vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), )); }); } @@ -1728,8 +1728,7 @@ fn nonce() { min_balance_evm_value * 100, vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ) .ok(); assert_eq!(System::account_nonce(&ALICE), 0); @@ -1742,8 +1741,7 @@ fn nonce() { min_balance_evm_value * 100, vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -1755,8 +1753,7 @@ fn nonce() { min_balance_evm_value * 200, vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -1768,8 +1765,7 @@ fn nonce() { min_balance_evm_value * 200, vec![], Some(&[0; 32]), - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), )); assert_eq!(System::account_nonce(&ALICE), 3); }); @@ -1836,7 +1832,7 @@ fn set_storage_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -1934,7 +1930,7 @@ fn set_storage_varsized_key_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -1972,7 +1968,7 @@ fn get_storage_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -2010,7 +2006,7 @@ fn get_storage_size_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -2059,7 +2055,7 @@ fn get_storage_varsized_key_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -2108,7 +2104,7 @@ fn get_storage_size_varsized_key_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -2182,7 +2178,7 @@ fn set_transient_storage_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -2252,7 +2248,7 @@ fn get_transient_storage_works() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -2290,7 +2286,7 @@ fn get_transient_storage_size_works() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), )); }); } @@ -2352,7 +2348,7 @@ fn rollback_transient_storage_works() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -2383,7 +2379,7 @@ fn ecdsa_to_eth_address_returns_proper_value() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -2481,7 +2477,7 @@ fn last_frame_output_works_on_instantiate() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap() }); @@ -2549,7 +2545,7 @@ fn last_frame_output_works_on_nested_call() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -2617,7 +2613,7 @@ fn last_frame_output_is_always_reset() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ); assert_matches!(result, Ok(_)); }); @@ -2666,7 +2662,7 @@ fn immutable_data_access_checks_work() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap() }); @@ -2735,7 +2731,7 @@ fn correct_immutable_data_in_delegate_call() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap() }); @@ -2774,8 +2770,7 @@ fn immutable_data_set_overrides() { U256::zero(), vec![], None, - false, - BumpNonce::Yes, + &ExecConfig::new_substrate_tx(), ) .unwrap() .0; @@ -2787,7 +2782,7 @@ fn immutable_data_set_overrides() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap() }); @@ -2833,7 +2828,7 @@ fn immutable_data_set_errors_with_empty_data() { &mut storage_meter, U256::zero(), vec![], - false, + &ExecConfig::new_substrate_tx(), ) .unwrap() }); @@ -2888,7 +2883,7 @@ fn block_hash_returns_proper_values() { &mut storage_meter, U256::zero(), vec![0], - false, + &ExecConfig::new_substrate_tx(), ), Ok(_) ); diff --git a/substrate/frame/revive/src/impl_fungibles.rs b/substrate/frame/revive/src/impl_fungibles.rs index 404690c6765b4..50450ba430cf2 100644 --- a/substrate/frame/revive/src/impl_fungibles.rs +++ b/substrate/frame/revive/src/impl_fungibles.rs @@ -24,6 +24,7 @@ #![cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +use crate::OriginFor; use alloy_core::{ primitives::{Address, U256 as EU256}, sol_types::*, @@ -38,13 +39,10 @@ use frame_support::{ }, PalletId, }; -use sp_core::{H160, H256, U256}; +use sp_core::{H160, U256}; use sp_runtime::{traits::AccountIdConversion, DispatchError}; -use super::{ - address::AddressMapper, pallet, BalanceOf, Bounded, Config, ContractResult, DepositLimit, - MomentOf, Pallet, Weight, -}; +use super::{address::AddressMapper, pallet, Config, ContractResult, ExecConfig, Pallet, Weight}; use ethereum_standards::IERC20; const GAS_LIMIT: Weight = Weight::from_parts(1_000_000_000, 100_000); @@ -58,12 +56,7 @@ impl Pallet { } } -impl fungibles::Inspect<::AccountId> for Pallet -where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, -{ +impl fungibles::Inspect<::AccountId> for Pallet { // The asset id of an ERC20 is its origin contract's address. type AssetId = H160; // The balance is always u128. @@ -73,14 +66,13 @@ where fn total_issuance(asset_id: Self::AssetId) -> Self::Balance { let data = IERC20::totalSupplyCall {}.abi_encode(); let ContractResult { result, .. } = Self::bare_call( - T::RuntimeOrigin::signed(Self::checking_account()), + OriginFor::::signed(Self::checking_account()), asset_id, U256::zero(), GAS_LIMIT, - DepositLimit::Balance( - <::Currency as fungible::Inspect<_>>::total_issuance(), - ), + <::Currency as fungible::Inspect<_>>::total_issuance(), data, + ExecConfig::new_substrate_tx(), ); if let Ok(return_value) = result { if let Ok(eu256) = EU256::abi_decode_validate(&return_value.data) { @@ -109,14 +101,13 @@ where let address = Address::from(Into::<[u8; 20]>::into(eth_address)); let data = IERC20::balanceOfCall { account: address }.abi_encode(); let ContractResult { result, .. } = Self::bare_call( - T::RuntimeOrigin::signed(account_id.clone()), + OriginFor::::signed(account_id.clone()), asset_id, U256::zero(), GAS_LIMIT, - DepositLimit::Balance( - <::Currency as fungible::Inspect<_>>::total_issuance(), - ), + <::Currency as fungible::Inspect<_>>::total_issuance(), data, + ExecConfig::new_substrate_tx(), ); if let Ok(return_value) = result { if let Ok(eu256) = EU256::abi_decode_validate(&return_value.data) { @@ -165,12 +156,7 @@ where // We implement `fungibles::Mutate` to override `burn_from` and `mint_to`. // // These functions are used in [`xcm_builder::FungiblesAdapter`]. -impl fungibles::Mutate<::AccountId> for Pallet -where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, -{ +impl fungibles::Mutate<::AccountId> for Pallet { fn burn_from( asset_id: Self::AssetId, who: &T::AccountId, @@ -184,14 +170,13 @@ where let data = IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode(); let ContractResult { result, gas_consumed, .. } = Self::bare_call( - T::RuntimeOrigin::signed(who.clone()), + OriginFor::::signed(who.clone()), asset_id, U256::zero(), GAS_LIMIT, - DepositLimit::Balance( - <::Currency as fungible::Inspect<_>>::total_issuance(), - ), + <::Currency as fungible::Inspect<_>>::total_issuance(), data, + ExecConfig::new_substrate_tx(), ); log::trace!(target: "whatiwant", "{gas_consumed}"); if let Ok(return_value) = result { @@ -221,14 +206,13 @@ where let address = Address::from(Into::<[u8; 20]>::into(eth_address)); let data = IERC20::transferCall { to: address, value: EU256::from(amount) }.abi_encode(); let ContractResult { result, .. } = Self::bare_call( - T::RuntimeOrigin::signed(Self::checking_account()), + OriginFor::::signed(Self::checking_account()), asset_id, U256::zero(), GAS_LIMIT, - DepositLimit::Balance( - <::Currency as fungible::Inspect<_>>::total_issuance(), - ), + <::Currency as fungible::Inspect<_>>::total_issuance(), data, + ExecConfig::new_substrate_tx(), ); if let Ok(return_value) = result { if return_value.did_revert() { @@ -253,12 +237,7 @@ where // However, we don't have this type of access to smart contracts. // Withdraw and deposit happen via the custom `fungibles::Mutate` impl above. // Because of this, all functions here return an error, when possible. -impl fungibles::Unbalanced<::AccountId> for Pallet -where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, -{ +impl fungibles::Unbalanced<::AccountId> for Pallet { fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {} fn handle_dust(_: fungibles::Dust) {} fn write_balance( @@ -416,7 +395,7 @@ mod tests { RuntimeOrigin::signed(checking_account.clone()), Code::Upload(code), ) - .storage_deposit_limit((1_000_000_000_000).into()) + .storage_deposit_limit(1_000_000_000_000) .data(constructor_data) .build_and_unwrap_contract(); assert_eq!( diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 7a67ad21dbf80..c1b7dce0b089d 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,8 +45,8 @@ pub mod weights; use crate::{ evm::{ - runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, PrestateTracer, Trace, - Tracer, TracerType, TYPE_EIP1559, + create_call, fees::InfoT as FeeInfo, runtime::SetWeightLimit, CallTracer, + GenericTransaction, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, gas::GasMeter, @@ -59,14 +59,15 @@ use codec::{Codec, Decode, Encode}; use environmental::*; use frame_support::{ dispatch::{ - DispatchErrorWithPostInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays, - PostDispatchInfo, RawOrigin, + DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, + Pays, PostDispatchInfo, RawOrigin, }, ensure, pallet_prelude::DispatchClass, traits::{ - fungible::{Inspect, Mutate, MutateHold}, - ConstU32, ConstU64, EnsureOrigin, Get, IsType, OriginTrait, Time, + fungible::{Balanced, Inspect, Mutate, MutateHold}, + tokens::Balance, + ConstU32, ConstU64, EnsureOrigin, Get, IsSubType, IsType, OriginTrait, Time, }, weights::WeightMeter, BoundedVec, RuntimeDebugNoBound, @@ -76,25 +77,23 @@ use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, Pallet as System, }; -use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; use sp_runtime::{ - traits::{Bounded, Convert, Dispatchable, Saturating}, - AccountId32, DispatchError, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, UniqueSaturatedInto, Zero}, + AccountId32, DispatchError, FixedPointNumber, FixedU128, }; pub use crate::{ address::{ create1, create2, is_eth_derived, AccountId32Mapper, AddressMapper, TestAccountMapper, }, - exec::{Key, MomentOf, Origin}, + exec::{Key, MomentOf, Origin as ExecOrigin}, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, }; pub use codec; pub use frame_support::{self, dispatch::DispatchInfo, weights::Weight}; pub use frame_system::{self, limits::BlockWeights}; -pub use pallet_transaction_payment; pub use primitives::*; pub use sp_core::{H160, H256, U256}; pub use sp_runtime; @@ -103,11 +102,10 @@ pub use weights::WeightInfo; #[cfg(doc)] pub use crate::vm::pvm::SyscallDoc; -pub type BalanceOf = - <::Currency as Inspect<::AccountId>>::Balance; +pub type BalanceOf = ::Balance; type TrieId = BoundedVec>; type ImmutableData = BoundedVec>; -pub(crate) type OnChargeTransactionBalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; +type CallOf = ::RuntimeCall; /// Used as a sentinel value when reading and writing contract memory. /// @@ -142,13 +140,20 @@ pub mod pallet { #[pallet::config(with_default)] pub trait Config: frame_system::Config { /// The time implementation used to supply timestamps to contracts through `seal_now`. - type Time: Time; + type Time: Time>; + + /// The balance type of [`Self::Currency`]. + /// + /// Just added here to add additional trait bounds. + #[pallet::no_default] + type Balance: Balance + TryFrom + Into + Bounded + UniqueSaturatedInto; /// The fungible in which fees are paid and contract balances are held. #[pallet::no_default] - type Currency: Inspect + type Currency: Inspect + Mutate - + MutateHold; + + MutateHold + + Balanced; /// The overarching event type. #[pallet::no_default_bounds] @@ -158,17 +163,24 @@ pub mod pallet { /// The overarching call type. #[pallet::no_default_bounds] type RuntimeCall: Parameter - + Dispatchable + + Dispatchable< + RuntimeOrigin = OriginFor, + Info = DispatchInfo, + PostInfo = PostDispatchInfo, + > + IsType<::RuntimeCall> + + From> + + IsSubType> + GetDispatchInfo; - /// Overarching hold reason. + /// The overarching origin type. #[pallet::no_default_bounds] - type RuntimeHoldReason: From; + type RuntimeOrigin: IsType> + + From> + + Into, OriginFor>>; - /// Used to answer contracts' queries regarding the current weight price. This is **not** - /// used to calculate the actual fee and is only for informational purposes. + /// Overarching hold reason. #[pallet::no_default_bounds] - type WeightPrice: Convert>; + type RuntimeHoldReason: From; /// Describes the weights of the dispatchables of this module and is also used to /// construct a default cost schedule. @@ -233,7 +245,7 @@ pub mod pallet { /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to upload contract /// code. #[pallet::no_default_bounds] - type UploadOrigin: EnsureOrigin; + type UploadOrigin: EnsureOrigin, Success = Self::AccountId>; /// Origin allowed to instantiate code. /// @@ -246,7 +258,7 @@ pub mod pallet { /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to instantiate /// contract code. #[pallet::no_default_bounds] - type InstantiateOrigin: EnsureOrigin; + type InstantiateOrigin: EnsureOrigin, Success = Self::AccountId>; /// The amount of memory in bytes that parachain nodes a lot to the runtime. /// @@ -274,10 +286,27 @@ pub mod pallet { #[pallet::constant] type NativeToEthRatio: Get; - /// Encode and decode Ethereum gas values. - /// Only valid value is `()`. See [`GasEncoder`]. + /// Set to [`crate::evm::fees::Info`] for a production runtime. + /// + /// For mock runtimes that do not need to interact with any eth compat functionality + /// the default value of `()` will suffice. #[pallet::no_default_bounds] - type EthGasEncoder: GasEncoder>; + type FeeInfo: FeeInfo; + + /// The fraction the maximum extrinsic weight `eth_transact` extrinsics are capped to. + /// + /// This is not a security measure but a requirement due to how we map gas to `(Weight, + /// StorageDeposit)`. The mapping might derive a `Weight` that is too large to fit into an + /// extrinsic. In this case we cap it to the limit specified here. + /// + /// `eth_transact` transactions that use more weight than specified will fail with an out of + /// gas error during execution. Larger fractions will allow more transactions to run. + /// Smaller values waste less block space: Choose as small as possible and as large as + /// necessary. + /// + /// Default: `0.5`. + #[pallet::constant] + type MaxEthExtrinsicWeight: Get; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -291,17 +320,20 @@ pub mod pallet { use sp_core::parameter_types; type Balance = u64; - const UNITS: Balance = 10_000_000_000; - const CENTS: Balance = UNITS / 100; + + pub const DOLLARS: Balance = 1_000_000_000_000; + pub const CENTS: Balance = DOLLARS / 100; + pub const MILLICENTS: Balance = CENTS / 1_000; pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS + items as Balance * 20 * CENTS + (bytes as Balance) * MILLICENTS } parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); + pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(1, 2); } /// A type providing default configurations for this pallet in testing environment. @@ -333,6 +365,10 @@ pub mod pallet { #[inject_runtime_type] type RuntimeCall = (); + + #[inject_runtime_type] + type RuntimeOrigin = (); + type Precompiles = (); type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type DepositPerByte = DepositPerByte; @@ -343,13 +379,13 @@ pub mod pallet { type UploadOrigin = EnsureSigned; type InstantiateOrigin = EnsureSigned; type WeightInfo = (); - type WeightPrice = Self; type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; type ChainId = ConstU64<42>; type NativeToEthRatio = ConstU32<1_000_000>; - type EthGasEncoder = (); type FindAuthor = (); + type FeeInfo = (); + type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; } } @@ -486,6 +522,10 @@ pub mod pallet { StackUnderflow = 0x33, /// Attempting to push a value onto a full stack. StackOverflow = 0x34, + /// Too much deposit was drawn from the shared txfee and deposit credit. + /// + /// This happens if the passed `gas` inside the ethereum transaction is too low. + TxFeeOverdraw = 0x35, } /// A reason for the pallet revive placing a hold on funds. @@ -499,6 +539,22 @@ pub mod pallet { AddressMapping, } + #[derive( + PartialEq, + Eq, + Clone, + MaxEncodedLen, + Encode, + Decode, + DecodeWithMemTracking, + TypeInfo, + RuntimeDebug, + )] + #[pallet::origin] + pub enum Origin { + EthTransaction(T::AccountId), + } + /// A mapping from a contract's code hash to its code. /// The code's size is bounded by [`crate::limits::BLOB_BYTES`] for PVM and /// [`revm::primitives::eip170::MAX_CODE_SIZE`] for EVM bytecode. @@ -584,12 +640,7 @@ pub mod pallet { } #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig - where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, - { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { use crate::{exec::Key, vm::ContractBlob}; use frame_support::traits::fungible::Mutate; @@ -676,6 +727,7 @@ pub mod pallet { System::::account_exists(&Pallet::::account_id()); return T::DbWeight::get().reads(1) } + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { let mut meter = WeightMeter::with_limit(limit); ContractInfo::::process_deletion_queue_batch(&mut meter); @@ -685,6 +737,8 @@ pub mod pallet { fn integrity_test() { assert!(T::ChainId::get() > 0, "ChainId must be greater than 0"); + T::FeeInfo::integrity_test(); + // The memory available in the block building runtime let max_runtime_mem: u32 = T::RuntimeMemory::get(); @@ -782,12 +836,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet - where - BalanceOf: Into + TryFrom, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, - { + impl Pallet { /// A raw EVM transaction, typically dispatched by an Ethereum JSON-RPC server. /// /// # Parameters @@ -827,7 +876,7 @@ pub mod pallet { /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] + #[pallet::weight(::WeightInfo::call().saturating_add(*gas_limit))] pub fn call( origin: OriginFor, dest: H160, @@ -841,8 +890,9 @@ pub mod pallet { dest, Pallet::::convert_native_to_evm(value), gas_limit, - DepositLimit::Balance(storage_deposit_limit), + storage_deposit_limit, data, + ExecConfig::new_substrate_tx(), ); if let Ok(return_value) = &output.result { @@ -850,7 +900,7 @@ pub mod pallet { output.result = Err(>::ContractReverted.into()); } } - dispatch_result(output.result, output.gas_consumed, T::WeightInfo::call()) + dispatch_result(output.result, output.gas_consumed, ::WeightInfo::call()) } /// Instantiates a contract from a previously deployed vm binary. @@ -860,7 +910,7 @@ pub mod pallet { /// must be supplied. #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit) + ::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit) )] pub fn instantiate( origin: OriginFor, @@ -876,11 +926,11 @@ pub mod pallet { origin, Pallet::::convert_native_to_evm(value), gas_limit, - DepositLimit::Balance(storage_deposit_limit), + storage_deposit_limit, Code::Existing(code_hash), data, salt, - BumpNonce::Yes, + ExecConfig::new_substrate_tx(), ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -890,7 +940,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::instantiate(data_len), + ::WeightInfo::instantiate(data_len), ) } @@ -923,7 +973,7 @@ pub mod pallet { /// - The `deploy` function is executed in the context of the newly-created account. #[pallet::call_index(3)] #[pallet::weight( - T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32) + ::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32) .saturating_add(*gas_limit) )] pub fn instantiate_with_code( @@ -941,11 +991,11 @@ pub mod pallet { origin, Pallet::::convert_native_to_evm(value), gas_limit, - DepositLimit::Balance(storage_deposit_limit), + storage_deposit_limit, Code::Upload(code), data, salt, - BumpNonce::Yes, + ExecConfig::new_substrate_tx(), ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -955,7 +1005,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::instantiate_with_code(code_len, data_len), + ::WeightInfo::instantiate_with_code(code_len, data_len), ) } @@ -968,7 +1018,7 @@ pub mod pallet { /// times within a batch call transaction. #[pallet::call_index(10)] #[pallet::weight( - T::WeightInfo::eth_instantiate_with_code(code.len() as u32, data.len() as u32, Pallet::::has_dust(*value).into()) + ::WeightInfo::eth_instantiate_with_code(code.len() as u32, data.len() as u32, Pallet::::has_dust(*value).into()) .saturating_add(*gas_limit) )] pub fn eth_instantiate_with_code( @@ -978,18 +1028,33 @@ pub mod pallet { #[pallet::compact] storage_deposit_limit: BalanceOf, code: Vec, data: Vec, + effective_gas_price: U256, + encoded_len: u32, ) -> DispatchResultWithPostInfo { + let origin = Self::ensure_eth_origin(origin)?; + let info = T::FeeInfo::dispatch_info( + &Call::::eth_instantiate_with_code { + value, + gas_limit, + storage_deposit_limit, + code: code.clone(), + data: data.clone(), + effective_gas_price, + encoded_len, + } + .into(), + ); let code_len = code.len() as u32; let data_len = data.len() as u32; let mut output = Self::bare_instantiate( origin, value, gas_limit, - DepositLimit::Balance(storage_deposit_limit), + storage_deposit_limit, Code::Upload(code), data, None, - BumpNonce::No, + ExecConfig::new_eth_tx(effective_gas_price), ); if let Ok(retval) = &output.result { @@ -997,21 +1062,22 @@ pub mod pallet { output.result = Err(>::ContractReverted.into()); } } - dispatch_result( + let result = dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::eth_instantiate_with_code( + ::WeightInfo::eth_instantiate_with_code( code_len, data_len, Pallet::::has_dust(value).into(), ), - ) + ); + T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result) } /// Same as [`Self::call`], but intended to be dispatched **only** /// by an EVM transaction through the EVM compatibility layer. #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::eth_call(Pallet::::has_dust(*value).into()).saturating_add(*gas_limit))] + #[pallet::weight(::WeightInfo::eth_call(Pallet::::has_dust(*value).into()).saturating_add(*gas_limit))] pub fn eth_call( origin: OriginFor, dest: H160, @@ -1019,26 +1085,42 @@ pub mod pallet { gas_limit: Weight, #[pallet::compact] storage_deposit_limit: BalanceOf, data: Vec, + effective_gas_price: U256, + encoded_len: u32, ) -> DispatchResultWithPostInfo { + let origin = Self::ensure_eth_origin(origin)?; + let info = T::FeeInfo::dispatch_info( + &Call::::eth_call { + dest, + value, + gas_limit, + storage_deposit_limit, + data: data.clone(), + effective_gas_price, + encoded_len, + } + .into(), + ); let mut output = Self::bare_call( origin, dest, value, gas_limit, - DepositLimit::Balance(storage_deposit_limit), + storage_deposit_limit, data, + ExecConfig::new_eth_tx(effective_gas_price), ); - if let Ok(return_value) = &output.result { if return_value.did_revert() { output.result = Err(>::ContractReverted.into()); } } - dispatch_result( + let result = dispatch_result( output.result, output.gas_consumed, - T::WeightInfo::eth_call(Pallet::::has_dust(value).into()), - ) + ::WeightInfo::eth_call(Pallet::::has_dust(value).into()), + ); + T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result) } /// Upload new `code` without instantiating a contract from it. @@ -1056,7 +1138,7 @@ pub mod pallet { /// If the refcount of the code reaches zero after terminating the last contract that /// references this code, the code will be removed automatically. #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))] + #[pallet::weight(::WeightInfo::upload_code(code.len() as u32))] pub fn upload_code( origin: OriginFor, code: Vec, @@ -1070,7 +1152,7 @@ pub mod pallet { /// A code can only be removed by its original uploader (its owner) and only if it is /// not used by any contract. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::remove_code())] + #[pallet::weight(::WeightInfo::remove_code())] pub fn remove_code( origin: OriginFor, code_hash: sp_core::H256, @@ -1092,7 +1174,7 @@ pub mod pallet { /// that the contract address is no longer derived from its code hash after calling /// this dispatchable. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::set_code())] + #[pallet::weight(::WeightInfo::set_code())] pub fn set_code( origin: OriginFor, dest: H160, @@ -1121,7 +1203,7 @@ pub mod pallet { /// This will error if the origin is already mapped or is a eth native `Address20`. It will /// take a deposit that can be released by calling [`Self::unmap_account`]. #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::map_account())] + #[pallet::weight(::WeightInfo::map_account())] pub fn map_account(origin: OriginFor) -> DispatchResult { let origin = ensure_signed(origin)?; T::AddressMapper::map(&origin) @@ -1132,7 +1214,7 @@ pub mod pallet { /// There is no reason to ever call this function other than freeing up the deposit. /// This is only useful when the account should no longer be used. #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::unmap_account())] + #[pallet::weight(::WeightInfo::unmap_account())] pub fn unmap_account(origin: OriginFor) -> DispatchResult { let origin = ensure_signed(origin)?; T::AddressMapper::unmap(&origin) @@ -1147,7 +1229,7 @@ pub mod pallet { #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( - T::WeightInfo::dispatch_as_fallback_account().saturating_add(dispatch_info.call_weight), + ::WeightInfo::dispatch_as_fallback_account().saturating_add(dispatch_info.call_weight), dispatch_info.class ) })] @@ -1179,12 +1261,7 @@ fn dispatch_result( .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e }) } -impl Pallet -where - BalanceOf: Into + TryFrom + Bounded, - MomentOf: Into, - T::Hash: frame_support::traits::IsType, -{ +impl Pallet { /// A generalized version of [`Self::call`]. /// /// Identical to [`Self::call`] but tailored towards being called by other code within the @@ -1196,8 +1273,9 @@ where dest: H160, evm_value: U256, gas_limit: Weight, - storage_deposit_limit: DepositLimit>, + storage_deposit_limit: BalanceOf, data: Vec, + exec_config: ExecConfig, ) -> ContractResult> { if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { return contract_result; @@ -1206,8 +1284,8 @@ where let mut storage_deposit = Default::default(); let try_call = || { - let origin = Origin::from_runtime_origin(origin)?; - let mut storage_meter = StorageMeter::new(storage_deposit_limit.limit()); + let origin = ExecOrigin::from_runtime_origin(origin)?; + let mut storage_meter = StorageMeter::new(storage_deposit_limit); let result = ExecStack::>::run_call( origin.clone(), dest, @@ -1215,11 +1293,10 @@ where &mut storage_meter, evm_value, data, - storage_deposit_limit.is_unchecked(), + &exec_config, )?; - storage_deposit = storage_meter - .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) - .inspect_err(|err| { + storage_deposit = + storage_meter.try_into_deposit(&origin, &exec_config).inspect_err(|err| { log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); })?; Ok(result) @@ -1253,11 +1330,11 @@ where origin: OriginFor, evm_value: U256, gas_limit: Weight, - storage_deposit_limit: DepositLimit>, + mut storage_deposit_limit: BalanceOf, code: Code, data: Vec, salt: Option<[u8; 32]>, - bump_nonce: BumpNonce, + exec_config: ExecConfig, ) -> ContractResult> { // Enforce EIP-3607 for top-level signed origins: deny signed contract addresses. if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { @@ -1265,8 +1342,6 @@ where } let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); - let mut storage_deposit_limit = storage_deposit_limit.limit(); let try_instantiate = || { let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; @@ -1278,7 +1353,7 @@ where upload_account, code, storage_deposit_limit, - unchecked_deposit_limit, + &exec_config, )?; storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) @@ -1294,7 +1369,7 @@ where Code::Existing(code_hash) => (ContractBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), }; - let instantiate_origin = Origin::from_account_id(instantiate_account.clone()); + let instantiate_origin = ExecOrigin::from_account_id(instantiate_account.clone()); let mut storage_meter = StorageMeter::new(storage_deposit_limit); let result = ExecStack::>::run_instantiate( instantiate_account, @@ -1304,11 +1379,10 @@ where evm_value, data, salt.as_ref(), - unchecked_deposit_limit, - bump_nonce, + &exec_config, ); storage_deposit = storage_meter - .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? + .try_into_deposit(&instantiate_origin, &exec_config)? .saturating_add(&StorageDeposit::Charge(upload_deposit)); result }; @@ -1328,35 +1402,28 @@ where /// # Parameters /// /// - `tx`: The Ethereum transaction to simulate. - /// - `gas_limit`: The gas limit enforced during contract execution. - /// - `tx_fee`: A function that returns the fee for the computed eth_transact and actual - /// dispatched call pub fn dry_run_eth_transact( mut tx: GenericTransaction, - gas_limit: Weight, - tx_fee: impl Fn(::RuntimeCall, ::RuntimeCall) -> BalanceOf, ) -> Result>, EthTransactError> where - ::RuntimeCall: - Dispatchable, - T: pallet_transaction_payment::Config, - OnChargeTransactionBalanceOf: Into>, - ::RuntimeCall: From>, - ::RuntimeCall: Encode, T::Nonce: Into, - T::Hash: frame_support::traits::IsType, + CallOf: SetWeightLimit, { - log::trace!(target: LOG_TARGET, "dry_run_eth_transact: {tx:?} gas_limit: {gas_limit:?}"); + log::debug!(target: LOG_TARGET, "dry_run_eth_transact: {tx:?}"); - let from = tx.from.unwrap_or_default(); - let origin = T::AddressMapper::to_account_id(&from); + let origin = T::AddressMapper::to_account_id(&tx.from.unwrap_or_default()); Self::prepare_dry_run(&origin); - let storage_deposit_limit = if tx.gas.is_some() { - DepositLimit::Balance(BalanceOf::::max_value()) - } else { - DepositLimit::UnsafeOnlyForDryRun - }; + let base_fee = Self::evm_gas_price(); + let effective_gas_price = tx.effective_gas_price(base_fee).unwrap_or(base_fee); + + if effective_gas_price < base_fee { + Err(EthTransactError::Message(format!( + "Effective gas price {effective_gas_price:?} lower than base fee {base_fee:?}" + )))?; + } + + let exec_config = ExecConfig::new_eth_tx(effective_gas_price); if tx.nonce.is_none() { tx.nonce = Some(>::account_nonce(&origin).into()); @@ -1365,14 +1432,16 @@ where tx.chain_id = Some(T::ChainId::get().into()); } if tx.gas_price.is_none() { - tx.gas_price = Some(GAS_PRICE.into()); + tx.gas_price = Some(effective_gas_price); } if tx.max_priority_fee_per_gas.is_none() { - tx.max_priority_fee_per_gas = Some(GAS_PRICE.into()); + tx.max_priority_fee_per_gas = Some(effective_gas_price); } if tx.max_fee_per_gas.is_none() { - tx.max_fee_per_gas = Some(GAS_PRICE.into()); + tx.max_fee_per_gas = Some(effective_gas_price); } + + let gas = tx.gas; if tx.gas.is_none() { tx.gas = Some(Self::evm_block_gas_limit()); } @@ -1380,62 +1449,72 @@ where tx.r#type = Some(TYPE_EIP1559.into()); } - // Convert the value to the native balance type. + // Store values before moving the tx let value = tx.value.unwrap_or_default(); let input = tx.input.clone().to_vec(); + let from = tx.from; + let to = tx.to; + + // we need to parse the weight from the transaction so that it is run + // using the exact weight limit passed by the eth wallet + let mut call_info = create_call::(tx, None) + .map_err(|err| EthTransactError::Message(format!("Invalid call: {err:?}")))?; + + // emulate transaction behavior + let fees = call_info.tx_fee.saturating_add(call_info.storage_deposit); + match (gas, from, value.is_zero()) { + (Some(_), Some(from), _) | (_, Some(from), false) => { + let balance = Self::evm_balance(&from).saturating_add(value); + if balance < Pallet::::convert_native_to_evm(fees) { + return Err(EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {from:?} have {balance:?} (supplied gas {gas:?})", + ))); + } + }, + _ => {}, + } + + T::FeeInfo::deposit_txfee(T::Currency::issue(fees)); let extract_error = |err| { - if err == Error::::TransferFailed.into() || - err == Error::::StorageDepositNotEnoughFunds.into() || - err == Error::::StorageDepositLimitExhausted.into() - { - let balance = Self::evm_balance(&from); - return Err(EthTransactError::Message(format!( - "insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})", - tx.gas.unwrap_or_default() - ))); + if err == Error::::StorageDepositNotEnoughFunds.into() { + Err(EthTransactError::Message(format!("Not enough gas supplied: {err:?}"))) + } else { + Err(EthTransactError::Message(format!("failed to run contract: {err:?}"))) } - - return Err(EthTransactError::Message(format!( - "Failed to instantiate contract: {err:?}" - ))); }; // Dry run the call - let (mut result, dispatch_call) = match tx.to { + let mut dry_run = match to { // A contract call. Some(dest) => { if dest == RUNTIME_PALLETS_ADDR { - let Ok(dispatch_call) = ::RuntimeCall::decode(&mut &input[..]) - else { + let Ok(dispatch_call) = >::decode(&mut &input[..]) else { return Err(EthTransactError::Message(format!( "Failed to decode pallet-call {input:?}" ))); }; - if let Err(err) = + if let Err(result) = dispatch_call.clone().dispatch(RawOrigin::Signed(origin).into()) { return Err(EthTransactError::Message(format!( - "Failed to dispatch call: {err:?}" + "Failed to dispatch call: {:?}", + result.error, ))); }; - let result = EthTransactInfo { - gas_required: dispatch_call.get_dispatch_info().total_weight(), - ..Default::default() - }; - - (result, dispatch_call) + Default::default() } else { // Dry run the call. let result = crate::Pallet::::bare_call( - T::RuntimeOrigin::signed(origin), + OriginFor::::signed(origin), dest, value, - gas_limit, - storage_deposit_limit, + call_info.weight_limit, + BalanceOf::::max_value(), input.clone(), + exec_config, ); let data = match result.result { @@ -1451,26 +1530,12 @@ where }, }; - let result = EthTransactInfo { + EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), data, eth_gas: Default::default(), - }; - - let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( - result.gas_required, - result.storage_deposit, - ); - let dispatch_call: ::RuntimeCall = crate::Call::::eth_call { - dest, - value, - gas_limit, - storage_deposit_limit, - data: input.clone(), } - .into(); - (result, dispatch_call) } }, // A contract deployment @@ -1484,14 +1549,14 @@ where // Dry run the call. let result = crate::Pallet::::bare_instantiate( - T::RuntimeOrigin::signed(origin), + OriginFor::::signed(origin), value, - gas_limit, - storage_deposit_limit, + call_info.weight_limit, + BalanceOf::::max_value(), Code::Upload(code.clone()), data.clone(), None, - BumpNonce::No, + exec_config, ); let returned_data = match result.result { @@ -1507,45 +1572,59 @@ where }, }; - let result = EthTransactInfo { + EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), data: returned_data, eth_gas: Default::default(), - }; - - // Get the dispatch info of the call. - let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( - result.gas_required, - result.storage_deposit, - ); - let dispatch_call: ::RuntimeCall = - crate::Call::::eth_instantiate_with_code { - value, - gas_limit, - storage_deposit_limit, - code, - data, - } - .into(); - (result, dispatch_call) + } }, }; - let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { - return Err(EthTransactError::Message("Invalid transaction".into())); - }; + // replace the weight passed in the transaction with the dry_run result + call_info.call.set_weight_limit(dry_run.gas_required); + + // we notify the wallet that the tx would not fit + let total_weight = T::FeeInfo::dispatch_info(&call_info.call).total_weight(); + let max_weight = Self::evm_max_extrinsic_weight(); + if total_weight.any_gt(max_weight) { + Err(EthTransactError::Message(format!( + "\ + The transaction consumes more than the allowed weight. \ + needed={total_weight} \ + allowed={max_weight} \ + overweight_by={}\ + ", + total_weight.saturating_sub(max_weight), + )))?; + } + + let transaction_fee = T::FeeInfo::tx_fee(call_info.encoded_len, &call_info.call); + let available_fee = T::FeeInfo::remaining_txfee(); + if transaction_fee > available_fee { + Err(EthTransactError::Message(format!( + "Not enough gas supplied: Off by: {:?}", + call_info.tx_fee.saturating_sub(available_fee), + )))?; + } + + // We add `1` to account for the potential rounding error of the multiplication. + // Returning a larger value here just increases the the pre-dispatch weight. + let eth_gas: U256 = T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(transaction_fee.saturating_add(dry_run.storage_deposit)) + .saturating_add(1_u32.into()) + .into(); - let eth_transact_call = - crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; - let fee = tx_fee(eth_transact_call.into(), dispatch_call); - let raw_gas = Self::evm_fee_to_gas(fee); - let eth_gas = - T::EthGasEncoder::encode(raw_gas, result.gas_required, result.storage_deposit); + log::debug!(target: LOG_TARGET, "\ + dry_run_eth_transact: \ + weight_limit={:?}: \ + eth_gas={eth_gas:?})\ + ", + dry_run.gas_required, - log::trace!(target: LOG_TARGET, "bare_eth_call: raw_gas: {raw_gas:?} eth_gas: {eth_gas:?}"); - result.eth_gas = eth_gas; - Ok(result) + ); + dry_run.eth_gas = eth_gas; + Ok(dry_run) } /// Get the balance with EVM decimals of the given `address`. @@ -1595,57 +1674,37 @@ where System::::account_nonce(account).into() } - /// Convert a substrate fee into a gas value, using the fixed `GAS_PRICE`. - /// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. - pub fn evm_fee_to_gas(fee: BalanceOf) -> U256 { - let fee = Self::convert_native_to_evm(fee); - let gas_price = GAS_PRICE.into(); - let (quotient, remainder) = fee.div_mod(gas_price); - if remainder.is_zero() { - quotient - } else { - quotient + U256::one() - } - } - - /// Convert a gas value into a substrate fee - fn evm_gas_to_fee(gas: U256, gas_price: U256) -> Result, Error> { - let fee = gas.saturating_mul(gas_price); - let value = BalanceWithDust::>::from_value::(fee) - .map_err(|_| >::BalanceConversionFailed)?; - Ok(value.into_rounded_balance()) - } - - /// Convert a weight to a gas value. - pub fn evm_gas_from_weight(weight: Weight) -> U256 { - let fee = T::WeightPrice::convert(weight); - Self::evm_fee_to_gas(fee) - } - /// Get the block gas limit. - pub fn evm_block_gas_limit() -> U256 - where - ::RuntimeCall: - Dispatchable, - T: pallet_transaction_payment::Config, - OnChargeTransactionBalanceOf: Into>, - { + pub fn evm_block_gas_limit() -> U256 { let max_block_weight = T::BlockWeights::get() .get(DispatchClass::Normal) .max_total .unwrap_or_else(|| T::BlockWeights::get().max_block); - let length_fee = pallet_transaction_payment::Pallet::::length_to_fee( - 5 * 1024 * 1024, // 5 MB + let length_fee = T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int( + T::FeeInfo::length_to_fee(*T::BlockLength::get().max.get(DispatchClass::Normal)), ); - Self::evm_gas_from_weight(max_block_weight) - .saturating_add(Self::evm_fee_to_gas(length_fee.into())) + Self::evm_gas_from_weight(max_block_weight).saturating_add(length_fee.into()) + } + + /// The maximum weight an `eth_transact` is allowed to consume. + pub fn evm_max_extrinsic_weight() -> Weight { + let factor = ::MaxEthExtrinsicWeight::get(); + let max_weight = ::BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or_else(|| ::BlockWeights::get().max_block); + Weight::from_parts( + factor.saturating_mul_int(max_weight.ref_time()), + factor.saturating_mul_int(max_weight.proof_size()), + ) } - /// Get the gas price. + /// Get the base gas price. pub fn evm_gas_price() -> U256 { - GAS_PRICE.into() + let multiplier = T::FeeInfo::next_fee_multiplier(); + multiplier.saturating_mul_int::(T::NativeToEthRatio::get().into()).into() } /// Build an EVM tracer from the given tracer type. @@ -1673,8 +1732,12 @@ where storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = - Self::try_upload_pvm_code(origin, code, storage_deposit_limit, false)?; + let (module, deposit) = Self::try_upload_pvm_code( + origin, + code, + storage_deposit_limit, + &ExecConfig::new_substrate_tx(), + )?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -1721,6 +1784,15 @@ where Ok(maybe_value) } + /// Convert a native balance to EVM balance. + pub fn convert_native_to_evm(value: impl Into>>) -> U256 { + let (value, dust) = value.into().deconstruct(); + value + .into() + .saturating_mul(T::NativeToEthRatio::get().into()) + .saturating_add(dust.into()) + } + /// Set storage of a specified contract under a specified key. /// /// If the `value` is `None`, the storage entry is deleted. @@ -1774,10 +1846,10 @@ where origin: T::AccountId, code: Vec, storage_deposit_limit: BalanceOf, - skip_transfer: bool, + exec_config: &ExecConfig, ) -> Result<(ContractBlob, BalanceOf), DispatchError> { let mut module = ContractBlob::from_pvm_code(code, origin)?; - let deposit = module.store_code(skip_transfer)?; + let deposit = module.store_code(exec_config, None)?; ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); Ok((module, deposit)) } @@ -1801,13 +1873,9 @@ where }) } - /// Convert a native balance to EVM balance. - pub fn convert_native_to_evm(value: impl Into>>) -> U256 { - let (value, dust) = value.into().deconstruct(); - value - .into() - .saturating_mul(T::NativeToEthRatio::get().into()) - .saturating_add(dust.into()) + /// Convert a weight to a gas value. + fn evm_gas_from_weight(weight: Weight) -> U256 { + T::FeeInfo::weight_to_fee(weight).into() } /// Ensure the origin has no code deplyoyed if it is a signed origin. @@ -1850,9 +1918,7 @@ where } Ok(()) } -} -impl Pallet { /// Pallet account, used to hold funds for contracts upload deposit. pub fn account_id() -> T::AccountId { use frame_support::PalletId; @@ -1860,26 +1926,6 @@ impl Pallet { PalletId(*b"py/reviv").into_account_truncating() } - /// Returns true if the evm value carries dust. - fn has_dust(value: U256) -> bool { - value % U256::from(::NativeToEthRatio::get()) != U256::zero() - } - - /// Returns true if the evm value carries balance. - fn has_balance(value: U256) -> bool { - value >= U256::from(::NativeToEthRatio::get()) - } - - /// Return the existential deposit of [`Config::Currency`]. - fn min_balance() -> BalanceOf { - >>::minimum_balance() - } - - /// Deposit a pallet revive event. - fn deposit_event(event: Event) { - >::deposit_event(::RuntimeEvent::from(event)) - } - /// The address of the validator that produced the current block. pub fn block_author() -> Option { use frame_support::traits::FindAuthor; @@ -1904,6 +1950,130 @@ impl Pallet { .map(|code| code.into()) .unwrap_or_default() } + + /// Transfer a deposit from some account to another. + /// + /// `from` is usually the transaction origin and `to` a contract or + /// the pallets own account. + fn charge_deposit( + hold_reason: Option, + from: &T::AccountId, + to: &T::AccountId, + amount: BalanceOf, + exec_config: &ExecConfig, + ) -> DispatchResult { + use frame_support::traits::tokens::{Fortitude, Precision, Preservation}; + match (exec_config.collect_deposit_from_hold, hold_reason) { + (true, hold_reason) => { + T::FeeInfo::withdraw_txfee(amount) + .ok_or(()) + .and_then(|credit| T::Currency::resolve(to, credit).map_err(|_| ())) + .and_then(|_| { + if let Some(hold_reason) = hold_reason { + T::Currency::hold(&hold_reason.into(), to, amount).map_err(|_| ())?; + } + Ok(()) + }) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + }, + (false, Some(hold_reason)) => { + T::Currency::transfer_and_hold( + &hold_reason.into(), + from, + to, + amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + }, + (false, None) => { + T::Currency::transfer(from, to, amount, Preservation::Preserve) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + }, + } + Ok(()) + } + + /// Refund a deposit. + /// + /// `to` is usually the transaction origin and `from` a contract or + /// the pallets own account. + fn refund_deposit( + hold_reason: HoldReason, + from: &T::AccountId, + to: &T::AccountId, + amount: BalanceOf, + exec_config: &ExecConfig, + ) -> Result, DispatchError> { + use frame_support::traits::{ + tokens::{Fortitude, Precision, Preservation, Restriction}, + Imbalance, + }; + if exec_config.collect_deposit_from_hold { + let amount = + T::Currency::release(&hold_reason.into(), from, amount, Precision::BestEffort) + .and_then(|amount| { + T::Currency::withdraw( + from, + amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ) + .and_then(|credit| { + let amount = credit.peek(); + T::FeeInfo::deposit_txfee(credit); + Ok(amount) + }) + }) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + amount + } else { + let amount = T::Currency::transfer_on_hold( + &hold_reason.into(), + from, + to, + amount, + Precision::BestEffort, + Restriction::Free, + Fortitude::Polite, + ) + .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; + amount + }; + + Ok(amount) + } + + /// Returns true if the evm value carries dust. + fn has_dust(value: U256) -> bool { + value % U256::from(::NativeToEthRatio::get()) != U256::zero() + } + + /// Returns true if the evm value carries balance. + fn has_balance(value: U256) -> bool { + value >= U256::from(::NativeToEthRatio::get()) + } + + /// Return the existential deposit of [`Config::Currency`]. + fn min_balance() -> BalanceOf { + >>::minimum_balance() + } + + /// Deposit a pallet contracts event. + fn deposit_event(event: Event) { + >::deposit_event(::RuntimeEvent::from(event)) + } + + /// Tranform a [`Origin::EthTransaction`] into a signed origin. + fn ensure_eth_origin(origin: OriginFor) -> Result, DispatchError> { + match ::RuntimeOrigin::from(origin).into() { + Ok(Origin::EthTransaction(signer)) => Ok(OriginFor::::signed(signer)), + _ => Err(BadOrigin.into()), + } + } } /// The address used to call the runtime's pallets dispatchables @@ -2045,16 +2215,35 @@ sp_api::decl_runtime_apis! { } } -/// This macro wraps substrate's `impl_runtime_apis!` and implements `pallet_revive` runtime APIs. +/// This macro wraps substrate's `impl_runtime_apis!` and implements `pallet_revive` runtime APIs +/// and other required traits. +/// +/// # Note +/// +/// This also implements [`SetWeightLimit`] for the runtime call. /// /// # Parameters /// - `$Runtime`: The runtime type to implement the APIs for. +/// - `$Revive`: The name under which revive is declared in `construct_runtime`. /// - `$Executive`: The Executive type of the runtime. /// - `$EthExtra`: Type for additional Ethereum runtime extension. /// - `$($rest:tt)*`: Remaining input to be forwarded to the underlying `impl_runtime_apis!`. #[macro_export] -macro_rules! impl_runtime_apis_plus_revive { - ($Runtime: ty, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => { +macro_rules! impl_runtime_apis_plus_revive_traits { + ($Runtime: ty, $Revive: ident, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => { + + impl $crate::evm::runtime::SetWeightLimit for RuntimeCall { + fn set_weight_limit(&mut self, weight_limit: Weight) { + use $crate::pallet::Call as ReviveCall; + match self { + Self::$Revive( + ReviveCall::eth_call{ gas_limit, .. } | + ReviveCall::eth_instantiate_with_code{ gas_limit, .. } + ) => *gas_limit = weight_limit, + _ => (), + } + } + } impl_runtime_apis! { $($rest)* @@ -2095,30 +2284,7 @@ macro_rules! impl_runtime_apis_plus_revive { sp_runtime::traits::TransactionExtension, sp_runtime::traits::Block as BlockT }; - - let tx_fee = |call: ::RuntimeCall, dispatch_call: ::RuntimeCall| { - use $crate::frame_support::dispatch::GetDispatchInfo; - - // Get the dispatch info of the actual call dispatched - let mut dispatch_info = dispatch_call.get_dispatch_info(); - dispatch_info.extension_weight = - <$EthExtra>::get_eth_extension(0, 0u32.into()).weight(&dispatch_call); - - // Build the extrinsic - let uxt: ::Extrinsic = - $crate::sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); - - // Compute the fee of the extrinsic - $crate::pallet_transaction_payment::Pallet::::compute_fee( - uxt.encoded_size() as u32, - &dispatch_info, - 0u32.into(), - ) - }; - - let blockweights: $crate::BlockWeights = - ::BlockWeights::get(); - $crate::Pallet::::dry_run_eth_transact(tx, blockweights.max_block, tx_fee) + $crate::Pallet::::dry_run_eth_transact(tx) } fn call( @@ -2139,8 +2305,9 @@ macro_rules! impl_runtime_apis_plus_revive { dest, $crate::Pallet::::convert_native_to_evm(value), gas_limit.unwrap_or(blockweights.max_block), - $crate::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), + storage_deposit_limit.unwrap_or(u128::MAX), input_data, + $crate::ExecConfig::new_substrate_tx(), ) } @@ -2162,11 +2329,11 @@ macro_rules! impl_runtime_apis_plus_revive { ::RuntimeOrigin::signed(origin), $crate::Pallet::::convert_native_to_evm(value), gas_limit.unwrap_or(blockweights.max_block), - $crate::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), + storage_deposit_limit.unwrap_or(u128::MAX), code, data, salt, - $crate::BumpNonce::Yes, + $crate::ExecConfig::new_substrate_tx(), ) } diff --git a/substrate/frame/revive/src/precompiles.rs b/substrate/frame/revive/src/precompiles.rs index 83b8b760e16a1..53a4044b1bbc7 100644 --- a/substrate/frame/revive/src/precompiles.rs +++ b/substrate/frame/revive/src/precompiles.rs @@ -602,9 +602,6 @@ pub mod run { where P: Precompile, E: ExtWithInfo, - BalanceOf: Into + TryFrom, - MomentOf: Into, - <::T as frame_system::Config>::Hash: frame_support::traits::IsType, { assert!(P::MATCHER.into_builtin().matches(address)); if P::HAS_CONTRACT_INFO { @@ -619,9 +616,6 @@ pub mod run { pub(crate) fn builtin(ext: &mut E, address: &[u8; 20], input: Vec) -> ExecResult where E: ExtWithInfo, - BalanceOf: Into + TryFrom, - MomentOf: Into, - <::T as frame_system::Config>::Hash: frame_support::traits::IsType, { let precompile = >::get(address) .ok_or(DispatchError::from("No pre-compile at address"))?; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 28cf48c00871d..d3a60478c2b2a 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -23,46 +23,12 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::weights::Weight; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; -use sp_arithmetic::traits::Bounded; use sp_core::Get; use sp_runtime::{ traits::{One, Saturating, Zero}, DispatchError, RuntimeDebug, }; -#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum DepositLimit { - /// Allows bypassing all balance transfer checks. - UnsafeOnlyForDryRun, - - /// Specifies a maximum allowable balance for a deposit. - Balance(Balance), -} - -impl DepositLimit { - pub fn is_unchecked(&self) -> bool { - match self { - Self::UnsafeOnlyForDryRun => true, - _ => false, - } - } -} - -impl From for DepositLimit { - fn from(value: T) -> Self { - Self::Balance(value) - } -} - -impl DepositLimit { - pub fn limit(&self) -> T { - match self { - Self::UnsafeOnlyForDryRun => T::max_value(), - Self::Balance(limit) => *limit, - } - } -} - /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and /// `ContractsApi::instantiate`. /// @@ -160,10 +126,7 @@ impl BalanceWithDust { /// Creates a new `BalanceWithDust` from the given EVM value. pub fn from_value( value: U256, - ) -> Result>, BalanceConversionError> - where - BalanceOf: TryFrom, - { + ) -> Result>, BalanceConversionError> { if value.is_zero() { return Ok(Default::default()) } @@ -356,22 +319,49 @@ where } } -/// Indicates whether the account nonce should be incremented after instantiating a new contract. -/// -/// In Substrate, where transactions can be batched, the account's nonce should be incremented after -/// each instantiation, ensuring that each instantiation uses a unique nonce. -/// -/// For transactions sent from Ethereum wallets, which cannot be batched, the nonce should only be -/// incremented once. In these cases, Use `BumpNonce::No` to suppress an extra nonce increment. -/// -/// Note: -/// The origin's nonce is already incremented pre-dispatch by the `CheckNonce` transaction -/// extension. -pub enum BumpNonce { - /// Do not increment the nonce after contract instantiation - No, - /// Increment the nonce after contract instantiation - Yes, +/// `Stack` wide configuration options. +#[derive(Debug, Clone)] +pub struct ExecConfig { + /// Indicates whether the account nonce should be incremented after instantiating a new + /// contract. + /// + /// In Substrate, where transactions can be batched, the account's nonce should be incremented + /// after each instantiation, ensuring that each instantiation uses a unique nonce. + /// + /// For transactions sent from Ethereum wallets, which cannot be batched, the nonce should only + /// be incremented once. In these cases, set this to `false` to suppress an extra nonce + /// increment. + /// + /// Note: + /// The origin's nonce is already incremented pre-dispatch by the `CheckNonce` transaction + /// extension. + /// + /// This does not apply to contract initiated instantatiations. Those will always bump the + /// instantiating contract's nonce. + pub bump_nonce: bool, + /// Whether deposits will be withdrawn from the pallet_transaction_payment credit (true) or + /// free balance (false). + pub collect_deposit_from_hold: bool, + /// The gas price that was chosen for this transaction. + /// + /// It is determined when transforming `eth_transact` into a proper extrinsic. + pub effective_gas_price: Option, +} + +impl ExecConfig { + /// Create a default config appropriate when the call originated from a subtrate tx. + pub fn new_substrate_tx() -> Self { + Self { bump_nonce: true, collect_deposit_from_hold: false, effective_gas_price: None } + } + + /// Create a default config appropriate when the call originated from a ethereum tx. + pub fn new_eth_tx(effective_gas_price: U256) -> Self { + Self { + bump_nonce: false, + collect_deposit_from_hold: true, + effective_gas_price: Some(effective_gas_price), + } + } } /// Indicates whether the code was removed after the last refcount was decremented. diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index f002b1f8b108d..3072d69c0b659 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -18,15 +18,15 @@ //! This module contains functions to meter the storage deposit. use crate::{ - storage::ContractInfo, AccountIdOf, BalanceOf, Config, Error, HoldReason, Inspect, Origin, - StorageDeposit as Deposit, System, LOG_TARGET, + storage::ContractInfo, AccountIdOf, BalanceOf, Config, Error, ExecConfig, ExecOrigin as Origin, + HoldReason, Inspect, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; use frame_support::{ traits::{ - fungible::{Mutate, MutateHold}, - tokens::{Fortitude, Fortitude::Polite, Precision, Preservation, Restriction}, + fungible::Mutate, + tokens::{Fortitude::Polite, Preservation}, Get, }, DefaultNoBound, RuntimeDebugNoBound, @@ -65,6 +65,7 @@ pub trait Ext { contract: &T::AccountId, amount: &DepositOf, state: &ContractState, + exec_config: &ExecConfig, ) -> Result<(), DispatchError>; } @@ -364,27 +365,23 @@ where pub fn try_into_deposit( self, origin: &Origin, - skip_transfer: bool, + exec_config: &ExecConfig, ) -> Result, DispatchError> { - if !skip_transfer { - // Only refund or charge deposit if the origin is not root. - let origin = match origin { - Origin::Root => return Ok(Deposit::Charge(Zero::zero())), - Origin::Signed(o) => o, - }; - let try_charge = || { - for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) - { - E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; - } - for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) - { - E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; - } - Ok(()) - }; - try_charge().map_err(|_: DispatchError| >::StorageDepositNotEnoughFunds)?; - } + // Only refund or charge deposit if the origin is not root. + let origin = match origin { + Origin::Root => return Ok(Deposit::Charge(Zero::zero())), + Origin::Signed(o) => o, + }; + let try_charge = || { + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state, exec_config)?; + } + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state, exec_config)?; + } + Ok(()) + }; + try_charge().map_err(|_: DispatchError| >::StorageDepositNotEnoughFunds)?; Ok(self.total_deposit) } @@ -456,29 +453,26 @@ impl Ext for ReservingExt { contract: &T::AccountId, amount: &DepositOf, state: &ContractState, + exec_config: &ExecConfig, ) -> Result<(), DispatchError> { match amount { Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()), Deposit::Charge(amount) => { - T::Currency::transfer_and_hold( - &HoldReason::StorageDepositReserve.into(), + >::charge_deposit( + Some(HoldReason::StorageDepositReserve), origin, contract, *amount, - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite, + exec_config, )?; }, Deposit::Refund(amount) => { - let transferred = T::Currency::transfer_on_hold( - &HoldReason::StorageDepositReserve.into(), + let transferred = >::refund_deposit( + HoldReason::StorageDepositReserve, contract, origin, *amount, - Precision::BestEffort, - Restriction::Free, - Fortitude::Polite, + exec_config, )?; if transferred < *amount { @@ -551,6 +545,7 @@ mod tests { contract: &AccountIdOf, amount: &DepositOf, state: &ContractState, + _exec_config: &ExecConfig, ) -> Result<(), DispatchError> { TestExtTestValue::mutate(|ext| { ext.charges.push(Charge { @@ -747,7 +742,9 @@ mod tests { meter.absorb(nested0, &BOB, Some(&mut nested0_info)); assert_eq!( - meter.try_into_deposit(&test_case.origin, false).unwrap(), + meter + .try_into_deposit(&test_case.origin, &ExecConfig::new_substrate_tx()) + .unwrap(), test_case.deposit ); @@ -820,7 +817,9 @@ mod tests { meter.absorb(nested0, &BOB, None); assert_eq!( - meter.try_into_deposit(&test_case.origin, false).unwrap(), + meter + .try_into_deposit(&test_case.origin, &ExecConfig::new_substrate_tx()) + .unwrap(), test_case.deposit ); assert_eq!(TestExtTestValue::get(), test_case.expected) diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index a1aeea6cdfa67..dd8fd0b6e0de6 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -17,8 +17,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ - address::AddressMapper, AccountIdOf, BalanceOf, BumpNonce, Code, Config, ContractResult, - DepositLimit, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, U256, + address::AddressMapper, AccountIdOf, BalanceOf, Code, Config, ContractResult, ExecConfig, + ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, U256, }; use alloc::{vec, vec::Vec}; use frame_support::pallet_prelude::DispatchResultWithPostInfo; @@ -48,12 +48,7 @@ macro_rules! builder { } #[allow(dead_code)] - impl $name - where - BalanceOf: Into + TryFrom, - crate::MomentOf: Into, - T::Hash: frame_support::traits::IsType, - { + impl $name { $( #[doc = concat!("Set the ", stringify!($field))] pub fn $field(mut self, value: $type) -> Self { @@ -134,11 +129,11 @@ builder!( origin: OriginFor, evm_value: U256, gas_limit: Weight, - storage_deposit_limit: DepositLimit>, + storage_deposit_limit: BalanceOf, code: Code, data: Vec, salt: Option<[u8; 32]>, - bump_nonce: BumpNonce, + exec_config: ExecConfig, ) -> ContractResult>; pub fn concat_evm_data(mut self, more_data: &[u8]) -> Self { @@ -176,11 +171,11 @@ builder!( origin, evm_value: Default::default(), gas_limit: GAS_LIMIT, - storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), + storage_deposit_limit: deposit_limit::(), code, data: vec![], salt: Some([0; 32]), - bump_nonce: BumpNonce::Yes, + exec_config: ExecConfig::new_substrate_tx(), } } ); @@ -214,8 +209,9 @@ builder!( dest: H160, evm_value: U256, gas_limit: Weight, - storage_deposit_limit: DepositLimit>, + storage_deposit_limit: BalanceOf, data: Vec, + exec_config: ExecConfig, ) -> ContractResult>; /// Set the call's evm_value using a native_value amount. @@ -236,8 +232,9 @@ builder!( dest, evm_value: Default::default(), gas_limit: GAS_LIMIT, - storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), + storage_deposit_limit: deposit_limit::(), data: vec![], + exec_config: ExecConfig::new_substrate_tx(), } } ); @@ -250,6 +247,8 @@ builder!( gas_limit: Weight, storage_deposit_limit: BalanceOf, data: Vec, + effective_gas_price: U256, + encoded_len: u32, ) -> DispatchResultWithPostInfo; /// Create a [`EthCallBuilder`] with default values. @@ -261,6 +260,8 @@ builder!( gas_limit: GAS_LIMIT, storage_deposit_limit: deposit_limit::(), data: vec![], + effective_gas_price: 0u32.into(), + encoded_len: 0, } } ); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 9dda6f55cadb9..f202477ebf518 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -22,28 +22,58 @@ mod sol; use crate::{ self as pallet_revive, + evm::{ + fees::{BlockRatioFee, Info as FeeInfo}, + runtime::{EthExtra, SetWeightLimit}, + }, genesis::{Account, ContractData}, test_utils::*, - AccountId32Mapper, AddressMapper, BalanceOf, BalanceWithDust, CodeInfoOf, Config, - GenesisConfig, Origin, Pallet, PristineCode, + AccountId32Mapper, AddressMapper, BalanceOf, BalanceWithDust, Call, CodeInfoOf, Config, + ExecOrigin as Origin, GenesisConfig, OriginFor, Pallet, PristineCode, }; use frame_support::{ assert_ok, derive_impl, pallet_prelude::EnsureOrigin, parameter_types, traits::{ConstU32, ConstU64, FindAuthor, StorageVersion}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, Weight}, }; use pallet_revive_fixtures::compile_module; -use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; +use pallet_transaction_payment::{ChargeTransactionPayment, ConstFeeMultiplier, Multiplier}; use sp_core::U256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ + generic::Header, traits::{BlakeTwo256, Convert, IdentityLookup, One}, - AccountId32, BuildStorage, Perbill, + AccountId32, BuildStorage, MultiAddress, MultiSignature, Perbill, }; -type Block = frame_system::mocking::MockBlock; +pub type Address = MultiAddress; +pub type Block = sp_runtime::generic::Block, UncheckedExtrinsic>; +pub type Signature = MultiSignature; +pub type SignedExtra = ( + frame_system::CheckNonce, + ChargeTransactionPayment, + crate::evm::tx_extension::SetOrigin, +); +pub type UncheckedExtrinsic = + crate::evm::runtime::UncheckedExtrinsic; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EthExtraImpl; + +impl EthExtra for EthExtraImpl { + type Config = Test; + type Extension = SignedExtra; + + fn get_eth_extension(nonce: u32, tip: BalanceOf) -> Self::Extension { + ( + frame_system::CheckNonce::from(nonce), + ChargeTransactionPayment::from(tip), + crate::evm::tx_extension::SetOrigin::::new_from_eth_transaction(), + ) + } +} frame_support::construct_runtime!( pub enum Test @@ -227,7 +257,7 @@ impl Test { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - Weight::from_parts(2 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + Weight::from_parts(2 * WEIGHT_REF_TIME_PER_SECOND, 10 * 1024 * 1024), ); pub static ExistentialDeposit: u64 = 1; } @@ -281,7 +311,7 @@ parameter_types! { #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Test { type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = IdentityFee<::Balance>; + type WeightToFee = BlockRatioFee<1, 1, Self>; type LengthToFee = FixedFee<100, ::Balance>; type FeeMultiplierUpdate = ConstFeeMultiplier; } @@ -314,7 +344,7 @@ where { type Success = T::AccountId; - fn try_origin(o: T::RuntimeOrigin) -> Result { + fn try_origin(o: OriginFor) -> Result> { let who = as EnsureOrigin<_>>::try_origin(o.clone())?; if matches!(A::get(), Some(a) if who != a) { return Err(o); @@ -324,7 +354,7 @@ where } #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin() -> Result { + fn try_successful_origin() -> Result, ()> { Err(()) } } @@ -347,6 +377,7 @@ impl FindAuthor<::AccountId> for Test { impl Config for Test { type Time = Timestamp; type AddressMapper = AccountId32Mapper; + type Balance = u64; type Currency = Balances; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; @@ -358,9 +389,10 @@ impl Config for Test { type ChainId = ChainId; type FindAuthor = Test; type Precompiles = (precompiles::WithInfo, precompiles::NoInfo); + type FeeInfo = FeeInfo; } -impl TryFrom for crate::Call { +impl TryFrom for Call { type Error = (); fn try_from(value: RuntimeCall) -> Result { @@ -371,6 +403,18 @@ impl TryFrom for crate::Call { } } +impl SetWeightLimit for RuntimeCall { + fn set_weight_limit(&mut self, weight_limit: Weight) { + match self { + Self::Contracts( + Call::eth_call { gas_limit, .. } | + Call::eth_instantiate_with_code { gas_limit, .. }, + ) => *gas_limit = weight_limit, + _ => (), + } + } +} + pub struct ExtBuilder { existential_deposit: u64, storage_version: Option, diff --git a/substrate/frame/revive/src/tests/precompiles.rs b/substrate/frame/revive/src/tests/precompiles.rs index b10e83e1d1fa7..bb1840d5f762c 100644 --- a/substrate/frame/revive/src/tests/precompiles.rs +++ b/substrate/frame/revive/src/tests/precompiles.rs @@ -20,7 +20,7 @@ use crate::{ exec::{ErrorOrigin, ExecError}, precompiles::{AddressMatcher, Error, Ext, ExtWithInfo, Precompile, Token}, - Config, DispatchError, Origin, Weight, U256, + Config, DispatchError, ExecOrigin as Origin, Weight, U256, }; use alloc::vec::Vec; use alloy_core::{ diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index d8c51ab03cccb..fa4161bfe58e3 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -24,7 +24,7 @@ use super::{ use crate::{ address::{create1, create2, AddressMapper}, assert_refcount, assert_return_code, - evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, + evm::{fees::InfoT, CallTrace, CallTracer, CallType}, exec::Key, limits, precompiles::alloy::sol_types::{ @@ -40,9 +40,8 @@ use crate::{ }, tracing::trace, weights::WeightInfo, - AccountInfo, AccountInfoOf, BalanceWithDust, BumpNonce, Code, Config, ContractInfo, - DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Pallet, PristineCode, - StorageDeposit, H160, + AccountInfo, AccountInfoOf, BalanceWithDust, Code, Config, ContractInfo, DeletionQueueCounter, + Error, ExecConfig, HoldReason, Origin, Pallet, PristineCode, StorageDeposit, H160, }; use assert_matches::assert_matches; use codec::Encode; @@ -50,7 +49,7 @@ use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_noop, assert_ok, storage::child, traits::{ - fungible::{BalancedHold, Inspect, Mutate}, + fungible::{Balanced, BalancedHold, Inspect, Mutate}, tokens::Preservation, OnIdle, OnInitialize, }, @@ -189,9 +188,14 @@ fn eth_call_transfer_with_dust_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + ::FeeInfo::deposit_txfee(::Currency::issue(5_000_000_000)); + let balance = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(100, 10)); - assert_ok!(builder::eth_call(addr).value(balance).build()); + assert_ok!(builder::eth_call(addr) + .origin(Origin::EthTransaction(ALICE).into()) + .value(balance) + .build()); assert_eq!(Pallet::::evm_balance(&addr), balance); }); @@ -243,17 +247,14 @@ fn deposit_limit_enforced_on_plain_transfer() { // sending balance to a new account should fail when the limit is lower than the ed let result = builder::bare_call(CHARLIE_ADDR) .native_value(1) - .storage_deposit_limit(190.into()) + .storage_deposit_limit(190) .build(); assert_err!(result.result, >::StorageDepositLimitExhausted); assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); assert_eq!(get_balance(&CHARLIE), 0); // works when the account is prefunded - let result = builder::bare_call(BOB_ADDR) - .native_value(1) - .storage_deposit_limit(0.into()) - .build(); + let result = builder::bare_call(BOB_ADDR).native_value(1).storage_deposit_limit(0).build(); assert_ok!(result.result); assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); assert_eq!(get_balance(&BOB), 1_000_001); @@ -261,7 +262,7 @@ fn deposit_limit_enforced_on_plain_transfer() { // also works allowing enough deposit let result = builder::bare_call(CHARLIE_ADDR) .native_value(1) - .storage_deposit_limit(200.into()) + .storage_deposit_limit(200) .build(); assert_ok!(result.result); assert_eq!(result.storage_deposit, StorageDeposit::Charge(200)); @@ -2653,7 +2654,7 @@ fn deposit_limit_in_nested_calls() { // that the nested call should have a deposit limit of at least 2 Balance. The // sub-call should be rolled back, which is covered by the next test case. let ret = builder::bare_call(addr_caller) - .storage_deposit_limit(DepositLimit::Balance(u64::MAX)) + .storage_deposit_limit(u64::MAX) .data((102u32, &addr_callee, U256::from(1u64)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); @@ -2731,7 +2732,7 @@ fn deposit_limit_in_nested_instantiate() { // Sub calls return first to they are checked first. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(0)) + .storage_deposit_limit(0) .data((&code_hash_callee, 100u32, &U256::MAX.to_little_endian()).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); @@ -2744,7 +2745,7 @@ fn deposit_limit_in_nested_instantiate() { // succeeds but the parent call runs out of storage. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit)) + .storage_deposit_limit(callee_min_deposit) .data((&code_hash_callee, 0u32, &U256::MAX.to_little_endian()).encode()) .build(); assert_err!(ret.result, >::StorageDepositLimitExhausted); @@ -2756,7 +2757,7 @@ fn deposit_limit_in_nested_instantiate() { // Same as above but stores one byte in both caller and callee. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(caller_min_deposit + 1)) + .storage_deposit_limit(caller_min_deposit + 1) .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); @@ -2768,7 +2769,7 @@ fn deposit_limit_in_nested_instantiate() { // Same as above but stores one byte in both caller and callee. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit + 1)) + .storage_deposit_limit(callee_min_deposit + 1) .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) .build(); assert_err!(ret.result, >::StorageDepositLimitExhausted); @@ -3361,7 +3362,10 @@ fn gas_price_api_works() { // Call the contract: It echoes back the value returned by the gas price API. let received = builder::bare_call(addr).build_and_unwrap_result(); assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(u64::from_le_bytes(received.data[..].try_into().unwrap()), u64::from(GAS_PRICE)); + assert_eq!( + u64::from_le_bytes(received.data[..].try_into().unwrap()), + u64::try_from(>::evm_gas_price()).unwrap(), + ); }); } @@ -3897,138 +3901,6 @@ fn recovery_works() { }); } -#[test] -fn skip_transfer_works() { - let (code_caller, _) = compile_module("call").unwrap(); - let (code, _) = compile_module("store_call").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - ::Currency::set_balance(&BOB, 0); - - // when gas is some (transfers enabled): bob has no money: fail - assert_err!( - Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - input: code.clone().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ), - EthTransactError::Message(format!( - "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" - )) - ); - - // no gas specified (all transfers are skipped): even without money bob can deploy - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - input: code.clone().into(), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(code_caller)).build_and_unwrap_contract(); - - // call directly: fails with enabled transfers - assert_err!( - Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(addr), - input: 0u32.encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ), - EthTransactError::Message(format!( - "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" - )) - ); - - // fails to call through other contract - // we didn't roll back the storage changes done by the previous - // call. So the item already exists. We simply increase the size of - // the storage item to incur some deposits (which bob can't pay). - assert!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (1u32, &addr).encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ) - .is_err(),); - - // works when no gas is specified (skip transfer) - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(addr), - input: 2u32.encode().into(), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - // call through contract works when transfers are skipped - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (3u32, &addr).encode().into(), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - // works with transfers enabled if we don't incur a storage cost - // we shrink the item so its actually a refund - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (2u32, &addr).encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - // fails when trying to increase the storage item size - assert!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (3u32, &addr).encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ) - .is_err()); - }); -} - #[test] fn gas_limit_api_works() { let (code, _) = compile_module("gas_limit").unwrap(); @@ -4789,12 +4661,14 @@ fn bump_nonce_once_works() { let _ = ::Currency::set_balance(&ALICE, 1_000_000); frame_system::Account::::mutate(&ALICE, |account| account.nonce = 1); + let mut do_not_bump = ExecConfig::new_substrate_tx(); + do_not_bump.bump_nonce = false; + let _ = ::Currency::set_balance(&BOB, 1_000_000); frame_system::Account::::mutate(&BOB, |account| account.nonce = 1); builder::bare_instantiate(Code::Upload(code.clone())) .origin(RuntimeOrigin::signed(ALICE)) - .bump_nonce(BumpNonce::Yes) .salt(None) .build_and_unwrap_result(); assert_eq!(System::account_nonce(&ALICE), 2); @@ -4802,7 +4676,6 @@ fn bump_nonce_once_works() { // instantiate again is ok let result = builder::bare_instantiate(Code::Existing(hash)) .origin(RuntimeOrigin::signed(ALICE)) - .bump_nonce(BumpNonce::Yes) .salt(None) .build() .result; @@ -4810,7 +4683,7 @@ fn bump_nonce_once_works() { builder::bare_instantiate(Code::Upload(code.clone())) .origin(RuntimeOrigin::signed(BOB)) - .bump_nonce(BumpNonce::No) + .exec_config(do_not_bump.clone()) .salt(None) .build_and_unwrap_result(); assert_eq!(System::account_nonce(&BOB), 1); @@ -4818,7 +4691,7 @@ fn bump_nonce_once_works() { // instantiate again should fail let err = builder::bare_instantiate(Code::Upload(code)) .origin(RuntimeOrigin::signed(BOB)) - .bump_nonce(BumpNonce::No) + .exec_config(do_not_bump) .salt(None) .build() .result @@ -4993,6 +4866,43 @@ fn return_data_limit_is_enforced() { }); } +#[test] +fn storage_deposit_from_hold_works() { + let ed = 200; + let (binary, code_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(ed).build().execute_with(|| { + let hold_initial = 500_000; + ::Currency::set_balance(&ALICE, 1_000_000); + ::FeeInfo::deposit_txfee(::Currency::issue(hold_initial)); + let mut exec_config = ExecConfig::new_substrate_tx(); + exec_config.collect_deposit_from_hold = true; + + // Instantiate the BOB contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .exec_config(exec_config) + .native_value(1_000) + .build_and_unwrap_contract(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + let account = ::AddressMapper::to_account_id(&addr); + let base_deposit = contract_base_deposit(&addr); + let code_deposit = get_code_deposit(&code_hash); + assert!(base_deposit > 0); + assert!(code_deposit > 0); + + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), + base_deposit, + ); + assert_eq!( + ::FeeInfo::remaining_txfee(), + hold_initial - base_deposit - code_deposit - ed, + ); + }); +} + /// EIP-3607 /// Test that a top-level signed transaction that uses a contract address as the signer is rejected. #[test] diff --git a/substrate/frame/revive/src/tests/sol/tx_info.rs b/substrate/frame/revive/src/tests/sol/tx_info.rs index 02f2296e8324c..86d819d29c746 100644 --- a/substrate/frame/revive/src/tests/sol/tx_info.rs +++ b/substrate/frame/revive/src/tests/sol/tx_info.rs @@ -18,10 +18,10 @@ //! The pallet-revive shared VM integration test suite. use crate::{ - evm::runtime::GAS_PRICE, test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, ExtBuilder, Test}, - Code, Config, + vm::evm::U256Converter, + Code, Config, Pallet, }; use alloy_core::sol_types::{SolCall, SolInterface}; @@ -48,7 +48,7 @@ fn gasprice_works(fixture_type: FixtureType) { ) .build_and_unwrap_result(); let decoded = TransactionInfo::gaspriceCall::abi_decode_returns(&result.data).unwrap(); - assert_eq!(GAS_PRICE as u64, decoded); + assert_eq!(>::evm_gas_price().into_revm_u256(), decoded); }); } diff --git a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs index 3060a4e4f83a5..575b82ebc6bf6 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs @@ -15,8 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{address::AddressMapper, evm::runtime::GAS_PRICE, vm::RuntimeCosts}; -use revm::primitives::{Address, U256}; +use crate::{ + address::AddressMapper, + vm::{evm::U256Converter, RuntimeCosts}, +}; +use revm::primitives::Address; use super::Context; use crate::{vm::Ext, Config}; @@ -26,7 +29,7 @@ use crate::{vm::Ext, Config}; /// Gets the gas price of the originating transaction. pub fn gasprice<'ext, E: Ext>(context: Context<'_, 'ext, E>) { gas!(context.interpreter, RuntimeCosts::GasPrice); - push!(context.interpreter, U256::from(GAS_PRICE)); + push!(context.interpreter, context.interpreter.extend.effective_gas_price().into_revm_u256()); } /// Implements the ORIGIN instruction. diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index dc01b7879ec3e..39efa6432dd52 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -28,10 +28,10 @@ use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, frame_support::{ensure, error::BadOrigin, traits::tokens::Restriction}, gas::{GasMeter, Token}, - storage::meter::Diff, + storage::meter::{Diff, NestedMeter}, weights::WeightInfo, - AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecError, HoldReason, - PristineCode, Weight, LOG_TARGET, + AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecConfig, ExecError, + HoldReason, Pallet, PristineCode, StorageDeposit, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -39,11 +39,11 @@ use frame_support::{ dispatch::DispatchResult, traits::{ fungible::MutateHold, - tokens::{Fortitude, Precision, Preservation}, + tokens::{Fortitude, Precision}, }, }; use pallet_revive_uapi::ReturnErrorCode; -use sp_core::{Get, H256, U256}; +use sp_core::{Get, H256}; use sp_runtime::DispatchError; /// Validated Vm module ready for execution. @@ -162,10 +162,7 @@ pub fn code_load_weight(code_len: u32) -> Weight { Token::::weight(&CodeLoadToken { code_len, code_type: BytecodeType::Pvm }) } -impl ContractBlob -where - BalanceOf: Into + TryFrom, -{ +impl ContractBlob { /// Remove the code from storage and refund the deposit to its owner. /// /// Applies all necessary checks before removing the code. @@ -176,7 +173,7 @@ where ensure!(&code_info.owner == origin, BadOrigin); T::Currency::transfer_on_hold( &HoldReason::CodeUploadDepositReserve.into(), - &crate::Pallet::::account_id(), + &Pallet::::account_id(), &code_info.owner, code_info.deposit, Precision::Exact, @@ -194,7 +191,11 @@ where } /// Puts the module blob into storage, and returns the deposit collected for the storage. - pub fn store_code(&mut self, skip_transfer: bool) -> Result, Error> { + pub fn store_code( + &mut self, + exec_config: &ExecConfig, + storage_meter: Option<&mut NestedMeter>, + ) -> Result, DispatchError> { let code_hash = *self.code_hash(); ensure!(code_hash != H256::zero(), >::CodeNotFound); @@ -209,20 +210,20 @@ where None => { let deposit = self.code_info.deposit; - if !skip_transfer { - T::Currency::transfer_and_hold( - &HoldReason::CodeUploadDepositReserve.into(), + >::charge_deposit( + Some(HoldReason::CodeUploadDepositReserve), &self.code_info.owner, - &crate::Pallet::::account_id(), + &Pallet::::account_id(), deposit, - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite, + &exec_config, ) .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to hold store code deposit {deposit:?} for owner: {:?}: {err:?}", self.code_info.owner); >::StorageDepositNotEnoughFunds })?; + + if let Some(meter) = storage_meter { + meter.record_charge(&StorageDeposit::Charge(deposit))?; } >::insert(code_hash, &self.code.to_vec()); @@ -321,10 +322,7 @@ impl CodeInfo { } } -impl Executable for ContractBlob -where - BalanceOf: Into + TryFrom, -{ +impl Executable for ContractBlob { fn from_storage(code_hash: H256, gas_meter: &mut GasMeter) -> Result { let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; gas_meter.charge(CodeLoadToken::from_code_info(&code_info))?; diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index 79902ef69b20e..c6d77c0b5e59f 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -23,13 +23,12 @@ pub mod env; pub use env::SyscallDoc; use crate::{ - evm::runtime::GAS_PRICE, exec::{ExecError, ExecResult, Ext, Key}, gas::ChargedAmount, limits, precompiles::{All as AllPrecompiles, Precompiles}, primitives::ExecReturnValue, - BalanceOf, Code, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, + Code, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, }; use alloc::{vec, vec::Vec}; use codec::Encode; @@ -894,11 +893,7 @@ pub struct PreparedCall<'a, E: Ext> { runtime: Runtime<'a, E, polkavm::RawInstance>, } -impl<'a, E: Ext> PreparedCall<'a, E> -where - BalanceOf: Into, - BalanceOf: TryFrom, -{ +impl<'a, E: Ext> PreparedCall<'a, E> { pub fn call(mut self) -> ExecResult { let exec_result = loop { let interrupt = self.instance.run(); diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index 3ed466041a18a..49f54ce3532d4 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -23,17 +23,16 @@ use crate::{ limits, primitives::ExecReturnValue, vm::{calculate_code_deposit, BytecodeType, ExportedFunction, RuntimeCosts}, - AccountIdOf, BalanceOf, CodeInfo, Config, ContractBlob, Error, Weight, SENTINEL, + AccountIdOf, CodeInfo, Config, ContractBlob, Error, Weight, SENTINEL, }; use alloc::vec::Vec; -use codec::Encode; use core::mem; use frame_support::traits::Get; use pallet_revive_proc_macro::define_env; use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags}; use sp_core::{H160, H256, U256}; use sp_io::hashing::keccak_256; -use sp_runtime::DispatchError; +use sp_runtime::{DispatchError, SaturatedConversion}; impl ContractBlob { /// Compile and instantiate contract. @@ -99,10 +98,7 @@ impl ContractBlob { } } -impl ContractBlob -where - BalanceOf: Into + TryFrom, -{ +impl ContractBlob { /// We only check for size and nothing else when the code is uploaded. pub fn from_pvm_code(code: Vec, owner: AccountIdOf) -> Result { // We do validation only when new code is deployed. This allows us to increase @@ -548,27 +544,6 @@ pub mod env { )?) } - /// Stores the price for the specified amount of weight into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. - #[stable] - fn weight_to_fee( - &mut self, - memory: &mut M, - ref_time_limit: u64, - proof_size_limit: u64, - out_ptr: u32, - ) -> Result<(), TrapReason> { - let weight = Weight::from_parts(ref_time_limit, proof_size_limit); - self.charge_gas(RuntimeCosts::WeightToFee)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.get_weight_price(weight).encode(), - false, - already_charged, - )?) - } - /// Stores the immutable data into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. #[stable] @@ -674,7 +649,7 @@ pub mod env { #[stable] fn gas_price(&mut self, memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::GasPrice)?; - Ok(GAS_PRICE.into()) + Ok(self.ext.effective_gas_price().saturated_into()) } /// Returns the simulated ethereum `BASEFEE` value. diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs index f96c9467e00b0..d146061723eec 100644 --- a/substrate/frame/revive/src/vm/runtime_costs.rs +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -92,8 +92,6 @@ pub enum RuntimeCosts { Now, /// Weight of calling `seal_gas_limit`. GasLimit, - /// Weight of calling `seal_weight_to_fee`. - WeightToFee, /// Weight of calling `seal_terminate`. Terminate { code_removed: bool }, /// Weight of calling `seal_deposit_event` with the given number of topics and event size. @@ -256,7 +254,6 @@ impl Token for RuntimeCosts { BaseFee => T::WeightInfo::seal_base_fee(), Now => T::WeightInfo::seal_now(), GasLimit => T::WeightInfo::seal_gas_limit(), - WeightToFee => T::WeightInfo::seal_weight_to_fee(), Terminate { code_removed } => T::WeightInfo::seal_terminate(code_removed.into()), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), SetStorage { new_bytes, old_bytes } => { diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 1f1cdc3095691..186f07bbf9da5 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -114,7 +114,6 @@ pub trait WeightInfo { fn seal_block_author() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; - fn seal_weight_to_fee() -> Weight; fn seal_copy_to_contract(n: u32, ) -> Weight; fn seal_call_data_load() -> Weight; fn seal_call_data_copy(n: u32, ) -> Weight; @@ -722,14 +721,7 @@ impl WeightInfo for SubstrateWeight { // Minimum execution time: 239_000 picoseconds. Weight::from_parts(276_000, 0) } - fn seal_weight_to_fee() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 1_595_000 picoseconds. - Weight::from_parts(1_665_000, 0) - } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -1875,14 +1867,7 @@ impl WeightInfo for () { // Minimum execution time: 239_000 picoseconds. Weight::from_parts(276_000, 0) } - fn seal_weight_to_fee() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 1_595_000 picoseconds. - Weight::from_parts(1_665_000, 0) - } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 71da29d815fa6..98918227d9eb8 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -397,15 +397,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the transferred value. fn value_transferred(output: &mut [u8; 32]); - /// Stores the price for the specified amount of gas into the supplied buffer. - /// - /// # Parameters - /// - /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. - /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. - /// - `output`: A reference to the output data buffer to write the price. - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]); - /// Returns the size of the returned data of the last contract call or instantiation. fn return_data_size() -> u64; diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 27d1c79bb856c..dbc599d8a687e 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -98,7 +98,6 @@ mod sys { pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_size(address_ptr: *const u8) -> u64; pub fn address(out_ptr: *mut u8); - pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn ref_time_left() -> u64; pub fn get_immutable_data(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn set_immutable_data(ptr: *const u8, len: u32); @@ -387,10 +386,6 @@ impl HostFn for HostFnImpl { unsafe { sys::block_author(output.as_mut_ptr()) } } - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { - unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; - } - fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) { unsafe { sys::hash_keccak_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } } diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 8deac03e7998d..774f6db4de657 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -359,7 +359,7 @@ impl Drop for NoDrop { /// Sealed trait that marks a type with a suppressed Drop implementation. /// /// Useful for constraining your storage items types by this bound to make -/// sure they won't runD rop when stored. +/// sure they won't run drop when stored. pub trait SuppressedDrop: sealed::Sealed { /// The wrapped whose drop function is ignored. type Inner;