@@ -3,44 +3,89 @@ pragma solidity 0.8.18;
33
44import 'contracts/hooks/EIP4337Hook.sol ' ;
55import 'contracts/hooks/interfaces/IEIP4337Hook.sol ' ;
6+ import 'contracts/modules/commons/ModuleAuth.sol ' ;
7+ import 'contracts/modules/commons/ModuleCalls.sol ' ;
68import 'contracts/modules/commons/ModuleHooks.sol ' ;
79import 'contracts/modules/MainModule.sol ' ;
810import 'contracts/modules/MainModuleUpgradable.sol ' ;
911import 'contracts/Factory.sol ' ;
1012
1113import 'foundry_test/base/AdvTest.sol ' ;
1214
15+ contract MockModules is ModuleAuth , ModuleCalls , ModuleHooks {
16+ function validateNonce (uint256 _rawNonce ) external {
17+ _validateNonce (_rawNonce);
18+ }
19+
20+ function writeNonce (uint256 _space , uint256 _nonce ) external {
21+ _writeNonce (_space, _nonce);
22+ }
23+
24+ // Module Auth imp
25+ mapping (bytes32 => mapping (bytes => bytes32 )) public sigToSubdigest;
26+ mapping (bytes32 => mapping (bytes => bool )) public sigToIsValid;
27+
28+ function _signatureValidation (
29+ bytes32 _digest ,
30+ bytes calldata _signature
31+ ) internal view override (IModuleAuth, ModuleAuth) returns (bool isValid , bytes32 subdigest ) {
32+ subdigest = sigToSubdigest[_digest][_signature];
33+ isValid = sigToIsValid[_digest][_signature];
34+ }
35+
36+ function mockSignature (bytes32 _digest , bytes calldata _signature , bytes32 _subdigest , bool _isValid ) external {
37+ sigToSubdigest[_digest][_signature] = _subdigest;
38+ sigToIsValid[_digest][_signature] = _isValid;
39+ }
40+
41+ // solhint-disable no-empty-blocks
42+ function _isValidImage (bytes32 ) internal view override returns (bool ) {}
43+
44+ function _updateImageHash (bytes32 ) internal override {}
45+
46+ // solhint-enable no-empty-blocks
47+
48+ function supportsInterface (
49+ bytes4 _interfaceID
50+ ) public pure virtual override (ModuleAuth, ModuleCalls, ModuleHooks) returns (bool ) {
51+ return
52+ ModuleAuth.supportsInterface (_interfaceID) ||
53+ ModuleCalls.supportsInterface (_interfaceID) ||
54+ ModuleHooks.supportsInterface (_interfaceID);
55+ }
56+ }
57+
1358contract EIP4337HookTest is AdvTest , IEIP4337HookErrors {
14- MainModule private template ;
59+ MockModules private walletMod ;
1560 EIP4337Hook private wallet;
1661
1762 address private constant ENTRYPOINT = address (uint160 (uint256 (keccak256 ('entrypoint ' ))));
1863
1964 function setUp () external {
2065 Factory factory = new Factory ();
21- address upgradeable = address (new MainModuleUpgradable ());
22- template = new MainModule (address (factory), upgradeable);
23- ModuleHooks walletMod = ModuleHooks (payable (factory.deploy (address (template), bytes32 (0 )))); // Add hook below
24- vm.label (address (walletMod), "wallet " );
66+ ModuleHooks template = new MockModules ();
67+ walletMod = MockModules (payable (factory.deploy (address (template), bytes32 (0 ))));
2568 EIP4337Hook hook = new EIP4337Hook (ENTRYPOINT);
2669
27- // Fund wallet
28- vm.deal (address (walletMod), 10 ether);
29-
3070 // Add hooks
3171 vm.startPrank (address (walletMod));
3272 walletMod.addHook (IAccount.validateUserOp.selector , address (hook));
3373 walletMod.addHook (IEIP4337Hook.eip4337SelfExecute.selector , address (hook));
3474 vm.stopPrank ();
3575
3676 wallet = EIP4337Hook (address (walletMod));
77+ vm.label (address (wallet), 'wallet ' );
3778 }
3879
3980 struct ToVal {
4081 address target;
4182 uint256 value;
4283 }
4384
85+ //
86+ // Execute
87+ //
88+
4489 function test_4337execute_invalidCaller (ToVal memory sendTx , address sender ) external {
4590 vm.assume (sender != ENTRYPOINT);
4691 IModuleCalls.Transaction[] memory txs = new IModuleCalls.Transaction [](1 );
@@ -59,14 +104,15 @@ contract EIP4337HookTest is AdvTest, IEIP4337HookErrors {
59104 }
60105
61106 function test_4337execute_sendEth (ToVal memory sendTx ) external {
62- vm.assume (sendTx.target.code.length == 0 ); // Non contract
63- uint256 walletBal = address (wallet).balance;
64- vm.assume (sendTx.value <= walletBal);
107+ _assumeSafeAddress (sendTx.target);
108+
109+ // Give wallet exact funds
110+ vm.deal (address (wallet), sendTx.value);
65111
66112 IModuleCalls.Transaction[] memory txs = new IModuleCalls.Transaction [](1 );
67113 txs[0 ] = IModuleCalls.Transaction ({
68114 delegateCall: false ,
69- revertOnError: false ,
115+ revertOnError: true ,
70116 gasLimit: 0 ,
71117 target: sendTx.target,
72118 value: sendTx.value,
@@ -76,9 +122,105 @@ contract EIP4337HookTest is AdvTest, IEIP4337HookErrors {
76122 vm.prank (ENTRYPOINT);
77123 wallet.eip4337SelfExecute (txs);
78124
79- assertEq (address (wallet).balance, walletBal - sendTx.value );
125+ assertEq (address (wallet).balance, 0 );
80126 assertEq (sendTx.target.balance, sendTx.value);
81127 }
82128
83- function test_validateUserOp (ToVal[] memory sendTx ) external {}
129+ function test_4337execute_insufficientEth (ToVal memory sendTx ) external {
130+ _assumeSafeAddress (sendTx.target);
131+ vm.assume (sendTx.value > 0 );
132+
133+ IModuleCalls.Transaction[] memory txs = new IModuleCalls.Transaction [](1 );
134+ txs[0 ] = IModuleCalls.Transaction ({
135+ delegateCall: false ,
136+ revertOnError: true ,
137+ gasLimit: 0 ,
138+ target: sendTx.target,
139+ value: sendTx.value,
140+ data: ''
141+ });
142+
143+ vm.expectRevert ();
144+ vm.prank (ENTRYPOINT);
145+ wallet.eip4337SelfExecute (txs);
146+ }
147+
148+ //
149+ // Validate
150+ //
151+
152+ function test_4337validateUserOp_invalidCaller (
153+ IAccount.UserOperation calldata userOp ,
154+ bytes32 userOpHash ,
155+ uint256 missingAccountFunds
156+ ) external {
157+ vm.expectRevert (InvalidCaller.selector );
158+ wallet.validateUserOp (userOp, userOpHash, missingAccountFunds);
159+ }
160+
161+ function test_4337validateUserOp (
162+ IAccount.UserOperation calldata userOp ,
163+ bytes32 userOpHash ,
164+ uint256 missingAccountFunds
165+ ) external {
166+ vm.assume (userOp.nonce == 0 );
167+
168+ // Give wallet enough funds
169+ vm.deal (address (wallet), missingAccountFunds);
170+
171+ // Accept the hash
172+ bytes32 encodedHash = keccak256 (abi.encodePacked ('\x19Ethereum Signed Message:\n32 ' , userOpHash));
173+ walletMod.mockSignature (encodedHash, userOp.signature, bytes32 (0 ), true );
174+
175+ // Validate
176+ vm.prank (ENTRYPOINT);
177+ uint256 validationData = wallet.validateUserOp (userOp, userOpHash, missingAccountFunds);
178+
179+ assertEq (validationData, 0 );
180+ }
181+
182+ function test_4337validateUserOp_failedSignature (
183+ IAccount.UserOperation calldata userOp ,
184+ bytes32 userOpHash ,
185+ uint256 missingAccountFunds
186+ ) external {
187+ vm.assume (userOp.nonce == 0 );
188+
189+ // Give wallet enough funds
190+ vm.deal (address (wallet), missingAccountFunds);
191+
192+ // Validate
193+ vm.prank (ENTRYPOINT);
194+ uint256 validationData = wallet.validateUserOp (userOp, userOpHash, missingAccountFunds);
195+
196+ assertEq (validationData, 1 );
197+ }
198+
199+ function test_4337validateUserOp_invalidFunds (
200+ IAccount.UserOperation calldata userOp ,
201+ bytes32 userOpHash ,
202+ uint256 missingAccountFunds
203+ ) external {
204+ vm.assume (userOp.nonce == 0 );
205+
206+ // Accept the hash
207+ bytes32 encodedHash = keccak256 (abi.encodePacked ('\x19Ethereum Signed Message:\n32 ' , userOpHash));
208+ walletMod.mockSignature (encodedHash, userOp.signature, bytes32 (0 ), true );
209+
210+ // Validate
211+ vm.prank (ENTRYPOINT);
212+ uint256 validationData = wallet.validateUserOp (userOp, userOpHash, missingAccountFunds);
213+
214+ // Passes. Account doesn't validate
215+ assertEq (validationData, 0 );
216+ }
217+
218+ //
219+ // Helpers
220+ //
221+
222+ function _assumeSafeAddress (address addr ) private view {
223+ vm.assume (uint160 (addr) > 20 ); // Non precompiled
224+ vm.assume (addr.code.length == 0 ); // Non contract
225+ }
84226}
0 commit comments