@rocketh/diamond 0.17.11 → 0.17.13
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 +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -15
- package/dist/index.js.map +1 -1
- package/dist/utils.js +2 -2
- package/package.json +12 -8
- package/src/hardhat-deploy-v1-artifacts/Diamond.ts +17525 -0
- package/src/hardhat-deploy-v1-artifacts/DiamondCutFacet.ts +10103 -0
- package/src/hardhat-deploy-v1-artifacts/DiamondERC165Init.ts +1559 -0
- package/src/hardhat-deploy-v1-artifacts/DiamondLoupeFacet.ts +3187 -0
- package/src/hardhat-deploy-v1-artifacts/DiamondLoupeFacetWithoutSupportsInterface.ts +3040 -0
- package/src/hardhat-deploy-v1-artifacts/EIP173Proxy.ts +4613 -0
- package/src/hardhat-deploy-v1-artifacts/EIP173ProxyWithReceive.ts +4429 -0
- package/src/hardhat-deploy-v1-artifacts/ERC1967Proxy.ts +4000 -0
- package/src/hardhat-deploy-v1-artifacts/OptimizedTransparentUpgradeableProxy.ts +6058 -0
- package/src/hardhat-deploy-v1-artifacts/OwnershipFacet.ts +784 -0
- package/src/hardhat-deploy-v1-artifacts/ProxyAdmin.ts +4150 -0
- package/src/hardhat-deploy-v1-artifacts/TransparentUpgradeableProxy.ts +6747 -0
- package/src/index.ts +515 -0
- package/src/types.ts +98 -0
- package/src/utils.ts +64 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import type {Abi, Artifact, Deployment, Environment} from '@rocketh/core/types';
|
|
2
|
+
import {resolveAccount} from '@rocketh/core/account';
|
|
3
|
+
|
|
4
|
+
import {encodeFunctionData, zeroAddress} from 'viem';
|
|
5
|
+
import {logs} from 'named-logs';
|
|
6
|
+
import artifactPureDiamond from './hardhat-deploy-v1-artifacts/Diamond.js';
|
|
7
|
+
import artifactDiamondLoupeFact from './hardhat-deploy-v1-artifacts/DiamondLoupeFacet.js';
|
|
8
|
+
import artifactDiamondCutFact from './hardhat-deploy-v1-artifacts/DiamondCutFacet.js';
|
|
9
|
+
import artifactOwnershipFacet from './hardhat-deploy-v1-artifacts/OwnershipFacet.js';
|
|
10
|
+
import artifactDiamondERC165Init from './hardhat-deploy-v1-artifacts/DiamondERC165Init.js';
|
|
11
|
+
import {filterABI, mergeABIs, sigsFromABI} from './utils.js';
|
|
12
|
+
import {deploy, DeployResult} from '@rocketh/deploy';
|
|
13
|
+
|
|
14
|
+
import {read, execute} from '@rocketh/read-execute';
|
|
15
|
+
import {DiamondDeploymentConstruction, DiamondDeployOptions, Facet, FacetCut, FacetCutAction} from './types.js';
|
|
16
|
+
import {toJSONCompatibleLinkedData} from '@rocketh/core/json';
|
|
17
|
+
|
|
18
|
+
const logger = logs('@rocketh/diamond');
|
|
19
|
+
|
|
20
|
+
type OwnershipFacetABI = typeof artifactOwnershipFacet.abi;
|
|
21
|
+
type DiamondLoupeABI = typeof artifactDiamondLoupeFact.abi;
|
|
22
|
+
type DiamondCutABI = typeof artifactDiamondCutFact.abi;
|
|
23
|
+
type PureDiamondABI = typeof artifactPureDiamond.abi;
|
|
24
|
+
|
|
25
|
+
// TODO merge type of PureDiamondABI & OwnershipFacetABI & DiamondLoupeABI & DiamondCutABI;
|
|
26
|
+
type DiamondABI = PureDiamondABI;
|
|
27
|
+
const diamondAbi = mergeABIs(
|
|
28
|
+
[artifactPureDiamond.abi, artifactOwnershipFacet.abi, artifactDiamondLoupeFact.abi, artifactDiamondCutFact.abi],
|
|
29
|
+
{
|
|
30
|
+
check: true,
|
|
31
|
+
skipSupportsInterface: true,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
const artifactDiamond = {
|
|
35
|
+
...artifactPureDiamond,
|
|
36
|
+
abi: diamondAbi,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function diamond(
|
|
40
|
+
env: Environment,
|
|
41
|
+
): <TAbi extends Abi>(
|
|
42
|
+
name: string,
|
|
43
|
+
params: DiamondDeploymentConstruction<TAbi>,
|
|
44
|
+
options: DiamondDeployOptions,
|
|
45
|
+
) => Promise<DeployResult<TAbi>> {
|
|
46
|
+
const _read = read(env);
|
|
47
|
+
const _deploy = deploy(env);
|
|
48
|
+
const _execute = execute(env);
|
|
49
|
+
return async <TAbi extends Abi>(
|
|
50
|
+
name: string,
|
|
51
|
+
params: DiamondDeploymentConstruction<TAbi>,
|
|
52
|
+
options: DiamondDeployOptions,
|
|
53
|
+
) => {
|
|
54
|
+
let proxy: Deployment<DiamondABI> | undefined;
|
|
55
|
+
const proxyName = `${name}_DiamondProxy`;
|
|
56
|
+
|
|
57
|
+
const oldDeployment = env.getOrNull(name);
|
|
58
|
+
if (oldDeployment) {
|
|
59
|
+
proxy = env.get<DiamondABI>(proxyName);
|
|
60
|
+
}
|
|
61
|
+
// TODO ?
|
|
62
|
+
// if (proxy && proxy.deployedBytecode === oldDiamonBase.deployedBytecode) {
|
|
63
|
+
// return _old_deployViaDiamondProxy(name, options);
|
|
64
|
+
// }
|
|
65
|
+
|
|
66
|
+
const {account, ...viemArgs} = params;
|
|
67
|
+
const deployerAddress = resolveAccount(account, env);
|
|
68
|
+
|
|
69
|
+
// TODO
|
|
70
|
+
// if (options.diamondContract) {
|
|
71
|
+
// diamondArtifact = options.diamondContract;
|
|
72
|
+
// }
|
|
73
|
+
|
|
74
|
+
const expectedOwner = options?.owner || deployerAddress;
|
|
75
|
+
|
|
76
|
+
const newSelectors: string[] = [];
|
|
77
|
+
const facetSnapshot: Facet[] = [];
|
|
78
|
+
let oldFacets: readonly Facet[] = [];
|
|
79
|
+
if (proxy) {
|
|
80
|
+
oldFacets = await _read(proxy as unknown as Deployment<DiamondLoupeABI>, {
|
|
81
|
+
functionName: 'facets',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// console.log({ oldFacets: JSON.stringify(oldFacets, null, " ") });
|
|
85
|
+
|
|
86
|
+
const facetsSet = options.facets;
|
|
87
|
+
if (options?.defaultCutFacet === undefined || options.defaultCutFacet) {
|
|
88
|
+
facetsSet.push({
|
|
89
|
+
name: '_DefaultDiamondCutFacet',
|
|
90
|
+
artifact: artifactDiamondCutFact,
|
|
91
|
+
args: [],
|
|
92
|
+
deterministic: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (options?.defaultOwnershipFacet === undefined || options.defaultOwnershipFacet) {
|
|
96
|
+
facetsSet.push({
|
|
97
|
+
name: '_DefaultDiamondOwnershipFacet',
|
|
98
|
+
artifact: artifactOwnershipFacet,
|
|
99
|
+
args: [],
|
|
100
|
+
deterministic: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
facetsSet.push({
|
|
104
|
+
name: '_DefaultDiamondLoupeFacet',
|
|
105
|
+
artifact: artifactDiamondLoupeFact,
|
|
106
|
+
args: [],
|
|
107
|
+
deterministic: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let changesDetected = !oldDeployment;
|
|
111
|
+
// will be populated
|
|
112
|
+
let abi: TAbi = artifactPureDiamond.abi.concat([]) as unknown as TAbi;
|
|
113
|
+
const facetCuts: FacetCut[] = [];
|
|
114
|
+
let executionFacetFound: `0x${string}` | undefined;
|
|
115
|
+
const excludeSelectors: Record<string, `0x${string}`[]> = options?.excludeSelectors || {};
|
|
116
|
+
let i = 0;
|
|
117
|
+
for (const facet of facetsSet) {
|
|
118
|
+
let deterministicFacet: `0x${string}` | boolean = true;
|
|
119
|
+
|
|
120
|
+
let linkedData = options?.linkedData;
|
|
121
|
+
let libraries = options?.libraries;
|
|
122
|
+
let facetArgs = options?.facetsArgs;
|
|
123
|
+
if (typeof facet !== 'string') {
|
|
124
|
+
if (facet.deterministic !== undefined) {
|
|
125
|
+
deterministicFacet = facet.deterministic;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let argsSpecific = false;
|
|
129
|
+
|
|
130
|
+
if (facet.linkedData) {
|
|
131
|
+
linkedData = facet.linkedData;
|
|
132
|
+
}
|
|
133
|
+
if (facet.libraries) {
|
|
134
|
+
libraries = facet.libraries;
|
|
135
|
+
}
|
|
136
|
+
if (facet.args !== undefined) {
|
|
137
|
+
// TODO fix in master
|
|
138
|
+
facetArgs = facet.args;
|
|
139
|
+
argsSpecific = true;
|
|
140
|
+
}
|
|
141
|
+
const artifact = facet.artifact;
|
|
142
|
+
|
|
143
|
+
const facetName = facet.name || artifact.contractName;
|
|
144
|
+
if (!facetName) {
|
|
145
|
+
throw new Error(`artifact for facet at index: ${i} has no name, specify a name for the facet`);
|
|
146
|
+
}
|
|
147
|
+
const constructor = artifact.abi.find((fragment) => fragment.type === 'constructor');
|
|
148
|
+
if (!argsSpecific && (!constructor || constructor.inputs.length === 0)) {
|
|
149
|
+
// reset args for case where facet do not expect any and there was no specific args set on it
|
|
150
|
+
facetArgs = [];
|
|
151
|
+
}
|
|
152
|
+
let excludeSighashes: Set<`0x${string}`> = new Set();
|
|
153
|
+
if (facetName in excludeSelectors) {
|
|
154
|
+
excludeSighashes = new Set(excludeSelectors[facetName]);
|
|
155
|
+
}
|
|
156
|
+
abi = mergeABIs<TAbi>([abi, filterABI(artifact.abi, excludeSighashes)], {
|
|
157
|
+
check: true,
|
|
158
|
+
skipSupportsInterface: false,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const implementation = await _deploy<typeof artifact.abi>(
|
|
162
|
+
facetName,
|
|
163
|
+
{
|
|
164
|
+
...params,
|
|
165
|
+
account: deployerAddress,
|
|
166
|
+
artifact,
|
|
167
|
+
args: facetArgs,
|
|
168
|
+
},
|
|
169
|
+
{libraries, linkedData, deterministic: deterministicFacet},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
let facetAddress: `0x${string}`;
|
|
173
|
+
// TODO updated, check if it is correct, seem to be trigger if linkedData get updated
|
|
174
|
+
if (implementation.newlyDeployed) {
|
|
175
|
+
// console.log(`facet ${facet} deployed at ${implementation.address}`);
|
|
176
|
+
facetAddress = implementation.address;
|
|
177
|
+
const newFacet = {
|
|
178
|
+
facetAddress,
|
|
179
|
+
functionSelectors: sigsFromABI(filterABI(implementation.abi, excludeSighashes)),
|
|
180
|
+
};
|
|
181
|
+
facetSnapshot.push(newFacet);
|
|
182
|
+
newSelectors.push(...newFacet.functionSelectors);
|
|
183
|
+
} else {
|
|
184
|
+
const oldImpl = env.get(facetName);
|
|
185
|
+
facetAddress = oldImpl.address;
|
|
186
|
+
const newFacet = {
|
|
187
|
+
facetAddress,
|
|
188
|
+
functionSelectors: sigsFromABI(filterABI(oldImpl.abi, excludeSighashes)),
|
|
189
|
+
};
|
|
190
|
+
facetSnapshot.push(newFacet);
|
|
191
|
+
newSelectors.push(...newFacet.functionSelectors);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (options.execute && options.execute.type == 'facet') {
|
|
195
|
+
const methods = artifact.abi.filter((v) => (v as any).name === options.execute?.functionName);
|
|
196
|
+
if (methods.length > 0) {
|
|
197
|
+
if (methods.length > 1) {
|
|
198
|
+
throw new Error(`multiple method named "${options.execute.functionName}" found in facet`);
|
|
199
|
+
} else {
|
|
200
|
+
if (executionFacetFound) {
|
|
201
|
+
throw new Error(`multiple facet with method named "${options.execute.functionName}"`);
|
|
202
|
+
} else {
|
|
203
|
+
executionFacetFound = facetAddress;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
i++;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const oldSelectors: `0x${string}`[] = [];
|
|
213
|
+
const oldSelectorsFacetAddress: {[selector: `0x${string}`]: `0x${string}`} = {};
|
|
214
|
+
for (const oldFacet of oldFacets) {
|
|
215
|
+
for (const selector of oldFacet.functionSelectors) {
|
|
216
|
+
oldSelectors.push(selector);
|
|
217
|
+
oldSelectorsFacetAddress[selector] = oldFacet.facetAddress;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const newFacet of facetSnapshot) {
|
|
222
|
+
const selectorsToAdd: `0x${string}`[] = [];
|
|
223
|
+
const selectorsToReplace: `0x${string}`[] = [];
|
|
224
|
+
|
|
225
|
+
for (const selector of newFacet.functionSelectors) {
|
|
226
|
+
// TODO fix in master >0 to transform into >= 0
|
|
227
|
+
if (oldSelectors.indexOf(selector) >= 0) {
|
|
228
|
+
if (oldSelectorsFacetAddress[selector].toLowerCase() !== newFacet.facetAddress.toLowerCase()) {
|
|
229
|
+
selectorsToReplace.push(selector);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
selectorsToAdd.push(selector);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (selectorsToReplace.length > 0) {
|
|
237
|
+
changesDetected = true;
|
|
238
|
+
facetCuts.push({
|
|
239
|
+
facetAddress: newFacet.facetAddress,
|
|
240
|
+
functionSelectors: selectorsToReplace,
|
|
241
|
+
action: FacetCutAction.Replace,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (selectorsToAdd.length > 0) {
|
|
246
|
+
changesDetected = true;
|
|
247
|
+
facetCuts.push({
|
|
248
|
+
facetAddress: newFacet.facetAddress,
|
|
249
|
+
functionSelectors: selectorsToAdd,
|
|
250
|
+
action: FacetCutAction.Add,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const selectorsToDelete: `0x${string}`[] = [];
|
|
256
|
+
for (const selector of oldSelectors) {
|
|
257
|
+
if (newSelectors.indexOf(selector) === -1) {
|
|
258
|
+
selectorsToDelete.push(selector);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (selectorsToDelete.length > 0) {
|
|
263
|
+
changesDetected = true;
|
|
264
|
+
facetCuts.unshift({
|
|
265
|
+
facetAddress: '0x0000000000000000000000000000000000000000',
|
|
266
|
+
functionSelectors: selectorsToDelete,
|
|
267
|
+
action: FacetCutAction.Remove,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let executeData: `0x${string}` = '0x';
|
|
272
|
+
let executeAddress: `0x${string}` = '0x0000000000000000000000000000000000000000';
|
|
273
|
+
|
|
274
|
+
if (options.execute) {
|
|
275
|
+
let addressSpecified: `0x${string}` | undefined;
|
|
276
|
+
if (options.execute.type === 'artifact') {
|
|
277
|
+
const executionDeployment = await _deploy(
|
|
278
|
+
'', // we do not save it as it is deterministic anyway
|
|
279
|
+
{
|
|
280
|
+
...params,
|
|
281
|
+
artifact: options.execute.artifact,
|
|
282
|
+
args: [], // we expect artifact use for execute to have no contructor args
|
|
283
|
+
// TODO support these with constructor arguments ?
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
deterministic: true,
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
addressSpecified = executionDeployment.address;
|
|
291
|
+
|
|
292
|
+
executeData = encodeFunctionData({
|
|
293
|
+
abi: executionDeployment.abi,
|
|
294
|
+
functionName: options.execute.functionName,
|
|
295
|
+
args: options.execute.args,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
executeAddress = addressSpecified || executionFacetFound || '0x0000000000000000000000000000000000000000';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (changesDetected) {
|
|
302
|
+
if (!proxy) {
|
|
303
|
+
const diamondConstructorArgs = options?.diamondContractArgs || ['{owner}', '{facetCuts}', '{initializations}'];
|
|
304
|
+
|
|
305
|
+
const initializationsArgIndex = diamondConstructorArgs.indexOf('{initializations}');
|
|
306
|
+
const erc165InitArgIndex = diamondConstructorArgs.indexOf('{erc165}');
|
|
307
|
+
const initArgIndex = diamondConstructorArgs.indexOf('{init}');
|
|
308
|
+
const initAddressArgIndex = diamondConstructorArgs.indexOf('{initAddress}');
|
|
309
|
+
const initDataArgIndex = diamondConstructorArgs.indexOf('{initData}');
|
|
310
|
+
const ownerArgIndex = diamondConstructorArgs.indexOf('{owner}');
|
|
311
|
+
const facetCutsArgIndex = diamondConstructorArgs.indexOf('{facetCuts}');
|
|
312
|
+
if (initializationsArgIndex >= 0 && (initArgIndex >= 0 || erc165InitArgIndex >= 0 || initDataArgIndex >= 0)) {
|
|
313
|
+
throw new Error(`{initializations} found but also one or more of {init} {erc165} {initData}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// TODO option to add more to the list
|
|
317
|
+
// else mechanism to set it up differently ? LoupeFacet without supportsInterface
|
|
318
|
+
const interfaceList: `0x${string}`[] = ['0x48e2b093'];
|
|
319
|
+
if (options?.defaultCutFacet) {
|
|
320
|
+
interfaceList.push('0x1f931c1c');
|
|
321
|
+
}
|
|
322
|
+
if (options?.defaultOwnershipFacet) {
|
|
323
|
+
interfaceList.push('0x7f5828d0');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (initializationsArgIndex >= 0 || erc165InitArgIndex >= 0) {
|
|
327
|
+
// TODO:TMP
|
|
328
|
+
const diamondERC165InitDeployment = await _deploy(
|
|
329
|
+
'_DefaultDiamondERC165Init',
|
|
330
|
+
{
|
|
331
|
+
...params,
|
|
332
|
+
artifact: artifactDiamondERC165Init,
|
|
333
|
+
args: [],
|
|
334
|
+
},
|
|
335
|
+
{deterministic: true},
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const interfaceInitCallData = encodeFunctionData({
|
|
339
|
+
abi: artifactDiamondERC165Init.abi,
|
|
340
|
+
functionName: 'setERC165',
|
|
341
|
+
args: [interfaceList, []],
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (initializationsArgIndex >= 0) {
|
|
345
|
+
const initializations = [];
|
|
346
|
+
initializations.push({
|
|
347
|
+
initContract: diamondERC165InitDeployment.address,
|
|
348
|
+
initData: interfaceInitCallData,
|
|
349
|
+
});
|
|
350
|
+
diamondConstructorArgs[initializationsArgIndex] = initializations;
|
|
351
|
+
} else {
|
|
352
|
+
diamondConstructorArgs[erc165InitArgIndex] = {
|
|
353
|
+
initContract: diamondERC165InitDeployment.address,
|
|
354
|
+
initData: interfaceInitCallData,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (ownerArgIndex >= 0) {
|
|
360
|
+
diamondConstructorArgs[ownerArgIndex] = expectedOwner;
|
|
361
|
+
} else {
|
|
362
|
+
// TODO ?
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (facetCutsArgIndex >= 0) {
|
|
366
|
+
diamondConstructorArgs[facetCutsArgIndex] = facetCuts;
|
|
367
|
+
} else {
|
|
368
|
+
throw new Error(`diamond constructor needs a {facetCuts} argument`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (executeData) {
|
|
372
|
+
if (initializationsArgIndex >= 0) {
|
|
373
|
+
if (executeData !== '0x') {
|
|
374
|
+
diamondConstructorArgs[initializationsArgIndex].push({
|
|
375
|
+
initContract: executeAddress,
|
|
376
|
+
initData: executeData,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
if (initArgIndex >= 0) {
|
|
381
|
+
diamondConstructorArgs[initArgIndex] = {
|
|
382
|
+
initContract: executeAddress,
|
|
383
|
+
initData: executeData,
|
|
384
|
+
};
|
|
385
|
+
} else if (initDataArgIndex >= 0) {
|
|
386
|
+
diamondConstructorArgs[initDataArgIndex] = executeData;
|
|
387
|
+
if (initAddressArgIndex >= 0) {
|
|
388
|
+
diamondConstructorArgs[initAddressArgIndex] = executeAddress;
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
throw new Error(`no {init} or {initData} found in list of args even though execute is set in option`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let salt = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
397
|
+
if (typeof options.deterministicSalt !== 'undefined') {
|
|
398
|
+
if (typeof options.deterministicSalt === 'string') {
|
|
399
|
+
if (options.deterministicSalt === salt) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`deterministicSalt cannot be 0x000..., it needs to be a non-zero bytes32 salt. This is to ensure you are explicitly specifying different addresses for multiple diamonds`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
if (options.deterministicSalt.length !== 66) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`deterministicSalt needs to be a string of 66 hexadecimal characters (including the 0x prefix)`,
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
salt = options.deterministicSalt;
|
|
410
|
+
} else {
|
|
411
|
+
throw new Error(`deterministicSalt need to be a string, an non-zero bytes32 salt`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
proxy = await _deploy(
|
|
416
|
+
proxyName,
|
|
417
|
+
{
|
|
418
|
+
...params,
|
|
419
|
+
artifact: artifactDiamond as unknown as Artifact<DiamondABI>,
|
|
420
|
+
args: diamondConstructorArgs as any,
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
deterministic: options.deterministicSalt,
|
|
424
|
+
skipIfAlreadyDeployed: true,
|
|
425
|
+
},
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
await env.save<TAbi>(
|
|
429
|
+
name,
|
|
430
|
+
{
|
|
431
|
+
...proxy,
|
|
432
|
+
abi,
|
|
433
|
+
linkedData: toJSONCompatibleLinkedData(options.linkedData),
|
|
434
|
+
facets: facetSnapshot,
|
|
435
|
+
execute: options.execute,
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
doNotCountAsNewDeployment: proxy.newlyDeployed ? false : true,
|
|
439
|
+
},
|
|
440
|
+
);
|
|
441
|
+
} else {
|
|
442
|
+
if (!oldDeployment) {
|
|
443
|
+
throw new Error(`Cannot find Deployment for ${name}`);
|
|
444
|
+
}
|
|
445
|
+
const currentOwner = await _read(proxy as unknown as Deployment<OwnershipFacetABI>, {
|
|
446
|
+
functionName: 'owner',
|
|
447
|
+
});
|
|
448
|
+
if (currentOwner.toLowerCase() !== expectedOwner.toLowerCase()) {
|
|
449
|
+
throw new Error('To change owner, you need to call `transferOwnership`');
|
|
450
|
+
}
|
|
451
|
+
if (currentOwner === zeroAddress) {
|
|
452
|
+
throw new Error('The Diamond belongs to no-one. It cannot be upgraded anymore');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const txHash = await _execute(proxy as unknown as Deployment<DiamondCutABI>, {
|
|
456
|
+
...params,
|
|
457
|
+
account: expectedOwner,
|
|
458
|
+
functionName: 'diamondCut',
|
|
459
|
+
args: [
|
|
460
|
+
facetCuts,
|
|
461
|
+
executeData === '0x'
|
|
462
|
+
? ('0x0000000000000000000000000000000000000000' as `0x${string}`)
|
|
463
|
+
: executeAddress || proxy.address, // TODO || proxy.address should not be required, the facet should have been found
|
|
464
|
+
executeData,
|
|
465
|
+
],
|
|
466
|
+
value: undefined,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const diamondDeployment: Deployment<TAbi> = {
|
|
470
|
+
...oldDeployment,
|
|
471
|
+
linkedData: toJSONCompatibleLinkedData(options.linkedData),
|
|
472
|
+
address: proxy.address,
|
|
473
|
+
abi,
|
|
474
|
+
facets: facetSnapshot,
|
|
475
|
+
execute: options.execute, // TODO add receipt + tx hash
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// // TODO reenable history with options
|
|
479
|
+
// if (oldDeployment.history && oldDeployment.history) {
|
|
480
|
+
// diamondDeployment.history = diamondDeployment.history
|
|
481
|
+
// ? diamondDeployment.history.concat([oldDeployment])
|
|
482
|
+
// : [oldDeployment];
|
|
483
|
+
// }
|
|
484
|
+
|
|
485
|
+
await env.save(name, diamondDeployment);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const deployment = env.get<TAbi>(name);
|
|
489
|
+
return {
|
|
490
|
+
...deployment,
|
|
491
|
+
newlyDeployed: true,
|
|
492
|
+
};
|
|
493
|
+
} else {
|
|
494
|
+
// const oldDeployment = await partialExtension.get(name);
|
|
495
|
+
|
|
496
|
+
// const proxiedDeployment: DeploymentSubmission = {
|
|
497
|
+
// ...oldDeployment,
|
|
498
|
+
// facets: facetSnapshot,
|
|
499
|
+
// abi,
|
|
500
|
+
// execute: options.execute,
|
|
501
|
+
// };
|
|
502
|
+
// // TODO ?
|
|
503
|
+
// // proxiedDeployment.history = proxiedDeployment.history
|
|
504
|
+
// // ? proxiedDeployment.history.concat([oldDeployment])
|
|
505
|
+
// // : [oldDeployment];
|
|
506
|
+
// await saveDeployment(name, proxiedDeployment);
|
|
507
|
+
|
|
508
|
+
const deployment = await env.get<TAbi>(name);
|
|
509
|
+
return {
|
|
510
|
+
...deployment,
|
|
511
|
+
newlyDeployed: false,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type {DeployOptions} from '@rocketh/deploy';
|
|
2
|
+
import type {EIP1193Account} from 'eip-1193';
|
|
3
|
+
import type {
|
|
4
|
+
Artifact,
|
|
5
|
+
Deployment,
|
|
6
|
+
DeploymentConstruction,
|
|
7
|
+
Libraries,
|
|
8
|
+
Abi,
|
|
9
|
+
LinkedDataProvided,
|
|
10
|
+
} from '@rocketh/core/types';
|
|
11
|
+
import type {ContractFunctionArgs, ContractFunctionName, WriteContractParameters} from 'viem';
|
|
12
|
+
|
|
13
|
+
export type Facet = {
|
|
14
|
+
facetAddress: `0x${string}`;
|
|
15
|
+
functionSelectors: readonly `0x${string}`[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export enum FacetCutAction {
|
|
19
|
+
Add,
|
|
20
|
+
Replace,
|
|
21
|
+
Remove,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type FacetCut = Facet & {
|
|
25
|
+
action: FacetCutAction;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type FacetOptions = {
|
|
29
|
+
name?: string;
|
|
30
|
+
artifact: Artifact;
|
|
31
|
+
args?: any[];
|
|
32
|
+
linkedData?: LinkedDataProvided;
|
|
33
|
+
libraries?: Libraries;
|
|
34
|
+
deterministic?: boolean | `0x${string}`;
|
|
35
|
+
};
|
|
36
|
+
export type DiamondFacets = Array<FacetOptions>;
|
|
37
|
+
|
|
38
|
+
export type ExecutionArgs<
|
|
39
|
+
TAbi extends Abi,
|
|
40
|
+
TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'>,
|
|
41
|
+
TArgs extends ContractFunctionArgs<TAbi, 'nonpayable' | 'payable', TFunctionName> = ContractFunctionArgs<
|
|
42
|
+
TAbi,
|
|
43
|
+
'nonpayable' | 'payable',
|
|
44
|
+
TFunctionName
|
|
45
|
+
>,
|
|
46
|
+
> = Pick<WriteContractParameters<TAbi, TFunctionName, TArgs>, 'args' | 'functionName'>;
|
|
47
|
+
|
|
48
|
+
export type ExecuteOptions<
|
|
49
|
+
TAbi extends Abi,
|
|
50
|
+
TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'>,
|
|
51
|
+
TArgs extends ContractFunctionArgs<TAbi, 'nonpayable' | 'payable', TFunctionName> = ContractFunctionArgs<
|
|
52
|
+
TAbi,
|
|
53
|
+
'nonpayable' | 'payable',
|
|
54
|
+
TFunctionName
|
|
55
|
+
>,
|
|
56
|
+
> = ExecutionArgs<TAbi, TFunctionName, TArgs> & {
|
|
57
|
+
type: 'artifact';
|
|
58
|
+
artifact: Artifact<TAbi>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type DiamondDeployOptions<
|
|
62
|
+
TAbi extends Abi = Abi,
|
|
63
|
+
TFunctionName extends ContractFunctionName<TAbi, 'nonpayable' | 'payable'> = ContractFunctionName<
|
|
64
|
+
TAbi,
|
|
65
|
+
'nonpayable' | 'payable'
|
|
66
|
+
>,
|
|
67
|
+
TArgs extends ContractFunctionArgs<TAbi, 'nonpayable' | 'payable', TFunctionName> = ContractFunctionArgs<
|
|
68
|
+
TAbi,
|
|
69
|
+
'nonpayable' | 'payable',
|
|
70
|
+
TFunctionName
|
|
71
|
+
>,
|
|
72
|
+
> = Omit<DeployOptions, 'skipIfAlreadyDeployed' | 'alwaysOverride' | 'deterministic'> & {
|
|
73
|
+
facets: DiamondFacets;
|
|
74
|
+
owner?: EIP1193Account;
|
|
75
|
+
execute?: ExecuteOptions<TAbi, TFunctionName, TArgs> | {type: 'facet'; functionName: string; args: any[]};
|
|
76
|
+
defaultCutFacet?: boolean;
|
|
77
|
+
defaultOwnershipFacet?: boolean;
|
|
78
|
+
diamondContractArgs?: any[];
|
|
79
|
+
excludeSelectors?: {
|
|
80
|
+
[facetName: string]: `0x${string}`[];
|
|
81
|
+
};
|
|
82
|
+
facetsArgs?: any[];
|
|
83
|
+
deterministicSalt?: `0x${string}`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// TODO omit nonce ? // TODO omit chain ? same for rocketh-deploy
|
|
87
|
+
export type DiamondDeploymentConstruction<TAbi extends Abi> = Omit<
|
|
88
|
+
DeploymentConstruction<TAbi>,
|
|
89
|
+
'artifact' | 'args'
|
|
90
|
+
> & {
|
|
91
|
+
artifact?: Artifact;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type DeployViaDiamondFunction = <TAbi extends Abi>(
|
|
95
|
+
name: string,
|
|
96
|
+
params: DiamondDeploymentConstruction<TAbi>,
|
|
97
|
+
options: DiamondDeployOptions,
|
|
98
|
+
) => Promise<Deployment<TAbi> & {newlyDeployed: boolean}>;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {Abi, AbiEvent, AbiFunction} from 'abitype';
|
|
2
|
+
import {toEventHash, toFunctionSelector} from 'viem';
|
|
3
|
+
|
|
4
|
+
export function sigsFromABI(abi: Abi): `0x${string}`[] {
|
|
5
|
+
return abi.filter((fragment) => fragment.type === 'function').map((fragment) => toFunctionSelector(fragment));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function filterABI(abi: Abi, excludeSighashes: Set<string>): any[] {
|
|
9
|
+
return abi.filter((fragment) => fragment.type !== 'function' || !excludeSighashes.has(toFunctionSelector(fragment)));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function mergeABIs<TAbi extends Abi = Abi>(
|
|
13
|
+
abis: Abi[],
|
|
14
|
+
options: {check: boolean; skipSupportsInterface: boolean},
|
|
15
|
+
): TAbi {
|
|
16
|
+
if (abis.length === 0) {
|
|
17
|
+
return [] as unknown as TAbi;
|
|
18
|
+
}
|
|
19
|
+
const result: Abi = structuredClone(abis[0]);
|
|
20
|
+
|
|
21
|
+
for (let i = 1; i < abis.length; i++) {
|
|
22
|
+
const abi = abis[i];
|
|
23
|
+
for (const fragment of abi) {
|
|
24
|
+
const newFragment = fragment;
|
|
25
|
+
// TODO constructor special handling ?
|
|
26
|
+
const foundSameSig = result.find((v) => {
|
|
27
|
+
const existingFragment = v;
|
|
28
|
+
if (v.type !== fragment.type) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (!existingFragment) {
|
|
32
|
+
return (v as any).name === (fragment as any).name; // TODO fallback and receive handling
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (existingFragment.type === 'constructor' || newFragment.type === 'constructor') {
|
|
36
|
+
return (existingFragment as any).name === (newFragment as any).name;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (newFragment.type === 'function') {
|
|
40
|
+
return toFunctionSelector(existingFragment as AbiFunction) === toFunctionSelector(newFragment);
|
|
41
|
+
} else if (newFragment.type === 'event') {
|
|
42
|
+
return toEventHash(existingFragment as AbiEvent) === toEventHash(newFragment as AbiEvent);
|
|
43
|
+
} else {
|
|
44
|
+
return (v as any).name === (fragment as any).name; // TODO fallback and receive handling
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (foundSameSig) {
|
|
48
|
+
if (options.check && !(options.skipSupportsInterface && (fragment as any).name === 'supportsInterface')) {
|
|
49
|
+
if (fragment.type === 'function') {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`function "${fragment.name}" will shadow "${
|
|
52
|
+
(foundSameSig as any).name
|
|
53
|
+
}". Please update code to avoid conflict.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
(result as any).push(fragment);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result as unknown as TAbi;
|
|
64
|
+
}
|