dequanto 0.2.40 → 0.2.42

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 (33) hide show
  1. package/lib/cjs/contracts/deploy/Deployments.js +132 -0
  2. package/lib/cjs/contracts/deploy/Deployments.js.map +1 -1
  3. package/lib/cjs/contracts/deploy/storage/DeploymentsStorage.js +5 -1
  4. package/lib/cjs/contracts/deploy/storage/DeploymentsStorage.js.map +1 -1
  5. package/lib/cjs/txs/TxWriter.js +19 -0
  6. package/lib/cjs/txs/TxWriter.js.map +1 -1
  7. package/lib/cjs/txs/agents/BatchAgent.js +77 -2
  8. package/lib/cjs/txs/agents/BatchAgent.js.map +1 -1
  9. package/lib/cjs/utils/$address.js.map +1 -1
  10. package/lib/cjs/utils/$require.js.map +1 -1
  11. package/lib/esm/contracts/deploy/Deployments.js.map +1 -1
  12. package/lib/esm/contracts/deploy/Deployments.mjs +132 -0
  13. package/lib/esm/contracts/deploy/storage/DeploymentsStorage.js.map +1 -1
  14. package/lib/esm/contracts/deploy/storage/DeploymentsStorage.mjs +5 -1
  15. package/lib/esm/txs/TxWriter.js.map +1 -1
  16. package/lib/esm/txs/TxWriter.mjs +19 -0
  17. package/lib/esm/txs/agents/BatchAgent.js.map +1 -1
  18. package/lib/esm/txs/agents/BatchAgent.mjs +74 -2
  19. package/lib/esm/utils/$address.js.map +1 -1
  20. package/lib/esm/utils/$require.js.map +1 -1
  21. package/lib/types/contracts/deploy/Deployments.d.ts +9 -0
  22. package/lib/types/contracts/deploy/storage/DeploymentsStorage.d.ts +2 -0
  23. package/lib/types/txs/TxWriter.d.ts +1 -0
  24. package/lib/types/txs/agents/BatchAgent.d.ts +7 -2
  25. package/lib/types/utils/$address.d.ts +1 -1
  26. package/lib/types/utils/$require.d.ts +10 -9
  27. package/package.json +1 -1
  28. package/src/contracts/deploy/Deployments.ts +167 -16
  29. package/src/contracts/deploy/storage/DeploymentsStorage.ts +7 -1
  30. package/src/txs/TxWriter.ts +22 -0
  31. package/src/txs/agents/BatchAgent.ts +85 -6
  32. package/src/utils/$address.ts +1 -1
  33. package/src/utils/$require.ts +12 -11
