dequanto 0.2.36 → 0.2.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/hardhat.config.js +10 -1
  2. package/lib/cjs/contracts/deploy/Deployments.js +33 -6
  3. package/lib/cjs/contracts/deploy/Deployments.js.map +1 -1
  4. package/lib/cjs/contracts/deploy/proxy/ProxyDeployment.js +37 -4
  5. package/lib/cjs/contracts/deploy/proxy/ProxyDeployment.js.map +1 -1
  6. package/lib/cjs/gen/Generator.js +15 -0
  7. package/lib/cjs/gen/Generator.js.map +1 -1
  8. package/lib/cjs/utils/$crypto.js +1 -1
  9. package/lib/cjs/utils/$crypto.js.map +1 -1
  10. package/lib/cjs/utils/$require.js +12 -5
  11. package/lib/cjs/utils/$require.js.map +1 -1
  12. package/lib/esm/contracts/deploy/Deployments.js.map +1 -1
  13. package/lib/esm/contracts/deploy/Deployments.mjs +33 -6
  14. package/lib/esm/contracts/deploy/proxy/ProxyDeployment.js.map +1 -1
  15. package/lib/esm/contracts/deploy/proxy/ProxyDeployment.mjs +37 -4
  16. package/lib/esm/gen/Generator.js.map +1 -1
  17. package/lib/esm/gen/Generator.mjs +15 -0
  18. package/lib/esm/utils/$crypto.js.map +1 -1
  19. package/lib/esm/utils/$crypto.mjs +1 -1
  20. package/lib/esm/utils/$require.js.map +1 -1
  21. package/lib/esm/utils/$require.mjs +12 -5
  22. package/lib/types/contracts/deploy/Deployments.d.ts +16 -10
  23. package/lib/types/contracts/deploy/proxy/ProxyDeployment.d.ts +2 -0
  24. package/lib/types/gen/Generator.d.ts +8 -0
  25. package/lib/types/utils/$require.d.ts +3 -1
  26. package/package.json +1 -1
  27. package/src/contracts/deploy/Deployments.ts +62 -18
  28. package/src/contracts/deploy/proxy/ProxyDeployment.ts +61 -16
  29. package/src/gen/Generator.ts +17 -0
  30. package/src/utils/$crypto.ts +1 -1
  31. package/src/utils/$require.ts +11 -5
@@ -7,8 +7,7 @@ import { TEth } from '@dequanto/models/TEth';
7
7
  import { $abiUtils } from '@dequanto/utils/$abiUtils';
8
8
  import { $contract } from '@dequanto/utils/$contract';
9
9
  import { $require } from '@dequanto/utils/$require';
10
- import { ParametersFromSecond } from '@dequanto/utils/types';
11
- import { Constructor } from '@dequanto/utils/types';
10
+ import { Constructor, ParametersFromSecond } from '@dequanto/utils/types';
12
11
 
13
12
  import { BlockchainExplorerFactory } from '@dequanto/explorer/BlockchainExplorerFactory';
14
13
  import { ContractVerifier } from '@dequanto/explorer/ContractVerifier';
@@ -16,13 +15,12 @@ import { HardhatWeb3Client } from '@dequanto/hardhat/HardhatWeb3Client';
16
15
  import { LoggerService } from '@dequanto/loggers/LoggerService';
17
16
  import { $is } from '@dequanto/utils/$is';
18
17
 
19
- import { IBeacon, IBeaconProxy, IProxy, IProxyAdmin, ProxyDeployment } from './proxy/ProxyDeployment';
20
- import { DeploymentsStorage, IDeployment } from './storage/DeploymentsStorage';
18
+ import { $bytecode } from '@dequanto/evm/utils/$bytecode';
21
19
  import { TAddress } from '@dequanto/models/TAddress';
22
- import { $promise } from '@dequanto/utils/$promise';
23
20
  import { l } from '@dequanto/utils/$logger';
24
- import { $bytecode } from '@dequanto/evm/utils/$bytecode';
25
- import { THex } from '@dequanto/models/THex';
21
+ import { $promise } from '@dequanto/utils/$promise';
22
+ import { IBeacon, IBeaconProxy, IProxy, IProxyAdmin, ProxyDeployment } from './proxy/ProxyDeployment';
23
+ import { DeploymentsStorage, IDeployment } from './storage/DeploymentsStorage';
26
24
 
27
25
 
28
26
 
