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
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Web3ClientFactory } from '
|
|
2
|
-
import { TxDataBuilder } from '
|
|
3
|
-
import { TxWriter } from '
|
|
4
|
-
import { $account } from '
|
|
5
|
-
import { $address } from '
|
|
6
|
-
import { $number } from '
|
|
7
|
-
import { $require } from '
|
|
1
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
|
+
import { TxDataBuilder } from 'dequanto/txs/TxDataBuilder';
|
|
3
|
+
import { TxWriter } from 'dequanto/txs/TxWriter';
|
|
4
|
+
import { $account } from 'dequanto/utils/$account';
|
|
5
|
+
import { $address } from 'dequanto/utils/$address';
|
|
6
|
+
import { $number } from 'dequanto/utils/$number';
|
|
7
|
+
import { $require } from 'dequanto/utils/$require';
|
|
8
8
|
|
|
9
9
|
UTest({
|
|
10
10
|
async 'build native transfer tx data with gas fields' () {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AavePool } from '../../../0xc/eth/AavePool/AavePool';
|
|
2
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
3
|
+
import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
|
|
4
|
+
import { SlotsParser } from 'dequanto/solidity/SlotsParser';
|
|
5
|
+
import { SlotsStorage } from 'dequanto/solidity/SlotsStorage';
|
|
6
|
+
import { $require } from 'dequanto/utils/$require';
|
|
7
|
+
|
|
8
|
+
// Generated classes include the storage layout: use instance.storage.$set(name, value).
|
|
9
|
+
// SlotsParser and SlotsStorage provide the low-level alternative for Solidity source.
|
|
10
|
+
UTest({
|
|
11
|
+
async 'read and set AavePool storage by variable name on an Ethereum fork' () {
|
|
12
|
+
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
13
|
+
const pool = new AavePool(undefined, client);
|
|
14
|
+
const premiumBefore = await pool.FLASHLOAN_PREMIUM_TOTAL();
|
|
15
|
+
const packedNeighborBefore = await pool.storage.__DEPRECATED_flashLoanPremiumToProtocol();
|
|
16
|
+
const id = await client.debug.snapshot();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const premium = premiumBefore + 1n;
|
|
20
|
+
await pool.storage.$set('_flashLoanPremium', premium);
|
|
21
|
+
|
|
22
|
+
$require.eq(await pool.storage._flashLoanPremium(), premium);
|
|
23
|
+
$require.eq(await pool.FLASHLOAN_PREMIUM_TOTAL(), premium);
|
|
24
|
+
// Both uint128 fields share slot 58; preserve the neighboring field.
|
|
25
|
+
$require.eq(await pool.storage.__DEPRECATED_flashLoanPremiumToProtocol(), packedNeighborBefore);
|
|
26
|
+
} finally {
|
|
27
|
+
await client.debug.revert(id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
$require.eq(await pool.FLASHLOAN_PREMIUM_TOTAL(), premiumBefore);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async 'read and write Solidity storage with SlotsParser and SlotsStorage' () {
|
|
34
|
+
const code = `
|
|
35
|
+
contract Vault {
|
|
36
|
+
uint256 public count = 123;
|
|
37
|
+
mapping(address => uint256) balances;
|
|
38
|
+
|
|
39
|
+
constructor () {
|
|
40
|
+
balances[address(0x1000000000000000000000000000000000000001)] = 7;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
const hh = new HardhatProvider();
|
|
45
|
+
const client = await Web3ClientFactory.getAsync('hh:memory');
|
|
46
|
+
const { contract } = await hh.deployCode(code, { client });
|
|
47
|
+
|
|
48
|
+
const slots = await SlotsParser.slots({ path: '', code }, 'Vault');
|
|
49
|
+
const storage = SlotsStorage.createWithClient(client, contract.address, slots);
|
|
50
|
+
|
|
51
|
+
$require.eq(await storage.get('count'), 123n);
|
|
52
|
+
$require.eq(await storage.get('balances["0x1000000000000000000000000000000000000001"]'), 7n);
|
|
53
|
+
|
|
54
|
+
// Direct storage writes require a debug network such as Hardhat.
|
|
55
|
+
await storage.set('count', 500n);
|
|
56
|
+
$require.eq(await storage.get('count'), 500n);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Web3ClientFactory } from '
|
|
2
|
-
import { ERC20 } from '
|
|
3
|
-
import { $account } from '
|
|
4
|
-
import { $erc20 } from '
|
|
5
|
-
import { $require } from '
|
|
1
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
2
|
+
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
3
|
+
import { $account } from 'dequanto/utils/$account';
|
|
4
|
+
import { $erc20 } from 'dequanto/utils/$erc20';
|
|
5
|
+
import { $require } from 'dequanto/utils/$require';
|
|
6
6
|
|
|
7
7
|
const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as const;
|
|
8
8
|
const INITIAL_SENDER_BALANCE = 100_000_000n;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { $abi } from 'dequanto/abi/$abi';
|
|
2
|
+
import { $contract } from 'dequanto/utils/$contract';
|
|
3
|
+
import { $date } from 'dequanto/utils/$date';
|
|
4
|
+
import { $hex } from 'dequanto/utils/$hex';
|
|
5
|
+
import { $promise } from 'dequanto/utils/$promise';
|
|
6
|
+
import { $require } from 'dequanto/utils/$require';
|
|
7
|
+
import { $sig } from 'dequanto/utils/$sig';
|
|
8
|
+
|
|
9
|
+
const ACCOUNT = '0x0000000000000000000000000000000000000002';
|
|
10
|
+
|
|
11
|
+
UTest({
|
|
12
|
+
'encode and decode ABI values and calldata' () {
|
|
13
|
+
const encoded = $abi.encode(['address', 'uint256'], [ACCOUNT, 5n]);
|
|
14
|
+
const [account, amount] = $abi.decode(['address', 'uint256'], encoded);
|
|
15
|
+
const calldata = $abi.encodeCall(
|
|
16
|
+
'function transfer(address to, uint256 amount)',
|
|
17
|
+
[ACCOUNT, 5n]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
$require.eq(account, ACCOUNT);
|
|
21
|
+
$require.eq(amount, 5n);
|
|
22
|
+
$require.eq(
|
|
23
|
+
calldata,
|
|
24
|
+
'0xa9059cbb00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005'
|
|
25
|
+
);
|
|
26
|
+
$require.eq(
|
|
27
|
+
$abi.decodeReturn<bigint>(
|
|
28
|
+
'function totalSupply() view returns (uint256)',
|
|
29
|
+
$abi.encode('uint256', 10n)
|
|
30
|
+
),
|
|
31
|
+
10n
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
'hash Solidity values and manipulate hex' () {
|
|
36
|
+
const topic = $contract.keccak256('Transfer(address,address,uint256)');
|
|
37
|
+
const payload = $hex.concat([$hex.ensure('1234'), $hex.toHexBuffer(5)]);
|
|
38
|
+
|
|
39
|
+
$require.eq(topic, '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef');
|
|
40
|
+
$require.eq(payload, '0x123405');
|
|
41
|
+
$require.eq($hex.padBytes('0x2a', 4), '0x0000002a');
|
|
42
|
+
$require.eq($hex.getBytes(payload, 0, 2), '0x1234');
|
|
43
|
+
$require.eq($hex.getBytesLength(payload), 3);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async 'validate values, dates, signatures, and promise results' () {
|
|
47
|
+
$require.eq($date.parseTimespan('5days', { get: 's' }), 432_000);
|
|
48
|
+
$require.AddressNotEmpty(ACCOUNT);
|
|
49
|
+
$require.BigInt(5n, 'Expected an amount', { min: 1n });
|
|
50
|
+
|
|
51
|
+
const signer = $sig.$account.generate();
|
|
52
|
+
const signature = await $sig.signMessage('Hello dequanto', signer);
|
|
53
|
+
$require.eq($sig.recoverMessage('Hello dequanto', signature), signer.address);
|
|
54
|
+
|
|
55
|
+
const success = await $promise.caught(Promise.resolve(42));
|
|
56
|
+
const failure = await $promise.caught(Promise.reject(new Error('Expected failure')));
|
|
57
|
+
|
|
58
|
+
$require.eq(success.result, 42);
|
|
59
|
+
$require.Null(success.error);
|
|
60
|
+
$require.notNull(failure.error);
|
|
61
|
+
$require.eq(failure.error.message, 'Expected failure');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -48,7 +48,7 @@ const balance = await token.balanceOf(account);
|
|
|
48
48
|
For ad hoc reads without a generated class:
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
|
-
import { ContractReader } from '
|
|
51
|
+
import { ContractReader } from 'dequanto/contracts/ContractReader';
|
|
52
52
|
|
|
53
53
|
const reader = new ContractReader(client);
|
|
54
54
|
const symbol = await reader.readAsync<string>(
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Use `HardhatProvider` for compile/deploy helper flows and `Deployments` for idempotent deployments, deployment storage, bytecode checks, proxy upgrades, beacon proxies, and verification.
|
|
4
4
|
|
|
5
|
+
See [Hardhat development and forking](hardhat.md) for creating development networks and live-contract forks, impersonating accounts, changing balances/storage, and restoring snapshots.
|
|
6
|
+
|
|
5
7
|
Key source files:
|
|
6
8
|
|
|
7
9
|
- `src/hardhat/HardhatProvider.ts`
|
|
@@ -21,7 +23,7 @@ Useful tests:
|
|
|
21
23
|
## HardhatProvider
|
|
22
24
|
|
|
23
25
|
```ts
|
|
24
|
-
import { HardhatProvider } from '
|
|
26
|
+
import { HardhatProvider } from 'dequanto/hardhat/HardhatProvider';
|
|
25
27
|
|
|
26
28
|
const hh = new HardhatProvider();
|
|
27
29
|
const client = hh.client();
|
|
@@ -43,7 +45,7 @@ Common methods:
|
|
|
43
45
|
## Idempotent Deployments
|
|
44
46
|
|
|
45
47
|
```ts
|
|
46
|
-
import { Deployments } from '
|
|
48
|
+
import { Deployments } from 'dequanto/contracts/deploy/Deployments';
|
|
47
49
|
|
|
48
50
|
const deployments = new Deployments(client, deployer, {
|
|
49
51
|
directory: './deployments/',
|
|
@@ -34,7 +34,7 @@ Use `'*'` for all events, or an event-name array for multiple events.
|
|
|
34
34
|
## EventsIndexer
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
|
-
import { EventsIndexer } from '
|
|
37
|
+
import { EventsIndexer } from 'dequanto/indexer/EventsIndexer';
|
|
38
38
|
|
|
39
39
|
const indexer = new EventsIndexer(contract, {
|
|
40
40
|
name: 'TokenTransfers',
|
|
@@ -19,7 +19,7 @@ Useful tests:
|
|
|
19
19
|
## Create Explorer
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { BlockchainExplorerFactory } from '
|
|
22
|
+
import { BlockchainExplorerFactory } from 'dequanto/explorer/BlockchainExplorerFactory';
|
|
23
23
|
|
|
24
24
|
const explorer = BlockchainExplorerFactory.get('eth');
|
|
25
25
|
```
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript class generation is a core dequanto workflow. Prefer generated contract classes over raw ABI calls: generated classes let application code call contracts as ordinary TypeScript classes, with no direct web3-specific ABI encoding, decoding, or JSON-RPC plumbing.
|
|
4
4
|
|
|
5
|
-
Before generating a contract, check whether dequanto already ships a prebuilt class. The package includes OpenZeppelin contract classes under `dequanto/prebuilt/**` /
|
|
5
|
+
Before generating a contract, check whether dequanto already ships a prebuilt class. The package includes OpenZeppelin contract classes under `dequanto/prebuilt/**` / `dequanto/prebuilt/openzeppelin/**`. Class names match the OpenZeppelin contract names, for example `ERC20`, `ERC721`, `Ownable`, or `TimelockController`.
|
|
6
6
|
|
|
7
7
|
For standard OpenZeppelin contracts, do not generate a new class. Import the prebuilt class and pass the target contract address plus a chain client loaded by platform/chain abbreviation:
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import { Web3ClientFactory } from '
|
|
11
|
-
import { ERC20 } from '
|
|
10
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
11
|
+
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
12
12
|
|
|
13
13
|
const platform = 'eth';
|
|
14
14
|
const address = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
|
|
@@ -71,7 +71,7 @@ By default, `0xweb install` saves the downloaded Solidity sources next to the ge
|
|
|
71
71
|
The generated class can then be imported and used like a normal TypeScript class:
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
|
-
import { Web3ClientFactory } from '
|
|
74
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
75
75
|
import { USDC } from './0xc/eth/USDC/USDC';
|
|
76
76
|
|
|
77
77
|
const client = await Web3ClientFactory.getAsync('eth');
|
|
@@ -121,7 +121,7 @@ Use `Generator` directly only for custom generation tasks, test fixtures, non-Ha
|
|
|
121
121
|
### Generate From Compiled Artifact
|
|
122
122
|
|
|
123
123
|
```ts
|
|
124
|
-
import { Generator } from '
|
|
124
|
+
import { Generator } from 'dequanto/gen/Generator';
|
|
125
125
|
|
|
126
126
|
const gen = new Generator({
|
|
127
127
|
name: 'AnyERC20',
|
|
@@ -180,7 +180,7 @@ When a contract is generated from verified on-chain source, such as with `0xweb
|
|
|
180
180
|
Use `GeneratorStorageReader` when the task needs generated storage accessors without generating the full contract class.
|
|
181
181
|
|
|
182
182
|
```ts
|
|
183
|
-
import { GeneratorStorageReader } from '
|
|
183
|
+
import { GeneratorStorageReader } from 'dequanto/gen/GeneratorStorageReader';
|
|
184
184
|
|
|
185
185
|
const gen = new GeneratorStorageReader();
|
|
186
186
|
const result = await gen.generate({
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Hardhat Development And Forking
|
|
2
|
+
|
|
3
|
+
Dequanto integrates Hardhat as a development and forking network for contract development and experimenting with live contracts on a local fork. Use `client.debug` to mine blocks, change balances and storage, impersonate accounts, and restore snapshots. These changes affect the local network only.
|
|
4
|
+
|
|
5
|
+
Key source files:
|
|
6
|
+
|
|
7
|
+
- `src/clients/Web3ClientFactory.ts`
|
|
8
|
+
- `src/hardhat/HardhatProvider.ts`
|
|
9
|
+
- `src/hardhat/HardhatWeb3Client.ts`
|
|
10
|
+
- `src/clients/debug/ClientDebugMethods.ts`
|
|
11
|
+
- `src/utils/$erc20.ts`
|
|
12
|
+
- `src/txs/TxWriter.ts`
|
|
13
|
+
|
|
14
|
+
Runnable examples: [Hardhat debug operations](../examples/hardhat-debug.spec.ts) and [forked ERC20 transfers](../examples/hardhat-fork.spec.ts). The fork example requires an accessible Ethereum RPC.
|
|
15
|
+
|
|
16
|
+
## Create A Network Or Fork
|
|
17
|
+
|
|
18
|
+
Use a project with Hardhat installed and a `hardhat.config.*` file.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
22
|
+
|
|
23
|
+
// Initialize an in-memory Ethereum fork using the platform's default/configured RPCs.
|
|
24
|
+
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Replace `eth` with the platform to fork. Optionally pass `{ block: 20_000_000, url: 'https://...' }` as the second argument to select a block and override the source RPC. Initializing an in-memory fork resets the shared Hardhat network; create it before taking snapshots or changing state.
|
|
28
|
+
|
|
29
|
+
To use a separately running local Hardhat node, start it with Ethereum forking configured through Hardhat's normal node setup, then connect:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const client = await Web3ClientFactory.getAsync('hh:eth');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This connects to the local node (by default `127.0.0.1:8545`) and associates it with Ethereum. It does not start the node or configure its fork.
|
|
36
|
+
|
|
37
|
+
For an in-memory development network without a fork, use `hh:memory`. Use `hardhat` to connect to a separately running local development node. See [deployments](deployments.md) for compiling and deploying contracts.
|
|
38
|
+
|
|
39
|
+
## Impersonate An Account
|
|
40
|
+
|
|
41
|
+
On Hardhat, submit transactions from any address, including a contract address, by passing an account with `type: 'impersonated'`. No private key is required. The transaction writer enables impersonation automatically.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { ERC20 } from 'dequanto/prebuilt/openzeppelin/ERC20';
|
|
45
|
+
import { $erc20 } from 'dequanto/utils/$erc20';
|
|
46
|
+
|
|
47
|
+
const usdc = new ERC20('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', client);
|
|
48
|
+
const account = {
|
|
49
|
+
address: '0x0000000000000000000000000000000000001234',
|
|
50
|
+
type: 'impersonated'
|
|
51
|
+
} as const;
|
|
52
|
+
const to = '0x0000000000000000000000000000000000005678';
|
|
53
|
+
const amount = 1_000_000n; // 1 USDC in its smallest units.
|
|
54
|
+
|
|
55
|
+
await client.debug.setBalance(account.address, 10n ** 18n);
|
|
56
|
+
await $erc20.setBalanceAny(client, usdc.address, account.address, amount);
|
|
57
|
+
const writer = await usdc.$receipt().transfer(account, to, amount);
|
|
58
|
+
console.log(writer.receipt.transactionHash);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Contract checks still apply, including sufficient token balances and allowances. For explicit control, use `client.debug.impersonateAccount(address)` and `client.debug.stopImpersonatingAccount(address)`.
|
|
62
|
+
|
|
63
|
+
## Advance The Network
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
await client.debug.mine('5days');
|
|
67
|
+
await client.debug.mine(10); // Mine 10 blocks.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Time strings are converted to seconds and used as the block count, with a default interval of one second per block. `mine('5days')` therefore mines `432_000` blocks, advancing network time by approximately five days. The optional second argument sets the interval in seconds.
|
|
71
|
+
|
|
72
|
+
## Set Native And ERC20 Balances
|
|
73
|
+
|
|
74
|
+
Set the native balance of any account or contract in wei:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await client.debug.setBalance(account.address, 10n ** 18n); // 1 ETH.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use `$erc20.setBalanceAny` to set an ERC20 balance without knowing its storage slot:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
await $erc20.setBalanceAny(client, usdc.address, account.address, 1_000_000n);
|
|
84
|
+
// A number is interpreted in whole-token units instead:
|
|
85
|
+
await $erc20.setBalanceAny(client, usdc.address, account.address, 1);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The helper traces `balanceOf(account)` with `debug_traceCall`, examines accessed storage for the returned balance, and writes the matching slot. A `bigint` amount is in the token's smallest units; a `number` is converted using the token's decimals. This changes balance storage directly without minting tokens or updating total supply. Tokens whose balances are computed rather than stored directly may not be supported.
|
|
89
|
+
|
|
90
|
+
## Set Contract Storage
|
|
91
|
+
|
|
92
|
+
For a known storage slot, use:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
await client.debug.setStorageAt(contractAddress, slot, '0x2a');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The value is padded to 32 bytes. For named variables, mappings, and packed fields, use a resolved storage layout with `SlotsStorage.set(...)`; see [Solidity storage](storage.md#write-storage-in-tests).
|
|
99
|
+
|
|
100
|
+
## Snapshots And Rollbacks
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const id = await client.debug.snapshot();
|
|
104
|
+
try {
|
|
105
|
+
await client.debug.mine('5days');
|
|
106
|
+
await client.debug.setBalance(account.address, 10n ** 18n);
|
|
107
|
+
// Run transactions or assertions against the modified state.
|
|
108
|
+
} finally {
|
|
109
|
+
await client.debug.revert(id);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Take the snapshot before modifying state. Create a fresh snapshot for each independent scenario.
|
|
@@ -8,6 +8,7 @@ Key source files:
|
|
|
8
8
|
- `src/clients/Web3Client.ts`
|
|
9
9
|
- `src/clients/EvmWeb3Client.ts`
|
|
10
10
|
- `src/clients/ClientPool.ts`
|
|
11
|
+
- `src/blocks/BlockDateResolver.ts`
|
|
11
12
|
- `src/rpc/Rpc.ts`
|
|
12
13
|
- `src/rpc/RpcBase.ts`
|
|
13
14
|
|
|
@@ -21,27 +22,29 @@ Useful tests:
|
|
|
21
22
|
## Client Creation
|
|
22
23
|
|
|
23
24
|
```ts
|
|
24
|
-
import { Web3ClientFactory } from '
|
|
25
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
25
26
|
|
|
26
27
|
const client = await Web3ClientFactory.getAsync('eth');
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
For Hardhat:
|
|
30
|
+
For an in-memory Hardhat fork using the platform's default/configured RPCs:
|
|
30
31
|
|
|
31
32
|
```ts
|
|
32
|
-
const client = await Web3ClientFactory.getAsync('
|
|
33
|
+
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
To connect to an already running local Hardhat node configured as an Ethereum fork:
|
|
36
37
|
|
|
37
38
|
```ts
|
|
38
39
|
const client = await Web3ClientFactory.getAsync('hh:eth');
|
|
39
40
|
```
|
|
40
41
|
|
|
42
|
+
Use `hh:memory` for an in-memory development network without a fork, or `hardhat` for a separately running local development node. See [Hardhat development and forking](hardhat.md) for setup, impersonation, balance/storage changes, mining, and snapshots.
|
|
43
|
+
|
|
41
44
|
For direct endpoints:
|
|
42
45
|
|
|
43
46
|
```ts
|
|
44
|
-
import { EvmWeb3Client } from '
|
|
47
|
+
import { EvmWeb3Client } from 'dequanto/clients/EvmWeb3Client';
|
|
45
48
|
|
|
46
49
|
const client = new EvmWeb3Client({
|
|
47
50
|
platform: 'eth',
|
|
@@ -80,6 +83,23 @@ const result = await client.readContract({
|
|
|
80
83
|
});
|
|
81
84
|
```
|
|
82
85
|
|
|
86
|
+
## Resolve Blocks By Date
|
|
87
|
+
|
|
88
|
+
Use `BlockDateResolver` to find the block nearest to a date, or to resolve a block number back to its date:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { BlockDateResolver } from 'dequanto/blocks/BlockDateResolver';
|
|
92
|
+
|
|
93
|
+
const resolver = new BlockDateResolver(client);
|
|
94
|
+
const date = new Date('2024-03-03T10:20:00Z');
|
|
95
|
+
|
|
96
|
+
const blockNumber = await resolver.getBlockNumberFor(date);
|
|
97
|
+
const { block, timestamp } = await resolver.getBlockInfoFor(date);
|
|
98
|
+
const blockDate = await resolver.getDate(blockNumber);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`getBlockNumberFor(date)` returns the nearest block number. `getBlockInfoFor(date)` also returns that block's Unix timestamp. The resolver estimates from the chain's average block time and refines the estimate by loading blocks, so the result can be immediately before or after the requested time. Compare the returned timestamp when an exact time boundary matters. Dates before the chain was active throw a `Date out of range` error.
|
|
102
|
+
|
|
83
103
|
## Logs
|
|
84
104
|
|
|
85
105
|
Use `getPastLogs(filter, options?)` for raw log queries. It handles block-range pagination and node range limits.
|
|
@@ -51,8 +51,8 @@ const tx = await token
|
|
|
51
51
|
## Manual Safe Flow
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
|
-
import { GnosisSafeHandler } from '
|
|
55
|
-
import { ContractWriter } from '
|
|
54
|
+
import { GnosisSafeHandler } from 'dequanto/safe/GnosisSafeHandler';
|
|
55
|
+
import { ContractWriter } from 'dequanto/contracts/ContractWriter';
|
|
56
56
|
|
|
57
57
|
const writer = new ContractWriter(target.address, client);
|
|
58
58
|
const txWriter = await writer.writeAsync(safeAccount, 'airdrop()', [], {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Solidity Storage
|
|
2
|
+
|
|
3
|
+
Prefer the storage reader included in generated contract classes. For manual layouts, 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
|
+
## Generated Contract Storage Readers
|
|
23
|
+
|
|
24
|
+
Contract classes installed from a blockchain explorer with `0xweb install`, or generated from Solidity sources with `@0xweb/hardhat`, include a storage reader on `contractInstance.storage`. It contains the storage layout derived from the source, including slot positions and packed fields, so you can access storage by variable name without manually resolving slots.
|
|
25
|
+
|
|
26
|
+
On a Hardhat development network or fork, write a variable with `await contractInstance.storage.$set('someVar', value)`. The generated reader exposes `$set` and `$get`; `set` and `get` are methods on the underlying `SlotsStorage`.
|
|
27
|
+
|
|
28
|
+
For example, using the generated AavePool class:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { Web3ClientFactory } from 'dequanto/clients/Web3ClientFactory';
|
|
32
|
+
import { AavePool } from '../../../0xc/eth/AavePool/AavePool';
|
|
33
|
+
|
|
34
|
+
const client = await Web3ClientFactory.getAsync('hh:memory:eth');
|
|
35
|
+
const pool = new AavePool(undefined, client);
|
|
36
|
+
const id = await client.debug.snapshot();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await pool.storage.$set('_flashLoanPremium', 12_345n);
|
|
40
|
+
const premium = await pool.storage._flashLoanPremium();
|
|
41
|
+
const publicPremium = await pool.FLASHLOAN_PREMIUM_TOTAL();
|
|
42
|
+
} finally {
|
|
43
|
+
await client.debug.revert(id);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The AavePool import points to this repository's generated class; use your project's generated path. Keep generated storage layouts in sync with the deployed implementation, especially after proxy upgrades. See the [storage example](../examples/storage.spec.ts) for assertions and rollback verification.
|
|
48
|
+
|
|
49
|
+
## Parse Slots From Solidity
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { SlotsParser } from 'dequanto/solidity/SlotsParser';
|
|
53
|
+
|
|
54
|
+
const slots = await SlotsParser.slots({
|
|
55
|
+
path: './contracts/Vault.sol'
|
|
56
|
+
}, 'Vault');
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For inline code:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const slots = await SlotsParser.slots({ path: '', code }, 'Foo');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Parse Slots From ABI Inputs
|
|
66
|
+
|
|
67
|
+
Use this for standalone struct or tuple ABI data:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const slots = await SlotsParser.slotsFromAbi(
|
|
71
|
+
'(uint256 foo, address owner, bool enabled)'
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Read Contract Storage
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { SlotsStorage } from 'dequanto/solidity/SlotsStorage';
|
|
79
|
+
|
|
80
|
+
const storage = SlotsStorage.createWithClient(client, contract.address, slots);
|
|
81
|
+
|
|
82
|
+
const owner = await storage.get('owner');
|
|
83
|
+
const user = await storage.get('users[0]');
|
|
84
|
+
const balance = await storage.get(`balances["${account}"]`);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Supported path forms include:
|
|
88
|
+
|
|
89
|
+
- `field`
|
|
90
|
+
- `struct.field`
|
|
91
|
+
- `array[0]`
|
|
92
|
+
- `mapping["0x..."]`
|
|
93
|
+
- `mapping["0x..."].field`
|
|
94
|
+
- `nested[0].balances[1]`
|
|
95
|
+
- breadcrumb arrays such as `['users', account, 'balance']`
|
|
96
|
+
|
|
97
|
+
## Read Diamond Or Offset Storage
|
|
98
|
+
|
|
99
|
+
For library-selected storage roots:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const storage = SlotsStorage.createWithClient(client, contract.address, slots, {
|
|
103
|
+
storageOffset: $contract.keccak256('diamond.app.storage')
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Write Storage In Tests
|
|
108
|
+
|
|
109
|
+
`SlotsStorage.set(...)` exists and is useful in Hardhat/debug contexts:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
await storage.set('user.balance', 1000n);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For a known raw slot on Hardhat, use `await client.debug.setStorageAt(contractAddress, slot, '0x2a')`; the value is padded to 32 bytes. To set an ERC20 balance without resolving its layout manually, use `$erc20.setBalanceAny(client, tokenAddress, accountAddress, amount)`. See [Hardhat development and forking](hardhat.md) for examples and snapshot/rollback handling.
|
|
116
|
+
|
|
117
|
+
Do not use storage writes in production code unless the transport explicitly supports it and the task requires test/debug mutation.
|
|
118
|
+
|
|
119
|
+
## Raw Slot Reads
|
|
120
|
+
|
|
121
|
+
Use `client.getStorageAt(address, slot)` or `client.getStorageAtBatched(address, slots)` only when raw words are needed.
|
|
122
|
+
|
|
@@ -21,7 +21,7 @@ Useful tests:
|
|
|
21
21
|
## Token Metadata
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import { TokensService } from '
|
|
24
|
+
import { TokensService } from 'dequanto/tokens/TokensService';
|
|
25
25
|
|
|
26
26
|
const tokens = new TokensService('eth');
|
|
27
27
|
const usdc = await tokens.getKnownToken('USDC');
|
|
@@ -30,7 +30,7 @@ const usdc = await tokens.getKnownToken('USDC');
|
|
|
30
30
|
For forked Hardhat platform strings:
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
|
-
import { TokensServiceFactory } from '
|
|
33
|
+
import { TokensServiceFactory } from 'dequanto/tokens/TokensServiceFactory';
|
|
34
34
|
|
|
35
35
|
const tokens = await TokensServiceFactory.getAsync('hh:eth');
|
|
36
36
|
```
|
|
@@ -38,7 +38,7 @@ const tokens = await TokensServiceFactory.getAsync('hh:eth');
|
|
|
38
38
|
## Token Transfer Service
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
|
-
import { TokenTransferService } from '
|
|
41
|
+
import { TokenTransferService } from 'dequanto/tokens/TokenTransferService';
|
|
42
42
|
|
|
43
43
|
const service = new TokenTransferService(client);
|
|
44
44
|
const tx = await service.transfer(sender, receiver, 'USDC', 10);
|
|
@@ -69,7 +69,7 @@ const tx = await service.transferAllWithRemainder(sender, receiver, 'USDC', 1);
|
|
|
69
69
|
Generate or derive EOA accounts:
|
|
70
70
|
|
|
71
71
|
```ts
|
|
72
|
-
import { $sig } from '
|
|
72
|
+
import { $sig } from 'dequanto/utils/$sig';
|
|
73
73
|
|
|
74
74
|
const account = $sig.$account.generate();
|
|
75
75
|
const fromMnemonic = await $sig.$account.fromMnemonic(mnemonic, 0);
|
|
@@ -79,7 +79,7 @@ const fromKey = await $sig.$account.fromKey(privateKey);
|
|
|
79
79
|
Use `ChainAccountService` for named encrypted account storage:
|
|
80
80
|
|
|
81
81
|
```ts
|
|
82
|
-
import { ChainAccountService } from '
|
|
82
|
+
import { ChainAccountService } from 'dequanto/ChainAccountService';
|
|
83
83
|
|
|
84
84
|
const service = new ChainAccountService();
|
|
85
85
|
const account = await service.get('deployer');
|
|
@@ -91,9 +91,15 @@ Transaction APIs accept several account shapes:
|
|
|
91
91
|
|
|
92
92
|
- EOA account: `{ address, key }`
|
|
93
93
|
- named account: string resolved through `ChainAccountService`
|
|
94
|
-
- impersonated account in Hardhat
|
|
94
|
+
- impersonated account in Hardhat: `{ type: 'impersonated', address }` (no private key required)
|
|
95
95
|
- Safe account: `{ type: 'safe', address, operator, owners? }`
|
|
96
96
|
- ERC-4337 account: `{ type: 'erc4337', address, operator }`
|
|
97
97
|
|
|
98
98
|
Inspect `src/models/TAccount.ts` before adding a new account shape.
|
|
99
99
|
|
|
100
|
+
## Balances And Impersonation On Hardhat
|
|
101
|
+
|
|
102
|
+
Use `client.debug.setBalance(address, 10n ** 18n)` to set a native balance to 1 ETH. Use `$erc20.setBalanceAny(client, tokenAddress, accountAddress, amount)` to resolve and overwrite an ERC20 balance slot: `bigint` amounts are raw smallest units, while `number` amounts are converted using token decimals.
|
|
103
|
+
|
|
104
|
+
Pass an impersonated account to generated write methods, for example `await usdc.$receipt().transfer({ address, type: 'impersonated' }, to, amount)`. See [Hardhat development and forking](hardhat.md) and the [fork example](../examples/hardhat-fork.spec.ts).
|
|
105
|
+
|