Skip to content

Skip auto-funding for accounts that were explicitly dealt#496

Merged
filip-parity merged 9 commits intomasterfrom
filip/fund-pranked-accounts-dealt-tracking
Jan 23, 2026
Merged

Skip auto-funding for accounts that were explicitly dealt#496
filip-parity merged 9 commits intomasterfrom
filip/fund-pranked-accounts-dealt-tracking

Conversation

@filip-parity
Copy link
Copy Markdown

Fix fund_pranked_accounts overwriting dealt account balances.
Track accounts explicitly funded via vm.deal() and skip auto-funding them in fund_pranked_accounts. Previously, pranking an account that had spent its balance to zero would incorrectly set it to u128::MAX, breaking balance assertions in tests.

Comment thread crates/cheatcodes/src/inspector.rs Outdated
Comment thread crates/revive-strategy/src/cheatcodes/mock_handler.rs Outdated
@filip-parity filip-parity requested a review from pkhry January 19, 2026 09:46
@pkhry
Copy link
Copy Markdown

pkhry commented Jan 19, 2026

Can you add a repro test case?

@filip-parity
Copy link
Copy Markdown
Author

Can you add a repro test case?

Added testcase for this

@pkhry
Copy link
Copy Markdown

pkhry commented Jan 21, 2026

what is going to happen when vm.deal was executed exclusively within foundry side?