@@ -127,6 +127,15 @@ export declare class Deployments {
127
127
  private isSameBytecode;
128
128
  private ensureVerification;
129
129
  fixBytecodeHashesByReread(): Promise<void>;
130
+ saveExisting(params: {
131
+ contractAddress: TEth.Address;
132
+ name: string;
133
+ id: string;
134
+ main: string;
135
+ }): Promise<void>;
136
+ private getContractDeploymentInfo;
137
+ private resolveMain;
138
+ private getSlotsForMain;
130
139
  private getBytecodeHash;
131
140
  /**
132
141
  * A simple method to configure contract state.
@@ -19,6 +19,7 @@ export interface IDeployment {
19
19
  deployer: TEth.Address;
20
20
  timestamp: number;
21
21
  bytecodeHash: TEth.Hex;
22
+ gas?: number;
22
23
  history?: (Omit<IDeployment, 'id' | 'name'> & {
23
24
  version?: string;
24
25
  })[];
@@ -59,6 +60,7 @@ export declare class DeploymentsStorage {
59
60
  updateDeployment(deployment: IDeployment): Promise<void>;
60
61
  updateProxyDeployment(deploymentImplementation: IDeployment, deploymentProxy: IDeployment): Promise<void>;
61
62
  saveAll(deployments: IDeployment[]): Promise<void>;
63
+ upsertMany(deployments: IDeployment[]): Promise<void>;
62
64
  saveDeployment(contract: ContractBase, info: {
63
65
  id: string;
64
66
  name: string;
@@ -104,6 +104,7 @@ export declare class TxWriter extends class_EventEmitter<ITxWriterEvents> implem
104
104
  toJSON(): TTxWriterJson;
105
105
  saveTxAndExit(additionalProperties?: any): Promise<void>;
106
106
  static fromJSON(json: TTxWriterJson, client?: Web3Client, account?: TAccount): Promise<TxWriter>;
107
+ static fromTxHash(client: Web3Client, hash: TEth.Hex): Promise<TxWriter>;
107
108
  static write(client: Web3Client, builder: TxDataBuilder, account: IAccount, options?: ITxWriterOptions): TxWriter;
108
109
  static create(client: Web3Client, builder: TxDataBuilder, account: TAccount, options?: ITxWriterOptions): TxWriter;
109
110
  static writeTxData(client: Web3Client, data: Partial<TEth.TxLike>, accountMix: TAccount, options?: ITxWriterOptions): Promise<TxWriter>;
@@ -11,13 +11,18 @@ export declare class BatchAgent implements ITxWriterAgent {
11
11
  constructor(options?: {
12
12
  ignoreContractCreation?: boolean;
13
13
  });
14
- enable(): void;
15
- disable(): void;
14
+ enable(): this;
15
+ disable(): this;
16
16
  getTxData(): (Pick<TEth.TxLike, "to" | "data" | "value"> & {
17
17
  sender: TEth.EoAccount;
18
18
  account: TEth.IAccount;
19
19
  })[];
20
20
  process(senderMix: string | EoAccount, account: TEth.IAccount, outerWriter: TxWriter): Promise<MockTxWriter>;
21
+ execute(): Promise<TxWriter[]>;
22
+ private executeGroup;
23
+ private executeBatchSafe;
24
+ private executeBatchTimelock;
25
+ private getTitle;
21
26
  }
22
27
  export declare class MockTxWriter extends class_EventEmitter<ITxWriterEvents> implements ITxWriterEmitter {
23
28
  sender: TEth.EoAccount;
@@ -6,7 +6,7 @@ export declare namespace $address {
6
6
  function isValid(address: any): address is TEth.Address;
7
7
  function isEmpty(address: TEth.Address): boolean;
8
8
  function expectValid(address: TEth.Address, message: string): `0x${string}`;
9
- function toBytes32(address: TEth.Address): string;
9
+ function toBytes32(address: TEth.Address): TEth.Hex;
10
10
  function fromBytes32(hex: string): TEth.Address;
11
11
  /** Supports https://eips.ethereum.org/EIPS/eip-1191 */
12
12
  function toChecksum(address_: TEth.Address, chainId?: number): TEth.Address;
@@ -1,26 +1,27 @@
1
1
  import { IToken } from '../models/IToken';
2
2
  import { TAddress } from '../models/TAddress';
3
+ import { TEth } from '../models/TEth';
3
4
  export declare namespace $require {
4
5
  function Number<T>(val: T, message?: string, opts?: {
5
6
  min?: T;
6
7
  max?: T;
7
- }): T;
8
+ }): number;
8
9
  function BigInt<T>(val: T, message?: string, opts?: {
9
10
  min?: T;
10
11
  max?: T;
11
- }): T;
12
+ }): bigint;
12
13
  function Numeric<T>(val: T, message?: string, opts?: {
13
14
  min?: T;
14
15
  max?: T;
15
- }): T;
16
- function Function<T>(val: T, message: string): T;
17
- function Array<T>(val: T, message: string): T;
18
- function String<T>(val: T, message: string): T;
19
- function notNull<T>(val: T, message: string, ...logs: any[]): T;
16
+ }): NonNullable<T>;
17
+ function Function<T>(val: T, message: string): NonNullable<T>;
18
+ function Array<T>(val: T, message: string): NonNullable<T>;
19
+ function String<T>(val: T, message: string): NonNullable<T>;
20
+ function notNull<T>(val: T, message: string, ...logs: any[]): NonNullable<T>;
20
21
  function Null<T>(val: T, message: string, ...logs: any[]): T;
21
22
  function notEmpty<T extends string | Array<any> | {
22
23
  length: number;
23
- }>(val: T, message: string): T;
24
+ }>(val: T, message: string): NonNullable<T>;
24
25
  function resolved<T>(x: PromiseLike<T>, message: string): Promise<T>;
25
26
  function True(value: boolean, message?: string): void;
26
27
  function notEq<T = any>(a: T, b: T, message?: string): void;
