-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
2,972 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.26; | ||
|
||
import {IExecutionHookModule} from "@erc-6900/reference-implementation/interfaces/IExecutionHookModule.sol"; | ||
import { | ||
ExecutionManifest, | ||
IModule, | ||
ManifestExecutionFunction, | ||
ManifestExecutionHook | ||
} from "@erc-6900/reference-implementation/interfaces/IExecutionModule.sol"; | ||
|
||
import {MockModule} from "../mocks/modules/MockModule.sol"; | ||
import {AccountTestBase} from "../utils/AccountTestBase.sol"; | ||
|
||
contract AccountExecHooksTest is AccountTestBase { | ||
MockModule public mockModule1; | ||
|
||
bytes4 internal constant _EXEC_SELECTOR = bytes4(uint32(1)); | ||
uint32 internal constant _PRE_HOOK_ENTITY_ID_1 = 1; | ||
uint32 internal constant _POST_HOOK_ENTITY_ID_2 = 2; | ||
uint32 internal constant _BOTH_HOOKS_ENTITY_ID_3 = 3; | ||
|
||
ExecutionManifest internal _m1; | ||
|
||
event ExecutionInstalled(address indexed module, ExecutionManifest manifest); | ||
event ExecutionUninstalled(address indexed module, bool onUninstallSucceeded, ExecutionManifest manifest); | ||
// emitted by MockModule | ||
event ReceivedCall(bytes msgData, uint256 msgValue); | ||
|
||
function setUp() public { | ||
_allowTestDirectCalls(); | ||
|
||
_m1.executionFunctions.push( | ||
ManifestExecutionFunction({ | ||
executionSelector: _EXEC_SELECTOR, | ||
skipRuntimeValidation: true, | ||
allowGlobalValidation: false | ||
}) | ||
); | ||
} | ||
|
||
function test_preExecHook_install() public { | ||
_installExecution1WithHooks( | ||
ManifestExecutionHook({ | ||
executionSelector: _EXEC_SELECTOR, | ||
entityId: _PRE_HOOK_ENTITY_ID_1, | ||
isPreHook: true, | ||
isPostHook: false | ||
}) | ||
); | ||
} | ||
|
||
/// @dev Module 1 hook pair: [1, null] | ||
/// Expected execution: [1, null] | ||
function test_preExecHook_run() public { | ||
test_preExecHook_install(); | ||
|
||
vm.expectEmit(true, true, true, true); | ||
emit ReceivedCall( | ||
abi.encodeWithSelector( | ||
IExecutionHookModule.preExecutionHook.selector, | ||
_PRE_HOOK_ENTITY_ID_1, | ||
address(this), // caller | ||
uint256(0), // msg.value in call to account | ||
abi.encodeWithSelector(_EXEC_SELECTOR) | ||
), | ||
0 // msg value in call to module | ||
); | ||
|
||
(bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); | ||
assertTrue(success); | ||
} | ||
|
||
function test_preExecHook_uninstall() public { | ||
test_preExecHook_install(); | ||
|
||
_uninstallExecution(mockModule1); | ||
} | ||
|
||
function test_execHookPair_install() public { | ||
_installExecution1WithHooks( | ||
ManifestExecutionHook({ | ||
executionSelector: _EXEC_SELECTOR, | ||
entityId: _BOTH_HOOKS_ENTITY_ID_3, | ||
isPreHook: true, | ||
isPostHook: true | ||
}) | ||
); | ||
} | ||
|
||
/// @dev Module 1 hook pair: [1, 2] | ||
/// Expected execution: [1, 2] | ||
function test_execHookPair_run() public { | ||
test_execHookPair_install(); | ||
|
||
vm.expectEmit(true, true, true, true); | ||
// pre hook call | ||
emit ReceivedCall( | ||
abi.encodeWithSelector( | ||
IExecutionHookModule.preExecutionHook.selector, | ||
_BOTH_HOOKS_ENTITY_ID_3, | ||
address(this), // caller | ||
uint256(0), // msg.value in call to account | ||
abi.encodeWithSelector(_EXEC_SELECTOR) | ||
), | ||
0 // msg value in call to module | ||
); | ||
vm.expectEmit(true, true, true, true); | ||
// exec call | ||
emit ReceivedCall(abi.encodePacked(_EXEC_SELECTOR), 0); | ||
vm.expectEmit(true, true, true, true); | ||
// post hook call | ||
emit ReceivedCall( | ||
abi.encodeCall(IExecutionHookModule.postExecutionHook, (_BOTH_HOOKS_ENTITY_ID_3, "")), | ||
0 // msg value in call to module | ||
); | ||
|
||
(bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); | ||
assertTrue(success); | ||
} | ||
|
||
function test_execHookPair_uninstall() public { | ||
test_execHookPair_install(); | ||
|
||
_uninstallExecution(mockModule1); | ||
} | ||
|
||
function test_postOnlyExecHook_install() public { | ||
_installExecution1WithHooks( | ||
ManifestExecutionHook({ | ||
executionSelector: _EXEC_SELECTOR, | ||
entityId: _POST_HOOK_ENTITY_ID_2, | ||
isPreHook: false, | ||
isPostHook: true | ||
}) | ||
); | ||
} | ||
|
||
/// @dev Module 1 hook pair: [null, 2] | ||
/// Expected execution: [null, 2] | ||
function test_postOnlyExecHook_run() public { | ||
test_postOnlyExecHook_install(); | ||
|
||
vm.expectEmit(true, true, true, true); | ||
emit ReceivedCall( | ||
abi.encodeCall(IExecutionHookModule.postExecutionHook, (_POST_HOOK_ENTITY_ID_2, "")), | ||
0 // msg value in call to module | ||
); | ||
|
||
(bool success,) = address(account1).call(abi.encodeWithSelector(_EXEC_SELECTOR)); | ||
assertTrue(success); | ||
} | ||
|
||
function test_postOnlyExecHook_uninstall() public { | ||
test_postOnlyExecHook_install(); | ||
|
||
_uninstallExecution(mockModule1); | ||
} | ||
|
||
function _installExecution1WithHooks(ManifestExecutionHook memory execHooks) internal { | ||
_m1.executionHooks.push(execHooks); | ||
mockModule1 = new MockModule(_m1); | ||
|
||
vm.expectEmit(true, true, true, true); | ||
emit ReceivedCall(abi.encodeCall(IModule.onInstall, (bytes("a"))), 0); | ||
vm.expectEmit(true, true, true, true); | ||
emit ExecutionInstalled(address(mockModule1), _m1); | ||
|
||
account1.installExecution({ | ||
module: address(mockModule1), | ||
manifest: mockModule1.executionManifest(), | ||
moduleInstallData: bytes("a") | ||
}); | ||
} | ||
|
||
function _uninstallExecution(MockModule module) internal { | ||
vm.expectEmit(true, true, true, true); | ||
emit ReceivedCall(abi.encodeCall(IModule.onUninstall, (bytes("b"))), 0); | ||
vm.expectEmit(true, true, true, true); | ||
emit ExecutionUninstalled(address(module), true, module.executionManifest()); | ||
|
||
account1.uninstallExecution(address(module), module.executionManifest(), bytes("b")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.26; | ||
|
||
import {ModularAccount} from "../../src/account/ModularAccount.sol"; | ||
|
||
import {AccountTestBase} from "../utils/AccountTestBase.sol"; | ||
import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; | ||
|
||
contract AccountFactoryTest is AccountTestBase { | ||
function test_createAccount() public { | ||
ModularAccount account = factory.createAccount(address(this), 100, TEST_DEFAULT_VALIDATION_ENTITY_ID); | ||
|
||
assertEq(address(account.entryPoint()), address(entryPoint)); | ||
} | ||
|
||
function test_createAccountAndGetAddress() public { | ||
ModularAccount account = factory.createAccount(address(this), 100, TEST_DEFAULT_VALIDATION_ENTITY_ID); | ||
|
||
assertEq( | ||
address(account), address(factory.createAccount(address(this), 100, TEST_DEFAULT_VALIDATION_ENTITY_ID)) | ||
); | ||
} | ||
|
||
function test_multipleDeploy() public { | ||
ModularAccount account = factory.createAccount(address(this), 100, TEST_DEFAULT_VALIDATION_ENTITY_ID); | ||
|
||
uint256 startGas = gasleft(); | ||
|
||
ModularAccount account2 = factory.createAccount(address(this), 100, TEST_DEFAULT_VALIDATION_ENTITY_ID); | ||
|
||
// Assert that the 2nd deployment call cost less than 1 sstore | ||
// Implies that no deployment was done on the second calls | ||
assertLe(startGas - 22_000, gasleft()); | ||
|
||
// Assert the return addresses are the same | ||
assertEq(address(account), address(account2)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.26; | ||
|
||
import {Call} from "@erc-6900/reference-implementation/interfaces/IModularAccount.sol"; | ||
import {IModularAccount} from "@erc-6900/reference-implementation/interfaces/IModularAccount.sol"; | ||
|
||
import {DIRECT_CALL_VALIDATION_ENTITYID} from "../../src/helpers/Constants.sol"; | ||
import {ValidationConfigLib} from "../../src/helpers/ValidationConfigLib.sol"; | ||
|
||
import { | ||
RegularResultContract, | ||
ResultConsumerModule, | ||
ResultCreatorModule | ||
} from "../mocks/modules/ReturnDataModuleMocks.sol"; | ||
import {AccountTestBase} from "../utils/AccountTestBase.sol"; | ||
|
||
// Tests all the different ways that return data can be read from modules through an account | ||
contract AccountReturnDataTest is AccountTestBase { | ||
RegularResultContract public regularResultContract; | ||
ResultCreatorModule public resultCreatorModule; | ||
ResultConsumerModule public resultConsumerModule; | ||
|
||
function setUp() public { | ||
_transferOwnershipToTest(); | ||
|
||
regularResultContract = new RegularResultContract(); | ||
resultCreatorModule = new ResultCreatorModule(); | ||
resultConsumerModule = new ResultConsumerModule(resultCreatorModule, regularResultContract); | ||
|
||
// Add the result creator module to the account | ||
vm.startPrank(address(entryPoint)); | ||
account1.installExecution({ | ||
module: address(resultCreatorModule), | ||
manifest: resultCreatorModule.executionManifest(), | ||
moduleInstallData: "" | ||
}); | ||
// Add the result consumer module to the account | ||
account1.installExecution({ | ||
module: address(resultConsumerModule), | ||
manifest: resultConsumerModule.executionManifest(), | ||
moduleInstallData: "" | ||
}); | ||
// Allow the result consumer module to perform direct calls to the account | ||
bytes4[] memory selectors = new bytes4[](1); | ||
selectors[0] = IModularAccount.execute.selector; | ||
account1.installValidation( | ||
ValidationConfigLib.pack( | ||
address(resultConsumerModule), DIRECT_CALL_VALIDATION_ENTITYID, false, false, true | ||
), // todo: does this need UO validation permission? | ||
selectors, | ||
"", | ||
new bytes[](0) | ||
); | ||
vm.stopPrank(); | ||
} | ||
|
||
// Tests the ability to read the result of module execution functions via the account's fallback | ||
function test_returnData_fallback() public view { | ||
bytes32 result = ResultCreatorModule(address(account1)).foo(); | ||
|
||
assertEq(result, keccak256("bar")); | ||
} | ||
|
||
// Tests the ability to read the results of contracts called via IModularAccount.execute | ||
function test_returnData_singular_execute() public { | ||
bytes memory returnData = account1.executeWithAuthorization( | ||
abi.encodeCall( | ||
account1.execute, | ||
(address(regularResultContract), 0, abi.encodeCall(RegularResultContract.foo, ())) | ||
), | ||
_encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") | ||
); | ||
|
||
bytes32 result = abi.decode(abi.decode(returnData, (bytes)), (bytes32)); | ||
|
||
assertEq(result, keccak256("bar")); | ||
} | ||
|
||
// Tests the ability to read the results of multiple contract calls via IModularAccount.executeBatch | ||
function test_returnData_executeBatch() public { | ||
Call[] memory calls = new Call[](2); | ||
calls[0] = Call({ | ||
target: address(regularResultContract), | ||
value: 0, | ||
data: abi.encodeCall(RegularResultContract.foo, ()) | ||
}); | ||
calls[1] = Call({ | ||
target: address(regularResultContract), | ||
value: 0, | ||
data: abi.encodeCall(RegularResultContract.bar, ()) | ||
}); | ||
|
||
bytes memory retData = account1.executeWithAuthorization( | ||
abi.encodeCall(account1.executeBatch, (calls)), | ||
_encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") | ||
); | ||
|
||
bytes[] memory returnDatas = abi.decode(retData, (bytes[])); | ||
|
||
bytes32 result1 = abi.decode(returnDatas[0], (bytes32)); | ||
bytes32 result2 = abi.decode(returnDatas[1], (bytes32)); | ||
|
||
assertEq(result1, keccak256("bar")); | ||
assertEq(result2, keccak256("foo")); | ||
} | ||
|
||
// Tests the ability to read data via routing to fallback functions | ||
function test_returnData_execFromModule_fallback() public view { | ||
bool result = ResultConsumerModule(address(account1)).checkResultFallback(keccak256("bar")); | ||
|
||
assertTrue(result); | ||
} | ||
|
||
// Tests the ability to read data via executeWithAuthorization | ||
function test_returnData_authorized_exec() public { | ||
bool result = ResultConsumerModule(address(account1)).checkResultExecuteWithAuthorization( | ||
address(regularResultContract), keccak256("bar") | ||
); | ||
|
||
assertTrue(result); | ||
} | ||
} |
Oops, something went wrong.