Skip to content

Fix generated address returned by Substrate RPC runtime call#8504

Merged
athei merged 27 commits intomasterfrom
castillax-dryrun-api
May 21, 2025
Merged

Fix generated address returned by Substrate RPC runtime call#8504
athei merged 27 commits intomasterfrom
castillax-dryrun-api

Conversation

@castillax
Copy link
Copy Markdown

@castillax castillax commented May 13, 2025

Description

When dry-running a contract deployment through the runtime API, the returned address does not match the actual address that will be used when the transaction is submitted. This inconsistency occurs because the address derivation logic doesn't properly account for the difference between transaction execution and dry-run execution contexts.

The issue stems from the create1 address derivation logic in exec.rs:

address::create1(
    &deployer,
    // the Nonce from the origin has been incremented pre-dispatch, so we
    // need to subtract 1 to get the nonce at the time of the call.
    if origin_is_caller {
        account_nonce.saturating_sub(1u32.into()).saturated_into()
    } else {
        account_nonce.saturated_into()
    },
)

The code correctly subtracts 1 from the account nonce during a transaction execution (because the nonce is incremented pre-dispatch), but doesn't account for execution context - whether it's a real transaction or a dry run through the RPC.

Review Notes

This PR adds a new condition to check for the IncrementOnce when calculating the nonce for address derivation:

address::create1(
    &deployer,
    // the Nonce from the origin has been incremented pre-dispatch, so we
    // need to subtract 1 to get the nonce at the time of the call.
    if origin_is_caller && matches!(exec_context, IncrementOnce::AlreadyIncremented) {
        account_nonce.saturating_sub(1u32.into()).saturated_into()
    } else {
        account_nonce.saturated_into()
    },
)

Before Fix

  • Dry-run contract deployment returns address derived with nonce N
  • Actual transaction deployment creates contract at address derived with nonce N-1
  • Result: Inconsistent addresses between simulation and actual execution

After Fix

  • Dry-run and actual transaction deployments both create contracts at the same address
  • Result: Consistent contract addresses regardless of execution context
  • Added test case to verify nonce handling in different execution contexts

This fix ensures that users can rely on the address returned by a dry run to match the actual address that will be used when the transaction is submitted.

Fixes paritytech/contract-issues#37

Checklist

  • My PR includes a detailed description as outlined in the "Description" and its two subsections above.
  • My PR follows the labeling requirements of this project (at minimum one label for T required)
    • External contributors: ask maintainers to put the right label on your PR.
  • I have made corresponding changes to the documentation (if applicable)
  • I have added tests that prove my fix is effective or that my feature works (if applicable)

@castillax
Copy link
Copy Markdown
Author

/cmd prdoc --audience runtime_dev --bump patch

Copy link
Copy Markdown
Contributor

@pgherveou pgherveou left a comment

Choose a reason for hiding this comment

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

skimmed trough it quickly, will take another look in a bit

@athei
Copy link
Copy Markdown
Member

athei commented May 13, 2025

/cmd fmt

@castillax castillax added the T7-smart_contracts This PR/Issue is related to smart contracts. label May 13, 2025
@pgherveou
Copy link
Copy Markdown
Contributor

can you cherry pick this eb1fe2b

I want to make sure that we are not breaking evm-test-suite here

@castillax castillax requested review from a team as code owners May 13, 2025 12:44
Self::convert_native_to_evm(value),
data,
storage_deposit_limit.is_unchecked(),
exec_context,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not good. Before the skip transfer was depending on whether we had a storage deposit limit. Now it is passed separately. We can have the situation where the limit is unchecked but the transfer is not skipped. I don't think we want that.

gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block),
pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
input_data,
pallet_revive::ExecContext::Transaction,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is dry_run. Same for the others.

