Skip to content

Commit 2674a1c

Browse files
committed
refactor Account upgradeability
1 parent 41d5c74 commit 2674a1c

16 files changed

+636
-1582
lines changed

packages/core/solidity/src/account.test.ts.md

Lines changed: 478 additions & 984 deletions
Large diffs are not rendered by default.
-338 Bytes
Binary file not shown.

packages/core/solidity/src/account.ts

Lines changed: 30 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import { ContractBuilder } from './contract';
22
import type { Contract } from './contract';
33
import { defineFunctions } from './utils/define-functions';
44
import { printContract } from './print';
5-
import { makeUpgradeable } from './helpers';
65
import { defaults as commonDefaults, withCommonDefaults, type CommonOptions } from './common-options';
6+
import { upgradeableName } from './options';
77
import { setInfo } from './set-info';
88
import {
9-
addLockingConstructorAllowReachable,
109
addSigner,
11-
signerArgs,
1210
signerFunctions,
1311
signers,
1412
type SignerOptions,
@@ -75,6 +73,7 @@ export function buildAccount(opts: AccountOptions): Contract {
7573
c.addImportOnly({
7674
name: 'PackedUserOperation',
7775
path: '@openzeppelin/contracts/interfaces/draft-IERC4337.sol',
76+
transpiled: false, // PackedUserOperation doesn't start with "I" so its not recognized as an "interface object"
7877
});
7978
}
8079

@@ -86,16 +85,16 @@ function addParents(c: ContractBuilder, opts: AccountOptions): void {
8685
c.addParent({
8786
name: 'Account',
8887
path: `@openzeppelin/contracts/account/Account.sol`,
88+
transpiled: false,
8989
});
90-
c.addOverride({ name: 'Account' }, functions._validateUserOp);
90+
c.addOverride({ name: 'Account', transpiled: false }, functions._validateUserOp);
9191

9292
if (opts.signatureValidation === 'ERC7739') addEIP712(c, opts);
9393

9494
// Extensions
9595
addSignatureValidation(c, opts);
9696
addERC7579Modules(c, opts);
9797
addSigner(c, opts.signer ?? false, opts.upgradeable ?? false);
98-
addSignerInitializer(c, opts);
9998
addMultisigFunctions(c, opts);
10099
addBatchedExecution(c, opts);
101100
addERC721Holder(c, opts);
@@ -108,6 +107,7 @@ function addSignatureValidation(c: ContractBuilder, opts: AccountOptions) {
108107
c.addParent({
109108
name: 'ERC7739',
110109
path: '@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol',
110+
transpiled: false,
111111
});
112112
break;
113113
case 'ERC1271':
@@ -133,6 +133,7 @@ function addERC721Holder(c: ContractBuilder, opts: AccountOptions): void {
133133
c.addParent({
134134
name: 'ERC721Holder',
135135
path: '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol',
136+
transpiled: false,
136137
});
137138
}
138139

@@ -141,6 +142,7 @@ function addERC1155Holder(c: ContractBuilder, opts: AccountOptions): void {
141142
c.addParent({
142143
name: 'ERC1155Holder',
143144
path: '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol',
145+
transpiled: false,
144146
});
145147
}
146148

@@ -150,8 +152,9 @@ function addBatchedExecution(c: ContractBuilder, opts: AccountOptions): void {
150152
c.addParent({
151153
name: 'ERC7821',
152154
path: '@openzeppelin/contracts/account/extensions/draft-ERC7821.sol',
155+
transpiled: false,
153156
});
154-
c.addOverride({ name: 'ERC7821' }, functions._erc7821AuthorizedExecutor);
157+
c.addOverride({ name: 'ERC7821', transpiled: false }, functions._erc7821AuthorizedExecutor);
155158
c.setFunctionBody(
156159
['return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);'],
157160
functions._erc7821AuthorizedExecutor,
@@ -162,59 +165,38 @@ function addERC7579Modules(c: ContractBuilder, opts: AccountOptions): void {
162165
if (!opts.ERC7579Modules) return;
163166

164167
// Base AccountERC7579 account (upgradeable or not)
165-
const name = makeUpgradeable('AccountERC7579', opts.upgradeable);
168+
const name = 'AccountERC7579';
166169

167170
c.addParent({
168-
name: makeUpgradeable(opts.ERC7579Modules, opts.upgradeable),
169-
path: makeUpgradeable(
170-
`@openzeppelin/contracts/account/extensions/draft-${opts.ERC7579Modules}.sol`,
171-
opts.upgradeable,
172-
),
171+
name: opts.ERC7579Modules,
172+
path: `@openzeppelin/contracts/account/extensions/draft-${opts.ERC7579Modules}.sol`,
173173
});
174174
if (opts.ERC7579Modules !== 'AccountERC7579') {
175175
c.addImportOnly({
176-
name: makeUpgradeable('AccountERC7579', opts.upgradeable),
177-
path: makeUpgradeable('@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol', opts.upgradeable),
176+
name: 'AccountERC7579',
177+
path: '@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol',
178178
});
179179
}
180180

181181
// Accounts that use ERC7579 without a signer must be constructed with at least one module (executor of validation)
182182
if (!opts.signer) {
183-
const args = [
184-
{ type: 'uint256', name: 'moduleTypeId' },
185-
{ type: 'address', name: 'module' },
186-
{ type: 'bytes calldata', name: 'initData' },
187-
];
188-
const body = [
189-
'require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);',
190-
'_installModule(moduleTypeId, module, initData);',
191-
];
192-
if (opts.upgradeable) {
193-
c.addParent({
194-
name: 'Initializable',
195-
path: '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol',
196-
});
197-
addLockingConstructorAllowReachable(c);
198-
199-
const fn = { name: 'initialize', kind: 'public' as const, args };
200-
c.addModifier('initializer', fn);
201-
c.setFunctionBody(body, fn);
202-
} else {
203-
for (const arg of args) c.addConstructorArgument(arg);
204-
for (const line of body) c.addConstructorCode(line);
205-
}
183+
c.addConstructorArgument({ type: 'uint256', name: 'moduleTypeId' });
184+
c.addConstructorArgument({ type: 'address', name: 'module' });
185+
c.addConstructorArgument({ type: 'bytes calldata', name: 'initData' });
186+
c.addConstructorCode('require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);');
187+
c.addConstructorCode('_installModule(moduleTypeId, module, initData);');
206188
}
207189

208190
// isValidSignature override
209191
c.addOverride({ name }, functions.isValidSignature);
210192
if (opts.signatureValidation === 'ERC7739') {
211-
c.addOverride({ name: 'ERC7739' }, functions.isValidSignature);
193+
c.addOverride({ name: 'ERC7739', transpiled: false }, functions.isValidSignature);
212194
c.setFunctionBody(
213195
[
214196
'// ERC-7739 can return the ERC-1271 magic value, 0xffffffff (invalid) or 0x77390001 (detection).',
215197
'// If the returned value is 0xffffffff, fallback to ERC-7579 validation.',
216198
'bytes4 erc7739magic = ERC7739.isValidSignature(hash, signature);',
217-
`return erc7739magic == bytes4(0xffffffff) ? ${name}.isValidSignature(hash, signature) : erc7739magic;`,
199+
`return erc7739magic == bytes4(0xffffffff) ? ${opts.upgradeable ? upgradeableName(name) : name}.isValidSignature(hash, signature) : erc7739magic;`,
218200
],
219201
functions.isValidSignature,
220202
);
@@ -224,51 +206,6 @@ function addERC7579Modules(c: ContractBuilder, opts: AccountOptions): void {
224206
c.addOverride({ name }, functions._validateUserOp);
225207
}
226208

227-
function addSignerInitializer(c: ContractBuilder, opts: AccountOptions): void {
228-
if (opts.upgradeable) {
229-
if (!opts.signer) {
230-
addLockingConstructorAllowReachable(c);
231-
}
232-
return; // Initializer added in signer.ts
233-
}
234-
if (!opts.signer || opts.signer === 'ERC7702') return; // No initialization required
235-
236-
c.addParent({
237-
name: 'Initializable',
238-
path: opts.upgradeable
239-
? '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol'
240-
: '@openzeppelin/contracts/proxy/utils/Initializable.sol',
241-
});
242-
243-
addLockingConstructorAllowReachable(c, [
244-
'// Accounts are typically deployed and initialized as clones during their first user op,',
245-
'// therefore, initializers are disabled for the implementation contract',
246-
]);
247-
248-
const fn = { name: 'initialize', kind: 'public' as const, args: signerArgs[opts.signer] };
249-
c.addModifier('initializer', fn);
250-
251-
switch (opts.signer) {
252-
case 'Multisig':
253-
c.addFunctionCode(`_addSigners(${signerArgs[opts.signer][0]!.name});`, fn);
254-
c.addFunctionCode(`_setThreshold(${signerArgs[opts.signer][1]!.name});`, fn);
255-
break;
256-
case 'MultisigWeighted':
257-
c.addFunctionCode(`_addSigners(${signerArgs[opts.signer][0]!.name});`, fn);
258-
c.addFunctionCode(
259-
`_setSignerWeights(${signerArgs[opts.signer][0]!.name}, ${signerArgs[opts.signer][1]!.name});`,
260-
fn,
261-
);
262-
c.addFunctionCode(`_setThreshold(${signerArgs[opts.signer][2]!.name});`, fn);
263-
break;
264-
case 'ECDSA':
265-
case 'P256':
266-
case 'RSA':
267-
c.addFunctionCode(`_setSigner(${signerArgs[opts.signer].map(({ name }) => name).join(', ')});`, fn);
268-
break;
269-
}
270-
}
271-
272209
function addMultisigFunctions(c: ContractBuilder, opts: AccountOptions): void {
273210
switch (opts.signer) {
274211
case 'MultisigWeighted':
@@ -296,6 +233,7 @@ function addEIP712(c: ContractBuilder, opts: AccountOptions): void {
296233
{
297234
name: 'EIP712',
298235
path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol',
236+
transpiled: false, // do not use the upgradeable variant for in Accounts
299237
},
300238
[opts.name, '1'],
301239
);
@@ -309,21 +247,22 @@ function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions
309247
// to provide a custom validation logic
310248
if (!opts.signer && !opts.ERC7579Modules) {
311249
// Custom validation logic
312-
c.addOverride({ name: 'Account' }, signerFunctions._rawSignatureValidation);
250+
c.addOverride({ name: 'Account', transpiled: false }, signerFunctions._rawSignatureValidation);
313251
c.setFunctionBody(['// Custom validation logic', 'return false;'], signerFunctions._rawSignatureValidation);
314252
}
315253

316254
// Disambiguate between Signer and AccountERC7579
317255
if (opts.signer && opts.ERC7579Modules) {
318-
const accountName = makeUpgradeable('AccountERC7579', opts.upgradeable);
319-
const signerName = makeUpgradeable(`Signer${opts.signer}`, opts.upgradeable);
256+
const accountName = opts.upgradeable ? upgradeableName('AccountERC7579') : 'AccountERC7579';
257+
const signerName = opts.upgradeable ? upgradeableName(`Signer${opts.signer}`) : `Signer${opts.signer}`;
320258

321259
c.addImportOnly({
322260
name: 'AbstractSigner',
323261
path: '@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol',
262+
transpiled: false,
324263
});
325-
c.addOverride({ name: 'AbstractSigner' }, signerFunctions._rawSignatureValidation);
326-
c.addOverride({ name: accountName }, signerFunctions._rawSignatureValidation);
264+
c.addOverride({ name: 'AbstractSigner', transpiled: false }, signerFunctions._rawSignatureValidation);
265+
c.addOverride({ name: 'AccountERC7579' }, signerFunctions._rawSignatureValidation);
327266
c.setFunctionComments(
328267
[
329268
`// IMPORTANT: Make sure ${signerName} is most derived than ${accountName}`,
@@ -337,8 +276,8 @@ function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions
337276
// Base override for `_rawSignatureValidation` given MultiSignerERC7913Weighted is MultiSignerERC7913
338277
if (opts.signer === 'MultisigWeighted') {
339278
c.addImportOnly({
340-
name: makeUpgradeable(signers.Multisig.name, opts.upgradeable),
341-
path: makeUpgradeable(signers.Multisig.path, opts.upgradeable),
279+
name: signers.Multisig.name,
280+
path: signers.Multisig.path,
342281
});
343282
}
344283
}

packages/core/solidity/src/contract.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ export interface Contract {
99
functions: ContractFunction[];
1010
constructorCode: string[];
1111
constructorArgs: FunctionArgument[];
12-
constructorComments: string[];
1312
variables: string[];
14-
shouldAutoTranspileImports: boolean;
15-
shouldInstallContractsUpgradeable: boolean;
16-
shouldUseUpgradesPluginsForProxyDeployment: boolean;
13+
upgradeable: boolean;
1714
}
1815

1916
export type Value = string | number | { lit: string } | { note: string; value: Value };
@@ -78,17 +75,13 @@ export interface NatspecTag {
7875
export class ContractBuilder implements Contract {
7976
readonly name: string;
8077
license: string = 'MIT';
81-
82-
shouldAutoTranspileImports: boolean = false;
83-
shouldInstallContractsUpgradeable: boolean = false;
84-
shouldUseUpgradesPluginsForProxyDeployment: boolean = false;
78+
upgradeable = false;
8579

8680
readonly using: Using[] = [];
8781
readonly natspecTags: NatspecTag[] = [];
8882

8983
readonly constructorArgs: FunctionArgument[] = [];
9084
readonly constructorCode: string[] = [];
91-
readonly constructorComments: string[] = [];
9285
readonly variableSet: Set<string> = new Set();
9386

9487
private parentMap: Map<string, Parent> = new Map<string, Parent>();
@@ -187,15 +180,6 @@ export class ContractBuilder implements Contract {
187180
this.constructorCode.push(code);
188181
}
189182

190-
addConstructorComment(comment: string) {
191-
if (this.shouldAutoTranspileImports) {
192-
throw new Error(
193-
'Constructor comments are not supported when `shouldAutoTranspileImports` is true, since constructor will be transformed into an initializer',
194-
);
195-
}
196-
this.constructorComments.push(comment);
197-
}
198-
199183
addFunctionCode(code: string, baseFn: BaseFunction, mutability?: FunctionMutability) {
200184
const fn = this.addFunction(baseFn);
201185
if (fn.final) {

packages/core/solidity/src/helpers.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

packages/core/solidity/src/options.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import path from 'path';
33
import type { Contract, ReferencedContract, ImportContract } from './contract';
44
import { inferTranspiled } from './infer-transpiled';
55

6-
const upgradeableName = (n: string) => {
6+
export function upgradeableName(n: string) {
77
if (n === 'Initializable') {
88
return n;
99
} else {
1010
return n.replace(/(Upgradeable)?(?=\.|$)/, 'Upgradeable');
1111
}
1212
};
1313

14-
const upgradeableImport = (p: ImportContract): ImportContract => {
14+
export function upgradeableImport(p: ImportContract): ImportContract {
1515
const { dir, ext, name } = path.parse(p.path);
1616
// Use path.posix to get forward slashes
1717
return {
@@ -30,19 +30,19 @@ export interface Options {
3030
}
3131

3232
export interface Helpers extends Required<Options> {
33-
shouldUseInitializers: boolean;
33+
upgradeable: boolean;
3434
transformName: (name: ReferencedContract) => string;
35+
transformImport: (name: ImportContract) => ImportContract;
3536
}
3637

3738
export function withHelpers(contract: Contract, opts: Options = {}): Helpers {
38-
const shouldAutoTranspileImports = contract.shouldAutoTranspileImports;
39-
const transformName = (n: ReferencedContract) =>
40-
shouldAutoTranspileImports && inferTranspiled(n) ? upgradeableName(n.name) : n.name;
39+
const contractUpgradeable = contract.upgradeable;
4140
return {
42-
shouldUseInitializers: shouldAutoTranspileImports,
43-
transformName,
44-
transformImport: p1 => {
45-
const p2 = shouldAutoTranspileImports && inferTranspiled(p1) ? upgradeableImport(p1) : p1;
41+
upgradeable: contractUpgradeable,
42+
transformName: (n: ReferencedContract) =>
43+
contractUpgradeable && inferTranspiled(n) ? upgradeableName(n.name) : n.name,
44+
transformImport: (p1: ImportContract) => {
45+
const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1;
4646
return opts.transformImport?.(p2) ?? p2;
4747
},
4848
};

0 commit comments

Comments
 (0)