@@ -288,13 +286,11 @@ export class Deployments {
288
286
  }
289
287
 
290
288
  async ensureWithProxy<
291
- T extends (TContract & { initialize?: TInit }),
292
- TInit extends TInitializer
289
+ T extends (TContract & TInitializers<TInit>),
290
+ TInit extends TFunction
293
291
  >(
294
292
  CtorImpl: Constructor<T>,
295
- opts?: TConstructorArgs<T> & TDeploymentOptions & {
296
- initialize?: ParametersFromSecond<T['initialize']>
297
- }
293
+ opts?: TConstructorArgs<T> & TDeploymentOptions & TInitializerParams<T>
298
294
  ): Promise<{
299
295
  // the Implementation Contract with the address set to Proxy
300
296
  contract: T
@@ -322,7 +318,8 @@ export class Deployments {
322
318
  deployment: opts?.deployment
323
319
  });
324
320
 
325
- let data = serializeInitData(id, contractImpl, opts.initialize);
321
+ let initData = serializeInitData(id, contractImpl, opts.initialize);
322
+ let { migrationData, migrationV } = serializeMigrationData(id, contractImpl, opts);
326
323
  let implementationAddress = contractImplDeployment.implementation ?? contractImplDeployment.address;
327
324
 
328
325
  let {
@@ -337,7 +334,9 @@ export class Deployments {
337
334
  deployments: this,
338
335
  implementation: {
339
336
  address: implementationAddress,
340
- initData: data
337
+ initData,
338
+ migrationData,
339
+ migrationV,
341
340
  },
342
341
  upgradeImplementation: opts.deployment?.upgradeProxy ?? this.opts?.whenUpgradeRequired !== 'ignore'
343
342
  })