Comment on lines +515 to +522
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExecContext {
/// A normal transaction execution, where the nonce has been pre-incremented.
Transaction,

/// A dry run execution (through RPC), where the nonce has not been incremented.
DryRun { skip_transfer: bool },
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think it makes sense to merge bump nonce logic with skip_transfer. Whether the transfer is skipped is already determined by the storage_deposit_limit argument. This also leads to the sitation that we need to add this arg to the call where it doesn't matter if we have to bump or not. We are also passing through this whole exec context even if we don't need the information later (as opposed to skip_transfer which we need for the whole stack duration).

I suggest just sticking to the original enum BumpNonce design. What do you think @pgherveou?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes you are right my bad, you can just pass the BumpNonce as an extra argument to run so we only carry it on the Stack
https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/revive/src/exec.rs?plain=1#L1078-L1074

@castillax
Copy link
Copy Markdown
Author

/cmd prdoc --audience runtime_dev --bump patch

@github-actions
Copy link
Copy Markdown
Contributor

Command "prdoc --audience runtime_dev --bump patch" has failed ❌! See logs here

@athei
Copy link
Copy Markdown
Member

athei commented May 16, 2025

I will have another look when the CI is green. The ones flagged as required.

castillax and others added 3 commits May 16, 2025 10:09
@paritytech-workflow-stopper
Copy link
Copy Markdown

All GitHub workflows were cancelled due to failure one of the required jobs.
Failed workflow url: https://github.com/paritytech/polkadot-sdk/actions/runs/15137311658
Failed job name: test-linux-stable

@athei athei added this pull request to the merge queue May 21, 2025
Merged via the queue into master with commit 2863b7a May 21, 2025
246 of 247 checks passed
@athei athei deleted the castillax-dryrun-api branch May 21, 2025 12:52
pgherveou added a commit that referenced this pull request May 26, 2025
ordian added a commit that referenced this pull request May 27, 2025
* master: (99 commits)
  Snowbridge: Remove asset location check for compatibility (#8473)
  add poke_deposit extrinsic to pallet-bounties (#8382)
  litep2p/peerset: Reject non-reserved peers in the reserved-only mode (#8650)
  Charge deposit based on key length (#8648)
  [pallet-revive] make subscription task panic on error (#8587)
  tx/metrics: Add metrics for the RPC v2 `transactionWatch_v1_submitAndWatch` (#8345)
  Bridges: Fix - Improve try-state for pallet-xcm-bridge-hub (#8615)
  Introduce CreateBare, deprecated CreateInherent (#7597)
  Use hashbrown hashmap/hashset in validation context (#8606)
  ci: rm gitlab config (#8622)
  🔪 flaky and Zombienet tests (#8600)
  cumulus: adjust unincluded segment size metric buckets (#8617)
  Benchmark storage access on block validation (#8069)
  Revert 7934 es/remove tj changes (#8611)
  collator-protocol: add more collation observability (#8230)
  `fatxpool`: add fallback for ready at light (#8533)
  txpool: fix tx removal from unlocks set (#8500)
  XCMP weight metering: account for the MQ page position (#8344)
  fix epmb solution duplicate issue + add remote mining apparatus to epm (#8585)
  Fix generated address returned by Substrate RPC runtime call (#8504)
  ...
github-merge-queue bot pushed a commit that referenced this pull request Jun 2, 2025
- Revert #8504
- Add a `prepare_dry_run` that run before bare_call / bare_instantiate

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
pgherveou added a commit that referenced this pull request Jun 11, 2025
- Revert #8504
- Add a `prepare_dry_run` that run before bare_call / bare_instantiate

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
alvicsam pushed a commit that referenced this pull request Oct 17, 2025
## Description

When dry-running a contract deployment through the runtime API, the
returned address does not match the actual address that will be used
when the transaction is submitted. This inconsistency occurs because the
address derivation logic doesn't properly account for the difference
between transaction execution and dry-run execution contexts.

The issue stems from the `create1` address derivation logic in
`exec.rs`:

```rust
address::create1(
    &deployer,
    // the Nonce from the origin has been incremented pre-dispatch, so we
    // need to subtract 1 to get the nonce at the time of the call.
    if origin_is_caller {
        account_nonce.saturating_sub(1u32.into()).saturated_into()
    } else {
        account_nonce.saturated_into()
    },
)
```

The code correctly subtracts 1 from the account nonce during a
transaction execution (because the nonce is incremented pre-dispatch),
but doesn't account for execution context - whether it's a real
transaction or a dry run through the RPC.

## Review Notes

This PR adds a new condition to check for the `IncrementOnce` when
calculating the nonce for address derivation:

```rust
address::create1(
    &deployer,
    // the Nonce from the origin has been incremented pre-dispatch, so we
    // need to subtract 1 to get the nonce at the time of the call.
    if origin_is_caller && matches!(exec_context, IncrementOnce::AlreadyIncremented) {
        account_nonce.saturating_sub(1u32.into()).saturated_into()
    } else {
        account_nonce.saturated_into()
    },
)
```


## Before Fix

- Dry-run contract deployment returns address derived with nonce N
- Actual transaction deployment creates contract at address derived with
nonce N-1
- Result: Inconsistent addresses between simulation and actual execution

## After Fix

- Dry-run and actual transaction deployments both create contracts at
the same address
- Result: Consistent contract addresses regardless of execution context
- Added test case to verify nonce handling in different execution
contexts

This fix ensures that users can rely on the address returned by a dry
run to match the actual address that will be used when the transaction
is submitted.

Fixes paritytech/contract-issues#37

# Checklist

* [x] My PR includes a detailed description as outlined in the
"Description" and its two subsections above.
* [x] My PR follows the [labeling requirements](

https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process
) of this project (at minimum one label for `T` required)
* External contributors: ask maintainers to put the right label on your
PR.
* [x] I have made corresponding changes to the documentation (if
applicable)
* [x] I have added tests that prove my fix is effective or that my
feature works (if applicable)

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: pgherveou <pgherveou@gmail.com>
alvicsam pushed a commit that referenced this pull request Oct 17, 2025
- Revert #8504
- Add a `prepare_dry_run` that run before bare_call / bare_instantiate

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T7-smart_contracts This PR/Issue is related to smart contracts.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Fix generated address returned by Substrate RPC runtime call

3 participants