@morpho-org/evm-simulation 4.2.0 → 5.0.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/README.md +109 -17
- package/lib/cjs/authorizations.d.ts +95 -0
- package/lib/cjs/authorizations.js +2 -0
- package/lib/cjs/errors.d.ts +207 -13
- package/lib/cjs/errors.js +197 -13
- package/lib/cjs/index.d.ts +10 -2
- package/lib/cjs/index.js +19 -3
- package/lib/cjs/limits.d.ts +112 -0
- package/lib/cjs/limits.js +50 -0
- package/lib/cjs/params.d.ts +25 -0
- package/lib/cjs/params.js +5 -0
- package/lib/cjs/requirements/index.d.ts +1 -0
- package/lib/cjs/requirements/index.js +5 -0
- package/lib/cjs/requirements/requirements.d.ts +64 -0
- package/lib/cjs/requirements/requirements.js +149 -0
- package/lib/cjs/result.d.ts +49 -0
- package/lib/cjs/result.js +2 -0
- package/lib/cjs/simulate/asset-changes.d.ts +7 -5
- package/lib/cjs/simulate/asset-changes.js +6 -5
- package/lib/cjs/simulate/backends/client.d.ts +16 -0
- package/lib/cjs/simulate/backends/client.js +27 -0
- package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +66 -20
- package/lib/cjs/simulate/backends/eth-simulate-v1.js +172 -98
- package/lib/cjs/simulate/backends/index.d.ts +1 -2
- package/lib/cjs/simulate/backends/index.js +3 -4
- package/lib/cjs/simulate/backends/parse-response.d.ts +69 -0
- package/lib/cjs/simulate/backends/parse-response.js +168 -0
- package/lib/cjs/simulate/backends/resolve-assets.d.ts +26 -0
- package/lib/cjs/simulate/backends/resolve-assets.js +111 -0
- package/lib/cjs/simulate/backends/resolve-pinned-block.d.ts +33 -0
- package/lib/cjs/simulate/backends/resolve-pinned-block.js +46 -0
- package/lib/cjs/simulate/context.d.ts +14 -0
- package/lib/cjs/simulate/context.js +2 -0
- package/lib/cjs/simulate/measurement-plan.d.ts +29 -0
- package/lib/cjs/simulate/measurement-plan.js +115 -0
- package/lib/cjs/simulate/parsing/transfers.d.ts +4 -3
- package/lib/cjs/simulate/parsing/transfers.js +4 -6
- package/lib/cjs/simulate/pipeline/bundles-retention.d.ts +7 -10
- package/lib/cjs/simulate/pipeline/bundles-retention.js +11 -14
- package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +5 -11
- package/lib/cjs/simulate/pipeline/resolve-chain.js +5 -10
- package/lib/cjs/simulate/plan/plan-execution.d.ts +58 -0
- package/lib/cjs/simulate/plan/plan-execution.js +75 -0
- package/lib/cjs/simulate/prepare.d.ts +30 -0
- package/lib/cjs/simulate/prepare.js +80 -0
- package/lib/cjs/simulate/request/index.d.ts +1 -0
- package/lib/cjs/simulate/request/index.js +5 -0
- package/lib/cjs/simulate/request/parse-request.d.ts +44 -0
- package/lib/cjs/simulate/request/parse-request.js +759 -0
- package/lib/cjs/simulate/result.d.ts +19 -0
- package/lib/cjs/simulate/result.js +29 -0
- package/lib/cjs/simulate/run-simulation.d.ts +18 -0
- package/lib/cjs/simulate/run-simulation.js +151 -0
- package/lib/cjs/simulate/simulate.d.ts +100 -50
- package/lib/cjs/simulate/simulate.js +99 -81
- package/lib/cjs/simulate/slippage.d.ts +18 -0
- package/lib/cjs/simulate/slippage.js +41 -0
- package/lib/cjs/simulate/state/contract.d.ts +19 -0
- package/lib/cjs/simulate/state/contract.js +2 -0
- package/lib/cjs/simulate/state/read-state.d.ts +60 -0
- package/lib/cjs/simulate/state/read-state.js +148 -0
- package/lib/cjs/simulate/verify-slippage.d.ts +24 -0
- package/lib/cjs/simulate/verify-slippage.js +85 -0
- package/lib/cjs/types.d.ts +35 -95
- package/lib/esm/authorizations.d.ts +95 -0
- package/lib/esm/authorizations.js +1 -0
- package/lib/esm/errors.d.ts +207 -13
- package/lib/esm/errors.js +189 -12
- package/lib/esm/index.d.ts +10 -2
- package/lib/esm/index.js +4 -1
- package/lib/esm/limits.d.ts +112 -0
- package/lib/esm/limits.js +47 -0
- package/lib/esm/params.d.ts +25 -0
- package/lib/esm/params.js +2 -0
- package/lib/esm/requirements/index.d.ts +1 -0
- package/lib/esm/requirements/index.js +1 -0
- package/lib/esm/requirements/requirements.d.ts +64 -0
- package/lib/esm/requirements/requirements.js +146 -0
- package/lib/esm/result.d.ts +49 -0
- package/lib/esm/result.js +1 -0
- package/lib/esm/simulate/asset-changes.d.ts +7 -5
- package/lib/esm/simulate/asset-changes.js +6 -5
- package/lib/esm/simulate/backends/client.d.ts +16 -0
- package/lib/esm/simulate/backends/client.js +24 -0
- package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +66 -20
- package/lib/esm/simulate/backends/eth-simulate-v1.js +170 -99
- package/lib/esm/simulate/backends/index.d.ts +1 -2
- package/lib/esm/simulate/backends/index.js +1 -2
- package/lib/esm/simulate/backends/parse-response.d.ts +69 -0
- package/lib/esm/simulate/backends/parse-response.js +165 -0
- package/lib/esm/simulate/backends/resolve-assets.d.ts +26 -0
- package/lib/esm/simulate/backends/resolve-assets.js +108 -0
- package/lib/esm/simulate/backends/resolve-pinned-block.d.ts +33 -0
- package/lib/esm/simulate/backends/resolve-pinned-block.js +43 -0
- package/lib/esm/simulate/context.d.ts +14 -0
- package/lib/esm/simulate/context.js +1 -0
- package/lib/esm/simulate/measurement-plan.d.ts +29 -0
- package/lib/esm/simulate/measurement-plan.js +112 -0
- package/lib/esm/simulate/parsing/transfers.d.ts +4 -3
- package/lib/esm/simulate/parsing/transfers.js +4 -6
- package/lib/esm/simulate/pipeline/bundles-retention.d.ts +7 -10
- package/lib/esm/simulate/pipeline/bundles-retention.js +11 -14
- package/lib/esm/simulate/pipeline/resolve-chain.d.ts +5 -11
- package/lib/esm/simulate/pipeline/resolve-chain.js +5 -10
- package/lib/esm/simulate/plan/plan-execution.d.ts +58 -0
- package/lib/esm/simulate/plan/plan-execution.js +72 -0
- package/lib/esm/simulate/prepare.d.ts +30 -0
- package/lib/esm/simulate/prepare.js +77 -0
- package/lib/esm/simulate/request/index.d.ts +1 -0
- package/lib/esm/simulate/request/index.js +1 -0
- package/lib/esm/simulate/request/parse-request.d.ts +44 -0
- package/lib/esm/simulate/request/parse-request.js +756 -0
- package/lib/esm/simulate/result.d.ts +19 -0
- package/lib/esm/simulate/result.js +26 -0
- package/lib/esm/simulate/run-simulation.d.ts +18 -0
- package/lib/esm/simulate/run-simulation.js +148 -0
- package/lib/esm/simulate/simulate.d.ts +100 -50
- package/lib/esm/simulate/simulate.js +99 -81
- package/lib/esm/simulate/slippage.d.ts +18 -0
- package/lib/esm/simulate/slippage.js +38 -0
- package/lib/esm/simulate/state/contract.d.ts +19 -0
- package/lib/esm/simulate/state/contract.js +1 -0
- package/lib/esm/simulate/state/read-state.d.ts +60 -0
- package/lib/esm/simulate/state/read-state.js +144 -0
- package/lib/esm/simulate/verify-slippage.d.ts +24 -0
- package/lib/esm/simulate/verify-slippage.js +82 -0
- package/lib/esm/types.d.ts +35 -95
- package/package.json +7 -6
- package/lib/cjs/simulate/authorizations/index.d.ts +0 -2
- package/lib/cjs/simulate/authorizations/index.js +0 -7
- package/lib/cjs/simulate/authorizations/resolve.d.ts +0 -9
- package/lib/cjs/simulate/authorizations/resolve.js +0 -29
- package/lib/cjs/simulate/authorizations/validate.d.ts +0 -6
- package/lib/cjs/simulate/authorizations/validate.js +0 -23
- package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +0 -23
- package/lib/cjs/simulate/backends/tenderly-rpc.js +0 -234
- package/lib/cjs/simulate/pipeline/build-simulation-txs.d.ts +0 -17
- package/lib/cjs/simulate/pipeline/build-simulation-txs.js +0 -50
- package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +0 -22
- package/lib/cjs/simulate/pipeline/execute-simulation.js +0 -82
- package/lib/cjs/simulate/pipeline/index.d.ts +0 -4
- package/lib/cjs/simulate/pipeline/index.js +0 -11
- package/lib/cjs/simulate/pipeline/validate-input.d.ts +0 -10
- package/lib/cjs/simulate/pipeline/validate-input.js +0 -55
- package/lib/esm/simulate/authorizations/index.d.ts +0 -2
- package/lib/esm/simulate/authorizations/index.js +0 -2
- package/lib/esm/simulate/authorizations/resolve.d.ts +0 -9
- package/lib/esm/simulate/authorizations/resolve.js +0 -26
- package/lib/esm/simulate/authorizations/validate.d.ts +0 -6
- package/lib/esm/simulate/authorizations/validate.js +0 -20
- package/lib/esm/simulate/backends/tenderly-rpc.d.ts +0 -23
- package/lib/esm/simulate/backends/tenderly-rpc.js +0 -231
- package/lib/esm/simulate/pipeline/build-simulation-txs.d.ts +0 -17
- package/lib/esm/simulate/pipeline/build-simulation-txs.js +0 -47
- package/lib/esm/simulate/pipeline/execute-simulation.d.ts +0 -22
- package/lib/esm/simulate/pipeline/execute-simulation.js +0 -79
- package/lib/esm/simulate/pipeline/index.d.ts +0 -4
- package/lib/esm/simulate/pipeline/index.js +0 -4
- package/lib/esm/simulate/pipeline/validate-input.d.ts +0 -10
- package/lib/esm/simulate/pipeline/validate-input.js +0 -52
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { blueAbi } from "@morpho-org/morpho-sdk/abis";
|
|
2
|
+
import { BaseError, ContractFunctionRevertedError, ExecutionRevertedError, erc4626Abi, HttpRequestError, isAddressEqual, RpcError, RpcRequestError, TimeoutError, zeroAddress, } from "viem";
|
|
3
|
+
import { readContract } from "viem/actions";
|
|
4
|
+
import { ExternalServiceError, MissingVerificationEvidenceError, } from "../../errors.js";
|
|
5
|
+
import { operationMeasurementPlan } from "../measurement-plan.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve underlying tokens only when an asset quote lacks an explicit token.
|
|
8
|
+
* Share-only quotes need no RPC reads; vault versions and factories are not discovered.
|
|
9
|
+
* @param params - Caller operations, Morpho address, client, request context, and pinned state block.
|
|
10
|
+
* @returns Operations with the token addresses required by their asset quotes.
|
|
11
|
+
* @throws {ExternalServiceError} For transport failures during a required metadata read.
|
|
12
|
+
* @throws {MissingVerificationEvidenceError} For reverted, malformed, or empty metadata.
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveAssets(params) {
|
|
16
|
+
const { client, morpho, operations, chainId, mode, blockNumber } = params;
|
|
17
|
+
const tokens = new Map();
|
|
18
|
+
const resolved = [];
|
|
19
|
+
for (const limit of operations) {
|
|
20
|
+
const plan = operationMeasurementPlan(limit);
|
|
21
|
+
const result = { limit };
|
|
22
|
+
for (const field of ["assetsPaid", "assetsReceived"]) {
|
|
23
|
+
if (limit.quote[field] === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
const explicit = field === "assetsPaid" ? limit.assetPaid : limit.assetReceived;
|
|
26
|
+
if (explicit !== undefined) {
|
|
27
|
+
result[field] = explicit;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const source = plan[field];
|
|
31
|
+
const key = source.type === "market"
|
|
32
|
+
? `market:${source.marketId.toLowerCase()}`
|
|
33
|
+
: `vault:${source.vault.toLowerCase()}`;
|
|
34
|
+
let pair = tokens.get(key);
|
|
35
|
+
if (pair === undefined) {
|
|
36
|
+
try {
|
|
37
|
+
if (source.type === "market") {
|
|
38
|
+
const values = await readContract(client, {
|
|
39
|
+
address: morpho,
|
|
40
|
+
abi: blueAbi,
|
|
41
|
+
functionName: "idToMarketParams",
|
|
42
|
+
args: [source.marketId],
|
|
43
|
+
blockNumber,
|
|
44
|
+
});
|
|
45
|
+
pair = [values[0], values[1]];
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const asset = await readContract(client, {
|
|
49
|
+
address: source.vault,
|
|
50
|
+
abi: erc4626Abi,
|
|
51
|
+
functionName: "asset",
|
|
52
|
+
blockNumber,
|
|
53
|
+
});
|
|
54
|
+
pair = [asset, asset];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
const reverted = cause instanceof BaseError &&
|
|
59
|
+
cause.walk((error) => error instanceof ContractFunctionRevertedError ||
|
|
60
|
+
error instanceof ExecutionRevertedError) !== null;
|
|
61
|
+
// Revert-shaped chains (code 3, or -32000 "execution reverted")
|
|
62
|
+
// can nest an RpcRequestError, so a revert must win over the
|
|
63
|
+
// transport match.
|
|
64
|
+
if (!reverted &&
|
|
65
|
+
((cause instanceof BaseError &&
|
|
66
|
+
cause.walk((error) => error instanceof HttpRequestError ||
|
|
67
|
+
error instanceof TimeoutError ||
|
|
68
|
+
error instanceof RpcRequestError ||
|
|
69
|
+
error instanceof RpcError ||
|
|
70
|
+
(error instanceof Error && error.name === "AbortError")) !== null) ||
|
|
71
|
+
(cause instanceof Error && cause.name === "AbortError"))) {
|
|
72
|
+
throw new ExternalServiceError("Cannot resolve the quoted asset because its metadata request failed. Check the RPC endpoint.", { cause });
|
|
73
|
+
}
|
|
74
|
+
throw new MissingVerificationEvidenceError(`Cannot resolve verification evidence for "${limit.type}" asset source "${key}".`, {
|
|
75
|
+
cause,
|
|
76
|
+
context: {
|
|
77
|
+
stage: "verification",
|
|
78
|
+
chainId,
|
|
79
|
+
mode,
|
|
80
|
+
blockNumber,
|
|
81
|
+
...plan.subject,
|
|
82
|
+
field,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
tokens.set(key, pair);
|
|
87
|
+
}
|
|
88
|
+
const token = source.type === "market"
|
|
89
|
+
? pair[source.asset === "loan" ? 0 : 1]
|
|
90
|
+
: pair[0];
|
|
91
|
+
if (isAddressEqual(token, zeroAddress)) {
|
|
92
|
+
throw new MissingVerificationEvidenceError(`Cannot resolve verification evidence for "${limit.type}" asset source "${key}": the resolved token address is zero.`, {
|
|
93
|
+
context: {
|
|
94
|
+
stage: "verification",
|
|
95
|
+
chainId,
|
|
96
|
+
mode,
|
|
97
|
+
blockNumber,
|
|
98
|
+
...plan.subject,
|
|
99
|
+
field,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
result[field] = token;
|
|
104
|
+
}
|
|
105
|
+
resolved.push(result);
|
|
106
|
+
}
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BlockTag, Client, Hex } from "viem";
|
|
2
|
+
/** One concrete canonical block: the state anchor for every pinned read. @internal */
|
|
3
|
+
export interface PinnedBlock {
|
|
4
|
+
readonly number: bigint;
|
|
5
|
+
readonly hash: Hex;
|
|
6
|
+
readonly timestamp: bigint;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the requested state block exactly once so `latest` cannot drift.
|
|
10
|
+
*
|
|
11
|
+
* Every downstream read and the `eth_simulateV1` call pin this number; the
|
|
12
|
+
* execution boundary re-fetches the hash afterwards to detect a reorg that
|
|
13
|
+
* swapped the block mid-flight.
|
|
14
|
+
*
|
|
15
|
+
* @param params - Resolution parameters.
|
|
16
|
+
* @param params.client - Shared simulation client (created by
|
|
17
|
+
* {@link createSimulationClient}).
|
|
18
|
+
* @param params.blockNumber - Requested block number or tag; defaults to
|
|
19
|
+
* `latest`.
|
|
20
|
+
* @param params.signal - Optional pipeline abort signal; checked around the
|
|
21
|
+
* request so an aborted pipeline never produces a pin.
|
|
22
|
+
* @returns The resolved {@link PinnedBlock}.
|
|
23
|
+
* @throws {ExternalServiceError} For transport failures, timeouts, or an
|
|
24
|
+
* aborted signal.
|
|
25
|
+
* @throws {InvalidSimulationResponseError} When the node returns a block
|
|
26
|
+
* without a number or hash.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolvePinnedBlock(params: {
|
|
30
|
+
readonly client: Client;
|
|
31
|
+
readonly blockNumber?: bigint | BlockTag;
|
|
32
|
+
readonly signal?: AbortSignal;
|
|
33
|
+
}): Promise<PinnedBlock>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getBlock } from "viem/actions";
|
|
2
|
+
import { InvalidSimulationResponseError } from "../../errors.js";
|
|
3
|
+
import { rpc } from "./eth-simulate-v1.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the requested state block exactly once so `latest` cannot drift.
|
|
6
|
+
*
|
|
7
|
+
* Every downstream read and the `eth_simulateV1` call pin this number; the
|
|
8
|
+
* execution boundary re-fetches the hash afterwards to detect a reorg that
|
|
9
|
+
* swapped the block mid-flight.
|
|
10
|
+
*
|
|
11
|
+
* @param params - Resolution parameters.
|
|
12
|
+
* @param params.client - Shared simulation client (created by
|
|
13
|
+
* {@link createSimulationClient}).
|
|
14
|
+
* @param params.blockNumber - Requested block number or tag; defaults to
|
|
15
|
+
* `latest`.
|
|
16
|
+
* @param params.signal - Optional pipeline abort signal; checked around the
|
|
17
|
+
* request so an aborted pipeline never produces a pin.
|
|
18
|
+
* @returns The resolved {@link PinnedBlock}.
|
|
19
|
+
* @throws {ExternalServiceError} For transport failures, timeouts, or an
|
|
20
|
+
* aborted signal.
|
|
21
|
+
* @throws {InvalidSimulationResponseError} When the node returns a block
|
|
22
|
+
* without a number or hash.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export async function resolvePinnedBlock(params) {
|
|
26
|
+
const { client, blockNumber, signal } = params;
|
|
27
|
+
const block = await rpc("eth_getBlock", async () => {
|
|
28
|
+
signal?.throwIfAborted();
|
|
29
|
+
const resolved = await getBlock(client, typeof blockNumber === "bigint"
|
|
30
|
+
? { blockNumber }
|
|
31
|
+
: { blockTag: blockNumber ?? "latest" });
|
|
32
|
+
signal?.throwIfAborted();
|
|
33
|
+
return resolved;
|
|
34
|
+
});
|
|
35
|
+
if (block.number === null || block.hash === null) {
|
|
36
|
+
throw new InvalidSimulationResponseError("eth_getBlock returned a block without number or hash. Check that the endpoint resolved the requested state block.");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
number: block.number,
|
|
40
|
+
hash: block.hash,
|
|
41
|
+
timestamp: block.timestamp,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
import type { OperationLimit } from "../limits.js";
|
|
3
|
+
import type { SimulationMode } from "../params.js";
|
|
4
|
+
import type { ExecutionBlock } from "./backends/parse-response.js";
|
|
5
|
+
/** Explicit simulation context shared by the checker and result assembly. @internal */
|
|
6
|
+
export interface CheckContext {
|
|
7
|
+
readonly chainId: number;
|
|
8
|
+
readonly mode: SimulationMode;
|
|
9
|
+
readonly block: ExecutionBlock;
|
|
10
|
+
readonly owner: Address;
|
|
11
|
+
readonly limits: {
|
|
12
|
+
readonly operations: readonly OperationLimit[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MarketId } from "@morpho-org/blue-sdk";
|
|
2
|
+
import type { Address } from "viem";
|
|
3
|
+
import type { OperationLimit, SimulationOperationSubject } from "../limits.js";
|
|
4
|
+
type AssetMeasurementSource = {
|
|
5
|
+
readonly type: "market";
|
|
6
|
+
readonly marketId: MarketId;
|
|
7
|
+
readonly asset: "loan" | "collateral";
|
|
8
|
+
} | {
|
|
9
|
+
readonly type: "vault";
|
|
10
|
+
readonly vault: Address;
|
|
11
|
+
};
|
|
12
|
+
type ShareMeasurementSource = {
|
|
13
|
+
readonly type: "position";
|
|
14
|
+
readonly marketId: MarketId;
|
|
15
|
+
readonly shares: "supplyShares" | "borrowShares";
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: "balance";
|
|
18
|
+
readonly token: Address;
|
|
19
|
+
};
|
|
20
|
+
interface OperationMeasurementPlan {
|
|
21
|
+
readonly subject: SimulationOperationSubject;
|
|
22
|
+
readonly assetsPaid: AssetMeasurementSource;
|
|
23
|
+
readonly assetsReceived: AssetMeasurementSource;
|
|
24
|
+
readonly sharesMinted?: ShareMeasurementSource;
|
|
25
|
+
readonly sharesBurned?: ShareMeasurementSource;
|
|
26
|
+
}
|
|
27
|
+
/** Describe each quote field's evidence source with an exhaustive operation switch. @internal */
|
|
28
|
+
export declare function operationMeasurementPlan(limit: OperationLimit): OperationMeasurementPlan;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const market = (marketId, asset) => ({ type: "market", marketId, asset });
|
|
2
|
+
const position = (marketId, shares) => ({ type: "position", marketId, shares });
|
|
3
|
+
const balance = (token) => ({
|
|
4
|
+
type: "balance",
|
|
5
|
+
token,
|
|
6
|
+
});
|
|
7
|
+
const vault = (address) => ({
|
|
8
|
+
type: "vault",
|
|
9
|
+
vault: address,
|
|
10
|
+
});
|
|
11
|
+
/** Describe each quote field's evidence source with an exhaustive operation switch. @internal */
|
|
12
|
+
export function operationMeasurementPlan(limit) {
|
|
13
|
+
switch (limit.type) {
|
|
14
|
+
case "blueSupply":
|
|
15
|
+
case "blueWithdraw": {
|
|
16
|
+
const shares = position(limit.marketId, "supplyShares");
|
|
17
|
+
return {
|
|
18
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
19
|
+
assetsPaid: market(limit.marketId, "loan"),
|
|
20
|
+
assetsReceived: market(limit.marketId, "loan"),
|
|
21
|
+
sharesMinted: shares,
|
|
22
|
+
sharesBurned: shares,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
case "blueSupplyCollateral":
|
|
26
|
+
case "blueWithdrawCollateral":
|
|
27
|
+
return {
|
|
28
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
29
|
+
assetsPaid: market(limit.marketId, "collateral"),
|
|
30
|
+
assetsReceived: market(limit.marketId, "collateral"),
|
|
31
|
+
};
|
|
32
|
+
case "blueBorrow":
|
|
33
|
+
case "blueRepay": {
|
|
34
|
+
const shares = position(limit.marketId, "borrowShares");
|
|
35
|
+
return {
|
|
36
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
37
|
+
assetsPaid: market(limit.marketId, "loan"),
|
|
38
|
+
assetsReceived: market(limit.marketId, "loan"),
|
|
39
|
+
sharesMinted: shares,
|
|
40
|
+
sharesBurned: shares,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
case "blueSupplyCollateralBorrow": {
|
|
44
|
+
const shares = position(limit.marketId, "borrowShares");
|
|
45
|
+
return {
|
|
46
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
47
|
+
assetsPaid: market(limit.marketId, "collateral"),
|
|
48
|
+
assetsReceived: market(limit.marketId, "loan"),
|
|
49
|
+
sharesMinted: shares,
|
|
50
|
+
sharesBurned: shares,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
case "blueRepayWithdrawCollateral": {
|
|
54
|
+
const shares = position(limit.marketId, "borrowShares");
|
|
55
|
+
return {
|
|
56
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
57
|
+
assetsPaid: market(limit.marketId, "loan"),
|
|
58
|
+
assetsReceived: market(limit.marketId, "collateral"),
|
|
59
|
+
sharesMinted: shares,
|
|
60
|
+
sharesBurned: shares,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
case "blueRefinance":
|
|
64
|
+
return {
|
|
65
|
+
subject: {
|
|
66
|
+
operation: limit.type,
|
|
67
|
+
sourceMarketId: limit.sourceMarketId,
|
|
68
|
+
targetMarketId: limit.targetMarketId,
|
|
69
|
+
},
|
|
70
|
+
assetsPaid: market(limit.sourceMarketId, "loan"),
|
|
71
|
+
assetsReceived: market(limit.sourceMarketId, "loan"),
|
|
72
|
+
sharesMinted: position(limit.targetMarketId, "borrowShares"),
|
|
73
|
+
sharesBurned: position(limit.sourceMarketId, "borrowShares"),
|
|
74
|
+
};
|
|
75
|
+
case "vaultV1Deposit":
|
|
76
|
+
case "vaultV2Deposit":
|
|
77
|
+
case "vaultV1Withdraw":
|
|
78
|
+
case "vaultV2Withdraw":
|
|
79
|
+
case "vaultV1Redeem":
|
|
80
|
+
case "vaultV2Redeem":
|
|
81
|
+
case "vaultV2ForceWithdraw":
|
|
82
|
+
case "vaultV2ForceRedeem":
|
|
83
|
+
case "vaultV1InKindRedeem":
|
|
84
|
+
case "vaultV2InKindRedeem":
|
|
85
|
+
return {
|
|
86
|
+
subject: {
|
|
87
|
+
operation: limit.type,
|
|
88
|
+
vault: limit.vault,
|
|
89
|
+
},
|
|
90
|
+
assetsPaid: vault(limit.vault),
|
|
91
|
+
assetsReceived: vault(limit.vault),
|
|
92
|
+
sharesMinted: balance(limit.vault),
|
|
93
|
+
sharesBurned: balance(limit.vault),
|
|
94
|
+
};
|
|
95
|
+
case "vaultV1MigrateToV2":
|
|
96
|
+
return {
|
|
97
|
+
subject: {
|
|
98
|
+
operation: limit.type,
|
|
99
|
+
sourceVault: limit.sourceVault,
|
|
100
|
+
targetVault: limit.targetVault,
|
|
101
|
+
},
|
|
102
|
+
assetsPaid: vault(limit.sourceVault),
|
|
103
|
+
assetsReceived: vault(limit.sourceVault),
|
|
104
|
+
sharesMinted: balance(limit.targetVault),
|
|
105
|
+
sharesBurned: balance(limit.sourceVault),
|
|
106
|
+
};
|
|
107
|
+
default: {
|
|
108
|
+
const exhaustive = limit;
|
|
109
|
+
return exhaustive;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type Address } from "viem";
|
|
2
2
|
import type { RawCall, SimulationLogger, Transfer } from "../../types.js";
|
|
3
|
+
/** `keccak256("Transfer(address,address,uint256)")` — ERC-20 Transfer event topic. @internal */
|
|
3
4
|
export declare const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
5
|
+
/** `keccak256("Withdrawal(address,uint256)")` — WETH9 Withdrawal event topic. @internal */
|
|
4
6
|
export declare const WITHDRAWAL_TOPIC = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65";
|
|
7
|
+
/** `keccak256("Deposit(address,uint256)")` — WETH9 Deposit event topic. @internal */
|
|
5
8
|
export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
|
|
6
9
|
/**
|
|
7
10
|
* Parse raw EVM logs into individual Transfer events.
|
|
@@ -23,9 +26,7 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
|
|
|
23
26
|
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
24
27
|
* synthesized as `Transfer` events emitted from the native sentinel
|
|
25
28
|
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
26
|
-
* collapsed to viem's `ethAddress` so native deltas key consistently
|
|
27
|
-
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
28
|
-
* separately), so this path is inert there.
|
|
29
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently.
|
|
29
30
|
*
|
|
30
31
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup for the
|
|
31
32
|
* registered wrapped-native token is scoped to the same tx: a zero-address
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { getAddress, isAddressEqual, zeroAddress, zeroHash, } from "viem";
|
|
2
2
|
import { normalizeAssetToken } from "../asset-changes.js";
|
|
3
|
-
|
|
3
|
+
/** `keccak256("Transfer(address,address,uint256)")` — ERC-20 Transfer event topic. @internal */
|
|
4
4
|
export const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
5
|
-
|
|
5
|
+
/** `keccak256("Withdrawal(address,uint256)")` — WETH9 Withdrawal event topic. @internal */
|
|
6
6
|
export const WITHDRAWAL_TOPIC = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65";
|
|
7
|
-
|
|
7
|
+
/** `keccak256("Deposit(address,uint256)")` — WETH9 Deposit event topic. @internal */
|
|
8
8
|
export const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
|
|
9
9
|
const TOPIC_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
10
10
|
const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
@@ -28,9 +28,7 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
|
28
28
|
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
29
29
|
* synthesized as `Transfer` events emitted from the native sentinel
|
|
30
30
|
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
31
|
-
* collapsed to viem's `ethAddress` so native deltas key consistently
|
|
32
|
-
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
33
|
-
* separately), so this path is inert there.
|
|
31
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently.
|
|
34
32
|
*
|
|
35
33
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup for the
|
|
36
34
|
* registered wrapped-native token is scoped to the same tx: a zero-address
|
|
@@ -20,14 +20,11 @@ interface AssertNoBundlesRetentionParams {
|
|
|
20
20
|
* violation.
|
|
21
21
|
*
|
|
22
22
|
* **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
|
|
23
|
-
* from parsed `transfers` (log-derived
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
|
|
29
|
-
* this, native ETH stuck in a bundles contract would pass the guard undetected
|
|
30
|
-
* on the Tenderly path (Cantina finding 1440).
|
|
23
|
+
* from parsed `transfers` (log-derived). Native ETH emits no event log of its
|
|
24
|
+
* own, so it is read from `assetChanges`, the source of truth for native value,
|
|
25
|
+
* which `eth_simulateV1` derives from the synthetic `traceTransfers` logs.
|
|
26
|
+
* Without this, native ETH stuck in a bundles contract would pass the guard
|
|
27
|
+
* undetected (Cantina finding 1440).
|
|
31
28
|
*
|
|
32
29
|
* To avoid double-counting native ETH on `eth_simulateV1` — where the same
|
|
33
30
|
* native move exists both as a synthetic `ethAddress` transfer log *and* in the
|
|
@@ -41,8 +38,8 @@ interface AssertNoBundlesRetentionParams {
|
|
|
41
38
|
* neither are skipped (a `logger.warn` records the skip).
|
|
42
39
|
* @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
|
|
43
40
|
* `eth_simulateV1` synthetic native sentinel) scanned for net retention.
|
|
44
|
-
* @param params.assetChanges - Per-account native-ETH deltas, the
|
|
45
|
-
*
|
|
41
|
+
* @param params.assetChanges - Per-account native-ETH deltas, the source of
|
|
42
|
+
* truth for native value stuck in a restricted contract.
|
|
46
43
|
* @param params.logger - Optional logger. Receives retention-check skip warnings
|
|
47
44
|
* and pre-existing-balance sweep telemetry (net-negative flow).
|
|
48
45
|
* @returns Nothing. Returns silently when no restricted contract retains value
|
|
@@ -27,14 +27,11 @@ const DUST_THRESHOLD = 100n;
|
|
|
27
27
|
* violation.
|
|
28
28
|
*
|
|
29
29
|
* **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
|
|
30
|
-
* from parsed `transfers` (log-derived
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
|
|
36
|
-
* this, native ETH stuck in a bundles contract would pass the guard undetected
|
|
37
|
-
* on the Tenderly path (Cantina finding 1440).
|
|
30
|
+
* from parsed `transfers` (log-derived). Native ETH emits no event log of its
|
|
31
|
+
* own, so it is read from `assetChanges`, the source of truth for native value,
|
|
32
|
+
* which `eth_simulateV1` derives from the synthetic `traceTransfers` logs.
|
|
33
|
+
* Without this, native ETH stuck in a bundles contract would pass the guard
|
|
34
|
+
* undetected (Cantina finding 1440).
|
|
38
35
|
*
|
|
39
36
|
* To avoid double-counting native ETH on `eth_simulateV1` — where the same
|
|
40
37
|
* native move exists both as a synthetic `ethAddress` transfer log *and* in the
|
|
@@ -48,8 +45,8 @@ const DUST_THRESHOLD = 100n;
|
|
|
48
45
|
* neither are skipped (a `logger.warn` records the skip).
|
|
49
46
|
* @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
|
|
50
47
|
* `eth_simulateV1` synthetic native sentinel) scanned for net retention.
|
|
51
|
-
* @param params.assetChanges - Per-account native-ETH deltas, the
|
|
52
|
-
*
|
|
48
|
+
* @param params.assetChanges - Per-account native-ETH deltas, the source of
|
|
49
|
+
* truth for native value stuck in a restricted contract.
|
|
53
50
|
* @param params.logger - Optional logger. Receives retention-check skip warnings
|
|
54
51
|
* and pre-existing-balance sweep telemetry (net-negative flow).
|
|
55
52
|
* @returns Nothing. Returns silently when no restricted contract retains value
|
|
@@ -104,9 +101,9 @@ export function assertNoBundlesRetention(params) {
|
|
|
104
101
|
flow.set(key, { address, token, netChange: delta });
|
|
105
102
|
}
|
|
106
103
|
};
|
|
107
|
-
// Native ETH from `assetChanges` (authoritative
|
|
108
|
-
//
|
|
109
|
-
//
|
|
104
|
+
// Native ETH from `assetChanges` (authoritative). Track which restricted
|
|
105
|
+
// contracts already have a native entry here so the transfer-log pass below
|
|
106
|
+
// does not re-add the same native move on `eth_simulateV1`.
|
|
110
107
|
const nativeFromAssetChanges = new Set();
|
|
111
108
|
for (const { account, changes } of assetChanges) {
|
|
112
109
|
if (!restrictedAddresses.has(account))
|
|
@@ -145,7 +142,7 @@ export function assertNoBundlesRetention(params) {
|
|
|
145
142
|
throw new BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
|
|
146
143
|
address: e.address,
|
|
147
144
|
token: e.token,
|
|
148
|
-
netRetained: e.netChange
|
|
145
|
+
netRetained: e.netChange,
|
|
149
146
|
})));
|
|
150
147
|
}
|
|
151
148
|
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
interface ChainCapability {
|
|
3
|
-
tenderlyRpc?: TenderlyRpcConfig;
|
|
4
|
-
simulateV1Url?: string;
|
|
5
|
-
}
|
|
1
|
+
import type { ChainSimulationConfig, SimulationConfig } from "../../types.js";
|
|
6
2
|
/**
|
|
7
3
|
* Stage 2 of the simulate() pipeline.
|
|
8
4
|
*
|
|
9
|
-
* Looks up the per-chain `ChainSimulationConfig
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* already enforces at least one at construction).
|
|
5
|
+
* Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
|
|
6
|
+
* when the chain is absent from the map or has no `simulateV1Url` (defensive —
|
|
7
|
+
* the type already requires it at construction).
|
|
13
8
|
*/
|
|
14
|
-
export declare function resolveChain(config: SimulationConfig, chainId: number):
|
|
15
|
-
export {};
|
|
9
|
+
export declare function resolveChain(config: SimulationConfig, chainId: number): ChainSimulationConfig;
|
|
@@ -2,18 +2,13 @@ import { UnsupportedChainError } from "../../errors.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Stage 2 of the simulate() pipeline.
|
|
4
4
|
*
|
|
5
|
-
* Looks up the per-chain `ChainSimulationConfig
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* already enforces at least one at construction).
|
|
5
|
+
* Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
|
|
6
|
+
* when the chain is absent from the map or has no `simulateV1Url` (defensive —
|
|
7
|
+
* the type already requires it at construction).
|
|
9
8
|
*/
|
|
10
9
|
export function resolveChain(config, chainId) {
|
|
11
10
|
const entry = config.chains.get(chainId);
|
|
12
|
-
if (!entry
|
|
11
|
+
if (!entry?.simulateV1Url)
|
|
13
12
|
throw new UnsupportedChainError(chainId);
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
tenderlyRpc: entry.tenderlyRpc,
|
|
17
|
-
simulateV1Url: entry.simulateV1Url,
|
|
18
|
-
};
|
|
13
|
+
return entry;
|
|
19
14
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
import type { SimulationTransaction } from "../../types.js";
|
|
3
|
+
import type { ParsedRequest } from "../request/parse-request.js";
|
|
4
|
+
import type { ReadPhase, StateRead } from "../state/contract.js";
|
|
5
|
+
/** One call planned inside the single `eth_simulateV1` run. @internal */
|
|
6
|
+
export type PlannedCall = {
|
|
7
|
+
/** What the call carries on the wire. */
|
|
8
|
+
readonly transaction: SimulationTransaction & {
|
|
9
|
+
readonly value: bigint;
|
|
10
|
+
};
|
|
11
|
+
} & ({
|
|
12
|
+
/** A caller transaction. */
|
|
13
|
+
readonly type: "transaction";
|
|
14
|
+
readonly transactionIndex: number;
|
|
15
|
+
} | {
|
|
16
|
+
/** A preview-authorization preparation call. */
|
|
17
|
+
readonly type: "preparation";
|
|
18
|
+
readonly authorizationIndex: number;
|
|
19
|
+
readonly callIndex: number;
|
|
20
|
+
} | {
|
|
21
|
+
/** A state read-back call at the `before`/`after` phase. */
|
|
22
|
+
readonly type: "stateRead";
|
|
23
|
+
readonly phase: ReadPhase;
|
|
24
|
+
readonly read: StateRead;
|
|
25
|
+
});
|
|
26
|
+
/** The ordered call plan for one simulation. @internal */
|
|
27
|
+
export interface ExecutionPlan {
|
|
28
|
+
readonly request: ParsedRequest;
|
|
29
|
+
readonly calls: readonly PlannedCall[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Plan the execution of a request as ordered `eth_simulateV1` calls.
|
|
33
|
+
*
|
|
34
|
+
* Sequence (design §plan order): every `before` state read → preparation
|
|
35
|
+
* calls → every user transaction → every `after` state read.
|
|
36
|
+
*
|
|
37
|
+
* Preparation calls run `from` the owner; state reads run `from`
|
|
38
|
+
* `zeroAddress` and never receive a public `txIdx`.
|
|
39
|
+
*
|
|
40
|
+
* @param request - The parsed request.
|
|
41
|
+
* @param owner - The bundle owner; sender of preparation calls.
|
|
42
|
+
* @param preparations - Ordered authorization preparations to simulate.
|
|
43
|
+
* @param reads - The full state-read list, replayed at `before` and `after`.
|
|
44
|
+
* @returns A deep-frozen {@link ExecutionPlan}; pure — equal inputs produce
|
|
45
|
+
* structurally equal plans.
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare function planExecution(params: {
|
|
49
|
+
readonly request: ParsedRequest;
|
|
50
|
+
readonly owner: Address;
|
|
51
|
+
readonly preparations: readonly {
|
|
52
|
+
readonly authorizationIndex: number;
|
|
53
|
+
readonly calls: readonly (SimulationTransaction & {
|
|
54
|
+
readonly value: bigint;
|
|
55
|
+
})[];
|
|
56
|
+
}[];
|
|
57
|
+
readonly reads: readonly StateRead[];
|
|
58
|
+
}): ExecutionPlan;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { deepFreeze } from "@morpho-org/morpho-ts";
|
|
2
|
+
import { zeroAddress } from "viem";
|
|
3
|
+
/**
|
|
4
|
+
* Plan the execution of a request as ordered `eth_simulateV1` calls.
|
|
5
|
+
*
|
|
6
|
+
* Sequence (design §plan order): every `before` state read → preparation
|
|
7
|
+
* calls → every user transaction → every `after` state read.
|
|
8
|
+
*
|
|
9
|
+
* Preparation calls run `from` the owner; state reads run `from`
|
|
10
|
+
* `zeroAddress` and never receive a public `txIdx`.
|
|
11
|
+
*
|
|
12
|
+
* @param request - The parsed request.
|
|
13
|
+
* @param owner - The bundle owner; sender of preparation calls.
|
|
14
|
+
* @param preparations - Ordered authorization preparations to simulate.
|
|
15
|
+
* @param reads - The full state-read list, replayed at `before` and `after`.
|
|
16
|
+
* @returns A deep-frozen {@link ExecutionPlan}; pure — equal inputs produce
|
|
17
|
+
* structurally equal plans.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function planExecution(params) {
|
|
21
|
+
const { request, owner, preparations, reads } = params;
|
|
22
|
+
const calls = [];
|
|
23
|
+
for (const read of reads) {
|
|
24
|
+
calls.push({
|
|
25
|
+
type: "stateRead",
|
|
26
|
+
phase: "before",
|
|
27
|
+
read,
|
|
28
|
+
transaction: {
|
|
29
|
+
from: zeroAddress,
|
|
30
|
+
to: read.to,
|
|
31
|
+
data: read.data,
|
|
32
|
+
value: 0n,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const preparation of preparations) {
|
|
37
|
+
preparation.calls.forEach((transaction, callIndex) => {
|
|
38
|
+
calls.push({
|
|
39
|
+
type: "preparation",
|
|
40
|
+
authorizationIndex: preparation.authorizationIndex,
|
|
41
|
+
callIndex,
|
|
42
|
+
transaction: { ...transaction, from: owner },
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
request.transactions.forEach((transaction, i) => {
|
|
47
|
+
calls.push({
|
|
48
|
+
type: "transaction",
|
|
49
|
+
transactionIndex: i,
|
|
50
|
+
transaction: {
|
|
51
|
+
from: transaction.from,
|
|
52
|
+
to: transaction.to,
|
|
53
|
+
data: transaction.data,
|
|
54
|
+
value: transaction.value ?? 0n,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
for (const read of reads) {
|
|
59
|
+
calls.push({
|
|
60
|
+
type: "stateRead",
|
|
61
|
+
phase: "after",
|
|
62
|
+
read,
|
|
63
|
+
transaction: {
|
|
64
|
+
from: zeroAddress,
|
|
65
|
+
to: read.to,
|
|
66
|
+
data: read.data,
|
|
67
|
+
value: 0n,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return deepFreeze({ request, calls });
|
|
72
|
+
}
|