@volga-sh/evm-ghostcall 0.0.3 → 0.0.4
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/README.md +51 -28
- package/dist/sdk/abi.d.ts +39 -0
- package/dist/sdk/abi.d.ts.map +1 -0
- package/dist/sdk/abi.js +20 -0
- package/dist/sdk/abi.js.map +1 -0
- package/dist/sdk/index.d.ts +55 -289
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +70 -312
- package/dist/sdk/index.js.map +1 -1
- package/package.json +5 -4
- package/src/Ghostcall.yul +15 -96
package/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# ghostcall
|
|
2
2
|
|
|
3
|
-
`ghostcall` batches EVM
|
|
3
|
+
`ghostcall` batches EVM contract reads without deploying a Multicall contract.
|
|
4
4
|
|
|
5
5
|
## Documentation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Start there for installation, examples, the API reference, protocol details, and endpoint limit notes.
|
|
7
|
+
Start at [ghostcall.volga.sh](https://ghostcall.volga.sh) for the setup guide,
|
|
8
|
+
recipes, API reference, protocol, and size limits.
|
|
10
9
|
|
|
11
10
|
## Install
|
|
12
11
|
|
|
@@ -14,16 +13,19 @@ Start there for installation, examples, the API reference, protocol details, and
|
|
|
14
13
|
npm install @volga-sh/evm-ghostcall
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
## Quick
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
This example uses viem for its client and ABI definition. ghostcall uses ox
|
|
19
|
+
internally to encode arguments and decode results:
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
```sh
|
|
22
|
+
npm install viem
|
|
23
|
+
```
|
|
20
24
|
|
|
21
25
|
```ts
|
|
22
26
|
import { aggregateDecodedCalls } from "@volga-sh/evm-ghostcall";
|
|
23
27
|
import {
|
|
24
28
|
createPublicClient,
|
|
25
|
-
decodeFunctionResult,
|
|
26
|
-
encodeFunctionData,
|
|
27
29
|
http,
|
|
28
30
|
parseAbi,
|
|
29
31
|
} from "viem";
|
|
@@ -34,35 +36,55 @@ const client = createPublicClient({
|
|
|
34
36
|
transport: http(),
|
|
35
37
|
});
|
|
36
38
|
|
|
37
|
-
const
|
|
39
|
+
const abi = parseAbi(["function totalSupply() view returns (uint256)"]);
|
|
40
|
+
const token = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
|
38
41
|
|
|
39
42
|
const [totalSupply] = await aggregateDecodedCalls(client, [
|
|
40
43
|
{
|
|
41
|
-
to:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
functionName: "totalSupply",
|
|
45
|
-
}),
|
|
46
|
-
decodeResult: (returnData) =>
|
|
47
|
-
decodeFunctionResult({
|
|
48
|
-
abi: erc20Abi,
|
|
49
|
-
functionName: "totalSupply",
|
|
50
|
-
data: returnData,
|
|
51
|
-
}),
|
|
44
|
+
to: token,
|
|
45
|
+
abi,
|
|
46
|
+
functionName: "totalSupply",
|
|
52
47
|
},
|
|
53
48
|
]);
|
|
49
|
+
// totalSupply is inferred as bigint.
|
|
54
50
|
```
|
|
55
51
|
|
|
56
|
-
|
|
52
|
+
Function names, arguments, and results are inferred from the ABI. Functions
|
|
53
|
+
with inputs require `args`, for example `args: [owner]` for `balanceOf`.
|
|
54
|
+
Keep ABIs literal with `as const`, viem's `parseAbi`, or ox's `Abi.from`.
|
|
55
|
+
|
|
56
|
+
For already-encoded calldata, use the raw API:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { aggregateCalls } from "@volga-sh/evm-ghostcall";
|
|
60
|
+
|
|
61
|
+
const results = await aggregateCalls(client, [
|
|
62
|
+
{ to: token, data: "0x18160ddd" },
|
|
63
|
+
]);
|
|
64
|
+
// [{ success: true, returnData: "0x..." }]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`aggregateDecodedCalls()` also accepts raw `data` with a custom `decodeResult`
|
|
68
|
+
callback, including in the same batch as ABI calls. Each entry uses either ABI
|
|
69
|
+
fields or raw calldata; TypeScript rejects entries that mix the two forms.
|
|
70
|
+
|
|
71
|
+
See [Getting Started](https://ghostcall.volga.sh/getting-started/) for a complete
|
|
72
|
+
two-call walkthrough.
|
|
57
73
|
|
|
58
74
|
## API
|
|
59
75
|
|
|
60
|
-
- `aggregateDecodedCalls()`
|
|
61
|
-
- `aggregateCalls()` sends
|
|
62
|
-
- `encodeCalls()` builds
|
|
63
|
-
- `decodeResults()` parses
|
|
76
|
+
- `aggregateDecodedCalls()` accepts ABI calls or custom decoders and returns a typed result tuple.
|
|
77
|
+
- `aggregateCalls()` sends calls and returns raw success or failure results.
|
|
78
|
+
- `encodeCalls()` builds request data for an `eth_call` without `to`.
|
|
79
|
+
- `decodeResults()` parses a raw ghostcall response.
|
|
80
|
+
|
|
81
|
+
Read the [API reference](https://ghostcall.volga.sh/api/) for signatures,
|
|
82
|
+
options, return types, and errors.
|
|
64
83
|
|
|
65
|
-
|
|
84
|
+
The public type surface contains seven types, including ghostcall's own `Hex`.
|
|
85
|
+
`GhostcallAggregateCall` is merged into `GhostcallCall`. See
|
|
86
|
+
[type import migration](https://ghostcall.volga.sh/api/types/#migrating-type-imports)
|
|
87
|
+
for the removed helper aliases. Runtime exports are unchanged.
|
|
66
88
|
|
|
67
89
|
## Development
|
|
68
90
|
|
|
@@ -73,11 +95,12 @@ npm run test
|
|
|
73
95
|
npm run check
|
|
74
96
|
```
|
|
75
97
|
|
|
76
|
-
|
|
98
|
+
To work on the documentation:
|
|
77
99
|
|
|
78
100
|
```sh
|
|
79
101
|
npm run docs:dev
|
|
80
102
|
npm run docs:build
|
|
81
103
|
```
|
|
82
104
|
|
|
83
|
-
The
|
|
105
|
+
The source is hosted at
|
|
106
|
+
[github.com/volga-sh/ghostcall](https://github.com/volga-sh/ghostcall).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Abi } from "ox/Abi";
|
|
2
|
+
import * as AbiFunction from "ox/AbiFunction";
|
|
3
|
+
import type * as AbiItem from "ox/AbiItem";
|
|
4
|
+
import type { GhostcallDecodedCall, Hex } from "./index.ts";
|
|
5
|
+
type FunctionCall<TFunction extends AbiFunction.AbiFunction> = TFunction extends AbiFunction.AbiFunction ? {
|
|
6
|
+
functionName: TFunction["name"];
|
|
7
|
+
} & (TFunction["inputs"] extends readonly [] ? {
|
|
8
|
+
args?: readonly [];
|
|
9
|
+
} : {
|
|
10
|
+
args: NonNullable<AbiFunction.encodeData.Args<TFunction>[0]>;
|
|
11
|
+
}) : never;
|
|
12
|
+
/**
|
|
13
|
+
* An ABI-described call. A literal ABI determines valid function names and args.
|
|
14
|
+
* Use `as const` or an ABI parser to retain those literal types.
|
|
15
|
+
*/
|
|
16
|
+
type GhostcallAbiCall<TAbi extends Abi = Abi> = {
|
|
17
|
+
to: Hex;
|
|
18
|
+
abi: TAbi;
|
|
19
|
+
data?: never;
|
|
20
|
+
decodeResult?: never;
|
|
21
|
+
allowFailure?: never;
|
|
22
|
+
} & (Abi extends TAbi ? {
|
|
23
|
+
functionName: string;
|
|
24
|
+
args?: readonly unknown[];
|
|
25
|
+
} : FunctionCall<Extract<TAbi[number], {
|
|
26
|
+
type: "function";
|
|
27
|
+
}>>);
|
|
28
|
+
type CallFunctionName<TCall extends GhostcallAbiCall> = TCall["functionName"] & AbiFunction.Name<TCall["abi"]>;
|
|
29
|
+
type CallArguments<TCall extends GhostcallAbiCall> = TCall extends {
|
|
30
|
+
args: infer TArgs extends readonly unknown[];
|
|
31
|
+
} ? TArgs : readonly [];
|
|
32
|
+
type ResolvedFunction<TCall extends GhostcallAbiCall> = AbiItem.fromAbi.Options<TCall["abi"], CallFunctionName<TCall>> extends AbiItem.fromAbi.Options<TCall["abi"], CallFunctionName<TCall>, infer TArgs> ? AbiItem.fromAbi.ReturnType<TCall["abi"], CallFunctionName<TCall>, Extract<CallArguments<TCall>, TArgs>, AbiFunction.AbiFunction> : never;
|
|
33
|
+
/** The decoded result of an ABI call, including argument-selected overloads. */
|
|
34
|
+
type GhostcallAbiResult<TCall extends GhostcallAbiCall> = AbiFunction.decodeResult.ReturnType<Extract<ResolvedFunction<TCall>, AbiFunction.AbiFunction>>;
|
|
35
|
+
/** Bind encoding and decoding to the same resolved ABI function. */
|
|
36
|
+
declare function prepareAbiCall(call: GhostcallAbiCall): GhostcallDecodedCall;
|
|
37
|
+
export type { GhostcallAbiCall, GhostcallAbiResult };
|
|
38
|
+
export { prepareAbiCall };
|
|
39
|
+
//# sourceMappingURL=abi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abi.d.ts","sourceRoot":"","sources":["../../src/sdk/abi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,KAAK,OAAO,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAG5D,KAAK,YAAY,CAAC,SAAS,SAAS,WAAW,CAAC,WAAW,IAC1D,SAAS,SAAS,WAAW,CAAC,WAAW,GACtC;IACA,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CAChC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,SAAS,EAAE,GACzC;IAAE,IAAI,CAAC,EAAE,SAAS,EAAE,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC,GACnE,KAAK,CAAC;AAEV;;;GAGG;AACH,KAAK,gBAAgB,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,IAAI;IAC/C,EAAE,EAAE,GAAG,CAAC;IACR,GAAG,EAAE,IAAI,CAAC;IACV,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,YAAY,CAAC,EAAE,KAAK,CAAC;CACrB,GAAG,CAAC,GAAG,SAAS,IAAI,GAClB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,GACnD,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC,CAAC;AAE9D,KAAK,gBAAgB,CAAC,KAAK,SAAS,gBAAgB,IAAI,KAAK,CAAC,cAAc,CAAC,GAC5E,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhC,KAAK,aAAa,CAAC,KAAK,SAAS,gBAAgB,IAAI,KAAK,SAAS;IAClE,IAAI,EAAE,MAAM,KAAK,SAAS,SAAS,OAAO,EAAE,CAAC;CAC7C,GACE,KAAK,GACL,SAAS,EAAE,CAAC;AAIf,KAAK,gBAAgB,CAAC,KAAK,SAAS,gBAAgB,IACnD,OAAO,CAAC,OAAO,CAAC,OAAO,CACtB,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,CACvB,SAAS,OAAO,CAAC,OAAO,CAAC,OAAO,CAChC,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,EACvB,MAAM,KAAK,CACX,GACE,OAAO,CAAC,OAAO,CAAC,UAAU,CAC1B,KAAK,CAAC,KAAK,CAAC,EACZ,gBAAgB,CAAC,KAAK,CAAC,EACvB,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,EACpC,WAAW,CAAC,WAAW,CACvB,GACA,KAAK,CAAC;AAEV,gFAAgF;AAChF,KAAK,kBAAkB,CAAC,KAAK,SAAS,gBAAgB,IACrD,WAAW,CAAC,YAAY,CAAC,UAAU,CAClC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,CACzD,CAAC;AAEH,oEAAoE;AACpE,iBAAS,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,oBAAoB,CAoBpE;AAED,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/sdk/abi.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as AbiFunction from "ox/AbiFunction";
|
|
2
|
+
/** Bind encoding and decoding to the same resolved ABI function. */
|
|
3
|
+
function prepareAbiCall(call) {
|
|
4
|
+
const args = call.args ?? [];
|
|
5
|
+
const abiFunction = AbiFunction.fromAbi(call.abi, call.functionName, {
|
|
6
|
+
args,
|
|
7
|
+
});
|
|
8
|
+
// ox permits selector-only encoding with no args. At this wire boundary,
|
|
9
|
+
// missing arguments must fail before RPC, including for dynamically loaded ABIs.
|
|
10
|
+
if (args.length !== abiFunction.inputs.length) {
|
|
11
|
+
throw new TypeError(`${call.functionName} expects ${abiFunction.inputs.length} arguments, received ${args.length}`);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
to: call.to,
|
|
15
|
+
data: AbiFunction.encodeData(abiFunction, args),
|
|
16
|
+
decodeResult: (returnData) => AbiFunction.decodeResult(abiFunction, returnData),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export { prepareAbiCall };
|
|
20
|
+
//# sourceMappingURL=abi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abi.js","sourceRoot":"","sources":["../../src/sdk/abi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,gBAAgB,CAAC;AA+D9C,oEAAoE;AACpE,SAAS,cAAc,CAAC,IAAsB;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE;QACpE,IAAI;KACJ,CAAC,CAAC;IAEH,yEAAyE;IACzE,iFAAiF;IACjF,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/C,MAAM,IAAI,SAAS,CAClB,GAAG,IAAI,CAAC,YAAY,YAAY,WAAW,CAAC,MAAM,CAAC,MAAM,wBAAwB,IAAI,CAAC,MAAM,EAAE,CAC9F,CAAC;IACH,CAAC;IAED,OAAO;QACN,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,WAAW,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC;QAC/C,YAAY,EAAE,CAAC,UAAU,EAAE,EAAE,CAC5B,WAAW,CAAC,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC;KAClD,CAAC;AACH,CAAC;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -1,322 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Ghostcall request and response data is represented as raw hex strings. The
|
|
5
|
-
* SDK does not accept byte arrays or ABI fragments.
|
|
6
|
-
*/
|
|
1
|
+
import { type GhostcallAbiCall, type GhostcallAbiResult } from "./abi.ts";
|
|
2
|
+
/** A 0x-prefixed string, validated at SDK wire boundaries. */
|
|
7
3
|
type Hex = `0x${string}`;
|
|
8
|
-
/**
|
|
9
|
-
* Hex-encoded RPC quantity prefixed with `0x`.
|
|
10
|
-
*/
|
|
11
|
-
type HexQuantity = `0x${string}`;
|
|
12
|
-
/**
|
|
13
|
-
* Block reference accepted by the outer `eth_call`.
|
|
14
|
-
*/
|
|
15
|
-
type GhostcallBlockReference = string | number | bigint;
|
|
16
|
-
/**
|
|
17
|
-
* One Ghostcall subcall entry.
|
|
18
|
-
*/
|
|
4
|
+
/** Raw calldata for one target. Encoding ignores the SDK-only failure policy. */
|
|
19
5
|
type GhostcallCall = {
|
|
20
|
-
/**
|
|
21
|
-
* Target contract address to invoke.
|
|
22
|
-
*/
|
|
23
6
|
to: Hex;
|
|
24
|
-
/**
|
|
25
|
-
* Hex-encoded call data to forward to {@link GhostcallCall.to}.
|
|
26
|
-
*
|
|
27
|
-
* The encoded payload is limited to `65535` bytes because Ghostcall stores each
|
|
28
|
-
* calldata length as a big-endian `uint16`.
|
|
29
|
-
*/
|
|
7
|
+
/** At most 65,535 bytes (the wire format stores a uint16 length). */
|
|
30
8
|
data: Hex;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* One Ghostcall aggregate subcall entry.
|
|
34
|
-
*
|
|
35
|
-
* The wire format does not include failure-policy bits. `allowFailure` is an SDK
|
|
36
|
-
* policy applied after Ghostcall returns the packed result entries.
|
|
37
|
-
*/
|
|
38
|
-
type GhostcallAggregateCall = GhostcallCall & {
|
|
39
|
-
/**
|
|
40
|
-
* Allows this subcall to return a failed result entry.
|
|
41
|
-
*
|
|
42
|
-
* Defaults to `false`, matching Multicall3's strict `aggregate3` behavior when
|
|
43
|
-
* a call does not explicitly opt into failure.
|
|
44
|
-
*/
|
|
9
|
+
/** Return failed entries from aggregateCalls instead of throwing. Default: false. */
|
|
45
10
|
allowFailure?: boolean;
|
|
46
11
|
};
|
|
47
|
-
/**
|
|
48
|
-
* One Ghostcall subcall entry for decoded aggregate results.
|
|
49
|
-
*/
|
|
12
|
+
/** Raw calldata with a decoder that only receives successful results. */
|
|
50
13
|
type GhostcallDecodedCall<TResult = unknown> = GhostcallCall & {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
decodeResult: GhostcallResultDecoder<TResult>;
|
|
14
|
+
abi?: never;
|
|
15
|
+
functionName?: never;
|
|
16
|
+
args?: never;
|
|
17
|
+
allowFailure?: never;
|
|
18
|
+
decodeResult: (returnData: Hex, entry: Extract<GhostcallResult, {
|
|
19
|
+
success: true;
|
|
20
|
+
}>, index: number) => TResult;
|
|
59
21
|
};
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
*/
|
|
63
|
-
type GhostcallSuccessResult = {
|
|
64
|
-
/**
|
|
65
|
-
* Indicates whether the underlying EVM `CALL` returned successfully.
|
|
66
|
-
*
|
|
67
|
-
* A `true` value means the target call returned successfully.
|
|
68
|
-
*/
|
|
22
|
+
/** Raw results retain input order and include revert data for failed calls. */
|
|
23
|
+
type GhostcallResult = {
|
|
69
24
|
success: true;
|
|
70
|
-
/**
|
|
71
|
-
* Raw return data produced by the target call.
|
|
72
|
-
*/
|
|
73
25
|
returnData: Hex;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* One failed Ghostcall result entry.
|
|
77
|
-
*/
|
|
78
|
-
type GhostcallFailedResult = {
|
|
79
|
-
/**
|
|
80
|
-
* Indicates whether the underlying EVM `CALL` returned successfully.
|
|
81
|
-
*
|
|
82
|
-
* A `false` value means the target call reverted or otherwise failed, but the
|
|
83
|
-
* Ghostcall batch itself still completed successfully.
|
|
84
|
-
*/
|
|
26
|
+
} | {
|
|
85
27
|
success: false;
|
|
86
|
-
/**
|
|
87
|
-
* Raw return data produced by the target call.
|
|
88
|
-
*
|
|
89
|
-
* For failed calls this contains revert data, if any. The SDK leaves higher-level
|
|
90
|
-
* ABI decoding and failure policy to the caller.
|
|
91
|
-
*/
|
|
92
28
|
returnData: Hex;
|
|
93
29
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
type
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
type
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*/
|
|
106
|
-
declare class GhostcallSubcallError extends Error {
|
|
107
|
-
readonly index: number;
|
|
108
|
-
readonly call: GhostcallAggregateCall;
|
|
109
|
-
readonly result: GhostcallFailedResult;
|
|
110
|
-
constructor(index: number, call: GhostcallAggregateCall, result: GhostcallFailedResult);
|
|
111
|
-
}
|
|
112
|
-
type GhostcallDecodedResults<TCalls extends readonly GhostcallDecodedCall[]> = {
|
|
113
|
-
-readonly [Index in keyof TCalls]: TCalls[Index] extends {
|
|
114
|
-
decodeResult: GhostcallResultDecoder<infer TResult>;
|
|
115
|
-
} ? TResult : never;
|
|
30
|
+
type GhostcallFailedResult = Extract<GhostcallResult, {
|
|
31
|
+
success: false;
|
|
32
|
+
}>;
|
|
33
|
+
type GhostcallDecodedInput = GhostcallAbiCall | GhostcallDecodedCall;
|
|
34
|
+
type ValidatedDecodedCall<TCall extends GhostcallDecodedInput> = TCall extends GhostcallAbiCall ? GhostcallAbiCall<TCall["abi"]> : TCall;
|
|
35
|
+
type ValidatedDecodedCalls<TCalls extends readonly GhostcallDecodedInput[]> = {
|
|
36
|
+
[Index in keyof TCalls]: ValidatedDecodedCall<TCalls[Index]>;
|
|
37
|
+
};
|
|
38
|
+
type DecodedResult<TCall extends GhostcallDecodedInput> = TCall extends GhostcallAbiCall ? GhostcallAbiResult<TCall> : TCall extends GhostcallDecodedCall ? ReturnType<TCall["decodeResult"]> : never;
|
|
39
|
+
type GhostcallDecodedResults<TCalls extends readonly GhostcallDecodedInput[]> = {
|
|
40
|
+
-readonly [Index in keyof TCalls]: DecodedResult<TCalls[Index]>;
|
|
116
41
|
};
|
|
117
42
|
type GhostcallEncodeOptions = {
|
|
118
|
-
/**
|
|
119
|
-
* Maximum allowed CREATE initcode size in bytes.
|
|
120
|
-
*
|
|
121
|
-
* This applies to the full request `data`, including bundled Ghostcall
|
|
122
|
-
* initcode and every encoded subcall entry.
|
|
123
|
-
*
|
|
124
|
-
* Defaults to Ethereum's EIP-3860 limit of `49,152` bytes.
|
|
125
|
-
*/
|
|
43
|
+
/** Full CREATE request ceiling, including bundled initcode. Default: 49,152 bytes. */
|
|
126
44
|
maxInitcodeBytes?: number;
|
|
127
45
|
};
|
|
128
|
-
type GhostcallEthCallOptions = {
|
|
129
|
-
/**
|
|
130
|
-
* Optional `from` address for the outer `eth_call`.
|
|
131
|
-
*/
|
|
132
|
-
from?: Hex;
|
|
133
|
-
/**
|
|
134
|
-
* Optional gas limit for the outer `eth_call`.
|
|
135
|
-
*/
|
|
136
|
-
gas?: HexQuantity;
|
|
137
|
-
/**
|
|
138
|
-
* Optional block tag, hex quantity, or block number for the outer `eth_call`.
|
|
139
|
-
*
|
|
140
|
-
* Decimal strings, numbers, and bigints are normalized to hex quantities.
|
|
141
|
-
* Defaults to `latest`.
|
|
142
|
-
*/
|
|
143
|
-
blockTag?: GhostcallBlockReference;
|
|
144
|
-
};
|
|
145
46
|
type GhostcallAggregateOptions = GhostcallEncodeOptions & {
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
47
|
+
/** Controls the outer eth_call, shared by the entire batch. */
|
|
48
|
+
ethCall?: {
|
|
49
|
+
from?: Hex;
|
|
50
|
+
/** Canonical RPC hex quantity. */
|
|
51
|
+
gas?: Hex;
|
|
52
|
+
/** Decimal block numbers are normalized to hex. Default: "latest". */
|
|
53
|
+
blockTag?: string | number | bigint;
|
|
54
|
+
};
|
|
151
55
|
};
|
|
152
|
-
|
|
153
|
-
* Minimal EIP-1193 provider shape used by the SDK.
|
|
154
|
-
*/
|
|
155
|
-
type EIP1193ProviderWithRequestFn = {
|
|
56
|
+
type Provider = {
|
|
156
57
|
request(args: {
|
|
157
58
|
method: string;
|
|
158
59
|
params?: unknown;
|
|
159
60
|
}): Promise<unknown>;
|
|
160
61
|
};
|
|
62
|
+
/** A disallowed failure, with its zero-based index, executed call, and revert data. */
|
|
63
|
+
declare class GhostcallSubcallError extends Error {
|
|
64
|
+
readonly index: number;
|
|
65
|
+
readonly call: GhostcallCall;
|
|
66
|
+
readonly result: GhostcallFailedResult;
|
|
67
|
+
constructor(index: number, call: GhostcallCall, result: GhostcallFailedResult);
|
|
68
|
+
}
|
|
161
69
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* The returned hex string already includes the bundled Ghostcall initcode followed
|
|
166
|
-
* by the compact binary payload for each subcall, so callers can pass it directly
|
|
167
|
-
* as the `data` field of an `eth_call` request without supplying a `to` address.
|
|
168
|
-
* Each encoded subcall entry uses the compact layout `[len(2)][target(20)][data]`.
|
|
169
|
-
* The bundled initcode assumes appended bytes follow this exact shape; this
|
|
170
|
-
* function is the supported boundary for producing well-formed Ghostcall payloads.
|
|
171
|
-
*
|
|
172
|
-
* @param calls - Ordered list of subcalls to execute. Each entry becomes one
|
|
173
|
-
* Ghostcall payload segment in the same order it appears here.
|
|
174
|
-
* @param options - Optional encoding controls.
|
|
175
|
-
*
|
|
176
|
-
* @returns Full CREATE payload consisting of the bundled Ghostcall initcode plus
|
|
177
|
-
* the encoded call list.
|
|
178
|
-
*
|
|
179
|
-
* @throws {TypeError} If any call address or calldata value is not valid hex.
|
|
180
|
-
* @throws {RangeError} If any call data exceeds the protocol `uint16` length limit
|
|
181
|
-
* or if the full encoded CREATE payload would exceed the
|
|
182
|
-
* configured initcode size limit.
|
|
183
|
-
*
|
|
184
|
-
* @example
|
|
185
|
-
* const data = encodeCalls([
|
|
186
|
-
* {
|
|
187
|
-
* // USDC on Ethereum mainnet: balanceOf(Binance 14)
|
|
188
|
-
* to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
189
|
-
* data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
|
|
190
|
-
* },
|
|
191
|
-
* {
|
|
192
|
-
* // WETH9 on Ethereum mainnet: totalSupply()
|
|
193
|
-
* to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
194
|
-
* data: "0x18160ddd",
|
|
195
|
-
* },
|
|
196
|
-
* ]);
|
|
197
|
-
*
|
|
198
|
-
* // Later:
|
|
199
|
-
* // provider.request({ method: "eth_call", params: [{ data }, "latest"] })
|
|
200
|
-
*/
|
|
201
|
-
declare function encodeCalls(calls: readonly GhostcallCall[], options?: GhostcallEncodeOptions): Hex;
|
|
202
|
-
/**
|
|
203
|
-
* Sends a Ghostcall batch with a CREATE-style `eth_call` and decodes the result.
|
|
204
|
-
*
|
|
205
|
-
* This is the provider-facing counterpart to {@link encodeCalls} and
|
|
206
|
-
* {@link decodeResults}. It sends the bundled Ghostcall initcode as the `data`
|
|
207
|
-
* field of `eth_call` without a `to` address, then returns raw result entries
|
|
208
|
-
* in the same order as the input calls. Request bytes are built through
|
|
209
|
-
* {@link encodeCalls}, so SDK callers get the supported payload validation before
|
|
210
|
-
* the RPC request is sent.
|
|
211
|
-
*
|
|
212
|
-
* By default, any failed subcall makes this method reject. Set
|
|
213
|
-
* `allowFailure: true` on a call to receive that failed entry in the returned
|
|
214
|
-
* results instead. Use {@link aggregateDecodedCalls} when you want a strict batch
|
|
215
|
-
* that returns decoded values directly. Use `options.ethCall` to forward `from`,
|
|
216
|
-
* `gas`, or `blockTag` to the outer `eth_call`.
|
|
217
|
-
*
|
|
218
|
-
* @param provider - EIP-1193-compatible provider with a `request` method.
|
|
219
|
-
* @param calls - Ordered list of subcalls to execute.
|
|
220
|
-
* @param options - Optional outer call and initcode controls.
|
|
221
|
-
*
|
|
222
|
-
* @returns Ordered Ghostcall result entries.
|
|
223
|
-
*
|
|
224
|
-
* @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
|
|
225
|
-
* provider returns a non-hex `eth_call` result.
|
|
226
|
-
* @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
|
|
227
|
-
* configured CREATE initcode ceiling.
|
|
228
|
-
* @throws {GhostcallSubcallError} If a subcall fails without `allowFailure: true`.
|
|
229
|
-
* @throws {Error} If the response entry count does not match the request entry count.
|
|
230
|
-
*
|
|
231
|
-
* @example
|
|
232
|
-
* const results = await aggregateCalls(provider, [
|
|
233
|
-
* {
|
|
234
|
-
* // USDC on Ethereum mainnet: balanceOf(Binance 14)
|
|
235
|
-
* to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
236
|
-
* data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
|
|
237
|
-
* },
|
|
238
|
-
* {
|
|
239
|
-
* // WETH9 on Ethereum mainnet: totalSupply()
|
|
240
|
-
* to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
241
|
-
* data: "0x18160ddd",
|
|
242
|
-
* },
|
|
243
|
-
* ]);
|
|
70
|
+
* Build CREATE-style eth_call data: initcode followed by [length (2)][target (20)][data].
|
|
71
|
+
* Send without a `to` address. Invalid inputs throw TypeError; size limits throw RangeError.
|
|
244
72
|
*/
|
|
245
|
-
declare function
|
|
73
|
+
declare function encodeCalls(calls: readonly GhostcallCall[], { maxInitcodeBytes, }?: GhostcallEncodeOptions): Hex;
|
|
246
74
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* This is the decoded counterpart to {@link aggregateCalls}. It sends the bundled
|
|
250
|
-
* Ghostcall initcode as the `data` field of `eth_call` without a `to` address,
|
|
251
|
-
* then runs each call's `decodeResult` callback over the successful return data in
|
|
252
|
-
* the same order as the input calls.
|
|
253
|
-
*
|
|
254
|
-
* `aggregateDecodedCalls` is always strict. Its TypeScript input shape requires a
|
|
255
|
-
* `decodeResult` callback on every call and does not accept `allowFailure`.
|
|
256
|
-
* Any failed subcall rejects with {@link GhostcallSubcallError}. Use
|
|
257
|
-
* {@link aggregateCalls} if you need raw failed entries. Use `options.ethCall`
|
|
258
|
-
* to forward `from`, `gas`, or `blockTag` to the outer `eth_call`.
|
|
259
|
-
*
|
|
260
|
-
* @param provider - EIP-1193-compatible provider with a `request` method.
|
|
261
|
-
* @param calls - Ordered list of strict decoded subcalls to execute.
|
|
262
|
-
* @param options - Optional outer call and initcode controls.
|
|
263
|
-
*
|
|
264
|
-
* @returns Ordered list of decoded values.
|
|
265
|
-
*
|
|
266
|
-
* @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
|
|
267
|
-
* provider returns a non-hex `eth_call` result.
|
|
268
|
-
* @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
|
|
269
|
-
* configured CREATE initcode ceiling.
|
|
270
|
-
* @throws {GhostcallSubcallError} If any subcall fails.
|
|
271
|
-
* @throws {Error} If the response entry count does not match the request entry count.
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* const erc20Abi = parseAbi([
|
|
275
|
-
* "function balanceOf(address account) view returns (uint256)",
|
|
276
|
-
* ]);
|
|
277
|
-
* const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
278
|
-
* const owner = "0x28C6c06298d514Db089934071355E5743bf21d60";
|
|
279
|
-
*
|
|
280
|
-
* const [balance] = await aggregateDecodedCalls(provider, [
|
|
281
|
-
* {
|
|
282
|
-
* to: usdc,
|
|
283
|
-
* data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
|
|
284
|
-
* decodeResult: (returnData) => decodeFunctionResult({
|
|
285
|
-
* abi: erc20Abi,
|
|
286
|
-
* functionName: "balanceOf",
|
|
287
|
-
* data: returnData,
|
|
288
|
-
* }),
|
|
289
|
-
* },
|
|
290
|
-
* ]);
|
|
75
|
+
* Execute a raw batch in order. Failed calls throw GhostcallSubcallError unless
|
|
76
|
+
* their entry sets allowFailure. Provider errors pass through unchanged.
|
|
291
77
|
*/
|
|
292
|
-
declare function
|
|
78
|
+
declare function aggregateCalls(provider: Provider, calls: readonly GhostcallCall[], options?: GhostcallAggregateOptions): Promise<GhostcallResult[]>;
|
|
293
79
|
/**
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* Each decoded entry corresponds to exactly one subcall in the original batch and
|
|
297
|
-
* preserves the original ordering. The SDK intentionally returns raw result bytes
|
|
298
|
-
* rather than ABI-decoding them so higher-level callers can apply their own
|
|
299
|
-
* decoding and failure policy.
|
|
300
|
-
*
|
|
301
|
-
* @param data - Raw bytes returned by Ghostcall, typically the direct result of a
|
|
302
|
-
* CREATE-style `eth_call`.
|
|
303
|
-
*
|
|
304
|
-
* @returns Ordered list of decoded Ghostcall result entries. Returns an empty
|
|
305
|
-
* array for `0x`.
|
|
306
|
-
*
|
|
307
|
-
* @throws {TypeError} If the provided data is not valid hex, if a result header is
|
|
308
|
-
* truncated, or if an entry body is shorter than advertised.
|
|
309
|
-
*
|
|
310
|
-
* @example
|
|
311
|
-
* const results = decodeResults("0x8002cafe0004deadbeef");
|
|
312
|
-
*
|
|
313
|
-
* console.log(results);
|
|
314
|
-
* // [
|
|
315
|
-
* // { success: true, returnData: "0xcafe" },
|
|
316
|
-
* // { success: false, returnData: "0xdeadbeef" }
|
|
317
|
-
* // ]
|
|
80
|
+
* Execute ABI-described calls or raw calls with custom decoders. Each tuple position
|
|
81
|
+
* retains its result type. Any failed call throws; encoding/decoding errors pass through.
|
|
318
82
|
*/
|
|
83
|
+
declare function aggregateDecodedCalls<const TCalls extends readonly GhostcallDecodedInput[]>(provider: Provider, calls: TCalls & NoInfer<ValidatedDecodedCalls<TCalls>>, options?: GhostcallAggregateOptions): Promise<GhostcallDecodedResults<TCalls>>;
|
|
84
|
+
/** Decode ordered [success bit | uint15 length][returndata] entries. Reject malformed data. */
|
|
319
85
|
declare function decodeResults(data: Hex): GhostcallResult[];
|
|
320
|
-
export type {
|
|
86
|
+
export type { GhostcallAbiCall, GhostcallAggregateOptions, GhostcallCall, GhostcallDecodedCall, GhostcallEncodeOptions, GhostcallResult, Hex, };
|
|
321
87
|
export { aggregateCalls, aggregateDecodedCalls, decodeResults, encodeCalls, GhostcallSubcallError, };
|
|
322
88
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/sdk/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"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"}
|
package/dist/sdk/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { validate as isAddress } from "ox/Address";
|
|
2
|
+
import { size as hexSize, validate as isHex } from "ox/Hex";
|
|
3
|
+
import { prepareAbiCall, } from "./abi.js";
|
|
1
4
|
import { ghostcallInitcode } from "./generated/initcode.js";
|
|
2
|
-
/**
|
|
3
|
-
* Error thrown when a strict Ghostcall batch encounters a failed subcall.
|
|
4
|
-
*/
|
|
5
|
+
/** A disallowed failure, with its zero-based index, executed call, and revert data. */
|
|
5
6
|
class GhostcallSubcallError extends Error {
|
|
6
7
|
index;
|
|
7
8
|
call;
|
|
@@ -12,139 +13,60 @@ class GhostcallSubcallError extends Error {
|
|
|
12
13
|
this.index = index;
|
|
13
14
|
this.call = call;
|
|
14
15
|
this.result = result;
|
|
15
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
const
|
|
18
|
+
const encodedCallHeaderSize = 22;
|
|
19
19
|
const encodedHeaderHexLength = 4;
|
|
20
20
|
const maxCalldataSize = 0xffff;
|
|
21
|
-
const encodedCallHeaderSize = 0x16;
|
|
22
21
|
const defaultMaxCreateInitcodeSize = 0xc000;
|
|
23
22
|
const successFlagMask = 0x8000;
|
|
24
23
|
const returnDataLengthMask = 0x7fff;
|
|
25
|
-
const bundledInitcodeSize =
|
|
24
|
+
const bundledInitcodeSize = hexSize(ghostcallInitcode);
|
|
26
25
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* The returned hex string already includes the bundled Ghostcall initcode followed
|
|
31
|
-
* by the compact binary payload for each subcall, so callers can pass it directly
|
|
32
|
-
* as the `data` field of an `eth_call` request without supplying a `to` address.
|
|
33
|
-
* Each encoded subcall entry uses the compact layout `[len(2)][target(20)][data]`.
|
|
34
|
-
* The bundled initcode assumes appended bytes follow this exact shape; this
|
|
35
|
-
* function is the supported boundary for producing well-formed Ghostcall payloads.
|
|
36
|
-
*
|
|
37
|
-
* @param calls - Ordered list of subcalls to execute. Each entry becomes one
|
|
38
|
-
* Ghostcall payload segment in the same order it appears here.
|
|
39
|
-
* @param options - Optional encoding controls.
|
|
40
|
-
*
|
|
41
|
-
* @returns Full CREATE payload consisting of the bundled Ghostcall initcode plus
|
|
42
|
-
* the encoded call list.
|
|
43
|
-
*
|
|
44
|
-
* @throws {TypeError} If any call address or calldata value is not valid hex.
|
|
45
|
-
* @throws {RangeError} If any call data exceeds the protocol `uint16` length limit
|
|
46
|
-
* or if the full encoded CREATE payload would exceed the
|
|
47
|
-
* configured initcode size limit.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* const data = encodeCalls([
|
|
51
|
-
* {
|
|
52
|
-
* // USDC on Ethereum mainnet: balanceOf(Binance 14)
|
|
53
|
-
* to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
54
|
-
* data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
|
|
55
|
-
* },
|
|
56
|
-
* {
|
|
57
|
-
* // WETH9 on Ethereum mainnet: totalSupply()
|
|
58
|
-
* to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
59
|
-
* data: "0x18160ddd",
|
|
60
|
-
* },
|
|
61
|
-
* ]);
|
|
62
|
-
*
|
|
63
|
-
* // Later:
|
|
64
|
-
* // provider.request({ method: "eth_call", params: [{ data }, "latest"] })
|
|
26
|
+
* Build CREATE-style eth_call data: initcode followed by [length (2)][target (20)][data].
|
|
27
|
+
* Send without a `to` address. Invalid inputs throw TypeError; size limits throw RangeError.
|
|
65
28
|
*/
|
|
66
|
-
function encodeCalls(calls,
|
|
29
|
+
function encodeCalls(calls, { maxInitcodeBytes = defaultMaxCreateInitcodeSize, } = {}) {
|
|
30
|
+
if (!Number.isSafeInteger(maxInitcodeBytes) || maxInitcodeBytes < 0) {
|
|
31
|
+
throw new TypeError("options.maxInitcodeBytes must be a non-negative safe integer");
|
|
32
|
+
}
|
|
67
33
|
const encodedParts = [ghostcallInitcode.slice(2)];
|
|
68
|
-
const maxInitcodeBytes = resolveMaxInitcodeBytes(options.maxInitcodeBytes);
|
|
69
34
|
let totalEncodedSize = bundledInitcodeSize;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
35
|
+
const sizeError = `encoded Ghostcall initcode exceeds the ${maxInitcodeBytes}-byte CREATE initcode limit`;
|
|
36
|
+
if (totalEncodedSize > maxInitcodeBytes)
|
|
37
|
+
throw new RangeError(sizeError);
|
|
73
38
|
for (const [index, call] of calls.entries()) {
|
|
74
39
|
assertAddress(call.to, `calls[${index}].to`);
|
|
75
40
|
const calldata = assertHex(call.data, `calls[${index}].data`);
|
|
76
|
-
const calldataSize =
|
|
41
|
+
const calldataSize = hexSize(calldata);
|
|
77
42
|
if (calldataSize > maxCalldataSize) {
|
|
78
43
|
throw new RangeError(`calls[${index}].data exceeds the ${maxCalldataSize}-byte calldata limit`);
|
|
79
44
|
}
|
|
80
45
|
totalEncodedSize += encodedCallHeaderSize + calldataSize;
|
|
81
|
-
if (totalEncodedSize > maxInitcodeBytes)
|
|
82
|
-
throw new RangeError(
|
|
83
|
-
|
|
84
|
-
encodedParts.push(calldataSize.toString(16).padStart(4, "0"));
|
|
85
|
-
encodedParts.push(call.to.slice(2));
|
|
86
|
-
encodedParts.push(calldata.slice(2));
|
|
46
|
+
if (totalEncodedSize > maxInitcodeBytes)
|
|
47
|
+
throw new RangeError(sizeError);
|
|
48
|
+
encodedParts.push(calldataSize.toString(16).padStart(4, "0"), call.to.slice(2), calldata.slice(2));
|
|
87
49
|
}
|
|
88
50
|
return `0x${encodedParts.join("")}`;
|
|
89
51
|
}
|
|
90
52
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* This is the provider-facing counterpart to {@link encodeCalls} and
|
|
94
|
-
* {@link decodeResults}. It sends the bundled Ghostcall initcode as the `data`
|
|
95
|
-
* field of `eth_call` without a `to` address, then returns raw result entries
|
|
96
|
-
* in the same order as the input calls. Request bytes are built through
|
|
97
|
-
* {@link encodeCalls}, so SDK callers get the supported payload validation before
|
|
98
|
-
* the RPC request is sent.
|
|
99
|
-
*
|
|
100
|
-
* By default, any failed subcall makes this method reject. Set
|
|
101
|
-
* `allowFailure: true` on a call to receive that failed entry in the returned
|
|
102
|
-
* results instead. Use {@link aggregateDecodedCalls} when you want a strict batch
|
|
103
|
-
* that returns decoded values directly. Use `options.ethCall` to forward `from`,
|
|
104
|
-
* `gas`, or `blockTag` to the outer `eth_call`.
|
|
105
|
-
*
|
|
106
|
-
* @param provider - EIP-1193-compatible provider with a `request` method.
|
|
107
|
-
* @param calls - Ordered list of subcalls to execute.
|
|
108
|
-
* @param options - Optional outer call and initcode controls.
|
|
109
|
-
*
|
|
110
|
-
* @returns Ordered Ghostcall result entries.
|
|
111
|
-
*
|
|
112
|
-
* @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
|
|
113
|
-
* provider returns a non-hex `eth_call` result.
|
|
114
|
-
* @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
|
|
115
|
-
* configured CREATE initcode ceiling.
|
|
116
|
-
* @throws {GhostcallSubcallError} If a subcall fails without `allowFailure: true`.
|
|
117
|
-
* @throws {Error} If the response entry count does not match the request entry count.
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* const results = await aggregateCalls(provider, [
|
|
121
|
-
* {
|
|
122
|
-
* // USDC on Ethereum mainnet: balanceOf(Binance 14)
|
|
123
|
-
* to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
124
|
-
* data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
|
|
125
|
-
* },
|
|
126
|
-
* {
|
|
127
|
-
* // WETH9 on Ethereum mainnet: totalSupply()
|
|
128
|
-
* to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
129
|
-
* data: "0x18160ddd",
|
|
130
|
-
* },
|
|
131
|
-
* ]);
|
|
53
|
+
* Execute a raw batch in order. Failed calls throw GhostcallSubcallError unless
|
|
54
|
+
* their entry sets allowFailure. Provider errors pass through unchanged.
|
|
132
55
|
*/
|
|
133
56
|
async function aggregateCalls(provider, calls, options) {
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
assertAddress(
|
|
140
|
-
ethCall.from =
|
|
141
|
-
}
|
|
142
|
-
if (
|
|
143
|
-
ethCall.gas = assertHexQuantity(
|
|
144
|
-
}
|
|
57
|
+
const { from, gas, blockTag } = options?.ethCall ?? {};
|
|
58
|
+
const ethCall = {
|
|
59
|
+
data: encodeCalls(calls, options ?? {}),
|
|
60
|
+
};
|
|
61
|
+
if (from !== undefined) {
|
|
62
|
+
assertAddress(from, "options.ethCall.from");
|
|
63
|
+
ethCall.from = from;
|
|
64
|
+
}
|
|
65
|
+
if (gas !== undefined)
|
|
66
|
+
ethCall.gas = assertHexQuantity(gas, "options.ethCall.gas");
|
|
145
67
|
const result = await provider.request({
|
|
146
68
|
method: "eth_call",
|
|
147
|
-
params: [ethCall, blockTag],
|
|
69
|
+
params: [ethCall, normalizeBlockTag(blockTag ?? "latest")],
|
|
148
70
|
});
|
|
149
71
|
const entries = decodeResults(assertHex(result, "eth_call result"));
|
|
150
72
|
if (entries.length !== calls.length) {
|
|
@@ -159,243 +81,79 @@ async function aggregateCalls(provider, calls, options) {
|
|
|
159
81
|
return entries;
|
|
160
82
|
}
|
|
161
83
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* This is the decoded counterpart to {@link aggregateCalls}. It sends the bundled
|
|
165
|
-
* Ghostcall initcode as the `data` field of `eth_call` without a `to` address,
|
|
166
|
-
* then runs each call's `decodeResult` callback over the successful return data in
|
|
167
|
-
* the same order as the input calls.
|
|
168
|
-
*
|
|
169
|
-
* `aggregateDecodedCalls` is always strict. Its TypeScript input shape requires a
|
|
170
|
-
* `decodeResult` callback on every call and does not accept `allowFailure`.
|
|
171
|
-
* Any failed subcall rejects with {@link GhostcallSubcallError}. Use
|
|
172
|
-
* {@link aggregateCalls} if you need raw failed entries. Use `options.ethCall`
|
|
173
|
-
* to forward `from`, `gas`, or `blockTag` to the outer `eth_call`.
|
|
174
|
-
*
|
|
175
|
-
* @param provider - EIP-1193-compatible provider with a `request` method.
|
|
176
|
-
* @param calls - Ordered list of strict decoded subcalls to execute.
|
|
177
|
-
* @param options - Optional outer call and initcode controls.
|
|
178
|
-
*
|
|
179
|
-
* @returns Ordered list of decoded values.
|
|
180
|
-
*
|
|
181
|
-
* @throws {TypeError} If inputs are not valid Ghostcall call entries or if the
|
|
182
|
-
* provider returns a non-hex `eth_call` result.
|
|
183
|
-
* @throws {RangeError} If the encoded CREATE payload exceeds protocol or the
|
|
184
|
-
* configured CREATE initcode ceiling.
|
|
185
|
-
* @throws {GhostcallSubcallError} If any subcall fails.
|
|
186
|
-
* @throws {Error} If the response entry count does not match the request entry count.
|
|
187
|
-
*
|
|
188
|
-
* @example
|
|
189
|
-
* const erc20Abi = parseAbi([
|
|
190
|
-
* "function balanceOf(address account) view returns (uint256)",
|
|
191
|
-
* ]);
|
|
192
|
-
* const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
193
|
-
* const owner = "0x28C6c06298d514Db089934071355E5743bf21d60";
|
|
194
|
-
*
|
|
195
|
-
* const [balance] = await aggregateDecodedCalls(provider, [
|
|
196
|
-
* {
|
|
197
|
-
* to: usdc,
|
|
198
|
-
* data: "0x70a0823100000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
|
|
199
|
-
* decodeResult: (returnData) => decodeFunctionResult({
|
|
200
|
-
* abi: erc20Abi,
|
|
201
|
-
* functionName: "balanceOf",
|
|
202
|
-
* data: returnData,
|
|
203
|
-
* }),
|
|
204
|
-
* },
|
|
205
|
-
* ]);
|
|
84
|
+
* Execute ABI-described calls or raw calls with custom decoders. Each tuple position
|
|
85
|
+
* retains its result type. Any failed call throws; encoding/decoding errors pass through.
|
|
206
86
|
*/
|
|
207
87
|
async function aggregateDecodedCalls(provider, calls, options) {
|
|
208
|
-
const
|
|
88
|
+
const preparedCalls = calls.map((call) => call.abi === undefined ? call : prepareAbiCall(call));
|
|
89
|
+
const entries = await aggregateCalls(provider, preparedCalls, options);
|
|
209
90
|
return entries.map((entry, index) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
91
|
+
// aggregateCalls has checked the count. Hidden allowFailure fields still cannot
|
|
92
|
+
// bypass this guard and send failed returndata to a success-only decoder.
|
|
93
|
+
const call = preparedCalls[index];
|
|
94
|
+
if (!entry.success)
|
|
95
|
+
throw new GhostcallSubcallError(index, call, entry);
|
|
96
|
+
return call.decodeResult(entry.returnData, entry, index);
|
|
213
97
|
});
|
|
214
98
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Decodes the packed result blob returned by Ghostcall.
|
|
217
|
-
*
|
|
218
|
-
* Each decoded entry corresponds to exactly one subcall in the original batch and
|
|
219
|
-
* preserves the original ordering. The SDK intentionally returns raw result bytes
|
|
220
|
-
* rather than ABI-decoding them so higher-level callers can apply their own
|
|
221
|
-
* decoding and failure policy.
|
|
222
|
-
*
|
|
223
|
-
* @param data - Raw bytes returned by Ghostcall, typically the direct result of a
|
|
224
|
-
* CREATE-style `eth_call`.
|
|
225
|
-
*
|
|
226
|
-
* @returns Ordered list of decoded Ghostcall result entries. Returns an empty
|
|
227
|
-
* array for `0x`.
|
|
228
|
-
*
|
|
229
|
-
* @throws {TypeError} If the provided data is not valid hex, if a result header is
|
|
230
|
-
* truncated, or if an entry body is shorter than advertised.
|
|
231
|
-
*
|
|
232
|
-
* @example
|
|
233
|
-
* const results = decodeResults("0x8002cafe0004deadbeef");
|
|
234
|
-
*
|
|
235
|
-
* console.log(results);
|
|
236
|
-
* // [
|
|
237
|
-
* // { success: true, returnData: "0xcafe" },
|
|
238
|
-
* // { success: false, returnData: "0xdeadbeef" }
|
|
239
|
-
* // ]
|
|
240
|
-
*/
|
|
99
|
+
/** Decode ordered [success bit | uint15 length][returndata] entries. Reject malformed data. */
|
|
241
100
|
function decodeResults(data) {
|
|
242
|
-
const
|
|
243
|
-
if (normalizedData === "0x") {
|
|
244
|
-
return [];
|
|
245
|
-
}
|
|
101
|
+
const encodedData = assertHex(data, "data").slice(2);
|
|
246
102
|
const results = [];
|
|
247
|
-
const encodedData = normalizedData.slice(2);
|
|
248
103
|
let cursor = 0;
|
|
249
104
|
while (cursor < encodedData.length) {
|
|
250
105
|
if (cursor + encodedHeaderHexLength > encodedData.length) {
|
|
251
106
|
throw new TypeError("Truncated Ghostcall response header");
|
|
252
107
|
}
|
|
253
108
|
const header = Number.parseInt(encodedData.slice(cursor, cursor + encodedHeaderHexLength), 16);
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
const nextCursor = cursor + encodedHeaderHexLength;
|
|
257
|
-
const returnDataEnd = nextCursor + returnDataSize * 2;
|
|
109
|
+
cursor += encodedHeaderHexLength;
|
|
110
|
+
const returnDataEnd = cursor + (header & returnDataLengthMask) * 2;
|
|
258
111
|
if (returnDataEnd > encodedData.length) {
|
|
259
112
|
throw new TypeError("Truncated Ghostcall response body");
|
|
260
113
|
}
|
|
261
114
|
results.push({
|
|
262
|
-
success,
|
|
263
|
-
returnData: `0x${encodedData.slice(
|
|
115
|
+
success: (header & successFlagMask) !== 0,
|
|
116
|
+
returnData: `0x${encodedData.slice(cursor, returnDataEnd)}`,
|
|
264
117
|
});
|
|
265
118
|
cursor = returnDataEnd;
|
|
266
119
|
}
|
|
267
120
|
return results;
|
|
268
121
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Validates that a value is a canonical 20-byte hex address.
|
|
271
|
-
*
|
|
272
|
-
* @param value - Unknown input to validate.
|
|
273
|
-
* @param label - Field name used in thrown error messages.
|
|
274
|
-
*
|
|
275
|
-
* @throws {TypeError} If the value is not valid `0x`-prefixed hex or is not
|
|
276
|
-
* exactly 20 bytes long.
|
|
277
|
-
*
|
|
278
|
-
* @internal
|
|
279
|
-
*/
|
|
280
122
|
function assertAddress(value, label) {
|
|
281
|
-
|
|
282
|
-
if (normalizedValue.length !== addressHexLength + 2) {
|
|
123
|
+
if (typeof value !== "string" || !isAddress(value, { strict: false })) {
|
|
283
124
|
throw new TypeError(`${label} must be a 20-byte hex string`);
|
|
284
125
|
}
|
|
285
126
|
}
|
|
286
|
-
/**
|
|
287
|
-
* Validates that a value is an even-length `0x`-prefixed hex string.
|
|
288
|
-
*
|
|
289
|
-
* @param value - Unknown input to validate.
|
|
290
|
-
* @param label - Field name used in thrown error messages.
|
|
291
|
-
*
|
|
292
|
-
* @returns The validated value narrowed to {@link Hex}.
|
|
293
|
-
*
|
|
294
|
-
* @throws {TypeError} If the value is not a string, lacks the `0x` prefix, has an
|
|
295
|
-
* odd number of hex characters, or contains non-hex digits.
|
|
296
|
-
*
|
|
297
|
-
* @internal
|
|
298
|
-
*/
|
|
299
127
|
function assertHex(value, label) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (!value.startsWith("0x")) {
|
|
304
|
-
throw new TypeError(`${label} must start with 0x`);
|
|
305
|
-
}
|
|
306
|
-
const rawValue = value.slice(2);
|
|
307
|
-
if (rawValue.length % 2 !== 0) {
|
|
308
|
-
throw new TypeError(`${label} must have an even number of hex characters`);
|
|
309
|
-
}
|
|
310
|
-
if (!/^[0-9a-fA-F]*$/.test(rawValue)) {
|
|
311
|
-
throw new TypeError(`${label} must contain only hexadecimal characters`);
|
|
128
|
+
// ox checks prefix/digits; the wire format additionally requires whole bytes.
|
|
129
|
+
if (!isHex(value, { strict: true }) || value.length % 2 !== 0) {
|
|
130
|
+
throw new TypeError(`${label} must be an even-length 0x-prefixed hex string`);
|
|
312
131
|
}
|
|
313
132
|
return value;
|
|
314
133
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Validates that a value is an RPC hex quantity.
|
|
317
|
-
*
|
|
318
|
-
* @param value - Unknown input to validate.
|
|
319
|
-
* @param label - Field name used in thrown error messages.
|
|
320
|
-
* @returns The validated value narrowed to {@link HexQuantity}.
|
|
321
|
-
* @throws {TypeError} If the value is not a valid `0x`-prefixed quantity.
|
|
322
|
-
*
|
|
323
|
-
* @internal
|
|
324
|
-
*/
|
|
325
134
|
function assertHexQuantity(value, label) {
|
|
326
|
-
if (typeof value !== "string"
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
if (!/^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(value)) {
|
|
135
|
+
if (typeof value !== "string" ||
|
|
136
|
+
!/^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(value)) {
|
|
330
137
|
throw new TypeError(`${label} must be a 0x-prefixed hex quantity`);
|
|
331
138
|
}
|
|
332
139
|
return value;
|
|
333
140
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
if (typeof value === "bigint") {
|
|
352
|
-
if (value < 0n) {
|
|
353
|
-
throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
|
|
354
|
-
}
|
|
355
|
-
return `0x${value.toString(16)}`;
|
|
356
|
-
}
|
|
357
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
358
|
-
throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
|
|
359
|
-
}
|
|
360
|
-
if (/^-?[0-9]+$/.test(value)) {
|
|
361
|
-
if (value.startsWith("-")) {
|
|
362
|
-
throw new TypeError(`${label} must be a non-negative safe integer, bigint, or non-empty string`);
|
|
363
|
-
}
|
|
364
|
-
return `0x${BigInt(value).toString(16)}`;
|
|
365
|
-
}
|
|
366
|
-
if (value.startsWith("0x") || value.startsWith("0X")) {
|
|
367
|
-
return assertHexQuantity(`0x${value.slice(2)}`, label);
|
|
368
|
-
}
|
|
369
|
-
return value;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Resolves the active CREATE initcode ceiling.
|
|
373
|
-
*
|
|
374
|
-
* @param value - Optional caller override.
|
|
375
|
-
* @returns Active initcode ceiling in bytes.
|
|
376
|
-
* @throws {TypeError} If the override is not a non-negative safe integer.
|
|
377
|
-
*
|
|
378
|
-
* @internal
|
|
379
|
-
*/
|
|
380
|
-
function resolveMaxInitcodeBytes(value) {
|
|
381
|
-
if (value === undefined) {
|
|
382
|
-
return defaultMaxCreateInitcodeSize;
|
|
383
|
-
}
|
|
384
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
385
|
-
throw new TypeError("options.maxInitcodeBytes must be a non-negative safe integer");
|
|
386
|
-
}
|
|
387
|
-
return value;
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Returns the byte length of a validated hex string.
|
|
391
|
-
*
|
|
392
|
-
* @param value - Validated hex string.
|
|
393
|
-
* @returns Number of bytes represented by {@link value}.
|
|
394
|
-
*
|
|
395
|
-
* @internal
|
|
396
|
-
*/
|
|
397
|
-
function byteLength(value) {
|
|
398
|
-
return (value.length - 2) / 2;
|
|
141
|
+
function normalizeBlockTag(value) {
|
|
142
|
+
if (typeof value === "bigint" ||
|
|
143
|
+
(typeof value === "number" && Number.isSafeInteger(value))) {
|
|
144
|
+
if (value >= 0)
|
|
145
|
+
return `0x${value.toString(16)}`;
|
|
146
|
+
}
|
|
147
|
+
else if (typeof value === "string" &&
|
|
148
|
+
value.length > 0 &&
|
|
149
|
+
!/^-\d+$/.test(value)) {
|
|
150
|
+
if (/^\d+$/.test(value))
|
|
151
|
+
return `0x${BigInt(value).toString(16)}`;
|
|
152
|
+
return /^0x/i.test(value)
|
|
153
|
+
? assertHexQuantity(`0x${value.slice(2)}`, "options.ethCall.blockTag")
|
|
154
|
+
: value;
|
|
155
|
+
}
|
|
156
|
+
throw new TypeError("options.ethCall.blockTag must be a non-negative safe integer, bigint, or non-empty string");
|
|
399
157
|
}
|
|
400
158
|
export { aggregateCalls, aggregateDecodedCalls, decodeResults, encodeCalls, GhostcallSubcallError, };
|
|
401
159
|
//# sourceMappingURL=index.js.map
|
package/dist/sdk/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volga-sh/evm-ghostcall",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Batch EVM
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Batch EVM contract reads without deploying a Multicall contract.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -48,11 +48,12 @@
|
|
|
48
48
|
"test:watch": "node --disable-warning=ExperimentalWarning --experimental-strip-types --test --watch test/*.test.ts",
|
|
49
49
|
"typecheck": "tsc --project tsconfig.json"
|
|
50
50
|
},
|
|
51
|
-
"dependencies": {
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"ox": "0.9.6"
|
|
53
|
+
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@biomejs/biome": "2.4.12",
|
|
54
56
|
"@safe-global/mock-contract": "4.1.0",
|
|
55
|
-
"ox": "0.9.6",
|
|
56
57
|
"@types/node": "24.3.1",
|
|
57
58
|
"typescript": "5.9.2"
|
|
58
59
|
}
|
package/src/Ghostcall.yul
CHANGED
|
@@ -1,130 +1,49 @@
|
|
|
1
1
|
object "Ghostcall" {
|
|
2
2
|
code {
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// 2. That initcode usually builds runtime bytecode and RETURNs it.
|
|
8
|
-
// 3. Ghostcall uses the same mechanism, but inside eth_call.
|
|
9
|
-
// 4. Because this is only a simulation, nothing is deployed.
|
|
10
|
-
// 5. Whatever bytes this program RETURNs become the eth_call result.
|
|
11
|
-
//
|
|
12
|
-
// In other words: Ghostcall treats CREATE initcode like a tiny one-shot program that can
|
|
13
|
-
// batch external CALLs and return their raw results.
|
|
14
|
-
//
|
|
15
|
-
// The caller sends one byte blob:
|
|
16
|
-
// <compiled ghostcall initcode><payload>
|
|
17
|
-
//
|
|
18
|
-
// The payload is appended directly after the compiled initcode. It is not normal calldata.
|
|
19
|
-
// This program reads that appended payload back out of its own code using CODECOPY.
|
|
20
|
-
//
|
|
21
|
-
// Payload layout:
|
|
22
|
-
// repeated call entries
|
|
23
|
-
//
|
|
24
|
-
// Each call entry:
|
|
25
|
-
// 2 bytes calldata length (big-endian uint16)
|
|
26
|
-
// 20 bytes target address
|
|
27
|
-
// N bytes calldata
|
|
28
|
-
//
|
|
29
|
-
// Output layout:
|
|
30
|
-
// repeated result entries
|
|
31
|
-
//
|
|
32
|
-
// Each result entry:
|
|
33
|
-
// 2 bytes packed header
|
|
34
|
-
// bit 15 = success flag from CALL
|
|
35
|
-
// bits 0-14 = returndata length (big-endian uint15)
|
|
36
|
-
// N bytes returndata
|
|
37
|
-
//
|
|
38
|
-
// The program does the same high-level loop for every entry:
|
|
39
|
-
// - read the next calldata length + target
|
|
40
|
-
// - copy that call's calldata into memory
|
|
41
|
-
// - execute CALL(target, calldata)
|
|
42
|
-
// - append (success, returndata) to the response buffer
|
|
43
|
-
// - continue until the payload is fully consumed
|
|
44
|
-
//
|
|
45
|
-
// The SDK is expected to validate caller-facing input invariants ahead of time. The only
|
|
46
|
-
// top-level check left here protects response packing, because returndata size is learned
|
|
47
|
-
// from the EVM after each CALL.
|
|
3
|
+
// CREATE-style eth_call initcode: RETURN becomes the simulated runtime bytes.
|
|
4
|
+
// Input: appended [uint16 calldata length][20-byte target][calldata] entries.
|
|
5
|
+
// Output: [success bit | uint15 returndata length][returndata] entries.
|
|
6
|
+
// The SDK validates inputs; malformed hand-built payloads are unsupported.
|
|
48
7
|
|
|
49
|
-
//
|
|
50
|
-
// the bottom of this file. Because that data section is placed after the code, its offset is
|
|
51
|
-
// exactly "the first byte after the compiled initcode". That makes it the start of the
|
|
52
|
-
// caller-appended payload.
|
|
8
|
+
// The empty trailing data section marks the first caller-appended byte.
|
|
53
9
|
let payloadCursor := dataoffset("user_payload_anchor")
|
|
54
10
|
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// - writePtr..writePtr+0x1f: scratch space for reading the current entry header
|
|
58
|
-
//
|
|
59
|
-
// writePtr always points to where the next result entry starts. The entry's memory is
|
|
60
|
-
// scratch until CALL completes, then the packed result overwrites that same region.
|
|
11
|
+
// [0, writePtr) is finalized output. Everything after it is scratch until
|
|
12
|
+
// CALL finishes, then overwritten with the next packed result.
|
|
61
13
|
let writePtr := 0x00
|
|
62
14
|
|
|
63
|
-
// Process entries until the cursor reaches the end of the CREATE payload. SDK-generated
|
|
64
|
-
// payloads always land exactly on codesize(); raw malformed trailing bytes are outside the
|
|
65
|
-
// supported boundary and are not checked here.
|
|
66
15
|
for {} lt(payloadCursor, codesize()) {} {
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// The header layout is [len(2)][target(20)]. One mload gives us:
|
|
70
|
-
// [2-byte len][20-byte target][10 trailing bytes]
|
|
16
|
+
// One word holds [length(2)][target(20)][unused(10)].
|
|
71
17
|
codecopy(writePtr, payloadCursor, 0x16)
|
|
72
|
-
|
|
73
18
|
let headerWord := mload(writePtr)
|
|
74
|
-
|
|
75
|
-
// The high 2 bytes hold the big-endian uint16 calldata length. The target occupies the
|
|
76
|
-
// next 20 bytes, so shr(80, headerWord) yields the address for CALL.
|
|
77
19
|
let calldataSize := shr(240, headerWord)
|
|
78
20
|
|
|
79
|
-
//
|
|
80
|
-
// only writePtr..writePtr+0x01 for its packed header and writePtr+0x02 onward for
|
|
81
|
-
// returndata, so this staging area can be safely overwritten after CALL.
|
|
21
|
+
// Stage calldata after the input header; returndata later overwrites it.
|
|
82
22
|
let calldataPtr := add(writePtr, 0x16)
|
|
83
23
|
let returndataPtr := add(writePtr, 0x02)
|
|
84
|
-
|
|
85
|
-
// Copy just this call's calldata into memory so CALL can read it.
|
|
86
24
|
codecopy(calldataPtr, add(payloadCursor, 0x16), calldataSize)
|
|
87
25
|
|
|
88
|
-
//
|
|
89
|
-
// -
|
|
90
|
-
// - zero ETH value
|
|
91
|
-
// - calldata in memory at calldataPtr
|
|
92
|
-
// - no output buffer yet, because we do not know returndata size in advance
|
|
93
|
-
//
|
|
26
|
+
// CALL truncates the shifted word to the low 160 address bits.
|
|
27
|
+
// Zero-value CALL (not STATICCALL) exposes state changes to later calls.
|
|
94
28
|
let success := call(gas(), shr(80, headerWord), 0, calldataPtr, calldataSize, 0, 0)
|
|
95
29
|
let returndataSize := returndatasize()
|
|
96
30
|
|
|
97
|
-
//
|
|
98
|
-
// Revert rather than letting oversized returndata collide with the success bit.
|
|
31
|
+
// Reject lengths that would collide with the success bit.
|
|
99
32
|
if shr(15, returndataSize) {
|
|
100
33
|
revert(0x00, 0x00)
|
|
101
34
|
}
|
|
102
35
|
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// chain/client/RPC environment will reject oversized responses according to its own
|
|
106
|
-
// code-size policy. Keeping this uncapped lets the same Ghostcall initcode benefit from
|
|
107
|
-
// networks with larger limits, such as Monad's MIP-2:
|
|
108
|
-
// https://mips.monad.xyz/MIPS/MIP-2
|
|
109
|
-
|
|
110
|
-
// Write the packed 2-byte result header into the high 2 bytes of the 32-byte word at
|
|
111
|
-
// writePtr. The rest of that word does not matter because the return length is computed
|
|
112
|
-
// explicitly at the end.
|
|
36
|
+
// Put the header in the high two bytes; only the final written length
|
|
37
|
+
// is returned, so the remainder of this word may be overwritten freely.
|
|
113
38
|
mstore(writePtr, shl(240, or(shl(15, success), returndataSize)))
|
|
114
|
-
|
|
115
|
-
// Append the raw returndata bytes immediately after the 2-byte header.
|
|
116
39
|
returndatacopy(returndataPtr, 0, returndataSize)
|
|
117
40
|
|
|
118
|
-
// Advance both cursors:
|
|
119
|
-
// - writePtr moves to the start of the next result entry
|
|
120
|
-
// - payloadCursor moves to the next input entry
|
|
121
41
|
writePtr := add(returndataPtr, returndataSize)
|
|
122
42
|
payloadCursor := add(payloadCursor, add(0x16, calldataSize))
|
|
123
43
|
}
|
|
124
44
|
|
|
125
|
-
//
|
|
45
|
+
// Aggregate size is governed by the active chain/client's CREATE policy.
|
|
126
46
|
return(0x00, writePtr)
|
|
127
|
-
|
|
128
47
|
}
|
|
129
48
|
|
|
130
49
|
data "user_payload_anchor" hex""
|