/// their balance is 0. This is intentional - vm.deal() is an explicit user action that
/// should be respected, while internal contract creations should still get auto-funding.
pub(crate) fn fund_pranked_accounts(&self, account: Address) {
// Fuzzed prank addresses have no balance, so they won't exist in revive, and
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does it work in REVM if prank addresses do not have any balance?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works, in REVM they don't need balance to execute

@filip-parity
Copy link
Copy Markdown
Author

what is going to happen when vm.deal was executed exclusively within foundry side?

Balance changes don't sync back when executed in pallet-revive, it's a different issue though, we can look separately into this

@pkhry
Copy link
Copy Markdown

pkhry commented Jan 22, 2026

what is going to happen when vm.deal was executed exclusively within foundry side?

Balance changes don't sync back when executed in pallet-revive, it's a different issue though, we can look separately into this

they do.
the question was: what's going to happen if we call the vm.deal inside a callback?

ie

proxy.call() // calls the test contract, original scope
// inside test contract
vm.deal(addr1, 2 ether); // executed inside callback to original scope.
//
vm.prank(addr1)
addr1.balance // if the addr1 didn't exist on pallet-revive side it's balance would be 0. instead of the balance that was set inside the vm.deal() call.

but i digress, it can be changed in other PR.

Comment thread testdata/default/revive/Prank.t.sol Outdated
@smiasojed
Copy link
Copy Markdown
Collaborator

hint from Claude AI, I did not check it in details but is worth to consider, I think:

pub(crate) fn fund_pranked_accounts(&self, account: Address) {
        // In pallet-revive, contracts need funds to cover storage deposits for their code.
        // If a pranked account is a contract with insufficient balance, it becomes non-functional
        // and any storage writes will fail with StorageDepositNotEnoughFunds.
        //
        // Unlike EVM where balance is just tracked, pallet-revive requires storage deposits
        // to be properly held (reserved) on the contract account. Just setting the free balance
        // is not enough - we must create the hold as would happen in a real transaction.
        //
        // Only fund if the pranked account is a contract.
        // EOAs don't need storage deposit funds since they don't execute code.
        let account_h160 = H160::from_slice(account.as_slice());

        // Check if account is a contract and get its info
        let Some(contract_info) = AccountInfo::<Runtime>::load_contract(&account_h160) else {
            return; // EOA or no contract - no funding needed
        };

        // Get the required deposit from contract info (includes code + storage deposits)
        let required_deposit: u128 = contract_info.total_deposit();
        if required_deposit == 0 {
            return;
        }

        // Convert to AccountId32 for balance operations
        let account_id = AccountId32Mapper::<Runtime>::to_fallback_account_id(&account_h160);

        // Convert HoldReason to RuntimeHoldReason
        let hold_reason: RuntimeHoldReason = HoldReason::StorageDepositReserve.into();

        // Fund the account with enough balance to cover the deposit + some buffer for ED
        // We need free balance available to then hold it
        let current_balance = Pallet::<Runtime>::evm_balance(&account_h160);
        let required_total = SpU256::from(required_deposit);
        if current_balance < required_total {
            Pallet::<Runtime>::set_evm_balance(&account_h160, required_total)
                .expect("Could not fund contract with required deposit");
        }

        // Create the hold on the contract account, as would happen in a real transaction.
        // This properly reserves the storage deposit funds.
        if let Err(e) = Balances::hold(&hold_reason, &account_id, required_deposit) {
            tracing::warn!(
                target: "cheatcodes",
                "Failed to hold storage deposit for contract {:?}: {:?}",
                account_h160,
                e
            );
        }
    }

pub mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
pub mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
/// Records of accounts that were explicitly dealt to via vm.deal().
pub eth_deals: Vec<DealRecord>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this field if we can just pass it to fund_pranked_accounts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed


// Skip accounts that were explicitly dealt to via vm.deal()
if mock_inner.eth_deals.iter().any(|deal| deal.address == account) {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should check if the balance aligns between foundry's REVM and pallet-revive and then align them if they are divergent in case of e.g vm.deal execution within a callback as it will not set the balance within pallet-revive but will be present in eth_deals.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also addressed, and we default to max if there is no deal

@filip-parity
Copy link
Copy Markdown
Author

hint from Claude AI, I did not check it in details but is worth to consider, I think:

pub(crate) fn fund_pranked_accounts(&self, account: Address) {
        // In pallet-revive, contracts need funds to cover storage deposits for their code.
        // If a pranked account is a contract with insufficient balance, it becomes non-functional
        // and any storage writes will fail with StorageDepositNotEnoughFunds.
        //
        // Unlike EVM where balance is just tracked, pallet-revive requires storage deposits
        // to be properly held (reserved) on the contract account. Just setting the free balance
        // is not enough - we must create the hold as would happen in a real transaction.
        //
        // Only fund if the pranked account is a contract.
        // EOAs don't need storage deposit funds since they don't execute code.
        let account_h160 = H160::from_slice(account.as_slice());

        // Check if account is a contract and get its info
        let Some(contract_info) = AccountInfo::<Runtime>::load_contract(&account_h160) else {
            return; // EOA or no contract - no funding needed
        };

        // Get the required deposit from contract info (includes code + storage deposits)
        let required_deposit: u128 = contract_info.total_deposit();
        if required_deposit == 0 {
            return;
        }

        // Convert to AccountId32 for balance operations
        let account_id = AccountId32Mapper::<Runtime>::to_fallback_account_id(&account_h160);

        // Convert HoldReason to RuntimeHoldReason
        let hold_reason: RuntimeHoldReason = HoldReason::StorageDepositReserve.into();

        // Fund the account with enough balance to cover the deposit + some buffer for ED
        // We need free balance available to then hold it
        let current_balance = Pallet::<Runtime>::evm_balance(&account_h160);
        let required_total = SpU256::from(required_deposit);
        if current_balance < required_total {
            Pallet::<Runtime>::set_evm_balance(&account_h160, required_total)
                .expect("Could not fund contract with required deposit");
        }

        // Create the hold on the contract account, as would happen in a real transaction.
        // This properly reserves the storage deposit funds.
        if let Err(e) = Balances::hold(&hold_reason, &account_id, required_deposit) {
            tracing::warn!(
                target: "cheatcodes",
                "Failed to hold storage deposit for contract {:?}: {:?}",
                account_h160,
                e
            );
        }
    }

Should this be addressed in the scope of this PR?

@filip-parity filip-parity requested a review from pkhry January 23, 2026 12:10
@filip-parity filip-parity merged commit e3ea87e into master Jan 23, 2026
60 of 78 checks passed
@filip-parity filip-parity deleted the filip/fund-pranked-accounts-dealt-tracking branch January 23, 2026 13:41
dimartiro pushed a commit to ChainSafe/foundry-polkadot that referenced this pull request Feb 9, 2026
…#496)

* Skip auto-funding for accounts that were explicitly dealt

* Use existing eth_deals

* Add repro test case for fund_pranked_accounts bug

* Remove unnecessary test

* Sync dealt balances to pallet-revive when pranking and pass eth_deals directly instead of cloning
re-gius added a commit that referenced this pull request Feb 10, 2026
* [Anvil] Fix impersonation marker collision with storage keys (#489)

* [Anvil] Fix impersonation marker collision with storage keys

Use 0xDE marker instead of 0x00 for impersonated transaction signatures
to avoid collision with Solidity mapping key computations for slot 0.

Closes #488

* !fixup af24a53a0

* Use original syntax

* fix

* update to latest polkadot-sdk (#467)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com>
Co-authored-by: zerosnacks <zerosnacks@protonmail.com>

* Fix etch cheatcode (#486)

* Add initial sync between REVM and pallet-revive (#487)

* add more deposit_limits (#493)

* add more deposit_limits

* clippy

* update compilers (#497)

* update foundry-compilers

* hardcode resolc version

* Fix external-projects results parsing (#499)

* Fix external-projects results parsing

* Change to json

* Add check for cheatcodes usage in pallet-revive (#494)

* Add immutable support (#498)

* add immutable

* Remove unnecessary genesis.rs changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* add test

* refactor: improve anvil_setImmutableStorageAt API to accept Vec<Bytes>

- Changed RPC interface to accept individual immutable values instead of concatenated bytes
- Moved endianness conversion (big-endian to little-endian) from test to RPC handler
- Updated test to pass immutable values as separate ABI-encoded Bytes elements
- Simplified byte conversion logic: removed unnecessary intermediate vector, using direct indexing
- Added comprehensive documentation explaining data format and conversion process
- All immutable values now handled consistently with better API clarity

This makes the RPC easier to invoke and aligns with how Sourcify and solc provide immutable data.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* Fix polkadotskip test (#502)

* Fix JSON parsing in external-projects CI (#501)

Co-authored-by: Pavlo Khrystenko <45178695+pkhry@users.noreply.github.com>

* Fix contract storage migration (#500)

* Skip auto-funding for accounts that were explicitly dealt (#496)

* Skip auto-funding for accounts that were explicitly dealt

* Use existing eth_deals

* Add repro test case for fund_pranked_accounts bug

* Remove unnecessary test

* Sync dealt balances to pallet-revive when pranking and pass eth_deals directly instead of cloning

* Add warning when overflow happens (#503)

* Set storage deposit for etched accounts (#504)

* Limit uint to u64::MAX for fuzz tests (#507)

* Add cheatcode tests (#435)

* update compilers (#511)

* Fix timestamp clamping to prevent overflow when converting to milliseconds (#510)

* Default eth_estimateGas block parameter to pending to match Anvil/EDR behavior (#509)

* Default eth_estimateGas to pending block to match Anvil/EDR

* Fix snapshot issues (#512)

* Add Claude config (#517)

* Attempt to fix CI (#513)

* change urls

* add transient storage support in `polkadot` test execution mode (#449)

* improve logging to match upstream anvil (#522)

* Fix compatibility issues after syncing with master

* Fmt

* Remove consensus.rs

* Fix retrieve para id

* skip proof recorder when forking is enabled

* Add comment explaining the irrelevant harcoded slot duration

* Fix fmt and clippy

---------

Co-authored-by: PG Herveou <pgherveou@parity.io>
Co-authored-by: Pavlo Khrystenko <45178695+pkhry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com>
Co-authored-by: zerosnacks <zerosnacks@protonmail.com>
Co-authored-by: Sebastian Miasojed <s.miasojed@gmail.com>
Co-authored-by: filip-parity <filip.baciu@parity.io>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Marian Radu <marian@parity.io>
Co-authored-by: Giuseppe Re <giuseppe.re@parity.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants