@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,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAssets = resolveAssets;
|
|
4
|
+
const abis_1 = require("@morpho-org/morpho-sdk/abis");
|
|
5
|
+
const viem_1 = require("viem");
|
|
6
|
+
const actions_1 = require("viem/actions");
|
|
7
|
+
const errors_js_1 = require("../../errors.js");
|
|
8
|
+
const measurement_plan_js_1 = require("../measurement-plan.js");
|
|
9
|
+
/**
|
|
10
|
+
* Resolve underlying tokens only when an asset quote lacks an explicit token.
|
|
11
|
+
* Share-only quotes need no RPC reads; vault versions and factories are not discovered.
|
|
12
|
+
* @param params - Caller operations, Morpho address, client, request context, and pinned state block.
|
|
13
|
+
* @returns Operations with the token addresses required by their asset quotes.
|
|
14
|
+
* @throws {ExternalServiceError} For transport failures during a required metadata read.
|
|
15
|
+
* @throws {MissingVerificationEvidenceError} For reverted, malformed, or empty metadata.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
async function resolveAssets(params) {
|
|
19
|
+
const { client, morpho, operations, chainId, mode, blockNumber } = params;
|
|
20
|
+
const tokens = new Map();
|
|
21
|
+
const resolved = [];
|
|
22
|
+
for (const limit of operations) {
|
|
23
|
+
const plan = (0, measurement_plan_js_1.operationMeasurementPlan)(limit);
|
|
24
|
+
const result = { limit };
|
|
25
|
+
for (const field of ["assetsPaid", "assetsReceived"]) {
|
|
26
|
+
if (limit.quote[field] === undefined)
|
|
27
|
+
continue;
|
|
28
|
+
const explicit = field === "assetsPaid" ? limit.assetPaid : limit.assetReceived;
|
|
29
|
+
if (explicit !== undefined) {
|
|
30
|
+
result[field] = explicit;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const source = plan[field];
|
|
34
|
+
const key = source.type === "market"
|
|
35
|
+
? `market:${source.marketId.toLowerCase()}`
|
|
36
|
+
: `vault:${source.vault.toLowerCase()}`;
|
|
37
|
+
let pair = tokens.get(key);
|
|
38
|
+
if (pair === undefined) {
|
|
39
|
+
try {
|
|
40
|
+
if (source.type === "market") {
|
|
41
|
+
const values = await (0, actions_1.readContract)(client, {
|
|
42
|
+
address: morpho,
|
|
43
|
+
abi: abis_1.blueAbi,
|
|
44
|
+
functionName: "idToMarketParams",
|
|
45
|
+
args: [source.marketId],
|
|
46
|
+
blockNumber,
|
|
47
|
+
});
|
|
48
|
+
pair = [values[0], values[1]];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const asset = await (0, actions_1.readContract)(client, {
|
|
52
|
+
address: source.vault,
|
|
53
|
+
abi: viem_1.erc4626Abi,
|
|
54
|
+
functionName: "asset",
|
|
55
|
+
blockNumber,
|
|
56
|
+
});
|
|
57
|
+
pair = [asset, asset];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (cause) {
|
|
61
|
+
const reverted = cause instanceof viem_1.BaseError &&
|
|
62
|
+
cause.walk((error) => error instanceof viem_1.ContractFunctionRevertedError ||
|
|
63
|
+
error instanceof viem_1.ExecutionRevertedError) !== null;
|
|
64
|
+
// Revert-shaped chains (code 3, or -32000 "execution reverted")
|
|
65
|
+
// can nest an RpcRequestError, so a revert must win over the
|
|
66
|
+
// transport match.
|
|
67
|
+
if (!reverted &&
|
|
68
|
+
((cause instanceof viem_1.BaseError &&
|
|
69
|
+
cause.walk((error) => error instanceof viem_1.HttpRequestError ||
|
|
70
|
+
error instanceof viem_1.TimeoutError ||
|
|
71
|
+
error instanceof viem_1.RpcRequestError ||
|
|
72
|
+
error instanceof viem_1.RpcError ||
|
|
73
|
+
(error instanceof Error && error.name === "AbortError")) !== null) ||
|
|
74
|
+
(cause instanceof Error && cause.name === "AbortError"))) {
|
|
75
|
+
throw new errors_js_1.ExternalServiceError("Cannot resolve the quoted asset because its metadata request failed. Check the RPC endpoint.", { cause });
|
|
76
|
+
}
|
|
77
|
+
throw new errors_js_1.MissingVerificationEvidenceError(`Cannot resolve verification evidence for "${limit.type}" asset source "${key}".`, {
|
|
78
|
+
cause,
|
|
79
|
+
context: {
|
|
80
|
+
stage: "verification",
|
|
81
|
+
chainId,
|
|
82
|
+
mode,
|
|
83
|
+
blockNumber,
|
|
84
|
+
...plan.subject,
|
|
85
|
+
field,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
tokens.set(key, pair);
|
|
90
|
+
}
|
|
91
|
+
const token = source.type === "market"
|
|
92
|
+
? pair[source.asset === "loan" ? 0 : 1]
|
|
93
|
+
: pair[0];
|
|
94
|
+
if ((0, viem_1.isAddressEqual)(token, viem_1.zeroAddress)) {
|
|
95
|
+
throw new errors_js_1.MissingVerificationEvidenceError(`Cannot resolve verification evidence for "${limit.type}" asset source "${key}": the resolved token address is zero.`, {
|
|
96
|
+
context: {
|
|
97
|
+
stage: "verification",
|
|
98
|
+
chainId,
|
|
99
|
+
mode,
|
|
100
|
+
blockNumber,
|
|
101
|
+
...plan.subject,
|
|
102
|
+
field,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
result[field] = token;
|
|
107
|
+
}
|
|
108
|
+
resolved.push(result);
|
|
109
|
+
}
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
@@ -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,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvePinnedBlock = resolvePinnedBlock;
|
|
4
|
+
const actions_1 = require("viem/actions");
|
|
5
|
+
const errors_js_1 = require("../../errors.js");
|
|
6
|
+
const eth_simulate_v1_js_1 = require("./eth-simulate-v1.js");
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the requested state block exactly once so `latest` cannot drift.
|
|
9
|
+
*
|
|
10
|
+
* Every downstream read and the `eth_simulateV1` call pin this number; the
|
|
11
|
+
* execution boundary re-fetches the hash afterwards to detect a reorg that
|
|
12
|
+
* swapped the block mid-flight.
|
|
13
|
+
*
|
|
14
|
+
* @param params - Resolution parameters.
|
|
15
|
+
* @param params.client - Shared simulation client (created by
|
|
16
|
+
* {@link createSimulationClient}).
|
|
17
|
+
* @param params.blockNumber - Requested block number or tag; defaults to
|
|
18
|
+
* `latest`.
|
|
19
|
+
* @param params.signal - Optional pipeline abort signal; checked around the
|
|
20
|
+
* request so an aborted pipeline never produces a pin.
|
|
21
|
+
* @returns The resolved {@link PinnedBlock}.
|
|
22
|
+
* @throws {ExternalServiceError} For transport failures, timeouts, or an
|
|
23
|
+
* aborted signal.
|
|
24
|
+
* @throws {InvalidSimulationResponseError} When the node returns a block
|
|
25
|
+
* without a number or hash.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
async function resolvePinnedBlock(params) {
|
|
29
|
+
const { client, blockNumber, signal } = params;
|
|
30
|
+
const block = await (0, eth_simulate_v1_js_1.rpc)("eth_getBlock", async () => {
|
|
31
|
+
signal?.throwIfAborted();
|
|
32
|
+
const resolved = await (0, actions_1.getBlock)(client, typeof blockNumber === "bigint"
|
|
33
|
+
? { blockNumber }
|
|
34
|
+
: { blockTag: blockNumber ?? "latest" });
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
return resolved;
|
|
37
|
+
});
|
|
38
|
+
if (block.number === null || block.hash === null) {
|
|
39
|
+
throw new errors_js_1.InvalidSimulationResponseError("eth_getBlock returned a block without number or hash. Check that the endpoint resolved the requested state block.");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
number: block.number,
|
|
43
|
+
hash: block.hash,
|
|
44
|
+
timestamp: block.timestamp,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -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,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,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.operationMeasurementPlan = operationMeasurementPlan;
|
|
4
|
+
const market = (marketId, asset) => ({ type: "market", marketId, asset });
|
|
5
|
+
const position = (marketId, shares) => ({ type: "position", marketId, shares });
|
|
6
|
+
const balance = (token) => ({
|
|
7
|
+
type: "balance",
|
|
8
|
+
token,
|
|
9
|
+
});
|
|
10
|
+
const vault = (address) => ({
|
|
11
|
+
type: "vault",
|
|
12
|
+
vault: address,
|
|
13
|
+
});
|
|
14
|
+
/** Describe each quote field's evidence source with an exhaustive operation switch. @internal */
|
|
15
|
+
function operationMeasurementPlan(limit) {
|
|
16
|
+
switch (limit.type) {
|
|
17
|
+
case "blueSupply":
|
|
18
|
+
case "blueWithdraw": {
|
|
19
|
+
const shares = position(limit.marketId, "supplyShares");
|
|
20
|
+
return {
|
|
21
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
22
|
+
assetsPaid: market(limit.marketId, "loan"),
|
|
23
|
+
assetsReceived: market(limit.marketId, "loan"),
|
|
24
|
+
sharesMinted: shares,
|
|
25
|
+
sharesBurned: shares,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
case "blueSupplyCollateral":
|
|
29
|
+
case "blueWithdrawCollateral":
|
|
30
|
+
return {
|
|
31
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
32
|
+
assetsPaid: market(limit.marketId, "collateral"),
|
|
33
|
+
assetsReceived: market(limit.marketId, "collateral"),
|
|
34
|
+
};
|
|
35
|
+
case "blueBorrow":
|
|
36
|
+
case "blueRepay": {
|
|
37
|
+
const shares = position(limit.marketId, "borrowShares");
|
|
38
|
+
return {
|
|
39
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
40
|
+
assetsPaid: market(limit.marketId, "loan"),
|
|
41
|
+
assetsReceived: market(limit.marketId, "loan"),
|
|
42
|
+
sharesMinted: shares,
|
|
43
|
+
sharesBurned: shares,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
case "blueSupplyCollateralBorrow": {
|
|
47
|
+
const shares = position(limit.marketId, "borrowShares");
|
|
48
|
+
return {
|
|
49
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
50
|
+
assetsPaid: market(limit.marketId, "collateral"),
|
|
51
|
+
assetsReceived: market(limit.marketId, "loan"),
|
|
52
|
+
sharesMinted: shares,
|
|
53
|
+
sharesBurned: shares,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
case "blueRepayWithdrawCollateral": {
|
|
57
|
+
const shares = position(limit.marketId, "borrowShares");
|
|
58
|
+
return {
|
|
59
|
+
subject: { operation: limit.type, marketId: limit.marketId },
|
|
60
|
+
assetsPaid: market(limit.marketId, "loan"),
|
|
61
|
+
assetsReceived: market(limit.marketId, "collateral"),
|
|
62
|
+
sharesMinted: shares,
|
|
63
|
+
sharesBurned: shares,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
case "blueRefinance":
|
|
67
|
+
return {
|
|
68
|
+
subject: {
|
|
69
|
+
operation: limit.type,
|
|
70
|
+
sourceMarketId: limit.sourceMarketId,
|
|
71
|
+
targetMarketId: limit.targetMarketId,
|
|
72
|
+
},
|
|
73
|
+
assetsPaid: market(limit.sourceMarketId, "loan"),
|
|
74
|
+
assetsReceived: market(limit.sourceMarketId, "loan"),
|
|
75
|
+
sharesMinted: position(limit.targetMarketId, "borrowShares"),
|
|
76
|
+
sharesBurned: position(limit.sourceMarketId, "borrowShares"),
|
|
77
|
+
};
|
|
78
|
+
case "vaultV1Deposit":
|
|
79
|
+
case "vaultV2Deposit":
|
|
80
|
+
case "vaultV1Withdraw":
|
|
81
|
+
case "vaultV2Withdraw":
|
|
82
|
+
case "vaultV1Redeem":
|
|
83
|
+
case "vaultV2Redeem":
|
|
84
|
+
case "vaultV2ForceWithdraw":
|
|
85
|
+
case "vaultV2ForceRedeem":
|
|
86
|
+
case "vaultV1InKindRedeem":
|
|
87
|
+
case "vaultV2InKindRedeem":
|
|
88
|
+
return {
|
|
89
|
+
subject: {
|
|
90
|
+
operation: limit.type,
|
|
91
|
+
vault: limit.vault,
|
|
92
|
+
},
|
|
93
|
+
assetsPaid: vault(limit.vault),
|
|
94
|
+
assetsReceived: vault(limit.vault),
|
|
95
|
+
sharesMinted: balance(limit.vault),
|
|
96
|
+
sharesBurned: balance(limit.vault),
|
|
97
|
+
};
|
|
98
|
+
case "vaultV1MigrateToV2":
|
|
99
|
+
return {
|
|
100
|
+
subject: {
|
|
101
|
+
operation: limit.type,
|
|
102
|
+
sourceVault: limit.sourceVault,
|
|
103
|
+
targetVault: limit.targetVault,
|
|
104
|
+
},
|
|
105
|
+
assetsPaid: vault(limit.sourceVault),
|
|
106
|
+
assetsReceived: vault(limit.sourceVault),
|
|
107
|
+
sharesMinted: balance(limit.targetVault),
|
|
108
|
+
sharesBurned: balance(limit.sourceVault),
|
|
109
|
+
};
|
|
110
|
+
default: {
|
|
111
|
+
const exhaustive = limit;
|
|
112
|
+
return exhaustive;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -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
|
|
@@ -4,11 +4,11 @@ exports.DEPOSIT_TOPIC = exports.WITHDRAWAL_TOPIC = exports.TRANSFER_TOPIC = void
|
|
|
4
4
|
exports.parseTransfers = parseTransfers;
|
|
5
5
|
const viem_1 = require("viem");
|
|
6
6
|
const asset_changes_js_1 = require("../asset-changes.js");
|
|
7
|
-
|
|
7
|
+
/** `keccak256("Transfer(address,address,uint256)")` — ERC-20 Transfer event topic. @internal */
|
|
8
8
|
exports.TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
9
|
-
|
|
9
|
+
/** `keccak256("Withdrawal(address,uint256)")` — WETH9 Withdrawal event topic. @internal */
|
|
10
10
|
exports.WITHDRAWAL_TOPIC = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65";
|
|
11
|
-
|
|
11
|
+
/** `keccak256("Deposit(address,uint256)")` — WETH9 Deposit event topic. @internal */
|
|
12
12
|
exports.DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
|
|
13
13
|
const TOPIC_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
14
14
|
const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
@@ -32,9 +32,7 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
|
32
32
|
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
33
33
|
* synthesized as `Transfer` events emitted from the native sentinel
|
|
34
34
|
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
35
|
-
* collapsed to viem's `ethAddress` so native deltas key consistently
|
|
36
|
-
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
37
|
-
* separately), so this path is inert there.
|
|
35
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently.
|
|
38
36
|
*
|
|
39
37
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup for the
|
|
40
38
|
* 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
|
|
@@ -30,14 +30,11 @@ const DUST_THRESHOLD = 100n;
|
|
|
30
30
|
* violation.
|
|
31
31
|
*
|
|
32
32
|
* **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
|
|
33
|
-
* from parsed `transfers` (log-derived
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
|
|
39
|
-
* this, native ETH stuck in a bundles contract would pass the guard undetected
|
|
40
|
-
* on the Tenderly path (Cantina finding 1440).
|
|
33
|
+
* from parsed `transfers` (log-derived). Native ETH emits no event log of its
|
|
34
|
+
* own, so it is read from `assetChanges`, the source of truth for native value,
|
|
35
|
+
* which `eth_simulateV1` derives from the synthetic `traceTransfers` logs.
|
|
36
|
+
* Without this, native ETH stuck in a bundles contract would pass the guard
|
|
37
|
+
* undetected (Cantina finding 1440).
|
|
41
38
|
*
|
|
42
39
|
* To avoid double-counting native ETH on `eth_simulateV1` — where the same
|
|
43
40
|
* native move exists both as a synthetic `ethAddress` transfer log *and* in the
|
|
@@ -51,8 +48,8 @@ const DUST_THRESHOLD = 100n;
|
|
|
51
48
|
* neither are skipped (a `logger.warn` records the skip).
|
|
52
49
|
* @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
|
|
53
50
|
* `eth_simulateV1` synthetic native sentinel) scanned for net retention.
|
|
54
|
-
* @param params.assetChanges - Per-account native-ETH deltas, the
|
|
55
|
-
*
|
|
51
|
+
* @param params.assetChanges - Per-account native-ETH deltas, the source of
|
|
52
|
+
* truth for native value stuck in a restricted contract.
|
|
56
53
|
* @param params.logger - Optional logger. Receives retention-check skip warnings
|
|
57
54
|
* and pre-existing-balance sweep telemetry (net-negative flow).
|
|
58
55
|
* @returns Nothing. Returns silently when no restricted contract retains value
|
|
@@ -107,9 +104,9 @@ function assertNoBundlesRetention(params) {
|
|
|
107
104
|
flow.set(key, { address, token, netChange: delta });
|
|
108
105
|
}
|
|
109
106
|
};
|
|
110
|
-
// Native ETH from `assetChanges` (authoritative
|
|
111
|
-
//
|
|
112
|
-
//
|
|
107
|
+
// Native ETH from `assetChanges` (authoritative). Track which restricted
|
|
108
|
+
// contracts already have a native entry here so the transfer-log pass below
|
|
109
|
+
// does not re-add the same native move on `eth_simulateV1`.
|
|
113
110
|
const nativeFromAssetChanges = new Set();
|
|
114
111
|
for (const { account, changes } of assetChanges) {
|
|
115
112
|
if (!restrictedAddresses.has(account))
|
|
@@ -148,7 +145,7 @@ function assertNoBundlesRetention(params) {
|
|
|
148
145
|
throw new errors_js_1.BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
|
|
149
146
|
address: e.address,
|
|
150
147
|
token: e.token,
|
|
151
|
-
netRetained: e.netChange
|
|
148
|
+
netRetained: e.netChange,
|
|
152
149
|
})));
|
|
153
150
|
}
|
|
154
151
|
}
|
|
@@ -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;
|
|
@@ -5,18 +5,13 @@ const errors_js_1 = require("../../errors.js");
|
|
|
5
5
|
/**
|
|
6
6
|
* Stage 2 of the simulate() pipeline.
|
|
7
7
|
*
|
|
8
|
-
* Looks up the per-chain `ChainSimulationConfig
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* already enforces at least one at construction).
|
|
8
|
+
* Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
|
|
9
|
+
* when the chain is absent from the map or has no `simulateV1Url` (defensive —
|
|
10
|
+
* the type already requires it at construction).
|
|
12
11
|
*/
|
|
13
12
|
function resolveChain(config, chainId) {
|
|
14
13
|
const entry = config.chains.get(chainId);
|
|
15
|
-
if (!entry
|
|
14
|
+
if (!entry?.simulateV1Url)
|
|
16
15
|
throw new errors_js_1.UnsupportedChainError(chainId);
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
tenderlyRpc: entry.tenderlyRpc,
|
|
20
|
-
simulateV1Url: entry.simulateV1Url,
|
|
21
|
-
};
|
|
16
|
+
return entry;
|
|
22
17
|
}
|
|
@@ -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;
|