From 69fac5e4fa3746a9c7e9bfe612ecb95988b97d2e Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:41:03 -0400 Subject: [PATCH] feat: use solady proxies (#187) --- ...odularAccount_Runtime_AccountCreation.snap | 2 +- .../ModularAccount_Runtime_Erc20Transfer.snap | 2 +- ...ModularAccount_Runtime_NativeTransfer.snap | 2 +- .../ModularAccount_UserOp_Erc20Transfer.snap | 2 +- .../ModularAccount_UserOp_NativeTransfer.snap | 2 +- ...iModularAccount_Runtime_Erc20Transfer.snap | 2 +- ...ModularAccount_Runtime_NativeTransfer.snap | 2 +- ...miModularAccount_UserOp_Erc20Transfer.snap | 2 +- ...iModularAccount_UserOp_NativeTransfer.snap | 2 +- gas/modular-account/ModularAccount.gas.t.sol | 23 ++++++++------- src/account/ModularAccount.sol | 5 ++-- src/factory/AccountFactory.sol | 28 +++++++++---------- 12 files changed, 36 insertions(+), 38 deletions(-) diff --git a/.forge-snapshots/ModularAccount_Runtime_AccountCreation.snap b/.forge-snapshots/ModularAccount_Runtime_AccountCreation.snap index fad0a28b..84556db4 100644 --- a/.forge-snapshots/ModularAccount_Runtime_AccountCreation.snap +++ b/.forge-snapshots/ModularAccount_Runtime_AccountCreation.snap @@ -1 +1 @@ -188373 \ No newline at end of file +177463 \ No newline at end of file diff --git a/.forge-snapshots/ModularAccount_Runtime_Erc20Transfer.snap b/.forge-snapshots/ModularAccount_Runtime_Erc20Transfer.snap index 87f63f6b..8757bc61 100644 --- a/.forge-snapshots/ModularAccount_Runtime_Erc20Transfer.snap +++ b/.forge-snapshots/ModularAccount_Runtime_Erc20Transfer.snap @@ -1 +1 @@ -84294 \ No newline at end of file +84150 \ No newline at end of file diff --git a/.forge-snapshots/ModularAccount_Runtime_NativeTransfer.snap b/.forge-snapshots/ModularAccount_Runtime_NativeTransfer.snap index d0b5b262..ff10abeb 100644 --- a/.forge-snapshots/ModularAccount_Runtime_NativeTransfer.snap +++ b/.forge-snapshots/ModularAccount_Runtime_NativeTransfer.snap @@ -1 +1 @@ -60206 \ No newline at end of file +60062 \ No newline at end of file diff --git a/.forge-snapshots/ModularAccount_UserOp_Erc20Transfer.snap b/.forge-snapshots/ModularAccount_UserOp_Erc20Transfer.snap index b1ad0972..e8e52470 100644 --- a/.forge-snapshots/ModularAccount_UserOp_Erc20Transfer.snap +++ b/.forge-snapshots/ModularAccount_UserOp_Erc20Transfer.snap @@ -1 +1 @@ -194101 \ No newline at end of file +193921 \ No newline at end of file diff --git a/.forge-snapshots/ModularAccount_UserOp_NativeTransfer.snap b/.forge-snapshots/ModularAccount_UserOp_NativeTransfer.snap index a3352bde..d57664f7 100644 --- a/.forge-snapshots/ModularAccount_UserOp_NativeTransfer.snap +++ b/.forge-snapshots/ModularAccount_UserOp_NativeTransfer.snap @@ -1 +1 @@ -169841 \ No newline at end of file +169673 \ No newline at end of file diff --git a/.forge-snapshots/SemiModularAccount_Runtime_Erc20Transfer.snap b/.forge-snapshots/SemiModularAccount_Runtime_Erc20Transfer.snap index 5543cca7..38aa1902 100644 --- a/.forge-snapshots/SemiModularAccount_Runtime_Erc20Transfer.snap +++ b/.forge-snapshots/SemiModularAccount_Runtime_Erc20Transfer.snap @@ -1 +1 @@ -78555 \ No newline at end of file +78533 \ No newline at end of file diff --git a/.forge-snapshots/SemiModularAccount_Runtime_NativeTransfer.snap b/.forge-snapshots/SemiModularAccount_Runtime_NativeTransfer.snap index f006152e..eabe2f4f 100644 --- a/.forge-snapshots/SemiModularAccount_Runtime_NativeTransfer.snap +++ b/.forge-snapshots/SemiModularAccount_Runtime_NativeTransfer.snap @@ -1 +1 @@ -54476 \ No newline at end of file +54454 \ No newline at end of file diff --git a/.forge-snapshots/SemiModularAccount_UserOp_Erc20Transfer.snap b/.forge-snapshots/SemiModularAccount_UserOp_Erc20Transfer.snap index 78eea394..1ce3356f 100644 --- a/.forge-snapshots/SemiModularAccount_UserOp_Erc20Transfer.snap +++ b/.forge-snapshots/SemiModularAccount_UserOp_Erc20Transfer.snap @@ -1 +1 @@ -186801 \ No newline at end of file +186779 \ No newline at end of file diff --git a/.forge-snapshots/SemiModularAccount_UserOp_NativeTransfer.snap b/.forge-snapshots/SemiModularAccount_UserOp_NativeTransfer.snap index c13071ba..8e1cd9a5 100644 --- a/.forge-snapshots/SemiModularAccount_UserOp_NativeTransfer.snap +++ b/.forge-snapshots/SemiModularAccount_UserOp_NativeTransfer.snap @@ -1 +1 @@ -162734 \ No newline at end of file +162712 \ No newline at end of file diff --git a/gas/modular-account/ModularAccount.gas.t.sol b/gas/modular-account/ModularAccount.gas.t.sol index f59a2fd2..6d8b3a30 100644 --- a/gas/modular-account/ModularAccount.gas.t.sol +++ b/gas/modular-account/ModularAccount.gas.t.sol @@ -28,19 +28,18 @@ contract ModularAccountGasTest is ModularAccountBenchmarkBase("ModularAccount") // Also assert that the event emitted by the factory is correct Vm.Log[] memory logs = vm.getRecordedLogs(); - assertEq(logs.length, 5); + assertEq(logs.length, 4); // Logs: - // 0: ERC1967Proxy `Upgraded` - // 1: SingleSignerValidationModule `SignerTransferred` (anonymous) - // 2: ModularAccount `ValidationInstalled` - // 3: ModularAccount `Initialized` - // 4: AccountFactory `ModularAccountDeployed` - - assertEq(logs[4].topics.length, 3); - assertEq(logs[4].topics[0], AccountFactory.ModularAccountDeployed.selector); - assertEq(logs[4].topics[1], bytes32(uint256(uint160(accountAddress)))); - assertEq(logs[4].topics[2], bytes32(uint256(uint160(owner1)))); - assertEq(keccak256(logs[4].data), keccak256(abi.encodePacked(salt))); + // 0: SingleSignerValidationModule `SignerTransferred` (anonymous) + // 1: ModularAccount `ValidationInstalled` + // 2: ModularAccount `Initialized` + // 3: AccountFactory `ModularAccountDeployed` + + assertEq(logs[3].topics.length, 3); + assertEq(logs[3].topics[0], AccountFactory.ModularAccountDeployed.selector); + assertEq(logs[3].topics[1], bytes32(uint256(uint160(accountAddress)))); + assertEq(logs[3].topics[2], bytes32(uint256(uint160(owner1)))); + assertEq(keccak256(logs[3].data), keccak256(abi.encodePacked(salt))); _snap(RUNTIME, "AccountCreation", gasUsed); } diff --git a/src/account/ModularAccount.sol b/src/account/ModularAccount.sol index 765d66c4..ef47571e 100644 --- a/src/account/ModularAccount.sol +++ b/src/account/ModularAccount.sol @@ -7,10 +7,11 @@ import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntry import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; + import {IExecutionHookModule} from "@erc6900/reference-implementation/interfaces/IExecutionHookModule.sol"; import {ExecutionManifest} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol"; import { @@ -335,7 +336,7 @@ contract ModularAccount is /// @inheritdoc UUPSUpgradeable /// @notice May be validated by a global validation. - function upgradeToAndCall(address newImplementation, bytes memory data) + function upgradeToAndCall(address newImplementation, bytes calldata data) public payable override diff --git a/src/factory/AccountFactory.sol b/src/factory/AccountFactory.sol index d2da8f2d..cd1194b3 100644 --- a/src/factory/AccountFactory.sol +++ b/src/factory/AccountFactory.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.26; import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {ModularAccount} from "../account/ModularAccount.sol"; import {SemiModularAccount} from "../account/SemiModularAccount.sol"; @@ -16,7 +14,6 @@ import {LibClone} from "solady/utils/LibClone.sol"; contract AccountFactory is Ownable { ModularAccount public immutable ACCOUNT_IMPL; SemiModularAccount public immutable SEMI_MODULAR_ACCOUNT_IMPL; - bytes32 private immutable _PROXY_BYTECODE_HASH; IEntryPoint public immutable ENTRY_POINT; address public immutable SINGLE_SIGNER_VALIDATION_MODULE; @@ -31,8 +28,6 @@ contract AccountFactory is Ownable { address owner ) Ownable(owner) { ENTRY_POINT = _entryPoint; - _PROXY_BYTECODE_HASH = - keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), ""))); ACCOUNT_IMPL = _accountImpl; SEMI_MODULAR_ACCOUNT_IMPL = _semiModularImpl; SINGLE_SIGNER_VALIDATION_MODULE = _singleSignerValidationModule; @@ -47,24 +42,25 @@ contract AccountFactory is Ownable { */ function createAccount(address owner, uint256 salt, uint32 entityId) external returns (ModularAccount) { bytes32 combinedSalt = getSalt(owner, salt, entityId); - address addr = Create2.computeAddress(combinedSalt, _PROXY_BYTECODE_HASH); + + // LibClone short-circuits if it's already deployed. + (bool alreadyDeployed, address instance) = + LibClone.createDeterministicERC1967(address(ACCOUNT_IMPL), combinedSalt); // short circuit if exists - if (addr.code.length == 0) { - bytes memory pluginInstallData = abi.encode(entityId, owner); - // not necessary to check return addr since next call will fail if so - new ERC1967Proxy{salt: combinedSalt}(address(ACCOUNT_IMPL), ""); + if (!alreadyDeployed) { + bytes memory moduleInstallData = abi.encode(entityId, owner); // point proxy to actual implementation and init plugins - ModularAccount(payable(addr)).initializeWithValidation( + ModularAccount(payable(instance)).initializeWithValidation( ValidationConfigLib.pack(SINGLE_SIGNER_VALIDATION_MODULE, entityId, true, true, true), new bytes4[](0), - pluginInstallData, + moduleInstallData, new bytes[](0) ); - emit ModularAccountDeployed(addr, owner, salt); + emit ModularAccountDeployed(instance, owner, salt); } - return ModularAccount(payable(addr)); + return ModularAccount(payable(instance)); } function createSemiModularAccount(address owner, uint256 salt) external returns (SemiModularAccount) { @@ -100,7 +96,9 @@ contract AccountFactory is Ownable { * Calculate the counterfactual address of this account as it would be returned by createAccount() */ function getAddress(address owner, uint256 salt, uint32 entityId) external view returns (address) { - return Create2.computeAddress(getSalt(owner, salt, entityId), _PROXY_BYTECODE_HASH); + return LibClone.predictDeterministicAddressERC1967( + address(ACCOUNT_IMPL), getSalt(owner, salt, entityId), address(this) + ); } function getAddressSemiModular(address owner, uint256 salt) public view returns (address) {