Skip to content

Commit

Permalink
feat: port account tests
Browse files Browse the repository at this point in the history
  • Loading branch information
adamegyed committed Sep 6, 2024
1 parent c9bb981 commit b002932
Show file tree
Hide file tree
Showing 24 changed files with 2,972 additions and 18 deletions.
4 changes: 3 additions & 1 deletion src/account/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ contract AccountFactory is Ownable {
}

function _getAddressSemiModular(bytes memory immutables, bytes32 salt) internal view returns (address) {
return LibClone.predictDeterministicAddressERC1967(address(ACCOUNT_IMPL), immutables, salt, address(this));
return LibClone.predictDeterministicAddressERC1967(
address(SEMI_MODULAR_ACCOUNT_IMPL), immutables, salt, address(this)
);
}

function _getImmutableArgs(address owner) private pure returns (bytes memory) {
Expand Down
2 changes: 1 addition & 1 deletion src/account/ModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ contract ModularAccount is

/// @inheritdoc IModularAccount
function accountId() external pure virtual returns (string memory) {
return "erc6900.reference-modular-account.0.8.0";
return "alchemy.modular-account.0.0.1";
}

/// @inheritdoc UUPSUpgradeable
Expand Down
2 changes: 1 addition & 1 deletion src/account/SemiModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ contract SemiModularAccount is ModularAccount {

/// @inheritdoc IModularAccount
function accountId() external pure override returns (string memory) {
return "erc6900.reference-semi-modular-account.0.8.0";
return "alchemy.semi-modular-account.0.0.1";
}

function replaySafeHash(bytes32 hash) public view virtual returns (bytes32) {
Expand Down
184 changes: 184 additions & 0 deletions test/account/AccountExecHooks.t.sol
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"));
}
}
38 changes: 38 additions & 0 deletions test/account/AccountFactory.t.sol
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));
}
}
122 changes: 122 additions & 0 deletions test/account/AccountReturnData.t.sol
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);
}
}
Loading

0 comments on commit b002932

Please sign in to comment.