@openzeppelin/wizard 0.8.0 → 0.9.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 (73) hide show
  1. package/dist/account.js +8 -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/erc1155.d.ts.map +1 -1
  12. package/dist/erc1155.js +6 -9
  13. package/dist/erc1155.js.map +1 -1
  14. package/dist/erc20.d.ts +1 -0
  15. package/dist/erc20.d.ts.map +1 -1
  16. package/dist/erc20.js +36 -27
  17. package/dist/erc20.js.map +1 -1
  18. package/dist/erc721.d.ts +1 -0
  19. package/dist/erc721.d.ts.map +1 -1
  20. package/dist/erc721.js +25 -17
  21. package/dist/erc721.js.map +1 -1
  22. package/dist/generate/alternatives.js +1 -1
  23. package/dist/generate/alternatives.js.map +1 -1
  24. package/dist/generate/erc20.d.ts.map +1 -1
  25. package/dist/generate/erc20.js +1 -0
  26. package/dist/generate/erc20.js.map +1 -1
  27. package/dist/generate/erc721.d.ts.map +1 -1
  28. package/dist/generate/erc721.js +1 -0
  29. package/dist/generate/erc721.js.map +1 -1
  30. package/dist/generate/stablecoin.d.ts.map +1 -1
  31. package/dist/generate/stablecoin.js +2 -0
  32. package/dist/generate/stablecoin.js.map +1 -1
  33. package/dist/governor.js +9 -10
  34. package/dist/governor.js.map +1 -1
  35. package/dist/infer-transpiled.js +1 -2
  36. package/dist/infer-transpiled.js.map +1 -1
  37. package/dist/options.js +1 -2
  38. package/dist/options.js.map +1 -1
  39. package/dist/print.d.ts.map +1 -1
  40. package/dist/print.js +24 -4
  41. package/dist/print.js.map +1 -1
  42. package/dist/scripts/prepare.js +1 -2
  43. package/dist/scripts/prepare.js.map +1 -1
  44. package/dist/set-access-control.d.ts.map +1 -1
  45. package/dist/set-access-control.js +1 -1
  46. package/dist/set-access-control.js.map +1 -1
  47. package/dist/set-namespaced-storage.d.ts +7 -0
  48. package/dist/set-namespaced-storage.d.ts.map +1 -0
  49. package/dist/set-namespaced-storage.js +61 -0
  50. package/dist/set-namespaced-storage.js.map +1 -0
  51. package/dist/stablecoin.js +5 -6
  52. package/dist/stablecoin.js.map +1 -1
  53. package/dist/utils/namespaced-slot.d.ts +5 -0
  54. package/dist/utils/namespaced-slot.d.ts.map +1 -0
  55. package/dist/utils/namespaced-slot.js +18 -0
  56. package/dist/utils/namespaced-slot.js.map +1 -0
  57. package/dist/utils/transitive-closure.js +1 -2
  58. package/dist/utils/transitive-closure.js.map +1 -1
  59. package/dist/zip-foundry.js +4 -4
  60. package/dist/zip-foundry.js.map +1 -1
  61. package/package.json +5 -2
  62. package/src/contract.ts +64 -9
  63. package/src/erc1155.ts +1 -3
  64. package/src/erc20.ts +43 -14
  65. package/src/erc721.ts +25 -9
  66. package/src/generate/erc20.ts +1 -0
  67. package/src/generate/erc721.ts +1 -0
  68. package/src/generate/stablecoin.ts +2 -0
  69. package/src/print.ts +34 -3
  70. package/src/set-access-control.ts +3 -1
  71. package/src/set-namespaced-storage.ts +69 -0
  72. package/src/stablecoin.ts +3 -1
  73. package/src/utils/namespaced-slot.ts +18 -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
  }
@@ -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,6 +39,7 @@ 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 = {
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)) {
@@ -258,6 +275,20 @@ function printFunction2(
258
275
  return fn;
259
276
  }
260
277
 
278
+ function printStruct(_struct: ContractStruct): Lines[] {
279
+ const [comments, kindedName, code] = [_struct.comments, _struct.name, _struct.variables];
280
+ const struct: Lines[] = [...comments];
281
+
282
+ const braces = code.length > 0 ? '{' : '{}';
283
+ struct.push([`struct ${kindedName}`, braces].join(' '));
284
+
285
+ if (code.length > 0) {
286
+ struct.push(code, '}');
287
+ }
288
+
289
+ return struct;
290
+ }
291
+
261
292
  function printArgument(arg: FunctionArgument, { transformName }: Helpers): string {
262
293
  let type: string;
263
294
  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
+ }
package/src/stablecoin.ts CHANGED
@@ -107,7 +107,9 @@ function addCustodian(c: ContractBuilder, access: Access) {
107
107
  case 'roles': {
108
108
  const roleOwner = 'custodian';
109
109
  const roleId = 'CUSTODIAN_ROLE';
110
- const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`);
110
+ const addedConstant = c.addConstantOrImmutableOrErrorDefinition(
111
+ `bytes32 public constant ${roleId} = keccak256("${roleId}");`,
112
+ );
111
113
  if (roleOwner && addedConstant) {
112
114
  c.addConstructorArgument({ type: 'address', name: roleOwner });
113
115
  c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`);
@@ -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
+ }