Skip to content

Commit 1048425

Browse files
committed
feat: add changedMappingKeys and mappingValueDiff precompile interfaces
1 parent 7d75436 commit 1048425

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

src/PhEvm.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,29 @@ interface PhEvm {
253253
/// @dev Returns the transaction envelope data for the assertion-triggering tx
254254
/// @return txObject The transaction data struct
255255
function getTxObject() external view returns (TxObject memory txObject);
256+
257+
/// @notice Returns canonical Solidity key encodings h(key) for keys
258+
/// whose mapping entry at baseSlot was written during the tx.
259+
/// @dev Best-effort heuristic: traces KECCAK256 -> SSTORE provenance in the
260+
/// execution trace. Custom inline assembly or precomputed hashed slots
261+
/// can bypass the visible keccak chain and produce false negatives.
262+
/// @param target The contract whose storage was modified.
263+
/// @param baseSlot The Solidity mapping's base storage slot.
264+
/// @return keys Array of encoded keys (each is the h(key) preimage).
265+
function changedMappingKeys(address target, bytes32 baseSlot) external view returns (bytes[] memory keys);
266+
267+
/// @notice Returns the pre/post values for a specific mapping entry.
268+
/// @dev Computes slot = keccak256(key ++ baseSlot) + fieldOffset, then reads
269+
/// pre from the PreTx fork and post from the PostTx fork.
270+
/// @param target The contract address.
271+
/// @param baseSlot The mapping's base slot.
272+
/// @param key The canonical encoding h(key) of the mapping key.
273+
/// @param fieldOffset Struct field offset (0 for the first slot of the value).
274+
/// @return pre The PreTx value.
275+
/// @return post The PostTx value.
276+
/// @return changed True if pre != post.
277+
function mappingValueDiff(address target, bytes32 baseSlot, bytes calldata key, uint256 fieldOffset)
278+
external
279+
view
280+
returns (bytes32 pre, bytes32 post, bool changed);
256281
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.13;
3+
4+
import {Assertion} from "../../Assertion.sol";
5+
import {PhEvm} from "../../PhEvm.sol";
6+
7+
contract MappingTarget {
8+
// slot 0
9+
mapping(address => uint256) public balances;
10+
11+
function setBalance(address user, uint256 amount) external {
12+
balances[user] = amount;
13+
}
14+
}
15+
16+
MappingTarget constant MAPPING_TARGET = MappingTarget(0xdCCf1eEB153eF28fdc3CF97d33f60576cF092e9c);
17+
18+
contract TestChangedMappingKeys is Assertion {
19+
constructor() payable {}
20+
21+
function checkChangedKeys() external view {
22+
bytes[] memory keys = ph.changedMappingKeys(address(MAPPING_TARGET), bytes32(uint256(0)));
23+
require(keys.length == 2, "expected 2 changed keys");
24+
}
25+
26+
function triggers() external view override {
27+
registerCallTrigger(this.checkChangedKeys.selector);
28+
}
29+
}
30+
31+
contract TestMappingValueDiff is Assertion {
32+
constructor() payable {}
33+
34+
function checkValueDiff() external view {
35+
address user = address(0xBEEF);
36+
bytes memory key = abi.encode(user);
37+
38+
(bytes32 pre, bytes32 post, bool changed) =
39+
ph.mappingValueDiff(address(MAPPING_TARGET), bytes32(uint256(0)), key, 0);
40+
41+
require(changed, "value should have changed");
42+
require(pre == bytes32(uint256(0)), "pre should be zero");
43+
require(post == bytes32(uint256(100)), "post should be 100");
44+
}
45+
46+
function triggers() external view override {
47+
registerCallTrigger(this.checkValueDiff.selector);
48+
}
49+
}
50+
51+
contract MappingTriggeringTx {
52+
constructor() payable {
53+
MAPPING_TARGET.setBalance(address(0xBEEF), 100);
54+
MAPPING_TARGET.setBalance(address(0xCAFE), 200);
55+
}
56+
}

0 commit comments

Comments
 (0)