Skip to content

Commit a70a20c

Browse files
authored
test: finish test cases for per hook data (#157)
1 parent ff62f45 commit a70a20c

File tree

2 files changed

+140
-45
lines changed

2 files changed

+140
-45
lines changed

test/account/PerHookData.t.sol

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface
66
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
77

88
import {ReferenceModularAccount} from "../../src/account/ReferenceModularAccount.sol";
9-
109
import {HookConfigLib} from "../../src/helpers/HookConfigLib.sol";
1110
import {ModuleEntity, ModuleEntityLib} from "../../src/helpers/ModuleEntityLib.sol";
1211
import {SparseCalldataSegmentLib} from "../../src/helpers/SparseCalldataSegmentLib.sol";
12+
import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol";
1313

1414
import {Counter} from "../mocks/Counter.sol";
1515
import {MockAccessControlHookModule} from "../mocks/modules/MockAccessControlHookModule.sol";
@@ -22,6 +22,10 @@ contract PerHookDataTest is CustomValidationTestBase {
2222

2323
Counter internal _counter;
2424

25+
uint32 internal constant _VALIDATION_ENTITY_ID = 0;
26+
uint32 internal constant _PRE_HOOK_ENTITY_ID_1 = 0;
27+
uint32 internal constant _PRE_HOOK_ENTITY_ID_2 = 1;
28+
2529
function setUp() public {
2630
_counter = new Counter();
2731

@@ -127,7 +131,58 @@ contract PerHookDataTest is CustomValidationTestBase {
127131
entryPoint.handleOps(userOps, beneficiary);
128132
}
129133

130-
// todo: index out of order failure case with 2 pre hooks
134+
function test_passAccessControl_twoHooks_userOp() public {
135+
_installSecondPreHook();
136+
137+
assertEq(_counter.number(), 0);
138+
139+
(PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP();
140+
141+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash());
142+
143+
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](2);
144+
preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)});
145+
preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)});
146+
147+
userOp.signature = _encodeSignature(
148+
_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)
149+
);
150+
151+
PackedUserOperation[] memory userOps = new PackedUserOperation[](1);
152+
userOps[0] = userOp;
153+
154+
entryPoint.handleOps(userOps, beneficiary);
155+
156+
assertEq(_counter.number(), 1);
157+
}
158+
159+
function test_failAccessControl_indexOutOfOrder_userOp() public {
160+
_installSecondPreHook();
161+
162+
(PackedUserOperation memory userOp, bytes32 userOpHash) = _getCounterUserOP();
163+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, userOpHash.toEthSignedMessageHash());
164+
165+
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](3);
166+
preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)});
167+
preValidationHookData[1] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)});
168+
169+
userOp.signature = _encodeSignature(
170+
_signerValidation, GLOBAL_VALIDATION, preValidationHookData, abi.encodePacked(r, s, v)
171+
);
172+
173+
PackedUserOperation[] memory userOps = new PackedUserOperation[](1);
174+
userOps[0] = userOp;
175+
176+
vm.expectRevert(
177+
abi.encodeWithSelector(
178+
IEntryPoint.FailedOpWithRevert.selector,
179+
0,
180+
"AA23 reverted",
181+
abi.encodeWithSelector(SparseCalldataSegmentLib.SegmentOutOfOrder.selector)
182+
)
183+
);
184+
entryPoint.handleOps(userOps, beneficiary);
185+
}
131186

132187
function test_failAccessControl_badTarget_userOp() public {
133188
PackedUserOperation memory userOp = PackedUserOperation({
@@ -248,7 +303,7 @@ contract PerHookDataTest is CustomValidationTestBase {
248303
abi.encodeWithSelector(
249304
ReferenceModularAccount.PreRuntimeValidationHookFailed.selector,
250305
_accessControlHookModule,
251-
uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK),
306+
_PRE_HOOK_ENTITY_ID_1,
252307
abi.encodeWithSignature("Error(string)", "Proof doesn't match target")
253308
)
254309
);
@@ -266,7 +321,7 @@ contract PerHookDataTest is CustomValidationTestBase {
266321
abi.encodeWithSelector(
267322
ReferenceModularAccount.PreRuntimeValidationHookFailed.selector,
268323
_accessControlHookModule,
269-
uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK),
324+
_PRE_HOOK_ENTITY_ID_1,
270325
abi.encodeWithSignature("Error(string)", "Proof doesn't match target")
271326
)
272327
);
@@ -295,7 +350,42 @@ contract PerHookDataTest is CustomValidationTestBase {
295350
);
296351
}
297352

