dequanto 0.2.50 → 0.2.51
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/explorer/BlockchainExplorer.js +9 -9
- package/lib/cjs/explorer/BlockchainExplorer.js.map +1 -1
- package/lib/cjs/explorer/BlockchainExplorerFactory.js +1 -1
- package/lib/cjs/explorer/BlockchainExplorerFactory.js.map +1 -1
- package/lib/cjs/explorer/BlockchainExplorerProvider.js +1 -1
- package/lib/cjs/explorer/BlockchainExplorerProvider.js.map +1 -1
- package/lib/cjs/explorer/BlockchainExplorerStorage.js +2 -2
- package/lib/cjs/explorer/BlockchainExplorerStorage.js.map +1 -1
- package/lib/cjs/explorer/ContractVerifier.js +10 -10
- package/lib/cjs/explorer/ContractVerifier.js.map +1 -1
- package/lib/cjs/explorer/verifiers/FsHtmlVerifier.js +1 -1
- package/lib/cjs/explorer/verifiers/FsHtmlVerifier.js.map +1 -1
- package/lib/esm/explorer/BlockchainExplorer.js.map +1 -1
- package/lib/esm/explorer/BlockchainExplorer.mjs +9 -9
- package/lib/esm/explorer/BlockchainExplorerFactory.js.map +1 -1
- package/lib/esm/explorer/BlockchainExplorerFactory.mjs +1 -1
- package/lib/esm/explorer/BlockchainExplorerProvider.js.map +1 -1
- package/lib/esm/explorer/BlockchainExplorerProvider.mjs +1 -1
- package/lib/esm/explorer/BlockchainExplorerStorage.js.map +1 -1
- package/lib/esm/explorer/BlockchainExplorerStorage.mjs +2 -2
- package/lib/esm/explorer/ContractVerifier.js.map +1 -1
- package/lib/esm/explorer/ContractVerifier.mjs +10 -10
- package/lib/esm/explorer/verifiers/FsHtmlVerifier.js.map +1 -1
- package/lib/esm/explorer/verifiers/FsHtmlVerifier.mjs +1 -1
- package/lib/types/explorer/BlockchainExplorerFactory.d.ts +1 -1
- package/package.json +1 -1
- package/skills/README.md +45 -0
- package/{docs/ai → skills/dequanto}/INDEX.md +12 -2
- package/{docs/ai/skill → skills}/dequanto/SKILL.md +27 -17
- package/{docs/ai → skills/dequanto}/examples/config.spec.ts +5 -5
- package/{docs/ai → skills/dequanto}/examples/contract-read.spec.ts +7 -7
- package/{docs/ai → skills/dequanto}/examples/contract-write.spec.ts +7 -7
- package/{docs/ai → skills/dequanto}/examples/deploy-contract.spec.ts +5 -5
- package/{docs/ai → skills/dequanto}/examples/erc4337-userop.spec.ts +7 -7
- package/{docs/ai → skills/dequanto}/examples/explorer-abi.spec.ts +3 -3
- package/{docs/ai → skills/dequanto}/examples/generate-contract-class.spec.ts +2 -2
- package/skills/dequanto/examples/hardhat-debug.spec.ts +29 -0
- package/skills/dequanto/examples/hardhat-fork.spec.ts +31 -0
- package/{docs/ai → skills/dequanto}/examples/index-events.spec.ts +4 -4
- package/{docs/ai → skills/dequanto}/examples/rpc-read.spec.ts +21 -4
- package/{docs/ai → skills/dequanto}/examples/safe-batch.spec.ts +6 -6
- package/{docs/ai → skills/dequanto}/examples/send-transaction.spec.ts +7 -7
- package/skills/dequanto/examples/storage.spec.ts +58 -0
- package/{docs/ai → skills/dequanto}/examples/token-transfer.spec.ts +5 -5
- package/skills/dequanto/examples/utils.spec.ts +63 -0
- package/{docs/ai → skills/dequanto}/references/contracts.md +1 -1
- package/{docs/ai → skills/dequanto}/references/deployments.md +4 -2
- package/{docs/ai → skills/dequanto}/references/events-indexing.md +1 -1
- package/{docs/ai → skills/dequanto}/references/explorer.md +1 -1
- package/{docs/ai → skills/dequanto}/references/generation.md +6 -6
- package/skills/dequanto/references/hardhat.md +113 -0
- package/{docs/ai → skills/dequanto}/references/rpc-clients.md +25 -5
- package/{docs/ai → skills/dequanto}/references/safe-erc4337.md +2 -2
- package/skills/dequanto/references/storage.md +122 -0
- package/{docs/ai → skills/dequanto}/references/tokens-accounts.md +12 -6
- package/{docs/ai → skills/dequanto}/references/transactions.md +6 -4
- package/skills/dequanto/references/utils.md +131 -0
- package/src/explorer/BlockchainExplorer.ts +9 -9
- package/src/explorer/BlockchainExplorerFactory.ts +1 -1
- package/src/explorer/BlockchainExplorerProvider.ts +1 -1
- package/src/explorer/BlockchainExplorerStorage.ts +2 -2
- package/src/explorer/ContractVerifier.ts +10 -10
- package/src/explorer/verifiers/FsHtmlVerifier.ts +1 -1
- package/tsconfig.json +2 -1
- package/docs/ai/examples/storage-read.spec.ts +0 -35
- package/docs/ai/references/storage.md +0 -93
- /package/{docs/ai → skills/dequanto}/references/migration-from-ethers-viem.md +0 -0
|
@@ -22,6 +22,8 @@ Useful tests:
|
|
|
22
22
|
|
|
23
23
|
## TxWriter Lifecycle
|
|
24
24
|
|
|
25
|
+
For transactions from any address on a Hardhat development network or fork, pass `{ address, type: 'impersonated' }` as the account. The writer enables impersonation automatically. See [Hardhat development and forking](hardhat.md#impersonate-an-account) for a complete ERC20 transfer example.
|
|
26
|
+
|
|
25
27
|
Generated write methods return a `TxWriter`.
|
|
26
28
|
|
|
27
29
|
```ts
|
|
@@ -45,7 +47,7 @@ const receipt = await tx.wait();
|
|
|
45
47
|
Use `BatchAgent` when a script should run its full transaction flow without immediately submitting transactions on-chain. `BatchAgent` plugs into `TxWriter.DEFAULTS.agent`, intercepts write calls, returns completed mock writers/receipts to the script, and caches the transactions for later review or execution.
|
|
46
48
|
|
|
47
49
|
```ts
|
|
48
|
-
import { BatchAgent } from '
|
|
50
|
+
import { BatchAgent } from 'dequanto/txs/agents/BatchAgent';
|
|
49
51
|
|
|
50
52
|
const batch = new BatchAgent().enable();
|
|
51
53
|
|
|
@@ -76,7 +78,7 @@ Important behavior:
|
|
|
76
78
|
## Build And Sign Raw Tx
|
|
77
79
|
|
|
78
80
|
```ts
|
|
79
|
-
import { TxDataBuilder } from '
|
|
81
|
+
import { TxDataBuilder } from 'dequanto/txs/TxDataBuilder';
|
|
80
82
|
|
|
81
83
|
const builder = new TxDataBuilder(client, sender, {
|
|
82
84
|
to: receiver
|
|
@@ -93,7 +95,7 @@ const receipt = await client.sendSignedTransaction(signed);
|
|
|
93
95
|
## Send Existing Tx Data
|
|
94
96
|
|
|
95
97
|
```ts
|
|
96
|
-
import { TxWriter } from '
|
|
98
|
+
import { TxWriter } from 'dequanto/txs/TxWriter';
|
|
97
99
|
|
|
98
100
|
const tx = await TxWriter.writeTxData(client, {
|
|
99
101
|
to: receiver,
|
|
@@ -133,7 +135,7 @@ const service = new TokenTransferService(client).$config({ nonce });
|
|
|
133
135
|
Use `TxLogParser` for receipts when no generated contract parser is available.
|
|
134
136
|
|
|
135
137
|
```ts
|
|
136
|
-
import { TxLogParser } from '
|
|
138
|
+
import { TxLogParser } from 'dequanto/txs/receipt/TxLogParser';
|
|
137
139
|
|
|
138
140
|
const parser = new TxLogParser();
|
|
139
141
|
const logs = await parser.parse(receipt, { abi });
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Utility Helpers
|
|
2
|
+
|
|
3
|
+
Dequanto includes helpers for common EVM and TypeScript operations. Prefer them when they make contract code shorter and more consistent. Verify unfamiliar helpers in the installed declarations.
|
|
4
|
+
|
|
5
|
+
Key source files:
|
|
6
|
+
|
|
7
|
+
- `src/abi/$abi.ts`
|
|
8
|
+
- `src/utils/$contract.ts`
|
|
9
|
+
- `src/utils/$require.ts`
|
|
10
|
+
- `src/utils/$hex.ts`
|
|
11
|
+
- `src/utils/$date.ts`
|
|
12
|
+
- `src/utils/$sig.ts`
|
|
13
|
+
- `src/utils/$promise.ts`
|
|
14
|
+
|
|
15
|
+
Runnable example: [utility helpers](../examples/utils.spec.ts).
|
|
16
|
+
|
|
17
|
+
## ABI Encoding And Decoding
|
|
18
|
+
|
|
19
|
+
Import `$abi` from `dequanto/abi/$abi`. Use it instead of another ABI coder or manually constructed calldata.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { $abi } from 'dequanto/abi/$abi';
|
|
23
|
+
|
|
24
|
+
const encoded = $abi.encode(['address', 'uint256'], [account, 5n]);
|
|
25
|
+
const [decodedAccount, decodedAmount] = $abi.decode(['address', 'uint256'], encoded);
|
|
26
|
+
const calldata = $abi.encodeCall(
|
|
27
|
+
'function transfer(address to, uint256 amount)',
|
|
28
|
+
[account, 5n]
|
|
29
|
+
);
|
|
30
|
+
const totalSupply = $abi.decodeReturn<bigint>(
|
|
31
|
+
'function totalSupply() view returns (uint256)',
|
|
32
|
+
responseHex
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `encode` and `decode` accept one type/value or arrays of types/values.
|
|
37
|
+
- `encodeCall` accepts a Solidity function declaration or JSON ABI item.
|
|
38
|
+
- `decodeReturn` accepts a function declaration, JSON ABI item, or output definitions. Multiple named outputs are returned as an object.
|
|
39
|
+
|
|
40
|
+
## Solidity-Compatible Keccak-256
|
|
41
|
+
|
|
42
|
+
Use `$contract.keccak256(...)` for the same Keccak-256 operation used by Solidity:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { $contract } from 'dequanto/utils/$contract';
|
|
46
|
+
|
|
47
|
+
const topic = $contract.keccak256('Transfer(address,address,uint256)');
|
|
48
|
+
const bytes = $contract.keccak256('0x1234', 'buffer');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A string beginning with `0x` is hashed as bytes; other strings are UTF-8 encoded. The default output is hex. Pass `'buffer'` for a `Uint8Array`.
|
|
52
|
+
|
|
53
|
+
## Runtime Requirements
|
|
54
|
+
|
|
55
|
+
`$require` provides compact runtime validation similar in purpose to Solidity's `require`:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { $require } from 'dequanto/utils/$require';
|
|
59
|
+
|
|
60
|
+
const address = $require.AddressNotEmpty(inputAddress, 'Recipient is required');
|
|
61
|
+
$require.BigInt(amount, 'Amount must be bigint', { min: 1n });
|
|
62
|
+
$require.gte(balance, amount, 'Insufficient balance');
|
|
63
|
+
$require.Hex(calldata, 'Invalid calldata');
|
|
64
|
+
$require.notNull(receipt, 'Receipt was not returned');
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Useful checks include `True`, `notNull`, `Null`, `notEmpty`, `eq`, `notEq`, `gt`, `gte`, `lt`, `lte`, `match`, `has`, `oneOf`, `Address`, `AddressNotEmpty`, `AddressChecked`, `TxHash`, `Hex`, `Number`, `BigInt`, and `Numeric`. Most value validators return the validated value.
|
|
68
|
+
|
|
69
|
+
## Hex Manipulation
|
|
70
|
+
|
|
71
|
+
Use `$hex` for `0x` normalization, conversion, padding, concatenation, slicing, and byte counts:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { $hex } from 'dequanto/utils/$hex';
|
|
75
|
+
|
|
76
|
+
const value = $hex.ensure('1234'); // 0x1234
|
|
77
|
+
const word = $hex.padBytes(value, 32); // left-pad to 32 bytes
|
|
78
|
+
const payload = $hex.concat([value, '0xabcd']);
|
|
79
|
+
const firstTwoBytes = $hex.getBytes(payload, 0, 2);
|
|
80
|
+
const byteLength = $hex.getBytesLength(payload);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`toHex('hello')` encodes a normal string as UTF-8. `toHexBuffer(...)` also ensures an even number of hex digits. `padBytes` takes a byte count and pads left by default; pass `{ padEnd: true }` to pad right.
|
|
84
|
+
|
|
85
|
+
## Date And Time
|
|
86
|
+
|
|
87
|
+
Use `$date` for time spans, Unix timestamps, formatting, date boundaries, and arithmetic:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { $date } from 'dequanto/utils/$date';
|
|
91
|
+
|
|
92
|
+
const timeoutMs = $date.parseTimespan('5min');
|
|
93
|
+
const timeoutSeconds = $date.parseTimespan('5min', { get: 's' });
|
|
94
|
+
const deadline = $date.tool().add('5min').toUnixTimestamp();
|
|
95
|
+
const date = $date.fromUnixTimestamp(deadline);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`parseTimespan` returns milliseconds by default. Request seconds explicitly when passing time to a contract or RPC API.
|
|
99
|
+
|
|
100
|
+
## Signing
|
|
101
|
+
|
|
102
|
+
Use `$sig` for account creation, message and typed-data signatures, transaction signing, and signer recovery:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { $sig } from 'dequanto/utils/$sig';
|
|
106
|
+
|
|
107
|
+
const account = $sig.$account.generate();
|
|
108
|
+
const signature = await $sig.signMessage('Approve operation', account);
|
|
109
|
+
const signer = $sig.recoverMessage('Approve operation', signature);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Use `signMessage`/`recoverMessage` for EIP-191 messages, `signTypedData` for typed data, and `signTx`/`recoverTx` for transactions. Account helpers include `generate`, `fromMnemonic`, and `fromKey`.
|
|
113
|
+
|
|
114
|
+
## Promise Results Without Try/Catch
|
|
115
|
+
|
|
116
|
+
`$promise.caught(...)` converts a rejection into a result object:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { $promise } from 'dequanto/utils/$promise';
|
|
120
|
+
|
|
121
|
+
const { result, error } = await $promise.caught(loadValue());
|
|
122
|
+
if (error) {
|
|
123
|
+
console.error(error.message);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(result);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
It accepts a promise or a function returning a promise and returns `{ result }` on success or `{ error }` on failure. Other helpers cover delays, timeouts, events, callbacks, and polling.
|
|
130
|
+
|
|
131
|
+
The remaining files under `src/utils/` mostly support organization, conversion, formatting, and readability. Use them when relevant, but verify current signatures in `lib/types/utils/` rather than guessing.
|
|
@@ -175,10 +175,10 @@ export class BlockchainExplorer implements IBlockchainExplorer {
|
|
|
175
175
|
try {
|
|
176
176
|
abi = await this.client.get(url);
|
|
177
177
|
} catch (err) {
|
|
178
|
-
$logger.log(`Blockchain explorer request
|
|
178
|
+
$logger.log(`Blockchain explorer request failed for "${url}": ${err.message}`);
|
|
179
179
|
let addressByByteCode = await this.getSimilarContract(address);
|
|
180
180
|
if (addressByByteCode != null) {
|
|
181
|
-
$logger.log(`Found similar
|
|
181
|
+
$logger.log(`Found similar bytecode address: ${addressByByteCode}`);
|
|
182
182
|
return this.getContractAbi(addressByByteCode);
|
|
183
183
|
}
|
|
184
184
|
throw err;
|
|
@@ -195,7 +195,7 @@ export class BlockchainExplorer implements IBlockchainExplorer {
|
|
|
195
195
|
let hex = $address.fromBytes32(uin256Hex);
|
|
196
196
|
return this.getContractAbi(hex)
|
|
197
197
|
}
|
|
198
|
-
throw new Error(`
|
|
198
|
+
throw new Error(`Implementation lookup is not supported for ${params.implementation}`);
|
|
199
199
|
}
|
|
200
200
|
if (isOpenZeppelinProxy(abiJson) || mightBeProxy(abiJson)) {
|
|
201
201
|
let web3 = this.getWeb3(this.platform);
|
|
@@ -384,12 +384,12 @@ export class BlockchainExplorer implements IBlockchainExplorer {
|
|
|
384
384
|
try {
|
|
385
385
|
return JSON.parse(str)
|
|
386
386
|
} catch (error) {
|
|
387
|
-
//
|
|
387
|
+
// Etherscan can return source code wrapped in {{...}}
|
|
388
388
|
}
|
|
389
389
|
str = str
|
|
390
390
|
.replace(/^\s*\{\{/g, '{')
|
|
391
391
|
.replace(/\}\}\s*$/g, '}');
|
|
392
|
-
// @TODO
|
|
392
|
+
// @TODO Check Etherscan serialized JSON. Does it always use "{{...}}" wrapping?
|
|
393
393
|
|
|
394
394
|
return JSON.parse(str)
|
|
395
395
|
}
|
|
@@ -555,7 +555,7 @@ function hasMethod(abi: TAbiItem[], name: string) {
|
|
|
555
555
|
function ensureDefaults(opts: TExplorerDefinition) {
|
|
556
556
|
|
|
557
557
|
let platform = opts.platform;
|
|
558
|
-
$require.notNull(platform, `Generic
|
|
558
|
+
$require.notNull(platform, `Generic blockchain explorer config must include a platform name`);
|
|
559
559
|
|
|
560
560
|
opts.ABI_CACHE ??= `./cache/${$platform.toPath(platform)}/abis.json`
|
|
561
561
|
opts.CONTRACTS ??= [];
|
|
@@ -594,7 +594,7 @@ class HttpClient {
|
|
|
594
594
|
async getHtml(url: string) {
|
|
595
595
|
let resp = await $http.get(url);
|
|
596
596
|
if (resp.status !== 200) {
|
|
597
|
-
throw new Error(`${url}
|
|
597
|
+
throw new Error(`${url} failed to load with status ${resp.status}.`);
|
|
598
598
|
}
|
|
599
599
|
return resp.data;
|
|
600
600
|
}
|
|
@@ -644,7 +644,7 @@ class HttpClient {
|
|
|
644
644
|
throw error;
|
|
645
645
|
}
|
|
646
646
|
if (data.result == null) {
|
|
647
|
-
$logger.warn(`Blockchain "${url}"
|
|
647
|
+
$logger.warn(`Blockchain explorer "${url}" returned an empty result`, data);
|
|
648
648
|
}
|
|
649
649
|
return data.result as TOut;
|
|
650
650
|
}
|
|
@@ -669,7 +669,7 @@ class HttpClient {
|
|
|
669
669
|
throw new Error(str);
|
|
670
670
|
}
|
|
671
671
|
if (data.result == null) {
|
|
672
|
-
$logger.warn(`Blockchain "${url}"
|
|
672
|
+
$logger.warn(`Blockchain explorer "${url}" returned an empty result`, data);
|
|
673
673
|
}
|
|
674
674
|
return data.result;
|
|
675
675
|
}
|
|
@@ -50,7 +50,7 @@ export namespace BlockchainExplorerFactory {
|
|
|
50
50
|
return new BlockchainExplorer(params);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/** Same as
|
|
53
|
+
/** Same as the synchronous variant, but ensures the config has been fetched */
|
|
54
54
|
export async function getAsync (platform: TPlatform, opts?: IBlockchainExplorerFactoryParams): Promise<IBlockchainExplorer> {
|
|
55
55
|
await Config.get();
|
|
56
56
|
return get(platform, opts);
|
|
@@ -26,7 +26,7 @@ export namespace BlockchainExplorerProvider {
|
|
|
26
26
|
if (cfg != null) {
|
|
27
27
|
return new BlockchainExplorer({ platform, ...cfg });
|
|
28
28
|
}
|
|
29
|
-
throw new Error(`Unsupported platform ${platform} for
|
|
29
|
+
throw new Error(`Unsupported platform ${platform} for blockchain explorer`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -82,7 +82,7 @@ export class BlockchainExplorerStorage implements IBlockchainExplorer {
|
|
|
82
82
|
let $address = opts?.implementation ?? address;
|
|
83
83
|
let $contract = this.contracts[$address.toLowerCase()];
|
|
84
84
|
$require.notNull($contract, `Contract "${$address}" not found`);
|
|
85
|
-
// @TODO: L1:
|
|
85
|
+
// @TODO: L1: Return ABI as JSON. *scan providers should serialize it earlier.
|
|
86
86
|
return {
|
|
87
87
|
implementation: $address,
|
|
88
88
|
abi: JSON.stringify($contract.abi)
|
|
@@ -92,7 +92,7 @@ export class BlockchainExplorerStorage implements IBlockchainExplorer {
|
|
|
92
92
|
let _address = address;
|
|
93
93
|
let _contract = this.contracts[_address.toLowerCase()];
|
|
94
94
|
$require.notNull(_contract, `Contract "${_address}" not found`);
|
|
95
|
-
// @TODO: L1:
|
|
95
|
+
// @TODO: L1: Return ABI as JSON. *scan providers should serialize it earlier.
|
|
96
96
|
return {
|
|
97
97
|
SourceCode: {
|
|
98
98
|
contractName: _contract.name,
|
|
@@ -42,7 +42,7 @@ export class ContractVerifier {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async ensure (Ctor: TContractInfo, opts: TSubmissionOptions = {}): Promise<void> {
|
|
45
|
-
this.logger.log(`
|
|
45
|
+
this.logger.log(`Submitting sources for ${typeof Ctor === 'string' ? Ctor : Ctor.name}`);
|
|
46
46
|
|
|
47
47
|
let status = await this.submit(Ctor, opts);
|
|
48
48
|
await this.waitForVerification(status, opts);
|
|
@@ -66,7 +66,7 @@ export class ContractVerifier {
|
|
|
66
66
|
} catch (e) {
|
|
67
67
|
let message = e.message;
|
|
68
68
|
if (/verified/i.test(message)) {
|
|
69
|
-
//
|
|
69
|
+
// Already verified.
|
|
70
70
|
return true;
|
|
71
71
|
}
|
|
72
72
|
if (/(pending|queue)/i.test(message)) {
|
|
@@ -100,13 +100,13 @@ export class ContractVerifier {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
try {
|
|
103
|
-
this.logger.log(`Checking
|
|
103
|
+
this.logger.log(`Checking whether contract ${address} is already verified`);
|
|
104
104
|
let currentSources = await this.explorer.getContractSource(address);
|
|
105
105
|
if ($is.notEmpty(currentSources?.ContractName)) {
|
|
106
106
|
return { status:'verified' };
|
|
107
107
|
}
|
|
108
108
|
} catch (error) {
|
|
109
|
-
//
|
|
109
|
+
// Ignore pre-check errors and continue with verification.
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
let deployedBytecode = await client.getCode(address);
|
|
@@ -140,7 +140,7 @@ export class ContractVerifier {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
let jsonMetaPath = info.ctx.source.path;
|
|
143
|
-
$require.notEmpty(jsonMetaPath, `Deployment should return a path to compilation JSON`);
|
|
143
|
+
$require.notEmpty(jsonMetaPath, `Deployment should return a path to the compilation JSON`);
|
|
144
144
|
$require.True(await File.existsAsync(jsonMetaPath) , `${jsonMetaPath} does not exist`);
|
|
145
145
|
|
|
146
146
|
let jsonMeta = await File.readAsync<{ sourceName: string, contractName: string }>(jsonMetaPath);
|
|
@@ -150,7 +150,7 @@ export class ContractVerifier {
|
|
|
150
150
|
let jsonMetaDbg = await File.readAsync<{ buildInfo: string }>(jsonMetaDbgPath);
|
|
151
151
|
|
|
152
152
|
let jsonMetaBuildInfoPath = jsonMetaDbg.buildInfo;
|
|
153
|
-
$require.notEmpty(jsonMetaPath, `${jsonMetaDbgPath} should contain the path to the
|
|
153
|
+
$require.notEmpty(jsonMetaPath, `${jsonMetaDbgPath} should contain the path to the build info JSON file`);
|
|
154
154
|
|
|
155
155
|
let buildInfoPath = new class_Uri(new class_Uri(jsonMetaPath).toDir()).combine(jsonMetaBuildInfoPath).toString();
|
|
156
156
|
$require.True(await File.existsAsync(buildInfoPath) , `${buildInfoPath} does not exist`);
|
|
@@ -170,7 +170,7 @@ export class ContractVerifier {
|
|
|
170
170
|
}
|
|
171
171
|
let buildInfo = await File.readAsync<TBuildInfo>(buildInfoPath);
|
|
172
172
|
|
|
173
|
-
$require.notNull(buildInfo.solcLongVersion, `${buildInfoPath} should contain the "solcLongVersion"
|
|
173
|
+
$require.notNull(buildInfo.solcLongVersion, `${buildInfoPath} should contain the "solcLongVersion" value`);
|
|
174
174
|
|
|
175
175
|
let sources = await this.getSources(jsonMeta.sourceName);
|
|
176
176
|
let sourcesSerialized = JSON.stringify({
|
|
@@ -181,7 +181,7 @@ export class ContractVerifier {
|
|
|
181
181
|
|
|
182
182
|
let sourcesSerializedWrapped = `${sourcesSerialized}`;
|
|
183
183
|
|
|
184
|
-
this.logger.log(`
|
|
184
|
+
this.logger.log(`Submitting ${jsonMeta.sourceName}:${jsonMeta.contractName} and dependencies to verify contract ${address}`);
|
|
185
185
|
|
|
186
186
|
try {
|
|
187
187
|
let guid = await this.explorer.submitContractVerification({
|
|
@@ -243,7 +243,7 @@ export class ContractVerifier {
|
|
|
243
243
|
|
|
244
244
|
await crawlSourceFile(new SourceFile(main));
|
|
245
245
|
|
|
246
|
-
this.logger.log(`Found ${Object.keys(sources).length} source files
|
|
246
|
+
this.logger.log(`Found ${Object.keys(sources).length} source files:`, Object.keys(sources));
|
|
247
247
|
return sources;
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -284,7 +284,7 @@ export class ContractVerifier {
|
|
|
284
284
|
} catch (e) {
|
|
285
285
|
let message = e.message;
|
|
286
286
|
if (/verified|success/i.test(message)) {
|
|
287
|
-
//
|
|
287
|
+
// Already verified.
|
|
288
288
|
return true;
|
|
289
289
|
}
|
|
290
290
|
if (/(pending|queue)/i.test(message)) {
|
|
@@ -115,7 +115,7 @@ export class FsHtmlVerifier implements IVerifier {
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
if (contractData.contractName.includes('Utils/Proxy')) {
|
|
118
|
-
//
|
|
118
|
+
// Rename duplicate Proxy contracts before writing the verification form.
|
|
119
119
|
html = html.replaceAll('contracts/Utils/Proxy.sol:Proxy', 'contracts/Utils/TUProxy.sol:TUProxy');
|
|
120
120
|
html = html.replace('Utils/Proxy.sol":', 'Utils/TUProxy.sol":');
|
|
121
121
|
html = html.replace('contract Proxy is ', 'contract TUProxy is ');
|
package/tsconfig.json
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Web3ClientFactory } from '@dequanto/clients/Web3ClientFactory';
|
|
2
|
-
import { HardhatProvider } from '@dequanto/hardhat/HardhatProvider';
|
|
3
|
-
import { SlotsParser } from '@dequanto/solidity/SlotsParser';
|
|
4
|
-
import { SlotsStorage } from '@dequanto/solidity/SlotsStorage';
|
|
5
|
-
import { $require } from '@dequanto/utils/$require';
|
|
6
|
-
|
|
7
|
-
// This is the low-level path for creating a storage reader directly from Solidity source.
|
|
8
|
-
// Classes generated from source with 0xweb install or @0xweb/hardhat expose storage on the instance: foo.storage.get('count').
|
|
9
|
-
UTest({
|
|
10
|
-
async 'read and write Solidity storage with SlotsParser and SlotsStorage' () {
|
|
11
|
-
const code = `
|
|
12
|
-
contract Vault {
|
|
13
|
-
uint256 public count = 123;
|
|
14
|
-
mapping(address => uint256) balances;
|
|
15
|
-
|
|
16
|
-
constructor () {
|
|
17
|
-
balances[address(0x1000000000000000000000000000000000000001)] = 7;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
`;
|
|
21
|
-
const hh = new HardhatProvider();
|
|
22
|
-
const client = await Web3ClientFactory.getAsync('hh:memory');
|
|
23
|
-
const { contract } = await hh.deployCode(code, { client });
|
|
24
|
-
|
|
25
|
-
const slots = await SlotsParser.slots({ path: '', code }, 'Vault');
|
|
26
|
-
const storage = SlotsStorage.createWithClient(client, contract.address, slots);
|
|
27
|
-
|
|
28
|
-
$require.eq(await storage.get('count'), 123n);
|
|
29
|
-
$require.eq(await storage.get('balances["0x1000000000000000000000000000000000000001"]'), 7n);
|
|
30
|
-
|
|
31
|
-
// Direct storage writes require a debug network such as Hardhat.
|
|
32
|
-
await storage.set('count', 500n);
|
|
33
|
-
$require.eq(await storage.get('count'), 500n);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Solidity Storage
|
|
2
|
-
|
|
3
|
-
Use `SlotsParser` to derive layout and `SlotsStorage` to read or write slots. Do not decode raw storage words as ABI payloads.
|
|
4
|
-
|
|
5
|
-
Key source files:
|
|
6
|
-
|
|
7
|
-
- `src/solidity/SlotsParser.ts`
|
|
8
|
-
- `src/solidity/SlotsStorage.ts`
|
|
9
|
-
- `src/solidity/storage/handlers/SlotStructHandler.ts`
|
|
10
|
-
- `src/solidity/storage/handlers/SlotMappingHandler.ts`
|
|
11
|
-
- `src/solidity/storage/handlers/SlotDynamicArrayHandler.ts`
|
|
12
|
-
- `src/solidity/storage/SlotsStorageTransport.ts`
|
|
13
|
-
- `src/gen/GeneratorStorageReader.ts`
|
|
14
|
-
|
|
15
|
-
Useful tests:
|
|
16
|
-
|
|
17
|
-
- `test/solidity/SlotsReader.spec.ts`
|
|
18
|
-
- `test/solidity/Storage.spec.ts`
|
|
19
|
-
- `test/solidity/SlotsParser.spec.ts`
|
|
20
|
-
- `test/generate/slotreader.spec.ts`
|
|
21
|
-
|
|
22
|
-
## Parse Slots From Solidity
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import { SlotsParser } from '@dequanto/solidity/SlotsParser';
|
|
26
|
-
|
|
27
|
-
const slots = await SlotsParser.slots({
|
|
28
|
-
path: './contracts/Vault.sol'
|
|
29
|
-
}, 'Vault');
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
For inline code:
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
const slots = await SlotsParser.slots({ path: '', code }, 'Foo');
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Parse Slots From ABI Inputs
|
|
39
|
-
|
|
40
|
-
Use this for standalone struct or tuple ABI data:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
const slots = await SlotsParser.slotsFromAbi(
|
|
44
|
-
'(uint256 foo, address owner, bool enabled)'
|
|
45
|
-
);
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Read Contract Storage
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
import { SlotsStorage } from '@dequanto/solidity/SlotsStorage';
|
|
52
|
-
|
|
53
|
-
const storage = SlotsStorage.createWithClient(client, contract.address, slots);
|
|
54
|
-
|
|
55
|
-
const owner = await storage.get('owner');
|
|
56
|
-
const user = await storage.get('users[0]');
|
|
57
|
-
const balance = await storage.get(`balances["${account}"]`);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Supported path forms include:
|
|
61
|
-
|
|
62
|
-
- `field`
|
|
63
|
-
- `struct.field`
|
|
64
|
-
- `array[0]`
|
|
65
|
-
- `mapping["0x..."]`
|
|
66
|
-
- `mapping["0x..."].field`
|
|
67
|
-
- `nested[0].balances[1]`
|
|
68
|
-
- breadcrumb arrays such as `['users', account, 'balance']`
|
|
69
|
-
|
|
70
|
-
## Read Diamond Or Offset Storage
|
|
71
|
-
|
|
72
|
-
For library-selected storage roots:
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
const storage = SlotsStorage.createWithClient(client, contract.address, slots, {
|
|
76
|
-
storageOffset: $contract.keccak256('diamond.app.storage')
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Write Storage In Tests
|
|
81
|
-
|
|
82
|
-
`SlotsStorage.set(...)` exists and is useful in Hardhat/debug contexts:
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
await storage.set('user.balance', 1000n);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Do not use storage writes in production code unless the transport explicitly supports it and the task requires test/debug mutation.
|
|
89
|
-
|
|
90
|
-
## Raw Slot Reads
|
|
91
|
-
|
|
92
|
-
Use `client.getStorageAt(address, slot)` or `client.getStorageAtBatched(address, slots)` only when raw words are needed.
|
|
93
|
-
|
|
File without changes
|