Skip to content
Merged
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
2 changes: 1 addition & 1 deletion crates/forge/tests/it/revive/cheat_etch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rstest::rstest;
#[tokio::test(flavor = "multi_thread")]
async fn test_etch(#[case] runtime_mode: ReviveRuntimeMode) {
let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(runtime_mode);
let filter = Filter::new(".*", "EtchTest", ".*/revive/EtchTest.t.sol");
let filter = Filter::new(".*", ".*", ".*/revive/EtchTest.t.sol");

TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
}
5 changes: 4 additions & 1 deletion crates/revive-env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ use polkadot_sdk::{
sp_tracing,
};

pub use crate::runtime::{Balance, BlockAuthor, GasScale, Runtime, System, Timestamp};
pub use crate::runtime::{
Balance, Balances, BlockAuthor, GasScale, NativeToEthRatio, Runtime, RuntimeHoldReason, System,
Timestamp,
};
pub use polkadot_sdk::parachains_common::AccountId;
mod runtime;

Expand Down
77 changes: 71 additions & 6 deletions crates/revive-strategy/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
use alloy_primitives::{Address, B256, Bytes, FixedBytes, U256};
use foundry_cheatcodes::{Error, Result};
use polkadot_sdk::{
frame_support::traits::{
fungible::{InspectHold, MutateHold},
tokens::Precision,
},
pallet_revive::{
self, AccountId32Mapper, AccountInfo, AddressMapper, BytecodeType, ContractInfo,
ExecConfig, Executable, Pallet, ResourceMeter,
ExecConfig, Executable, HoldReason, Pallet, ResourceMeter,
},
sp_core::{self, H160, H256},
sp_externalities::Externalities,
sp_io::TestExternalities,
sp_runtime::AccountId32,
sp_weights::Weight,
};
use revive_env::{BlockAuthor, ExtBuilder, Runtime, System, Timestamp};
use revive_env::{Balances, BlockAuthor, ExtBuilder, NativeToEthRatio, Runtime, System, Timestamp};
use std::{
fmt::Debug,
sync::{Arc, Mutex},
Expand Down Expand Up @@ -188,6 +193,51 @@ impl TestEnv {
})
}

fn set_base_deposit_hold(
target_address: &H160,
target_account: &AccountId32,
contract_info: &mut ContractInfo<Runtime>,
code_deposit: u128,
) -> foundry_cheatcodes::Result {
contract_info.update_base_deposit(code_deposit);

let base_deposit: u128 = contract_info.storage_base_deposit();
let hold_reason: revive_env::RuntimeHoldReason = HoldReason::StorageDepositReserve.into();

// Release any existing hold
let current_held = Balances::balance_on_hold(&hold_reason, target_account);
if current_held > 0 {
Balances::release(&hold_reason, target_account, current_held, Precision::BestEffort)
.map_err(|_| <&str as Into<Error>>::into("Could not release old hold"))?;

// Decrease EVM balance by released amount (hold became free, so visible balance would
// increase)
let current_evm_balance = Pallet::<Runtime>::evm_balance(target_address);
let release_wei = sp_core::U256::from(current_held)
.saturating_mul(sp_core::U256::from(NativeToEthRatio::get() as u128));
let adjusted_balance = current_evm_balance.saturating_sub(release_wei);
Pallet::<Runtime>::set_evm_balance(target_address, adjusted_balance).map_err(|_| {
<&str as Into<Error>>::into("Could not adjust balance after release")
})?;
}

// Create new hold with correct amount
if base_deposit > 0 {
let current_evm_balance = Pallet::<Runtime>::evm_balance(target_address);
let hold_wei = sp_core::U256::from(base_deposit)
.saturating_mul(sp_core::U256::from(NativeToEthRatio::get() as u128));
let new_evm_balance = current_evm_balance.saturating_add(hold_wei);

Pallet::<Runtime>::set_evm_balance(target_address, new_evm_balance)
.map_err(|_| <&str as Into<Error>>::into("Could not set balance for new hold"))?;

Balances::hold(&hold_reason, target_account, base_deposit)
.map_err(|_| <&str as Into<Error>>::into("Could not create new hold"))?;
}

Ok(Default::default())
}

pub fn etch_call(&mut self, target: &Address, new_runtime_code: &Bytes) -> Result {
self.0.lock().unwrap().externalities.execute_with(|| {
let target_address = H160::from_slice(target.as_slice());
Expand All @@ -210,15 +260,18 @@ impl TestEnv {
)
.map_err(|_| <&str as Into<Error>>::into("Could not upload PVM code"))?;

let code_deposit = contract_blob.code_info().deposit();
let code_hash = *contract_blob.code_hash();

let mut contract_info = if let Some(contract_info) =
AccountInfo::<Runtime>::load_contract(&target_address)
{
contract_info
} else {
let contract_info = ContractInfo::<Runtime>::new(
&target_address,
System::account_nonce(target_account),
*contract_blob.code_hash(),
System::account_nonce(&target_account),
code_hash,
)
.map_err(|err| {
tracing::error!("Could not create contract info: {:?}", err);
Expand All @@ -229,11 +282,23 @@ impl TestEnv {
));
contract_info
};
contract_info.code_hash = *contract_blob.code_hash();

contract_info.code_hash = code_hash;

// Update base deposit hold for both new and existing contracts
// Note: Code upload deposits are already held on the pallet account by try_upload_code
Self::set_base_deposit_hold(
&target_address,
&target_account,
&mut contract_info,
code_deposit,
)?;

AccountInfo::<Runtime>::insert_contract(
&H160::from_slice(target.as_slice()),
contract_info,
contract_info.clone(),
);

Ok::<(), Error>(())
})?;
Ok(Default::default())
Expand Down
48 changes: 48 additions & 0 deletions testdata/default/revive/EtchTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,51 @@ contract EtchTest is DSTest {
assertEq(nested_call_result2, 3);
}
}

// Simple contract that writes to storage
contract StorageWriter {
uint256 public value;

function setValue(uint256 _value) external {
value = _value;
}
}

contract MinimalStorageDeposit is DSTest {
Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))));

// Test that etch with pre-funded address preserves the visible balance
function testEtchPreservesBalance() public {
bytes memory code = vm.getDeployedCode("EtchTest.t.sol:StorageWriter");

// Fund an address with a specific balance BEFORE etching
address etched = address(0xABCDEF);
uint256 expectedBalance = 1 ether;
vm.deal(etched, expectedBalance);
assertEq(etched.balance, expectedBalance, "Initial balance should be 1 ether");

vm.etch(etched, code);

assertEq(etched.balance, expectedBalance, "Balance should be preserved after etch");

StorageWriter(etched).setValue(42);
assertEq(StorageWriter(etched).value(), 42);

assertEq(etched.balance, expectedBalance, "Balance should be preserved after storage write");
}

// Test that etch to unfunded address keeps balance at 0
function testEtchUnfundedAddressZeroBalance() public {
bytes memory code = vm.getDeployedCode("EtchTest.t.sol:StorageWriter");

address etched = address(0x9999999999);
assertEq(etched.balance, 0, "Balance should be 0 before etch");

vm.etch(etched, code);

assertEq(etched.balance, 0, "Balance should still be 0 after etch");

StorageWriter(etched).setValue(123);
assertEq(StorageWriter(etched).value(), 123);
}
}
1 change: 0 additions & 1 deletion testdata/default/revive/Prank.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -710,5 +710,4 @@ contract FundPrankedAccountsReproTest is DSTest {
assertEq(alice.balance, 5 ether, "alice balance should remain 5 ether");
vm.stopPrank();
}

}
Loading