Skip to content

Commit a319166

Browse files
committed
Check in the NonceManager contract and its Foundry test
1 parent 25ee95f commit a319166

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

contracts/predeploys/NonceManager.sol

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.15;
3+
4+
/// @title NonceManager
5+
/// @notice The NonceManager manages nonce of smart accounts using RIP-7560.
6+
contract NonceManager {
7+
/// @notice Semantic version.
8+
/// @custom:semver 0.1.0
9+
string public constant version = "0.1.0";
10+
11+
/// @notice The EntryPoint address defined at RIP-7560.
12+
address internal constant AA_ENTRY_POINT = 0x0000000000000000000000000000000000007560;
13+
14+
fallback(bytes calldata data) external returns (bytes memory) {
15+
if (msg.sender == AA_ENTRY_POINT) {
16+
_validateIncrement(data);
17+
return new bytes(0);
18+
} else {
19+
return abi.encodePacked(_get(data));
20+
}
21+
}
22+
23+
/// @notice Return the next nonce for this sender. Within a given key, the nonce values are sequenced
24+
/// (starting with zero, and incremented by one on each transaction).
25+
/// But transactions with different keys can come with arbitrary order.
26+
/// @return nonce a full nonce to pass for next transaction with given sender and key.
27+
function _get(bytes calldata /* data */) internal view returns (uint256 nonce) {
28+
assembly {
29+
// Check if calldata is 44 bytes long
30+
if iszero(eq(calldatasize(), 44)) {
31+
mstore(0x00, 0x947d5a84) // 'InvalidLength()'
32+
revert(0x1c, 0x04)
33+
}
34+
35+
let ptr := mload(0x40)
36+
calldatacopy(ptr, 0, 44)
37+
38+
// Extract key and sender from calldata
39+
let key := shr(64, mload(add(ptr, 20)))
40+
mstore(0x00, key)
41+
mstore(0x14, shr(96, mload(ptr)))
42+
43+
// Load nonce from storage
44+
nonce := or(shl(64, key), sload(keccak256(0x04, 0x30)))
45+
}
46+
}
47+
48+
/// @notice validate nonce uniqueness for this account. Called by AA_ENTRY_POINT.
49+
function _validateIncrement(bytes calldata /* data */) internal {
50+
assembly {
51+
let ptr := mload(0x40)
52+
calldatacopy(ptr, 0, calldatasize())
53+
54+
// Store key and sender in memory
55+
mstore(0x00, shr(64, mload(add(ptr, 20))))
56+
mstore(0x14, shr(96, mload(ptr)))
57+
58+
// Calculate storage slot and load current nonce
59+
let nonceSlot := keccak256(0x04, 0x30)
60+
let currentNonce := sload(nonceSlot)
61+
62+
// Revert if nonce mismatch
63+
if iszero(eq(shr(192, mload(add(ptr, 44))), currentNonce)) {
64+
mstore(0, 0)
65+
revert(0, 0) // Revert if nonce mismatch
66+
}
67+
68+
// Increment nonce
69+
sstore(nonceSlot, add(currentNonce, 1))
70+
}
71+
}
72+
}

contracts/test/NonceManager.t.sol

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.15;
3+
4+
// Testing utilities
5+
import { Test } from "forge-std/Test.sol";
6+
import "../predeploys/NonceManager.sol";
7+
8+
contract NonceManagerTest is Test {
9+
address private constant aaEntryPoint = 0x0000000000000000000000000000000000007560;
10+
address private constant alice = 0x0000000000000000000000000000000000007777;
11+
uint192 public aliceKey;
12+
error InvalidLength();
13+
NonceManager nonceManager;
14+
15+
/// @dev Sets up the test suite.
16+
function setUp() public virtual {
17+
nonceManager = new NonceManager();
18+
aliceKey = uint192(3);
19+
}
20+
21+
/// @dev Tests that increasing nonce is done properly.
22+
function test_validateIncrement_succeeds() public {
23+
vm.prank(aaEntryPoint);
24+
(bool success, bytes memory returnData) =
25+
address(nonceManager).call(abi.encodePacked(alice, aliceKey, uint64(0)));
26+
assertTrue(success);
27+
assertEq(returnData, new bytes(0));
28+
}
29+
30+
/// @dev Tests that increasing nonce with invalid nonce fails.
31+
function test_validateIncrement_invalidNonce_fails() external {
32+
vm.prank(aaEntryPoint);
33+
(bool success,) = address(nonceManager).call(abi.encodePacked(alice, aliceKey, uint64(1)));
34+
assertFalse(success);
35+
}
36+
37+
/// @dev Tests that getting nonce through fallback is done properly.
38+
function test_fallback_get_succeeds() external {
39+
(bool success, bytes memory returnData) = address(nonceManager).call(abi.encodePacked(alice, aliceKey));
40+
assertTrue(success);
41+
assertEq(returnData, abi.encodePacked(uint192(aliceKey), uint64(0)));
42+
43+
test_validateIncrement_succeeds();
44+
45+
(success, returnData) = address(nonceManager).call(abi.encodePacked(alice, aliceKey));
46+
assertTrue(success);
47+
assertEq(returnData, abi.encodePacked(uint192(aliceKey), uint64(1)));
48+
}
49+
50+
/// @dev Tests that getting nonce with invalid length fails.
51+
function test_fallback_get_invalidLength_fails() external {
52+
vm.expectRevert(InvalidLength.selector);
53+
(bool success,) = address(nonceManager).call(abi.encodePacked(alice, aliceKey, uint64(1)));
54+
(success); // silence unused variable warning
55+
}
56+
57+
/// @dev Fuzz test for validateIncrement function.
58+
function testFuzz_validateIncrement_succeeds(uint256 n) external {
59+
bool success;
60+
bytes memory returnData;
61+
62+
n = n % 1000; // limit the number of iterations to prevent OOG
63+
for (uint256 i = 0; i < n; i++) {
64+
vm.prank(aaEntryPoint);
65+
(success,) = address(nonceManager).call(abi.encodePacked(alice, aliceKey, uint64(i)));
66+
assertTrue(success);
67+
}
68+
69+
(success, returnData) = address(nonceManager).call(abi.encodePacked(alice, aliceKey));
70+
assertTrue(success);
71+
assertEq(returnData, abi.encodePacked(uint192(aliceKey), uint64(n)));
72+
}
73+
}

0 commit comments

Comments
 (0)