diff --git a/.env.example b/.env.example index 4d35e4c1..7412b0ad 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,8 @@ ENTRYPOINT= # Create2 expected addresses of the contracts. # When running for the first time, the error message will contain the expected addresses. ACCOUNT_IMPL= -FACTORY= SINGLE_SIGNER_VALIDATION= +FACTORY= # Optional, defaults to bytes32(0) ACCOUNT_IMPL_SALT= @@ -18,3 +18,7 @@ SINGLE_SIGNER_VALIDATION_SALT= # Optional, defaults to 0.1 ether and 1 day, respectively STAKE_AMOUNT= UNSTAKE_DELAY= + +# Allowlist Module +ALLOWLIST_MODULE= +ALLOWLIST_MODULE_SALT= diff --git a/.solhint-script.json b/.solhint-script.json new file mode 100644 index 00000000..307eb64c --- /dev/null +++ b/.solhint-script.json @@ -0,0 +1,21 @@ +{ + "extends": "solhint:recommended", + "rules": { + "func-name-mixedcase": "off", + "immutable-vars-naming": ["error"], + "no-unused-import": ["error"], + "compiler-version": ["error", ">=0.8.19"], + "custom-errors": "off", + "no-console": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "max-states-count": ["warn", 30], + "modifier-name-mixedcase": ["error"], + "private-vars-leading-underscore": ["error"], + "no-inline-assembly": "warn", + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "no-empty-blocks": "off", + "reason-string": "off" + } +} diff --git a/package.json b/package.json index 1c1540e7..0f8b51ea 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "solhint": "^3.6.2" }, "scripts": { - "lint": "pnpm lint:src && pnpm lint:test", + "lint": "pnpm lint:src && pnpm lint:test && pnpm lint:script", "lint:src": "solhint --max-warnings 0 -c .solhint-src.json './src/**/*.sol'", - "lint:test": "solhint --max-warnings 0 -c .solhint-test.json './test/**/*.sol'" + "lint:test": "solhint --max-warnings 0 -c .solhint-test.json './test/**/*.sol'", + "lint:script": "solhint --max-warnings 0 -c .solhint-script.json './script/**/*.sol'" } } diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 042a0ab8..5c4dd973 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -2,8 +2,7 @@ pragma solidity ^0.8.25; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; -import {Script} from "forge-std/Script.sol"; -import {console2} from "forge-std/Test.sol"; +import {Script, console} from "forge-std/Script.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; @@ -28,10 +27,10 @@ contract DeployScript is Script { uint256 public requiredUnstakeDelay = vm.envOr("UNSTAKE_DELAY", uint256(1 days)); function run() public { - console2.log("******** Deploying ERC-6900 Reference Implementation ********"); - console2.log("Chain: ", block.chainid); - console2.log("EP: ", address(entryPoint)); - console2.log("Factory owner: ", owner); + console.log("******** Deploying ERC-6900 Reference Implementation ********"); + console.log("Chain: ", block.chainid); + console.log("EP: ", address(entryPoint)); + console.log("Factory owner: ", owner); vm.startBroadcast(); _deployAccountImpl(accountImplSalt, accountImpl); @@ -42,7 +41,7 @@ contract DeployScript is Script { } function _deployAccountImpl(bytes32 salt, address expected) internal { - console2.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt))); + console.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt))); address addr = Create2.computeAddress( salt, @@ -50,61 +49,61 @@ contract DeployScript is Script { CREATE2_FACTORY ); if (addr != expected) { - console2.log("Expected address mismatch"); - console2.log("Expected: ", expected); - console2.log("Actual: ", addr); + console.log("Expected address mismatch"); + console.log("Expected: ", expected); + console.log("Actual: ", addr); revert(); } if (addr.code.length == 0) { - console2.log("No code found at expected address, deploying..."); + console.log("No code found at expected address, deploying..."); UpgradeableModularAccount deployed = new UpgradeableModularAccount{salt: salt}(entryPoint); if (address(deployed) != expected) { - console2.log("Deployed address mismatch"); - console2.log("Expected: ", expected); - console2.log("Deployed: ", address(deployed)); + console.log("Deployed address mismatch"); + console.log("Expected: ", expected); + console.log("Deployed: ", address(deployed)); revert(); } - console2.log("Deployed AccountImpl at: ", address(deployed)); + console.log("Deployed AccountImpl at: ", address(deployed)); } else { - console2.log("Code found at expected address, skipping deployment"); + console.log("Code found at expected address, skipping deployment"); } } function _deploySingleSignerValidation(bytes32 salt, address expected) internal { - console2.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt))); + console.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt))); address addr = Create2.computeAddress( salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), CREATE2_FACTORY ); if (addr != expected) { - console2.log("Expected address mismatch"); - console2.log("Expected: ", expected); - console2.log("Actual: ", addr); + console.log("Expected address mismatch"); + console.log("Expected: ", expected); + console.log("Actual: ", addr); revert(); } if (addr.code.length == 0) { - console2.log("No code found at expected address, deploying..."); + console.log("No code found at expected address, deploying..."); SingleSignerValidation deployed = new SingleSignerValidation{salt: salt}(); if (address(deployed) != expected) { - console2.log("Deployed address mismatch"); - console2.log("Expected: ", expected); - console2.log("Deployed: ", address(deployed)); + console.log("Deployed address mismatch"); + console.log("Expected: ", expected); + console.log("Deployed: ", address(deployed)); revert(); } - console2.log("Deployed SingleSignerValidation at: ", address(deployed)); + console.log("Deployed SingleSignerValidation at: ", address(deployed)); } else { - console2.log("Code found at expected address, skipping deployment"); + console.log("Code found at expected address, skipping deployment"); } } function _deployAccountFactory(bytes32 salt, address expected) internal { - console2.log(string.concat("Deploying AccountFactory with salt: ", vm.toString(salt))); + console.log(string.concat("Deploying AccountFactory with salt: ", vm.toString(salt))); address addr = Create2.computeAddress( salt, @@ -117,46 +116,46 @@ contract DeployScript is Script { CREATE2_FACTORY ); if (addr != expected) { - console2.log("Expected address mismatch"); - console2.log("Expected: ", expected); - console2.log("Actual: ", addr); + console.log("Expected address mismatch"); + console.log("Expected: ", expected); + console.log("Actual: ", addr); revert(); } if (addr.code.length == 0) { - console2.log("No code found at expected address, deploying..."); + console.log("No code found at expected address, deploying..."); AccountFactory deployed = new AccountFactory{salt: salt}( entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidation, owner ); if (address(deployed) != expected) { - console2.log("Deployed address mismatch"); - console2.log("Expected: ", expected); - console2.log("Deployed: ", address(deployed)); + console.log("Deployed address mismatch"); + console.log("Expected: ", expected); + console.log("Deployed: ", address(deployed)); revert(); } - console2.log("Deployed AccountFactory at: ", address(deployed)); + console.log("Deployed AccountFactory at: ", address(deployed)); } else { - console2.log("Code found at expected address, skipping deployment"); + console.log("Code found at expected address, skipping deployment"); } } function _addStakeForFactory(uint32 unstakeDelay, uint256 stakeAmount) internal { - console2.log("Adding stake to factory"); + console.log("Adding stake to factory"); uint256 currentStake = entryPoint.getDepositInfo(address(factory)).stake; - console2.log("Current stake: ", currentStake); + console.log("Current stake: ", currentStake); uint256 stakeToAdd = stakeAmount - currentStake; if (stakeToAdd > 0) { - console2.log("Adding stake: ", stakeToAdd); - entryPoint.addStake{value: stakeToAdd}(unstakeDelay); - console2.log("Staked factory: ", address(factory)); - console2.log("Total stake amount: ", entryPoint.getDepositInfo(address(factory)).stake); - console2.log("Unstake delay: ", entryPoint.getDepositInfo(address(factory)).unstakeDelaySec); + console.log("Adding stake: ", stakeToAdd); + AccountFactory(factory).addStake{value: stakeAmount}(unstakeDelay); + console.log("Staked factory: ", address(factory)); + console.log("Total stake amount: ", entryPoint.getDepositInfo(address(factory)).stake); + console.log("Unstake delay: ", entryPoint.getDepositInfo(address(factory)).unstakeDelaySec); } else { - console2.log("No stake to add"); + console.log("No stake to add"); } } } diff --git a/script/DeployAllowlistModule.s.sol b/script/DeployAllowlistModule.s.sol new file mode 100644 index 00000000..42cde52f --- /dev/null +++ b/script/DeployAllowlistModule.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Script, console} from "forge-std/Script.sol"; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {AllowlistModule} from "../src/modules/permissionhooks/AllowlistModule.sol"; + +contract DeployAllowlistModuleScript is Script { + address public allowlistModule = vm.envOr("ALLOWLIST_MODULE", address(0)); + + bytes32 public allowlistModuleSalt = bytes32(vm.envOr("ALLOWLIST_MODULE_SALT", uint256(0))); + + function run() public { + console.log("******** Deploying AllowlistModule ********"); + console.log("Chain: ", block.chainid); + + vm.startBroadcast(); + _deployAllowlistModule(allowlistModuleSalt, allowlistModule); + vm.stopBroadcast(); + } + + function _deployAllowlistModule(bytes32 salt, address expected) internal { + console.log(string.concat("Deploying AllowlistModule with salt: ", vm.toString(salt))); + + address addr = Create2.computeAddress(salt, keccak256(type(AllowlistModule).creationCode), CREATE2_FACTORY); + if (addr != expected) { + console.log("Expected address mismatch"); + console.log("Expected: ", expected); + console.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console.log("No code found at expected address, deploying..."); + AllowlistModule deployed = new AllowlistModule{salt: salt}(); + + if (address(deployed) != expected) { + console.log("Deployed address mismatch"); + console.log("Expected: ", expected); + console.log("Actual: ", address(deployed)); + revert(); + } + + console.log("Deployed AllowlistModule at: ", address(deployed)); + } else { + console.log("Code found at expected address, skipping deployment"); + } + } +} diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index 07a74d09..4119461f 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -13,7 +13,6 @@ import {ValidationConfigLib} from "../helpers/ValidationConfigLib.sol"; contract AccountFactory is Ownable { UpgradeableModularAccount public immutable ACCOUNT_IMPL; bytes32 private immutable _PROXY_BYTECODE_HASH; - uint32 public constant UNSTAKE_DELAY = 1 weeks; IEntryPoint public immutable ENTRY_POINT; address public immutable SINGLE_SIGNER_VALIDATION; @@ -61,8 +60,8 @@ contract AccountFactory is Ownable { return UpgradeableModularAccount(payable(addr)); } - function addStake() external payable onlyOwner { - ENTRY_POINT.addStake{value: msg.value}(UNSTAKE_DELAY); + function addStake(uint32 unstakeDelay) external payable onlyOwner { + ENTRY_POINT.addStake{value: msg.value}(unstakeDelay); } function unlockStake() external onlyOwner { diff --git a/test/script/Deploy.s.t.sol b/test/script/Deploy.s.t.sol index 105ce9b8..a0789e54 100644 --- a/test/script/Deploy.s.t.sol +++ b/test/script/Deploy.s.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.25; import {Test} from "forge-std/Test.sol"; import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {IStakeManager} from "@eth-infinitism/account-abstraction/interfaces/IStakeManager.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {DeployScript} from "../../script/Deploy.s.sol"; @@ -25,7 +26,10 @@ contract DeployTest is Test { function setUp() public { _entryPoint = new EntryPoint(); - _owner = makeAddr("OWNER"); + + // Set the owner to the foundry default sender, as this is what will be used as the sender within the + // `startBroadcast` segment of the script. + _owner = DEFAULT_SENDER; vm.setEnv("ENTRYPOINT", vm.toString(address(_entryPoint))); vm.setEnv("OWNER", vm.toString(_owner)); @@ -72,5 +76,18 @@ contract DeployTest is Test { assertTrue(_accountImpl.code.length > 0); assertTrue(_factory.code.length > 0); assertTrue(_singleSignerValidation.code.length > 0); + + assertEq( + _singleSignerValidation.code, + type(SingleSignerValidation).runtimeCode, + "SingleSignerValidation runtime code mismatch" + ); + + // Check factory stake + IStakeManager.DepositInfo memory depositInfo = _entryPoint.getDepositInfo(_factory); + + assertTrue(depositInfo.staked, "Factory not staked"); + assertEq(depositInfo.stake, 0.1 ether, "Unexpected factory stake amount"); + assertEq(depositInfo.unstakeDelaySec, 1 days, "Unexpected factory unstake delay"); } } diff --git a/test/script/DeployAllowlistModule.s.t.sol b/test/script/DeployAllowlistModule.s.t.sol new file mode 100644 index 00000000..27d4666a --- /dev/null +++ b/test/script/DeployAllowlistModule.s.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {DeployAllowlistModuleScript} from "../../script/DeployAllowlistModule.s.sol"; + +import {AllowlistModule} from "../../src/modules/permissionhooks/AllowlistModule.sol"; + +contract DeployAllowlistModuleTest is Test { + DeployAllowlistModuleScript internal _deployScript; + + address internal _allowlistModule; + + function setUp() public { + _allowlistModule = + Create2.computeAddress(bytes32(0), keccak256(type(AllowlistModule).creationCode), CREATE2_FACTORY); + + vm.setEnv("ALLOWLIST_MODULE", vm.toString(address(_allowlistModule))); + + _deployScript = new DeployAllowlistModuleScript(); + } + + function test_deployAllowlistModuleScript_run() public { + _deployScript.run(); + + assertTrue(_allowlistModule.code.length > 0, "AllowlistModule not deployed"); + assertEq(_allowlistModule.code, type(AllowlistModule).runtimeCode, "AllowlistModule runtime code mismatch"); + } +}