@@ -33,7 +34,7 @@ export declare namespace $require {
33
34
  function AddressNotEmpty(val: TAddress | string, message?: string): TAddress;
34
35
  function AddressChecked(val: TAddress, message?: string): TAddress;
35
36
  function TxHash(val: string, message?: string): string;
36
- function Hex(val: string, message?: string): string;
37
+ function Hex(val: string, message?: string): TEth.Hex;
37
38
  function Token(token: IToken, message?: string): IToken;
38
39
  /**
39
40
  * must be a > b, throws when a <= b
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dequanto",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "author": {
5
5
  "name": "Alex Kit",
6
6
  "email": "alex.kit@atmajs.com"
@@ -21,6 +21,10 @@ import { l } from '@dequanto/utils/$logger';
21
21
  import { $promise } from '@dequanto/utils/$promise';
22
22
  import { IBeacon, IBeaconProxy, IProxy, IProxyAdmin, ProxyDeployment } from './proxy/ProxyDeployment';
23
23
  import { DeploymentsStorage, IDeployment } from './storage/DeploymentsStorage';
24
+ import { $address } from '@dequanto/utils/$address';
25
+ import { $date } from '@dequanto/utils/$date';
26
+ import { TransparentUpgradeableProxy } from '@dequanto/prebuilt/openzeppelin/compiled/TransparentUpgradeableProxy/TransparentUpgradeableProxy';
27
+ import { Directory, File, env } from 'atma-io';
24
28
 
25
29
 
26
30
 
@@ -178,7 +182,7 @@ export class Deployments {
178
182
  : new CtorWrapped(deployment.address, this.client);
179
183
  }
180
184
 
181
- async verify (params: {
185
+ async verify(params: {
182
186
  id: string
183
187
  address?: TAddress,
184
188
  Ctor: Constructor<ContractBase>
@@ -193,7 +197,6 @@ export class Deployments {
193
197
  constructorParams: params.constructorParams,
194
198
  proxyFor: deployment.proxyFor,
195
199
  });
196
-
197
200
  }
198
201
 
199
202
 
@@ -489,7 +492,7 @@ export class Deployments {
489
492
 
490
493
  let { bytecode: bytecodeOnchain } = $bytecode.splitToMetadata(bytecode);
491
494
  let { bytecode: bytecodeLocal } = $bytecode.splitToMetadata(deployedBytecode);
492
- let [ localDiff, onchainDiff ] = Str.getDifference(bytecodeLocal, bytecodeOnchain);
495
+ let [localDiff, onchainDiff] = Str.getDifference(bytecodeLocal, bytecodeOnchain);
493
496
  if (localDiff === '' || /^0+$/.test(localDiff)) {
494
497
  this._logger.log(`${deployment.id} bytecode has only immutable data diff, assume unchanged`);
495
498
  // Local deployedBytecode does not contain immutable data.
@@ -504,7 +507,7 @@ export class Deployments {
504
507
  }
505
508
 
506
509
 
507
- private async ensureVerification <T extends TContract> (Ctor: Constructor<T>, deployment: IDeployment, opts: TVerificationOptions) {
510
+ private async ensureVerification<T extends TContract>(Ctor: Constructor<T>, deployment: IDeployment, opts: TVerificationOptions) {
508
511
  if (this.client.platform === 'hardhat' || opts?.verification === false || this.opts.verification === false) {
509
512
  return;
510
513
  }
@@ -563,6 +566,154 @@ export class Deployments {
563
566
  await this.store.saveAll(deployments);
564
567
  }
565
568
 
569
+ public async saveExisting(params: {
570
+ contractAddress: TEth.Address
571
+ name: string
572
+ id: string
573
+
574
+ // The generated 0xc contract to save the deployment for
575
+ main: string
576
+ }) {
577
+ $require.AddressNotEmpty(params.contractAddress);
578
+
579
+ const main = await this.resolveMain(params.main);
580
+
581
+ let deployments = await this.store.getDeployments();
582
+ let current = deployments.find(x => $address.eq(x.address, params.contractAddress));
583
+ $require.Null(current, `Contract already exists in deployments`);
584
+
585
+ let explorer = await BlockchainExplorerFactory.getAsync(this.client.network);
586
+ let abiInfo = await explorer.getContractAbi(params.contractAddress);
587
+ if ($is.Address(abiInfo.implementation)) {
588
+ // Is proxy
589
+ const deployments = [] as IDeployment[];
590
+
591
+ const contractInfo = await this.getContractDeploymentInfo(abiInfo.implementation);
592
+ deployments.push({
593
+ id: params.id,
594
+ name: params.name,
595
+ main: main,
596
+ ...contractInfo,
597
+
598
+ address: params.contractAddress,
599
+ implementation: abiInfo.implementation,
600
+ });
601
+
602
+ const proxyInfo = await this.getContractDeploymentInfo(params.contractAddress);
603
+ const proxy = {
604
+ id: `${params.id}Proxy`,
605
+ name: 'TransparentUpgradeableProxy',
606
+ main: 'artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json',
607
+ ...proxyInfo,
608
+ address: params.contractAddress,
609
+ proxyFor: params.contractAddress,
610
+ };
611
+ deployments.push(proxy);
612
+
613
+ const proxyReceipt = await this.client.getTransactionReceipt(proxy.tx);
614
+ const proxyAdmin = new TransparentUpgradeableProxy($address.ZERO, this.client);
615
+ const [proxyAdminLog] = proxyAdmin.extractLogsAdminChanged(proxyReceipt);
616
+ if (proxyAdminLog) {
617
+ deployments.push({
618
+ id: `${params.id}ProxyAdmin`,
619
+ name: 'ProxyAdmin',
620
+ main: 'artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json',
621
+ address: proxyAdminLog.params.newAdmin,
622
+ block: proxy.block,
623
+ tx: proxy.tx,
624
+ gas: proxy.gas,
625
+ timestamp: proxy.timestamp,
626
+
627
+ // Not required for the proxyadmin contract
628
+ deployer: null,
629
+ bytecodeHash: null,
630
+ });
631
+ }
632
+
633
+ let slots = await this.getSlotsForMain(main);
634
+ if (slots != null) {
635
+ this.store.saveStorageLayoutInfo({
636
+ id: proxy.id,
637
+ slots
638
+ });
639
+ }
640
+ await this.store.upsertMany(deployments);
641
+ return;
642
+ }
643
+
644
+ const contractInfo = await this.getContractDeploymentInfo(params.contractAddress);
645
+ const deployment = {
646
+ id: params.id,
647
+ name: params.name,
648
+ main: main,
649
+ ...contractInfo,
650
+ };
651
+ await this.store.upsertMany([ deployment ]);
652
+ }
653
+
654
+ private async getContractDeploymentInfo(address: TAddress) {
655
+ const client = this.client;
656
+ const explorer = await BlockchainExplorerFactory.getAsync(client.network);
657
+ const creation = await explorer.getContractCreation(address);
658
+ const receipt = await this.client.getTransactionReceipt(creation.txHash);
659
+ const block = await this.client.getBlock(receipt.blockNumber);
660
+ const bytecode = await this.client.getCode(address);
661
+ const info = {
662
+ address: address,
663
+ bytecodeHash: this.getBytecodeHash(bytecode),
664
+ block: receipt.blockNumber,
665
+ tx: receipt.transactionHash,
666
+ timestamp: block.timestamp,
667
+ gas: Number(receipt.gasUsed),
668
+ deployer: receipt.from,
669
+ verified: $date.fromUnixTimestamp(block.timestamp).toISOString()
670
+ };
671
+ return info;
672
+ }
673
+
674
+ private async resolveMain(main: string) {
675
+ let extMatch = /\.\w+$/.exec(main);
676
+ if (extMatch != null) {
677
+ let exists = await File.existsAsync(main);
678
+ $require.True(exists, `The main file "${main}" must exist`);
679
+ return main;
680
+ }
681
+ let files = await Directory.readFilesAsync('./0xc/hardhat/', main);
682
+ if (files.length > 1) {
683
+ throw new Error(`Multiple contracts found: ${files.map(x => x.uri.toLocalFile())} by the match "${main}"`);
684
+ }
685
+ if (files.length === 0) {
686
+ throw new Error(`No files found by the match "${main}"`);
687
+ }
688
+ let [file] = files;
689
+ return file.uri.toRelativeString(env.currentDir);
690
+ }
691
+
692
+ private async getSlotsForMain(main: string) {
693
+ let code = await File.readAsync<string>(main, { skipHooks: true });
694
+ // parse from ts-generated code (consider to output slots in extra file like abi json)
695
+ let rgxStart = /^\s*\$slots\s*=\s*\[/mg;
696
+ let rgxStartMatch = rgxStart.exec(code);
697
+ if (rgxStartMatch == null) {
698
+ console.error(`${main} has no generated $slots field`);
699
+ return null;
700
+ }
701
+ let rgxEnd = /^\s*\]/mg;
702
+ rgxEnd.lastIndex = rgxStartMatch.index;
703
+ let rgxEndMatch = rgxEnd.exec(code);
704
+ if (rgxEndMatch == null) {
705
+ console.error(`${main}: End not found of the $slots value`);
706
+ return null;
707
+ }
708
+ let json = code.substring(rgxStartMatch.index + rgxStartMatch[0].length - 1, rgxEndMatch.index + 1);
709
+ try {
710
+ return JSON.parse(json);
711
+ } catch (error) {
712
+ console.error(`Slots not parsed from ${main}: ${error.message}`)
713
+ return null;
714
+ }
715
+ }
716
+
566
717
  private getBytecodeHash(bytecode: TEth.Hex) {
567
718
  let { bytecode: bytecodeRaw } = $bytecode.splitToMetadata(bytecode);
568
719
  return $contract.keccak256(bytecodeRaw);
@@ -614,7 +765,7 @@ export class Deployments {
614
765
  return;
615
766
  }
616
767
  }
617
- if (opts.title!= null) {
768
+ if (opts.title != null) {
618
769
  let currentStr = currentVal == null || typeof currentVal === 'object'
619
770
  ? ''
620
771
  : ` from ${currentVal}`;
@@ -628,14 +779,14 @@ type TFunction = (...args: any[]) => any;
628
779
  type TInitializerName = 'initialize' | `initializeV${number}`;
629
780
  type TInitializerParams<T> = {
630
781
  initialize?: T extends { initialize: infer TInit }
631
- ? TInit extends TFunction
632
- ? ParametersFromSecond<TInit>
633
- : never
634
- : any[];
782
+ ? TInit extends TFunction
783
+ ? ParametersFromSecond<TInit>
784
+ : never
785
+ : any[];
635
786
  } & {
636
787
  [K in Extract<keyof T, TInitializerName>]?: T[K] extends TFunction
637
- ? ParametersFromSecond<T[K]>
638
- : never;
788
+ ? ParametersFromSecond<T[K]>
789
+ : never;
639
790
  };
640
791
 
641
792
  type TContract = ContractBase & { $constructor?: (...args: any[]) => any }
@@ -652,7 +803,7 @@ function isEqual(a, b) {
652
803
  }
653
804
  if (typeof a !== 'object' && typeof b !== 'object') {
654
805
 
655
- if (typeof a === 'string' && typeof b ==='string') {
806
+ if (typeof a === 'string' && typeof b === 'string') {
656
807
  if (a.startsWith('0x') && b.startsWith('0x') && $is.Hex(a) && $is.Hex(b)) {
657
808
  a = a.toLowerCase();
658
809
  b = b.toLowerCase();
@@ -730,7 +881,7 @@ function serializeMigrationData(id: string, contract: ContractBase, opts: any) {
730
881
  * Normalizes the contract name by removing any version suffix from the name.
731
882
  * "FooV1" is actually the "Foo" contract.
732
883
  */
733
- function getImplementationId (Ctor: Constructor<TContract>) {
884
+ function getImplementationId(Ctor: Constructor<TContract>) {
734
885
  let id = Ctor.name;
735
886
  let version = /V?(?<version>\d)$/i.exec(id);
736
887
  if (version) {
@@ -740,7 +891,7 @@ function getImplementationId (Ctor: Constructor<TContract>) {
740
891
  }
741
892
 
742
893
 
743
- function getImmutablesKey (args: any[]) {
894
+ function getImmutablesKey(args: any[]) {
744
895
  if (args == null || args.length === 0) {
745
896
  return null;
746
897
  }
@@ -749,9 +900,9 @@ function getImmutablesKey (args: any[]) {
749
900
  }
750
901
 
751
902
  namespace Str {
752
- export function getDifference (a: TEth.Hex, b: TEth.Hex) {
903
+ export function getDifference(a: TEth.Hex, b: TEth.Hex) {
753
904
  if (a === b) {
754
- return [ '', '' ];
905
+ return ['', ''];
755
906
  }
756
907
 
757
908
  let start = -1;
@@ -39,6 +39,7 @@ export interface IDeployment {
39
39
  deployer: TEth.Address
40
40
  timestamp: number
41
41
  bytecodeHash: TEth.Hex
42
+ gas?: number
42
43
 
43
44
  history?: (Omit<IDeployment, 'id' | 'name'> & { version?: string })[]
44
45
  }
@@ -156,6 +157,11 @@ export class DeploymentsStorage {
156
157
  await store.saveAll(deployments);
157
158
  }
158
159
 
160
+ async upsertMany (deployments: IDeployment[]) {
161
+ let store = await this.getDeploymentsStore();
162
+ await store.upsertMany(deployments);
163
+ }
164
+
159
165
  async saveDeployment (contract: ContractBase, info: {
160
166
  id: string
161
167
  name: string
@@ -176,7 +182,7 @@ export class DeploymentsStorage {
176
182
  address: contract.address,
177
183
  block: receipt.blockNumber,
178
184
  tx: receipt.transactionHash,
179
- gas: receipt.gasUsed,
185
+ gas: Number(receipt.gasUsed),
180
186
  deployer: this.deployer.address,
181
187
  timestamp: $date.toUnixTimestamp(new Date()),
182
188
 
@@ -619,6 +619,28 @@ export class TxWriter extends class_EventEmitter<ITxWriterEvents> implements ITx
619
619
  return writer;
620
620
  }
621
621
 
622
+ static async fromTxHash (client: Web3Client, hash: TEth.Hex): Promise<TxWriter> {
623
+ let txData = await client.getTransaction(hash);
624
+ let txReceipt = await client.getTransactionReceipt(hash);
625
+ let account = await TxWriter.prepareAccount(txData.from);
626
+
627
+ let builder = TxDataBuilder.fromJSON(client, account, {
628
+ config: null,
629
+ tx: txData
630
+ });
631
+ let writer = TxWriter.create(client, builder, account);
632
+ writer.tx = {
633
+ hash: hash,
634
+ receipt: txReceipt,
635
+ confirmations: null,
636
+ timestamp: null,
637
+ };
638
+ writer.txs = [ writer.tx ];
639
+ writer.receipt = txReceipt;
640
+
641
+ return writer;
642
+ }
643
+
622
644
 
623
645
  static write (
624
646
  client: Web3Client,
@@ -1,3 +1,4 @@
1
+ import alot from 'alot';
1
2
  import { EoAccount } from '@dequanto/models/TAccount';
2
3
  import { ITxWriterAgent } from './TxWriterAccountAgents';
3
4
  import { ITxWriterEmitter, ITxWriterEvents, ITxWriterTransaction, TxWriter } from '../TxWriter';
@@ -7,11 +8,13 @@ import { class_Dfr, class_EventEmitter } from 'atma-utils';
7
8
  import { TEth } from '@dequanto/models/TEth';
8
9
  import { $date } from '@dequanto/utils/$date';
9
10
 
10
-
11
- import { $require } from '@dequanto/utils/$require';
12
11
  import { $address } from '@dequanto/utils/$address';
13
12
  import { $logger } from '@dequanto/utils/$logger';
14
-
13
+ import { SafeTx } from '@dequanto/safe/SafeTx';
14
+ import { TimelockController } from '@dequanto/prebuilt/openzeppelin/TimelockController';
15
+ import { TimelockService } from '@dequanto/services/TimelockService/TimelockService';
16
+ import { TxDataBuilder } from '../TxDataBuilder';
17
+ import { $contract } from '@dequanto/utils/$contract';
15
18
 
16
19
  export class BatchAgent implements ITxWriterAgent {
17
20
 
@@ -26,10 +29,12 @@ export class BatchAgent implements ITxWriterAgent {
26
29
 
27
30
  enable () {
28
31
  TxWriter.DEFAULTS.agent = this;
32
+ return this;
29
33
  }
30
34
 
31
35
  disable () {
32
36
  TxWriter.DEFAULTS.agent = null;
37
+ return this;
33
38
  }
34
39
 
35
40
  getTxData (): (Pick<TEth.TxLike, "to" | "data" | "value"> & { sender: TEth.EoAccount, account: TEth.IAccount })[] {
@@ -39,11 +44,12 @@ export class BatchAgent implements ITxWriterAgent {
39
44
  to: data.to,
40
45
  value: data.value,
41
46
  data: data.data,
47
+ chainId: data.chainId,
42
48
 
43
49
  sender: tx.sender,
44
50
  account: tx.account,
45
51
  };
46
- })
52
+ });
47
53
  }
48
54
 
49
55
  async process (senderMix: string | EoAccount, account: TEth.IAccount, outerWriter: TxWriter) {
@@ -62,13 +68,86 @@ export class BatchAgent implements ITxWriterAgent {
62
68
  outerWriter,
63
69
  );
64
70
 
65
- let methodInfo = await outerWriter.builder.getInputDataInfo();
66
- $logger.log(`[BatchAgent] ${outerWriter.builder.data.to} (${methodInfo?.method})`);
71
+ let title = await this.getTitle(outerWriter.builder);
72
+ $logger.log(`[BatchAgent] ${title}`);
67
73
 
68
74
  this.transactions.push(inner);
69
75
  await inner.process();
70
76
  return inner;
71
77
  }
78
+
79
+ async execute (): Promise<TxWriter[]> {
80
+ this.disable();
81
+
82
+ let groupStart = 0;
83
+ let writers = [];
84
+ for (let i = 0; i < this.transactions.length; i++) {
85
+ let tx = this.transactions[i];
86
+ let next = i < this.transactions.length - 1
87
+ ? this.transactions[i + i]
88
+ : null;
89
+
90
+ if (next == null || $address.eq(tx.account.address, next.account.address) === false) {
91
+ let arr = await this.executeGroup(this.transactions.slice(groupStart, i + 1));
92
+ writers.push(...arr);
93
+ }
94
+ }
95
+ this.enable();
96
+ return writers;
97
+ }
98
+
99
+ private async executeGroup (txs: MockTxWriter[]) {
100
+ let { account, sender } = txs[0];
101
+ if (account.name.includes('safe/')) {
102
+ let tx = await this.executeBatchSafe(sender, account, txs);
103
+ return [ tx ];
104
+ }
105
+ if (account.name.includes('timelock/')) {
106
+ let tx = await this.executeBatchTimelock(sender, account, txs);
107
+ return [ tx ];
108
+ }
109
+ let writers = [];
110
+ for (let i = 0; i < txs.length; i++) {
111
+ let tx = txs[i];
112
+ let writer = tx.outerWriter.send();
113
+ await writer.wait();
114
+ writers.push(writer);
115
+ }
116
+ return writers;
117
+ }
118
+ private async executeBatchSafe (sender: TEth.EoAccount, account: TEth.IAccount, txs: MockTxWriter[]) {
119
+ let client = txs[0].outerWriter.client;
120
+ let safe = new SafeTx(account as TEth.SafeAccount, client);
121
+
122
+ let calls = txs.map(x => x.outerWriter.builder.getTxData());
123
+ let writer = await safe.executeBatch(...calls);
124
+ await writer.wait();
125
+ return writer;
126
+ }
127
+ private async executeBatchTimelock (sender: TEth.EoAccount, account: TEth.IAccount, txs: MockTxWriter[]) {
128
+ let client = txs[0].outerWriter.client;
129
+ let timelock = new TimelockController(account.address, client);
130
+ let service = new TimelockService(timelock, {
131
+
132
+ });
133
+ let titles = await alot(txs)
134
+ .map(x => this.getTitle(x.outerWriter.builder))
135
+ .toArrayAsync();
136
+ let taskName = $contract.keccak256(titles.join(','), 'hex');
137
+ let calls = txs.map(x => x.outerWriter.builder.getTxData());
138
+
139
+ let { tx } = await service.processBatch(taskName, sender, calls);
140
+ if (tx == null) {
141
+ // No transactions
142
+ return null;
143
+ }
144
+ return TxWriter.fromTxHash(client, tx);
145
+ }
146
+
147
+ private async getTitle (builder: TxDataBuilder) {
148
+ let methodInfo = await builder.getInputDataInfo();
149
+ return `${builder.data.to} [${methodInfo?.method}(${JSON.stringify(methodInfo?.params ?? [])})]`
150
+ }
72
151
  }
73
152
 
74
153
  export class MockTxWriter extends class_EventEmitter<ITxWriterEvents> implements ITxWriterEmitter {
@@ -36,7 +36,7 @@ export namespace $address {
36
36
  }
37
37
 
38
38
  export function toBytes32(address: TEth.Address) {
39
- return address.toLowerCase().substring(2).padStart(32, '0');
39
+ return address.toLowerCase().substring(2).padStart(32, '0') as TEth.Hex;
40
40
  }
41
41
  export function fromBytes32(hex: string): TEth.Address {
42
42
  const SIZE = 40;