diff --git a/.changeset/metal-ghosts-listen.md b/.changeset/metal-ghosts-listen.md new file mode 100644 index 000000000..6aea76406 --- /dev/null +++ b/.changeset/metal-ghosts-listen.md @@ -0,0 +1,5 @@ +--- +'@openzeppelin/wizard': patch +--- + +Add initializers to ERC-7579 non-upgradeable accounts diff --git a/packages/core/solidity/src/account.test.ts.md b/packages/core/solidity/src/account.test.ts.md index 59f7f04d9..cb107593f 100644 --- a/packages/core/solidity/src/account.test.ts.md +++ b/packages/core/solidity/src/account.test.ts.md @@ -225,11 +225,20 @@ Generated by [AVA](https://avajs.dev). import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol";␊ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";␊ import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol";␊ + import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol";␊ ␊ - contract MyAccount is Account, EIP712, ERC7739, AccountERC7579 {␊ - constructor(uint256 moduleTypeId, address module, bytes calldata initData)␊ - EIP712("MyAccount", "1")␊ + contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579 {␊ + /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ + constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(uint256 moduleTypeId, address module, bytes calldata initData)␊ + public␊ + initializer␊ {␊ require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);␊ _installModule(moduleTypeId, module, initData);␊ @@ -270,10 +279,21 @@ Generated by [AVA](https://avajs.dev). import {Account} from "@openzeppelin/contracts/account/Account.sol";␊ import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol";␊ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";␊ + import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol";␊ ␊ - contract MyAccount is Account, IERC1271, AccountERC7579 {␊ - constructor(uint256 moduleTypeId, address module, bytes calldata initData) {␊ + contract MyAccount is Initializable, Account, IERC1271, AccountERC7579 {␊ + /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ + constructor() {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(uint256 moduleTypeId, address module, bytes calldata initData)␊ + public␊ + initializer␊ + {␊ require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);␊ _installModule(moduleTypeId, module, initData);␊ }␊ @@ -311,11 +331,20 @@ Generated by [AVA](https://avajs.dev). import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol";␊ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";␊ import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol";␊ + import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol";␊ ␊ - contract MyAccount is Account, EIP712, ERC7739, AccountERC7579 {␊ - constructor(uint256 moduleTypeId, address module, bytes calldata initData)␊ - EIP712("MyAccount", "1")␊ + contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579 {␊ + /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ + constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(uint256 moduleTypeId, address module, bytes calldata initData)␊ + public␊ + initializer␊ {␊ require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);␊ _installModule(moduleTypeId, module, initData);␊ @@ -358,11 +387,20 @@ Generated by [AVA](https://avajs.dev). import {AccountERC7579Hooked} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579Hooked.sol";␊ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";␊ import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol";␊ + import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol";␊ ␊ - contract MyAccount is Account, EIP712, ERC7739, AccountERC7579Hooked {␊ - constructor(uint256 moduleTypeId, address module, bytes calldata initData)␊ - EIP712("MyAccount", "1")␊ + contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579Hooked {␊ + /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ + constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(uint256 moduleTypeId, address module, bytes calldata initData)␊ + public␊ + initializer␊ {␊ require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);␊ _installModule(moduleTypeId, module, initData);␊ @@ -699,6 +737,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579Upgradeable, UUPSUpgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -758,6 +798,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, IERC1271, AccountERC7579Upgradeable, UUPSUpgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -815,6 +857,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579Upgradeable, UUPSUpgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -876,6 +920,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579HookedUpgradeable, UUPSUpgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -1174,6 +1220,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579Upgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -1226,6 +1274,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, IERC1271, AccountERC7579Upgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -1276,6 +1326,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579Upgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ @@ -1330,6 +1382,8 @@ Generated by [AVA](https://avajs.dev). contract MyAccount is Initializable, Account, EIP712, ERC7739, AccountERC7579HookedUpgradeable {␊ /// @custom:oz-upgrades-unsafe-allow-reachable constructor␊ constructor() EIP712("MyAccount", "1") {␊ + // Accounts are typically deployed and initialized as clones during their first user op,␊ + // therefore, initializers are disabled for the implementation contract␊ _disableInitializers();␊ }␊ ␊ diff --git a/packages/core/solidity/src/account.test.ts.snap b/packages/core/solidity/src/account.test.ts.snap index 8539b23ac..3e40ed318 100644 Binary files a/packages/core/solidity/src/account.test.ts.snap and b/packages/core/solidity/src/account.test.ts.snap differ diff --git a/packages/core/solidity/src/account.ts b/packages/core/solidity/src/account.ts index 8d9efcb3a..18215601c 100644 --- a/packages/core/solidity/src/account.ts +++ b/packages/core/solidity/src/account.ts @@ -189,20 +189,15 @@ function addERC7579Modules(c: ContractBuilder, opts: AccountOptions): void { 'require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);', '_installModule(moduleTypeId, module, initData);', ]; - if (opts.upgradeable) { - c.addParent({ - name: 'Initializable', - path: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', - }); - addLockingConstructorAllowReachable(c); + c.addParent({ + name: 'Initializable', + path: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', + }); + addLockingConstructorAllowReachableAccountFactory(c); - const fn = { name: 'initialize', kind: 'public' as const, args }; - c.addModifier('initializer', fn); - c.setFunctionBody(body, fn); - } else { - for (const arg of args) c.addConstructorArgument(arg); - for (const line of body) c.addConstructorCode(line); - } + const fn = { name: 'initialize', kind: 'public' as const, args }; + c.addModifier('initializer', fn); + c.setFunctionBody(body, fn); } // isValidSignature override @@ -240,10 +235,7 @@ function addSignerInitializer(c: ContractBuilder, opts: AccountOptions): void { : '@openzeppelin/contracts/proxy/utils/Initializable.sol', }); - addLockingConstructorAllowReachable(c, [ - '// Accounts are typically deployed and initialized as clones during their first user op,', - '// therefore, initializers are disabled for the implementation contract', - ]); + addLockingConstructorAllowReachableAccountFactory(c); const fn = { name: 'initialize', kind: 'public' as const, args: signerArgs[opts.signer] }; c.addModifier('initializer', fn); @@ -344,6 +336,13 @@ function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions } } +function addLockingConstructorAllowReachableAccountFactory(c: ContractBuilder): void { + addLockingConstructorAllowReachable(c, [ + '// Accounts are typically deployed and initialized as clones during their first user op,', + '// therefore, initializers are disabled for the implementation contract', + ]); +} + const functions = { ...defineFunctions({ isValidSignature: { diff --git a/packages/core/solidity/src/signer.ts b/packages/core/solidity/src/signer.ts index 42e209a35..b7ce5ea9a 100644 --- a/packages/core/solidity/src/signer.ts +++ b/packages/core/solidity/src/signer.ts @@ -70,7 +70,7 @@ export function addLockingConstructorAllowReachable(c: ContractBuilder, bodyComm if (!c.constructorCode.includes(disableInitializers)) { c.addConstructorComment('/// @custom:oz-upgrades-unsafe-allow-reachable constructor'); bodyComments?.forEach(comment => c.addConstructorCode(comment)); - c.addConstructorCode(disableInitializers); + c.addConstructorCode(`_disableInitializers();`); } }