From d4d1a63d84b855a0773f567b9e0c1d821fdaf57b Mon Sep 17 00:00:00 2001 From: zer0dot Date: Wed, 21 Aug 2024 18:58:59 -0400 Subject: [PATCH 1/3] test: initial draft of sma-specific tests, move helper to test base --- test/account/SemiModularAccount.t.sol | 106 ++++++++++++++++++++++++++ test/utils/AccountTestBase.sol | 4 + 2 files changed, 110 insertions(+) create mode 100644 test/account/SemiModularAccount.t.sol diff --git a/test/account/SemiModularAccount.t.sol b/test/account/SemiModularAccount.t.sol new file mode 100644 index 00000000..4c867044 --- /dev/null +++ b/test/account/SemiModularAccount.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {AccountTestBase} from "../utils/AccountTestBase.sol"; +import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; +import {SemiModularAccount} from "src/account/SemiModularAccount.sol"; +import {ValidationConfig} from "src/helpers/ValidationConfigLib.sol"; + +import {console} from "forge-std/Test.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; + +contract SemiModularAccountTest is AccountTestBase { + SemiModularAccount internal _sma; + + address internal _other; + + function setUp() public { + // This is separate from the equivalence testing framework (with the env boolean variable "SMA_TEST") with + // the goal of testing specific SMA functionality, rather than equivalence. This is also why we deploy a + // new account. + SemiModularAccount impl = new SemiModularAccount(entryPoint); + + _other = address(0x4546b); + + bytes32 salt = bytes32(0); + bytes memory immutables = abi.encodePacked(address(owner1)); + (bool alreadyDeployed, address instance) = + LibClone.createDeterministicERC1967(address(impl), immutables, salt); + + assertFalse(alreadyDeployed); + + _sma = SemiModularAccount(payable(instance)); + } + + /* -------------------------------------------------------------------------- */ + /* Negatives */ + /* -------------------------------------------------------------------------- */ + + function test_Fail_InitializeDisabled() external { + ValidationConfig config; + bytes4[] memory selectors; + bytes memory installData; + bytes[] memory hooks; + + vm.expectRevert(SemiModularAccount.InitializerDisabled.selector); + _sma.initializeWithValidation(config, selectors, installData, hooks); + } + + function test_Fail_AccessControl_Functions() external { + vm.expectRevert(_buildDirectCallDisallowedError(SemiModularAccount.setFallbackSignerDisabled.selector)); + _sma.setFallbackSignerDisabled(true); + + vm.expectRevert(_buildDirectCallDisallowedError(SemiModularAccount.updateFallbackSigner.selector)); + _sma.updateFallbackSigner(address(0)); + } + + function test_Fail_ExecuteWithAuthorization_DisabledFallbackSigner() external { + vm.prank(address(entryPoint)); + _sma.setFallbackSignerDisabled(true); + + vm.expectRevert(SemiModularAccount.FallbackSignerDisabled.selector); + vm.prank(owner1); + _executeWithFallbackSigner(); + } + + function test_Fail_ExecuteWithAuthorization_BytecodeOverriden() external { + vm.prank(address(entryPoint)); + _sma.updateFallbackSigner(_other); + + vm.expectRevert(SemiModularAccount.FallbackSignerMismatch.selector); + vm.prank(owner1); + _executeWithFallbackSigner(); + } + + /* -------------------------------------------------------------------------- */ + /* Positives */ + /* -------------------------------------------------------------------------- */ + + function test_Pass_GetFallbackSigner_Bytecode() external { + assertEq(_sma.getFallbackSigner(), owner1); + } + + function test_Pass_GetFallbackSigner_Storage() external { + vm.prank(address(entryPoint)); + _sma.updateFallbackSigner(_other); + + assertEq(_sma.getFallbackSigner(), _other); + } + + function test_Pass_ExecuteWithAuthorization_FallbackSigner() external { + vm.prank(owner1); + _executeWithFallbackSigner(); + } + + /* -------------------------------------------------------------------------- */ + /* Internals */ + /* -------------------------------------------------------------------------- */ + + function _executeWithFallbackSigner() internal { + // _signerValidation is already the ModuleEntity for fallback validation + _sma.executeWithAuthorization( + abi.encodeCall(account1.execute, (address(owner1), 0, "")), + _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") + ); + } +} diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index f4e37462..a65311d4 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -277,4 +277,8 @@ abstract contract AccountTestBase is OptimizedTest { { return abi.encodePacked(uint32(validationData.length + 1), index, validationData); } + + function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { + return abi.encodeWithSelector(UpgradeableModularAccount.ValidationFunctionMissing.selector, selector); + } } From df545d9e3af46d5d5f396feed2e2a0caf20f3f8d Mon Sep 17 00:00:00 2001 From: zer0dot Date: Fri, 23 Aug 2024 12:31:05 -0400 Subject: [PATCH 2/3] chore: formatting/linting --- test/account/SemiModularAccount.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/account/SemiModularAccount.t.sol b/test/account/SemiModularAccount.t.sol index 4c867044..9b582312 100644 --- a/test/account/SemiModularAccount.t.sol +++ b/test/account/SemiModularAccount.t.sol @@ -2,11 +2,9 @@ pragma solidity ^0.8.19; import {AccountTestBase} from "../utils/AccountTestBase.sol"; -import {TEST_DEFAULT_VALIDATION_ENTITY_ID} from "../utils/TestConstants.sol"; import {SemiModularAccount} from "src/account/SemiModularAccount.sol"; import {ValidationConfig} from "src/helpers/ValidationConfigLib.sol"; -import {console} from "forge-std/Test.sol"; import {LibClone} from "solady/utils/LibClone.sol"; contract SemiModularAccountTest is AccountTestBase { From f0a4e743d8bff42dc95d2aa3e37bbb720b7552cf Mon Sep 17 00:00:00 2001 From: zer0dot Date: Wed, 4 Sep 2024 17:29:19 -0400 Subject: [PATCH 3/3] test: bring tests up to date, fix unintended slot warming --- test/account/DirectCallsFromModule.t.sol | 4 ---- test/account/SemiModularAccount.t.sol | 7 +++++-- test/utils/AccountTestBase.sol | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/test/account/DirectCallsFromModule.t.sol b/test/account/DirectCallsFromModule.t.sol index 668ef0c2..c8100307 100644 --- a/test/account/DirectCallsFromModule.t.sol +++ b/test/account/DirectCallsFromModule.t.sol @@ -180,8 +180,4 @@ contract DirectCallsFromModuleTest is AccountTestBase { emit ValidationUninstalled(module, entityId, true); account1.uninstallValidation(_moduleEntity, "", new bytes[](1)); } - - function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { - return abi.encodeWithSelector(ReferenceModularAccount.ValidationFunctionMissing.selector, selector); - } } diff --git a/test/account/SemiModularAccount.t.sol b/test/account/SemiModularAccount.t.sol index 9b582312..894caefe 100644 --- a/test/account/SemiModularAccount.t.sol +++ b/test/account/SemiModularAccount.t.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.19; import {AccountTestBase} from "../utils/AccountTestBase.sol"; + +import {ReferenceModularAccount} from "src/account/ReferenceModularAccount.sol"; import {SemiModularAccount} from "src/account/SemiModularAccount.sol"; import {ValidationConfig} from "src/helpers/ValidationConfigLib.sol"; @@ -11,14 +13,15 @@ contract SemiModularAccountTest is AccountTestBase { SemiModularAccount internal _sma; address internal _other; + address internal _other2; // Used to ensure slots are not warmed function setUp() public { // This is separate from the equivalence testing framework (with the env boolean variable "SMA_TEST") with // the goal of testing specific SMA functionality, rather than equivalence. This is also why we deploy a // new account. SemiModularAccount impl = new SemiModularAccount(entryPoint); - _other = address(0x4546b); + _other2 = address(0x4546ab); bytes32 salt = bytes32(0); bytes memory immutables = abi.encodePacked(address(owner1)); @@ -97,7 +100,7 @@ contract SemiModularAccountTest is AccountTestBase { function _executeWithFallbackSigner() internal { // _signerValidation is already the ModuleEntity for fallback validation _sma.executeWithAuthorization( - abi.encodeCall(account1.execute, (address(owner1), 0, "")), + abi.encodeCall(account1.execute, (_other2, 0, "")), _encodeSignature(_signerValidation, GLOBAL_VALIDATION, "") ); } diff --git a/test/utils/AccountTestBase.sol b/test/utils/AccountTestBase.sol index a65311d4..7528af2a 100644 --- a/test/utils/AccountTestBase.sol +++ b/test/utils/AccountTestBase.sol @@ -279,6 +279,6 @@ abstract contract AccountTestBase is OptimizedTest { } function _buildDirectCallDisallowedError(bytes4 selector) internal pure returns (bytes memory) { - return abi.encodeWithSelector(UpgradeableModularAccount.ValidationFunctionMissing.selector, selector); + return abi.encodeWithSelector(ReferenceModularAccount.ValidationFunctionMissing.selector, selector); } }