@openzeppelin/wizard 0.1.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.
- package/README.md +82 -0
- package/dist/add-pausable.d.ts +4 -0
- package/dist/add-pausable.d.ts.map +1 -0
- package/dist/add-pausable.js +30 -0
- package/dist/add-pausable.js.map +1 -0
- package/dist/api.d.ts +24 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +24 -0
- package/dist/api.js.map +1 -0
- package/dist/build-generic.d.ts +21 -0
- package/dist/build-generic.d.ts.map +1 -0
- package/dist/build-generic.js +24 -0
- package/dist/build-generic.js.map +1 -0
- package/dist/common-functions.d.ts +3 -0
- package/dist/common-functions.d.ts.map +1 -0
- package/dist/common-functions.js +13 -0
- package/dist/common-functions.js.map +1 -0
- package/dist/common-options.d.ts +11 -0
- package/dist/common-options.d.ts.map +1 -0
- package/dist/common-options.js +19 -0
- package/dist/common-options.js.map +1 -0
- package/dist/contract.d.ts +86 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +125 -0
- package/dist/contract.js.map +1 -0
- package/dist/contract.test.d.ts +2 -0
- package/dist/contract.test.d.ts.map +1 -0
- package/dist/contract.test.js +147 -0
- package/dist/contract.test.js.map +1 -0
- package/dist/erc1155.d.ts +14 -0
- package/dist/erc1155.d.ts.map +1 -0
- package/dist/erc1155.js +130 -0
- package/dist/erc1155.js.map +1 -0
- package/dist/erc1155.test.d.ts +2 -0
- package/dist/erc1155.test.d.ts.map +1 -0
- package/dist/erc1155.test.js +80 -0
- package/dist/erc1155.test.js.map +1 -0
- package/dist/erc20.d.ts +19 -0
- package/dist/erc20.d.ts.map +1 -0
- package/dist/erc20.js +202 -0
- package/dist/erc20.js.map +1 -0
- package/dist/erc20.test.d.ts +2 -0
- package/dist/erc20.test.d.ts.map +1 -0
- package/dist/erc20.test.js +126 -0
- package/dist/erc20.test.js.map +1 -0
- package/dist/erc721.d.ts +18 -0
- package/dist/erc721.d.ts.map +1 -0
- package/dist/erc721.js +200 -0
- package/dist/erc721.js.map +1 -0
- package/dist/erc721.test.d.ts +2 -0
- package/dist/erc721.test.d.ts.map +1 -0
- package/dist/erc721.test.js +106 -0
- package/dist/erc721.test.js.map +1 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +11 -0
- package/dist/error.js.map +1 -0
- package/dist/general.d.ts +8 -0
- package/dist/general.d.ts.map +1 -0
- package/dist/general.js +22 -0
- package/dist/general.js.map +1 -0
- package/dist/general.test.d.ts +2 -0
- package/dist/general.test.d.ts.map +1 -0
- package/dist/general.test.js +42 -0
- package/dist/general.test.js.map +1 -0
- package/dist/generate/alternatives.d.ts +7 -0
- package/dist/generate/alternatives.d.ts.map +1 -0
- package/dist/generate/alternatives.js +29 -0
- package/dist/generate/alternatives.js.map +1 -0
- package/dist/generate/erc1155.d.ts +3 -0
- package/dist/generate/erc1155.d.ts.map +1 -0
- package/dist/generate/erc1155.js +24 -0
- package/dist/generate/erc1155.js.map +1 -0
- package/dist/generate/erc20.d.ts +3 -0
- package/dist/generate/erc20.d.ts.map +1 -0
- package/dist/generate/erc20.js +28 -0
- package/dist/generate/erc20.js.map +1 -0
- package/dist/generate/erc721.d.ts +3 -0
- package/dist/generate/erc721.d.ts.map +1 -0
- package/dist/generate/erc721.js +28 -0
- package/dist/generate/erc721.js.map +1 -0
- package/dist/generate/governor.d.ts +3 -0
- package/dist/generate/governor.d.ts.map +1 -0
- package/dist/generate/governor.js +32 -0
- package/dist/generate/governor.js.map +1 -0
- package/dist/generate/sources.d.ts +16 -0
- package/dist/generate/sources.d.ts.map +1 -0
- package/dist/generate/sources.js +79 -0
- package/dist/generate/sources.js.map +1 -0
- package/dist/governor.d.ts +26 -0
- package/dist/governor.d.ts.map +1 -0
- package/dist/governor.js +386 -0
- package/dist/governor.js.map +1 -0
- package/dist/governor.test.d.ts +2 -0
- package/dist/governor.test.d.ts.map +1 -0
- package/dist/governor.test.js +104 -0
- package/dist/governor.test.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/kind.d.ts +4 -0
- package/dist/kind.d.ts.map +1 -0
- package/dist/kind.js +28 -0
- package/dist/kind.js.map +1 -0
- package/dist/options.d.ts +11 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +40 -0
- package/dist/options.js.map +1 -0
- package/dist/print-versioned.d.ts +3 -0
- package/dist/print-versioned.d.ts.map +1 -0
- package/dist/print-versioned.js +12 -0
- package/dist/print-versioned.js.map +1 -0
- package/dist/print.d.ts +6 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +182 -0
- package/dist/print.js.map +1 -0
- package/dist/scripts/prepare.d.ts +2 -0
- package/dist/scripts/prepare.d.ts.map +1 -0
- package/dist/scripts/prepare.js +52 -0
- package/dist/scripts/prepare.js.map +1 -0
- package/dist/set-access-control.d.ts +5 -0
- package/dist/set-access-control.d.ts.map +1 -0
- package/dist/set-access-control.js +38 -0
- package/dist/set-access-control.js.map +1 -0
- package/dist/set-info.d.ts +13 -0
- package/dist/set-info.d.ts.map +1 -0
- package/dist/set-info.js +17 -0
- package/dist/set-info.js.map +1 -0
- package/dist/set-upgradeable.d.ts +6 -0
- package/dist/set-upgradeable.d.ts.map +1 -0
- package/dist/set-upgradeable.js +43 -0
- package/dist/set-upgradeable.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +23 -0
- package/dist/test.js.map +1 -0
- package/dist/utils/define-functions.d.ts +5 -0
- package/dist/utils/define-functions.d.ts.map +1 -0
- package/dist/utils/define-functions.js +11 -0
- package/dist/utils/define-functions.js.map +1 -0
- package/dist/utils/duration.d.ts +3 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +31 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/find-cover.d.ts +2 -0
- package/dist/utils/find-cover.d.ts.map +1 -0
- package/dist/utils/find-cover.js +23 -0
- package/dist/utils/find-cover.js.map +1 -0
- package/dist/utils/format-lines.d.ts +7 -0
- package/dist/utils/format-lines.d.ts.map +1 -0
- package/dist/utils/format-lines.js +30 -0
- package/dist/utils/format-lines.js.map +1 -0
- package/dist/utils/map-values.d.ts +2 -0
- package/dist/utils/map-values.d.ts.map +1 -0
- package/dist/utils/map-values.js +12 -0
- package/dist/utils/map-values.js.map +1 -0
- package/dist/utils/sorted-by.d.ts +2 -0
- package/dist/utils/sorted-by.d.ts.map +1 -0
- package/dist/utils/sorted-by.js +8 -0
- package/dist/utils/sorted-by.js.map +1 -0
- package/dist/utils/to-identifier.d.ts +2 -0
- package/dist/utils/to-identifier.d.ts.map +1 -0
- package/dist/utils/to-identifier.js +12 -0
- package/dist/utils/to-identifier.js.map +1 -0
- package/dist/utils/to-identifier.test.d.ts +2 -0
- package/dist/utils/to-identifier.test.d.ts.map +1 -0
- package/dist/utils/to-identifier.test.js +21 -0
- package/dist/utils/to-identifier.test.js.map +1 -0
- package/dist/utils/transitive-closure.d.ts +5 -0
- package/dist/utils/transitive-closure.d.ts.map +1 -0
- package/dist/utils/transitive-closure.js +27 -0
- package/dist/utils/transitive-closure.js.map +1 -0
- package/dist/zip.d.ts +4 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +48 -0
- package/dist/zip.js.map +1 -0
- package/dist/zip.test.d.ts +2 -0
- package/dist/zip.test.d.ts.map +1 -0
- package/dist/zip.test.js +37 -0
- package/dist/zip.test.js.map +1 -0
- package/package.json +37 -0
- package/src/add-pausable.ts +32 -0
- package/src/api.ts +39 -0
- package/src/build-generic.ts +33 -0
- package/src/common-functions.ts +11 -0
- package/src/common-options.ts +24 -0
- package/src/contract.test.ts +164 -0
- package/src/contract.test.ts.md +272 -0
- package/src/contract.test.ts.snap +0 -0
- package/src/contract.ts +201 -0
- package/src/erc1155.test.ts +90 -0
- package/src/erc1155.test.ts.md +416 -0
- package/src/erc1155.test.ts.snap +0 -0
- package/src/erc1155.ts +160 -0
- package/src/erc20.test.ts +144 -0
- package/src/erc20.test.ts.md +571 -0
- package/src/erc20.test.ts.snap +0 -0
- package/src/erc20.ts +250 -0
- package/src/erc721.test.ts +122 -0
- package/src/erc721.test.ts.md +517 -0
- package/src/erc721.test.ts.snap +0 -0
- package/src/erc721.ts +250 -0
- package/src/error.ts +7 -0
- package/src/generate/alternatives.ts +38 -0
- package/src/generate/erc1155.ts +23 -0
- package/src/generate/erc20.ts +27 -0
- package/src/generate/erc721.ts +27 -0
- package/src/generate/governor.ts +30 -0
- package/src/generate/sources.ts +91 -0
- package/src/governor.test.ts +120 -0
- package/src/governor.test.ts.md +1419 -0
- package/src/governor.test.ts.snap +0 -0
- package/src/governor.ts +445 -0
- package/src/index.ts +23 -0
- package/src/kind.ts +30 -0
- package/src/options.ts +45 -0
- package/src/print-versioned.ts +12 -0
- package/src/print.ts +236 -0
- package/src/scripts/prepare.ts +60 -0
- package/src/set-access-control.ts +39 -0
- package/src/set-info.ts +24 -0
- package/src/set-upgradeable.ts +49 -0
- package/src/test.ts +21 -0
- package/src/utils/define-functions.ts +18 -0
- package/src/utils/duration.ts +34 -0
- package/src/utils/find-cover.ts +26 -0
- package/src/utils/format-lines.ts +31 -0
- package/src/utils/map-values.ts +10 -0
- package/src/utils/sorted-by.ts +3 -0
- package/src/utils/to-identifier.test.ts +20 -0
- package/src/utils/to-identifier.ts +7 -0
- package/src/utils/transitive-closure.ts +27 -0
- package/src/zip.test.ts +35 -0
- package/src/zip.ts +53 -0
package/src/print.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import 'array.prototype.flatmap/auto';
|
|
2
|
+
|
|
3
|
+
import type { Contract, Parent, ContractFunction, FunctionArgument, Value, NatspecTag } from './contract';
|
|
4
|
+
import { Options, Helpers, withHelpers } from './options';
|
|
5
|
+
|
|
6
|
+
import { formatLines, spaceBetween, Lines } from './utils/format-lines';
|
|
7
|
+
import { mapValues } from './utils/map-values';
|
|
8
|
+
|
|
9
|
+
const SOLIDITY_VERSION = '0.8.4';
|
|
10
|
+
|
|
11
|
+
export function printContract(contract: Contract, opts?: Options): string {
|
|
12
|
+
const helpers = withHelpers(contract, opts);
|
|
13
|
+
|
|
14
|
+
const fns = mapValues(
|
|
15
|
+
sortedFunctions(contract),
|
|
16
|
+
fns => fns.map(fn => printFunction(fn, helpers)),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const hasOverrides = fns.override.some(l => l.length > 0);
|
|
20
|
+
|
|
21
|
+
return formatLines(
|
|
22
|
+
...spaceBetween(
|
|
23
|
+
[
|
|
24
|
+
`// SPDX-License-Identifier: ${contract.license}`,
|
|
25
|
+
`pragma solidity ^${SOLIDITY_VERSION};`,
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
contract.imports.map(p => `import "${helpers.transformImport(p)}";`),
|
|
29
|
+
|
|
30
|
+
[
|
|
31
|
+
...printNatspecTags(contract.natspecTags),
|
|
32
|
+
[`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '),
|
|
33
|
+
|
|
34
|
+
spaceBetween(
|
|
35
|
+
printUsingFor(contract, helpers),
|
|
36
|
+
contract.variables.map(helpers.transformVariable),
|
|
37
|
+
printConstructor(contract, helpers),
|
|
38
|
+
...fns.code,
|
|
39
|
+
...fns.modifiers,
|
|
40
|
+
hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [],
|
|
41
|
+
...fns.override,
|
|
42
|
+
),
|
|
43
|
+
|
|
44
|
+
`}`,
|
|
45
|
+
],
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printInheritance(contract: Contract, { transformName }: Helpers): [] | [string] {
|
|
51
|
+
if (contract.parents.length > 0) {
|
|
52
|
+
return ['is ' + contract.parents.map(p => transformName(p.contract.name)).join(', ')];
|
|
53
|
+
} else {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function printUsingFor(contract: Contract, { transformName }: Helpers): string[] {
|
|
59
|
+
return contract.using.map(
|
|
60
|
+
u => `using ${transformName(u.library.name)} for ${transformName(u.usingFor)};`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printConstructor(contract: Contract, helpers: Helpers): Lines[] {
|
|
65
|
+
const hasParentParams = contract.parents.some(p => p.params.length > 0);
|
|
66
|
+
const hasConstructorCode = contract.constructorCode.length > 0;
|
|
67
|
+
if (hasParentParams || hasConstructorCode) {
|
|
68
|
+
const parents = contract.parents
|
|
69
|
+
.filter(hasInitializer)
|
|
70
|
+
.flatMap(p => printParentConstructor(p, helpers));
|
|
71
|
+
const modifiers = helpers.upgradeable ? ['initializer public'] : parents;
|
|
72
|
+
const args = contract.constructorArgs.map(a => printArgument(a, helpers));
|
|
73
|
+
const body = helpers.upgradeable
|
|
74
|
+
? spaceBetween(
|
|
75
|
+
parents.map(p => p + ';'),
|
|
76
|
+
contract.constructorCode,
|
|
77
|
+
)
|
|
78
|
+
: contract.constructorCode;
|
|
79
|
+
const head = helpers.upgradeable ? 'function initialize' : 'constructor';
|
|
80
|
+
const constructor = printFunction2(
|
|
81
|
+
head,
|
|
82
|
+
args,
|
|
83
|
+
modifiers,
|
|
84
|
+
body,
|
|
85
|
+
);
|
|
86
|
+
if (!helpers.upgradeable) {
|
|
87
|
+
return constructor;
|
|
88
|
+
} else {
|
|
89
|
+
return spaceBetween(
|
|
90
|
+
[
|
|
91
|
+
'/// @custom:oz-upgrades-unsafe-allow constructor',
|
|
92
|
+
'constructor() {',
|
|
93
|
+
[
|
|
94
|
+
'_disableInitializers();'
|
|
95
|
+
],
|
|
96
|
+
'}'
|
|
97
|
+
],
|
|
98
|
+
constructor,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hasInitializer(parent: Parent) {
|
|
107
|
+
// CAUTION
|
|
108
|
+
// This list is validated by compilation of SafetyCheck.sol.
|
|
109
|
+
// Always keep this list and that file in sync.
|
|
110
|
+
return !['Initializable'].includes(parent.contract.name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type SortedFunctions = Record<'code' | 'modifiers' | 'override', ContractFunction[]>;
|
|
114
|
+
|
|
115
|
+
// Functions with code first, then those with modifiers, then the rest
|
|
116
|
+
function sortedFunctions(contract: Contract): SortedFunctions {
|
|
117
|
+
const fns: SortedFunctions = { code: [], modifiers: [], override: [] };
|
|
118
|
+
|
|
119
|
+
for (const fn of contract.functions) {
|
|
120
|
+
if (fn.code.length > 0) {
|
|
121
|
+
fns.code.push(fn);
|
|
122
|
+
} else if (fn.modifiers.length > 0) {
|
|
123
|
+
fns.modifiers.push(fn);
|
|
124
|
+
} else {
|
|
125
|
+
fns.override.push(fn);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return fns;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function printParentConstructor({ contract, params }: Parent, helpers: Helpers): [] | [string] {
|
|
133
|
+
const fn = helpers.upgradeable ? `__${contract.name}_init` : contract.name;
|
|
134
|
+
if (helpers.upgradeable || params.length > 0) {
|
|
135
|
+
return [
|
|
136
|
+
fn + '(' + params.map(printValue).join(', ') + ')',
|
|
137
|
+
];
|
|
138
|
+
} else {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function printValue(value: Value): string {
|
|
144
|
+
if (typeof value === 'object') {
|
|
145
|
+
if ('lit' in value) {
|
|
146
|
+
return value.lit;
|
|
147
|
+
} else if ('note' in value) {
|
|
148
|
+
return `${printValue(value.value)} /* ${value.note} */`;
|
|
149
|
+
} else {
|
|
150
|
+
throw Error('Unknown value type');
|
|
151
|
+
}
|
|
152
|
+
} else if (typeof value === 'number') {
|
|
153
|
+
if (Number.isSafeInteger(value)) {
|
|
154
|
+
return value.toFixed(0);
|
|
155
|
+
} else {
|
|
156
|
+
throw new Error(`Number not representable (${value})`);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
return JSON.stringify(value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] {
|
|
164
|
+
const { transformName } = helpers;
|
|
165
|
+
|
|
166
|
+
if (fn.override.size <= 1 && fn.modifiers.length === 0 && fn.code.length === 0 && !fn.final) {
|
|
167
|
+
return []
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const modifiers: string[] = [fn.kind, ...fn.modifiers];
|
|
171
|
+
|
|
172
|
+
if (fn.mutability !== 'nonpayable') {
|
|
173
|
+
modifiers.splice(1, 0, fn.mutability);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (fn.override.size === 1) {
|
|
177
|
+
modifiers.push(`override`);
|
|
178
|
+
} else if (fn.override.size > 1) {
|
|
179
|
+
modifiers.push(`override(${[...fn.override].map(transformName).join(', ')})`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (fn.returns?.length) {
|
|
183
|
+
modifiers.push(`returns (${fn.returns.join(', ')})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const code = [...fn.code];
|
|
187
|
+
|
|
188
|
+
if (fn.override.size > 0 && !fn.final) {
|
|
189
|
+
const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`;
|
|
190
|
+
code.push(fn.returns?.length ? 'return ' + superCall : superCall);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (modifiers.length + fn.code.length > 1) {
|
|
194
|
+
return printFunction2(
|
|
195
|
+
'function ' + fn.name,
|
|
196
|
+
fn.args.map(a => printArgument(a, helpers)),
|
|
197
|
+
modifiers,
|
|
198
|
+
code,
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// generic for functions and constructors
|
|
206
|
+
// kindedName = 'function foo' or 'constructor'
|
|
207
|
+
function printFunction2(kindedName: string, args: string[], modifiers: string[], code: Lines[]): Lines[] {
|
|
208
|
+
const fn = [];
|
|
209
|
+
|
|
210
|
+
const headingLength = [kindedName, ...args, ...modifiers]
|
|
211
|
+
.map(s => s.length)
|
|
212
|
+
.reduce((a, b) => a + b);
|
|
213
|
+
|
|
214
|
+
const braces = code.length > 0 ? '{' : '{}';
|
|
215
|
+
|
|
216
|
+
if (headingLength <= 72) {
|
|
217
|
+
fn.push([`${kindedName}(${args.join(', ')})`, ...modifiers, braces].join(' '));
|
|
218
|
+
} else {
|
|
219
|
+
fn.push(`${kindedName}(${args.join(', ')})`, modifiers, braces);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (code.length > 0) {
|
|
223
|
+
fn.push(code, '}');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return fn;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function printArgument(arg: FunctionArgument, { transformName }: Helpers): string {
|
|
230
|
+
const type = /^[A-Z]/.test(arg.type) ? transformName(arg.type) : arg.type;
|
|
231
|
+
return [type, arg.name].join(' ');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function printNatspecTags(tags: NatspecTag[]): string[] {
|
|
235
|
+
return tags.map(({ key, value }) => `/// ${key} ${value}`);
|
|
236
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import hre from 'hardhat';
|
|
4
|
+
import type { BuildInfo } from 'hardhat/types';
|
|
5
|
+
import type { SourceUnit } from 'solidity-ast';
|
|
6
|
+
import { findAll } from 'solidity-ast/utils';
|
|
7
|
+
import _rimraf from 'rimraf';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { version } from "@openzeppelin/contracts/package.json";
|
|
10
|
+
|
|
11
|
+
const rimraf = promisify(_rimraf);
|
|
12
|
+
|
|
13
|
+
import type { OpenZeppelinContracts } from '../../openzeppelin-contracts';
|
|
14
|
+
import { writeGeneratedSources } from '../generate/sources';
|
|
15
|
+
import { mapValues } from '../utils/map-values';
|
|
16
|
+
import { transitiveClosure } from '../utils/transitive-closure';
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
const generatedSourcesPath = path.join(hre.config.paths.sources, 'generated');
|
|
20
|
+
await rimraf(generatedSourcesPath);
|
|
21
|
+
await writeGeneratedSources(generatedSourcesPath, 'minimal-cover');
|
|
22
|
+
await hre.run('compile');
|
|
23
|
+
|
|
24
|
+
const dependencies: Record<string, Set<string>> = {};
|
|
25
|
+
const sources: Record<string, string> = {};
|
|
26
|
+
|
|
27
|
+
for (const buildInfoPath of await hre.artifacts.getBuildInfoPaths()) {
|
|
28
|
+
const buildInfo: BuildInfo = JSON.parse(
|
|
29
|
+
await fs.readFile(buildInfoPath, 'utf8'),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
for (const [sourceFile, { ast }] of Object.entries(buildInfo.output.sources)) {
|
|
33
|
+
if (sourceFile.startsWith('@openzeppelin/contracts')) {
|
|
34
|
+
const sourceDependencies = (dependencies[sourceFile] ??= new Set());
|
|
35
|
+
for (const imp of findAll('ImportDirective', ast)) {
|
|
36
|
+
sourceDependencies.add(imp.absolutePath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const [sourceFile, { content }] of Object.entries(buildInfo.input.sources)) {
|
|
42
|
+
if (sourceFile.startsWith('@openzeppelin/contracts')) {
|
|
43
|
+
sources[sourceFile] = content;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const contracts: OpenZeppelinContracts = {
|
|
49
|
+
version,
|
|
50
|
+
sources,
|
|
51
|
+
dependencies: mapValues(transitiveClosure(dependencies), d => Array.from(d)),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
await fs.writeFile('openzeppelin-contracts.json', JSON.stringify(contracts, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main().catch(e => {
|
|
58
|
+
console.error(e);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ContractBuilder, BaseFunction } from './contract';
|
|
2
|
+
import { supportsInterface } from './common-functions';
|
|
3
|
+
|
|
4
|
+
export const accessOptions = ['ownable', 'roles'] as const;
|
|
5
|
+
|
|
6
|
+
export type Access = typeof accessOptions[number];
|
|
7
|
+
|
|
8
|
+
export function setAccessControl(c: ContractBuilder, fn: BaseFunction, access: Access, role: string) {
|
|
9
|
+
switch (access) {
|
|
10
|
+
case 'ownable': {
|
|
11
|
+
c.addParent(parents.Ownable);
|
|
12
|
+
c.addModifier('onlyOwner', fn);
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
case 'roles': {
|
|
16
|
+
const roleId = role + '_ROLE';
|
|
17
|
+
if (c.addParent(parents.AccessControl)) {
|
|
18
|
+
c.addConstructorCode('_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);');
|
|
19
|
+
}
|
|
20
|
+
c.addOverride(parents.AccessControl.name, supportsInterface);
|
|
21
|
+
if (c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`)) {
|
|
22
|
+
c.addConstructorCode(`_grantRole(${roleId}, msg.sender);`);
|
|
23
|
+
}
|
|
24
|
+
c.addModifier(`onlyRole(${roleId})`, fn);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const parents = {
|
|
31
|
+
Ownable: {
|
|
32
|
+
name: 'Ownable',
|
|
33
|
+
path: '@openzeppelin/contracts/access/Ownable.sol',
|
|
34
|
+
},
|
|
35
|
+
AccessControl: {
|
|
36
|
+
name: 'AccessControl',
|
|
37
|
+
path: '@openzeppelin/contracts/access/AccessControl.sol',
|
|
38
|
+
},
|
|
39
|
+
};
|
package/src/set-info.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ContractBuilder } from "./contract";
|
|
2
|
+
|
|
3
|
+
export const TAG_SECURITY_CONTACT = `@custom:security-contact`;
|
|
4
|
+
|
|
5
|
+
export const infoOptions = [{}, { securityContact: 'security@example.com', license: 'WTFPL' }] as const;
|
|
6
|
+
|
|
7
|
+
export const defaults: Info = { license: 'MIT' };
|
|
8
|
+
|
|
9
|
+
export type Info = {
|
|
10
|
+
securityContact?: string;
|
|
11
|
+
license?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setInfo(c: ContractBuilder, info: Info) {
|
|
15
|
+
const { securityContact, license } = info;
|
|
16
|
+
|
|
17
|
+
if (securityContact) {
|
|
18
|
+
c.addNatspecTag(TAG_SECURITY_CONTACT, securityContact);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (license) {
|
|
22
|
+
c.license = license;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ContractBuilder } from './contract';
|
|
2
|
+
import { Access, setAccessControl } from './set-access-control';
|
|
3
|
+
import { defineFunctions } from './utils/define-functions';
|
|
4
|
+
|
|
5
|
+
export const upgradeableOptions = [false, 'transparent', 'uups'] as const;
|
|
6
|
+
|
|
7
|
+
export type Upgradeable = typeof upgradeableOptions[number];
|
|
8
|
+
|
|
9
|
+
export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) {
|
|
10
|
+
if (upgradeable === false) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
c.upgradeable = true;
|
|
15
|
+
|
|
16
|
+
c.addParent({
|
|
17
|
+
name: 'Initializable',
|
|
18
|
+
path: '@openzeppelin/contracts/proxy/utils/Initializable.sol',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
switch (upgradeable) {
|
|
22
|
+
case 'transparent': break;
|
|
23
|
+
|
|
24
|
+
case 'uups': {
|
|
25
|
+
setAccessControl(c, functions._authorizeUpgrade, access, 'UPGRADER');
|
|
26
|
+
c.addParent({
|
|
27
|
+
name: 'UUPSUpgradeable',
|
|
28
|
+
path: '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol',
|
|
29
|
+
});
|
|
30
|
+
c.addOverride('UUPSUpgradeable', functions._authorizeUpgrade);
|
|
31
|
+
c.setFunctionBody([], functions._authorizeUpgrade);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
default: {
|
|
36
|
+
const _: never = upgradeable;
|
|
37
|
+
throw new Error('Unknown value for `upgradeable`');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const functions = defineFunctions({
|
|
43
|
+
_authorizeUpgrade: {
|
|
44
|
+
args: [
|
|
45
|
+
{ name: 'newImplementation', type: 'address' },
|
|
46
|
+
],
|
|
47
|
+
kind: 'internal',
|
|
48
|
+
},
|
|
49
|
+
});
|
package/src/test.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import hre from 'hardhat';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import { writeGeneratedSources } from './generate/sources';
|
|
7
|
+
|
|
8
|
+
test('result compiles', async t => {
|
|
9
|
+
const generatedSourcesPath = path.join(hre.config.paths.sources, 'generated');
|
|
10
|
+
await writeGeneratedSources(generatedSourcesPath, 'all');
|
|
11
|
+
|
|
12
|
+
// We only want to check that contracts compile and we don't care about any
|
|
13
|
+
// of the outputs. Setting empty outputSelection causes compilation to go a
|
|
14
|
+
// lot faster and not run out of memory.
|
|
15
|
+
for (const { settings } of hre.config.solidity.compilers) {
|
|
16
|
+
settings.outputSelection = {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await hre.run('compile');
|
|
20
|
+
t.pass();
|
|
21
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BaseFunction } from '../contract';
|
|
2
|
+
|
|
3
|
+
type ImplicitNameFunction = Omit<BaseFunction, 'name'>;
|
|
4
|
+
|
|
5
|
+
export function defineFunctions<F extends string>(
|
|
6
|
+
fns: Record<F, ImplicitNameFunction>,
|
|
7
|
+
): Record<F, BaseFunction>;
|
|
8
|
+
|
|
9
|
+
export function defineFunctions(
|
|
10
|
+
fns: Record<string, ImplicitNameFunction>,
|
|
11
|
+
): Record<string, BaseFunction> {
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.entries(fns).map(([name, fn]) => [
|
|
14
|
+
name,
|
|
15
|
+
Object.assign({ name }, fn),
|
|
16
|
+
]),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const durationUnits = ['block', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const;
|
|
2
|
+
type DurationUnit = typeof durationUnits[number];
|
|
3
|
+
export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`);
|
|
4
|
+
|
|
5
|
+
const second = 1;
|
|
6
|
+
const minute = 60 * second;
|
|
7
|
+
const hour = 60 * minute;
|
|
8
|
+
const day = 24 * hour;
|
|
9
|
+
const week = 7 * day;
|
|
10
|
+
const month = 30 * day;
|
|
11
|
+
const year = 365 * day;
|
|
12
|
+
const secondsForUnit = { second, minute, hour, day, week, month, year };
|
|
13
|
+
|
|
14
|
+
export function durationToBlocks(duration: string, blockTime: number): number {
|
|
15
|
+
const match = duration.trim().match(durationPattern);
|
|
16
|
+
|
|
17
|
+
if (!match) {
|
|
18
|
+
throw new Error('Bad duration format');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const value = parseFloat(match[1]!);
|
|
22
|
+
const unit = match[2]! as DurationUnit;
|
|
23
|
+
|
|
24
|
+
if (unit === 'block') {
|
|
25
|
+
if (!Number.isInteger(value)) {
|
|
26
|
+
throw new Error('Invalid number of blocks');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const durationSeconds = value * secondsForUnit[unit];
|
|
33
|
+
return Math.round(durationSeconds / blockTime);
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sortedBy } from './sorted-by';
|
|
2
|
+
|
|
3
|
+
// Greedy approximation of minimum set cover.
|
|
4
|
+
|
|
5
|
+
export function findCover<T extends object>(sets: T[], getElements: (set: T) => unknown[]): T[] {
|
|
6
|
+
const sortedSets = sortedBy(
|
|
7
|
+
sets.map(set => ({ set, elems: getElements(set) })),
|
|
8
|
+
s => -s.elems.length,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const seen = new Set<unknown>();
|
|
12
|
+
const res = [];
|
|
13
|
+
|
|
14
|
+
for (const { set, elems } of sortedSets) {
|
|
15
|
+
let included = false;
|
|
16
|
+
for (const e of elems) {
|
|
17
|
+
if (!included && !seen.has(e)) {
|
|
18
|
+
res.push(set);
|
|
19
|
+
included = true;
|
|
20
|
+
}
|
|
21
|
+
seen.add(e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return res;
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import 'array.prototype.flatmap/auto';
|
|
2
|
+
|
|
3
|
+
export type Lines = string | typeof whitespace | Lines[];
|
|
4
|
+
|
|
5
|
+
const whitespace = Symbol('whitespace');
|
|
6
|
+
|
|
7
|
+
export function formatLines(...lines: Lines[]): string {
|
|
8
|
+
return [...indentEach(0, lines)].join('\n') + '\n';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function* indentEach(
|
|
12
|
+
indent: number,
|
|
13
|
+
lines: Lines[],
|
|
14
|
+
): Generator<string | typeof whitespace> {
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (line === whitespace) {
|
|
17
|
+
yield '';
|
|
18
|
+
} else if (Array.isArray(line)) {
|
|
19
|
+
yield* indentEach(indent + 1, line);
|
|
20
|
+
} else {
|
|
21
|
+
yield ' '.repeat(indent) + line;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function spaceBetween(...lines: Lines[][]): Lines[] {
|
|
27
|
+
return lines
|
|
28
|
+
.filter(l => l.length > 0)
|
|
29
|
+
.flatMap<Lines>(l => [whitespace, ...l])
|
|
30
|
+
.slice(1);
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
|
|
3
|
+
import { toIdentifier } from './to-identifier';
|
|
4
|
+
|
|
5
|
+
test('unmodified', t => {
|
|
6
|
+
t.is(toIdentifier('abc'), 'abc');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('remove leading specials', t => {
|
|
10
|
+
t.is(toIdentifier('--abc'), 'abc');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('remove specials and upcase next char', t => {
|
|
14
|
+
t.is(toIdentifier('abc-def'), 'abcDef');
|
|
15
|
+
t.is(toIdentifier('abc--def'), 'abcDef');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('capitalize', t => {
|
|
19
|
+
t.is(toIdentifier('abc', true), 'Abc');
|
|
20
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function toIdentifier(str: string, capitalize = false): string {
|
|
2
|
+
return str
|
|
3
|
+
.normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents
|
|
4
|
+
.replace(/^[^a-zA-Z$_]+/, '')
|
|
5
|
+
.replace(/^(.)/, c => capitalize ? c.toUpperCase() : c)
|
|
6
|
+
.replace(/[^\w$]+(.?)/g, (_, c) => c.toUpperCase());
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type T = string;
|
|
2
|
+
|
|
3
|
+
export function transitiveClosure(obj: Record<T, Iterable<T>>): Record<T, Set<T>> {
|
|
4
|
+
const closure = {} as Record<T, Set<T>>;
|
|
5
|
+
|
|
6
|
+
for (const key in obj) {
|
|
7
|
+
closure[key] = reachable(obj, key);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return closure;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function reachable(obj: Record<T, Iterable<T>>, key: T): Set<T> {
|
|
14
|
+
let prevSize = 0;
|
|
15
|
+
const res = new Set(obj[key]);
|
|
16
|
+
|
|
17
|
+
while (prevSize < res.size) {
|
|
18
|
+
prevSize = res.size;
|
|
19
|
+
for (const k of res) {
|
|
20
|
+
for (const v of obj[k] ?? []) {
|
|
21
|
+
res.add(v);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return res;
|
|
27
|
+
}
|
package/src/zip.test.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
|
|
3
|
+
import { zipContract } from './zip';
|
|
4
|
+
import { buildERC20 } from './erc20';
|
|
5
|
+
import { generateSources } from './generate/sources';
|
|
6
|
+
import { buildGeneric } from './build-generic';
|
|
7
|
+
|
|
8
|
+
test('erc20 basic', t => {
|
|
9
|
+
const c = buildERC20({ name: 'MyToken', symbol: 'MTK' });
|
|
10
|
+
const zip = zipContract(c);
|
|
11
|
+
const files = Object.values(zip.files).map(f => f.name).sort();
|
|
12
|
+
|
|
13
|
+
t.deepEqual(files, [
|
|
14
|
+
'@openzeppelin/',
|
|
15
|
+
'@openzeppelin/contracts/',
|
|
16
|
+
'@openzeppelin/contracts/README.md',
|
|
17
|
+
'@openzeppelin/contracts/token/',
|
|
18
|
+
'@openzeppelin/contracts/token/ERC20/',
|
|
19
|
+
'@openzeppelin/contracts/token/ERC20/ERC20.sol',
|
|
20
|
+
'@openzeppelin/contracts/token/ERC20/IERC20.sol',
|
|
21
|
+
'@openzeppelin/contracts/token/ERC20/extensions/',
|
|
22
|
+
'@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol',
|
|
23
|
+
'@openzeppelin/contracts/utils/',
|
|
24
|
+
'@openzeppelin/contracts/utils/Context.sol',
|
|
25
|
+
'MyToken.sol',
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('can zip all combinations', t => {
|
|
30
|
+
for (const { options } of generateSources('all')) {
|
|
31
|
+
const c = buildGeneric(options);
|
|
32
|
+
zipContract(c);
|
|
33
|
+
}
|
|
34
|
+
t.pass();
|
|
35
|
+
});
|