@morpho-org/evm-simulation 4.2.1 → 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 +199 -12
- package/lib/cjs/errors.js +190 -12
- 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 -51
- package/lib/cjs/simulate/simulate.js +99 -82
- 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 +199 -12
- package/lib/esm/errors.js +182 -11
- 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 -51
- package/lib/esm/simulate/simulate.js +99 -82
- 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,19 @@
|
|
|
1
|
+
import type { AuthorizationPreparation, SimulatedOperation, VerifiedSimulationResult } from "../result.js";
|
|
2
|
+
import type { AccountAssetChanges, SimulationCall, Transfer } from "../types.js";
|
|
3
|
+
import type { CheckContext } from "./context.js";
|
|
4
|
+
import type { ParsedRequest } from "./request/parse-request.js";
|
|
5
|
+
/**
|
|
6
|
+
* Assemble the public result from the checked pipeline. Only user
|
|
7
|
+
* transactions appear in `simulationTxs`/`calls`/`transfers` — preparation
|
|
8
|
+
* calls and state reads never get a public `txIdx`.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function assembleResult(params: {
|
|
12
|
+
readonly ctx: CheckContext;
|
|
13
|
+
readonly request: ParsedRequest;
|
|
14
|
+
readonly operations: readonly SimulatedOperation[];
|
|
15
|
+
readonly authorizations: readonly AuthorizationPreparation[];
|
|
16
|
+
readonly userCalls: readonly SimulationCall[];
|
|
17
|
+
readonly transfers: readonly Transfer[];
|
|
18
|
+
readonly assetChanges: readonly AccountAssetChanges[];
|
|
19
|
+
}): VerifiedSimulationResult;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleResult = assembleResult;
|
|
4
|
+
const morpho_ts_1 = require("@morpho-org/morpho-ts");
|
|
5
|
+
/**
|
|
6
|
+
* Assemble the public result from the checked pipeline. Only user
|
|
7
|
+
* transactions appear in `simulationTxs`/`calls`/`transfers` — preparation
|
|
8
|
+
* calls and state reads never get a public `txIdx`.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
function assembleResult(params) {
|
|
12
|
+
const { ctx, request, operations, authorizations, userCalls, transfers, assetChanges, } = params;
|
|
13
|
+
const verification = {
|
|
14
|
+
mode: ctx.mode,
|
|
15
|
+
chainId: ctx.chainId,
|
|
16
|
+
blockNumber: ctx.block.blockNumber,
|
|
17
|
+
blockTimestamp: ctx.block.blockTimestamp,
|
|
18
|
+
limits: ctx.limits,
|
|
19
|
+
operations,
|
|
20
|
+
authorizations,
|
|
21
|
+
};
|
|
22
|
+
return (0, morpho_ts_1.deepFreeze)({
|
|
23
|
+
simulationTxs: request.transactions,
|
|
24
|
+
calls: userCalls,
|
|
25
|
+
transfers,
|
|
26
|
+
assetChanges,
|
|
27
|
+
verification,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { VerifiedSimulationResult } from "../result.js";
|
|
2
|
+
import type { SimulationConfig } from "../types.js";
|
|
3
|
+
import type { ParsedRequest } from "./request/parse-request.js";
|
|
4
|
+
/**
|
|
5
|
+
* Run the verified simulation pipeline in a single `eth_simulateV1` call:
|
|
6
|
+
* resolve quoted assets → plan quoted observations → execution →
|
|
7
|
+
* slippage comparisons → bundle-retention assertion → result assembly.
|
|
8
|
+
*
|
|
9
|
+
* One `AbortSignal` (the request timeout) covers every RPC step.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
* @param params - The simulation config and the parsed caller request.
|
|
13
|
+
* @returns The deep-frozen {@link VerifiedSimulationResult}.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runSimulation(params: {
|
|
16
|
+
readonly config: SimulationConfig;
|
|
17
|
+
readonly request: ParsedRequest;
|
|
18
|
+
}): Promise<VerifiedSimulationResult>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSimulation = runSimulation;
|
|
4
|
+
const blue_sdk_1 = require("@morpho-org/blue-sdk");
|
|
5
|
+
const morpho_ts_1 = require("@morpho-org/morpho-ts");
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const errors_js_1 = require("../errors.js");
|
|
8
|
+
const asset_changes_js_1 = require("./asset-changes.js");
|
|
9
|
+
const client_js_1 = require("./backends/client.js");
|
|
10
|
+
const index_js_1 = require("./backends/index.js");
|
|
11
|
+
const resolve_assets_js_1 = require("./backends/resolve-assets.js");
|
|
12
|
+
const resolve_pinned_block_js_1 = require("./backends/resolve-pinned-block.js");
|
|
13
|
+
const index_js_2 = require("./parsing/index.js");
|
|
14
|
+
const bundles_retention_js_1 = require("./pipeline/bundles-retention.js");
|
|
15
|
+
const resolve_chain_js_1 = require("./pipeline/resolve-chain.js");
|
|
16
|
+
const plan_execution_js_1 = require("./plan/plan-execution.js");
|
|
17
|
+
const prepare_js_1 = require("./prepare.js");
|
|
18
|
+
const result_js_1 = require("./result.js");
|
|
19
|
+
const read_state_js_1 = require("./state/read-state.js");
|
|
20
|
+
const verify_slippage_js_1 = require("./verify-slippage.js");
|
|
21
|
+
/** Total execution budget for a single `simulate()` call. */
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* Run the verified simulation pipeline in a single `eth_simulateV1` call:
|
|
25
|
+
* resolve quoted assets → plan quoted observations → execution →
|
|
26
|
+
* slippage comparisons → bundle-retention assertion → result assembly.
|
|
27
|
+
*
|
|
28
|
+
* One `AbortSignal` (the request timeout) covers every RPC step.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
* @param params - The simulation config and the parsed caller request.
|
|
32
|
+
* @returns The deep-frozen {@link VerifiedSimulationResult}.
|
|
33
|
+
*/
|
|
34
|
+
async function runSimulation(params) {
|
|
35
|
+
const { config, request } = params;
|
|
36
|
+
const chain = (0, resolve_chain_js_1.resolveChain)(config, request.chainId);
|
|
37
|
+
const signal = AbortSignal.timeout(config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
38
|
+
const client = (0, client_js_1.createSimulationClient)(chain.simulateV1Url, signal);
|
|
39
|
+
const limits = { operations: request.limits?.operations ?? [] };
|
|
40
|
+
const preview = request.mode === "preview";
|
|
41
|
+
const owner = request.transactions[0].from;
|
|
42
|
+
const addresses = (0, morpho_ts_1._try)(() => (0, blue_sdk_1.getChainAddresses)(request.chainId), blue_sdk_1.UnsupportedChainIdError);
|
|
43
|
+
const requiresMorpho = limits.operations.length > 0 ||
|
|
44
|
+
(preview && request.authorizations.length > 0);
|
|
45
|
+
if (requiresMorpho && addresses?.blue == null)
|
|
46
|
+
throw new errors_js_1.UnsupportedChainError(request.chainId);
|
|
47
|
+
const morpho = addresses?.blue ?? viem_1.zeroAddress;
|
|
48
|
+
await (0, index_js_1.assertEndpointChain)({
|
|
49
|
+
client,
|
|
50
|
+
chainId: request.chainId,
|
|
51
|
+
mode: request.mode,
|
|
52
|
+
blockNumber: typeof request.blockNumber === "bigint" ? request.blockNumber : undefined,
|
|
53
|
+
});
|
|
54
|
+
const pinnedBlock = await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
|
|
55
|
+
client,
|
|
56
|
+
blockNumber: request.blockNumber,
|
|
57
|
+
signal,
|
|
58
|
+
});
|
|
59
|
+
// With no calldata decoding, the caller's declared `limits.operations`
|
|
60
|
+
// entries are the operations: subjects for state reads and per-operation
|
|
61
|
+
// checks come straight from the limit list.
|
|
62
|
+
const resolved = await (0, resolve_assets_js_1.resolveAssets)({
|
|
63
|
+
client,
|
|
64
|
+
morpho,
|
|
65
|
+
operations: limits.operations,
|
|
66
|
+
chainId: request.chainId,
|
|
67
|
+
mode: request.mode,
|
|
68
|
+
blockNumber: pinnedBlock.number,
|
|
69
|
+
});
|
|
70
|
+
const observations = (0, read_state_js_1.planStateReads)({ operations: resolved, owner, morpho });
|
|
71
|
+
const preparations = preview
|
|
72
|
+
? (0, prepare_js_1.prepareAuthorizations)({
|
|
73
|
+
authorizations: request.authorizations,
|
|
74
|
+
owner,
|
|
75
|
+
morpho,
|
|
76
|
+
})
|
|
77
|
+
: [];
|
|
78
|
+
const plan = (0, plan_execution_js_1.planExecution)({
|
|
79
|
+
request,
|
|
80
|
+
owner,
|
|
81
|
+
preparations,
|
|
82
|
+
reads: observations.reads,
|
|
83
|
+
});
|
|
84
|
+
// Execute against the block resolved once above; the boundary rechecks its hash.
|
|
85
|
+
const execution = await (0, index_js_1.executePlan)({
|
|
86
|
+
client,
|
|
87
|
+
plan,
|
|
88
|
+
stateBlock: pinnedBlock,
|
|
89
|
+
});
|
|
90
|
+
const userCalls = execution.calls
|
|
91
|
+
.filter((call) => call.planned.type === "transaction")
|
|
92
|
+
.map((call) => call.result);
|
|
93
|
+
if (userCalls.length !== request.transactions.length)
|
|
94
|
+
throw new errors_js_1.InvalidSimulationResponseError(`Execution returned ${userCalls.length} user call result(s) for ${request.transactions.length} transaction(s)`);
|
|
95
|
+
const decodePhase = (phase) => new Map(execution.stateReads
|
|
96
|
+
.filter((read) => read.phase === phase)
|
|
97
|
+
.map((read) => [
|
|
98
|
+
read.read.id,
|
|
99
|
+
(0, read_state_js_1.decodeStateRead)(read.read, read.returnData, {
|
|
100
|
+
chainId: request.chainId,
|
|
101
|
+
mode: request.mode,
|
|
102
|
+
blockNumber: pinnedBlock.number,
|
|
103
|
+
}),
|
|
104
|
+
]));
|
|
105
|
+
const transfers = (0, index_js_2.parseTransfers)(userCalls, {
|
|
106
|
+
wNative: addresses === undefined ? undefined : (addresses.wNative ?? null),
|
|
107
|
+
logger: config.logger,
|
|
108
|
+
});
|
|
109
|
+
const assetChanges = (0, asset_changes_js_1.groupAssetChanges)(transfers.flatMap(({ token, from, to, amount }) => [
|
|
110
|
+
{ account: to, token, diff: amount },
|
|
111
|
+
{ account: from, token, diff: -amount },
|
|
112
|
+
]));
|
|
113
|
+
const ctx = {
|
|
114
|
+
chainId: request.chainId,
|
|
115
|
+
mode: request.mode,
|
|
116
|
+
block: execution.block,
|
|
117
|
+
owner,
|
|
118
|
+
limits,
|
|
119
|
+
};
|
|
120
|
+
const authorizations = request.authorizations.map((authorization, authorizationIndex) => ({
|
|
121
|
+
authorizationIndex,
|
|
122
|
+
authorization,
|
|
123
|
+
calls: execution.calls.flatMap(({ planned, result }) => planned.type === "preparation" &&
|
|
124
|
+
planned.authorizationIndex === authorizationIndex
|
|
125
|
+
? [{ transaction: planned.transaction, result }]
|
|
126
|
+
: []),
|
|
127
|
+
}));
|
|
128
|
+
const checked = (0, verify_slippage_js_1.verifySlippage)({
|
|
129
|
+
ctx,
|
|
130
|
+
operations: observations.operations,
|
|
131
|
+
before: decodePhase("before"),
|
|
132
|
+
after: decodePhase("after"),
|
|
133
|
+
transfers,
|
|
134
|
+
requestTransactions: request.transactions,
|
|
135
|
+
});
|
|
136
|
+
(0, bundles_retention_js_1.assertNoBundlesRetention)({
|
|
137
|
+
chainId: request.chainId,
|
|
138
|
+
transfers,
|
|
139
|
+
assetChanges,
|
|
140
|
+
logger: config.logger,
|
|
141
|
+
});
|
|
142
|
+
return (0, result_js_1.assembleResult)({
|
|
143
|
+
ctx,
|
|
144
|
+
request,
|
|
145
|
+
operations: checked,
|
|
146
|
+
authorizations,
|
|
147
|
+
userCalls,
|
|
148
|
+
transfers,
|
|
149
|
+
assetChanges,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -1,68 +1,117 @@
|
|
|
1
|
-
import type { SimulateParams
|
|
1
|
+
import type { SimulateParams } from "../params.js";
|
|
2
|
+
import type { VerifiedSimulationResult } from "../result.js";
|
|
3
|
+
import type { SimulationConfig } from "../types.js";
|
|
2
4
|
/**
|
|
3
5
|
* Simulate a bundle of EVM transactions.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* Parses and normalizes the request → resolves the chain identity → resolves
|
|
8
|
+
* a single pinned block → resolves quoted-asset metadata → plans state reads
|
|
9
|
+
* and the execution → executes once through `eth_simulateV1` under the full
|
|
10
|
+
* timeout budget (simulation with in-block state reads → response parsing →
|
|
11
|
+
* reorg check) → derives ERC20/WETH9 transfers and net asset changes from the
|
|
12
|
+
* user calls only → decodes quoted balances/positions → runs slippage checks
|
|
13
|
+
* → asserts no funds are retained by standalone `bundles` periphery contracts
|
|
14
|
+
* → returns the result. The caller reads whichever fields they need:
|
|
13
15
|
*
|
|
14
|
-
* - `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
16
|
+
* - `simulationTxs` → exactly the caller's ordered transactions, normalized
|
|
17
|
+
* (checksummed addresses, `value` defaulted to `0n`). Internal state reads are
|
|
18
|
+
* never exposed.
|
|
19
|
+
* - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`,
|
|
20
|
+
* `gasUsed`), aligned 1:1 with `simulationTxs[i]`. `gasUsed` is not a safe
|
|
21
|
+
* gas limit; consumers deriving one must add their own headroom.
|
|
22
|
+
* - `transfers` → user-facing preview / server-side verification; each
|
|
23
|
+
* transfer's `txIdx` indexes into `simulationTxs`.
|
|
24
|
+
* - `assetChanges` → net per-asset balance changes grouped by account (sender
|
|
25
|
+
* and counterparties) over the whole bundle.
|
|
23
26
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
27
|
+
* **Modes.** `mode` defaults to `"final"`, which executes the signed calldata
|
|
28
|
+
* against actual permissions and accepts no `authorizations`. `mode:
|
|
29
|
+
* "preview"` accepts typed authorization descriptors, which the pipeline
|
|
30
|
+
* prepares as simulated approval calls. No authorization read-back checks run.
|
|
31
|
+
* `limits` are optional caller-supplied quotes and percentage tolerances;
|
|
32
|
+
* violations throw `ConsumerLimitViolationError`.
|
|
33
|
+
*
|
|
34
|
+
* **Funding.** `value` transfers are funded by the sender's real native
|
|
35
|
+
* balance — no balance inflation — so under-funded bundles revert exactly as
|
|
36
|
+
* they would on-chain. `validation: false` keeps gas from being charged,
|
|
37
|
+
* separating gas from economic effects.
|
|
38
|
+
*
|
|
39
|
+
* @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
|
|
40
|
+
* the overall timeout budget.
|
|
26
41
|
* @param params - Per-call simulation input.
|
|
27
|
-
* @param params.chainId - Chain id the bundle targets.
|
|
28
|
-
* @param params.transactions - The bundle's transactions, in execution order.
|
|
29
|
-
* same `from`.
|
|
30
|
-
* @param params.
|
|
31
|
-
*
|
|
32
|
-
* @param params.
|
|
33
|
-
* @
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @throws {
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* @throws {
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* @param params.chainId - Chain id the bundle targets; must match the endpoint.
|
|
43
|
+
* @param params.transactions - The bundle's transactions, in execution order.
|
|
44
|
+
* All must share the same `from`.
|
|
45
|
+
* @param params.mode - `"final"` (default) or `"preview"`.
|
|
46
|
+
* @param params.authorizations - Preview-only typed authorization descriptors.
|
|
47
|
+
* @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
|
|
48
|
+
* @param params.blockNumber - Optional pinned block number or `BlockTag` other
|
|
49
|
+
* than `"pending"`.
|
|
50
|
+
* Defaults to `latest`, resolved exactly once.
|
|
51
|
+
* @throws {SimulationValidationError} for invalid input (mixed senders, bad
|
|
52
|
+
* addresses, empty transactions, malformed authorizations, final-mode
|
|
53
|
+
* authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
|
|
54
|
+
* `blueSupplyCollateral` / `blueWithdrawCollateral`).
|
|
55
|
+
* @throws {ConsumerLimitViolationError} when a declared `limits` bound is
|
|
56
|
+
* violated by the observed effects.
|
|
57
|
+
* @throws {UnsupportedChainError} when the chain has no `eth_simulateV1`
|
|
58
|
+
* endpoint configured, or limits/preview authorizations require a Morpho
|
|
59
|
+
* Blue address absent from blue-sdk's `getChainAddresses`.
|
|
60
|
+
* @throws {SimulationRevertedError} when a preparation or user transaction reverts.
|
|
61
|
+
* @throws {MissingVerificationEvidenceError} when a planned state read fails
|
|
62
|
+
* or returns empty data, native outgoing traces do not cover value
|
|
63
|
+
* sent, or required metadata reverts or returns empty/invalid data.
|
|
64
|
+
* @throws {InvalidSimulationResponseError} when the node response cannot be
|
|
65
|
+
* trusted (bad shape, call-count mismatch, block that is neither the pinned
|
|
66
|
+
* state block nor its immediate successor, a successor with a mismatched
|
|
67
|
+
* `parentHash`, a block timestamp earlier than the pinned block's, a
|
|
68
|
+
* malformed per-call result, a quoted balance/position read whose non-empty
|
|
69
|
+
* return data cannot be decoded, a state-block hash that changed or a pinned
|
|
70
|
+
* block that vanished mid-flight, or an
|
|
71
|
+
* endpoint whose `eth_chainId` differs from `params.chainId`; chain identity
|
|
72
|
+
* is checked before any block lookup).
|
|
73
|
+
* @throws {BlacklistViolationError} when the simulation leaves value retained
|
|
74
|
+
* beyond the dust threshold by a `bundles` periphery contract
|
|
75
|
+
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
|
|
76
|
+
* Never bypassable.
|
|
77
|
+
* @throws {ExternalServiceError} when the RPC is unavailable within the
|
|
78
|
+
* timeout budget or returns a malformed JSON-RPC envelope. Chain-id
|
|
79
|
+
* mismatches and a state block without number/hash are reported as
|
|
80
|
+
* `InvalidSimulationResponseError`.
|
|
81
|
+
* @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
|
|
82
|
+
* `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
|
|
83
|
+
* stamped with `txIdx`), per-account net `assetChanges`, and per-operation
|
|
84
|
+
* verification outcomes and preparations. Each `verification.operations[i]`
|
|
85
|
+
* mirrors the `limits.operations[i]` entry it verified. Only the amounts
|
|
86
|
+
* quoted in each entry are checked; `checkedLimits` identifies precisely
|
|
87
|
+
* what was checked. Measurements are scoped to the whole bundle.
|
|
47
88
|
* @example
|
|
48
89
|
* ```ts
|
|
49
90
|
* import { simulate } from "@morpho-org/evm-simulation";
|
|
91
|
+
* import { type Address, encodeFunctionData, erc20Abi, getAddress } from "viem";
|
|
92
|
+
*
|
|
93
|
+
* const rpcUrl = "https://rpc.example";
|
|
94
|
+
* const user: Address = getAddress("0x1111111111111111111111111111111111111111");
|
|
95
|
+
* const usdc: Address = getAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
|
|
96
|
+
* const recipient: Address = getAddress("0x2222222222222222222222222222222222222222");
|
|
50
97
|
*
|
|
51
98
|
* const result = await simulate(
|
|
52
|
-
* {
|
|
53
|
-
* chains: new Map([
|
|
54
|
-
* [1, {
|
|
55
|
-
* tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
|
|
56
|
-
* simulateV1Url: process.env.MAINNET_RPC_URL,
|
|
57
|
-
* }],
|
|
58
|
-
* ]),
|
|
59
|
-
* },
|
|
99
|
+
* { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
|
|
60
100
|
* {
|
|
61
101
|
* chainId: 1,
|
|
62
|
-
* transactions: [
|
|
102
|
+
* transactions: [
|
|
103
|
+
* {
|
|
104
|
+
* from: user,
|
|
105
|
+
* to: usdc,
|
|
106
|
+
* data: encodeFunctionData({
|
|
107
|
+
* abi: erc20Abi,
|
|
108
|
+
* functionName: "transfer",
|
|
109
|
+
* args: [recipient, 1_000_000n],
|
|
110
|
+
* }),
|
|
111
|
+
* },
|
|
112
|
+
* ],
|
|
63
113
|
* },
|
|
64
114
|
* );
|
|
65
|
-
* // result satisfies SimulationResult
|
|
66
115
|
* ```
|
|
67
116
|
*/
|
|
68
|
-
export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<
|
|
117
|
+
export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<VerifiedSimulationResult>;
|
|
@@ -1,104 +1,121 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.simulate = simulate;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const index_js_1 = require("./parsing/index.js");
|
|
7
|
-
const index_js_2 = require("./pipeline/index.js");
|
|
4
|
+
const index_js_1 = require("./request/index.js");
|
|
5
|
+
const run_simulation_js_1 = require("./run-simulation.js");
|
|
8
6
|
/**
|
|
9
7
|
* Simulate a bundle of EVM transactions.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
9
|
+
* Parses and normalizes the request → resolves the chain identity → resolves
|
|
10
|
+
* a single pinned block → resolves quoted-asset metadata → plans state reads
|
|
11
|
+
* and the execution → executes once through `eth_simulateV1` under the full
|
|
12
|
+
* timeout budget (simulation with in-block state reads → response parsing →
|
|
13
|
+
* reorg check) → derives ERC20/WETH9 transfers and net asset changes from the
|
|
14
|
+
* user calls only → decodes quoted balances/positions → runs slippage checks
|
|
15
|
+
* → asserts no funds are retained by standalone `bundles` periphery contracts
|
|
16
|
+
* → returns the result. The caller reads whichever fields they need:
|
|
19
17
|
*
|
|
20
|
-
* - `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
18
|
+
* - `simulationTxs` → exactly the caller's ordered transactions, normalized
|
|
19
|
+
* (checksummed addresses, `value` defaulted to `0n`). Internal state reads are
|
|
20
|
+
* never exposed.
|
|
21
|
+
* - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`,
|
|
22
|
+
* `gasUsed`), aligned 1:1 with `simulationTxs[i]`. `gasUsed` is not a safe
|
|
23
|
+
* gas limit; consumers deriving one must add their own headroom.
|
|
24
|
+
* - `transfers` → user-facing preview / server-side verification; each
|
|
25
|
+
* transfer's `txIdx` indexes into `simulationTxs`.
|
|
26
|
+
* - `assetChanges` → net per-asset balance changes grouped by account (sender
|
|
27
|
+
* and counterparties) over the whole bundle.
|
|
29
28
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* **Modes.** `mode` defaults to `"final"`, which executes the signed calldata
|
|
30
|
+
* against actual permissions and accepts no `authorizations`. `mode:
|
|
31
|
+
* "preview"` accepts typed authorization descriptors, which the pipeline
|
|
32
|
+
* prepares as simulated approval calls. No authorization read-back checks run.
|
|
33
|
+
* `limits` are optional caller-supplied quotes and percentage tolerances;
|
|
34
|
+
* violations throw `ConsumerLimitViolationError`.
|
|
35
|
+
*
|
|
36
|
+
* **Funding.** `value` transfers are funded by the sender's real native
|
|
37
|
+
* balance — no balance inflation — so under-funded bundles revert exactly as
|
|
38
|
+
* they would on-chain. `validation: false` keeps gas from being charged,
|
|
39
|
+
* separating gas from economic effects.
|
|
40
|
+
*
|
|
41
|
+
* @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
|
|
42
|
+
* the overall timeout budget.
|
|
32
43
|
* @param params - Per-call simulation input.
|
|
33
|
-
* @param params.chainId - Chain id the bundle targets.
|
|
34
|
-
* @param params.transactions - The bundle's transactions, in execution order.
|
|
35
|
-
* same `from`.
|
|
36
|
-
* @param params.
|
|
37
|
-
*
|
|
38
|
-
* @param params.
|
|
39
|
-
* @
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @throws {
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* @throws {
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
44
|
+
* @param params.chainId - Chain id the bundle targets; must match the endpoint.
|
|
45
|
+
* @param params.transactions - The bundle's transactions, in execution order.
|
|
46
|
+
* All must share the same `from`.
|
|
47
|
+
* @param params.mode - `"final"` (default) or `"preview"`.
|
|
48
|
+
* @param params.authorizations - Preview-only typed authorization descriptors.
|
|
49
|
+
* @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
|
|
50
|
+
* @param params.blockNumber - Optional pinned block number or `BlockTag` other
|
|
51
|
+
* than `"pending"`.
|
|
52
|
+
* Defaults to `latest`, resolved exactly once.
|
|
53
|
+
* @throws {SimulationValidationError} for invalid input (mixed senders, bad
|
|
54
|
+
* addresses, empty transactions, malformed authorizations, final-mode
|
|
55
|
+
* authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
|
|
56
|
+
* `blueSupplyCollateral` / `blueWithdrawCollateral`).
|
|
57
|
+
* @throws {ConsumerLimitViolationError} when a declared `limits` bound is
|
|
58
|
+
* violated by the observed effects.
|
|
59
|
+
* @throws {UnsupportedChainError} when the chain has no `eth_simulateV1`
|
|
60
|
+
* endpoint configured, or limits/preview authorizations require a Morpho
|
|
61
|
+
* Blue address absent from blue-sdk's `getChainAddresses`.
|
|
62
|
+
* @throws {SimulationRevertedError} when a preparation or user transaction reverts.
|
|
63
|
+
* @throws {MissingVerificationEvidenceError} when a planned state read fails
|
|
64
|
+
* or returns empty data, native outgoing traces do not cover value
|
|
65
|
+
* sent, or required metadata reverts or returns empty/invalid data.
|
|
66
|
+
* @throws {InvalidSimulationResponseError} when the node response cannot be
|
|
67
|
+
* trusted (bad shape, call-count mismatch, block that is neither the pinned
|
|
68
|
+
* state block nor its immediate successor, a successor with a mismatched
|
|
69
|
+
* `parentHash`, a block timestamp earlier than the pinned block's, a
|
|
70
|
+
* malformed per-call result, a quoted balance/position read whose non-empty
|
|
71
|
+
* return data cannot be decoded, a state-block hash that changed or a pinned
|
|
72
|
+
* block that vanished mid-flight, or an
|
|
73
|
+
* endpoint whose `eth_chainId` differs from `params.chainId`; chain identity
|
|
74
|
+
* is checked before any block lookup).
|
|
75
|
+
* @throws {BlacklistViolationError} when the simulation leaves value retained
|
|
76
|
+
* beyond the dust threshold by a `bundles` periphery contract
|
|
77
|
+
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
|
|
78
|
+
* Never bypassable.
|
|
79
|
+
* @throws {ExternalServiceError} when the RPC is unavailable within the
|
|
80
|
+
* timeout budget or returns a malformed JSON-RPC envelope. Chain-id
|
|
81
|
+
* mismatches and a state block without number/hash are reported as
|
|
82
|
+
* `InvalidSimulationResponseError`.
|
|
83
|
+
* @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
|
|
84
|
+
* `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
|
|
85
|
+
* stamped with `txIdx`), per-account net `assetChanges`, and per-operation
|
|
86
|
+
* verification outcomes and preparations. Each `verification.operations[i]`
|
|
87
|
+
* mirrors the `limits.operations[i]` entry it verified. Only the amounts
|
|
88
|
+
* quoted in each entry are checked; `checkedLimits` identifies precisely
|
|
89
|
+
* what was checked. Measurements are scoped to the whole bundle.
|
|
53
90
|
* @example
|
|
54
91
|
* ```ts
|
|
55
92
|
* import { simulate } from "@morpho-org/evm-simulation";
|
|
93
|
+
* import { type Address, encodeFunctionData, erc20Abi, getAddress } from "viem";
|
|
94
|
+
*
|
|
95
|
+
* const rpcUrl = "https://rpc.example";
|
|
96
|
+
* const user: Address = getAddress("0x1111111111111111111111111111111111111111");
|
|
97
|
+
* const usdc: Address = getAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
|
|
98
|
+
* const recipient: Address = getAddress("0x2222222222222222222222222222222222222222");
|
|
56
99
|
*
|
|
57
100
|
* const result = await simulate(
|
|
58
|
-
* {
|
|
59
|
-
* chains: new Map([
|
|
60
|
-
* [1, {
|
|
61
|
-
* tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
|
|
62
|
-
* simulateV1Url: process.env.MAINNET_RPC_URL,
|
|
63
|
-
* }],
|
|
64
|
-
* ]),
|
|
65
|
-
* },
|
|
101
|
+
* { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
|
|
66
102
|
* {
|
|
67
103
|
* chainId: 1,
|
|
68
|
-
* transactions: [
|
|
104
|
+
* transactions: [
|
|
105
|
+
* {
|
|
106
|
+
* from: user,
|
|
107
|
+
* to: usdc,
|
|
108
|
+
* data: encodeFunctionData({
|
|
109
|
+
* abi: erc20Abi,
|
|
110
|
+
* functionName: "transfer",
|
|
111
|
+
* args: [recipient, 1_000_000n],
|
|
112
|
+
* }),
|
|
113
|
+
* },
|
|
114
|
+
* ],
|
|
69
115
|
* },
|
|
70
116
|
* );
|
|
71
|
-
* // result satisfies SimulationResult
|
|
72
117
|
* ```
|
|
73
118
|
*/
|
|
74
119
|
async function simulate(config, params) {
|
|
75
|
-
(0,
|
|
76
|
-
const wNative = (0, blue_sdk_1._try)(() => (0, blue_sdk_1.getChainAddresses)(params.chainId).wNative ?? null, blue_sdk_1.UnsupportedChainIdError);
|
|
77
|
-
const simulationTxs = (0, index_js_2.buildSimulationTxs)(params);
|
|
78
|
-
const result = await (0, index_js_2.executeSimulation)({
|
|
79
|
-
config,
|
|
80
|
-
chainId: params.chainId,
|
|
81
|
-
transactions: simulationTxs,
|
|
82
|
-
blockNumber: params.blockNumber,
|
|
83
|
-
wNative,
|
|
84
|
-
});
|
|
85
|
-
if (result.calls.length !== simulationTxs.length) {
|
|
86
|
-
throw new errors_js_1.ExternalServiceError(`Backend returned ${result.calls.length} call result(s) for ${simulationTxs.length} transaction(s) — refusing to map transfers with mismatched lengths`);
|
|
87
|
-
}
|
|
88
|
-
const transfers = (0, index_js_1.parseTransfers)(result.calls, {
|
|
89
|
-
wNative,
|
|
90
|
-
logger: config.logger,
|
|
91
|
-
});
|
|
92
|
-
(0, index_js_2.assertNoBundlesRetention)({
|
|
93
|
-
chainId: params.chainId,
|
|
94
|
-
transfers,
|
|
95
|
-
assetChanges: result.assetChanges,
|
|
96
|
-
logger: config.logger,
|
|
97
|
-
});
|
|
98
|
-
return {
|
|
99
|
-
simulationTxs,
|
|
100
|
-
calls: result.calls,
|
|
101
|
-
transfers,
|
|
102
|
-
assetChanges: result.assetChanges,
|
|
103
|
-
};
|
|
120
|
+
return (0, run_simulation_js_1.runSimulation)({ config, request: (0, index_js_1.parseRequest)(params) });
|
|
104
121
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type SimulationVerificationContext } from "../errors.js";
|
|
2
|
+
import type { SlippageLimits, SlippageQuote } from "../limits.js";
|
|
3
|
+
/**
|
|
4
|
+
* Compare observed amounts against the caller's quote and percentage tolerance.
|
|
5
|
+
* Minimum outputs round up and maximum inputs round down, so rounding never
|
|
6
|
+
* permits a deviation greater than the requested percentage.
|
|
7
|
+
* @param params - Parsed quote/tolerance, matching observations, and error context.
|
|
8
|
+
* @returns The checked quote and tolerance, deep-frozen.
|
|
9
|
+
* @throws {ConsumerLimitViolationError} When adverse slippage exceeds tolerance.
|
|
10
|
+
* @throws {MissingVerificationEvidenceError} When a quoted amount has no observation.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkSlippage(params: {
|
|
14
|
+
readonly limits: SlippageLimits;
|
|
15
|
+
readonly observed: SlippageQuote;
|
|
16
|
+
readonly context: SimulationVerificationContext;
|
|
17
|
+
readonly debtShares?: boolean;
|
|
18
|
+
}): SlippageLimits;
|