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.
- package/lib/cjs/contracts/deploy/Deployments.js +132 -0
- package/lib/cjs/contracts/deploy/Deployments.js.map +1 -1
- package/lib/cjs/contracts/deploy/storage/DeploymentsStorage.js +5 -1
- package/lib/cjs/contracts/deploy/storage/DeploymentsStorage.js.map +1 -1
- package/lib/cjs/txs/TxWriter.js +19 -0
- package/lib/cjs/txs/TxWriter.js.map +1 -1
- package/lib/cjs/txs/agents/BatchAgent.js +77 -2
- package/lib/cjs/txs/agents/BatchAgent.js.map +1 -1
- package/lib/cjs/utils/$address.js.map +1 -1
- package/lib/cjs/utils/$require.js.map +1 -1
- package/lib/esm/contracts/deploy/Deployments.js.map +1 -1
- package/lib/esm/contracts/deploy/Deployments.mjs +132 -0
- package/lib/esm/contracts/deploy/storage/DeploymentsStorage.js.map +1 -1
- package/lib/esm/contracts/deploy/storage/DeploymentsStorage.mjs +5 -1
- package/lib/esm/txs/TxWriter.js.map +1 -1
- package/lib/esm/txs/TxWriter.mjs +19 -0
- package/lib/esm/txs/agents/BatchAgent.js.map +1 -1
- package/lib/esm/txs/agents/BatchAgent.mjs +74 -2
- package/lib/esm/utils/$address.js.map +1 -1
- package/lib/esm/utils/$require.js.map +1 -1
- package/lib/types/contracts/deploy/Deployments.d.ts +9 -0
- package/lib/types/contracts/deploy/storage/DeploymentsStorage.d.ts +2 -0
- package/lib/types/txs/TxWriter.d.ts +1 -0
- package/lib/types/txs/agents/BatchAgent.d.ts +7 -2
- package/lib/types/utils/$address.d.ts +1 -1
- package/lib/types/utils/$require.d.ts +10 -9
- package/package.json +1 -1
- package/src/contracts/deploy/Deployments.ts +167 -16
- package/src/contracts/deploy/storage/DeploymentsStorage.ts +7 -1
- package/src/txs/TxWriter.ts +22 -0
- package/src/txs/agents/BatchAgent.ts +85 -6
- package/src/utils/$address.ts +1 -1
- 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():
|
|
15
|
-
disable():
|
|
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):
|
|
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
|
-
}):
|
|
8
|
+
}): number;
|
|
8
9
|
function BigInt<T>(val: T, message?: string, opts?: {
|
|
9
10
|
min?: T;
|
|
10
11
|
max?: T;
|
|
11
|
-
}):
|
|
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):
|
|
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
|
@@ -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
|
|
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 [
|
|
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
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
638
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/src/txs/TxWriter.ts
CHANGED
|
@@ -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
|
|
66
|
-
$logger.log(`[BatchAgent] ${
|
|
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 {
|
package/src/utils/$address.ts
CHANGED
|
@@ -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;
|