diff --git a/crates/anvil-polkadot/tests/it/state_injector.rs b/crates/anvil-polkadot/tests/it/state_injector.rs index 27a2f96b26e27..bbaf7e7d0cbef 100644 --- a/crates/anvil-polkadot/tests/it/state_injector.rs +++ b/crates/anvil-polkadot/tests/it/state_injector.rs @@ -804,12 +804,9 @@ async fn test_set_immutable_storage() { )); // Verify initial immutable value - let call_tx = TransactionRequest::default() - .from(alith_addr) - .to(contract_address) - .input(TransactionInput::both( - ImmutableStorage::getImmutableValueCall {}.abi_encode().into(), - )); + let call_tx = TransactionRequest::default().from(alith_addr).to(contract_address).input( + TransactionInput::both(ImmutableStorage::getImmutableValueCall {}.abi_encode().into()), + ); let result = unwrap_response::( node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(), ) @@ -820,12 +817,9 @@ async fn test_set_immutable_storage() { ); // Verify initial immutable address - let call_tx = TransactionRequest::default() - .from(alith_addr) - .to(contract_address) - .input(TransactionInput::both( - ImmutableStorage::getImmutableAddressCall {}.abi_encode().into(), - )); + let call_tx = TransactionRequest::default().from(alith_addr).to(contract_address).input( + TransactionInput::both(ImmutableStorage::getImmutableAddressCall {}.abi_encode().into()), + ); let result = unwrap_response::( node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(), ) @@ -842,28 +836,20 @@ async fn test_set_immutable_storage() { Account::from(subxt_signer::eth::dev::charleth()).address(), )); - let immutables = vec![ - Bytes::from(new_value.abi_encode()), - Bytes::from(new_address.abi_encode()), - ]; + let immutables = + vec![Bytes::from(new_value.abi_encode()), Bytes::from(new_address.abi_encode())]; unwrap_response::<()>( - node.eth_rpc(EthRequest::SetImmutableStorageAt( - contract_address, - immutables, - )) - .await - .unwrap(), + node.eth_rpc(EthRequest::SetImmutableStorageAt(contract_address, immutables)) + .await + .unwrap(), ) .unwrap(); // Verify new immutable value - let call_tx = TransactionRequest::default() - .from(alith_addr) - .to(contract_address) - .input(TransactionInput::both( - ImmutableStorage::getImmutableValueCall {}.abi_encode().into(), - )); + let call_tx = TransactionRequest::default().from(alith_addr).to(contract_address).input( + TransactionInput::both(ImmutableStorage::getImmutableValueCall {}.abi_encode().into()), + ); let result = unwrap_response::( node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(), ) @@ -874,12 +860,9 @@ async fn test_set_immutable_storage() { ); // Verify new immutable address - let call_tx = TransactionRequest::default() - .from(alith_addr) - .to(contract_address) - .input(TransactionInput::both( - ImmutableStorage::getImmutableAddressCall {}.abi_encode().into(), - )); + let call_tx = TransactionRequest::default().from(alith_addr).to(contract_address).input( + TransactionInput::both(ImmutableStorage::getImmutableAddressCall {}.abi_encode().into()), + ); let result = unwrap_response::( node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(), ) diff --git a/crates/anvil-polkadot/tests/it/utils.rs b/crates/anvil-polkadot/tests/it/utils.rs index a1eb4922180e9..46c89c6d57aef 100644 --- a/crates/anvil-polkadot/tests/it/utils.rs +++ b/crates/anvil-polkadot/tests/it/utils.rs @@ -465,16 +465,12 @@ fn load_contract_json(name: &str) -> Value { let contract_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("test-data/{name}.json")); - serde_json::from_reader(std::io::BufReader::new( - std::fs::File::open(contract_path).unwrap(), - )) - .unwrap() + serde_json::from_reader(std::io::BufReader::new(std::fs::File::open(contract_path).unwrap())) + .unwrap() } fn decode_hex_field(json: &Value, field: &str) -> Option> { - json.get(field) - .and_then(|v| v.as_str()) - .map(|s| hex::decode(s).unwrap()) + json.get(field).and_then(|v| v.as_str()).map(|s| hex::decode(s).unwrap()) } pub fn get_contract_code(name: &str) -> ContractCode { diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index 9905c388fcff5..ef3468602708b 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -6,7 +6,7 @@ use alloy_sol_types::SolValue; use foundry_cheatcodes::{ Broadcast, BroadcastableTransactions, CheatcodeInspectorStrategy, CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyRunner, CheatsConfig, CheatsCtxt, - CommonCreateInput, Ecx, EvmCheatcodeInspectorStrategyRunner, Result, + CommonCreateInput, DynCheatcode, Ecx, EvmCheatcodeInspectorStrategyRunner, Result, Vm::{ AccountAccessKind, chainIdCall, coinbaseCall, dealCall, etchCall, getNonce_0Call, loadCall, polkadot_0Call, polkadot_1Call, polkadotSkipCall, resetNonceCall, @@ -318,8 +318,9 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive); let dealCall { account, newBalance } = cheatcode.as_any().downcast_ref().unwrap(); - ctx.externalities.set_balance(*account, *newBalance); - cheatcode.dyn_apply(ccx, executor) + let clamped_balance = ctx.externalities.set_balance(*account, *newBalance); + let clamped_deal = dealCall { account: *account, newBalance: clamped_balance }; + clamped_deal.dyn_apply(ccx, executor) } t if using_revive && is::(t) => { tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive); @@ -354,29 +355,10 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { } t if using_revive && is::(t) => { let &rollCall { newHeight } = cheatcode.as_any().downcast_ref().unwrap(); - let new_block_number: u64 = newHeight.saturating_to(); - - // blockhash should be the same on both revive and revm sides, so fetch it before - // changing the block number. - let prev_new_height_hash = ccx - .ecx - .journaled_state - .database - .block_hash(new_block_number.saturating_sub(1)) - .expect("Should not fail"); - let new_height_hash = ccx - .ecx - .journaled_state - .database - .block_hash(new_block_number) - .expect("Should not fail"); - ctx.externalities.set_block_number( - newHeight, - prev_new_height_hash, - new_height_hash, - ); + let clamped_height = + ctx.externalities.roll(newHeight, &mut *ccx.ecx.journaled_state.database); - cheatcode.dyn_apply(ccx, executor) + rollCall { newHeight: clamped_height }.dyn_apply(ccx, executor) } t if using_revive && is::(t) => { ctx.externalities.start_snapshotting(); @@ -398,10 +380,9 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { t if using_revive && is::(t) => { let &warpCall { newTimestamp } = cheatcode.as_any().downcast_ref().unwrap(); - tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive); - ctx.externalities.set_timestamp(newTimestamp); + let clamped_timestamp = ctx.externalities.set_timestamp(newTimestamp); - cheatcode.dyn_apply(ccx, executor) + warpCall { newTimestamp: clamped_timestamp }.dyn_apply(ccx, executor) } t if using_revive && is::(t) => { @@ -425,19 +406,30 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { cheatcode.as_any().downcast_ref().unwrap(); tracing::info!(cheatcode = ?cheatcode.as_debug(), using_revive = ?using_revive); + let u64_max: U256 = U256::from(u64::MAX); + let clamped_block_number = if blockNumber > u64_max { + tracing::warn!( + blockNumber = ?blockNumber, + max = ?u64_max, + "Block number exceeds u64::MAX. Clamping to u64::MAX." + ); + u64_max + } else { + blockNumber + }; // Validate blockNumber is not in the future let current_block = ctx.externalities.get_block_number(); - if blockNumber > current_block { + if clamped_block_number > current_block { return Err(foundry_cheatcodes::Error::from( "block number must be less than or equal to the current block number", )); } - let block_num_u64 = blockNumber.to::(); - ctx.externalities.set_blockhash(block_num_u64, blockHash); + ctx.externalities.set_blockhash(clamped_block_number.to(), blockHash); - cheatcode.dyn_apply(ccx, executor) + setBlockhashCall { blockNumber: clamped_block_number, blockHash } + .dyn_apply(ccx, executor) } t if using_revive && is::(t) => { let etchCall { target, newRuntimeBytecode } = @@ -669,6 +661,8 @@ fn select_revive( let block_number = data.block.number; let timestamp = data.block.timestamp; + ctx.externalities.set_timestamp(timestamp); + ctx.externalities.roll(block_number, &mut *data.journaled_state.database); ctx.externalities.execute_with(||{ // Enable debug mode to bypass EIP-170 size checks during testing @@ -676,8 +670,6 @@ fn select_revive( let debug_settings = DebugSettings::new(true, true, true); debug_settings.write_to_storage::(); } - System::set_block_number(block_number.saturating_to()); - Timestamp::set_timestamp(timestamp.saturating_to::() * 1000); ::ChainId::set( &data.cfg.chain_id, ); @@ -703,7 +695,22 @@ fn select_revive( let account = H160::from_slice(address.as_slice()); let account_id = AccountId32Mapper::::to_fallback_account_id(&account); - let amount_pvm = sp_core::U256::from_little_endian(&amount.as_le_bytes()).min(u128::MAX.into()); + + let u128_max: U256 = U256::from(u128::MAX); + let clamped_amount = if amount > u128_max { + tracing::info!( + address = ?address, + requested = ?amount, + actual = ?u128_max, + "Migration: balance exceeds u128::MAX, clamping to u128::MAX. \ + pallet-revive uses u128 for balances." + ); + u128_max + } else { + amount + }; + + let amount_pvm = sp_core::U256::from_little_endian(&clamped_amount.as_le_bytes()); Pallet::::set_evm_balance(&account, amount_pvm) .expect("failed to set evm balance"); @@ -1045,6 +1052,30 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector } }; + let u128_max: U256 = U256::from(u128::MAX); + if input.value() > u128_max { + tracing::warn!( + caller = ?input.caller(), + value = ?input.value(), + max = ?u128_max, + "Create value exceeds u128::MAX. pallet-revive uses u128 for balances." + ); + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter( + format!( + "Create value {} exceeds u128::MAX ({}). pallet-revive uses u128 for balances.", + input.value(), + u128::MAX + ).as_bytes(), + ), + gas: Gas::new(input.gas_limit()), + }, + address: None, + }); + } + let gas_price_pvm = sp_core::U256::from_little_endian(&U256::from(ecx.tx.gas_price).as_le_bytes()); let mut tracer = Tracer::new(state.expected_calls.clone(), state.expected_creates.clone()); @@ -1216,6 +1247,32 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector tracing::info!("running call on pallet-revive with {} {:#?}", ctx.runtime_mode, call); + let u128_max: U256 = U256::from(u128::MAX); + let call_value = call.call_value(); + if call_value > u128_max { + tracing::warn!( + caller = ?call.caller, + target = ?call.target_address, + value = ?call_value, + max = ?u128_max, + "Call value exceeds u128::MAX. pallet-revive uses u128 for balances." + ); + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter( + format!( + "Call value {} exceeds u128::MAX ({}). pallet-revive uses u128 for balances.", + call_value, + u128::MAX + ).as_bytes(), + ), + gas: Gas::new(call.gas_limit), + }, + memory_offset: call.return_memory_offset.clone(), + }); + } + let gas_price_pvm = sp_core::U256::from_little_endian(&U256::from(ecx.tx.gas_price).as_le_bytes()); let mock_handler = MockHandlerImpl::new( diff --git a/crates/revive-strategy/src/state.rs b/crates/revive-strategy/src/state.rs index fc5ec2c626c34..4689eae1b5d98 100644 --- a/crates/revive-strategy/src/state.rs +++ b/crates/revive-strategy/src/state.rs @@ -112,25 +112,41 @@ impl TestEnv { new_height: U256, prev_new_height_hash: B256, new_height_hash: B256, - ) { + ) -> U256 { // Set block number in pallet-revive runtime. self.0.lock().unwrap().externalities.execute_with(|| { - let new_block_number: u64 = new_height.saturating_to(); + let u64_max = U256::from(u64::MAX); + let clamped_height = if new_height > u64_max { + tracing::warn!( + block_number = ?new_height, + max = ?u64_max, + "Block number exceeds u64::MAX. Clamping to u64::MAX." + ); + u64_max + } else { + new_height + }; + + let new_block_number: u64 = clamped_height.to(); let digest = System::digest(); if System::block_hash(new_block_number) == H256::zero() { // First initialize and finalize the parent block to set up correct hashes. - System::set_block_number(new_block_number - 1); - let current_hash = H256::from_slice(prev_new_height_hash.0.as_slice()); - System::initialize(&new_block_number, ¤t_hash, &digest); + if new_block_number > 0 { + System::set_block_number(new_block_number - 1); + let current_hash = H256::from_slice(prev_new_height_hash.0.as_slice()); + System::initialize(&new_block_number, ¤t_hash, &digest); + } // Now finalize the new block to set up its hash. - System::set_block_number(new_block_number); - let current_hash = H256::from_slice(new_height_hash.0.as_slice()); - - System::initialize(&(new_block_number + 1), ¤t_hash, &digest); + if new_block_number < u64::MAX { + System::set_block_number(new_block_number); + let current_hash = H256::from_slice(new_height_hash.0.as_slice()); + System::initialize(&(new_block_number + 1), ¤t_hash, &digest); + } } System::set_block_number(new_block_number); - }); + clamped_height + }) } pub fn get_block_number(&mut self) -> U256 { @@ -138,12 +154,38 @@ impl TestEnv { self.0.lock().unwrap().externalities.execute_with(|| U256::from(System::block_number())) } - pub fn set_timestamp(&mut self, new_timestamp: U256) { + pub fn roll( + &mut self, + new_height: U256, + database: &mut DB, + ) -> U256 { + let block_num_u64 = new_height.saturating_to::(); + let prev_block_hash = + database.block_hash(block_num_u64.saturating_sub(1)).unwrap_or_default(); + let current_block_hash = database.block_hash(block_num_u64).unwrap_or_default(); + + self.set_block_number(new_height, prev_block_hash, current_block_hash) + } + + pub fn set_timestamp(&mut self, new_timestamp: U256) -> U256 { // Set timestamp in pallet-revive runtime (milliseconds). self.0.lock().unwrap().externalities.execute_with(|| { - let timestamp_ms = new_timestamp.saturating_to::().saturating_mul(1000); + let u64_max = U256::from(u64::MAX); + let clamped_timestamp = if new_timestamp > u64_max { + tracing::warn!( + timestamp = ?new_timestamp, + max = ?u64_max, + "Timestamp exceeds u64::MAX. Clamping to u64::MAX." + ); + u64_max + } else { + new_timestamp + }; + + let timestamp_ms = clamped_timestamp.saturating_to::().saturating_mul(1000); Timestamp::set_timestamp(timestamp_ms); - }); + clamped_timestamp + }) } pub fn etch_call(&mut self, target: &Address, new_runtime_code: &Bytes) -> Result { @@ -235,16 +277,32 @@ impl TestEnv { Ok(()) } - pub fn set_balance(&mut self, address: Address, amount: U256) { - let amount_pvm = - sp_core::U256::from_little_endian(&amount.as_le_bytes()).min(u128::MAX.into()); + pub fn set_balance(&mut self, address: Address, amount: U256) -> U256 { + let u128_max = U256::from(u128::MAX); + let clamped_amount = if amount > u128_max { + tracing::warn!( + address = ?address, + requested = ?amount, + actual = ?u128_max, + "Balance exceeds u128::MAX, clamping to u128::MAX. \ + pallet-revive uses u128 for balances." + ); + u128_max + } else { + amount + }; + + let amount_pvm = sp_core::U256::from_little_endian(&clamped_amount.as_le_bytes()); self.0.lock().unwrap().externalities.execute_with(|| { let h160_addr = H160::from_slice(address.as_slice()); pallet_revive::Pallet::::set_evm_balance(&h160_addr, amount_pvm) .expect("failed to set evm balance"); }); + + clamped_amount } + pub fn get_balance(&mut self, address: Address) -> U256 { U256::from_limbs( self.0