@openzeppelin/wizard 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/account.js +9 -10
  2. package/dist/account.js.map +1 -1
  3. package/dist/common-options.js +3 -4
  4. package/dist/common-options.js.map +1 -1
  5. package/dist/contract.d.ts +25 -6
  6. package/dist/contract.d.ts.map +1 -1
  7. package/dist/contract.js +48 -7
  8. package/dist/contract.js.map +1 -1
  9. package/dist/custom.js +1 -2
  10. package/dist/custom.js.map +1 -1
  11. package/dist/environments/hardhat/package-lock.json +64 -64
  12. package/dist/environments/hardhat/package.json +2 -2
  13. package/dist/environments/hardhat/upgradeable/package-lock.json +69 -69
  14. package/dist/environments/hardhat/upgradeable/package.json +4 -4
  15. package/dist/erc1155.d.ts.map +1 -1
  16. package/dist/erc1155.js +6 -9
  17. package/dist/erc1155.js.map +1 -1
  18. package/dist/erc20.d.ts +1 -0
  19. package/dist/erc20.d.ts.map +1 -1
  20. package/dist/erc20.js +36 -27
  21. package/dist/erc20.js.map +1 -1
  22. package/dist/erc721.d.ts +1 -0
  23. package/dist/erc721.d.ts.map +1 -1
  24. package/dist/erc721.js +25 -17
  25. package/dist/erc721.js.map +1 -1
  26. package/dist/generate/account.js +1 -1
  27. package/dist/generate/account.js.map +1 -1
  28. package/dist/generate/alternatives.js +1 -1
  29. package/dist/generate/alternatives.js.map +1 -1
  30. package/dist/generate/erc20.d.ts.map +1 -1
  31. package/dist/generate/erc20.js +1 -0
  32. package/dist/generate/erc20.js.map +1 -1
  33. package/dist/generate/erc721.d.ts.map +1 -1
  34. package/dist/generate/erc721.js +1 -0
  35. package/dist/generate/erc721.js.map +1 -1
  36. package/dist/generate/stablecoin.d.ts.map +1 -1
  37. package/dist/generate/stablecoin.js +3 -1
  38. package/dist/generate/stablecoin.js.map +1 -1
  39. package/dist/governor.js +9 -10
  40. package/dist/governor.js.map +1 -1
  41. package/dist/infer-transpiled.js +1 -2
  42. package/dist/infer-transpiled.js.map +1 -1
  43. package/dist/options.js +1 -2
  44. package/dist/options.js.map +1 -1
  45. package/dist/print.d.ts.map +1 -1
  46. package/dist/print.js +28 -7
  47. package/dist/print.js.map +1 -1
  48. package/dist/scripts/prepare.js +1 -2
  49. package/dist/scripts/prepare.js.map +1 -1
  50. package/dist/set-access-control.d.ts.map +1 -1
  51. package/dist/set-access-control.js +1 -1
  52. package/dist/set-access-control.js.map +1 -1
  53. package/dist/set-namespaced-storage.d.ts +7 -0
  54. package/dist/set-namespaced-storage.d.ts.map +1 -0
  55. package/dist/set-namespaced-storage.js +61 -0
  56. package/dist/set-namespaced-storage.js.map +1 -0
  57. package/dist/set-upgradeable.d.ts.map +1 -1
  58. package/dist/set-upgradeable.js +2 -0
  59. package/dist/set-upgradeable.js.map +1 -1
  60. package/dist/signer.d.ts +4 -4
  61. package/dist/signer.js +7 -7
  62. package/dist/signer.js.map +1 -1
  63. package/dist/stablecoin.d.ts +1 -1
  64. package/dist/stablecoin.d.ts.map +1 -1
  65. package/dist/stablecoin.js +29 -21
  66. package/dist/stablecoin.js.map +1 -1
  67. package/dist/utils/namespaced-slot.d.ts +5 -0
  68. package/dist/utils/namespaced-slot.d.ts.map +1 -0
  69. package/dist/utils/namespaced-slot.js +18 -0
  70. package/dist/utils/namespaced-slot.js.map +1 -0
  71. package/dist/utils/transitive-closure.js +1 -2
  72. package/dist/utils/transitive-closure.js.map +1 -1
  73. package/dist/utils/version.d.ts +1 -1
  74. package/dist/utils/version.js +1 -1
  75. package/dist/zip-foundry.js +4 -4
  76. package/dist/zip-foundry.js.map +1 -1
  77. package/dist/zip-hardhat.d.ts.map +1 -1
  78. package/dist/zip-hardhat.js +1 -0
  79. package/dist/zip-hardhat.js.map +1 -1
  80. package/package.json +7 -4
  81. package/src/account.ts +1 -0
  82. package/src/contract.ts +64 -9
  83. package/src/environments/hardhat/package-lock.json +64 -64
  84. package/src/environments/hardhat/package.json +3 -3
  85. package/src/environments/hardhat/upgradeable/package-lock.json +69 -69
  86. package/src/environments/hardhat/upgradeable/package.json +5 -5
  87. package/src/erc1155.ts +1 -3
  88. package/src/erc20.ts +43 -14
  89. package/src/erc721.ts +25 -9
  90. package/src/generate/account.ts +1 -1
  91. package/src/generate/erc20.ts +1 -0
  92. package/src/generate/erc721.ts +1 -0
  93. package/src/generate/stablecoin.ts +3 -1
  94. package/src/print.ts +41 -6
  95. package/src/set-access-control.ts +3 -1
  96. package/src/set-namespaced-storage.ts +69 -0
  97. package/src/set-upgradeable.ts +2 -0
  98. package/src/signer.ts +8 -8
  99. package/src/stablecoin.ts +31 -18
  100. package/src/utils/namespaced-slot.ts +18 -0
  101. package/src/utils/version.ts +1 -1
  102. package/src/zip-hardhat.ts +1 -0
