From 5772b9dbde8f88718ec5c6409f444d6e5b4e4e03 Mon Sep 17 00:00:00 2001 From: PG Herveou <pgherveou@gmail.com> Date: Thu, 23 Jan 2025 10:57:06 +0100 Subject: [PATCH] [pallet-revive] fee estimation fixes (#7281) - Fix the EVM fee cost estimation. The estimation shown in EVM wallet was using Native instead of EVM decimals - Remove the precise code length estimation in dry run call. Over-estimating is fine, since extra gas is refunded anyway. - Ensure that the estimated fee calculated from gas_price x gas use the encoded weight & deposit limit instead of the exact one calculated by the dry-run. Else we can end up with a fee that is lower than the actual fee paid by the user --------- Co-authored-by: command-bot <> --- .../assets/asset-hub-westend/src/lib.rs | 6 +- prdoc/pr_7281.prdoc | 13 ++ substrate/bin/node/runtime/src/lib.rs | 4 + .../rpc/examples/js/src/build-contracts.ts | 2 +- .../rpc/examples/js/src/geth-diff.test.ts | 162 +++++++++--------- .../frame/revive/rpc/examples/js/src/util.ts | 11 +- .../frame/revive/rpc/revive_chain.metadata | Bin 661585 -> 671115 bytes substrate/frame/revive/rpc/src/client.rs | 17 +- .../frame/revive/src/benchmarking/mod.rs | 6 +- substrate/frame/revive/src/evm/gas_encoder.rs | 11 ++ substrate/frame/revive/src/evm/runtime.rs | 64 +++---- substrate/frame/revive/src/exec.rs | 6 +- substrate/frame/revive/src/lib.rs | 129 ++++++++------ substrate/frame/revive/src/primitives.rs | 8 + 14 files changed, 249 insertions(+), 190 deletions(-) create mode 100644 prdoc/pr_7281.prdoc 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 f56c4568f2d..ecbe1fb0e62 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_005, + spec_version: 1_017_006, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -2189,6 +2189,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&address); System::account_nonce(account) diff --git a/prdoc/pr_7281.prdoc b/prdoc/pr_7281.prdoc new file mode 100644 index 00000000000..33e04c419ba --- /dev/null +++ b/prdoc/pr_7281.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] fix eth fee estimation' +doc: +- audience: Runtime Dev + description: |- + Fix EVM fee cost estimation. + The current estimation was shown in Native and not EVM decimal currency. +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 26f4dacf9a1..220929fdfd8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3301,6 +3301,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&address); System::account_nonce(account) diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index f26f275ec3d..b162b8be0ad 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -55,7 +55,7 @@ for (const file of input) { } console.log('Compiling with revive...') - const reviveOut = await compile(input, { bin: 'resolc' }) + const reviveOut = await compile(input) for (const contracts of Object.values(reviveOut.contracts)) { for (const [name, contract] of Object.entries(contracts)) { diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 86b8ec50bd6..2a4ff2edcdf 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -12,62 +12,64 @@ import { ErrorsAbi } from '../abi/Errors' import { FlipperCallerAbi } from '../abi/FlipperCaller' import { FlipperAbi } from '../abi/Flipper' import { Subprocess, spawn } from 'bun' +import { fail } from 'node:assert' const procs: Subprocess[] = [] -beforeAll(async () => { - if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - await (async () => { - killProcessOnPort(8546) - const proc = spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + console.log('Starting geth') + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) - await waitForHealth('http://localhost:8546').catch() - return proc - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: polkadotSdkPath, - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: polkadotSdkPath, - } - ) - await waitForHealth('http://localhost:8545').catch() - return proc - })() - ) - } -}) + await waitForHealth('http://localhost:8546').catch() + return proc + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + console.log('Starting substrate node') + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + console.log('Starting eth-rpc') + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) +} afterEach(() => { jsonRpcErrors.length = 0 @@ -88,7 +90,7 @@ for (const env of envs) { { const hash = await env.serverWallet.deployContract({ abi: ErrorsAbi, - bytecode: getByteCode('errors', env.evm), + bytecode: getByteCode('Errors', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -99,7 +101,7 @@ for (const env of envs) { { const hash = await env.serverWallet.deployContract({ abi: FlipperAbi, - bytecode: getByteCode('flipper', env.evm), + bytecode: getByteCode('Flipper', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -111,7 +113,7 @@ for (const env of envs) { const hash = await env.serverWallet.deployContract({ abi: FlipperCallerAbi, args: [flipperAddr], - bytecode: getByteCode('flipperCaller', env.evm), + bytecode: getByteCode('FlipperCaller', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -121,13 +123,13 @@ for (const env of envs) { }) test('triggerAssertError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerAssertError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -139,13 +141,13 @@ for (const env of envs) { }) test('triggerRevertError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerRevertError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -157,13 +159,13 @@ for (const env of envs) { }) test('triggerDivisionByZero', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerDivisionByZero', }) + expect.assertions(3) } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -177,13 +179,13 @@ for (const env of envs) { }) test('triggerOutOfBoundsError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerOutOfBoundsError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -197,13 +199,13 @@ for (const env of envs) { }) test('triggerCustomError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerCustomError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -215,15 +217,15 @@ for (const env of envs) { }) test('eth_call (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.simulateContract({ + await env.emptyWallet.simulateContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -233,12 +235,15 @@ for (const env of envs) { }) test('eth_call transfer (not enough funds)', async () => { - expect.assertions(3) + const value = parseEther('10') + const balance = await env.emptyWallet.getBalance(env.emptyWallet.account) + expect(balance, 'Balance should be less than 10').toBeLessThan(value) try { - await env.accountWallet.sendTransaction({ + await env.emptyWallet.sendTransaction({ to: '0x75E480dB528101a381Ce68544611C169Ad7EB342', - value: parseEther('10'), + value, }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -248,15 +253,15 @@ for (const env of envs) { }) test('eth_estimate (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -266,15 +271,15 @@ for (const env of envs) { }) test('eth_estimate call caller (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -284,7 +289,6 @@ for (const env of envs) { }) test('eth_estimate (revert)', async () => { - expect.assertions(3) try { await env.serverWallet.estimateContractGas({ address: errorsAddr, @@ -293,6 +297,7 @@ for (const env of envs) { value: parseEther('11'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -313,17 +318,16 @@ for (const env of envs) { }) test('eth_estimate (not enough funds to cover gas specified)', async () => { - expect.assertions(4) + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) + expect(balance).toBe(0n) try { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) - expect(balance).toBe(0n) - - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'setState', args: [true], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -333,7 +337,7 @@ for (const env of envs) { }) test('eth_estimate (no gas specified)', async () => { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) expect(balance).toBe(0n) const data = encodeFunctionData({ @@ -342,12 +346,12 @@ for (const env of envs) { args: [true], }) - await env.accountWallet.request({ + await env.emptyWallet.request({ method: 'eth_estimateGas', params: [ { data, - from: env.accountWallet.account.address, + from: env.emptyWallet.account.address, to: errorsAddr, }, ], diff --git a/substrate/frame/revive/rpc/examples/js/src/util.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts index bdc64eea1ef..2991bdfe636 100644 --- a/substrate/frame/revive/rpc/examples/js/src/util.ts +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -85,7 +85,16 @@ export async function createEnv(name: 'geth' | 'kitchensink') { chain, }).extend(publicActions) - return { serverWallet, accountWallet, evm: name == 'geth' } + const emptyWallet = createWalletClient({ + account: privateKeyToAccount( + '0x4450c571bae82da0528ecf76fcf7079e12ecc46dc873c9cacb6db8b75ed22f41', + { nonceManager } + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, emptyWallet, accountWallet, evm: name == 'geth' } } export function wait(ms: number) { diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index a03c95b4944f663225642b1678ef66aaccec3fb5..ff365892a265e1fd9b59f6811ea1c59642b65d91 100644 GIT binary patch delta 25763 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 B%I^RG delta 15939 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 diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 7a72f8e26b0..440972c7a68 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -18,7 +18,6 @@ //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ extract_receipts_from_block, - runtime::gas_from_fee, subxt_client::{ revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, @@ -649,8 +648,7 @@ impl Client { hydrated_transactions: bool, ) -> Result<Block, ClientError> { let runtime_api = self.api.runtime_api().at(block.hash()); - let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; - let gas_limit = gas_from_fee(max_fee); + let gas_limit = Self::block_gas_limit(&runtime_api).await?; let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -695,16 +693,13 @@ impl Client { } /// Convert a weight to a fee. - async fn weight_to_fee( + async fn block_gas_limit( runtime_api: &subxt::runtime_api::RuntimeApi<SrcChainConfig, OnlineClient<SrcChainConfig>>, - weight: Weight, - ) -> Result<Balance, ClientError> { - let payload = subxt_client::apis() - .transaction_payment_api() - .query_weight_to_fee(weight.into()); + ) -> Result<U256, ClientError> { + let payload = subxt_client::apis().revive_api().block_gas_limit(); - let fee = runtime_api.call(payload).await?; - Ok(fee) + let gas_limit = runtime_api.call(payload).await?; + Ok(*gas_limit) } /// Get the chain ID. diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 16bdd6d1a18..a19ed28dd9b 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -27,7 +27,7 @@ use crate::{ exec::{Key, MomentOf}, limits, storage::WriteOutcome, - Pallet as Contracts, *, + ConversionPrecision, Pallet as Contracts, *, }; use alloc::{vec, vec::Vec}; use codec::{Encode, MaxEncodedLen}; @@ -1771,7 +1771,9 @@ mod benchmarks { assert!(ContractInfoOf::<T>::get(&addr).is_some()); assert_eq!( T::Currency::balance(&account_id), - Pallet::<T>::min_balance() + Pallet::<T>::convert_evm_to_native(value.into()).unwrap() + Pallet::<T>::min_balance() + + Pallet::<T>::convert_evm_to_native(value.into(), ConversionPrecision::Exact) + .unwrap() ); Ok(()) } diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index ffdf8b13c04..8853e77e958 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -72,6 +72,12 @@ pub trait GasEncoder<Balance>: private::Sealed { /// 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<Balance> GasEncoder<Balance> for () @@ -148,6 +154,11 @@ mod test { assert!(decoded_deposit >= deposit); assert!(deposit * 2 >= decoded_deposit); + + assert_eq!( + (decoded_weight, decoded_deposit), + <() as GasEncoder<u64>>::as_encoded_values(weight, deposit) + ); } #[test] diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 0e5fc3da545..09bfbf380c6 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,8 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, ConversionPrecision, MomentOf, Pallet, + LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -34,8 +35,8 @@ use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, traits::{ - self, AtLeast32BitUnsigned, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, - IdentifyAccount, Member, TransactionExtension, + self, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, Member, + TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, RuntimeDebug, Saturating, @@ -56,34 +57,6 @@ type CallOf<T> = <T as frame_system::Config>::RuntimeCall; /// - Not too low, enabling users to adjust the gas price to define a tip. pub const GAS_PRICE: u32 = 1_000u32; -/// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. -/// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. -pub fn gas_from_fee<Balance>(fee: Balance) -> U256 -where - u32: Into<Balance>, - Balance: Into<U256> + AtLeast32BitUnsigned + Copy, -{ - let gas_price = GAS_PRICE.into(); - let remainder = fee % gas_price; - if remainder.is_zero() { - (fee / gas_price).into() - } else { - (fee.saturating_add(gas_price) / gas_price).into() - } -} - -/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`. -/// and the `Config::WeightPrice` to compute the fee. -/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. -pub fn gas_from_weight<T: Config>(weight: Weight) -> U256 -where - BalanceOf<T>: Into<U256>, -{ - use sp_runtime::traits::Convert; - let fee: BalanceOf<T> = T::WeightPrice::convert(weight); - gas_from_fee(fee) -} - /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] @@ -346,11 +319,14 @@ pub trait EthExtra { return Err(InvalidTransaction::Call); } - let value = crate::Pallet::<Self::Config>::convert_evm_to_native(value.unwrap_or_default()) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); - InvalidTransaction::Call - })?; + let value = crate::Pallet::<Self::Config>::convert_evm_to_native( + value.unwrap_or_default(), + ConversionPrecision::Exact, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); + InvalidTransaction::Call + })?; let data = input.unwrap_or_default().0; @@ -393,17 +369,21 @@ pub trait EthExtra { let nonce = nonce.unwrap_or_default().try_into().map_err(|_| InvalidTransaction::Call)?; // Fees calculated with the fixed `GAS_PRICE` - // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` + // When we dry-run the transaction, we set the gas to `fee / GAS_PRICE` let eth_fee_no_tip = U256::from(GAS_PRICE) .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; - // Fees with the actual gas_price from the transaction. - let eth_fee: BalanceOf<Self::Config> = U256::from(gas_price.unwrap_or_default()) - .saturating_mul(gas) - .try_into() - .map_err(|_| InvalidTransaction::Call)?; + // Fees calculated from the gas and gas_price of the transaction. + let eth_fee = Pallet::<Self::Config>::convert_evm_to_native( + U256::from(gas_price.unwrap_or_default()).saturating_mul(gas), + ConversionPrecision::RoundUp, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to compute eth_fee: {err:?}"); + InvalidTransaction::Call + })?; let info = call.get_dispatch_info(); let function: CallOf<Self::Config> = call.into(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index d2ef6c9c7ba..14ab917c0d4 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -24,8 +24,8 @@ use crate::{ storage::{self, meter::Diff, WriteOutcome}, tracing::if_tracing, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, - ImmutableData, ImmutableDataOf, Pallet as Contracts, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, ConversionPrecision, + Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -1273,7 +1273,7 @@ where to: &T::AccountId, value: U256, ) -> ExecResult { - let value = crate::Pallet::<T>::convert_evm_to_native(value)?; + let value = crate::Pallet::<T>::convert_evm_to_native(value, ConversionPrecision::Exact)?; if value.is_zero() { return Ok(Default::default()); } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index c36cb3f47ca..7f4565a9f08 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,10 +41,7 @@ pub mod tracing; pub mod weights; use crate::{ - evm::{ - runtime::{gas_from_fee, GAS_PRICE}, - GasEncoder, GenericTransaction, - }, + evm::{runtime::GAS_PRICE, GasEncoder, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -1140,16 +1137,20 @@ where if tx.nonce.is_none() { tx.nonce = Some(<System<T>>::account_nonce(&origin).into()); } + if tx.chain_id.is_none() { + tx.chain_id = Some(T::ChainId::get().into()); + } if tx.gas_price.is_none() { tx.gas_price = Some(GAS_PRICE.into()); } - if tx.chain_id.is_none() { - tx.chain_id = Some(T::ChainId::get().into()); + if tx.gas.is_none() { + tx.gas = Some(Self::evm_block_gas_limit()); } // Convert the value to the native balance type. let evm_value = tx.value.unwrap_or_default(); - let native_value = match Self::convert_evm_to_native(evm_value) { + let native_value = match Self::convert_evm_to_native(evm_value, ConversionPrecision::Exact) + { Ok(v) => v, Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())), }; @@ -1206,12 +1207,16 @@ where 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: <T as Config>::RuntimeCall = crate::Call::<T>::call { dest, value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, data: input.clone(), } .into(); @@ -1264,11 +1269,15 @@ where }; // 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: <T as Config>::RuntimeCall = crate::Call::<T>::instantiate_with_code { value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, code: code.to_vec(), data: data.to_vec(), salt: None, @@ -1278,38 +1287,26 @@ where }, }; - // The transaction fees depend on the extrinsic's length, which in turn is influenced by - // the encoded length of the gas limit specified in the transaction (tx.gas). - // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. - // with a maximum of 3 iterations to avoid an infinite loop. - for _ in 0..3 { - let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { - log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); - return Err(EthTransactError::Message("Invalid transaction".into())); - }; - - let eth_dispatch_call = - crate::Call::<T>::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; - let encoded_len = utx_encoded_size(eth_dispatch_call); - let fee = pallet_transaction_payment::Pallet::<T>::compute_fee( - encoded_len, - &dispatch_info, - 0u32.into(), - ) - .into(); - let eth_gas = gas_from_fee(fee); - let eth_gas = - T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); - - if eth_gas == result.eth_gas { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); - break; - } - result.eth_gas = eth_gas; - tx.gas = Some(eth_gas.into()); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}"); - } + let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { + return Err(EthTransactError::Message("Invalid transaction".into())); + }; + let eth_dispatch_call = + crate::Call::<T>::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; + + let encoded_len = utx_encoded_size(eth_dispatch_call); + let fee = pallet_transaction_payment::Pallet::<T>::compute_fee( + encoded_len, + &dispatch_info, + 0u32.into(), + ) + .into(); + let eth_gas = Self::evm_fee_to_gas(fee); + let eth_gas = + T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); + + log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); + result.eth_gas = eth_gas; Ok(result) } @@ -1319,6 +1316,29 @@ where Self::convert_native_to_evm(T::Currency::reducible_balance(&account, Preserve, Polite)) } + /// Convert an EVM 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<T>) -> 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() + } + } + + 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 fee = T::WeightPrice::convert(max_block_weight); + Self::evm_fee_to_gas(fee) + } + /// A generalized version of [`Self::upload_code`]. /// /// It is identical to [`Self::upload_code`] and only differs in the information it returns. @@ -1379,16 +1399,22 @@ where } /// Convert an EVM balance to a native balance. - fn convert_evm_to_native(value: U256) -> Result<BalanceOf<T>, Error<T>> { + fn convert_evm_to_native( + value: U256, + precision: ConversionPrecision, + ) -> Result<BalanceOf<T>, Error<T>> { if value.is_zero() { return Ok(Zero::zero()) } - let ratio = T::NativeToEthRatio::get().into(); - let res = value.checked_div(ratio).expect("divisor is non-zero; qed"); - if res.saturating_mul(ratio) == value { - res.try_into().map_err(|_| Error::<T>::BalanceConversionFailed) - } else { - Err(Error::<T>::DecimalPrecisionLoss) + + let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into()); + match (precision, remainder.is_zero()) { + (ConversionPrecision::Exact, false) => Err(Error::<T>::DecimalPrecisionLoss), + (_, true) => quotient.try_into().map_err(|_| Error::<T>::BalanceConversionFailed), + (_, false) => quotient + .saturating_add(U256::one()) + .try_into() + .map_err(|_| Error::<T>::BalanceConversionFailed), } } } @@ -1417,6 +1443,9 @@ sp_api::decl_runtime_apis! { Nonce: Codec, BlockNumber: Codec, { + /// Returns the block gas limit. + fn block_gas_limit() -> U256; + /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 9c149c7cc38..e2900bd027b 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -108,6 +108,14 @@ pub enum EthTransactError { Message(String), } +/// Precision used for converting between Native and EVM balances. +pub enum ConversionPrecision { + /// Exact conversion without any rounding. + Exact, + /// Conversion that rounds up to the nearest whole number. + RoundUp, +} + /// Result type of a `bare_code_upload` call. pub type CodeUploadResult<Balance> = Result<CodeUploadReturnValue<Balance>, DispatchError>; -- GitLab