rocketh 0.17.13 → 0.17.15
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 +340 -1
- package/dist/environment/chains.d.ts +7 -1
- package/dist/environment/chains.d.ts.map +1 -1
- package/dist/environment/chains.js +6 -3
- package/dist/environment/chains.js.map +1 -1
- package/dist/environment/index.js.map +1 -1
- package/dist/executor/index.d.ts.map +1 -1
- package/dist/executor/index.js +6 -3
- package/dist/executor/index.js.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/environment/chains.ts +240 -0
- package/src/environment/index.ts +833 -0
- package/src/executor/index.ts +540 -0
- package/src/index.ts +20 -0
- package/src/internal/logging.ts +35 -0
- package/src/internal/types.ts +4 -0
- package/src/types.ts +3 -0
- package/src/utils/eth.ts +120 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountType,
|
|
3
|
+
Artifact,
|
|
4
|
+
Deployment,
|
|
5
|
+
Environment,
|
|
6
|
+
Signer,
|
|
7
|
+
PendingDeployment,
|
|
8
|
+
PendingTransaction,
|
|
9
|
+
ResolvedAccount,
|
|
10
|
+
ResolvedNamedAccounts,
|
|
11
|
+
ResolvedNamedSigners,
|
|
12
|
+
UnknownDeployments,
|
|
13
|
+
UnresolvedUnknownNamedAccounts,
|
|
14
|
+
UnresolvedNetworkSpecificData,
|
|
15
|
+
ResolvedNetworkSpecificData,
|
|
16
|
+
DataType,
|
|
17
|
+
ResolvedExecutionParams,
|
|
18
|
+
ResolvedUserConfig,
|
|
19
|
+
PendingExecution,
|
|
20
|
+
DeploymentStore,
|
|
21
|
+
ProgressIndicator,
|
|
22
|
+
} from '@rocketh/core/types';
|
|
23
|
+
import {Abi, Address} from 'abitype';
|
|
24
|
+
import {InternalEnvironment} from '../internal/types.js';
|
|
25
|
+
import {JSONToString, stringToJSON} from '@rocketh/core/json';
|
|
26
|
+
import {
|
|
27
|
+
EIP1193Account,
|
|
28
|
+
EIP1193Block,
|
|
29
|
+
EIP1193BlockWithTransactions,
|
|
30
|
+
EIP1193DATA,
|
|
31
|
+
EIP1193Transaction,
|
|
32
|
+
EIP1193TransactionReceipt,
|
|
33
|
+
} from 'eip-1193';
|
|
34
|
+
import {logger, spin} from '../internal/logging.js';
|
|
35
|
+
import {mergeArtifacts} from '@rocketh/core/artifacts';
|
|
36
|
+
import {TransactionHashTracker, TransactionHashTrackerProvider} from '@rocketh/core/providers';
|
|
37
|
+
|
|
38
|
+
function wait(numSeconds: number): Promise<void> {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
setTimeout(resolve, numSeconds * 1000);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function displayTransaction(transaction: EIP1193Transaction) {
|
|
45
|
+
if ('maxFeePerGas' in transaction) {
|
|
46
|
+
return `(type ${transaction.type}, maxFeePerGas: ${BigInt(
|
|
47
|
+
transaction.maxFeePerGas,
|
|
48
|
+
).toString()}, maxPriorityFeePerGas: ${BigInt(transaction.maxPriorityFeePerGas).toString()})`;
|
|
49
|
+
} else if ('gasPrice' in transaction) {
|
|
50
|
+
return `(type ${transaction.type ? Number(transaction.type) : '0'}, gasPrice: ${BigInt(
|
|
51
|
+
transaction.gasPrice,
|
|
52
|
+
).toString()})`;
|
|
53
|
+
} else {
|
|
54
|
+
return `(tx with no gas pricing, type: ${Number((transaction as any).type)})`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadDeployments(
|
|
59
|
+
deploymentStore: DeploymentStore,
|
|
60
|
+
deploymentsPath: string,
|
|
61
|
+
networkName: string,
|
|
62
|
+
onlyABIAndAddress?: boolean,
|
|
63
|
+
expectedChain?: {chainId: string; genesisHash?: `0x${string}`; deleteDeploymentsIfDifferentGenesisHash?: boolean},
|
|
64
|
+
): Promise<{
|
|
65
|
+
deployments: UnknownDeployments;
|
|
66
|
+
migrations: Record<string, number>;
|
|
67
|
+
chainId?: string;
|
|
68
|
+
genesisHash?: `0x${string}`;
|
|
69
|
+
}> {
|
|
70
|
+
const deploymentsFound: UnknownDeployments = {};
|
|
71
|
+
|
|
72
|
+
let fileNames: string[];
|
|
73
|
+
try {
|
|
74
|
+
fileNames = await deploymentStore.listFiles(
|
|
75
|
+
deploymentsPath,
|
|
76
|
+
networkName,
|
|
77
|
+
(name) => !(name.startsWith('.') && name !== '.migrations.json') && name !== 'solcInputs',
|
|
78
|
+
);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// console.log('no folder at ' + deployPath);
|
|
81
|
+
return {deployments: {}, migrations: {}};
|
|
82
|
+
}
|
|
83
|
+
let chainId: string;
|
|
84
|
+
let genesisHash: `0x${string}` | undefined;
|
|
85
|
+
if (fileNames.length > 0) {
|
|
86
|
+
if (await deploymentStore.hasFile(deploymentsPath, networkName, '.chain')) {
|
|
87
|
+
const chainSTR = await deploymentStore.readFile(deploymentsPath, networkName, '.chain');
|
|
88
|
+
const chainData = JSON.parse(chainSTR);
|
|
89
|
+
chainId = chainData.chainId;
|
|
90
|
+
genesisHash = chainData.genesisHash;
|
|
91
|
+
} else if (await deploymentStore.hasFile(deploymentsPath, networkName, '.chainId')) {
|
|
92
|
+
chainId = await deploymentStore.readFile(deploymentsPath, networkName, '.chainId');
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`A '.chain' or '.chainId' file is expected to be present in the deployment folder for network ${networkName}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (expectedChain) {
|
|
100
|
+
if (expectedChain.chainId !== chainId) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Loading deployment from environment '${networkName}' (with chainId: ${chainId}) for a different chainId (${expectedChain.chainId})`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (genesisHash) {
|
|
107
|
+
if (expectedChain.genesisHash && expectedChain.genesisHash !== genesisHash) {
|
|
108
|
+
if (expectedChain.deleteDeploymentsIfDifferentGenesisHash) {
|
|
109
|
+
// we delete the old folder
|
|
110
|
+
|
|
111
|
+
await deploymentStore.deleteAll(deploymentsPath, networkName);
|
|
112
|
+
return {deployments: {}, migrations: {}};
|
|
113
|
+
} else {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Loading deployment from environment '${networkName}' (with genesisHash: ${genesisHash}) for a different genesisHash (${expectedChain.genesisHash})`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.warn(
|
|
121
|
+
`genesisHash not found in environment '${networkName}' (with chainId: ${chainId}), writing .chain with expected one...`,
|
|
122
|
+
);
|
|
123
|
+
await deploymentStore.writeFile(
|
|
124
|
+
deploymentsPath,
|
|
125
|
+
networkName,
|
|
126
|
+
'.chain',
|
|
127
|
+
JSON.stringify({chainId: expectedChain.chainId, genesisHash: expectedChain.genesisHash}),
|
|
128
|
+
);
|
|
129
|
+
try {
|
|
130
|
+
await deploymentStore.deleteFile(deploymentsPath, networkName, '.chainId');
|
|
131
|
+
} catch {}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
return {deployments: {}, migrations: {}};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let migrations: Record<string, number> = {};
|
|
139
|
+
const migrationsFileName = '.migrations.json';
|
|
140
|
+
if (await deploymentStore.hasFile(deploymentsPath, networkName, migrationsFileName)) {
|
|
141
|
+
try {
|
|
142
|
+
migrations = JSON.parse(await deploymentStore.readFile(deploymentsPath, networkName, migrationsFileName));
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error(`failed to parse .migrations.json`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const fileName of fileNames) {
|
|
149
|
+
if (fileName.substring(fileName.length - 5) === '.json' && fileName !== '.migrations.json') {
|
|
150
|
+
let deployment = JSON.parse(await deploymentStore.readFile(deploymentsPath, networkName, fileName));
|
|
151
|
+
if (onlyABIAndAddress) {
|
|
152
|
+
deployment = {
|
|
153
|
+
address: deployment.address,
|
|
154
|
+
abi: deployment.abi,
|
|
155
|
+
linkedData: deployment.linkedData,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const name = fileName.slice(0, fileName.length - 5);
|
|
159
|
+
// console.log('fetching ' + deploymentFileName + ' for ' + name);
|
|
160
|
+
|
|
161
|
+
deploymentsFound[name] = deployment;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {deployments: deploymentsFound, migrations, chainId, genesisHash};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function createEnvironment<
|
|
168
|
+
NamedAccounts extends UnresolvedUnknownNamedAccounts = UnresolvedUnknownNamedAccounts,
|
|
169
|
+
Data extends UnresolvedNetworkSpecificData = UnresolvedNetworkSpecificData,
|
|
170
|
+
Deployments extends UnknownDeployments = UnknownDeployments,
|
|
171
|
+
>(
|
|
172
|
+
userConfig: ResolvedUserConfig<NamedAccounts, Data>,
|
|
173
|
+
resolvedExecutionParams: ResolvedExecutionParams,
|
|
174
|
+
deploymentStore: DeploymentStore,
|
|
175
|
+
): Promise<{internal: InternalEnvironment; external: Environment<NamedAccounts, Data, Deployments>}> {
|
|
176
|
+
const rawProvider = resolvedExecutionParams.provider;
|
|
177
|
+
|
|
178
|
+
const provider: TransactionHashTracker = new TransactionHashTrackerProvider(rawProvider);
|
|
179
|
+
|
|
180
|
+
const chainIdHex = await provider.request({method: 'eth_chainId'});
|
|
181
|
+
const chainId = '' + Number(chainIdHex);
|
|
182
|
+
let genesisHash: `0x${string}` | undefined;
|
|
183
|
+
try {
|
|
184
|
+
let genesisBlock: EIP1193Block | EIP1193BlockWithTransactions | null;
|
|
185
|
+
try {
|
|
186
|
+
genesisBlock = await provider.request({method: 'eth_getBlockByNumber', params: ['earliest', false]});
|
|
187
|
+
} catch {
|
|
188
|
+
genesisBlock = await provider.request({method: 'eth_getBlockByNumber', params: ['0x0', false]});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!genesisBlock) {
|
|
192
|
+
console.error(`failed to get genesis block, returned null`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
genesisHash = genesisBlock?.hash;
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error(`failed to get genesis block`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const deploymentsFolder = userConfig.deployments;
|
|
201
|
+
const environmentName = resolvedExecutionParams.environment.name;
|
|
202
|
+
const saveDeployments = resolvedExecutionParams.saveDeployments;
|
|
203
|
+
let networkTags: {[tag: string]: boolean} = {};
|
|
204
|
+
for (const networkTag of resolvedExecutionParams.environment.tags) {
|
|
205
|
+
networkTags[networkTag] = true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const resolvedAccounts: {[name: string]: ResolvedAccount} = {};
|
|
209
|
+
|
|
210
|
+
const allRemoteAccounts = await provider.request({method: 'eth_accounts'});
|
|
211
|
+
const accountCache: {[name: string]: ResolvedAccount} = {};
|
|
212
|
+
|
|
213
|
+
async function getAccount(
|
|
214
|
+
name: string,
|
|
215
|
+
accounts: UnresolvedUnknownNamedAccounts,
|
|
216
|
+
accountDef: AccountType,
|
|
217
|
+
): Promise<ResolvedAccount | undefined> {
|
|
218
|
+
if (accountCache[name]) {
|
|
219
|
+
return accountCache[name];
|
|
220
|
+
}
|
|
221
|
+
let account: ResolvedAccount | undefined;
|
|
222
|
+
if (typeof accountDef === 'number') {
|
|
223
|
+
const accountPerIndex = allRemoteAccounts[accountDef];
|
|
224
|
+
if (accountPerIndex) {
|
|
225
|
+
accountCache[name] = account = {
|
|
226
|
+
type: 'remote',
|
|
227
|
+
address: accountPerIndex,
|
|
228
|
+
signer: provider,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
} else if (typeof accountDef === 'string') {
|
|
232
|
+
if (accountDef.startsWith('0x')) {
|
|
233
|
+
if (accountDef.length === 66) {
|
|
234
|
+
const privateKeyProtocol = userConfig.signerProtocols?.['privateKey'];
|
|
235
|
+
if (privateKeyProtocol) {
|
|
236
|
+
const namedSigner = await privateKeyProtocol(`privateKey:${accountDef}`);
|
|
237
|
+
const [address] = await namedSigner.signer.request({method: 'eth_accounts'});
|
|
238
|
+
accountCache[name] = account = {
|
|
239
|
+
...namedSigner,
|
|
240
|
+
address,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
accountCache[name] = account = {
|
|
245
|
+
type: 'remote',
|
|
246
|
+
address: accountDef as `0x${string}`,
|
|
247
|
+
signer: provider,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
if (accountDef.indexOf(':') > 0) {
|
|
252
|
+
const [protocolID, extra] = accountDef.split(':');
|
|
253
|
+
const protocol = userConfig.signerProtocols?.[protocolID];
|
|
254
|
+
if (!protocol) {
|
|
255
|
+
throw new Error(`protocol: ${protocolID} is not supported`);
|
|
256
|
+
}
|
|
257
|
+
const namedSigner = await protocol(accountDef);
|
|
258
|
+
const [address] = await namedSigner.signer.request({method: 'eth_accounts'});
|
|
259
|
+
accountCache[name] = account = {
|
|
260
|
+
...namedSigner,
|
|
261
|
+
address,
|
|
262
|
+
};
|
|
263
|
+
} else {
|
|
264
|
+
const accountFetched = await getAccount(name, accounts, accounts[accountDef]);
|
|
265
|
+
if (accountFetched) {
|
|
266
|
+
accountCache[name] = account = accountFetched;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// TODO allow for canonical chain name ?
|
|
272
|
+
const accountForNetwork = accountDef[environmentName] || accountDef[chainId] || accountDef['default'];
|
|
273
|
+
if (typeof accountForNetwork !== undefined) {
|
|
274
|
+
const accountFetched = await getAccount(name, accounts, accountForNetwork);
|
|
275
|
+
if (accountFetched) {
|
|
276
|
+
accountCache[name] = account = accountFetched;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return account;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (userConfig.accounts) {
|
|
285
|
+
const accountNames = Object.keys(userConfig.accounts);
|
|
286
|
+
for (const accountName of accountNames) {
|
|
287
|
+
const account = await getAccount(accountName, userConfig.accounts, userConfig.accounts[accountName]);
|
|
288
|
+
if (!account) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`cannot get account for ${accountName} = ${JSON.stringify(
|
|
291
|
+
userConfig.accounts[accountName],
|
|
292
|
+
null,
|
|
293
|
+
2,
|
|
294
|
+
)}\nEnsure your provider (or hardhat) has some accounts set up for ${environmentName}\n`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
(resolvedAccounts as any)[accountName] = account;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const resolvedData: ResolvedNetworkSpecificData<Data> = {} as ResolvedNetworkSpecificData<Data>;
|
|
302
|
+
async function getData<T = unknown>(name: string, dataDef: DataType<T>): Promise<T | undefined> {
|
|
303
|
+
const dataForNetwork = dataDef[environmentName] || dataDef[chainId] || dataDef['default'];
|
|
304
|
+
return dataForNetwork;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (userConfig.data) {
|
|
308
|
+
logger.debug(`getting data for env = ${environmentName}, chainId = ${chainId}`);
|
|
309
|
+
const dataFields = Object.keys(userConfig.data);
|
|
310
|
+
for (const dataField of dataFields) {
|
|
311
|
+
let fieldData = await getData(dataField, userConfig.data[dataField]);
|
|
312
|
+
(resolvedData as any)[dataField] = fieldData;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const context = {
|
|
317
|
+
accounts: resolvedAccounts,
|
|
318
|
+
data: resolvedData,
|
|
319
|
+
fork: resolvedExecutionParams.environment.fork,
|
|
320
|
+
saveDeployments,
|
|
321
|
+
tags: networkTags,
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const {deployments, migrations} = await loadDeployments(
|
|
325
|
+
deploymentStore,
|
|
326
|
+
deploymentsFolder,
|
|
327
|
+
environmentName,
|
|
328
|
+
false,
|
|
329
|
+
context.fork
|
|
330
|
+
? undefined
|
|
331
|
+
: {
|
|
332
|
+
chainId,
|
|
333
|
+
genesisHash,
|
|
334
|
+
deleteDeploymentsIfDifferentGenesisHash: true,
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const namedAccounts: {[name: string]: EIP1193Account} = {};
|
|
339
|
+
const namedSigners: {[name: string]: Signer} = {};
|
|
340
|
+
const addressSigners: {[name: `0x${string}`]: Signer} = {};
|
|
341
|
+
|
|
342
|
+
for (const entry of Object.entries(resolvedAccounts)) {
|
|
343
|
+
const name = entry[0];
|
|
344
|
+
const {address, ...namedSigner} = entry[1];
|
|
345
|
+
namedAccounts[name] = address;
|
|
346
|
+
addressSigners[address] = namedSigner;
|
|
347
|
+
namedSigners[name] = namedSigner;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const unnamedAccounts = allRemoteAccounts.filter((v) => !addressSigners[v]);
|
|
351
|
+
for (const account of unnamedAccounts) {
|
|
352
|
+
addressSigners[account] = {
|
|
353
|
+
type: 'remote',
|
|
354
|
+
signer: provider,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const perliminaryEnvironment = {
|
|
359
|
+
context: {
|
|
360
|
+
saveDeployments: context.saveDeployments,
|
|
361
|
+
},
|
|
362
|
+
name: environmentName,
|
|
363
|
+
tags: context.tags,
|
|
364
|
+
deployments: deployments as Deployments,
|
|
365
|
+
namedAccounts: namedAccounts as ResolvedNamedAccounts<NamedAccounts>,
|
|
366
|
+
data: resolvedData,
|
|
367
|
+
namedSigners: namedSigners as ResolvedNamedSigners<ResolvedNamedAccounts<NamedAccounts>>,
|
|
368
|
+
unnamedAccounts,
|
|
369
|
+
addressSigners: addressSigners,
|
|
370
|
+
network: {
|
|
371
|
+
chain: resolvedExecutionParams.chain,
|
|
372
|
+
fork: context.fork,
|
|
373
|
+
provider,
|
|
374
|
+
deterministicDeployment: resolvedExecutionParams.environment.deterministicDeployment,
|
|
375
|
+
|
|
376
|
+
// for backward compatibility
|
|
377
|
+
tags: context.tags,
|
|
378
|
+
},
|
|
379
|
+
extra: resolvedExecutionParams.extra || {},
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// const signer = {
|
|
383
|
+
// async sendTransaction(
|
|
384
|
+
// provider: EIP1193ProviderWithoutEvents,
|
|
385
|
+
// account: {
|
|
386
|
+
// addresss: EIP1193Account;
|
|
387
|
+
// config: unknown;
|
|
388
|
+
// },
|
|
389
|
+
// transaction: EIP1193TransactionEIP1193DATA
|
|
390
|
+
// ): Promise<EIP1193DATA> {
|
|
391
|
+
// return '0x';
|
|
392
|
+
// },
|
|
393
|
+
// };
|
|
394
|
+
|
|
395
|
+
// async function sendTransaction(transaction: EIP1193TransactionEIP1193DATA): Promise<EIP1193DATA> {
|
|
396
|
+
// return '0x';
|
|
397
|
+
// }
|
|
398
|
+
|
|
399
|
+
function get<TAbi extends Abi>(name: string): Deployment<TAbi> {
|
|
400
|
+
const deployment = deployments[name] as Deployment<TAbi>;
|
|
401
|
+
if (!deployment) {
|
|
402
|
+
throw new Error(`no deployment named "${name}" found.`);
|
|
403
|
+
}
|
|
404
|
+
return deployment;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function getOrNull<TAbi extends Abi>(name: string): Deployment<TAbi> | null {
|
|
408
|
+
return (deployments[name] || null) as Deployment<TAbi> | null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function hasMigrationBeenDone(id: string): boolean {
|
|
412
|
+
return migrations[id] ? true : false;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function recordMigration(id: string): void {
|
|
416
|
+
migrations[id] = Math.floor(Date.now() / 1000);
|
|
417
|
+
if (context.saveDeployments) {
|
|
418
|
+
deploymentStore.writeFileWithChainInfo(
|
|
419
|
+
{chainId, genesisHash},
|
|
420
|
+
deploymentsFolder,
|
|
421
|
+
environmentName,
|
|
422
|
+
'.migrations.json',
|
|
423
|
+
JSON.stringify(migrations),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function fromAddressToNamedABIOrNull<TAbi extends Abi>(address: Address): {mergedABI: TAbi; names: string[]} | null {
|
|
429
|
+
let list: {name: string; artifact: Artifact<Abi>}[] = [];
|
|
430
|
+
for (const name of Object.keys(deployments)) {
|
|
431
|
+
const deployment = deployments[name];
|
|
432
|
+
if (deployment.address.toLowerCase() == address.toLowerCase()) {
|
|
433
|
+
list.push({name, artifact: deployment});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (list.length === 0) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const {mergedABI} = mergeArtifacts(list);
|
|
441
|
+
return {
|
|
442
|
+
mergedABI: mergedABI as unknown as TAbi,
|
|
443
|
+
names: list.map((v) => v.name),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function fromAddressToNamedABI<TAbi extends Abi>(address: Address): {mergedABI: TAbi; names: string[]} {
|
|
448
|
+
const n = fromAddressToNamedABIOrNull<TAbi>(address);
|
|
449
|
+
if (!n) {
|
|
450
|
+
throw new Error(`could not find artifact for address ${address}`);
|
|
451
|
+
}
|
|
452
|
+
return n;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function save<TAbi extends Abi>(
|
|
456
|
+
name: string,
|
|
457
|
+
deployment: Deployment<TAbi>,
|
|
458
|
+
options?: {doNotCountAsNewDeployment?: boolean},
|
|
459
|
+
): Promise<Deployment<TAbi>> {
|
|
460
|
+
if (!options?.doNotCountAsNewDeployment) {
|
|
461
|
+
let numDeployments = 1;
|
|
462
|
+
const oldDeployment = deployments[name];
|
|
463
|
+
if (oldDeployment) {
|
|
464
|
+
numDeployments = (oldDeployment.numDeployments || 1) + 1;
|
|
465
|
+
}
|
|
466
|
+
deployments[name] = {...deployment, numDeployments};
|
|
467
|
+
} else {
|
|
468
|
+
deployments[name] = {...deployment, numDeployments: 1};
|
|
469
|
+
}
|
|
470
|
+
if (context.saveDeployments) {
|
|
471
|
+
deploymentStore.writeFileWithChainInfo(
|
|
472
|
+
{chainId, genesisHash},
|
|
473
|
+
deploymentsFolder,
|
|
474
|
+
environmentName,
|
|
475
|
+
`${name}.json`,
|
|
476
|
+
JSONToString(deployment, 2),
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return deployment;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function recoverTransactionsIfAny(): Promise<void> {
|
|
483
|
+
if (!context.saveDeployments) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
let existingPendingTansactions: PendingTransaction[];
|
|
487
|
+
try {
|
|
488
|
+
existingPendingTansactions = stringToJSON(
|
|
489
|
+
await deploymentStore.readFile(deploymentsFolder, environmentName, '.pending_transactions.json'),
|
|
490
|
+
);
|
|
491
|
+
} catch {
|
|
492
|
+
existingPendingTansactions = [];
|
|
493
|
+
}
|
|
494
|
+
if (existingPendingTansactions.length > 0) {
|
|
495
|
+
while (existingPendingTansactions.length > 0) {
|
|
496
|
+
const pendingTransaction = existingPendingTansactions.shift();
|
|
497
|
+
if (pendingTransaction) {
|
|
498
|
+
if (pendingTransaction.type === 'deployment') {
|
|
499
|
+
const spinner = spin(
|
|
500
|
+
`recovering ${pendingTransaction.name} with transaction ${pendingTransaction.transaction.hash}`,
|
|
501
|
+
);
|
|
502
|
+
try {
|
|
503
|
+
await waitForDeploymentTransactionAndSave(pendingTransaction);
|
|
504
|
+
await deploymentStore.writeFileWithChainInfo(
|
|
505
|
+
{chainId, genesisHash},
|
|
506
|
+
deploymentsFolder,
|
|
507
|
+
environmentName,
|
|
508
|
+
'.pending_transactions.json',
|
|
509
|
+
JSONToString(existingPendingTansactions, 2),
|
|
510
|
+
);
|
|
511
|
+
spinner.succeed();
|
|
512
|
+
} catch (e) {
|
|
513
|
+
spinner.fail();
|
|
514
|
+
throw e;
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
const spinner = spin(`recovering execution's transaction ${pendingTransaction.transaction.hash}`);
|
|
518
|
+
const transaction = await provider.request({
|
|
519
|
+
method: 'eth_getTransactionByHash',
|
|
520
|
+
params: [pendingTransaction.transaction.hash],
|
|
521
|
+
});
|
|
522
|
+
try {
|
|
523
|
+
await waitForTransaction(pendingTransaction.transaction.hash, {
|
|
524
|
+
transaction: transaction,
|
|
525
|
+
message: ` tx: {hash}\n {transaction}`,
|
|
526
|
+
});
|
|
527
|
+
await deploymentStore.writeFileWithChainInfo(
|
|
528
|
+
{chainId, genesisHash},
|
|
529
|
+
deploymentsFolder,
|
|
530
|
+
environmentName,
|
|
531
|
+
'.pending_transactions.json',
|
|
532
|
+
JSONToString(existingPendingTansactions, 2),
|
|
533
|
+
);
|
|
534
|
+
spinner.succeed();
|
|
535
|
+
} catch (e) {
|
|
536
|
+
spinner.fail();
|
|
537
|
+
throw e;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
await deploymentStore.deleteFile(deploymentsFolder, environmentName, '.pending_transactions.json');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function savePendingTransaction(pendingTransaction: PendingTransaction) {
|
|
547
|
+
if (context.saveDeployments) {
|
|
548
|
+
let existingPendinTransactions: PendingTransaction[];
|
|
549
|
+
try {
|
|
550
|
+
existingPendinTransactions = stringToJSON(
|
|
551
|
+
await deploymentStore.readFile(deploymentsFolder, environmentName, '.pending_transactions.json'),
|
|
552
|
+
);
|
|
553
|
+
} catch {
|
|
554
|
+
existingPendinTransactions = [];
|
|
555
|
+
}
|
|
556
|
+
existingPendinTransactions.push(pendingTransaction);
|
|
557
|
+
await deploymentStore.writeFileWithChainInfo(
|
|
558
|
+
{chainId, genesisHash},
|
|
559
|
+
deploymentsFolder,
|
|
560
|
+
environmentName,
|
|
561
|
+
'.pending_transactions.json',
|
|
562
|
+
JSONToString(existingPendinTransactions, 2),
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
return deployments;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function waitForTransactionReceipt(params: {
|
|
569
|
+
hash: EIP1193DATA;
|
|
570
|
+
// confirmations?: number; // TODO
|
|
571
|
+
// timeout?: number; // TODO
|
|
572
|
+
}): Promise<EIP1193TransactionReceipt> {
|
|
573
|
+
const {hash, pollingInterval} = {pollingInterval: resolvedExecutionParams.pollingInterval, ...params};
|
|
574
|
+
|
|
575
|
+
let receipt: EIP1193TransactionReceipt | null = null;
|
|
576
|
+
try {
|
|
577
|
+
receipt = await provider.request({
|
|
578
|
+
method: 'eth_getTransactionReceipt',
|
|
579
|
+
params: [hash],
|
|
580
|
+
});
|
|
581
|
+
} catch (err) {}
|
|
582
|
+
|
|
583
|
+
if (!receipt || !receipt.blockHash) {
|
|
584
|
+
await wait(pollingInterval);
|
|
585
|
+
return waitForTransactionReceipt(params);
|
|
586
|
+
}
|
|
587
|
+
return receipt;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function deleteTransaction<TAbi extends Abi = Abi>(hash: string) {
|
|
591
|
+
if (context.saveDeployments) {
|
|
592
|
+
let existingPendinTransactions: PendingTransaction[];
|
|
593
|
+
try {
|
|
594
|
+
existingPendinTransactions = stringToJSON(
|
|
595
|
+
await deploymentStore.readFile(deploymentsFolder, environmentName, '.pending_transactions.json'),
|
|
596
|
+
);
|
|
597
|
+
} catch {
|
|
598
|
+
existingPendinTransactions = [];
|
|
599
|
+
}
|
|
600
|
+
existingPendinTransactions = existingPendinTransactions.filter((v) => v.transaction.hash !== hash);
|
|
601
|
+
if (existingPendinTransactions.length === 0) {
|
|
602
|
+
await deploymentStore.deleteFile(deploymentsFolder, environmentName, '.pending_transactions.json');
|
|
603
|
+
} else {
|
|
604
|
+
await deploymentStore.writeFileWithChainInfo(
|
|
605
|
+
{chainId, genesisHash},
|
|
606
|
+
deploymentsFolder,
|
|
607
|
+
environmentName,
|
|
608
|
+
'.pending_transactions.json',
|
|
609
|
+
JSONToString(existingPendinTransactions, 2),
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function waitForTransaction(
|
|
616
|
+
hash: `0x${string}`,
|
|
617
|
+
info?: {message?: string; transaction?: EIP1193Transaction | null},
|
|
618
|
+
): Promise<EIP1193TransactionReceipt> {
|
|
619
|
+
let message = ` - Broadcasting tx:\n ${hash}${
|
|
620
|
+
info?.transaction ? `\n ${displayTransaction(info?.transaction)}` : ''
|
|
621
|
+
}`;
|
|
622
|
+
if (info?.message) {
|
|
623
|
+
message = info.message.replaceAll('{hash}', hash);
|
|
624
|
+
if (info?.transaction) {
|
|
625
|
+
message = message.replaceAll('{transaction}', displayTransaction(info.transaction));
|
|
626
|
+
} else {
|
|
627
|
+
message = message.replaceAll('{transaction}', '(tx not found)');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const spinner = spin(message);
|
|
631
|
+
let receipt: EIP1193TransactionReceipt;
|
|
632
|
+
try {
|
|
633
|
+
receipt = await waitForTransactionReceipt({
|
|
634
|
+
hash,
|
|
635
|
+
});
|
|
636
|
+
} catch (e) {
|
|
637
|
+
spinner.fail();
|
|
638
|
+
throw e;
|
|
639
|
+
}
|
|
640
|
+
if (!receipt) {
|
|
641
|
+
throw new Error(`receipt for ${hash} not found`);
|
|
642
|
+
} else {
|
|
643
|
+
spinner.succeed();
|
|
644
|
+
}
|
|
645
|
+
return receipt;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function waitForDeploymentTransactionAndSave<TAbi extends Abi = Abi>(
|
|
649
|
+
pendingDeployment: PendingDeployment<TAbi>,
|
|
650
|
+
info?: {message?: string; transaction?: EIP1193Transaction | null},
|
|
651
|
+
): Promise<Deployment<TAbi>> {
|
|
652
|
+
const nameToDisplay = pendingDeployment.name || '<no name>';
|
|
653
|
+
let message = ` - Deploying ${nameToDisplay} with tx:\n {hash}\n {transaction}`;
|
|
654
|
+
if (info?.message) {
|
|
655
|
+
message = info.message.replaceAll('{name}', nameToDisplay);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const receipt = await waitForTransaction(pendingDeployment.transaction.hash, {
|
|
659
|
+
transaction: info?.transaction,
|
|
660
|
+
message,
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// TODO we could make pendingDeployment.expectedAddress a spec for fetching address from event too
|
|
664
|
+
const contractAddress = pendingDeployment.expectedAddress || receipt.contractAddress;
|
|
665
|
+
if (!contractAddress) {
|
|
666
|
+
console.error(receipt);
|
|
667
|
+
throw new Error(`no contract address found for ${nameToDisplay}`);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
showMessage(` => ${contractAddress}`);
|
|
671
|
+
|
|
672
|
+
const {abi, ...artifactObjectWithoutABI} = pendingDeployment.partialDeployment;
|
|
673
|
+
|
|
674
|
+
if (!pendingDeployment.transaction.nonce) {
|
|
675
|
+
// const spinner = spin(`fetching nonce for ${pendingDeployment.transaction.hash}`);
|
|
676
|
+
let transaction: EIP1193Transaction | null = null;
|
|
677
|
+
try {
|
|
678
|
+
transaction = await provider.request({
|
|
679
|
+
method: 'eth_getTransactionByHash',
|
|
680
|
+
params: [pendingDeployment.transaction.hash],
|
|
681
|
+
});
|
|
682
|
+
} catch (e) {
|
|
683
|
+
// spinner.fail(`failed to get transaction, even after receipt was found`);
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
if (!transaction) {
|
|
687
|
+
// spinner.fail(`tx ${pendingDeployment.transaction.hash} not found, even after receipt was found`);
|
|
688
|
+
// or : spinner.stop();
|
|
689
|
+
} else {
|
|
690
|
+
// spinner.stop();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (transaction) {
|
|
694
|
+
pendingDeployment.transaction = {
|
|
695
|
+
nonce: transaction.nonce,
|
|
696
|
+
hash: transaction.hash,
|
|
697
|
+
origin: transaction.from,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// TODO options
|
|
703
|
+
for (const key of Object.keys(artifactObjectWithoutABI)) {
|
|
704
|
+
if (key.startsWith('_')) {
|
|
705
|
+
delete (artifactObjectWithoutABI as any)[key];
|
|
706
|
+
}
|
|
707
|
+
if (key === 'evm') {
|
|
708
|
+
if (artifactObjectWithoutABI.evm) {
|
|
709
|
+
if ('gasEstimates' in artifactObjectWithoutABI['evm']) {
|
|
710
|
+
const {gasEstimates} = artifactObjectWithoutABI.evm;
|
|
711
|
+
artifactObjectWithoutABI.evm = {
|
|
712
|
+
gasEstimates,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const deployment = {
|
|
720
|
+
address: contractAddress,
|
|
721
|
+
abi,
|
|
722
|
+
...artifactObjectWithoutABI,
|
|
723
|
+
transaction: pendingDeployment.transaction,
|
|
724
|
+
receipt: {
|
|
725
|
+
blockHash: receipt.blockHash,
|
|
726
|
+
blockNumber: receipt.blockNumber,
|
|
727
|
+
transactionIndex: receipt.transactionIndex,
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
if (pendingDeployment.name) {
|
|
731
|
+
return save(pendingDeployment.name, deployment);
|
|
732
|
+
} else {
|
|
733
|
+
return deployment;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async function savePendingExecution(pendingExecution: PendingExecution, msg?: string) {
|
|
738
|
+
await savePendingTransaction(pendingExecution);
|
|
739
|
+
let transaction: EIP1193Transaction | null = null;
|
|
740
|
+
const spinner = spin(); // TODO spin(`fetching tx from peers ${pendingDeployment.txHash}`);
|
|
741
|
+
try {
|
|
742
|
+
transaction = await provider.request({
|
|
743
|
+
method: 'eth_getTransactionByHash',
|
|
744
|
+
params: [pendingExecution.transaction.hash],
|
|
745
|
+
});
|
|
746
|
+
} catch (e) {
|
|
747
|
+
spinner.fail();
|
|
748
|
+
throw e;
|
|
749
|
+
}
|
|
750
|
+
if (!transaction) {
|
|
751
|
+
// spinner.fail(`execution tx ${pendingExecution.transaction.hash} not found in the mempool yet`);
|
|
752
|
+
spinner.stop();
|
|
753
|
+
} else {
|
|
754
|
+
spinner.stop();
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (transaction) {
|
|
758
|
+
pendingExecution.transaction.nonce = transaction.nonce;
|
|
759
|
+
pendingExecution.transaction.origin = transaction.from;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const receipt = await waitForTransaction(pendingExecution.transaction.hash, {transaction, message: msg});
|
|
763
|
+
|
|
764
|
+
await deleteTransaction(pendingExecution.transaction.hash);
|
|
765
|
+
return receipt;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function savePendingDeployment<TAbi extends Abi = Abi>(
|
|
769
|
+
pendingDeployment: PendingDeployment<TAbi>,
|
|
770
|
+
msg?: string,
|
|
771
|
+
) {
|
|
772
|
+
await savePendingTransaction(pendingDeployment);
|
|
773
|
+
let transaction: EIP1193Transaction | null = null;
|
|
774
|
+
const spinner = spin(); // TODO spin(`fetching tx from peers ${pendingDeployment.txHash}`);
|
|
775
|
+
try {
|
|
776
|
+
transaction = await provider.request({
|
|
777
|
+
method: 'eth_getTransactionByHash',
|
|
778
|
+
params: [pendingDeployment.transaction.hash],
|
|
779
|
+
});
|
|
780
|
+
} catch (e) {
|
|
781
|
+
spinner.fail(`failed to fetch tx ${pendingDeployment.transaction.hash}. Can't know its status`);
|
|
782
|
+
throw e;
|
|
783
|
+
}
|
|
784
|
+
if (!transaction) {
|
|
785
|
+
// spinner.fail(`deployment tx ${pendingDeployment.transaction.hash} not found in the mempool yet`);
|
|
786
|
+
spinner.stop();
|
|
787
|
+
} else {
|
|
788
|
+
spinner.stop();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (transaction) {
|
|
792
|
+
// we update the tx data with the one we get from the network
|
|
793
|
+
pendingDeployment = {
|
|
794
|
+
...pendingDeployment,
|
|
795
|
+
transaction: {hash: transaction.hash, nonce: transaction.nonce, origin: transaction.from},
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const deployment = await waitForDeploymentTransactionAndSave<TAbi>(pendingDeployment, {transaction, message: msg});
|
|
800
|
+
await deleteTransaction(pendingDeployment.transaction.hash);
|
|
801
|
+
return deployment;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function showMessage(message: string) {
|
|
805
|
+
logger.log(message);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function showProgress(message?: string): ProgressIndicator {
|
|
809
|
+
return spin(message);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
let env: Environment<NamedAccounts, Data, Deployments> = {
|
|
813
|
+
...perliminaryEnvironment,
|
|
814
|
+
save,
|
|
815
|
+
savePendingDeployment,
|
|
816
|
+
savePendingExecution,
|
|
817
|
+
get,
|
|
818
|
+
getOrNull,
|
|
819
|
+
fromAddressToNamedABI,
|
|
820
|
+
fromAddressToNamedABIOrNull,
|
|
821
|
+
showMessage,
|
|
822
|
+
showProgress,
|
|
823
|
+
hasMigrationBeenDone,
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
external: env,
|
|
828
|
+
internal: {
|
|
829
|
+
recoverTransactionsIfAny,
|
|
830
|
+
recordMigration,
|
|
831
|
+
},
|
|
832
|
+
};
|
|
833
|
+
}
|