From f94a910dc11a0f022248a4fd02850eff17f523dd Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 24 Jul 2024 13:00:38 -0400 Subject: [PATCH 1/4] deploy script and config update --- foundry.toml | 4 + script/Deploy.s.sol | 156 ++++++++++++++++++++++++++++++ src/account/AccountFactory.sol | 9 +- test/account/AccountFactory.t.sol | 2 +- test/script/Deploy.s.t.sol | 79 +++++++++++++++ 5 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 script/Deploy.s.sol create mode 100644 test/script/Deploy.s.t.sol diff --git a/foundry.toml b/foundry.toml index 0252d4e5..09a60df9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,6 +7,9 @@ libs = ['lib'] out = 'out' optimizer = true optimizer_runs = 200 +auto_detect_solc = false +bytecode_hash = "none" +auto_detect_remappings = false fs_permissions = [ { access = "read", path = "./out-optimized" } ] @@ -22,6 +25,7 @@ depth = 10 [profile.optimized-build] via_ir = true test = 'src' +optimizer_runs = 20000 out = 'out-optimized' [profile.optimized-test] diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 00000000..988cc6b0 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: UNLICENSED +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 {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {AccountFactory} from "../src/account/AccountFactory.sol"; +import {UpgradeableModularAccount} from "../src/account/UpgradeableModularAccount.sol"; +import {SingleSignerValidation} from "../src/modules/validation/SingleSignerValidation.sol"; + +contract DeployScript is Script { + IEntryPoint public entryPoint = IEntryPoint(payable(vm.envAddress("ENTRYPOINT"))); + + address public owner = vm.envAddress("OWNER"); + + address public accountImpl = vm.envOr("ACCOUNT_IMPL", address(0)); + address public factory = vm.envOr("FACTORY", address(0)); + address public singleSignerValidation = vm.envOr("SINGLE_SIGNER_VALIDATION", address(0)); + + bytes32 public accountImplSalt = bytes32(vm.envOr("ACCOUNT_IMPL_SALT", uint256(0))); + bytes32 public factorySalt = bytes32(vm.envOr("FACTORY_SALT", uint256(0))); + bytes32 public singleSignerValidationSalt = bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_SALT", uint256(0))); + + uint256 public requiredStakeAmount = vm.envOr("STAKE_AMOUNT", uint256(0.1 ether)); + 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); + + _deployAccountImpl(accountImplSalt, accountImpl); + _deploySingleSignerValidation(singleSignerValidationSalt, singleSignerValidation); + _deployAccountFactory(factorySalt, factory); + _addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount); + } + + function _deployAccountImpl(bytes32 salt, address expected) internal { + console2.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt))); + + address addr = Create2.computeAddress( + salt, keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(entryPoint))) + ); + if (addr != expected) { + console2.log("Expected address mismatch"); + console2.log("Expected: ", expected); + console2.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console2.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)); + revert(); + } + + console2.log("Deployed AccountImpl at: ", address(deployed)); + } else { + console2.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))); + + address addr = + Create2.computeAddress(salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode))); + if (addr != expected) { + console2.log("Expected address mismatch"); + console2.log("Expected: ", expected); + console2.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console2.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)); + revert(); + } + + console2.log("Deployed SingleSignerValidation at: ", address(deployed)); + } else { + console2.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))); + + address addr = Create2.computeAddress( + salt, + keccak256( + abi.encodePacked( + type(AccountFactory).creationCode, + abi.encode(entryPoint, accountImpl, singleSignerValidation, owner) + ) + ) + ); + if (addr != expected) { + console2.log("Expected address mismatch"); + console2.log("Expected: ", expected); + console2.log("Actual: ", addr); + revert(); + } + + if (addr.code.length == 0) { + console2.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)); + revert(); + } + + console2.log("Deployed AccountFactory at: ", address(deployed)); + } else { + console2.log("Code found at expected address, skipping deployment"); + } + } + + function _addStakeForFactory(uint32 unstakeDelay, uint256 stakeAmount) internal { + console2.log("Adding stake to factory"); + + uint256 currentStake = entryPoint.getDepositInfo(address(factory)).stake; + console2.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); + } else { + console2.log("No stake to add"); + } + } +} diff --git a/src/account/AccountFactory.sol b/src/account/AccountFactory.sol index ca15e815..07a74d09 100644 --- a/src/account/AccountFactory.sol +++ b/src/account/AccountFactory.sol @@ -17,9 +17,12 @@ contract AccountFactory is Ownable { IEntryPoint public immutable ENTRY_POINT; address public immutable SINGLE_SIGNER_VALIDATION; - constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl, address _singleSignerValidation) - Ownable(msg.sender) - { + constructor( + IEntryPoint _entryPoint, + UpgradeableModularAccount _accountImpl, + address _singleSignerValidation, + address owner + ) Ownable(owner) { ENTRY_POINT = _entryPoint; _PROXY_BYTECODE_HASH = keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); diff --git a/test/account/AccountFactory.t.sol b/test/account/AccountFactory.t.sol index 6e79b1c3..37ffa74c 100644 --- a/test/account/AccountFactory.t.sol +++ b/test/account/AccountFactory.t.sol @@ -11,7 +11,7 @@ contract AccountFactoryTest is AccountTestBase { function setUp() public { _account = new UpgradeableModularAccount(entryPoint); - _factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation)); + _factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation), address(this)); } function test_createAccount() public { diff --git a/test/script/Deploy.s.t.sol b/test/script/Deploy.s.t.sol new file mode 100644 index 00000000..c0b09c17 --- /dev/null +++ b/test/script/Deploy.s.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; + +import {DeployScript} from "../../script/Deploy.s.sol"; + +import {AccountFactory} from "../../src/account/AccountFactory.sol"; +import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol"; +import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol"; + +contract DeployTest is Test { + DeployScript internal _deployScript; + + EntryPoint internal _entryPoint; + + address internal _owner; + + address internal _accountImpl; + address internal _singleSignerValidation; + address internal _factory; + + function setUp() public { + _entryPoint = new EntryPoint(); + _owner = makeAddr("OWNER"); + + vm.setEnv("ENTRYPOINT", vm.toString(address(_entryPoint))); + vm.setEnv("OWNER", vm.toString(_owner)); + + // Create1 derivation of the 2nd address deployed + address deployScriptAddr = address(0x2e234DAe75C793f67A35089C9d99245E1C58470b); + + _accountImpl = Create2.computeAddress( + bytes32(0), + keccak256( + abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint))) + ), + deployScriptAddr + ); + + _singleSignerValidation = Create2.computeAddress( + bytes32(0), keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), deployScriptAddr + ); + + _factory = Create2.computeAddress( + bytes32(0), + keccak256( + abi.encodePacked( + type(AccountFactory).creationCode, + abi.encode(address(_entryPoint), _accountImpl, _singleSignerValidation, _owner) + ) + ), + deployScriptAddr + ); + + vm.setEnv("ACCOUNT_IMPL", vm.toString(address(_accountImpl))); + vm.setEnv("FACTORY", vm.toString(address(_factory))); + vm.setEnv("SINGLE_SIGNER_VALIDATION", vm.toString(address(_singleSignerValidation))); + + vm.setEnv("ACCOUNT_IMPL_SALT", vm.toString(uint256(0))); + vm.setEnv("FACTORY_SALT", vm.toString(uint256(0))); + vm.setEnv("SINGLE_SIGNER_VALIDATION_SALT", vm.toString(uint256(0))); + + _deployScript = new DeployScript(); + + vm.deal(address(_deployScript), 0.1 ether); + } + + function test_deployScript_run() public { + _deployScript.run(); + + assertTrue(_accountImpl.code.length > 0); + assertTrue(_factory.code.length > 0); + assertTrue(_singleSignerValidation.code.length > 0); + } +} From 240c54f004088a31e8f6cef93f39de54cf9e2d52 Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 25 Jul 2024 12:58:38 -0400 Subject: [PATCH 2/4] .env.example and README update --- .env.example | 20 ++++++++++++++++++++ README.md | 11 +++++++++++ 2 files changed, 31 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..4d52b27b --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ + +# Factory owner capable only of managing stake +OWNER= +# EP 0.7 address +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= + +# Optional, defaults to bytes32(0) +ACCOUNT_IMPL_SALT= +FACTORY_SALT= +SINGLE_SIGNER_VALIDATION_SALT= + +# Optional, defaults to 0.1 ether and 1 day, respectively +STAKE_AMOUNT= +UNSTAKE_DELAY= \ No newline at end of file diff --git a/README.md b/README.md index b3b625b4..97476b43 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,14 @@ Since IR compilation generates different bytecode, it's useful to test against t FOUNDRY_PROFILE=optimized-build forge build FOUNDRY_PROFILE=optimized-test forge test -vvv ``` + +## Integration testing + +The reference implementation provides a sample factory and deploy script for the factory, account implementation, and the demo validation module `SingleSignerValidation`. This is not auditted, nor intended for production use. Limitations set by the GPL-V3 license apply. + +To run this script, provide appropriate values in a `.env` file based on the `.env.example` template, then run: +```bash +forge script script/Deploy.s.sol -r --broadcast +``` + +Where `` specifies a way to sign the deployment transaction (see [here](https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw)) and `` specifies an RPC for the network you are deploying on. From 2c3dd808ab964155f67369a97a46249022e7c19c Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 25 Jul 2024 18:14:03 -0400 Subject: [PATCH 3/4] add .env to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3189d156..39dd4f5d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ node_modules/ # Coverage report/ lcov.info + +# env vars +.env From 38e6d8974f7167b73138b6d2af872ef459e366aa Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 25 Jul 2024 18:15:51 -0400 Subject: [PATCH 4/4] whitespace --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 4d52b27b..4d35e4c1 100644 --- a/.env.example +++ b/.env.example @@ -17,4 +17,4 @@ SINGLE_SIGNER_VALIDATION_SALT= # Optional, defaults to 0.1 ether and 1 day, respectively STAKE_AMOUNT= -UNSTAKE_DELAY= \ No newline at end of file +UNSTAKE_DELAY=