298-
//todo: index out of order failure case with 2 pre hooks
353+
function test_passAccessControl_twoHooks_runtime() public {
354+
_installSecondPreHook();
355+
356+
assertEq(_counter.number(), 0);
357+
358+
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](2);
359+
preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)});
360+
preValidationHookData[1] = PreValidationHookData({index: 1, validationData: abi.encodePacked(_counter)});
361+
362+
vm.prank(owner1);
363+
account1.executeWithAuthorization(
364+
abi.encodeCall(
365+
ReferenceModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ()))
366+
),
367+
_encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "")
368+
);
369+
370+
assertEq(_counter.number(), 1);
371+
}
372+
373+
function test_failAccessControl_indexOutOfOrder_runtime() public {
374+
_installSecondPreHook();
375+
376+
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](3);
377+
preValidationHookData[0] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)});
378+
preValidationHookData[1] = PreValidationHookData({index: 0, validationData: abi.encodePacked(_counter)});
379+
380+
vm.prank(owner1);
381+
vm.expectRevert(abi.encodeWithSelector(SparseCalldataSegmentLib.SegmentOutOfOrder.selector));
382+
account1.executeWithAuthorization(
383+
abi.encodeCall(
384+
ReferenceModularAccount.execute, (address(_counter), 0 wei, abi.encodeCall(Counter.increment, ()))
385+
),
386+
_encodeSignature(_signerValidation, GLOBAL_VALIDATION, preValidationHookData, "")
387+
);
388+
}
299389

300390
function test_failAccessControl_badTarget_runtime() public {
301391
PreValidationHookData[] memory preValidationHookData = new PreValidationHookData[](1);
@@ -306,7 +396,7 @@ contract PerHookDataTest is CustomValidationTestBase {
306396
abi.encodeWithSelector(
307397
ReferenceModularAccount.PreRuntimeValidationHookFailed.selector,
308398
_accessControlHookModule,
309-
uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK),
399+
_PRE_HOOK_ENTITY_ID_1,
310400
abi.encodeWithSignature("Error(string)", "Target not allowed")
311401
)
312402
);
@@ -394,6 +484,19 @@ contract PerHookDataTest is CustomValidationTestBase {
394484
account1.isValidSignature(messageHash, _encode1271Signature(_signerValidation, abi.encodePacked(r, s, v)));
395485
}
396486

