diff --git a/domains/client/domain-operator/Cargo.toml b/domains/client/domain-operator/Cargo.toml index a29de60eda..d850cfaa41 100644 --- a/domains/client/domain-operator/Cargo.toml +++ b/domains/client/domain-operator/Cargo.toml @@ -80,5 +80,5 @@ sp-state-machine.workspace = true subspace-core-primitives.workspace = true subspace-test-runtime.workspace = true subspace-test-service.workspace = true -subspace-test-primitives.workspace = true +subspace-test-primitives = { workspace = true, features = ["std"] } tempfile.workspace = true diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 8f23628ac7..5830ba50b7 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -43,8 +43,8 @@ use sp_domains::merkle_tree::MerkleTree; use sp_domains::test_ethereum::{generate_legacy_tx, max_extrinsic_gas}; use sp_domains::test_ethereum_tx::{address_build, contract_address, AccountInfo}; use sp_domains::{ - Bundle, BundleValidity, ChainId, ChannelId, DomainsApi, HeaderHashingFor, InboxedBundle, - InvalidBundleType, PermissionedActionAllowedBy, Transfers, + BlockFees, Bundle, BundleValidity, ChainId, ChannelId, DomainsApi, HeaderHashingFor, + InboxedBundle, InvalidBundleType, PermissionedActionAllowedBy, Transfers, }; use sp_domains_fraud_proof::fraud_proof::{ ApplyExtrinsicMismatch, ExecutionPhase, FinalizeBlockMismatch, FraudProofVariant, @@ -1264,10 +1264,10 @@ async fn test_evm_domain_create_contracts_with_allow_list_multiple() { #[tokio::test(flavor = "multi_thread")] async fn test_evm_domain_gas_estimates() { - let (_directory, _ferdie, alice, account_infos) = + let (_directory, mut ferdie, mut alice, account_infos) = setup_evm_test_accounts(Sr25519Alice, false, None).await; - let test_estimate_gas = |evm_call| { + let test_estimate_gas = |evm_call, is_estimate| { let ::RuntimeCall::EVM(evm_call) = evm_call else { panic!("Unexpected RuntimeCall type"); }; @@ -1295,8 +1295,8 @@ async fn test_evm_domain_gas_estimates() { Some(max_fee_per_gas), max_priority_fee_per_gas, nonce, - // Estimate gas - true, + // Do we want to estimate gas, or run the call? + is_estimate, Some(access_list.clone()), ) .expect("test EVM Create runtime call must succeed") @@ -1308,7 +1308,7 @@ async fn test_evm_domain_gas_estimates() { create_info.used_gas.effective, ), // The exact estimate is not important, but we want to know if it changes - (4_327_174.into(), 4_327_174.into()), + (4_326_280.into(), 4_326_280.into()), "Incorrect EVM Create gas estimate: {:?} {:?}", evm_call, create_info, @@ -1338,8 +1338,8 @@ async fn test_evm_domain_gas_estimates() { Some(max_fee_per_gas), max_priority_fee_per_gas, nonce, - // Estimate gas - true, + // Do we want to estimate gas, or run the call? + is_estimate, Some(access_list.clone()), ) .expect("test EVM Call runtime call must succeed") @@ -1348,7 +1348,7 @@ async fn test_evm_domain_gas_estimates() { assert_eq!( (call_info.used_gas.standard, call_info.used_gas.effective), // The exact estimate is not important, but we want to know if it changes - (22_258.into(), 22_258.into()), + (21_400.into(), 21_400.into()), "Incorrect EVM Call gas estimate: {:?} {:?}", evm_call, call_info, @@ -1364,6 +1364,7 @@ async fn test_evm_domain_gas_estimates() { .gas_price(alice.client.info().best_hash) .unwrap(); + // Estimates let evm_nonce = alice .client .runtime_api() @@ -1377,7 +1378,7 @@ async fn test_evm_domain_gas_estimates() { evm_nonce, gas_price, ); - test_estimate_gas(evm_create); + test_estimate_gas(evm_create, true); let evm_nonce = alice .client @@ -1392,7 +1393,140 @@ async fn test_evm_domain_gas_estimates() { evm_nonce, gas_price, ); - test_estimate_gas(evm_call); + test_estimate_gas(evm_call, true); + + // Really do it, using runtime API calls + let evm_nonce = alice + .client + .runtime_api() + .account_basic(alice.client.info().best_hash, account_infos[0].address) + .unwrap() + .nonce; + let evm_create = generate_evm_domain_call( + account_infos[0].clone(), + ethereum::TransactionAction::Create, + 0, + evm_nonce, + gas_price, + ); + test_estimate_gas(evm_create, false); + + let evm_contract_address = contract_address(account_infos[0].address, evm_nonce.as_u64()); + + produce_blocks!(ferdie, alice, 3).await.unwrap(); + + let evm_nonce = alice + .client + .runtime_api() + .account_basic(alice.client.info().best_hash, account_infos[0].address) + .unwrap() + .nonce; + let evm_call = generate_evm_domain_call( + account_infos[0].clone(), + ethereum::TransactionAction::Call(evm_contract_address), + 0, + evm_nonce, + gas_price, + ); + test_estimate_gas(evm_call, false); + + produce_blocks!(ferdie, alice, 3).await.unwrap(); + + // Really do it, using construct_and_send_extrinsic() + let evm_nonce = alice + .client + .runtime_api() + .account_basic(alice.client.info().best_hash, account_infos[0].address) + .unwrap() + .nonce; + let evm_tx = generate_evm_domain_call( + account_infos[0].clone(), + ethereum::TransactionAction::Create, + 0, + evm_nonce, + gas_price, + ); + let evm_contract_address = contract_address(account_infos[0].address, evm_nonce.as_u64()); + + // Use alice's account ID, which does the signing in construct_and_send_extrinsic() + let result = alice.construct_and_send_extrinsic(evm_tx).await; + assert_matches!( + result, + Ok(_), + "Unexpectedly failed to send signed extrinsic" + ); + + // Produce a bundle that contains just the sent extrinsics + let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + assert_eq!(bundle.extrinsics.len(), 1); + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + let consensus_block_hash = ferdie.client.info().best_hash; + + // Produce one more bundle, this bundle should contain the ER of the previous bundle + let (_, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let receipt = bundle.into_receipt(); + assert_eq!(receipt.consensus_block_hash, consensus_block_hash); + assert_eq!( + receipt.block_fees, + // Check the actual block fees for an EVM contract create + BlockFees { + consensus_storage_fee: 789, + domain_execution_fee: 10_815_700_000_631, + burned_balance: 0, + chain_rewards: [].into(), + } + ); + + produce_blocks!(ferdie, alice, 3).await.unwrap(); + + let evm_nonce = alice + .client + .runtime_api() + .account_basic(alice.client.info().best_hash, account_infos[0].address) + .unwrap() + .nonce; + let evm_tx = generate_evm_domain_call( + account_infos[0].clone(), + ethereum::TransactionAction::Call(evm_contract_address), + 0, + evm_nonce, + gas_price, + ); + + // Use alice's account ID, which does the signing in construct_and_send_extrinsic() + let result = alice.construct_and_send_extrinsic(evm_tx).await; + assert_matches!( + result, + Ok(_), + "Unexpectedly failed to send signed extrinsic" + ); + + // Produce a bundle that contains just the sent extrinsics + let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + assert_eq!(bundle.extrinsics.len(), 1); + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + let consensus_block_hash = ferdie.client.info().best_hash; + + // Produce one more bundle, this bundle should contain the ER of the previous bundle + let (_, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let receipt = bundle.into_receipt(); + assert_eq!(receipt.consensus_block_hash, consensus_block_hash); + assert_eq!( + receipt.block_fees, + // Check the actual block fees for an EVM contract call + BlockFees { + consensus_storage_fee: 849, + domain_execution_fee: 10_815_700_000_651, + burned_balance: 0, + chain_rewards: [].into(), + } + ); + + produce_blocks!(ferdie, alice, 3).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 3975f58e55..915e7aec1b 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -77,7 +77,7 @@ use sp_runtime::transaction_validity::{ }; use sp_runtime::type_with_default::TypeWithDefault; use sp_runtime::{ - generic, impl_opaque_keys, ApplyExtrinsicResult, ArithmeticError, ConsensusEngineId, Digest, + generic, impl_opaque_keys, ApplyExtrinsicResult, ConsensusEngineId, Digest, ExtrinsicInclusionMode, }; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -1593,56 +1593,22 @@ impl_runtime_apis! { let (weight_limit, proof_size_base_cost) = pallet_ethereum::Pallet::::transaction_weight(&transaction_data); - let mut call_info = ::Runner::call( + ::Runner::call( from, to, - data.clone(), + data, value, gas_limit.unique_saturated_into(), max_fee_per_gas, max_priority_fee_per_gas, nonce, - access_list.clone().unwrap_or_default(), + access_list.unwrap_or_default(), is_transactional, validate, weight_limit, proof_size_base_cost, evm_config, - ).map_err(|err| err.error)?; - - // Add the storage fee to the estimated gas cost - // (in the actual call, this is handled by OnChargeEVMTransaction) - if estimate { - // It doesn't matter if we use pallet_evm or pallet_ethereum calls here, because - // they will be roughly the same size. - // TODO: try all possibilities, using all 3 ethereum formats, and choose the - // largest as the estimate - let xt = UncheckedExtrinsic::new_bare( - pallet_evm::Call::call { - source: from, - target: to, - input: data, - value, - gas_limit: gas_limit.unique_saturated_into(), - // TODO: use the actual default here (but that shouldn't change the - // extrinsic size) - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas, - nonce, - access_list: access_list.unwrap_or_default(), - }.into() - ); - - let len = xt.encoded_size(); - let consensus_storage_fee = consensus_storage_fee(len).map_err(|_| ArithmeticError::Overflow)?; - - // TODO: handle the effective gas ratio correctly: - // - call_info.used_gas.standard += consensus_storage_fee.into(); - call_info.used_gas.effective += consensus_storage_fee.into(); - } - - Ok(call_info) + ).map_err(|err| err.error.into()) } fn create( @@ -1669,58 +1635,21 @@ impl_runtime_apis! { let weight_limit = None; let proof_size_base_cost = None; let evm_config = config.as_ref().unwrap_or(::config()); - - let mut create_info = ::Runner::create( + ::Runner::create( from, - data.clone(), + data, value, gas_limit.unique_saturated_into(), max_fee_per_gas, max_priority_fee_per_gas, nonce, - access_list.clone().unwrap_or_default(), + access_list.unwrap_or_default(), is_transactional, validate, weight_limit, proof_size_base_cost, evm_config, - ).map_err(|err| err.error)?; - - // Add the storage fee to the estimated gas cost - // (in the actual call, this is handled by OnChargeEVMTransaction) - if estimate { - // It doesn't matter if we use pallet_evm or pallet_ethereum, or create or create2, - // because they will be roughly the same size. - // TODO: try all possibilities, using create/create2 and all 3 ethereum formats, - // and choose the largest as the estimate - let xt = UncheckedExtrinsic::new_bare( - pallet_evm::Call::create2 { - source: from, - init: data, - // TODO: use an actual salt here (but that shouldn't change the extrinsic - // size) - salt: H256::zero(), - value, - gas_limit: gas_limit.unique_saturated_into(), - // TODO: use the actual default here (but that shouldn't change the - // extrinsic size) - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas, - nonce, - access_list: access_list.unwrap_or_default(), - }.into() - ); - - let len = xt.encoded_size(); - let consensus_storage_fee = consensus_storage_fee(len).map_err(|_| ArithmeticError::Overflow)?; - - // TODO: handle the effective gas ratio correctly: - // - create_info.used_gas.standard += consensus_storage_fee.into(); - create_info.used_gas.effective += consensus_storage_fee.into(); - } - - Ok(create_info) + ).map_err(|err| err.error.into()) } fn current_transaction_statuses() -> Option> { diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index d9c4a55c97..d1901a0042 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -43,8 +43,7 @@ use frame_system::pallet_prelude::{BlockNumberFor, RuntimeCallFor}; use pallet_block_fees::fees::OnChargeDomainTransaction; use pallet_ethereum::Call::transact; use pallet_ethereum::{ - Call, PostLogContent, Transaction as EthereumTransaction, TransactionAction, TransactionData, - TransactionStatus, + Call, PostLogContent, Transaction as EthereumTransaction, TransactionData, TransactionStatus, }; use pallet_evm::{ Account as EVMAccount, EnsureAddressNever, EnsureAddressRoot, FeeCalculator, @@ -77,7 +76,7 @@ use sp_runtime::transaction_validity::{ }; use sp_runtime::type_with_default::TypeWithDefault; use sp_runtime::{ - generic, impl_opaque_keys, ApplyExtrinsicResult, ArithmeticError, ConsensusEngineId, Digest, + generic, impl_opaque_keys, ApplyExtrinsicResult, ConsensusEngineId, Digest, ExtrinsicInclusionMode, SaturatedConversion, }; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -1594,75 +1593,25 @@ impl_runtime_apis! { let is_transactional = false; let validate = true; + let weight_limit = None; + let proof_size_base_cost = None; let evm_config = config.as_ref().unwrap_or(::config()); - - let gas_limit = gas_limit.min(u64::MAX.into()); - - let transaction_data = TransactionData::new( - TransactionAction::Call(to), - data.clone(), - nonce.unwrap_or_default(), - gas_limit, - None, - max_fee_per_gas, - max_priority_fee_per_gas, - value, - Some(::ChainId::get()), - access_list.clone().unwrap_or_default(), - ); - - let (weight_limit, proof_size_base_cost) = pallet_ethereum::Pallet::::transaction_weight(&transaction_data); - - let mut call_info = ::Runner::call( + ::Runner::call( from, to, - data.clone(), + data, value, gas_limit.unique_saturated_into(), max_fee_per_gas, max_priority_fee_per_gas, nonce, - access_list.clone().unwrap_or_default(), + access_list.unwrap_or_default(), is_transactional, validate, weight_limit, proof_size_base_cost, evm_config, - ).map_err(|err| err.error)?; - - // Add the storage fee to the estimated gas cost - // (in the actual call, this is handled by OnChargeEVMTransaction) - if estimate { - // It doesn't matter if we use pallet_evm or pallet_ethereum calls here, because - // they will be roughly the same size. - // TODO: try all possibilities, using all 3 ethereum formats, and choose the - // largest as the estimate - let xt = UncheckedExtrinsic::new_bare( - pallet_evm::Call::call { - source: from, - target: to, - input: data, - value, - gas_limit: gas_limit.unique_saturated_into(), - // TODO: use the actual default here (but that shouldn't change the - // extrinsic size) - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas, - nonce, - access_list: access_list.unwrap_or_default(), - }.into() - ); - - let len = xt.encoded_size(); - let consensus_storage_fee = consensus_storage_fee(len).map_err(|_| ArithmeticError::Overflow)?; - - // TODO: handle the effective gas ratio correctly: - // - call_info.used_gas.standard += consensus_storage_fee.into(); - call_info.used_gas.effective += consensus_storage_fee.into(); - } - - Ok(call_info) + ).map_err(|err| err.error.into()) } fn create( @@ -1689,58 +1638,21 @@ impl_runtime_apis! { let weight_limit = None; let proof_size_base_cost = None; let evm_config = config.as_ref().unwrap_or(::config()); - - let mut create_info = ::Runner::create( + ::Runner::create( from, - data.clone(), + data, value, gas_limit.unique_saturated_into(), max_fee_per_gas, max_priority_fee_per_gas, nonce, - access_list.clone().unwrap_or_default(), + access_list.unwrap_or_default(), is_transactional, validate, weight_limit, proof_size_base_cost, evm_config, - ).map_err(|err| err.error)?; - - // Add the storage fee to the estimated gas cost - // (in the actual call, this is handled by OnChargeEVMTransaction) - if estimate { - // It doesn't matter if we use pallet_evm or pallet_ethereum, or create or create2, - // because they will be roughly the same size. - // TODO: try all possibilities, using create/create2 and all 3 ethereum formats, - // and choose the largest as the estimate - let xt = UncheckedExtrinsic::new_bare( - pallet_evm::Call::create2 { - source: from, - init: data, - // TODO: use an actual salt here (but that shouldn't change the extrinsic - // size) - salt: H256::zero(), - value, - gas_limit: gas_limit.unique_saturated_into(), - // TODO: use the actual default here (but that shouldn't change the - // extrinsic size) - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas, - nonce, - access_list: access_list.unwrap_or_default(), - }.into() - ); - - let len = xt.encoded_size(); - let consensus_storage_fee = consensus_storage_fee(len).map_err(|_| ArithmeticError::Overflow)?; - - // TODO: handle the effective gas ratio correctly: - // - create_info.used_gas.standard += consensus_storage_fee.into(); - create_info.used_gas.effective += consensus_storage_fee.into(); - } - - Ok(create_info) + ).map_err(|err| err.error.into()) } fn current_transaction_statuses() -> Option> {