@crewhaus/tool-chaincall 0.7.0 → 0.8.0
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/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +76 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +50 -15
- package/dist/lib/aggregator.d.ts +24 -0
- package/dist/lib/aggregator.js +70 -0
- package/dist/lib/codec.d.ts +5 -1
- package/dist/lib/codec.js +23 -3
- package/dist/lib/rpc.d.ts +58 -16
- package/dist/lib/rpc.js +105 -24
- package/package.json +13 -8
- package/dist/fixtures.d.ts +0 -120
- package/dist/fixtures.js +0 -205
package/dist/fixtures.js
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recorded node shapes, and a transport that serves them.
|
|
3
|
-
*
|
|
4
|
-
* Nothing in this package's suite opens a socket. Every test installs one of
|
|
5
|
-
* these through `_setRpc`, so the suite runs identically on a CI box with no
|
|
6
|
-
* egress and never depends on somebody else's rate limit or on a public
|
|
7
|
-
* endpoint being up.
|
|
8
|
-
*
|
|
9
|
-
* The return blobs are built with `@crewhaus/tool-onchain`'s real encoder
|
|
10
|
-
* rather than pasted as hex. A hand-typed `(bool,bytes)[]` with a plausible
|
|
11
|
-
* offset in it would make the decoder's own correctness untestable here: the
|
|
12
|
-
* fixture and the bug would agree.
|
|
13
|
-
*/
|
|
14
|
-
import { abiEncodeCall } from "@crewhaus/tool-onchain";
|
|
15
|
-
import { KNOWN_INTERFACES } from "./lib/proxy";
|
|
16
|
-
/** A 32-byte word holding an unsigned integer. */
|
|
17
|
-
export function word(value) {
|
|
18
|
-
return `0x${BigInt(value).toString(16).padStart(64, "0")}`;
|
|
19
|
-
}
|
|
20
|
-
/** A 32-byte word holding an address in its low 20 bytes. */
|
|
21
|
-
export function addressWord(address) {
|
|
22
|
-
return `0x${address.replace(/^0x/, "").toLowerCase().padStart(64, "0")}`;
|
|
23
|
-
}
|
|
24
|
-
/** A 32-byte word that is not an address: the top bytes are dirty. */
|
|
25
|
-
export const NON_ADDRESS_WORD = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bec8";
|
|
26
|
-
export const ZERO_WORD = word(0);
|
|
27
|
-
/**
|
|
28
|
-
* ABI-encode what `aggregate3` returns, through the real encoder.
|
|
29
|
-
*
|
|
30
|
-
* The trick is to encode a call taking exactly that argument and drop the
|
|
31
|
-
* four-byte selector: the remaining bytes are the argument's encoding, which
|
|
32
|
-
* is what a node hands back.
|
|
33
|
-
*/
|
|
34
|
-
export async function aggregate3Return(rows) {
|
|
35
|
-
const result = await abiEncodeCall.execute({
|
|
36
|
-
signature: "fixture((bool,bytes)[])",
|
|
37
|
-
args: [rows.map(([ok, data]) => [ok, data])],
|
|
38
|
-
});
|
|
39
|
-
if (typeof result !== "string")
|
|
40
|
-
throw new Error("the encoder returned non-text content");
|
|
41
|
-
const { data } = JSON.parse(result);
|
|
42
|
-
return `0x${data.slice(10)}`;
|
|
43
|
-
}
|
|
44
|
-
/** `Error(string)` revert bytes for a given message, through the real encoder. */
|
|
45
|
-
export async function errorStringRevert(message) {
|
|
46
|
-
const result = await abiEncodeCall.execute({ signature: "Error(string)", args: [message] });
|
|
47
|
-
if (typeof result !== "string")
|
|
48
|
-
throw new Error("the encoder returned non-text content");
|
|
49
|
-
return JSON.parse(result).data;
|
|
50
|
-
}
|
|
51
|
-
/** The canonical EIP-1167 runtime bytecode delegating to `target`. */
|
|
52
|
-
export function minimalProxyCode(target) {
|
|
53
|
-
return `0x363d3d373d3d3d363d73${target.replace(/^0x/, "").toLowerCase()}5af43d82803e903d91602b57fd5bf3`;
|
|
54
|
-
}
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// the transport stub
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
/** A JSON-RPC error as a provider throws it: an Error carrying `code`. */
|
|
59
|
-
export function jsonRpcError(code, message, data) {
|
|
60
|
-
const err = new Error(message);
|
|
61
|
-
err.code = code;
|
|
62
|
-
if (data !== undefined)
|
|
63
|
-
err.data = data;
|
|
64
|
-
return err;
|
|
65
|
-
}
|
|
66
|
-
/** What a node says about a method it has never heard of. */
|
|
67
|
-
export function methodNotFound(method) {
|
|
68
|
-
return jsonRpcError(-32601, `the method ${method} does not exist/is not available`);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* A transport built from a table of handlers.
|
|
72
|
-
*
|
|
73
|
-
* An unhandled method answers -32601, which is what a real node does and is
|
|
74
|
-
* also the one failure this package degrades on — so a test that forgets a
|
|
75
|
-
* handler exercises the degradation path loudly rather than hanging.
|
|
76
|
-
*/
|
|
77
|
-
export function rpcStub(handlers) {
|
|
78
|
-
const calls = [];
|
|
79
|
-
const perMethod = new Map();
|
|
80
|
-
const rpc = async (method, params) => {
|
|
81
|
-
calls.push({ method, params });
|
|
82
|
-
const seen = perMethod.get(method) ?? 0;
|
|
83
|
-
perMethod.set(method, seen + 1);
|
|
84
|
-
const handler = handlers[method];
|
|
85
|
-
if (handler === undefined)
|
|
86
|
-
throw methodNotFound(method);
|
|
87
|
-
return handler(params, seen);
|
|
88
|
-
};
|
|
89
|
-
return { rpc, calls, count: (method) => perMethod.get(method) ?? 0 };
|
|
90
|
-
}
|
|
91
|
-
/** Serve a different blob to each successive `eth_call`, in order. */
|
|
92
|
-
export function sequentialCalls(blobs) {
|
|
93
|
-
return (_params, index) => {
|
|
94
|
-
const blob = blobs[index];
|
|
95
|
-
if (blob === undefined)
|
|
96
|
-
throw new Error(`the stub has no eth_call blob #${index}`);
|
|
97
|
-
return blob;
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* The view rows `ContractInspect` expects, in the order it builds them:
|
|
102
|
-
* the 0xffffffff sentinel, one row per probed interface id, implementation(),
|
|
103
|
-
* proxiableUUID(), and the beacon's implementation() when there is a beacon.
|
|
104
|
-
*/
|
|
105
|
-
export function inspectViewRows(spec) {
|
|
106
|
-
const claimed = new Set((spec.supports ?? []).map((id) => id.toLowerCase()));
|
|
107
|
-
const boolRow = (value) => [true, word(value ? 1 : 0)];
|
|
108
|
-
const revertRow = [false, "0x"];
|
|
109
|
-
const rows = [
|
|
110
|
-
spec.sentinel === "revert" ? revertRow : boolRow(spec.sentinel ?? false),
|
|
111
|
-
...KNOWN_INTERFACES.map((i) => spec.sentinel === "revert" ? revertRow : boolRow(claimed.has(i.id.toLowerCase()))),
|
|
112
|
-
spec.implementationView === undefined
|
|
113
|
-
? revertRow
|
|
114
|
-
: [true, addressWord(spec.implementationView)],
|
|
115
|
-
spec.proxiable === true ? [true, word(1)] : revertRow,
|
|
116
|
-
];
|
|
117
|
-
if (spec.beaconImplementation !== undefined) {
|
|
118
|
-
rows.push(spec.beaconImplementation === "revert"
|
|
119
|
-
? revertRow
|
|
120
|
-
: [true, addressWord(spec.beaconImplementation)]);
|
|
121
|
-
}
|
|
122
|
-
return rows;
|
|
123
|
-
}
|
|
124
|
-
/** One block of `eth_simulateV1` output. */
|
|
125
|
-
export function simulateV1Result(blockNumber, calls) {
|
|
126
|
-
return [
|
|
127
|
-
{
|
|
128
|
-
number: `0x${blockNumber.toString(16)}`,
|
|
129
|
-
gasLimit: "0x1c9c380",
|
|
130
|
-
gasUsed: "0x5208",
|
|
131
|
-
calls: calls.map((call) => ({
|
|
132
|
-
returnData: call.returnData ?? "0x",
|
|
133
|
-
gasUsed: `0x${(call.gasUsed ?? 21_000).toString(16)}`,
|
|
134
|
-
status: `0x${(call.status ?? 1).toString(16)}`,
|
|
135
|
-
logs: call.logs ?? [],
|
|
136
|
-
...(call.error !== undefined ? { error: call.error } : {}),
|
|
137
|
-
})),
|
|
138
|
-
},
|
|
139
|
-
];
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* One block of `eth_simulateV1` output with the call objects passed through
|
|
143
|
-
* VERBATIM.
|
|
144
|
-
*
|
|
145
|
-
* `simulateV1Result` fills in `status`, `gasUsed` and `logs` for every call,
|
|
146
|
-
* which makes the case worth testing — a node that leaves one of them out —
|
|
147
|
-
* inexpressible. A fixture that cannot express a shape hides every bug that
|
|
148
|
-
* shape causes.
|
|
149
|
-
*/
|
|
150
|
-
export function simulateV1Raw(blockNumber, calls) {
|
|
151
|
-
return [
|
|
152
|
-
{
|
|
153
|
-
number: `0x${blockNumber.toString(16)}`,
|
|
154
|
-
gasLimit: "0x1c9c380",
|
|
155
|
-
gasUsed: "0x5208",
|
|
156
|
-
calls,
|
|
157
|
-
},
|
|
158
|
-
];
|
|
159
|
-
}
|
|
160
|
-
/** An ERC-20 Transfer log, the shape a simulation returns them in. */
|
|
161
|
-
export const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
162
|
-
export function transferLog(token, from, to, amount) {
|
|
163
|
-
return {
|
|
164
|
-
address: token,
|
|
165
|
-
topics: [TRANSFER_TOPIC, addressWord(from), addressWord(to)],
|
|
166
|
-
data: word(amount),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
export function blockResult(block) {
|
|
170
|
-
return {
|
|
171
|
-
number: `0x${block.number.toString(16)}`,
|
|
172
|
-
gasUsed: `0x${block.gasUsed.toString(16)}`,
|
|
173
|
-
gasLimit: `0x${block.gasLimit.toString(16)}`,
|
|
174
|
-
...(block.baseFeePerGas === undefined
|
|
175
|
-
? {}
|
|
176
|
-
: { baseFeePerGas: `0x${block.baseFeePerGas.toString(16)}` }),
|
|
177
|
-
hash: "0x00000000000000000000000000000000000000000000000000000000000000aa",
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
export function feeHistoryResult(history) {
|
|
181
|
-
const hex = (v) => `0x${v.toString(16)}`;
|
|
182
|
-
return {
|
|
183
|
-
oldestBlock: `0x${history.oldestBlock.toString(16)}`,
|
|
184
|
-
baseFeePerGas: history.baseFeePerGas.map(hex),
|
|
185
|
-
// A float on purpose: this is what the wire carries, and the projection
|
|
186
|
-
// must be proven not to use it.
|
|
187
|
-
gasUsedRatio: history.baseFeePerGas.slice(0, -1).map(() => 0.5123456789),
|
|
188
|
-
...(history.reward === undefined ? {} : { reward: history.reward.map((row) => row.map(hex)) }),
|
|
189
|
-
...(history.baseFeePerBlobGas === undefined
|
|
190
|
-
? {}
|
|
191
|
-
: { baseFeePerBlobGas: history.baseFeePerBlobGas.map(hex) }),
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
// addresses used across the suite
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
export const ADDR = Object.freeze({
|
|
198
|
-
proxy: "0x1111111111111111111111111111111111111111",
|
|
199
|
-
implementation: "0x2222222222222222222222222222222222222222",
|
|
200
|
-
admin: "0x3333333333333333333333333333333333333333",
|
|
201
|
-
beacon: "0x4444444444444444444444444444444444444444",
|
|
202
|
-
other: "0x5555555555555555555555555555555555555555",
|
|
203
|
-
token: "0x6666666666666666666666666666666666666666",
|
|
204
|
-
wallet: "0x7777777777777777777777777777777777777777",
|
|
205
|
-
});
|