487+
function _installSecondPreHook() internal {
488+
// depends on the ability of `installValidation` to append hooks
489+
bytes[] memory hooks = new bytes[](1);
490+
hooks[0] = abi.encodePacked(
491+
HookConfigLib.packValidationHook(address(_accessControlHookModule), _PRE_HOOK_ENTITY_ID_2),
492+
abi.encode(_PRE_HOOK_ENTITY_ID_2, _counter)
493+
);
494+
vm.prank(address(entryPoint));
495+
account1.installValidation(
496+
ValidationConfigLib.pack(_signerValidation, true, false), new bytes4[](0), "", hooks
497+
);
498+
}
499+
397500
function _getCounterUserOP() internal view returns (PackedUserOperation memory, bytes32) {
398501
PackedUserOperation memory userOp = PackedUserOperation({
399502
sender: address(account1),
@@ -424,13 +527,11 @@ contract PerHookDataTest is CustomValidationTestBase {
424527
{
425528
bytes[] memory hooks = new bytes[](1);
426529
hooks[0] = abi.encodePacked(
427-
HookConfigLib.packValidationHook(
428-
address(_accessControlHookModule), uint32(MockAccessControlHookModule.EntityId.PRE_VALIDATION_HOOK)
429-
),
430-
abi.encode(_counter)
530+
HookConfigLib.packValidationHook(address(_accessControlHookModule), _PRE_HOOK_ENTITY_ID_1),
531+
abi.encode(_PRE_HOOK_ENTITY_ID_1, _counter)
431532
);
432533
// patched to also work during SMA tests by differentiating the validation
433-
_signerValidation = ModuleEntityLib.pack(address(singleSignerValidationModule), type(uint32).max - 1);
434-
return (_signerValidation, true, true, new bytes4[](0), abi.encode(type(uint32).max - 1, owner1), hooks);
534+
_signerValidation = ModuleEntityLib.pack(address(singleSignerValidationModule), _VALIDATION_ENTITY_ID);
535+
return (_signerValidation, true, true, new bytes4[](0), abi.encode(_VALIDATION_ENTITY_ID, owner1), hooks);
435536
}
436537
}

test/mocks/modules/MockAccessControlHookModule.sol

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,16 @@ import {BaseModule} from "../../../src/modules/BaseModule.sol";
1313
// This is just a mock - it does not enforce this over `executeBatch` and other methods of making calls, and should
1414
// not be used in production..
1515
contract MockAccessControlHookModule is IValidationHookModule, BaseModule {
16-
enum EntityId {
17-
PRE_VALIDATION_HOOK
18-
}
19-
20-
mapping(address account => address allowedTarget) public allowedTargets;
16+
mapping(uint32 entityId => mapping(address account => address allowedTarget)) public allowedTargets;
2117

2218
function onInstall(bytes calldata data) external override {
23-
address allowedTarget = abi.decode(data, (address));
24-
allowedTargets[msg.sender] = allowedTarget;
19+
(uint32 entityId, address allowedTarget) = abi.decode(data, (uint32, address));
20+
allowedTargets[entityId][msg.sender] = allowedTarget;
2521
}
2622

27-
function onUninstall(bytes calldata) external override {
28-
delete allowedTargets[msg.sender];
23+
function onUninstall(bytes calldata data) external override {
24+
uint32 entityId = abi.decode(data, (uint32));
25+
delete allowedTargets[entityId][msg.sender];
2926
}
3027

3128
function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32)
@@ -34,18 +31,17 @@ contract MockAccessControlHookModule is IValidationHookModule, BaseModule {
3431
override
3532
returns (uint256)
3633
{
37-
if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) {
38-
if (bytes4(userOp.callData[:4]) == IModularAccount.execute.selector) {
39-
address target = abi.decode(userOp.callData[4:36], (address));
40-
41-
// Simulate a merkle proof - require that the target address is also provided in the signature
42-
address proof = address(bytes20(userOp.signature));
43-
require(proof == target, "Proof doesn't match target");
44-
require(target == allowedTargets[msg.sender], "Target not allowed");
45-
return 0;
46-
}
34+
if (bytes4(userOp.callData[:4]) == IModularAccount.execute.selector) {
35+
address target = abi.decode(userOp.callData[4:36], (address));
36+
37+
// Simulate a merkle proof - require that the target address is also provided in the signature
38+
address proof = address(bytes20(userOp.signature));
39+
require(proof == target, "Proof doesn't match target");
40+
require(target == allowedTargets[entityId][msg.sender], "Target not allowed");
41+
return 0;
4742
}
48-
revert NotImplemented();
43+
44+
revert("Unsupported method");
4945
}
5046

5147
function preRuntimeValidationHook(
@@ -55,21 +51,19 @@ contract MockAccessControlHookModule is IValidationHookModule, BaseModule {
5551
bytes calldata data,
5652
bytes calldata authorization
5753
) external view override {
58-
if (entityId == uint32(EntityId.PRE_VALIDATION_HOOK)) {
59-
if (bytes4(data[:4]) == IModularAccount.execute.selector) {
60-
address target = abi.decode(data[4:36], (address));
61-
62-
// Simulate a merkle proof - require that the target address is also provided in the authorization
63-
// data
64-
address proof = address(bytes20(authorization));
65-
require(proof == target, "Proof doesn't match target");
66-
require(target == allowedTargets[msg.sender], "Target not allowed");
67-
68-
return;
69-
}
54+
if (bytes4(data[:4]) == IModularAccount.execute.selector) {
55+
address target = abi.decode(data[4:36], (address));
56+
57+
// Simulate a merkle proof - require that the target address is also provided in the authorization
58+
// data
59+
address proof = address(bytes20(authorization));
60+
require(proof == target, "Proof doesn't match target");
61+
require(target == allowedTargets[entityId][msg.sender], "Target not allowed");
62+
63+
return;
7064
}
7165

72-
revert NotImplemented();
66+
revert("Unsupported method");
7367
}
7468

7569
function preSignatureValidationHook(uint32, address, bytes32 hash, bytes calldata signature)

0 commit comments

Comments
 (0)