@@ -370,7 +369,7 @@ export class Deployments {
370
369
  **/
371
370
  async ensureWithBeacon<
372
371
  T extends (TContract & { initialize?: TInit }),
373
- TInit extends TInitializer
372
+ TInit extends TFunction
374
373
  >(
375
374
  CtorImpl: Constructor<T>,
376
375
  opts: TConstructorArgs<T> & TDeploymentOptions & {
@@ -426,7 +425,9 @@ export class Deployments {
426
425
  deployments: this,
427
426
  implementation: {
428
427
  address: implementationAddress,
429
- initData: data
428
+ initData: data,
429
+ // @TODO implement migrations for Beacons
430
+ migrationData: null
430
431
  }
431
432
  });
432
433
 
@@ -612,8 +613,26 @@ export class Deployments {
612
613
  }
613
614
 
614
615
 
615
- type TInitializer = (...args: any[]) => any
616
-
616
+ type TFunction = (...args: any[]) => any
617
+ type TInitializerName = 'initialize' | `initializeV${number}`
618
+ type TInitializers<TInit extends TFunction> = {
619
+ [K in TInitializerName]?: TInit
620
+ }
621
+ type TInitializerParams<T extends TInitializers<TFunction>> = {
622
+ initialize?: T['initialize'] extends TFunction
623
+ ? ParametersFromSecond<T['initialize']>
624
+ : never
625
+ initializeV2?: T['initializeV2'] extends TFunction
626
+ ? ParametersFromSecond<T['initializeV2']>
627
+ : never
628
+ initializeV3?: T['initializeV3'] extends TFunction
629
+ ? ParametersFromSecond<T['initializeV3']>
630
+ : never
631
+ } & {
632
+ [K in Extract<keyof T, `initializeV${number}`>]?: T[K] extends TFunction
633
+ ? ParametersFromSecond<T[K]>
634
+ : any
635
+ }
617
636
 
618
637
  type TContract = ContractBase & { $constructor?: (...args: any[]) => any }
619
638
  type TConstructorArgs<T extends TContract> = T['$constructor'] extends Function ? {
@@ -678,6 +697,31 @@ function serializeInitData(id: string, contract: ContractBase, initializeParams:
678
697
  }
679
698
 
680
699
 
700
+ function serializeMigrationData(id: string, contract: ContractBase, opts: any) {
701
+ let rgx = /^initializeV(?<version>[\d+])$/;
702
+ let migrations = contract
703
+ .abi
704
+ .map(x => rgx.exec(x.name))
705
+ .filter(x => x != null)
706
+ .map(match => Number(match.groups.version))
707
+ ;
708
+ if (migrations.length === 0) {
709
+ return { migrationData: null, migrationV: 1 };
710
+ }
711
+ let v = alot(migrations).max(x => x);
712
+ let key = `initializeV${v}`;
713
+ let migrationAbi = contract.abi.find(x => x.name === key);
714
+ let migrationParams = opts?.[key] ?? [];
715
+ if (migrationParams?.length !== migrationAbi.inputs.length) {
716
+ throw new Error(`Wrong number of arguments (${migrationParams?.length}) for initializer method (${migrationAbi.inputs.length}) in ${id}.`);
717
+ }
718
+ return {
719
+ migrationData: $abiUtils.serializeMethodCallData(migrationAbi, migrationParams ?? []),
720
+ migrationV: v
721
+ };
722
+ }
723
+
724
+
681
725
  /**
682
726
  * Normalize the contract name by removing any versions from name
683
727
  * "FooV1" is actually the "Foo" contract
@@ -15,6 +15,9 @@ import { $proxyDeploy } from './$proxyDeploy';
15
15
  import { File } from 'atma-io';
16
16
  import { IContractWrapped } from '@dequanto/contracts/ContractClassFactory';
17
17
  import { HardhatProvider } from '@dequanto/hardhat/HardhatProvider';
18
+ import { $hex } from '@dequanto/utils/$hex';
19
+ import { SlotsStorage } from '@dequanto/solidity/SlotsStorage';
20
+ import { SlotsParser } from '@dequanto/solidity/SlotsParser';
18
21
 
19
22
  export interface IProxy extends ContractBase {
20
23
  changeAdmin?
@@ -24,14 +27,14 @@ export interface IProxyAdmin extends ContractBase {
24
27
  }
25
28
 
26
29
  export interface IBeaconProxy extends ContractBase {
27
- $constructor (deployer: IAccount, beacon: TEth.Address, initData: TEth.Hex)
30
+ $constructor(deployer: IAccount, beacon: TEth.Address, initData: TEth.Hex)
28
31
  }
29
32
  export interface IBeacon extends ContractBase {
30
- $constructor (deployer: IAccount, implementation: TEth.Address, initialOwner?: TEth.Address)
33
+ $constructor(deployer: IAccount, implementation: TEth.Address, initialOwner?: TEth.Address)
31
34
 
32
35
 
33
36
  implementation(): Promise<TEth.Address>
34
- upgradeTo (sender: IAccount, newImplementation: TAddress)
37
+ upgradeTo(sender: IAccount, newImplementation: TAddress)
35
38
  }
36
39
 
37
40
  interface IDeploymentCtx {
@@ -42,6 +45,8 @@ interface IDeploymentCtx {
42
45
  implementation: {
43
46
  address: TAddress
44
47
  initData: TEth.Hex
48
+ migrationData: TEth.Hex
49
+ migrationV?: number
45
50
  }
46
51
  options?: {
47
52
  skipStorageLayoutCheck?: boolean
@@ -117,7 +122,9 @@ export class ProxyDeployment {
117
122
  } = deployments;
118
123
  let {
119
124
  address: implAddress,
120
- initData
125
+ initData,
126
+ migrationData,
127
+ migrationV,
121
128
  } = ctx.implementation;
122
129
  let {
123
130
  Proxy,
@@ -134,7 +141,7 @@ export class ProxyDeployment {
134
141
  $require.notNull(ProxyAdmin, 'TransparentProxy.ProxyAdmin is required');
135
142
 
136
143
 
137
- let proxyOpts = <Parameters<Deployments['ensure']>[1]> {
144
+ let proxyOpts = <Parameters<Deployments['ensure']>[1]>{
138
145
  id: proxyId,
139
146
  // will not compare the contract updates, once deployed. As proxies normally not updated
140
147
  latest: false,
@@ -150,8 +157,6 @@ export class ProxyDeployment {
150
157
  let v = proxyAbi.some(x => x.name === 'upgradeToAndCall') || !proxyAbi.some(x => x.type === 'error') ? 'V4' : 'V5';
151
158
  /** OpenZeppelin V5 hides admin/upgrade public methods and introduces "error" types*/
152
159
 
153
-
154
-
155
160
  let hasProxy = await deployments.has(Proxy, proxyOpts);
156
161
  let shouldUpdate = ctx.upgradeImplementation ?? true;
157
162
  let {
@@ -206,6 +211,13 @@ export class ProxyDeployment {
206
211
  if ($address.eq(address, implAddress) === false) {
207
212
  if (shouldUpdate) {
208
213
  await this.requireCompatibleStorageLayout(proxyId, ctx);
214
+ if ($hex.isEmpty(migrationData) === false) {
215
+ let version = await Interfaces.TransparentProxy[v].contractProxy.version(contractProxy);
216
+ if (migrationV <= version) {
217
+ // Clear migration call; This upgrade is raw implementation upgrade
218
+ migrationData = null;
219
+ }
220
+ }
209
221
  $logger.log(`Upgrading ProxyAdmin(${contractProxyAdmin.address}) to ${implAddress} (${v}) from ${address}`);
210
222
  let receipt = await Interfaces.call(
211
223
  ctx.owner ?? deployer,
@@ -213,7 +225,7 @@ export class ProxyDeployment {
213
225
  Interfaces.TransparentProxy[v].contractProxyAdmin.upgradeAndCall,
214
226
  contractProxy.address,
215
227
  implAddress,
216
- null // data
228
+ migrationData,
217
229
  );
218
230
  await this.saveStorageLayout(proxyId, ctx);
219
231
  } else {
@@ -232,7 +244,7 @@ export class ProxyDeployment {
232
244
  }
233
245
  }
234
246
 
235
- private async getOpenzeppelinUpgradable (opts?: { proxy?: boolean, beacon?: boolean }) {
247
+ private async getOpenzeppelinUpgradable(opts?: { proxy?: boolean, beacon?: boolean }) {
236
248
  // We can't compile OpenZeppelin's contracts directly from node_modules folder, so create the wrappers
237
249
  const baseSource = `./node_modules/@openzeppelin/contracts/proxy`;
238
250
  const baseOutput = `./contracts/oz`;
@@ -250,7 +262,7 @@ export class ProxyDeployment {
250
262
  import \"${deps.TransparentUpgradeableProxy}\";
251
263
  `,
252
264
  //install: `TransparentUpgradeableProxy,ProxyAdmin`,
253
- contracts: [`TransparentUpgradeableProxy`,`ProxyAdmin`]
265
+ contracts: [`TransparentUpgradeableProxy`, `ProxyAdmin`]
254
266
  },
255
267
  Beacon: {
256
268
  source: `${baseSource}/beacon/UpgradeableBeacon.sol`,
@@ -260,7 +272,7 @@ export class ProxyDeployment {
260
272
  import \"${deps.BeaconProxy}\";
261
273
  `,
262
274
  //install: `UpgradeableBeacon,BeaconProxy`,
263
- contracts: [`UpgradeableBeacon`,`BeaconProxy`],
275
+ contracts: [`UpgradeableBeacon`, `BeaconProxy`],
264
276
  }
265
277
  };
266
278
 
@@ -271,7 +283,7 @@ export class ProxyDeployment {
271
283
  delete paths.TransparentUpgradeableProxy;
272
284
  }
273
285
 
274
- function fmt (template: string) {
286
+ function fmt(template: string) {
275
287
  let match = /^ +/m.exec(template);
276
288
  return template.trim().replace(new RegExp(`^${match[0]}`, 'gm'), '');
277
289
  }
@@ -342,12 +354,12 @@ export class ProxyDeployment {
342
354
  ? [
343
355
  // address implementation
344
356
  implAddress
345
- ] as [ TEth.Address ]
357
+ ] as [TEth.Address]
346
358
  : [
347
359
  // address implementation_, address initialOwner
348
360
  implAddress,
349
361
  deployer.address
350
- ] as [ TEth.Address, TEth.Address ]
362
+ ] as [TEth.Address, TEth.Address]
351
363
  };
352
364
  let hasBeacon = await deployments.has(Beacon, beaconOpts);
353
365
  let {
@@ -428,7 +440,7 @@ export class ProxyDeployment {
428
440
  }
429
441
  }
430
442
 
431
- private getOzVersionByBeacon (Beacon: Constructor<IBeacon>): 5 | 4 {
443
+ private getOzVersionByBeacon(Beacon: Constructor<IBeacon>): 5 | 4 {
432
444
  let $constructor = new Beacon().abi?.find(x => x.type === 'constructor');
433
445
  $require.notNull($constructor, `Invalid Beacon contract: constructor not found`);
434
446
 
@@ -465,9 +477,31 @@ namespace Interfaces {
465
477
  export const V4 = {
466
478
  contractProxy: {
467
479
  changeAdmin: 'changeAdmin(address newAdmin) external',
480
+ async version(contract: ContractBase) {
481
+ const slots = await SlotsParser.slotsFromAbi(`
482
+ (uint8 _version, bool initializing)
483
+ `);
484
+ const storage = SlotsStorage.createWithClient(
485
+ contract.client,
486
+ contract.address,
487
+ slots
488
+ );
489
+ let v = await storage.get('_version');
490
+ return v;
491
+ },
468
492
  },
469
493
  contractProxyAdmin: {
470
494
  async upgradeAndCall(account, contract, proxyAddress, implementationAddress, data) {
495
+ if (data != null && $hex.isEmpty(data) === false) {
496
+ return call(
497
+ account,
498
+ contract,
499
+ 'upgradeAndCall(address proxy, address implementation, bytes data) external',
500
+ proxyAddress,
501
+ implementationAddress,
502
+ data,
503
+ );
504
+ }
471
505
  return call(
472
506
  account,
473
507
  contract,
@@ -480,7 +514,18 @@ namespace Interfaces {
480
514
  }
481
515
  export const V5 = {
482
516
  contractProxy: {
483
-
517
+ async version(contract: ContractBase) {
518
+ // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
519
+ const position = '0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00';
520
+ const slots = await SlotsParser.slotsFromAbi(`
521
+ (uint8 initialized, bool initializing)
522
+ `);
523
+ const storage = SlotsStorage.createWithClient(contract.client, contract.address, slots, {
524
+ storageOffset: position
525
+ });
526
+ const v = await storage.get('initialized');
527
+ return v;
528
+ },
484
529
  },
485
530
  contractProxyAdmin: {
486
531
  upgradeAndCall: 'upgradeAndCall(address proxy, address implementation, bytes memory data) external'
@@ -108,6 +108,23 @@ export class Generator {
108
108
  return generator.generate();
109
109
  }
110
110
 
111
+ static async generateFromJson (artifact: string) {
112
+ $require.True(await File.existsAsync(artifact), `${artifact} does not exist`);
113
+ let name = /(?<contractName>[^\\/]+).json$/.exec(artifact)?.groups?.contractName;
114
+ $require.notEmpty(name, `Contract name not resolved from the path ${artifact}`);
115
+
116
+ let generator = new Generator({
117
+ platform: 'hardhat',
118
+ name: name,
119
+ source: {
120
+ path: artifact
121
+ },
122
+ output: './0xc/hardhat/',
123
+ saveSources: false,
124
+ });
125
+ return generator.generate();
126
+ }
127
+
111
128
  /**
112
129
  * @deprecated Was possible to generate the Contract Class based on the meta information header in TS file
113
130
  */
@@ -49,7 +49,7 @@ abstract class CryptoBase implements ICrypto {
49
49
  const buffer = utils.toBuffer(mix);
50
50
  $require.gt(buffer.length, 0, `Buffer to encrypt must be a non-empty`);
51
51
  const secret = opts.secret;
52
- $require.gt(secret.length, 0, `Secret must be a non-empty`);
52
+ $require.notEmpty(secret, `Secret must be a non-empty`);
53
53
  const bufferSecret = await this.prepareSecret(secret);
54
54
 
55
55
  let encrypted = await this.encryptInner(buffer, bufferSecret);
@@ -74,14 +74,20 @@ export namespace $require {
74
74
  }
75
75
  return val;
76
76
  }
77
- export function notEmpty<T extends string | Array<any>> (val: T, message: string): T {
77
+ export function notEmpty<T extends string | Array<any> | { length: number }> (val: T, message: string): T {
78
78
  if (val == null) {
79
79
  throw new Error(`Value is undefined. ${message}`);
80
80
  }
81
- if (typeof val === 'string' && val.trim().length === 0) {
82
- throw new Error(`Value is empty string. ${message}`);
83
- } else if ($Array.isArray(val) && val.length === 0) {
84
- throw new Error(`Value is empty array. ${message}`);
81
+ if (typeof val === 'string') {
82
+ if (val.trim().length === 0) {
83
+ throw new Error(`Value is empty string. ${message}`);
84
+ }
85
+ } else if (typeof val ==='object' && 'length' in val) {
86
+ if (val.length === 0) {
87
+ throw new Error(`Value is empty array. ${message}`);
88
+ }
89
+ } else {
90
+ throw new Error(`Invalid type for notEmpty check: ${typeof val}: ${val}`);
85
91
  }
86
92
  return val;
87
93
  }