Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions crates/cli/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,7 @@ pub fn enable_paint() {
/// <https://github.com/awslabs/aws-sdk-rust/discussions/1257>
pub fn install_crypto_provider() {
// https://github.com/snapview/tokio-tungstenite/issues/353
rustls::crypto::ring::default_provider()
.install_default()
.expect("Failed to install default rustls crypto provider");
let _ = rustls::crypto::ring::default_provider().install_default();
}

/// Useful extensions to [`std::process::Command`].
Expand Down
220 changes: 180 additions & 40 deletions crates/revive-strategy/src/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ use std::{
use tracing::warn;

use polkadot_sdk::{
frame_support::{dispatch::DispatchClass, traits::fungible::Inspect, weights::Weight},
frame_system, pallet_balances,
pallet_revive::{
self, AccountInfo, AddressMapper, BalanceOf, Code, ContractInfo, ExecConfig, Pallet,
evm::CallTrace,
self, AccountInfo, AddressMapper, BalanceOf, BalanceWithDust, Code, Config, ContractInfo,
ExecConfig, Pallet, evm::CallTrace,
},
polkadot_sdk_frame::prelude::OriginFor,
sp_core::{self, H160},
sp_weights::Weight,
sp_runtime::traits::{SaturatedConversion, Zero},
};

use crate::{
Expand All @@ -46,6 +48,7 @@ use revm::{
},
state::Bytecode,
};

pub trait PvmCheatcodeInspectorStrategyBuilder {
fn new_pvm(
dual_compiled_contracts: DualCompiledContracts,
Expand Down Expand Up @@ -143,6 +146,86 @@ impl CheatcodeInspectorStrategyContext for PvmCheatcodeInspectorStrategyContext
}
}

fn compute_gas_caps(
caller: &H160,
gas_limit: u64,
value_native: BalanceOf<Runtime>,
) -> Option<(Weight, BalanceOf<Runtime>)> {
execute_with_externalities(|externalities| {
externalities.execute_with(|| {
let block_weights = <Runtime as frame_system::Config>::BlockWeights::get();
let normal = block_weights.get(DispatchClass::Normal);
let max_normal =
normal.max_extrinsic.unwrap_or(normal.max_total.unwrap_or(block_weights.max_block));
let base_extrinsic = normal.base_extrinsic;

let account_id = AccountId::to_fallback_account_id(caller);
let free_balance = pallet_balances::Pallet::<Runtime>::free_balance(&account_id);
let existential_deposit = <pallet_balances::Pallet::<Runtime> as Inspect<_>>::minimum_balance();

// Compute weight per gas from runtime benchmarks
let weight_per_gas = {
use polkadot_sdk::pallet_revive::weights::WeightInfo;
let loop_iteration = <Runtime as Config>::WeightInfo::instr(1)
.saturating_sub(<Runtime as Config>::WeightInfo::instr(0))
.ref_time();
let empty_loop = <Runtime as Config>::WeightInfo::instr_empty_loop(1)
.saturating_sub(<Runtime as Config>::WeightInfo::instr_empty_loop(0))
.ref_time();
loop_iteration.saturating_sub(empty_loop)
};

if weight_per_gas == 0 || max_normal.is_zero() {
warn!(
"Gas weight computation disabled (weight_per_gas={}, max_normal={:?}); falling back to static caps",
weight_per_gas,
max_normal
);
return None;
}

let max_budget_weight = max_normal.saturating_sub(base_extrinsic);

if max_budget_weight.is_zero() {
warn!("No weight budget available after subtracting base_extrinsic; falling back to static caps");
return None;
}

let weight = if gas_limit == 0 {
max_budget_weight
} else {
let ref_time_cap = max_budget_weight.ref_time();
let mut requested_ref_time = weight_per_gas.saturating_mul(gas_limit);

if requested_ref_time > ref_time_cap {
requested_ref_time = ref_time_cap;
}

let proof_size_budget = if requested_ref_time == 0 {
0
} else {
let max_proof_size = max_budget_weight.proof_size();
((max_proof_size as u128)
.saturating_mul(requested_ref_time as u128)
.saturating_div(ref_time_cap as u128)) as u64
};

Weight::from_parts(requested_ref_time, proof_size_budget)
};

let storage = free_balance
.saturating_sub(existential_deposit)
.saturating_sub(value_native);

Some((weight, storage))
})
})
}

fn clamp_evm_value_to_native(value: sp_core::U256) -> BalanceOf<Runtime> {
value.min(u128::MAX.into()).as_u128().saturated_into()
}

fn set_nonce(address: Address, nonce: u64, ecx: Ecx<'_, '_, '_>, check_nonce: bool) {
execute_with_externalities(|externalities| {
externalities.execute_with(|| {
Expand All @@ -169,8 +252,15 @@ fn set_nonce(address: Address, nonce: u64, ecx: Ecx<'_, '_, '_>, check_nonce: bo
fn set_balance(address: Address, amount: U256, ecx: Ecx<'_, '_, '_>) -> U256 {
let account = ecx.journaled_state.load_account(address).expect("account loaded").data;
account.mark_touch();
account.info.balance = amount;

let amount_pvm = sp_core::U256::from_little_endian(&amount.as_le_bytes()).min(u128::MAX.into());
let amount_clamped = U256::from(amount_pvm.min(u128::MAX.into()).as_u128());
account.info.balance = amount_clamped;

let _balance_native =
BalanceWithDust::<BalanceOf<Runtime>>::from_value::<Runtime>(amount_pvm).unwrap();

let _min_balance = <pallet_balances::Pallet<Runtime> as Inspect<_>>::minimum_balance();

let old_balance = execute_with_externalities(|externalities| {
externalities.execute_with(|| {
Expand Down Expand Up @@ -413,6 +503,16 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {
}

let target_address_h160 = H160::from_slice(target.as_slice());
let has_pvm_contract = execute_with_externalities(|externalities| {
externalities.execute_with(|| {
AccountInfo::<Runtime>::load_contract(&target_address_h160).is_some()
})
});
if !has_pvm_contract {
tracing::info!(target = ?target, "no PVM contract; delegating vm.store to EVM");
return cheatcode.dyn_apply(ccx, executor);
}

let _ = execute_with_externalities(|externalities| {
externalities.execute_with(|| {
Pallet::<Runtime>::set_storage(
Expand Down Expand Up @@ -751,35 +851,45 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
}

let init_code = input.init_code();

// Determine which bytecode to use based on runtime mode
let (code_bytes, constructor_args) = match ctx.runtime_mode {
crate::ReviveRuntimeMode::Pvm => {
// PVM mode: use resolc (PVM) bytecode
tracing::info!("running create in PVM mode with PVM bytecode");
let find_contract = ctx
.dual_compiled_contracts
.find_bytecode(&init_code.0)
.unwrap_or_else(|| panic!("failed finding contract for {init_code:?}"));
let constructor_args = find_contract.constructor_args();
let contract = find_contract.contract();
(contract.resolc_bytecode.as_bytes().unwrap().to_vec(), constructor_args.to_vec())
}
crate::ReviveRuntimeMode::Evm => {
// EVM mode: use EVM bytecode directly
tracing::info!("running create in EVM mode with EVM bytecode");
(init_code.0.to_vec(), vec![])
}
let Some(find_contract) = ctx.dual_compiled_contracts.find_bytecode(&init_code.0) else {
tracing::info!("no resolc match for init code; creating in EVM");
return None;
};
tracing::info!("running create in PVM");

let constructor_args = find_contract.constructor_args();
let contract = find_contract.contract();
let code_bytes = contract.resolc_bytecode.as_bytes().unwrap().to_vec();

let caller_h160 = H160::from_slice(input.caller().as_slice());
let evm_value = sp_core::U256::from_little_endian(&input.value().as_le_bytes());
let value_native = clamp_evm_value_to_native(evm_value);

let (max_weight_cap, storage_deposit_cap) =
compute_gas_caps(&caller_h160, input.gas_limit(), value_native).unwrap_or_else(|| {
warn!("Falling back to legacy gas caps for CREATE; using max block weight");
execute_with_externalities(|ext| {
ext.execute_with(|| {
let max_weight = <Runtime as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.max_extrinsic
.unwrap_or_default();
let account_id = AccountId::to_fallback_account_id(&caller_h160);
let free = pallet_balances::Pallet::<Runtime>::free_balance(&account_id);
let ed =
<pallet_balances::Pallet<Runtime> as Inspect<_>>::minimum_balance();
(max_weight, free.saturating_sub(ed).saturating_sub(value_native))
})
})
});

let mut tracer = Tracer::new(true);
let res = execute_with_externalities(|externalities| {
externalities.execute_with(|| {
tracer.trace(|| {
let origin = OriginFor::<Runtime>::signed(AccountId::to_fallback_account_id(
&H160::from_slice(input.caller().as_slice()),
&caller_h160,
));
let evm_value = sp_core::U256::from_little_endian(&input.value().as_le_bytes());

let code = Code::Upload(code_bytes.clone());
let data = constructor_args;
Expand All @@ -798,11 +908,10 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
Pallet::<Runtime>::bare_instantiate(
origin,
evm_value,
Weight::MAX,
// TODO: fixing.
BalanceOf::<Runtime>::MAX,
max_weight_cap,
storage_deposit_cap,
code,
data,
data.to_vec(),
salt,
ExecConfig::new_substrate_tx(),
)
Expand Down Expand Up @@ -888,27 +997,58 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
return None;
}

tracing::info!("running call on pallet-revive with {} {:#?}", ctx.runtime_mode, call);
tracing::info!("running call in PVM {:#?}", call);

let caller_h160 = H160::from_slice(call.caller.as_slice());
let evm_value = sp_core::U256::from_little_endian(&call.call_value().as_le_bytes());
let value_native = clamp_evm_value_to_native(evm_value);

let (max_weight_cap, storage_deposit_cap) =
compute_gas_caps(&caller_h160, call.gas_limit, value_native).unwrap_or_else(|| {
warn!("Falling back to legacy gas caps for CALL; using max block weight");
execute_with_externalities(|ext| {
ext.execute_with(|| {
let max_weight = <Runtime as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.max_extrinsic
.unwrap_or_default();
let account_id = AccountId::to_fallback_account_id(&caller_h160);
let free = pallet_balances::Pallet::<Runtime>::free_balance(&account_id);
let ed =
<pallet_balances::Pallet<Runtime> as Inspect<_>>::minimum_balance();
(max_weight, free.saturating_sub(ed).saturating_sub(value_native))
})
})
});

let target_address_h160 = H160::from_slice(call.target_address.as_slice());
let has_pvm_contract = execute_with_externalities(|externalities| {
externalities.execute_with(|| {
AccountInfo::<Runtime>::load_contract(&target_address_h160).is_some()
})
});
if !has_pvm_contract {
tracing::info!(
target = ?call.target_address,
"no PVM contract at target; calling in EVM"
);
return None;
}

let mut tracer = Tracer::new(true);
let res = execute_with_externalities(|externalities| {
externalities.execute_with(|| {
tracer.trace(|| {
let origin = OriginFor::<Runtime>::signed(AccountId::to_fallback_account_id(
&H160::from_slice(call.caller.as_slice()),
&caller_h160,
));

let evm_value =
sp_core::U256::from_little_endian(&call.call_value().as_le_bytes());

let target = H160::from_slice(call.target_address.as_slice());

Pallet::<Runtime>::bare_call(
origin,
target,
target_address_h160,
evm_value,
Weight::MAX,
// TODO: fixing.
BalanceOf::<Runtime>::MAX,
max_weight_cap,
storage_deposit_cap,
call.input.bytes(ecx).to_vec(),
ExecConfig::new_substrate_tx(),
)
Expand Down
4 changes: 3 additions & 1 deletion crates/revive-strategy/src/executor/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ impl ExecutorStrategyRunner for ReviveExecutorStrategyRunner {
let balance_native =
BalanceWithDust::<BalanceOf<Runtime>>::from_value::<Runtime>(amount_pvm).unwrap();

EvmExecutorStrategyRunner.set_balance(executor, address, amount)?;
// Set EVM balance with clamped PVM balance
let amount_clamped = U256::from(amount_pvm.min(u128::MAX.into()).as_u128());
EvmExecutorStrategyRunner.set_balance(executor, address, amount_clamped)?;

let min_balance = pallet_balances::Pallet::<Runtime>::minimum_balance();

Expand Down
Loading
Loading