Fix/ny dynamic calldatacopy fmp (#564)

Bug found by @jubnzv:

`--newyork`: a `calldatacopy`/`codecopy`/`returndatacopy` to a dynamic
destination that could overwrite the free-memory-pointer word truncated
a subsequent `mload(0x40)` to `0`.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Agent <agent@local>
This commit is contained in:
authored and GitHub committed 2026-07-08 15:44:11 +02:00
1 parent 066c6d26f0
commit 9adfd9db24
4 files changed
+191 -1

No files matched your search

+30
View File
@@ -4769,6 +4769,36 @@ fn array_bounds_probe() {
}
}
/// Regression (newyork FMP range proof): a `calldatacopy` whose *dynamic*
/// destination can land on the free-memory-pointer word `[0x40, 0x60)` corrupts
/// the FMP, but only *static* copy destinations flagged `fmp_could_be_unbounded`.
/// `calldatacopy(and(b, 0xFF), 0, 23)` (with `b`'s low byte `0x40`) overwrites the
/// FMP; the subsequent `mload(0x40)` was truncated to `0` by the surviving range
/// proof instead of returning the copied bytes (23 × 0x11 then 9 × 0x00). The fix
/// flags a dynamic copy destination unless it is provably free-pointer-relative
/// (`add(mload(0x40), k)`, `>= 0x80`). Compared newyork-PVM vs solc-EVM.
#[test]
fn calldatacopy_dynamic_dest_fmp_corruption() {
let mut actions = instantiate_yul(
"contracts/CalldatacopyFmpDynBug.yul",
"CalldatacopyFmpDynBug",
);
// word0: source bytes (0x11); word1: `b` with low byte 0x40 (the copy destination).
let mut data = vec![0x11u8; 32];
let mut b = [0u8; 32];
b[31] = 0x40;
data.extend_from_slice(&b);
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
data,
});
run_differential(actions);
}
/// Regression (newyork dead-store elimination): a store read back by an
/// intervening unaligned *overlapping* load must not be eliminated as dead.
/// `mem_opt` marked a pending store read only on an exact-offset load, so