diff --git a/script/DeployFactory.s.sol b/script/DeployFactory.s.sol new file mode 100644 index 00000000..ef53ffb2 --- /dev/null +++ b/script/DeployFactory.s.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {console} from "forge-std/console.sol"; + +import {ModularAccount} from "../src/account/ModularAccount.sol"; +import {SemiModularAccountBytecode} from "../src/account/SemiModularAccountBytecode.sol"; +import {Artifacts} from "./Artifacts.sol"; +import {ScriptBase} from "./ScriptBase.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; + +// Deploys the Account Factory. This requires the following env vars to be set: +// - ENTRY_POINT +// - MODULAR_ACCOUNT_IMPL +// - SEMI_MODULAR_ACCOUNT_BYTECODE_IMPL +// - SINGLE_SIGNER_VALIDATION_MODULE +// - WEBAUTHN_VALIDATION_MODULE +// - FACTORY_OWNER +contract DeployFactoryScript is ScriptBase, Artifacts { + // State vars for expected addresses and salts. + + address public expectedFactoryAddr; + uint256 public factorySalt; + + // State vars for factory dependencies + + IEntryPoint public entryPoint; + ModularAccount public modularAccountImpl; + SemiModularAccountBytecode public semiModularAccountBytecodeImpl; + address public singleSignerValidationModule; + address public webAuthnValidationModule; + address public factoryOwner; + + function setUp() public { + // Load the required addresses for the factory deployment from env vars. + entryPoint = _getEntryPoint(); + modularAccountImpl = _getModularAccountImpl(); + semiModularAccountBytecodeImpl = _getSemiModularAccountBytecodeImpl(); + singleSignerValidationModule = _getSingleSignerValidationModule(); + webAuthnValidationModule = _getWebAuthnValidationModule(); + factoryOwner = _getFactoryOwner(); + + // Load the expected address and salt from env vars. + expectedFactoryAddr = vm.envOr("ACCOUNT_FACTORY", address(0)); + factorySalt = vm.envOr("ACCOUNT_FACTORY_SALT", uint256(0)); + } + + function run() public onlyProfile("optimized-build") { + console.log("******** Deploying Factory *********"); + + vm.startBroadcast(); + + _safeDeploy( + "Account Factory", + expectedFactoryAddr, + factorySalt, + _getAccountFactoryInitcode( + entryPoint, + modularAccountImpl, + semiModularAccountBytecodeImpl, + singleSignerValidationModule, + webAuthnValidationModule, + factoryOwner + ), + _deployFactory + ); + + vm.stopBroadcast(); + + console.log("******** Factory Deployed *********"); + } + + // Wrapper function to be called within _safeDeploy using the context in this contract. + function _deployFactory(bytes32 salt) internal returns (address) { + _ensureNonzeroFactoryArgs(); + return _deployAccountFactory( + salt, + entryPoint, + modularAccountImpl, + semiModularAccountBytecodeImpl, + singleSignerValidationModule, + webAuthnValidationModule, + factoryOwner + ); + } + + function _ensureNonzeroFactoryArgs() internal view { + bool shouldRevert; + if (address(modularAccountImpl) == address(0)) { + console.log("Env Variable 'MODULAR_ACCOUNT_IMPL' not found or invalid during factory deployment"); + shouldRevert = true; + } else { + console.log("Using user-defined ModularAccount at: %x", address(modularAccountImpl)); + } + + if (address(semiModularAccountBytecodeImpl) == address(0)) { + console.log( + "Env Variable 'SEMI_MODULAR_ACCOUNT_BYTECODE_IMPL' not found or invalid during factory deployment" + ); + shouldRevert = true; + } else { + console.log( + "Using user-defined SemiModularAccountBytecode at: %x", address(semiModularAccountBytecodeImpl) + ); + } + + if (singleSignerValidationModule == address(0)) { + console.log( + "Env Variable 'SINGLE_SIGNER_VALIDATION_MODULE' not found or invalid during factory deployment" + ); + shouldRevert = true; + } else { + console.log("Using user-defined SingleSignerValidationModule at: %x", singleSignerValidationModule); + } + + if (webAuthnValidationModule == address(0)) { + console.log("Env Variable 'WEBAUTHN_VALIDATION_MODULE' not found or invalid during factory deployment"); + shouldRevert = true; + } else { + console.log("Using user-defined WebAuthnValidationModule at: %x", webAuthnValidationModule); + } + + if (factoryOwner == address(0)) { + console.log("Env Variable 'FACTORY_OWNER' not found or invalid during factory deployment"); + shouldRevert = true; + } else { + console.log("Using user-defined factory owner at: %x", factoryOwner); + } + + if (shouldRevert) { + revert("Missing or invalid env variables during factory deployment"); + } + } +} diff --git a/script/GetInitcodeHash.s.sol b/script/GetInitcodeHash.s.sol index f2fa5caa..56e0ddd6 100644 --- a/script/GetInitcodeHash.s.sol +++ b/script/GetInitcodeHash.s.sol @@ -47,16 +47,8 @@ contract GetInitcodeHashScript is ScriptBase, Artifacts { console.log("- WebAuthnValidationModule: %x", uint256(keccak256(_getWebAuthnValidationModuleInitcode()))); console.log("Artifact initcode hashes with dependencies on EntryPoint and ExecutionInstallDelegate:"); - IEntryPoint entryPoint = IEntryPoint(payable(vm.envOr("ENTRYPOINT", address(0)))); - if (address(entryPoint) == address(0)) { - console.log( - "Env Variable 'ENTRYPOINT' not found or invalid, defaulting to v0.7 EntryPoint at " - "0x0000000071727De22E5E9d8BAf0edAc6f37da032" - ); - entryPoint = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032); - } else { - console.log("Using user-defined EntryPoint at: %x", address(entryPoint)); - } + IEntryPoint entryPoint = _getEntryPoint(); + ExecutionInstallDelegate executionInstallDelegate = ExecutionInstallDelegate(vm.envOr("EXECUTION_INSTALL_DELEGATE", address(0))); @@ -93,12 +85,11 @@ contract GetInitcodeHashScript is ScriptBase, Artifacts { "WebAuthnValidationModule, and owner address:" ); - ModularAccount modularAccountImpl = ModularAccount(payable(vm.envOr("MODULAR_ACCOUNT_IMPL", address(0)))); - SemiModularAccountBytecode semiModularImpl = - SemiModularAccountBytecode(payable(vm.envOr("SEMI_MODULAR_ACCOUNT_BYTECODE_IMPL", address(0)))); - address singleSignerValidationModule = vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE", address(0)); - address webAuthnValidationModule = vm.envOr("WEBAUTHN_VALIDATION_MODULE", address(0)); - address factoryOwner = vm.envOr("FACTORY_OWNER", address(0)); + ModularAccount modularAccountImpl = _getModularAccountImpl(); + SemiModularAccountBytecode semiModularImpl = _getSemiModularAccountBytecodeImpl(); + address singleSignerValidationModule = _getSingleSignerValidationModule(); + address webAuthnValidationModule = _getWebAuthnValidationModule(); + address factoryOwner = _getFactoryOwner(); if (address(modularAccountImpl) == address(0)) { console.log( diff --git a/script/PredictModules.s.sol b/script/PredictAddresses.s.sol similarity index 61% rename from script/PredictModules.s.sol rename to script/PredictAddresses.s.sol index 196f11ea..25dc7fe7 100644 --- a/script/PredictModules.s.sol +++ b/script/PredictAddresses.s.sol @@ -3,8 +3,11 @@ pragma solidity ^0.8.26; import {console} from "forge-std/console.sol"; +import {ModularAccount} from "../src/account/ModularAccount.sol"; +import {SemiModularAccountBytecode} from "../src/account/SemiModularAccountBytecode.sol"; import {Artifacts} from "./Artifacts.sol"; import {ScriptBase} from "./ScriptBase.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; // Predicts addresses for all standalone modules with salts taken from the environment. @@ -14,7 +17,7 @@ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; // - SingleSignerValidationModule // - TimeRangeModule // - WebAuthnValidationModule -contract PredictModulesScript is ScriptBase, Artifacts { +contract PredictAddressScript is ScriptBase, Artifacts { // State vars for salts. uint256 public allowlistModuleSalt; @@ -23,6 +26,14 @@ contract PredictModulesScript is ScriptBase, Artifacts { uint256 public singleSignerValidationModuleSalt; uint256 public timeRangeModuleSalt; uint256 public webAuthnValidationModuleSalt; + uint256 public factorySalt; + + IEntryPoint public entryPoint; + ModularAccount public modularAccountImpl; + SemiModularAccountBytecode public semiModularAccountBytecodeImpl; + address public singleSignerValidationModule; + address public webAuthnValidationModule; + address public factoryOwner; function setUp() public { // Load the salts from env vars. @@ -33,10 +44,19 @@ contract PredictModulesScript is ScriptBase, Artifacts { singleSignerValidationModuleSalt = vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE_SALT", uint256(0)); timeRangeModuleSalt = vm.envOr("TIME_RANGE_MODULE_SALT", uint256(0)); webAuthnValidationModuleSalt = vm.envOr("WEBAUTHN_VALIDATION_MODULE_SALT", uint256(0)); + factorySalt = vm.envOr("ACCOUNT_FACTORY_SALT", uint256(0)); + + // Load the env vars for the factory. + entryPoint = _getEntryPoint(); + modularAccountImpl = _getModularAccountImpl(); + semiModularAccountBytecodeImpl = _getSemiModularAccountBytecodeImpl(); + singleSignerValidationModule = _getSingleSignerValidationModule(); + webAuthnValidationModule = _getWebAuthnValidationModule(); + factoryOwner = _getFactoryOwner(); } function run() public view onlyProfile("optimized-build") { - console.log("******** Logging Expected Module Addresses With Env Salts *********"); + console.log("******** Logging Expected Addresses With Env Salts *********"); console.log( "ALLOWLIST_MODULE=", @@ -85,5 +105,25 @@ contract PredictModulesScript is ScriptBase, Artifacts { CREATE2_FACTORY ) ); + + console.log(""); + console.log("******** Logging Expected Factory Address With Env Salt And Env Addresses *********"); + console.log( + "ACCOUNT_FACTORY=", + Create2.computeAddress( + bytes32(factorySalt), + keccak256( + _getAccountFactoryInitcode( + entryPoint, + modularAccountImpl, + semiModularAccountBytecodeImpl, + singleSignerValidationModule, + webAuthnValidationModule, + factoryOwner + ) + ), + CREATE2_FACTORY + ) + ); } } diff --git a/script/ScriptBase.sol b/script/ScriptBase.sol index 570d0b67..a74cfd33 100644 --- a/script/ScriptBase.sol +++ b/script/ScriptBase.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.26; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; +import {ModularAccount} from "../src/account/ModularAccount.sol"; +import {SemiModularAccountBytecode} from "../src/account/SemiModularAccountBytecode.sol"; +import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; abstract contract ScriptBase is Script { @@ -17,6 +20,41 @@ abstract contract ScriptBase is Script { _; } + function _getEntryPoint() internal view returns (IEntryPoint) { + IEntryPoint entryPoint = IEntryPoint(payable(vm.envOr("ENTRYPOINT", address(0)))); + if (address(entryPoint) == address(0)) { + console.log( + "Env Variable 'ENTRYPOINT' not found or invalid, defaulting to v0.7 EntryPoint at " + "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + ); + entryPoint = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032); + } else { + console.log("Using user-defined EntryPoint at: %x", address(entryPoint)); + } + + return entryPoint; + } + + function _getModularAccountImpl() internal view returns (ModularAccount) { + return ModularAccount(payable(vm.envOr("MODULAR_ACCOUNT_IMPL", address(0)))); + } + + function _getSemiModularAccountBytecodeImpl() internal view returns (SemiModularAccountBytecode) { + return SemiModularAccountBytecode(payable(vm.envOr("SEMI_MODULAR_ACCOUNT_BYTECODE_IMPL", address(0)))); + } + + function _getSingleSignerValidationModule() internal view returns (address) { + return vm.envOr("SINGLE_SIGNER_VALIDATION_MODULE", address(0)); + } + + function _getWebAuthnValidationModule() internal view returns (address) { + return vm.envOr("WEBAUTHN_VALIDATION_MODULE", address(0)); + } + + function _getFactoryOwner() internal view returns (address) { + return vm.envOr("ACCOUNT_FACTORY_OWNER", address(0)); + } + function _safeDeploy( string memory contractName, address expectedAddress, diff --git a/test/script/DeployFactory.s.t.sol b/test/script/DeployFactory.s.t.sol new file mode 100644 index 00000000..4a3d8cd3 --- /dev/null +++ b/test/script/DeployFactory.s.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {Test} from "forge-std/Test.sol"; + +import {DeployFactoryScript} from "../../script/DeployFactory.s.sol"; +import {AccountFactory} from "../../src/factory/AccountFactory.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +contract DeployFactoryTest is Test { + DeployFactoryScript internal _deployFactoryScript; + + address public entryPoint; + address public modularAccountImpl; + address public semiModularAccountBytecodeImpl; + address public singleSignerValidationModule; + address public webAuthnValidationModule; + address public factoryOwner; + + AccountFactory public factory; + + function setUp() public { + _deployFactoryScript = new DeployFactoryScript(); + + bytes32 zeroSalt = bytes32(0); + + entryPoint = makeAddr("Entrypoint"); + modularAccountImpl = makeAddr("Modular Account Impl"); + semiModularAccountBytecodeImpl = makeAddr("Semi Modular Account Bytecode Impl"); + singleSignerValidationModule = makeAddr("Single Signer Validation Module"); + webAuthnValidationModule = makeAddr("Webauthn Validation Module"); + factoryOwner = makeAddr("Factory Owner"); + + vm.setEnv("ENTRYPOINT", vm.toString(entryPoint)); + vm.setEnv("MODULAR_ACCOUNT_IMPL", vm.toString(modularAccountImpl)); + vm.setEnv("SEMI_MODULAR_ACCOUNT_BYTECODE_IMPL", vm.toString(semiModularAccountBytecodeImpl)); + vm.setEnv("SINGLE_SIGNER_VALIDATION_MODULE", vm.toString(singleSignerValidationModule)); + vm.setEnv("WEBAUTHN_VALIDATION_MODULE", vm.toString(webAuthnValidationModule)); + vm.setEnv("ACCOUNT_FACTORY_OWNER", vm.toString(factoryOwner)); + + factory = AccountFactory( + Create2.computeAddress( + zeroSalt, + keccak256( + bytes.concat( + type(AccountFactory).creationCode, + abi.encode( + entryPoint, + modularAccountImpl, + semiModularAccountBytecodeImpl, + singleSignerValidationModule, + webAuthnValidationModule, + factoryOwner + ) + ) + ), + CREATE2_FACTORY + ) + ); + + vm.setEnv("ACCOUNT_FACTORY", vm.toString(address(factory))); + + string memory zeroSaltString = vm.toString(zeroSalt); + + vm.setEnv("ACCOUNT_FACTORY_SALT", zeroSaltString); + + // Spoof as though the profile is set to "optimized-build". + vm.setEnv("FOUNDRY_PROFILE", "optimized-build"); + } + + function test_deployFactoryScript() public { + assertEq(address(factory).code.length, 0); + + _deployFactoryScript.setUp(); + + _deployFactoryScript.run(); + + assertGt(address(factory).code.length, 0); + + // Test an arbitrary function, ensuring the factory doesn't revert. + factory.createSemiModularAccount(address(this), 1); + } +}