dequanto 0.1.4 → 0.1.7
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/assets/background.jpg +0 -0
- package/package.json +1 -1
- package/readme.md +54 -61
- package/src/gen/Generator.ts +5 -0
- package/src/gen/GeneratorFromAbi.ts +50 -42
- package/src/hardhat/HardhatProvider.ts +29 -1
- package/src/json/JsonObjectStore.ts +1 -1
- package/src/safe/SafeTx.ts +1 -1
- package/src/services/TimelockService/ITimelockService.ts +123 -0
- package/src/services/{TimelockService.ts → TimelockService/TimelockService.ts} +12 -71
- package/src/services/TimelockService/TimelockServiceFallbackSafe.ts +310 -0
- package/src/tokens/TokenTransferService.ts +2 -2
- package/src/utils/types.ts +6 -4
- package/test/ns.spec.ts +1 -1
- package/test/services/timelock.spec.ts +1 -1
- package/tools/copy-dts.ts +5 -3
|
Binary file
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -3,92 +3,85 @@
|
|
|
3
3
|
[](https://docs.0xweb.org/dequanto)
|
|
4
4
|
[](https://circleci.com/gh/0xweb-org/dequanto)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
1. RPC Client Pool
|
|
11
|
-
2. Transaction Builder
|
|
12
|
-
3. Generated TypeScript classes for contracts
|
|
13
|
-
4. Etherscan & Co clients
|
|
14
|
-
5. Tokens provider - get token data by symbol, address, platform.
|
|
15
|
-
6. Tokens service - transfer and swap
|
|
16
|
-
7. Transaction **Indexers** - listens to blocks and parse new transactions to simplify further processing
|
|
17
|
-
8. Native **`BigInt`** types and various utility methods
|
|
18
|
-
9. OpenZeppelin contracts
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
### **`Source-Code`** Distribution
|
|
22
|
-
|
|
23
|
-
Not as a library, but as the source code. Clone or fork it and use it as a **submodule**. Means:
|
|
24
|
-
a) you can `import` any `class`, `namespace` and `utility` method.
|
|
25
|
-
b) you can easily extend and modify any `class`, `namespace` and `utility` method.
|
|
26
|
-
c) you get the documentation direct from the code. _We don't have much time yet to document all the parts_
|
|
27
|
-
|
|
28
|
-
And path prefix (alias) to your `tsconfig.json`
|
|
29
|
-
```json
|
|
30
|
-
{
|
|
31
|
-
"compilerOptions": {
|
|
32
|
-
"paths": {
|
|
33
|
-
"@dequanto/*": [ "your-sub-module-folder/src/*" ],
|
|
34
|
-
"@dequanto-contract/*": [ "your-sub-module-folder/contract/*" ]
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
```
|
|
6
|
+
<p align='center'>
|
|
7
|
+
<img src='assets/background.jpg'/>
|
|
8
|
+
</p>
|
|
39
9
|
|
|
40
|
-
### Configuration
|
|
41
10
|
|
|
42
|
-
|
|
11
|
+
A versatile library for EVM Blockchain Developers and Consumers. Based on your requirements and expertise, this library can either fully abstract the Blockchain Layer with autogenerated classes for contract communication or give you direct access to low-level methods.
|
|
43
12
|
|
|
44
|
-
|
|
13
|
+
This library includes numerous classes to cover all facets of blockchain development. Here’s a summary of some key features:
|
|
45
14
|
|
|
46
|
-
|
|
15
|
+
### TypeScript and ES6 Class Generation
|
|
47
16
|
|
|
48
|
-
|
|
17
|
+
Generate contract clients based on ABI or source code, which can be automatically retrieved from blockchain explorers by address.
|
|
49
18
|
|
|
19
|
+
> This can serve as an all-in-one tool for submitting transactions, querying contracts, or fetching events, as the contract clients handle the underlying steps.
|
|
50
20
|
|
|
51
|
-
|
|
21
|
+
---
|
|
52
22
|
|
|
53
|
-
|
|
23
|
+
### RPC Clients
|
|
54
24
|
|
|
25
|
+
A robust communication layer to connect to blockchain nodes. You can add multiple nodes (private or public) to the pool to manage **request balancing**, **throttling**, **rate limits**, and **automatic retries**.
|
|
55
26
|
|
|
56
|
-
|
|
27
|
+
### Transaction Builder
|
|
57
28
|
|
|
58
|
-
|
|
29
|
+
Provides fine-tuning for transaction submission. Supports Gnosis {Safe} and Account Abstraction Transactions.
|
|
59
30
|
|
|
60
|
-
|
|
61
|
-
b) parse receipts of the contract transactions
|
|
62
|
-
c) easily subscribe to event streams of the contract
|
|
31
|
+
### Etherscan & Co Clients
|
|
63
32
|
|
|
64
|
-
|
|
33
|
+
An integrated API for blockchain explorers.
|
|
65
34
|
|
|
66
|
-
|
|
35
|
+
### Event Indexer
|
|
67
36
|
|
|
68
|
-
|
|
37
|
+
An easy-to-use indexer for fetching contract events.
|
|
69
38
|
|
|
70
|
-
|
|
39
|
+
### Event and Transaction Watcher
|
|
71
40
|
|
|
72
|
-
|
|
41
|
+
Listen to events and monitor transactions in real-time.
|
|
73
42
|
|
|
74
|
-
|
|
43
|
+
### Native **`BigInt`** Types with **`BigFloat`** Support for Mantissa Math
|
|
75
44
|
|
|
76
|
-
|
|
45
|
+
### Pre-generated OpenZeppelin Contracts
|
|
77
46
|
|
|
78
|
-
|
|
47
|
+
### Flashbots Support
|
|
79
48
|
|
|
80
|
-
##
|
|
49
|
+
## Installation
|
|
81
50
|
|
|
82
|
-
|
|
51
|
+
### Using 0xweb CLI
|
|
83
52
|
|
|
53
|
+
You can use the [0xweb](https://github.com/0xweb-org/0xweb) tool to install contracts and dependencies.
|
|
84
54
|
|
|
85
|
-
###
|
|
55
|
+
### NPM
|
|
86
56
|
|
|
87
|
-
|
|
57
|
+
Install the library using npm:
|
|
88
58
|
|
|
89
59
|
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let erc20 = new ERC20(`0x...`);
|
|
93
|
-
let balance = await erc20.balanceOf('0x...');
|
|
60
|
+
npm i dequanto
|
|
94
61
|
```
|
|
62
|
+
|
|
63
|
+
The package includes three types of source files:
|
|
64
|
+
|
|
65
|
+
1. **TypeScript Sources**: Configure paths in your `tsconfig.json` like so:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"compilerOptions": {
|
|
70
|
+
"paths": {
|
|
71
|
+
"@dequanto/*": [ "node_modules/dequanto/src/*" ],
|
|
72
|
+
"@dequanto-contract/*": [ "node_modules/dequanto/src/prebuilt/*" ]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
2. **Compiled CommonJS (CJS) Files**: Located at `node_modules/dequanto/lib/cjs/*/**.js`
|
|
79
|
+
3. **Compiled ESM (Modules)**: Located at `node_modules/dequanto/lib/esm/*/**.mjs`
|
|
80
|
+
|
|
81
|
+
### Configuration
|
|
82
|
+
|
|
83
|
+
While the library includes configurations for various blockchains and {Safe} infrastructure, you may need to set custom RPC URLs, Blockchain Explorer API Keys, and other settings. This can be done in a **YAML** configuration file that loads when the application starts.
|
|
84
|
+
|
|
85
|
+
----
|
|
86
|
+
|
|
87
|
+
(c) 2024 0xweb.org
|
package/src/gen/Generator.ts
CHANGED
|
@@ -335,7 +335,12 @@ export class Generator {
|
|
|
335
335
|
|
|
336
336
|
if (typeof path === 'string' && path.endsWith('.json')) {
|
|
337
337
|
let json = await this.readFile(path);
|
|
338
|
+
if (Array.isArray(json)) {
|
|
339
|
+
return { abi: json, bytecode: null, artifact: null, source: null }
|
|
340
|
+
}
|
|
338
341
|
let { abi, bytecode, sourceName, contractName } = json
|
|
342
|
+
$require.True(Array.isArray(abi), `Invalid abi json: ${path}. Expected the "abi" property to be the array.`);
|
|
343
|
+
|
|
339
344
|
let source = await this.getSources(contractName, {
|
|
340
345
|
sourcePath: sourceName,
|
|
341
346
|
contractName,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import alot from 'alot';
|
|
2
2
|
import { type TAbiItem } from '@dequanto/types/TAbi';
|
|
3
3
|
import { TAddress } from '@dequanto/models/TAddress';
|
|
4
|
-
import { File } from 'atma-io';
|
|
4
|
+
import { env, File } from 'atma-io';
|
|
5
5
|
import { class_Uri } from 'atma-utils';
|
|
6
6
|
import { $abiType } from '@dequanto/utils/$abiType';
|
|
7
7
|
import { $date } from '@dequanto/utils/$date';
|
|
@@ -19,11 +19,11 @@ import { $str } from '@dequanto/solidity/utils/$str';
|
|
|
19
19
|
|
|
20
20
|
export class GeneratorFromAbi {
|
|
21
21
|
|
|
22
|
-
static get Gen
|
|
22
|
+
static get Gen() {
|
|
23
23
|
return Gen;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
async generate
|
|
26
|
+
async generate(abiJson: TAbiItem[], opts: {
|
|
27
27
|
network: TPlatform
|
|
28
28
|
name: string
|
|
29
29
|
contractName: string
|
|
@@ -88,7 +88,6 @@ export class GeneratorFromAbi {
|
|
|
88
88
|
.map(x => Gen.serializeEvent(x))
|
|
89
89
|
.filter(Boolean)
|
|
90
90
|
.map(Str.formatMethod);
|
|
91
|
-
;
|
|
92
91
|
|
|
93
92
|
let eventTypes = alot(abiJson)
|
|
94
93
|
.filter(x => x.type === 'event')
|
|
@@ -120,7 +119,6 @@ export class GeneratorFromAbi {
|
|
|
120
119
|
// .map(x => Gen.serializeEventInterface(x))
|
|
121
120
|
// .filter(Boolean)
|
|
122
121
|
// .map(Str.formatMethod);
|
|
123
|
-
;
|
|
124
122
|
|
|
125
123
|
let methods = methodsArr.join('\n\n');
|
|
126
124
|
let events = eventsArr.join('\n\n');
|
|
@@ -223,7 +221,7 @@ export class GeneratorFromAbi {
|
|
|
223
221
|
: void 0,
|
|
224
222
|
class: this.getRelativePath(outputPath)
|
|
225
223
|
};
|
|
226
|
-
let metaProperty = `$meta = ${
|
|
224
|
+
let metaProperty = `$meta = ${JSON.stringify(meta, null, 4)}`;
|
|
227
225
|
|
|
228
226
|
|
|
229
227
|
let storageReaderInitializer = '';
|
|
@@ -297,7 +295,17 @@ export class GeneratorFromAbi {
|
|
|
297
295
|
if (sources && opts.saveSources !== false) {
|
|
298
296
|
sourceFiles = await alot.fromObject(sources).mapAsync(async entry => {
|
|
299
297
|
let sourceFilename = /\/?([^/]+$)/.exec(entry.key)[1];
|
|
300
|
-
let
|
|
298
|
+
let sourcePath = entry.key;
|
|
299
|
+
let cwd = env.currentDir.toString().toLowerCase();
|
|
300
|
+
if (sourcePath.toLowerCase().startsWith(cwd)) {
|
|
301
|
+
sourcePath = sourcePath.substring(cwd.length + 1);
|
|
302
|
+
}
|
|
303
|
+
// Remove: solc should load deps also from the root directory
|
|
304
|
+
// if (sourcePath.startsWith('@')) {
|
|
305
|
+
// // handle @openzeppelin and other npm packages
|
|
306
|
+
// sourcePath = `node_modules/${ sourcePath }`;
|
|
307
|
+
// }
|
|
308
|
+
let path = class_Uri.combine(opts.output, outputDirectory, outputFilename, sourcePath);
|
|
301
309
|
await File.writeAsync(path, entry.value.content, { skipHooks: true });
|
|
302
310
|
|
|
303
311
|
$logger.log(`Source code saved: ${path}`);
|
|
@@ -315,7 +323,7 @@ export class GeneratorFromAbi {
|
|
|
315
323
|
}
|
|
316
324
|
|
|
317
325
|
|
|
318
|
-
private getRelativePath
|
|
326
|
+
private getRelativePath(path: string) {
|
|
319
327
|
if (path == null) {
|
|
320
328
|
return path;
|
|
321
329
|
}
|
|
@@ -330,7 +338,7 @@ export class GeneratorFromAbi {
|
|
|
330
338
|
|
|
331
339
|
namespace Gen {
|
|
332
340
|
|
|
333
|
-
export function serializeMethodTs
|
|
341
|
+
export function serializeMethodTs(abi: TAbiItem) {
|
|
334
342
|
if (abi.type === 'constructor') {
|
|
335
343
|
return serializeConstructorMethodTs(abi);
|
|
336
344
|
}
|
|
@@ -340,7 +348,7 @@ namespace Gen {
|
|
|
340
348
|
}
|
|
341
349
|
return serializeWriteMethodTs(abi);
|
|
342
350
|
}
|
|
343
|
-
export function serializeMethodTsOverloads
|
|
351
|
+
export function serializeMethodTsOverloads(abis: TAbiItem[]) {
|
|
344
352
|
let isRead = abis.every(abi => $abiUtils.isReadMethod(abi));
|
|
345
353
|
if (isRead) {
|
|
346
354
|
return serializeReadMethodTsOverloads(abis);
|
|
@@ -349,7 +357,7 @@ namespace Gen {
|
|
|
349
357
|
}
|
|
350
358
|
|
|
351
359
|
// abi.length > 1 if has method overloads
|
|
352
|
-
export function serializeMethodInterfacesTs
|
|
360
|
+
export function serializeMethodInterfacesTs(name: string, abis: TAbiItem[]) {
|
|
353
361
|
let args = abis.map(abi => {
|
|
354
362
|
let { fnInputArguments } = serializeArgumentsTs(abi);
|
|
355
363
|
return `[ ${fnInputArguments} ]`;
|
|
@@ -369,7 +377,7 @@ namespace Gen {
|
|
|
369
377
|
};
|
|
370
378
|
}
|
|
371
379
|
|
|
372
|
-
export function serializeMethodTypeTs
|
|
380
|
+
export function serializeMethodTypeTs(name: string, abis: TAbiItem[]) {
|
|
373
381
|
let args = abis.map(abi => {
|
|
374
382
|
let { fnInputArguments } = serializeArgumentsTs(abi);
|
|
375
383
|
return `[ ${fnInputArguments} ]`;
|
|
@@ -389,7 +397,7 @@ namespace Gen {
|
|
|
389
397
|
};
|
|
390
398
|
}
|
|
391
399
|
|
|
392
|
-
export function serializeMethodInterfacesAllTs
|
|
400
|
+
export function serializeMethodInterfacesAllTs(methods: { method: string, interface: string }[]) {
|
|
393
401
|
let fields = methods.map(method => {
|
|
394
402
|
return ` ${method.method}: ${method.interface}`;
|
|
395
403
|
});
|
|
@@ -403,7 +411,7 @@ namespace Gen {
|
|
|
403
411
|
code: code.join('\n')
|
|
404
412
|
};
|
|
405
413
|
}
|
|
406
|
-
export function serializeEventsInterfacesAllTs
|
|
414
|
+
export function serializeEventsInterfacesAllTs(events: TAbiItem[]) {
|
|
407
415
|
let fields = events.map(item => {
|
|
408
416
|
return ` ${item.name}: TLog${item.name}Parameters`;
|
|
409
417
|
});
|
|
@@ -417,14 +425,14 @@ namespace Gen {
|
|
|
417
425
|
code: code.join('\n')
|
|
418
426
|
};
|
|
419
427
|
}
|
|
420
|
-
export function serializeEventType
|
|
428
|
+
export function serializeEventType(event: TAbiItem) {
|
|
421
429
|
let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(event);
|
|
422
430
|
let outputParams = `{ ${fnInputArguments} }`;
|
|
423
431
|
let outputArgs = `[ ${fnInputArguments.replace('\n', '')} ]`;
|
|
424
432
|
let code = [
|
|
425
433
|
`${event.name}: {`,
|
|
426
|
-
` outputParams: ${
|
|
427
|
-
` outputArgs: ${
|
|
434
|
+
` outputParams: ${outputParams},`,
|
|
435
|
+
` outputArgs: ${outputArgs},`,
|
|
428
436
|
`}`
|
|
429
437
|
];
|
|
430
438
|
return {
|
|
@@ -432,7 +440,7 @@ namespace Gen {
|
|
|
432
440
|
};
|
|
433
441
|
}
|
|
434
442
|
|
|
435
|
-
export function serializeEvent
|
|
443
|
+
export function serializeEvent(abi: TAbiItem) {
|
|
436
444
|
let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
|
|
437
445
|
return `
|
|
438
446
|
on${abi.name} (fn?: (event: TClientEventsStreamData<TEventArguments<'${abi.name}'>>) => void): ClientEventsStream<TClientEventsStreamData<TEventArguments<'${abi.name}'>>> {
|
|
@@ -440,7 +448,7 @@ namespace Gen {
|
|
|
440
448
|
}
|
|
441
449
|
`;
|
|
442
450
|
}
|
|
443
|
-
export function serializeEventExtractor
|
|
451
|
+
export function serializeEventExtractor(abi: TAbiItem) {
|
|
444
452
|
return `
|
|
445
453
|
extractLogs${abi.name} (tx: TEth.TxReceipt): ITxLogItem<TEventParams<'${abi.name}'>>[] {
|
|
446
454
|
let abi = this.$getAbiItem('event', '${abi.name}');
|
|
@@ -448,10 +456,10 @@ namespace Gen {
|
|
|
448
456
|
}
|
|
449
457
|
`;
|
|
450
458
|
}
|
|
451
|
-
export function serializeEventFetcher
|
|
459
|
+
export function serializeEventFetcher(abi: TAbiItem) {
|
|
452
460
|
let inputs = abi.inputs;
|
|
453
461
|
let indexed = alot(inputs).takeWhile(x => x.indexed).toArray();
|
|
454
|
-
let indexedParams = indexed.map(param => `${param.name}?: ${
|
|
462
|
+
let indexedParams = indexed.map(param => `${param.name}?: ${$abiType.getTsType(param.type, param)}`)
|
|
455
463
|
return `
|
|
456
464
|
async getPastLogs${abi.name} (options?: {
|
|
457
465
|
fromBlock?: number | Date
|
|
@@ -488,7 +496,7 @@ namespace Gen {
|
|
|
488
496
|
return `function ${abi.name}(${params}) ${returnsStr}`.trim();
|
|
489
497
|
}
|
|
490
498
|
|
|
491
|
-
function serializeReadMethodTs
|
|
499
|
+
function serializeReadMethodTs(abi: TAbiItem) {
|
|
492
500
|
let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
|
|
493
501
|
if (callInputArguments) {
|
|
494
502
|
callInputArguments = `, ${callInputArguments}`;
|
|
@@ -500,7 +508,7 @@ namespace Gen {
|
|
|
500
508
|
}
|
|
501
509
|
`;
|
|
502
510
|
}
|
|
503
|
-
function serializeReadMethodTsOverloads
|
|
511
|
+
function serializeReadMethodTsOverloads(abis: TAbiItem[]) {
|
|
504
512
|
let overrides = abis.map(abi => {
|
|
505
513
|
let { fnInputArguments, fnResult } = serializeArgumentsTs(abi);
|
|
506
514
|
return `
|
|
@@ -513,7 +521,7 @@ namespace Gen {
|
|
|
513
521
|
let { fnResult } = serializeArgumentsTs(abi);
|
|
514
522
|
let sigs = abis.map(abi => serializeMethodAbi(abi)).map(x => `'${x}'`).join(', ');
|
|
515
523
|
return `
|
|
516
|
-
${
|
|
524
|
+
${overrides}
|
|
517
525
|
async ${abi.name} (...args): ${fnResult} {
|
|
518
526
|
let abi = this.$getAbiItemOverload([ ${sigs} ], args);
|
|
519
527
|
return this.$read(abi, ...args);
|
|
@@ -521,7 +529,7 @@ namespace Gen {
|
|
|
521
529
|
`;
|
|
522
530
|
}
|
|
523
531
|
|
|
524
|
-
function serializeWriteMethodTs
|
|
532
|
+
function serializeWriteMethodTs(abi: TAbiItem) {
|
|
525
533
|
let { fnInputArguments, callInputArguments } = serializeArgumentsTs(abi);
|
|
526
534
|
if (callInputArguments) {
|
|
527
535
|
callInputArguments = `, ${callInputArguments}`;
|
|
@@ -534,7 +542,7 @@ namespace Gen {
|
|
|
534
542
|
}
|
|
535
543
|
`;
|
|
536
544
|
}
|
|
537
|
-
function serializeConstructorMethodTs
|
|
545
|
+
function serializeConstructorMethodTs(abi: TAbiItem) {
|
|
538
546
|
let { fnInputArguments, callInputArguments } = serializeArgumentsTs(abi);
|
|
539
547
|
if (callInputArguments) {
|
|
540
548
|
callInputArguments = `, ${callInputArguments}`;
|
|
@@ -545,7 +553,7 @@ namespace Gen {
|
|
|
545
553
|
}
|
|
546
554
|
`;
|
|
547
555
|
}
|
|
548
|
-
function serializeWriteMethodTsOverloads
|
|
556
|
+
function serializeWriteMethodTsOverloads(abis: TAbiItem[]) {
|
|
549
557
|
let overrides = abis.map(abi => {
|
|
550
558
|
let { fnInputArguments, fnResult } = serializeArgumentsTs(abi);
|
|
551
559
|
return `
|
|
@@ -557,7 +565,7 @@ namespace Gen {
|
|
|
557
565
|
let abi = abis[0];
|
|
558
566
|
let sigs = abis.map(abi => serializeMethodAbi(abi)).map(x => `'${x}'`).join(', ');
|
|
559
567
|
return `
|
|
560
|
-
${
|
|
568
|
+
${overrides}
|
|
561
569
|
async ${abi.name} (sender: TSender, ...args): Promise<TxWriter> {
|
|
562
570
|
let abi = this.$getAbiItemOverload([ ${sigs} ], args);
|
|
563
571
|
return this.$write(abi, sender, ...args);
|
|
@@ -565,7 +573,7 @@ namespace Gen {
|
|
|
565
573
|
`;
|
|
566
574
|
}
|
|
567
575
|
|
|
568
|
-
function serializeArgumentsTs
|
|
576
|
+
function serializeArgumentsTs(abi: TAbiItem) {
|
|
569
577
|
let inputs = abi.inputs.map((input, i) => {
|
|
570
578
|
let result = { ...input };
|
|
571
579
|
if (result.name == null || result.name === '') {
|
|
@@ -599,10 +607,10 @@ namespace Gen {
|
|
|
599
607
|
return { fnInputArguments, callInputArguments, fnResult };
|
|
600
608
|
}
|
|
601
609
|
|
|
602
|
-
function isObjectParams
|
|
610
|
+
function isObjectParams(params: { name?, type }[]) {
|
|
603
611
|
return params?.every(x => Boolean(x.name));
|
|
604
612
|
}
|
|
605
|
-
function serializeMethodAbiReturns
|
|
613
|
+
function serializeMethodAbiReturns(params: { name?, type, components?}[]) {
|
|
606
614
|
if (params == null) {
|
|
607
615
|
return '';
|
|
608
616
|
}
|
|
@@ -611,23 +619,23 @@ namespace Gen {
|
|
|
611
619
|
// }
|
|
612
620
|
return params?.map(x => serializeMethodAbiReturnsSingle(x)).join(',');
|
|
613
621
|
}
|
|
614
|
-
function serializeMethodAbiReturnsSingle
|
|
622
|
+
function serializeMethodAbiReturnsSingle(param: { name?, type, components?}) {
|
|
615
623
|
if (param == null) {
|
|
616
624
|
return null;
|
|
617
625
|
}
|
|
618
626
|
if (param.components) {
|
|
619
627
|
// tuple, tuple[]
|
|
620
628
|
let fields = serializeMethodAbiReturns(param.components);
|
|
621
|
-
return `[${fields}]${
|
|
629
|
+
return `[${fields}]${param.type === 'tuple[]' ? '[]' : ''}`;
|
|
622
630
|
}
|
|
623
631
|
// if (param.name && param.type) {
|
|
624
632
|
// return `${param.type} ${param.name}`;
|
|
625
633
|
// }
|
|
626
634
|
return param.type;
|
|
627
635
|
}
|
|
628
|
-
function serializeMethodTsReturns
|
|
636
|
+
function serializeMethodTsReturns(params: { name?, type }[]) {
|
|
629
637
|
if (params == null || params.length === 0) {
|
|
630
|
-
params = [
|
|
638
|
+
params = [{ name: '', type: 'uint256' }];
|
|
631
639
|
}
|
|
632
640
|
|
|
633
641
|
let tsTypes = params.map(param => {
|
|
@@ -668,19 +676,19 @@ namespace Gen {
|
|
|
668
676
|
lines.push(` type: '${error.name}'`);
|
|
669
677
|
lines.push(` params: {`);
|
|
670
678
|
error.inputs.forEach(input => {
|
|
671
|
-
lines.push(` ${input.name}: ${
|
|
679
|
+
lines.push(` ${input.name}: ${$abiType.getTsType(input.type, input)}`);
|
|
672
680
|
});
|
|
673
681
|
lines.push(` }`);
|
|
674
682
|
lines.push(` }`);
|
|
675
683
|
});
|
|
676
684
|
|
|
677
|
-
lines.push(` export type Error = ${
|
|
685
|
+
lines.push(` export type Error = ${errors.map(x => x.name).join(' | ')}`);
|
|
678
686
|
lines.push(`}`);
|
|
679
687
|
|
|
680
688
|
return lines.join('\n');
|
|
681
689
|
}
|
|
682
690
|
|
|
683
|
-
export function serializeTxCallerMethods
|
|
691
|
+
export function serializeTxCallerMethods(className: string, abiJson: TAbiItem[]): string {
|
|
684
692
|
let methods = abiJson.filter(x => x.type === 'function');
|
|
685
693
|
let writeMethods = methods.filter(abi => $abiUtils.isReadMethod(abi) === false);
|
|
686
694
|
let lines = [];
|
|
@@ -691,16 +699,16 @@ namespace Gen {
|
|
|
691
699
|
return lines.join('\n');
|
|
692
700
|
|
|
693
701
|
|
|
694
|
-
function serializeMethodTs
|
|
702
|
+
function serializeMethodTs(abi: TAbiItem) {
|
|
695
703
|
let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
|
|
696
704
|
if (callInputArguments) {
|
|
697
705
|
callInputArguments = `, ${callInputArguments}`;
|
|
698
706
|
}
|
|
699
|
-
return
|
|
707
|
+
return ` ${abi.name} (sender: TSender, ${fnInputArguments}): Promise<{ error?: Error & { data?: { type: string, params } }, result? }>`;
|
|
700
708
|
}
|
|
701
709
|
}
|
|
702
710
|
|
|
703
|
-
export function serializeTxDataMethods
|
|
711
|
+
export function serializeTxDataMethods(className: string, abiJson: TAbiItem[]): string {
|
|
704
712
|
let methods = abiJson.filter(x => x.type === 'function');
|
|
705
713
|
let writeMethods = methods.filter(abi => $abiUtils.isReadMethod(abi) === false);
|
|
706
714
|
let lines = [];
|
|
@@ -711,7 +719,7 @@ namespace Gen {
|
|
|
711
719
|
return lines.join('\n');
|
|
712
720
|
|
|
713
721
|
|
|
714
|
-
function serializeInterfaceMethodTs
|
|
722
|
+
function serializeInterfaceMethodTs(abi: TAbiItem) {
|
|
715
723
|
let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi);
|
|
716
724
|
if (callInputArguments) {
|
|
717
725
|
callInputArguments = `, ${callInputArguments}`;
|
|
@@ -30,6 +30,8 @@ import { $address } from '@dequanto/utils/$address';
|
|
|
30
30
|
import { $promise } from '@dequanto/utils/$promise';
|
|
31
31
|
import { $date } from '@dequanto/utils/$date';
|
|
32
32
|
import { $hex } from '@dequanto/utils/$hex';
|
|
33
|
+
import { TAddress } from '@dequanto/models/TAddress';
|
|
34
|
+
import { $contract } from '@dequanto/utils/$contract';
|
|
33
35
|
|
|
34
36
|
export class HardhatProvider {
|
|
35
37
|
|
|
@@ -272,6 +274,7 @@ export class HardhatProvider {
|
|
|
272
274
|
artifact: string
|
|
273
275
|
source: IGeneratorSources
|
|
274
276
|
ContractCtor: Constructor<T>
|
|
277
|
+
linkReferences?: Record<string /* path */, Record<string /* name */, any>>
|
|
275
278
|
}> {
|
|
276
279
|
|
|
277
280
|
solContractPath = $path.normalize(solContractPath);
|
|
@@ -353,7 +356,12 @@ export class HardhatProvider {
|
|
|
353
356
|
});
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
let { abi, bytecode, contractName } = await File.readAsync<{
|
|
359
|
+
let { abi, bytecode, contractName, linkReferences } = await File.readAsync<{
|
|
360
|
+
abi,
|
|
361
|
+
bytecode,
|
|
362
|
+
contractName,
|
|
363
|
+
linkReferences: Record<string /* path */, Record<string /* name */, any>>
|
|
364
|
+
}> (output);
|
|
357
365
|
let files = await Directory.readFilesAsync(dir, '*.sol');
|
|
358
366
|
let { contract, ContractCtor } = ContractClassFactory.fromAbi<T>(null, abi, null, null, {
|
|
359
367
|
$meta: {
|
|
@@ -379,6 +387,7 @@ export class HardhatProvider {
|
|
|
379
387
|
contractName: contractName ?? options?.contractName,
|
|
380
388
|
files: fileMap
|
|
381
389
|
},
|
|
390
|
+
linkReferences,
|
|
382
391
|
ContractCtor
|
|
383
392
|
};
|
|
384
393
|
}
|
|
@@ -399,6 +408,25 @@ export class HardhatProvider {
|
|
|
399
408
|
}
|
|
400
409
|
}
|
|
401
410
|
|
|
411
|
+
async linkReferences (
|
|
412
|
+
bytecode: TEth.Hex,
|
|
413
|
+
linkReferences: Record<string /* path */, Record<string /* name */, any>>,
|
|
414
|
+
addresses: Record<string /* name */, TAddress>
|
|
415
|
+
): Promise<TEth.Hex> {
|
|
416
|
+
for (let path in linkReferences) {
|
|
417
|
+
for (let name in linkReferences[path]) {
|
|
418
|
+
let address = addresses[name];
|
|
419
|
+
$require.AddressNotEmpty(address, `Address for "${path}:${name}" not found`);
|
|
420
|
+
|
|
421
|
+
let str = `${path}:${name}`;
|
|
422
|
+
let hash = $contract.keccak256(str, 'hex').substring(2, 34 + 2);
|
|
423
|
+
let placeholder = `__$${hash}$__`;
|
|
424
|
+
bytecode = bytecode.replaceAll(placeholder, address.substring(2)) as TEth.Hex;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return bytecode;
|
|
428
|
+
}
|
|
429
|
+
|
|
402
430
|
async compileCode (solidityCode: string, options: Parameters<HardhatProvider['deploySol']>[1] = {}) {
|
|
403
431
|
let { tmpFile, tmpDir, options: optionsNormalized } = await this.createTmpFile(solidityCode, options);
|
|
404
432
|
|
package/src/safe/SafeTx.ts
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ContractBase } from '@dequanto/contracts/ContractBase';
|
|
2
|
+
import { IAccount } from '@dequanto/models/TAccount'
|
|
3
|
+
import { TAddress } from '@dequanto/models/TAddress'
|
|
4
|
+
import { TEth } from '@dequanto/models/TEth'
|
|
5
|
+
import { TxWriter } from '@dequanto/txs/TxWriter';
|
|
6
|
+
import { TTxWriteMethodKeys } from '@dequanto/utils/types';
|
|
7
|
+
|
|
8
|
+
export interface ITimelockService {
|
|
9
|
+
|
|
10
|
+
getPendingByTitle (title: string): Promise<{
|
|
11
|
+
status: ETimelockTxStatus;
|
|
12
|
+
schedule: ITimelockTx;
|
|
13
|
+
}>
|
|
14
|
+
|
|
15
|
+
executePendingByTitle (sender: IAccount, title: string): Promise<{
|
|
16
|
+
tx: TxWriter;
|
|
17
|
+
schedule: ITimelockTx;
|
|
18
|
+
}>
|
|
19
|
+
|
|
20
|
+
executePending (sender: IAccount, txParams: ITimelockTx): Promise<{
|
|
21
|
+
tx: TxWriter;
|
|
22
|
+
schedule: ITimelockTx;
|
|
23
|
+
}>
|
|
24
|
+
/** Schedules-Wait-Execute a task distinguished by the unique task name
|
|
25
|
+
*
|
|
26
|
+
* 1. if schedule doesn't exist, create it
|
|
27
|
+
* 2. if schedule date NOT yet ready, exit
|
|
28
|
+
* 3. if schedule date IS ready, execute it
|
|
29
|
+
* 4. if executed, exit
|
|
30
|
+
*/
|
|
31
|
+
process <
|
|
32
|
+
T extends ContractBase,
|
|
33
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
34
|
+
> (
|
|
35
|
+
uniqueTaskName: string,
|
|
36
|
+
sender: IAccount,
|
|
37
|
+
contract: T,
|
|
38
|
+
method: TMethodName,
|
|
39
|
+
...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
|
|
40
|
+
): Promise<{
|
|
41
|
+
prevStatus: ETimelockTxStatus
|
|
42
|
+
status: ETimelockTxStatus
|
|
43
|
+
schedule: ITimelockTx
|
|
44
|
+
tx?: TEth.Hex
|
|
45
|
+
}>
|
|
46
|
+
|
|
47
|
+
/** Schedules-Wait-Execute a task distinguished by the unique task name
|
|
48
|
+
*
|
|
49
|
+
* 1. if schedule doesn't exist, create it
|
|
50
|
+
* 2. if schedule date NOT yet ready, exit
|
|
51
|
+
* 3. if schedule date IS ready, execute it
|
|
52
|
+
* 4. if executed, exit
|
|
53
|
+
*/
|
|
54
|
+
processBatch <
|
|
55
|
+
T extends ContractBase,
|
|
56
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
57
|
+
> (
|
|
58
|
+
uniqueTaskName: string,
|
|
59
|
+
sender: IAccount,
|
|
60
|
+
batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
|
|
61
|
+
): Promise<{
|
|
62
|
+
prevStatus: ETimelockTxStatus
|
|
63
|
+
status: ETimelockTxStatus
|
|
64
|
+
schedule: ITimelockTx
|
|
65
|
+
tx?: TEth.Hex
|
|
66
|
+
}>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ITimelockTx {
|
|
70
|
+
// operation hash, unique as includes random salt
|
|
71
|
+
id: TEth.Hex
|
|
72
|
+
// operation key, should be unique only within pending operations
|
|
73
|
+
key: TEth.Hex
|
|
74
|
+
|
|
75
|
+
salt: TEth.Hex
|
|
76
|
+
|
|
77
|
+
title?: string
|
|
78
|
+
sender: string | TAddress
|
|
79
|
+
|
|
80
|
+
to: TAddress | TAddress[]
|
|
81
|
+
data: TEth.Hex | TEth.Hex[]
|
|
82
|
+
value?: TEth.Hex | TEth.Hex[]
|
|
83
|
+
predecessor?: TEth.Hex
|
|
84
|
+
|
|
85
|
+
createdAt: number
|
|
86
|
+
validAt: number
|
|
87
|
+
|
|
88
|
+
status: 'pending' | 'completed' | 'canceled'
|
|
89
|
+
txSchedule: TEth.Hex
|
|
90
|
+
txExecute?: TEth.Hex
|
|
91
|
+
txCancel?: TEth.Hex
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ITimelockTxParams {
|
|
95
|
+
title?: string
|
|
96
|
+
|
|
97
|
+
sender: IAccount
|
|
98
|
+
to: TAddress | TAddress[]
|
|
99
|
+
data: TEth.Hex | TEth.Hex[]
|
|
100
|
+
|
|
101
|
+
value?: bigint | bigint[]
|
|
102
|
+
predecessor?: TEth.Hex
|
|
103
|
+
delay?: bigint
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ITimelockTxParamsNormalized {
|
|
107
|
+
sender: IAccount
|
|
108
|
+
to: TAddress | TAddress[]
|
|
109
|
+
data: TEth.Hex | TEth.Hex[]
|
|
110
|
+
|
|
111
|
+
title: string
|
|
112
|
+
value: bigint | bigint[]
|
|
113
|
+
predecessor: TEth.Hex
|
|
114
|
+
delay: bigint
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
export enum ETimelockTxStatus {
|
|
119
|
+
None = 'none',
|
|
120
|
+
Pending = 'pending',
|
|
121
|
+
Ready = 'ready',
|
|
122
|
+
Executed = 'completed',
|
|
123
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import memd from 'memd';
|
|
1
2
|
import { TimelockController } from '@dequanto-contracts/openzeppelin/TimelockController';
|
|
2
|
-
import { Web3Client } from '@dequanto/clients/Web3Client';
|
|
3
3
|
import { ContractBase } from '@dequanto/contracts/ContractBase';
|
|
4
4
|
import { JsonArrayStore } from '@dequanto/json/JsonArrayStore';
|
|
5
5
|
import { IAccount } from '@dequanto/models/TAccount';
|
|
@@ -16,69 +16,16 @@ import { $number } from '@dequanto/utils/$number';
|
|
|
16
16
|
import { $platform } from '@dequanto/utils/$platform';
|
|
17
17
|
import { $require } from '@dequanto/utils/$require';
|
|
18
18
|
import { File } from 'atma-io';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
interface ITimelockTx {
|
|
23
|
-
// operation hash, unique as includes random salt
|
|
24
|
-
id: TEth.Hex
|
|
25
|
-
// operation key, should be unique only within pending operations
|
|
26
|
-
key: TEth.Hex
|
|
27
|
-
|
|
28
|
-
salt: TEth.Hex
|
|
29
|
-
|
|
30
|
-
title?: string
|
|
31
|
-
sender: string | TAddress
|
|
32
|
-
|
|
33
|
-
to: TAddress | TAddress[]
|
|
34
|
-
data: TEth.Hex | TEth.Hex[]
|
|
35
|
-
value?: TEth.Hex | TEth.Hex[]
|
|
36
|
-
predecessor?: TEth.Hex
|
|
37
|
-
|
|
38
|
-
createdAt: number
|
|
39
|
-
validAt: number
|
|
40
|
-
|
|
41
|
-
status: 'pending' | 'completed' | 'canceled'
|
|
42
|
-
txSchedule: TEth.Hex
|
|
43
|
-
txExecute?: TEth.Hex
|
|
44
|
-
txCancel?: TEth.Hex
|
|
45
|
-
}
|
|
19
|
+
import { ITimelockTx, ITimelockTxParamsNormalized, ITimelockTxParams, ITimelockService, ETimelockTxStatus } from './ITimelockService';
|
|
20
|
+
import { TTxWriteMethodKeys } from '@dequanto/utils/types';
|
|
46
21
|
|
|
47
|
-
interface ITimelockTxParams {
|
|
48
|
-
title?: string
|
|
49
22
|
|
|
50
|
-
sender: IAccount
|
|
51
|
-
to: TAddress | TAddress[]
|
|
52
|
-
data: TEth.Hex | TEth.Hex[]
|
|
53
|
-
|
|
54
|
-
value?: bigint | bigint[]
|
|
55
|
-
predecessor?: TEth.Hex
|
|
56
|
-
delay?: bigint
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface ITimelockTxParamsNormalized {
|
|
60
|
-
sender: IAccount
|
|
61
|
-
to: TAddress | TAddress[]
|
|
62
|
-
data: TEth.Hex | TEth.Hex[]
|
|
63
|
-
|
|
64
|
-
title: string
|
|
65
|
-
value: bigint | bigint[]
|
|
66
|
-
predecessor: TEth.Hex
|
|
67
|
-
delay: bigint
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export enum ETimelockTxStatus {
|
|
71
|
-
None = 'none',
|
|
72
|
-
Pending = 'pending',
|
|
73
|
-
Ready = 'ready',
|
|
74
|
-
Executed = 'completed',
|
|
75
|
-
}
|
|
76
23
|
|
|
77
24
|
type TTimelockController = ContractBase & Pick<TimelockController,
|
|
78
25
|
'schedule' | 'scheduleBatch' | 'execute' | 'executeBatch' | 'getMinDelay' | 'cancel'
|
|
79
26
|
>;
|
|
80
27
|
|
|
81
|
-
export class TimelockService {
|
|
28
|
+
export class TimelockService implements ITimelockService {
|
|
82
29
|
|
|
83
30
|
|
|
84
31
|
constructor(
|
|
@@ -90,7 +37,7 @@ export class TimelockService {
|
|
|
90
37
|
// Only for hardhat client. Default: false
|
|
91
38
|
execute?: boolean
|
|
92
39
|
|
|
93
|
-
// Directory to store the submitted schedules, default: `./data
|
|
40
|
+
// Directory to store the submitted schedules, default: `./0x/data/`
|
|
94
41
|
dir?: string
|
|
95
42
|
}
|
|
96
43
|
) {
|
|
@@ -143,7 +90,7 @@ export class TimelockService {
|
|
|
143
90
|
*/
|
|
144
91
|
async process <
|
|
145
92
|
T extends ContractBase,
|
|
146
|
-
TMethodName extends
|
|
93
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
147
94
|
> (
|
|
148
95
|
uniqueTaskName: string,
|
|
149
96
|
sender: IAccount,
|
|
@@ -170,7 +117,7 @@ export class TimelockService {
|
|
|
170
117
|
*/
|
|
171
118
|
async processBatch <
|
|
172
119
|
T extends ContractBase,
|
|
173
|
-
TMethodName extends
|
|
120
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
174
121
|
> (
|
|
175
122
|
uniqueTaskName: string,
|
|
176
123
|
sender: IAccount,
|
|
@@ -225,7 +172,7 @@ export class TimelockService {
|
|
|
225
172
|
|
|
226
173
|
async scheduleCall <
|
|
227
174
|
T extends ContractBase,
|
|
228
|
-
TMethodName extends
|
|
175
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
229
176
|
> (
|
|
230
177
|
sender: IAccount,
|
|
231
178
|
contract: T,
|
|
@@ -253,7 +200,7 @@ export class TimelockService {
|
|
|
253
200
|
|
|
254
201
|
async executeCall <
|
|
255
202
|
T extends ContractBase,
|
|
256
|
-
TMethodName extends
|
|
203
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
257
204
|
> (
|
|
258
205
|
sender: IAccount,
|
|
259
206
|
contract: T,
|
|
@@ -587,7 +534,7 @@ export class TimelockService {
|
|
|
587
534
|
}
|
|
588
535
|
private async getTxParamsNormalizedFromContract <
|
|
589
536
|
T extends ContractBase,
|
|
590
|
-
TMethodName extends
|
|
537
|
+
TMethodName extends TTxWriteMethodKeys<T>,
|
|
591
538
|
> (
|
|
592
539
|
title: string | '' | null,
|
|
593
540
|
sender: IAccount,
|
|
@@ -630,7 +577,7 @@ export class TimelockService {
|
|
|
630
577
|
@memd.deco.memoize({ perInstance: true })
|
|
631
578
|
private async getStore () {
|
|
632
579
|
let { platform, network } = this.timelock.client;
|
|
633
|
-
let dir = this.options?.dir ?? `data
|
|
580
|
+
let dir = this.options?.dir ?? `0x/data`;
|
|
634
581
|
|
|
635
582
|
let path = getPath(platform, dir);
|
|
636
583
|
if (platform === 'hardhat' && network !== platform) {
|
|
@@ -641,7 +588,7 @@ export class TimelockService {
|
|
|
641
588
|
}
|
|
642
589
|
}
|
|
643
590
|
function getPath (p: TPlatform, directory: string) {
|
|
644
|
-
return `/${directory}/timelocks
|
|
591
|
+
return `/${directory}/timelocks/${ $platform.toPath(p) }.json`
|
|
645
592
|
}
|
|
646
593
|
return new JsonArrayStore<ITimelockTx>({
|
|
647
594
|
path,
|
|
@@ -650,12 +597,6 @@ export class TimelockService {
|
|
|
650
597
|
}
|
|
651
598
|
}
|
|
652
599
|
|
|
653
|
-
type WriteMethodKeys<T> = {
|
|
654
|
-
[P in keyof T]: T[P] extends ((sender: IAccount, ...args) => (Promise<TxWriter>)) ? P : never;
|
|
655
|
-
}[keyof T];
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
600
|
namespace util {
|
|
660
601
|
export function toBigInt (mix: string | string[]) {
|
|
661
602
|
if (typeof mix === 'string') {
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { TimelockController } from '@dequanto-contracts/openzeppelin/TimelockController';
|
|
2
|
+
import { ContractBase } from '@dequanto/contracts/ContractBase';
|
|
3
|
+
import { IAccount } from '@dequanto/models/TAccount';
|
|
4
|
+
import { TAddress } from '@dequanto/models/TAddress';
|
|
5
|
+
import { TEth } from '@dequanto/models/TEth';
|
|
6
|
+
import { SafeTx } from '@dequanto/safe/SafeTx';
|
|
7
|
+
import { TxWriter } from '@dequanto/txs/TxWriter';
|
|
8
|
+
import { $contract } from '@dequanto/utils/$contract';
|
|
9
|
+
import { $hex } from '@dequanto/utils/$hex';
|
|
10
|
+
import memd from 'memd';
|
|
11
|
+
import { ETimelockTxStatus, ITimelockTx, ITimelockTxParamsNormalized, ITimelockTxParams, ITimelockService } from './ITimelockService';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export class TimelockServiceFallbackSafe implements ITimelockService {
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private safeTx: SafeTx,
|
|
19
|
+
private options?: {
|
|
20
|
+
// Only for hardhat client. Default: true
|
|
21
|
+
simulate?: boolean
|
|
22
|
+
|
|
23
|
+
// Only for hardhat client. Default: false
|
|
24
|
+
execute?: boolean
|
|
25
|
+
|
|
26
|
+
// Directory to store the submitted schedules, default: `./0x/data/`
|
|
27
|
+
dir?: string
|
|
28
|
+
}
|
|
29
|
+
) {
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getPendingByTitle(title: string): Promise<{
|
|
34
|
+
status: ETimelockTxStatus;
|
|
35
|
+
schedule: ITimelockTx;
|
|
36
|
+
}> {
|
|
37
|
+
return { status: ETimelockTxStatus.None, schedule: null };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async executePendingByTitle(sender: IAccount, title: string): Promise<{
|
|
41
|
+
tx: TxWriter;
|
|
42
|
+
schedule: ITimelockTx;
|
|
43
|
+
}> {
|
|
44
|
+
throw new Error(`No pending tx found ${title}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async executePending(sender: IAccount, txParams: ITimelockTx): Promise<{
|
|
48
|
+
tx: TxWriter;
|
|
49
|
+
schedule: ITimelockTx;
|
|
50
|
+
}> {
|
|
51
|
+
let result = await this.execute({
|
|
52
|
+
...txParams,
|
|
53
|
+
value: util.toBigInt(txParams.value),
|
|
54
|
+
sender: sender,
|
|
55
|
+
});
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Schedules-Wait-Execute a task distinguished by the unique task name
|
|
60
|
+
*
|
|
61
|
+
* 1. if schedule doesn't exist, create it
|
|
62
|
+
* 2. if schedule date NOT yet ready, exit
|
|
63
|
+
* 3. if schedule date IS ready, execute it
|
|
64
|
+
* 4. if executed, exit
|
|
65
|
+
*/
|
|
66
|
+
async process<
|
|
67
|
+
T extends ContractBase,
|
|
68
|
+
TMethodName extends WriteMethodKeys<T>,
|
|
69
|
+
>(
|
|
70
|
+
uniqueTaskName: string,
|
|
71
|
+
sender: IAccount,
|
|
72
|
+
contract: T,
|
|
73
|
+
method: TMethodName,
|
|
74
|
+
...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
|
|
75
|
+
): Promise<{
|
|
76
|
+
prevStatus: ETimelockTxStatus
|
|
77
|
+
status: ETimelockTxStatus
|
|
78
|
+
schedule: ITimelockTx
|
|
79
|
+
tx?: TEth.Hex
|
|
80
|
+
}> {
|
|
81
|
+
|
|
82
|
+
let txParams = await this.getTxParamsNormalizedFromContract(uniqueTaskName, sender, contract, method, ...params);
|
|
83
|
+
return await this.processTxParams(txParams);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Schedules-Wait-Execute a task distinguished by the unique task name
|
|
87
|
+
*
|
|
88
|
+
* 1. if schedule doesn't exist, create it
|
|
89
|
+
* 2. if schedule date NOT yet ready, exit
|
|
90
|
+
* 3. if schedule date IS ready, execute it
|
|
91
|
+
* 4. if executed, exit
|
|
92
|
+
*/
|
|
93
|
+
async processBatch<
|
|
94
|
+
T extends ContractBase,
|
|
95
|
+
TMethodName extends WriteMethodKeys<T>,
|
|
96
|
+
>(
|
|
97
|
+
uniqueTaskName: string,
|
|
98
|
+
sender: IAccount,
|
|
99
|
+
batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
|
|
100
|
+
): Promise<{
|
|
101
|
+
prevStatus: ETimelockTxStatus
|
|
102
|
+
status: ETimelockTxStatus
|
|
103
|
+
schedule: ITimelockTx
|
|
104
|
+
tx?: TEth.Hex
|
|
105
|
+
}> {
|
|
106
|
+
|
|
107
|
+
let txParams = await this.getTxParamsNormalizedFromContractBatch(uniqueTaskName, sender, batch);
|
|
108
|
+
return await this.processTxParams(txParams);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private async processTxParams(txParams: ITimelockTxParamsNormalized): Promise<{
|
|
112
|
+
prevStatus: ETimelockTxStatus
|
|
113
|
+
status: ETimelockTxStatus
|
|
114
|
+
schedule: ITimelockTx
|
|
115
|
+
tx?: TEth.Hex
|
|
116
|
+
}> {
|
|
117
|
+
|
|
118
|
+
let result = await this.execute(txParams);
|
|
119
|
+
return {
|
|
120
|
+
prevStatus: ETimelockTxStatus.None,
|
|
121
|
+
status: ETimelockTxStatus.Executed,
|
|
122
|
+
schedule: result.schedule,
|
|
123
|
+
tx: result.tx.tx?.hash
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async scheduleCall<
|
|
128
|
+
T extends ContractBase,
|
|
129
|
+
TMethodName extends WriteMethodKeys<T>,
|
|
130
|
+
>(
|
|
131
|
+
sender: IAccount,
|
|
132
|
+
contract: T,
|
|
133
|
+
method: TMethodName,
|
|
134
|
+
...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
|
|
135
|
+
) {
|
|
136
|
+
let txParams = await this.getTxParamsNormalizedFromContract('', sender, contract, method, ...params);
|
|
137
|
+
let tx = await this.schedule(txParams);
|
|
138
|
+
return tx;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* E.g. `.scheduleCallBatch(title, sender, [ c1.$data().foo(sender), c2.$data().bar(sender, param1) ])`
|
|
143
|
+
*/
|
|
144
|
+
async scheduleCallBatch(
|
|
145
|
+
title: string,
|
|
146
|
+
sender: IAccount,
|
|
147
|
+
batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
|
|
148
|
+
) {
|
|
149
|
+
let txParams = await this.getTxParamsNormalizedFromContractBatch(title, sender, batch);
|
|
150
|
+
let tx = await this.schedule(txParams);
|
|
151
|
+
return tx;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async executeCall<
|
|
156
|
+
T extends ContractBase,
|
|
157
|
+
TMethodName extends WriteMethodKeys<T>,
|
|
158
|
+
>(
|
|
159
|
+
sender: IAccount,
|
|
160
|
+
contract: T,
|
|
161
|
+
method: TMethodName,
|
|
162
|
+
...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
|
|
163
|
+
) {
|
|
164
|
+
let txParams = await this.getTxParamsNormalizedFromContract('', sender, contract, method, ...params);
|
|
165
|
+
let tx = await this.execute(txParams);
|
|
166
|
+
return tx;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* E.g. `.scheduleCallBatch(title, sender, [ c1.$data().foo(sender), c2.$data().bar(sender, param1) ])`
|
|
171
|
+
*/
|
|
172
|
+
async executeCallBatch(
|
|
173
|
+
title: string,
|
|
174
|
+
sender: IAccount,
|
|
175
|
+
batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
|
|
176
|
+
) {
|
|
177
|
+
let txParams = await this.getTxParamsNormalizedFromContractBatch(title, sender, batch);
|
|
178
|
+
let tx = await this.execute(txParams);
|
|
179
|
+
return tx;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@memd.deco.queued()
|
|
183
|
+
async schedule(params: ITimelockTxParams): Promise<ITimelockTx> {
|
|
184
|
+
let executed = await this.execute(params);
|
|
185
|
+
return executed.schedule;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async execute(params: ITimelockTxParams): Promise<{
|
|
189
|
+
tx: TxWriter
|
|
190
|
+
schedule: ITimelockTx
|
|
191
|
+
}> {
|
|
192
|
+
let txParams = await this.getTxParamsNormalized(params);
|
|
193
|
+
let isBatch = Array.isArray(txParams.to);
|
|
194
|
+
let tx = isBatch
|
|
195
|
+
? await this.safeTx.executeBatch(
|
|
196
|
+
...(txParams.to as TAddress[]).map((to, i) => {
|
|
197
|
+
return {
|
|
198
|
+
to,
|
|
199
|
+
value: (txParams.value as bigint[])[i],
|
|
200
|
+
data: (txParams.data as TEth.Hex[])[i]
|
|
201
|
+
}
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
: await this.safeTx.execute(
|
|
205
|
+
{
|
|
206
|
+
to: txParams.to as TAddress,
|
|
207
|
+
value: txParams.value as bigint,
|
|
208
|
+
data: txParams.data as TEth.Hex
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
let receipt = await tx.wait();
|
|
213
|
+
return {
|
|
214
|
+
tx: tx,
|
|
215
|
+
schedule: {
|
|
216
|
+
txExecute: receipt.transactionHash,
|
|
217
|
+
status: ETimelockTxStatus.Executed,
|
|
218
|
+
} as ITimelockTx
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async cancel(sender: IAccount, id: TEth.Hex, opts?: { storage?: boolean }) {
|
|
223
|
+
throw new Error(`Not implemented for Safe Fallback`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public async clearSchedules() {
|
|
227
|
+
// SafeFallback has no schedules
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public async updateSchedule(txInfo: Partial<ITimelockTx>) {
|
|
231
|
+
// SafeFallback has no schedules
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public async debugMoveToSchedule(txInfo: ITimelockTx) {
|
|
235
|
+
// SafeFallback has no schedules
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async getTxParamsNormalized(params: ITimelockTxParams): Promise<ITimelockTxParamsNormalized> {
|
|
239
|
+
let value = params.value ?? 0n;
|
|
240
|
+
let data = params.data;
|
|
241
|
+
let predecessor = params.predecessor ?? $hex.ZERO;
|
|
242
|
+
let delay = 0n;
|
|
243
|
+
return {
|
|
244
|
+
title: params.title ?? '',
|
|
245
|
+
sender: params.sender,
|
|
246
|
+
to: params.to,
|
|
247
|
+
|
|
248
|
+
value,
|
|
249
|
+
data,
|
|
250
|
+
predecessor,
|
|
251
|
+
delay
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
private async getTxParamsNormalizedFromContract<
|
|
255
|
+
T extends ContractBase,
|
|
256
|
+
TMethodName extends WriteMethodKeys<T>,
|
|
257
|
+
>(
|
|
258
|
+
title: string | '' | null,
|
|
259
|
+
sender: IAccount,
|
|
260
|
+
contract: T,
|
|
261
|
+
method: TMethodName,
|
|
262
|
+
...params: T[TMethodName] extends (sender: IAccount, ...args: infer A) => any ? A : never
|
|
263
|
+
): Promise<ITimelockTxParamsNormalized> {
|
|
264
|
+
|
|
265
|
+
let txData = await contract.$data()[method](sender, ...params);
|
|
266
|
+
let abi = contract.abi?.find(x => x.name === method);
|
|
267
|
+
let methodCallStr = $contract.formatCallFromAbi(abi, params);
|
|
268
|
+
|
|
269
|
+
title ??= `${contract.constructor.name}.${methodCallStr}`;
|
|
270
|
+
|
|
271
|
+
let tx = await this.getTxParamsNormalized({
|
|
272
|
+
title,
|
|
273
|
+
sender,
|
|
274
|
+
to: txData.to,
|
|
275
|
+
data: txData.data,
|
|
276
|
+
})
|
|
277
|
+
return tx;
|
|
278
|
+
}
|
|
279
|
+
private async getTxParamsNormalizedFromContractBatch(
|
|
280
|
+
title: string | '' | null,
|
|
281
|
+
sender: IAccount,
|
|
282
|
+
batch: Pick<TEth.TxLike, 'to' | 'data' | 'value'>[],
|
|
283
|
+
): Promise<ITimelockTxParamsNormalized> {
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
let tx = await this.getTxParamsNormalized({
|
|
287
|
+
title,
|
|
288
|
+
sender,
|
|
289
|
+
to: batch.map(x => x.to),
|
|
290
|
+
data: batch.map(x => x.data),
|
|
291
|
+
value: batch.map(x => BigInt(x.value)),
|
|
292
|
+
})
|
|
293
|
+
return tx;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
type WriteMethodKeys<T> = {
|
|
298
|
+
[P in keyof T]: T[P] extends ((sender: IAccount, ...args) => (Promise<TxWriter>)) ? P : never;
|
|
299
|
+
}[keyof T];
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
namespace util {
|
|
304
|
+
export function toBigInt(mix: string | string[]) {
|
|
305
|
+
if (typeof mix === 'string') {
|
|
306
|
+
return BigInt(mix);
|
|
307
|
+
}
|
|
308
|
+
return mix.map(BigInt);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -83,7 +83,7 @@ export class TokenTransferService {
|
|
|
83
83
|
token = await this.getToken(token);
|
|
84
84
|
amount = this.getAmount(amount, token);
|
|
85
85
|
|
|
86
|
-
let isNative = this.tokenService.isNative(token
|
|
86
|
+
let isNative = this.tokenService.isNative(token);
|
|
87
87
|
if (isNative) {
|
|
88
88
|
return this.transferNative(from, to, amount);
|
|
89
89
|
}
|
|
@@ -98,7 +98,7 @@ export class TokenTransferService {
|
|
|
98
98
|
return token;
|
|
99
99
|
}
|
|
100
100
|
isNativeToken (token: string | IToken): boolean {
|
|
101
|
-
return this.tokenService.isNative(typeof token === 'string' ? token : token.address);
|
|
101
|
+
return this.tokenService.isNative(typeof token === 'string' ? token : (token.address ?? token.symbol));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
private async transferNativeAll (from: EoAccount, to: TAddress, opts?: { remainder: number | bigint }): Promise<TxWriter> {
|
package/src/utils/types.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { IAccount } from '@dequanto/models/TAccount';
|
|
1
2
|
import { TEth } from '@dequanto/models/TEth';
|
|
3
|
+
import { TxWriter } from '@dequanto/txs/TxWriter';
|
|
2
4
|
|
|
3
5
|
type NoneMethodKeys<T> = {
|
|
4
6
|
[P in keyof T]: T[P] extends ((...args) => any) ? never : P;
|
|
@@ -31,11 +33,9 @@ export type ThenArg<T> = T extends PromiseLike<infer U> ? U : T
|
|
|
31
33
|
|
|
32
34
|
export type ParametersFromSecond<T extends (x, ...args: any) => any> = T extends (x, ...args: infer P) => any ? P : never;
|
|
33
35
|
|
|
34
|
-
|
|
35
36
|
export type TCallback<TResult = any> = (error: Error, result?: TResult) => void
|
|
36
37
|
export type TFnWithCallback<TArgs extends any[], TResult> = (...args: [...TArgs, TCallback<TResult>]) => void
|
|
37
38
|
|
|
38
|
-
|
|
39
39
|
export type DataLike<T> = T extends bigint
|
|
40
40
|
? bigint | number | TEth.Hex
|
|
41
41
|
: (T extends number
|
|
@@ -49,10 +49,12 @@ export type DataLike<T> = T extends bigint
|
|
|
49
49
|
)
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
52
|
export type TOverrideReturns<TObject extends { }, TReturn> = {
|
|
55
53
|
[P in keyof TObject]: TObject[P] extends (...args: infer TParams) => any
|
|
56
54
|
? (...args: TParams) => TReturn
|
|
57
55
|
: never;
|
|
58
56
|
}
|
|
57
|
+
|
|
58
|
+
export type TTxWriteMethodKeys<T> = {
|
|
59
|
+
[P in keyof T]: T[P] extends ((sender: IAccount, ...args) => (Promise<TxWriter>)) ? P : never;
|
|
60
|
+
}[keyof T];
|
package/test/ns.spec.ts
CHANGED
|
@@ -26,7 +26,7 @@ UTest({
|
|
|
26
26
|
eq_(urlRecord, 'https://vitalik.ca');
|
|
27
27
|
|
|
28
28
|
let { value: contentHash } = await ns.getContent('vitalik.eth');
|
|
29
|
-
eq_(contentHash, `ipfs://
|
|
29
|
+
eq_(contentHash, `ipfs://QmdTpYZMWsNFh6ja6SYUrmDQ6YnBc2vpS1QkV9gfekociF`);
|
|
30
30
|
},
|
|
31
31
|
async 'reverse registrar' () {
|
|
32
32
|
let ns = new NameService(Web3ClientFactory.get('eth'));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TimelockController } from '@dequanto-contracts/openzeppelin/TimelockController';
|
|
2
2
|
import { HardhatProvider } from '@dequanto/hardhat/HardhatProvider'
|
|
3
|
-
import { TimelockService } from '@dequanto/services/TimelockService';
|
|
3
|
+
import { TimelockService } from '@dequanto/services/TimelockService/TimelockService';
|
|
4
4
|
import { $address } from '@dequanto/utils/$address';
|
|
5
5
|
import { $date } from '@dequanto/utils/$date';
|
|
6
6
|
import { l } from '@dequanto/utils/$logger';
|
package/tools/copy-dts.ts
CHANGED
|
@@ -12,7 +12,7 @@ async function process () {
|
|
|
12
12
|
await await alot(files).forEachAsync(async (file, i) => {
|
|
13
13
|
|
|
14
14
|
let path = file.uri.toString();
|
|
15
|
-
let typesRoot = path.replace(/\/lib\/types
|
|
15
|
+
let typesRoot = path.replace(/\/lib\/types\/.+$/, '/lib/types/');
|
|
16
16
|
let cjsFile = path.replace('/lib/types/', '/lib/cjs/').replace(/\.d\.ts$/, '.js');
|
|
17
17
|
let mjsFile = path.replace('/lib/types/', '/lib/esm/').replace(/\.d\.ts$/, '.mjs');
|
|
18
18
|
|
|
@@ -28,8 +28,10 @@ async function process () {
|
|
|
28
28
|
|
|
29
29
|
content = content.replace(/from (?<q>['"])(?<path>@dequanto\/[^'"]+)['"]/g, (match, q, importPath) => {
|
|
30
30
|
let importPathFull = importPath.replace(/^@dequanto\//, typesRoot);
|
|
31
|
-
let importPathRelative = new class_Uri(importPathFull).toRelativeString(
|
|
32
|
-
|
|
31
|
+
let importPathRelative = new class_Uri(importPathFull).toRelativeString(path + '.js');
|
|
32
|
+
if (importPathRelative.startsWith('.') === false) {
|
|
33
|
+
importPathRelative = './' + importPathRelative;
|
|
34
|
+
}
|
|
33
35
|
return `from ${q}${importPathRelative}${q}`;
|
|
34
36
|
});
|
|
35
37
|
|