package/src/erc721.ts CHANGED
@@ -8,10 +8,12 @@ import { defineFunctions } from './utils/define-functions';
8
8
  import type { CommonOptions } from './common-options';
9
9
  import { withCommonDefaults, defaults as commonDefaults } from './common-options';
10
10
  import { setUpgradeable } from './set-upgradeable';
11
+ import type { Upgradeable } from './set-upgradeable';
11
12
  import { setInfo } from './set-info';
12
13
  import { printContract } from './print';
13
14
  import type { ClockMode } from './set-clock-mode';
14
15
  import { clockModeDefault, setClockMode } from './set-clock-mode';
16
+ import { setNamespacedStorage, toStorageStructInstantiation } from './set-namespaced-storage';
15
17
 
16
18
  export interface ERC721Options extends CommonOptions {
17
19
  name: string;
@@ -28,9 +30,11 @@ export interface ERC721Options extends CommonOptions {
28
30
  * Setting `true` is equivalent to 'blocknumber'. Setting a clock mode implies voting is enabled.
29
31
  */
30
32
  votes?: boolean | ClockMode;
33
+ namespacePrefix?: string;
31
34
  }
32
35
 
33
36
  export const defaults: Required<ERC721Options> = {
37
+ ...commonDefaults,
34
38
  name: 'MyToken',
35
39
  symbol: 'MTK',
36
40
  baseUri: '',
@@ -41,9 +45,7 @@ export const defaults: Required<ERC721Options> = {
41
45
  mintable: false,
42
46
  incremental: false,
43
47
  votes: false,
44
- access: commonDefaults.access,
45
- upgradeable: commonDefaults.upgradeable,
46
- info: commonDefaults.info,
48
+ namespacePrefix: 'myProject',
47
49
  } as const;
48
50
 
49
51
  function withDefaults(opts: ERC721Options): Required<ERC721Options> {
@@ -58,6 +60,7 @@ function withDefaults(opts: ERC721Options): Required<ERC721Options> {
58
60
  mintable: opts.mintable ?? defaults.mintable,
59
61
  incremental: opts.incremental ?? defaults.incremental,
60
62
  votes: opts.votes ?? defaults.votes,
63
+ namespacePrefix: opts.namespacePrefix ?? defaults.namespacePrefix,
61
64
  };
62
65
  }
63
66
 
@@ -99,7 +102,7 @@ export function buildERC721(opts: ERC721Options): Contract {
99
102
  }
100
103
 
101
104
  if (allOpts.mintable) {
102
- addMintable(c, access, allOpts.incremental, allOpts.uriStorage);
105
+ addMintable(c, access, allOpts.incremental, allOpts.uriStorage, allOpts.upgradeable, allOpts.namespacePrefix);
103
106
  }
104
107
 
105
108
  if (allOpts.votes) {
@@ -174,14 +177,27 @@ function addBurnable(c: ContractBuilder) {
174
177
  });
175
178
  }
176
179
 
177
- function addMintable(c: ContractBuilder, access: Access, incremental = false, uriStorage = false) {
180
+ function addMintable(
181
+ c: ContractBuilder,
182
+ access: Access,
183
+ incremental = false,
184
+ uriStorage = false,
185
+ upgradeable: Upgradeable,
186
+ namespacePrefix: string,
187
+ ) {
178
188
  const fn = getMintFunction(incremental, uriStorage);
179
189
  requireAccessControl(c, fn, access, 'MINTER', 'minter');
180
-
181
190
  if (incremental) {
182
- c.addVariable('uint256 private _nextTokenId;');
183
- c.addFunctionCode('uint256 tokenId = _nextTokenId++;', fn);
184
- c.addFunctionCode('_safeMint(to, tokenId);', fn);
191
+ if (!upgradeable) {
192
+ c.addStateVariable('uint256 private _nextTokenId;', upgradeable);
193
+ c.addFunctionCode('uint256 tokenId = _nextTokenId++;', fn);
194
+ c.addFunctionCode('_safeMint(to, tokenId);', fn);
195
+ } else {
196
+ setNamespacedStorage(c, ['uint256 _nextTokenId;'], namespacePrefix);
197
+ c.addFunctionCode(toStorageStructInstantiation(c.name), fn);
198
+ c.addFunctionCode('uint256 tokenId = $._nextTokenId++;', fn);
199
+ c.addFunctionCode('_safeMint(to, tokenId);', fn);
200
+ }
185
201
  } else {
186
202
  c.addFunctionCode('_safeMint(to, tokenId);', fn);
187
203
  }
@@ -8,7 +8,7 @@ const account = {
8
8
  signatureValidation: [false, 'ERC1271', 'ERC7739'] as const,
9
9
  ERC721Holder: [false, true] as const,
10
10
  ERC1155Holder: [false, true] as const,
11
- signer: ['ERC7702', 'ECDSA', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const,
11
+ signer: ['ECDSA', 'EIP7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const,
12
12
  batchedExecution: [false, true] as const,
13
13
  ERC7579Modules: [false, 'AccountERC7579', 'AccountERC7579Hooked'] as const,
14
14
  access: [false] as const,
@@ -22,6 +22,7 @@ const blueprint = {
22
22
  crossChainBridging: crossChainBridgingOptions,
23
23
  access: accessOptions,
24
24
  upgradeable: upgradeableOptions,
25
+ namespacePrefix: ['myProject'],
25
26
  info: infoOptions,
26
27
  };
27
28
 
@@ -19,6 +19,7 @@ const blueprint = {
19
19
  incremental: booleans,
20
20
  access: accessOptions,
21
21
  upgradeable: upgradeableOptions,
22
+ namespacePrefix: ['myProject'],
22
23
  info: infoOptions,
23
24
  votes: [...booleans, ...clockModeOptions] as const,
24
25
  };
@@ -21,6 +21,7 @@ const erc20Basic = {
21
21
  crossChainBridging: [false] as const,
22
22
  access: [false] as const,
23
23
  info: [{}] as const,
24
+ namespacePrefix: ['myProject'],
24
25
  };
25
26
 
26
27
  const erc20Full = {
@@ -38,10 +39,11 @@ const erc20Full = {
38
39
  crossChainBridging: crossChainBridgingOptions,
39
40
  access: accessOptions,
40
41
  info: infoOptions,
42
+ namespacePrefix: ['myProject'],
41
43
  };
42
44
 
43
45
  const stablecoinExtensions = {
44
- limitations: [false, 'allowlist', 'blocklist'] as const,
46
+ restrictions: [false, 'allowlist', 'blocklist'] as const,
45
47
  custodian: booleans,
46
48
  upgradeable: [false] as const,
47
49
  };
package/src/print.ts CHANGED
@@ -6,6 +6,8 @@ import type {
6
6
  Value,
7
7
  NatspecTag,
8
8
  ImportContract,
9
+ ContractStruct,
10
+ VariableOrErrorDefinition,
9
11
  } from './contract';
10
12
  import type { Options, Helpers } from './options';
11
13
  import { withHelpers } from './options';
@@ -23,10 +25,9 @@ import { getCommunityContractsGitCommit } from './utils/community-contracts-git-
23
25
  export function printContract(contract: Contract, opts?: Options): string {
24
26
  const helpers = withHelpers(contract, opts);
25
27
 
28
+ const structs = contract.structs.map(_struct => printStruct(_struct));
26
29
  const fns = mapValues(sortedFunctions(contract), fns => fns.map(fn => printFunction(fn, helpers)));
27
-
28
30
  const hasOverrides = fns.override.some(l => l.length > 0);
29
-
30
31
  return formatLines(
31
32
  ...spaceBetween(
32
33
  [
@@ -42,7 +43,9 @@ export function printContract(contract: Contract, opts?: Options): string {
42
43
  [`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '),
43
44
 
44
45
  spaceBetween(
45
- contract.variables,
46
+ ...structs,
47
+ printVariableOrErrorDefinitionsWithComments(contract.variableOrErrorDefinitions),
48
+ printVariableOrErrorDefinitionsWithoutComments(contract.variableOrErrorDefinitions),
46
49
  printConstructor(contract, helpers),
47
50
  ...fns.code,
48
51
  ...fns.modifiers,
@@ -56,6 +59,20 @@ export function printContract(contract: Contract, opts?: Options): string {
56
59
  );
57
60
  }
58
61
 
62
+ function printVariableOrErrorDefinitionsWithComments(variableOrErrorDefinitions: VariableOrErrorDefinition[]): Lines[] {
63
+ const withComments = variableOrErrorDefinitions.filter(v => v.comments?.length);
64
+ // Spaces between each item that has comments
65
+ return spaceBetween(...withComments.map(v => [...v.comments!, v.code]));
66
+ }
67
+
68
+ function printVariableOrErrorDefinitionsWithoutComments(
69
+ variableOrErrorDefinitions: VariableOrErrorDefinition[],
70
+ ): Lines[] {
71
+ const withoutComments = variableOrErrorDefinitions.filter(v => !v.comments?.length);
72
+ // No spaces between items that don't have comments
73
+ return withoutComments.map(v => v.code);
74
+ }
75
+
59
76
  function printCompatibleLibraryVersions(contract: Contract): string {
60
77
  let result = `// Compatible with OpenZeppelin Contracts ${compatibleContractsSemver}`;
61
78
  if (importsCommunityContracts(contract)) {
@@ -84,16 +101,20 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] {
84
101
  if (hasParentParams || hasConstructorCode || (helpers.upgradeable && parentsWithInitializers.length > 0)) {
85
102
  if (helpers.upgradeable) {
86
103
  const upgradeableParents = parentsWithInitializers.filter(p => inferTranspiled(p.contract));
87
- const nonUpgradeableParents = contract.parents.filter(p => !inferTranspiled(p.contract));
104
+ // Omit Initializable and UUPSUpgradeable since they don't have explicit constructors
105
+ const nonUpgradeableParentsWithConstructors = contract.parents.filter(
106
+ p =>
107
+ !inferTranspiled(p.contract) && p.contract.name !== 'Initializable' && p.contract.name !== 'UUPSUpgradeable',
108
+ );
88
109
  const constructor = printFunction2(
89
110
  [
90
- nonUpgradeableParents.length > 0
111
+ nonUpgradeableParentsWithConstructors.length > 0
91
112
  ? '/// @custom:oz-upgrades-unsafe-allow-reachable constructor'
92
113
  : '/// @custom:oz-upgrades-unsafe-allow constructor',
93
114
  ],
94
115
  'constructor',
95
116
  [],
96
- nonUpgradeableParents.flatMap(p => printParentConstructor(p, helpers)),
117
+ nonUpgradeableParentsWithConstructors.flatMap(p => printParentConstructor(p, helpers)),
97
118
  ['_disableInitializers();'],
98
119
  );
99
120
  const initializer = printFunction2(
@@ -258,6 +279,20 @@ function printFunction2(
258
279
  return fn;
259
280
  }
260
281
 
282
+ function printStruct(_struct: ContractStruct): Lines[] {
283
+ const [comments, kindedName, code] = [_struct.comments, _struct.name, _struct.variables];
284
+ const struct: Lines[] = [...comments];
285
+
286
+ const braces = code.length > 0 ? '{' : '{}';
287
+ struct.push([`struct ${kindedName}`, braces].join(' '));
288
+
289
+ if (code.length > 0) {
290
+ struct.push(code, '}');
291
+ }
292
+
293
+ return struct;
294
+ }
295
+
261
296
  function printArgument(arg: FunctionArgument, { transformName }: Helpers): string {
262
297
  let type: string;
263
298
  if (typeof arg.type === 'string') {
@@ -65,7 +65,9 @@ export function requireAccessControl(
65
65
  }
66
66
  case 'roles': {
67
67
  const roleId = roleIdPrefix + '_ROLE';
68
- const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`);
68
+ const addedConstant = c.addConstantOrImmutableOrErrorDefinition(
69
+ `bytes32 public constant ${roleId} = keccak256("${roleId}");`,
70
+ );
69
71
  if (roleOwner && addedConstant) {
70
72
  c.addConstructorArgument({ type: 'address', name: roleOwner });
71
73
  c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`);
@@ -0,0 +1,69 @@
1
+ import type { BaseFunction, ContractBuilder, ContractStruct } from './contract';
2
+ import { computeNamespacedStorageSlot } from './utils/namespaced-slot';
3
+ import { OptionsError } from './error';
4
+
5
+ /**
6
+ * Sets namespaced variables in storage struct, and adds a function to retrieve namespaced storage.
7
+ */
8
+ export function setNamespacedStorage(c: ContractBuilder, structVariables: string[], namespacePrefix: string) {
9
+ validateNoWhitespace(namespacePrefix);
10
+
11
+ const namespaceId = toNamespaceId(namespacePrefix, c.name);
12
+ const storageFn = makeStorageFunction(c.name);
13
+ const storageStruct = makeStorageStruct(c.name, namespaceId);
14
+ const namespacedStorageConstant = `${c.name.toUpperCase()}_STORAGE_LOCATION`;
15
+
16
+ structVariables.forEach(v => c.addStructVariable(storageStruct, v));
17
+
18
+ c.addConstantOrImmutableOrErrorDefinition(
19
+ `bytes32 private constant ${namespacedStorageConstant} = ${computeNamespacedStorageSlot(namespaceId)};`,
20
+ [`// keccak256(abi.encode(uint256(keccak256("${namespaceId}")) - 1)) & ~bytes32(uint256(0xff))`],
21
+ );
22
+ c.addFunctionCode(`assembly { $.slot := ${namespacedStorageConstant} }`, storageFn);
23
+ }
24
+
25
+ export function toStorageStructInstantiation(name: string) {
26
+ return `${name}Storage storage $ = ${makeStorageFunction(name).name}();`;
27
+ }
28
+
29
+ /**
30
+ * According to ERC-7201, namespace ids should not contain any whitespace characters.
31
+ */
32
+ function validateNoWhitespace(namespacePrefix: string) {
33
+ if (namespacePrefix.match(/\s+/)) {
34
+ throw new OptionsError({ namespacePrefix: 'Namespace prefix should not contain whitespace characters' });
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Creates a namespace ID from a namespace prefix and a contract name.
40
+ * If the namespace prefix is empty, returns the contract name.
41
+ */
42
+ function toNamespaceId(namespacePrefix: string, name: string) {
43
+ if (namespacePrefix.length === 0) {
44
+ return name;
45
+ } else {
46
+ return `${namespacePrefix}.${name}`;
47
+ }
48
+ }
49
+
50
+ function makeStorageFunction(name: string): BaseFunction {
51
+ const fn: BaseFunction = {
52
+ name: `_get${name}Storage`,
53
+ kind: 'private' as const,
54
+ mutability: 'pure',
55
+ args: [],
56
+ returns: [`${name}Storage storage $`],
57
+ };
58
+
59
+ return fn;
60
+ }
61
+
62
+ function makeStorageStruct(name: string, namespaceId: string) {
63
+ const struct: ContractStruct = {
64
+ name: `${name}Storage`,
65
+ comments: [`/// @custom:storage-location erc7201:${namespaceId}`],
66
+ variables: [],
67
+ };
68
+ return struct;
69
+ }
@@ -21,6 +21,7 @@ function setUpgradeableBase(
21
21
  c.addParent({
22
22
  name: 'Initializable',
23
23
  path: '@openzeppelin/contracts/proxy/utils/Initializable.sol',
24
+ transpiled: false,
24
25
  });
25
26
 
26
27
  switch (upgradeable) {
@@ -32,6 +33,7 @@ function setUpgradeableBase(
32
33
  const UUPSUpgradeable = {
33
34
  name: 'UUPSUpgradeable',
34
35
  path: '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol',
36
+ transpiled: false,
35
37
  };
36
38
  c.addParent(UUPSUpgradeable);
37
39
  c.addOverride(UUPSUpgradeable, functions._authorizeUpgrade);
package/src/signer.ts CHANGED
@@ -3,7 +3,7 @@ import { OptionsError } from './error';
3
3
  import type { Upgradeable } from './set-upgradeable';
4
4
  import { defineFunctions } from './utils/define-functions';
5
5
 
6
- export const SignerOptions = [false, 'ERC7702', 'ECDSA', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const;
6
+ export const SignerOptions = [false, 'ECDSA', 'EIP7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const;
7
7
  export type SignerOptions = (typeof SignerOptions)[number];
8
8
 
9
9
  export function addSigner(c: ContractBuilder, signer: SignerOptions, upgradeable: Upgradeable): void {
@@ -13,11 +13,11 @@ export function addSigner(c: ContractBuilder, signer: SignerOptions, upgradeable
13
13
  c.addOverride({ name: signerName }, signerFunctions._rawSignatureValidation);
14
14
 
15
15
  switch (signer) {
16
- case 'ERC7702':
16
+ case 'EIP7702':
17
17
  c.addParent(signers[signer]);
18
18
  if (upgradeable) {
19
19
  throw new OptionsError({
20
- erc7702: 'EOAs can upgrade by redelegating to a new account',
20
+ eip7702: 'EOAs can upgrade by redelegating to a new account',
21
21
  upgradeable: 'EOAs can upgrade by redelegating to a new account',
22
22
  });
23
23
  }
@@ -38,14 +38,14 @@ export function addSigner(c: ContractBuilder, signer: SignerOptions, upgradeable
38
38
  }
39
39
 
40
40
  export const signers = {
41
- ERC7702: {
42
- name: 'SignerERC7702',
43
- path: '@openzeppelin/contracts/utils/cryptography/signers/SignerERC7702.sol',
44
- },
45
41
  ECDSA: {
46
42
  name: 'SignerECDSA',
47
43
  path: '@openzeppelin/contracts/utils/cryptography/signers/SignerECDSA.sol',
48
44
  },
45
+ EIP7702: {
46
+ name: 'SignerEIP7702',
47
+ path: '@openzeppelin/contracts/utils/cryptography/signers/SignerEIP7702.sol',
48
+ },
49
49
  P256: {
50
50
  name: 'SignerP256',
51
51
  path: '@openzeppelin/contracts/utils/cryptography/signers/SignerP256.sol',
@@ -64,7 +64,7 @@ export const signers = {
64
64
  },
65
65
  };
66
66
 
67
- export const signerArgs: Record<Exclude<SignerOptions, false | 'ERC7702'>, { name: string; type: string }[]> = {
67
+ export const signerArgs: Record<Exclude<SignerOptions, false | 'EIP7702'>, { name: string; type: string }[]> = {
68
68
  ECDSA: [{ name: 'signer', type: 'address' }],
69
69
  P256: [
70
70
  { name: 'qx', type: 'bytes32' },
package/src/stablecoin.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  } from './erc20';
13
13
 
14
14
  export interface StablecoinOptions extends ERC20Options {
15
- limitations?: false | 'allowlist' | 'blocklist';
15
+ restrictions?: false | 'allowlist' | 'blocklist';
16
16
  custodian?: boolean;
17
17
  }
18
18
 
@@ -20,7 +20,7 @@ export const defaults: Required<StablecoinOptions> = {
20
20
  ...erc20defaults,
21
21
  name: 'MyStablecoin',
22
22
  symbol: 'MST',
23
- limitations: false,
23
+ restrictions: false,
24
24
  custodian: false,
25
25
  } as const;
26
26
 
@@ -29,7 +29,7 @@ function withDefaults(opts: StablecoinOptions): Required<StablecoinOptions> {
29
29
  ...withERC20Defaults(opts),
30
30
  name: opts.name ?? defaults.name,
31
31
  symbol: opts.symbol ?? defaults.symbol,
32
- limitations: opts.limitations ?? defaults.limitations,
32
+ restrictions: opts.restrictions ?? defaults.restrictions,
33
33
  custodian: opts.custodian ?? defaults.custodian,
34
34
  };
35
35
  }
@@ -39,7 +39,7 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string {
39
39
  }
40
40
 
41
41
  export function isAccessControlRequired(opts: Partial<StablecoinOptions>): boolean {
42
- return opts.mintable || opts.limitations !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups';
42
+ return opts.mintable || opts.restrictions !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups';
43
43
  }
44
44
 
45
45
  export function buildStablecoin(opts: StablecoinOptions): Contract {
@@ -54,33 +54,37 @@ export function buildStablecoin(opts: StablecoinOptions): Contract {
54
54
  addCustodian(c, allOpts.access);
55
55
  }
56
56
 
57
- if (allOpts.limitations) {
58
- addLimitations(c, allOpts.access, allOpts.limitations);
57
+ if (allOpts.restrictions) {
58
+ addRestrictions(c, allOpts.access, allOpts.restrictions);
59
59
  }
60
60
 
61
61
  return c;
62
62
  }
63
63
 
64
- function addLimitations(c: ContractBuilder, access: Access, mode: boolean | 'allowlist' | 'blocklist') {
65
- const type = mode === 'allowlist';
66
- const ERC20Limitation = {
67
- name: type ? 'ERC20Allowlist' : 'ERC20Blocklist',
68
- path: `@openzeppelin/community-contracts/token/ERC20/extensions/${type ? 'ERC20Allowlist' : 'ERC20Blocklist'}.sol`,
64
+ function addRestrictions(c: ContractBuilder, access: Access, mode: 'allowlist' | 'blocklist') {
65
+ const isAllowlist = mode === 'allowlist';
66
+ const ERC20Restricted = {
67
+ name: 'ERC20Restricted',
68
+ path: `@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol`,
69
69
  };
70
70
 
71
- c.addParent(ERC20Limitation);
72
- c.addOverride(ERC20Limitation, functions._update);
73
- c.addOverride(ERC20Limitation, functions._approve);
71
+ c.addParent(ERC20Restricted);
72
+ c.addOverride(ERC20Restricted, functions._update);
74
73
 
75
- const [addFn, removeFn] = type
74
+ if (isAllowlist) {
75
+ c.addOverride(ERC20Restricted, functions.isUserAllowed);
76
+ c.setFunctionBody([`return getRestriction(user) == Restriction.ALLOWED;`], functions.isUserAllowed);
77
+ }
78
+
79
+ const [addFn, removeFn] = isAllowlist
76
80
  ? [functions.allowUser, functions.disallowUser]
77
81
  : [functions.blockUser, functions.unblockUser];
78
82
 
79
83
  requireAccessControl(c, addFn, access, 'LIMITER', 'limiter');
80
- c.addFunctionCode(`_${type ? 'allowUser' : 'blockUser'}(user);`, addFn);
84
+ c.addFunctionCode(`_${isAllowlist ? 'allow' : 'block'}User(user);`, addFn);
81
85
 
82
86
  requireAccessControl(c, removeFn, access, 'LIMITER', 'limiter');
83
- c.addFunctionCode(`_${type ? 'disallowUser' : 'unblockUser'}(user);`, removeFn);
87
+ c.addFunctionCode(`_resetUser(user);`, removeFn);
84
88
  }
85
89
 
86
90
  function addCustodian(c: ContractBuilder, access: Access) {
@@ -107,7 +111,9 @@ function addCustodian(c: ContractBuilder, access: Access) {
107
111
  case 'roles': {
108
112
  const roleOwner = 'custodian';
109
113
  const roleId = 'CUSTODIAN_ROLE';
110
- const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`);
114
+ const addedConstant = c.addConstantOrImmutableOrErrorDefinition(
115
+ `bytes32 public constant ${roleId} = keccak256("${roleId}");`,
116
+ );
111
117
  if (roleOwner && addedConstant) {
112
118
  c.addConstructorArgument({ type: 'address', name: roleOwner });
113
119
  c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`);
@@ -159,5 +165,12 @@ const functions = {
159
165
  kind: 'public' as const,
160
166
  args: [{ name: 'user', type: 'address' }],
161
167
  },
168
+
169
+ isUserAllowed: {
170
+ kind: 'public' as const,
171
+ args: [{ name: 'user', type: 'address' }],
172
+ returns: ['bool'],
173
+ mutability: 'view' as const,
174
+ },
162
175
  }),
163
176
  };
@@ -0,0 +1,18 @@
1
+ import { keccak256 } from 'ethereum-cryptography/keccak';
2
+ import { hexToBytes, toHex, utf8ToBytes } from 'ethereum-cryptography/utils';
3
+
4
+ /**
5
+ * Returns the ERC-7201 storage location for a given namespace id
6
+ */
7
+ export function computeNamespacedStorageSlot(id: string): string {
8
+ const innerHash = keccak256(utf8ToBytes(id));
9
+ const minusOne = BigInt('0x' + toHex(innerHash)) - 1n;
10
+ const minusOneBytes = hexToBytes(minusOne.toString(16).padStart(64, '0'));
11
+
12
+ const outerHash = keccak256(minusOneBytes);
13
+
14
+ const mask = BigInt('0xff');
15
+ const masked = BigInt('0x' + toHex(outerHash)) & ~mask;
16
+
17
+ return '0x' + masked.toString(16).padStart(64, '0');
18
+ }
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Semantic version string representing of the minimum compatible version of Contracts to display in output.
3
3
  */
4
- export const compatibleContractsSemver = '^5.4.0';
4
+ export const compatibleContractsSemver = '^5.5.0';
@@ -15,6 +15,7 @@ const config: HardhatUserConfig = {
15
15
  solidity: {
16
16
  version: "${SOLIDITY_VERSION}",
17
17
  settings: {
18
+ evmVersion: 'cancun',
18
19
  optimizer: {
19
20
  enabled: true,
20
21
  },