Skip to content

feat: revert on msg.sender usage inside broadcast in script contracts#13349

Open
gakonst wants to merge 7 commits intomasterfrom
fix/revert-msg-sender-in-broadcast
Open

feat: revert on msg.sender usage inside broadcast in script contracts#13349
gakonst wants to merge 7 commits intomasterfrom
fix/revert-msg-sender-in-broadcast

Conversation

@gakonst
Copy link
Member

@gakonst gakonst commented Feb 6, 2026

Summary

Prevents the class of bugs where msg.sender in forge scripts resolves to the wrong address inside broadcast blocks.

Ref: #13346

Changes

crates/cheatcodes/src/inspector.rs

Added script_address: Option<Address> field to Cheatcodes. In step(), when the CALLER opcode executes inside the script contract while a broadcast is active and broadcast.original_origin != broadcast.new_origin, the script reverts with:

Usage of `msg.sender` inside a `broadcast` in script contract detected.
`msg.sender` is the default sender `0x1804...`, not the broadcast signer `0xf39f...`.
Use `vm.addr(<pk>)` instead.

Same pattern as the existing address(this) / ADDRESS opcode protection. Gated behind script_execution_protection.

crates/evm/evm/src/inspectors/stack.rs

Moved InspectorStack::script() from InspectorStackInner to InspectorStack so it can propagate the script address to both ScriptExecutionInspector and Cheatcodes.

crates/script/src/lib.rs

Replaced maybe_load_private_key() with maybe_load_sender(). The old method only checked raw private keys and Turnkey to infer --sender, so Ledger, Trezor, AWS KMS, GCP KMS, keystore, and browser wallet users would always get the default sender even with a single signer. Now uses Wallets::signers() which includes all wallet backends.

Removes the alloy-signer dependency from forge-script.

crates/forge/tests/cli/script.rs

Two new tests:

  • should_revert_on_msg_sender_in_broadcastmsg.sender inside startBroadcast(pk) reverts; disabled with script_execution_protection = false
  • should_allow_msg_sender_in_broadcast_when_matchingmsg.sender inside startBroadcast() with --private-key passes (sender matches)

testdata/default/cheats/Broadcast.t.sol

Updated CheckOverrides: removed msg.sender / tx.origin assertions from inside the startBroadcast(0x1337) block since these now correctly revert.

When a script uses msg.sender inside a vm.startBroadcast(pk) block where
the broadcast signer differs from the default script sender, the script
now reverts with a clear error message. This prevents a class of bugs
where tokens or values are accidentally sent to the wrong address
(Foundry's default sender 0x1804... instead of the intended signer).

The check is gated behind script_execution_protection (same as the
existing address(this) check) and only triggers when:
- CALLER opcode executes in the script contract
- A broadcast is active
- The broadcast signer differs from tx.origin (default sender)

When --sender or --private-key is passed to forge script, msg.sender
already matches the broadcast signer, so no revert occurs.

Ref: #13346
Amp-Thread-ID: https://ampcode.com/threads/T-019c320e-8bbc-730f-a652-2a2c2fba1714
Co-authored-by: Amp <amp@ampcode.com>
@gakonst gakonst force-pushed the fix/revert-msg-sender-in-broadcast branch from 448eba2 to bc54e26 Compare February 6, 2026 09:26
gakonst and others added 5 commits February 6, 2026 09:33
Previously, maybe_load_private_key only checked raw private keys and
Turnkey to infer the --sender. This meant hardware wallets (Ledger,
Trezor) and cloud KMS signers (AWS, GCP) were not considered, causing
msg.sender in scripts to remain the default sender even when exactly
one signer was provided.

Renamed to maybe_load_sender and now uses Wallets::signers() which
includes all wallet types (private keys, keystores, ledger, trezor,
AWS KMS, GCP KMS, Turnkey, browser). If exactly one signer exists
across all backends, it is used as the script sender.

Amp-Thread-ID: https://ampcode.com/threads/T-019c320e-8bbc-730f-a652-2a2c2fba1714
Co-authored-by: Amp <amp@ampcode.com>
- Fix formatting in stack.rs (nightly rustfmt)
- Update CheckOverrides test to not use msg.sender inside a
  startBroadcast(0x1337) block, since the new protection correctly
  reverts on this pattern

Amp-Thread-ID: https://ampcode.com/threads/T-019c320e-8bbc-730f-a652-2a2c2fba1714
Co-authored-by: Amp <amp@ampcode.com>
- vm.broadcast(pk) single-call variant
- startBroadcast(address) variant
- msg.sender before/after broadcast (allowed)
- msg.sender inside deployed contracts called from broadcast (allowed)
- tx.origin inside broadcast (allowed, only CALLER is checked)
- --sender flag matching broadcast signer (allowed)
- startBroadcast() no args, no --sender (allowed, signer == default)

Amp-Thread-ID: https://ampcode.com/threads/T-019c320e-8bbc-730f-a652-2a2c2fba1714
Co-authored-by: Amp <amp@ampcode.com>
Replace 'Use vm.addr(<pk>) instead' with 'Use the --sender flag or
store the deployer address in a variable instead' to avoid pushing
users toward private key injection in scripts.

Amp-Thread-ID: https://ampcode.com/threads/T-019c320e-8bbc-730f-a652-2a2c2fba1714
Co-authored-by: Amp <amp@ampcode.com>
@zerosnacks zerosnacks marked this pull request as ready for review February 6, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants