@morpho-org/evm-simulation 5.0.1 → 6.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 +6 -4
- package/lib/cjs/errors.d.ts +17 -2
- package/lib/cjs/errors.js +19 -3
- package/lib/cjs/index.d.ts +2 -2
- package/lib/cjs/index.js +2 -1
- package/lib/cjs/params.d.ts +20 -1
- package/lib/cjs/requirements/requirements.js +2 -1
- package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +26 -34
- package/lib/cjs/simulate/backends/eth-simulate-v1.js +35 -74
- package/lib/cjs/simulate/backends/index.d.ts +1 -1
- package/lib/cjs/simulate/backends/index.js +1 -2
- package/lib/cjs/simulate/backends/parse-response.d.ts +1 -0
- package/lib/cjs/simulate/backends/parse-response.js +4 -2
- package/lib/cjs/simulate/backends/resolve-pinned-block.d.ts +6 -12
- package/lib/cjs/simulate/backends/resolve-pinned-block.js +3 -4
- package/lib/cjs/simulate/pipeline/bundles-retention.d.ts +3 -3
- package/lib/cjs/simulate/pipeline/bundles-retention.js +6 -6
- package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +5 -1
- package/lib/cjs/simulate/pipeline/resolve-chain.js +12 -1
- package/lib/cjs/simulate/request/parse-request.d.ts +2 -1
- package/lib/cjs/simulate/request/parse-request.js +21 -0
- package/lib/cjs/simulate/run-simulation.js +9 -12
- package/lib/cjs/simulate/simulate.d.ts +30 -19
- package/lib/cjs/simulate/simulate.js +30 -19
- package/lib/cjs/types.d.ts +15 -0
- package/lib/esm/errors.d.ts +17 -2
- package/lib/esm/errors.js +17 -2
- package/lib/esm/index.d.ts +2 -2
- package/lib/esm/index.js +1 -1
- package/lib/esm/params.d.ts +20 -1
- package/lib/esm/requirements/requirements.js +2 -1
- package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +26 -34
- package/lib/esm/simulate/backends/eth-simulate-v1.js +37 -75
- package/lib/esm/simulate/backends/index.d.ts +1 -1
- package/lib/esm/simulate/backends/index.js +1 -1
- package/lib/esm/simulate/backends/parse-response.d.ts +1 -0
- package/lib/esm/simulate/backends/parse-response.js +4 -2
- package/lib/esm/simulate/backends/resolve-pinned-block.d.ts +6 -12
- package/lib/esm/simulate/backends/resolve-pinned-block.js +3 -4
- package/lib/esm/simulate/pipeline/bundles-retention.d.ts +3 -3
- package/lib/esm/simulate/pipeline/bundles-retention.js +6 -6
- package/lib/esm/simulate/pipeline/resolve-chain.d.ts +5 -1
- package/lib/esm/simulate/pipeline/resolve-chain.js +13 -2
- package/lib/esm/simulate/request/parse-request.d.ts +2 -1
- package/lib/esm/simulate/request/parse-request.js +21 -0
- package/lib/esm/simulate/run-simulation.js +10 -13
- package/lib/esm/simulate/simulate.d.ts +30 -19
- package/lib/esm/simulate/simulate.js +30 -19
- package/lib/esm/types.d.ts +15 -0
- package/package.json +4 -4
|
@@ -18,7 +18,7 @@ const DUST_THRESHOLD = 100n;
|
|
|
18
18
|
/**
|
|
19
19
|
* Assert that no value is retained by standalone `bundles` periphery contracts
|
|
20
20
|
* (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
|
|
21
|
-
* `
|
|
21
|
+
* `MidnightBundlesV2` (`midnightBundlesV2`).
|
|
22
22
|
*
|
|
23
23
|
* Uses net flow (inbound minus outbound) per (restricted address, token) pair.
|
|
24
24
|
* These contracts legitimately receive tokens as an intermediary (user →
|
|
@@ -43,7 +43,7 @@ const DUST_THRESHOLD = 100n;
|
|
|
43
43
|
*
|
|
44
44
|
* @internal Internal pipeline stage, composed by `simulate`; not part of the
|
|
45
45
|
* package's public API (only re-exported from the internal pipeline barrel).
|
|
46
|
-
* @param params.chainId - Chain whose blue-sdk `bundles` and `
|
|
46
|
+
* @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundlesV2`
|
|
47
47
|
* registry entries define the restricted address set. Chains cataloging
|
|
48
48
|
* neither are skipped (a `logger.warn` records the skip).
|
|
49
49
|
* @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
|
|
@@ -55,12 +55,12 @@ const DUST_THRESHOLD = 100n;
|
|
|
55
55
|
* @returns Nothing. Returns silently when no restricted contract retains value
|
|
56
56
|
* above `DUST_THRESHOLD`; otherwise throws.
|
|
57
57
|
* @throws {BlacklistViolationError} when net inbound flow to a restricted
|
|
58
|
-
* `bundles` or `
|
|
58
|
+
* `bundles` or `midnightBundlesV2` contract exceeds `DUST_THRESHOLD` for any
|
|
59
59
|
* token.
|
|
60
60
|
*/
|
|
61
61
|
function assertNoBundlesRetention(params) {
|
|
62
62
|
const { chainId, transfers, assetChanges, logger } = params;
|
|
63
|
-
// Standalone bundles contracts and `
|
|
63
|
+
// Standalone bundles contracts and `MidnightBundlesV2` are transient
|
|
64
64
|
// intermediaries that route user value and must never retain it.
|
|
65
65
|
let addresses;
|
|
66
66
|
try {
|
|
@@ -82,8 +82,8 @@ function assertNoBundlesRetention(params) {
|
|
|
82
82
|
for (const addr of Object.values(addresses.bundles).filter(morpho_ts_1.isDefined))
|
|
83
83
|
restrictedAddresses.add((0, viem_1.getAddress)(addr));
|
|
84
84
|
}
|
|
85
|
-
if (addresses.
|
|
86
|
-
restrictedAddresses.add((0, viem_1.getAddress)(addresses.
|
|
85
|
+
if (addresses.midnightBundlesV2) {
|
|
86
|
+
restrictedAddresses.add((0, viem_1.getAddress)(addresses.midnightBundlesV2));
|
|
87
87
|
}
|
|
88
88
|
if (restrictedAddresses.size === 0) {
|
|
89
89
|
// blue-sdk knows the chain but cataloged no restricted intermediary for it.
|
|
@@ -4,6 +4,10 @@ import type { ChainSimulationConfig, SimulationConfig } from "../../types.js";
|
|
|
4
4
|
*
|
|
5
5
|
* Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
|
|
6
6
|
* when the chain is absent from the map or has no `simulateV1Url` (defensive —
|
|
7
|
-
* the type already requires it at construction).
|
|
7
|
+
* the type already requires it at construction). Throws
|
|
8
|
+
* `SimulationValidationError` when `blockOverrides.gasLimit` is set but is
|
|
9
|
+
* not a positive bigint, or `parentHashCheck` is set but is not a boolean, so a
|
|
10
|
+
* misconfiguration never surfaces as a bypassable
|
|
11
|
+
* `ExternalServiceError`.
|
|
8
12
|
*/
|
|
9
13
|
export declare function resolveChain(config: SimulationConfig, chainId: number): ChainSimulationConfig;
|
|
@@ -7,11 +7,22 @@ const errors_js_1 = require("../../errors.js");
|
|
|
7
7
|
*
|
|
8
8
|
* Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
|
|
9
9
|
* when the chain is absent from the map or has no `simulateV1Url` (defensive —
|
|
10
|
-
* the type already requires it at construction).
|
|
10
|
+
* the type already requires it at construction). Throws
|
|
11
|
+
* `SimulationValidationError` when `blockOverrides.gasLimit` is set but is
|
|
12
|
+
* not a positive bigint, or `parentHashCheck` is set but is not a boolean, so a
|
|
13
|
+
* misconfiguration never surfaces as a bypassable
|
|
14
|
+
* `ExternalServiceError`.
|
|
11
15
|
*/
|
|
12
16
|
function resolveChain(config, chainId) {
|
|
13
17
|
const entry = config.chains.get(chainId);
|
|
14
18
|
if (!entry?.simulateV1Url)
|
|
15
19
|
throw new errors_js_1.UnsupportedChainError(chainId);
|
|
20
|
+
const gasLimit = entry.blockOverrides?.gasLimit;
|
|
21
|
+
if (gasLimit !== undefined &&
|
|
22
|
+
(typeof gasLimit !== "bigint" || gasLimit <= 0n))
|
|
23
|
+
throw new errors_js_1.SimulationValidationError(`Chain ${chainId} blockOverrides.gasLimit must be a positive bigint, got "${String(gasLimit)}" (${typeof gasLimit}). Pass a bigint greater than 0n or omit gasLimit.`, ["blockOverrides.gasLimit: must be a positive bigint"]);
|
|
24
|
+
const parentHashCheck = entry.parentHashCheck;
|
|
25
|
+
if (parentHashCheck !== undefined && typeof parentHashCheck !== "boolean")
|
|
26
|
+
throw new errors_js_1.SimulationValidationError(`Chain ${chainId} parentHashCheck must be a boolean, got "${String(parentHashCheck)}" (${typeof parentHashCheck}). Pass true or false, or omit parentHashCheck.`, ["parentHashCheck: must be a boolean"]);
|
|
16
27
|
return entry;
|
|
17
28
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Address, type BlockTag, type Hex } from "viem";
|
|
2
2
|
import type { SimulationAuthorization } from "../../authorizations.js";
|
|
3
3
|
import type { SimulationLimits } from "../../limits.js";
|
|
4
|
-
import { type SimulateParams, type SimulationMode } from "../../params.js";
|
|
4
|
+
import { type SimulateParams, type SimulationMode, type StateBlock } from "../../params.js";
|
|
5
5
|
/** A normalized user transaction: checksummed addresses, `value` defaulted to `0n`.
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
@@ -21,6 +21,7 @@ export interface ParsedRequest {
|
|
|
21
21
|
/** Always empty in final mode. */
|
|
22
22
|
readonly authorizations: readonly SimulationAuthorization[];
|
|
23
23
|
readonly blockNumber?: bigint | Exclude<BlockTag, "pending">;
|
|
24
|
+
readonly block?: StateBlock;
|
|
24
25
|
readonly limits?: SimulationLimits;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
@@ -52,6 +52,7 @@ const PERMIT2_MESSAGE_KEYS = PERMIT2_TRANSFER_FIELDS.map((field) => field.name);
|
|
|
52
52
|
const PERMIT2_PERMITTED_KEYS = ["token", "amount"];
|
|
53
53
|
const BLUE_AUTHORIZATION_MESSAGE_KEYS = BLUE_AUTHORIZATION_FIELDS.map((field) => field.name);
|
|
54
54
|
const TRANSACTION_KEYS = ["from", "to", "data", "value"];
|
|
55
|
+
const BLOCK_KEYS = ["number", "hash", "timestamp"];
|
|
55
56
|
const LIMITS_KEYS = ["operations"];
|
|
56
57
|
const AUTHORIZATION_KEYS = {
|
|
57
58
|
erc20Approval: ["type", "token", "owner", "spender", "amount"],
|
|
@@ -570,6 +571,7 @@ function parseRequest(input) {
|
|
|
570
571
|
"chainId",
|
|
571
572
|
"transactions",
|
|
572
573
|
"blockNumber",
|
|
574
|
+
"block",
|
|
573
575
|
"mode",
|
|
574
576
|
"authorizations",
|
|
575
577
|
"limits",
|
|
@@ -652,6 +654,24 @@ function parseRequest(input) {
|
|
|
652
654
|
}
|
|
653
655
|
}
|
|
654
656
|
}
|
|
657
|
+
// block
|
|
658
|
+
const rawBlock = input.block;
|
|
659
|
+
let block;
|
|
660
|
+
if (rawBlock !== undefined) {
|
|
661
|
+
if (!isRecord(rawBlock)) {
|
|
662
|
+
fieldErrors.push("block: must be an object");
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
check.keys(rawBlock, { allow: BLOCK_KEYS, path: "block" });
|
|
666
|
+
const number = check.uint256(readField(rawBlock, "number"), "block.number");
|
|
667
|
+
const hash = check.bytes32(readField(rawBlock, "hash"), "block.hash");
|
|
668
|
+
const timestamp = check.uint256(readField(rawBlock, "timestamp"), "block.timestamp");
|
|
669
|
+
if (number !== undefined && hash !== undefined && timestamp !== undefined)
|
|
670
|
+
block = { number, hash: hash.toLowerCase(), timestamp };
|
|
671
|
+
}
|
|
672
|
+
if (rawBlockNumber !== undefined)
|
|
673
|
+
fieldErrors.push("block: cannot be combined with blockNumber");
|
|
674
|
+
}
|
|
655
675
|
// authorizations
|
|
656
676
|
const rawAuthorizations = input.authorizations;
|
|
657
677
|
const authorizations = [];
|
|
@@ -754,6 +774,7 @@ function parseRequest(input) {
|
|
|
754
774
|
transactions,
|
|
755
775
|
authorizations: mode === "preview" ? authorizations : [],
|
|
756
776
|
...(blockNumber !== undefined ? { blockNumber } : {}),
|
|
777
|
+
...(block !== undefined ? { block } : {}),
|
|
757
778
|
...(normalizedLimits !== undefined ? { limits: normalizedLimits } : {}),
|
|
758
779
|
});
|
|
759
780
|
}
|
|
@@ -45,21 +45,16 @@ async function runSimulation(params) {
|
|
|
45
45
|
if (requiresMorpho && addresses?.blue == null)
|
|
46
46
|
throw new errors_js_1.UnsupportedChainError(request.chainId);
|
|
47
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
48
|
// Monad nodes reject `validation: false` and charge no gas in the simulated
|
|
55
49
|
// block either way. Its `latest` block is not final: simulating at its number
|
|
56
50
|
// runs on another parent, so default to `finalized`.
|
|
57
51
|
const monad = request.chainId === blue_sdk_1.ChainId.MonadMainnet;
|
|
58
|
-
const pinnedBlock =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
const pinnedBlock = request.block ??
|
|
53
|
+
(await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
|
|
54
|
+
client,
|
|
55
|
+
blockNumber: request.blockNumber ?? (monad ? "finalized" : undefined),
|
|
56
|
+
signal,
|
|
57
|
+
}));
|
|
63
58
|
// With no calldata decoding, the caller's declared `limits.operations`
|
|
64
59
|
// entries are the operations: subjects for state reads and per-operation
|
|
65
60
|
// checks come straight from the limit list.
|
|
@@ -85,12 +80,14 @@ async function runSimulation(params) {
|
|
|
85
80
|
preparations,
|
|
86
81
|
reads: observations.reads,
|
|
87
82
|
});
|
|
88
|
-
// Execute against the block resolved once above
|
|
83
|
+
// Execute against the block resolved once above.
|
|
89
84
|
const execution = await (0, index_js_1.executePlan)({
|
|
90
85
|
client,
|
|
91
86
|
plan,
|
|
92
87
|
stateBlock: pinnedBlock,
|
|
93
88
|
validation: monad,
|
|
89
|
+
blockGasLimit: chain.blockOverrides?.gasLimit,
|
|
90
|
+
parentHashCheck: chain.parentHashCheck ?? request.chainId !== blue_sdk_1.ChainId.StableMainnet,
|
|
94
91
|
});
|
|
95
92
|
const userCalls = execution.calls
|
|
96
93
|
.filter((call) => call.planned.type === "transaction")
|
|
@@ -4,11 +4,11 @@ import type { SimulationConfig } from "../types.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Simulate a bundle of EVM transactions.
|
|
6
6
|
*
|
|
7
|
-
* Parses and normalizes the request → resolves
|
|
8
|
-
*
|
|
9
|
-
* and the execution → executes once through `eth_simulateV1` under
|
|
10
|
-
* timeout budget (simulation with in-block state reads → response
|
|
11
|
-
*
|
|
7
|
+
* Parses and normalizes the request → resolves a single pinned block (skipped
|
|
8
|
+
* when `params.block` is supplied) → resolves quoted-asset metadata → plans
|
|
9
|
+
* state reads and the execution → executes once through `eth_simulateV1` under
|
|
10
|
+
* the full timeout budget (simulation with in-block state reads → response
|
|
11
|
+
* parsing) → derives ERC20/WETH9 transfers and net asset changes from the
|
|
12
12
|
* user calls only → decodes quoted balances/positions → runs slippage checks
|
|
13
13
|
* → asserts no funds are retained by standalone `bundles` periphery contracts
|
|
14
14
|
* → returns the result. The caller reads whichever fields they need:
|
|
@@ -37,10 +37,13 @@ import type { SimulationConfig } from "../types.js";
|
|
|
37
37
|
* separating gas from economic effects. Monad nodes reject `false`, so Monad
|
|
38
38
|
* simulations send `true`; its simulated block charges no gas either way.
|
|
39
39
|
*
|
|
40
|
-
* @param config - Required per-chain `eth_simulateV1` URL, optional
|
|
41
|
-
*
|
|
40
|
+
* @param config - Required per-chain `eth_simulateV1` URL, optional per-chain
|
|
41
|
+
* `blockOverrides.gasLimit` and `parentHashCheck`, optional logger, and the
|
|
42
|
+
* overall timeout budget.
|
|
42
43
|
* @param params - Per-call simulation input.
|
|
43
|
-
* @param params.chainId - Chain id the bundle targets
|
|
44
|
+
* @param params.chainId - Chain id the bundle targets. Sent on every call inside
|
|
45
|
+
* `eth_simulateV1`, so nodes that check it reject a wrong-chain endpoint;
|
|
46
|
+
* there is no separate `eth_chainId` lookup.
|
|
44
47
|
* @param params.transactions - The bundle's transactions, in execution order.
|
|
45
48
|
* All must share the same `from`.
|
|
46
49
|
* @param params.mode - `"final"` (default) or `"preview"`.
|
|
@@ -50,9 +53,16 @@ import type { SimulationConfig } from "../types.js";
|
|
|
50
53
|
* than `"pending"`.
|
|
51
54
|
* Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
|
|
52
55
|
* final), resolved exactly once.
|
|
56
|
+
* @param params.block - Optional caller-supplied state block (`number`,
|
|
57
|
+
* `hash`, `timestamp`). Skips the block lookup, so a simulation without
|
|
58
|
+
* asset metadata reads makes a single `eth_simulateV1` request. Cannot be
|
|
59
|
+
* combined with `blockNumber`.
|
|
53
60
|
* @throws {SimulationValidationError} for invalid input (mixed senders, bad
|
|
54
61
|
* addresses, empty transactions, malformed authorizations, final-mode
|
|
55
|
-
* authorizations, malformed limits, unknown fields, a `"pending"` block tag,
|
|
62
|
+
* authorizations, malformed limits, unknown fields, a `"pending"` block tag,
|
|
63
|
+
* a malformed `block` or one combined with `blockNumber`, a
|
|
64
|
+
* `blockOverrides.gasLimit` that is not a positive bigint, a `parentHashCheck`
|
|
65
|
+
* that is not a boolean, or share quotes for
|
|
56
66
|
* `blueSupplyCollateral` / `blueWithdrawCollateral`).
|
|
57
67
|
* @throws {ConsumerLimitViolationError} when a declared `limits` bound is
|
|
58
68
|
* violated by the observed effects.
|
|
@@ -66,20 +76,21 @@ import type { SimulationConfig } from "../types.js";
|
|
|
66
76
|
* @throws {InvalidSimulationResponseError} when the node response cannot be
|
|
67
77
|
* trusted (bad shape, call-count mismatch, block that is neither the pinned
|
|
68
78
|
* state block nor its immediate successor, a successor with a mismatched
|
|
69
|
-
* `parentHash
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
79
|
+
* `parentHash` (unless `parentHashCheck` is off; off by default on Stable,
|
|
80
|
+
* chain 988), a block timestamp earlier than the pinned block's, a
|
|
81
|
+
* malformed per-call result, or a quoted balance/position read whose
|
|
82
|
+
* non-empty return data cannot be decoded).
|
|
83
|
+
* @throws {InvalidChainIdError} when the node rejects the request `chainId`
|
|
84
|
+
* because the endpoint serves another chain (a subclass of
|
|
85
|
+
* `InvalidSimulationResponseError` sharing its `name`/`code`;
|
|
86
|
+
* `instanceof InvalidChainIdError` tells them apart). Never bypassable.
|
|
75
87
|
* @throws {BlacklistViolationError} when the simulation leaves value retained
|
|
76
88
|
* beyond the dust threshold by a `bundles` periphery contract
|
|
77
|
-
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1,
|
|
89
|
+
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV2).
|
|
78
90
|
* Never bypassable.
|
|
79
91
|
* @throws {ExternalServiceError} when the RPC is unavailable within the
|
|
80
|
-
* timeout budget or returns a malformed JSON-RPC envelope.
|
|
81
|
-
*
|
|
82
|
-
* `InvalidSimulationResponseError`.
|
|
92
|
+
* timeout budget or returns a malformed JSON-RPC envelope. A looked-up state
|
|
93
|
+
* block without number/hash is reported as `InvalidSimulationResponseError`.
|
|
83
94
|
* @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
|
|
84
95
|
* `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
|
|
85
96
|
* stamped with `txIdx`), per-account net `assetChanges`, and per-operation
|
|
@@ -6,11 +6,11 @@ const run_simulation_js_1 = require("./run-simulation.js");
|
|
|
6
6
|
/**
|
|
7
7
|
* Simulate a bundle of EVM transactions.
|
|
8
8
|
*
|
|
9
|
-
* Parses and normalizes the request → resolves
|
|
10
|
-
*
|
|
11
|
-
* and the execution → executes once through `eth_simulateV1` under
|
|
12
|
-
* timeout budget (simulation with in-block state reads → response
|
|
13
|
-
*
|
|
9
|
+
* Parses and normalizes the request → resolves a single pinned block (skipped
|
|
10
|
+
* when `params.block` is supplied) → resolves quoted-asset metadata → plans
|
|
11
|
+
* state reads and the execution → executes once through `eth_simulateV1` under
|
|
12
|
+
* the full timeout budget (simulation with in-block state reads → response
|
|
13
|
+
* parsing) → derives ERC20/WETH9 transfers and net asset changes from the
|
|
14
14
|
* user calls only → decodes quoted balances/positions → runs slippage checks
|
|
15
15
|
* → asserts no funds are retained by standalone `bundles` periphery contracts
|
|
16
16
|
* → returns the result. The caller reads whichever fields they need:
|
|
@@ -39,10 +39,13 @@ const run_simulation_js_1 = require("./run-simulation.js");
|
|
|
39
39
|
* separating gas from economic effects. Monad nodes reject `false`, so Monad
|
|
40
40
|
* simulations send `true`; its simulated block charges no gas either way.
|
|
41
41
|
*
|
|
42
|
-
* @param config - Required per-chain `eth_simulateV1` URL, optional
|
|
43
|
-
*
|
|
42
|
+
* @param config - Required per-chain `eth_simulateV1` URL, optional per-chain
|
|
43
|
+
* `blockOverrides.gasLimit` and `parentHashCheck`, optional logger, and the
|
|
44
|
+
* overall timeout budget.
|
|
44
45
|
* @param params - Per-call simulation input.
|
|
45
|
-
* @param params.chainId - Chain id the bundle targets
|
|
46
|
+
* @param params.chainId - Chain id the bundle targets. Sent on every call inside
|
|
47
|
+
* `eth_simulateV1`, so nodes that check it reject a wrong-chain endpoint;
|
|
48
|
+
* there is no separate `eth_chainId` lookup.
|
|
46
49
|
* @param params.transactions - The bundle's transactions, in execution order.
|
|
47
50
|
* All must share the same `from`.
|
|
48
51
|
* @param params.mode - `"final"` (default) or `"preview"`.
|
|
@@ -52,9 +55,16 @@ const run_simulation_js_1 = require("./run-simulation.js");
|
|
|
52
55
|
* than `"pending"`.
|
|
53
56
|
* Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
|
|
54
57
|
* final), resolved exactly once.
|
|
58
|
+
* @param params.block - Optional caller-supplied state block (`number`,
|
|
59
|
+
* `hash`, `timestamp`). Skips the block lookup, so a simulation without
|
|
60
|
+
* asset metadata reads makes a single `eth_simulateV1` request. Cannot be
|
|
61
|
+
* combined with `blockNumber`.
|
|
55
62
|
* @throws {SimulationValidationError} for invalid input (mixed senders, bad
|
|
56
63
|
* addresses, empty transactions, malformed authorizations, final-mode
|
|
57
|
-
* authorizations, malformed limits, unknown fields, a `"pending"` block tag,
|
|
64
|
+
* authorizations, malformed limits, unknown fields, a `"pending"` block tag,
|
|
65
|
+
* a malformed `block` or one combined with `blockNumber`, a
|
|
66
|
+
* `blockOverrides.gasLimit` that is not a positive bigint, a `parentHashCheck`
|
|
67
|
+
* that is not a boolean, or share quotes for
|
|
58
68
|
* `blueSupplyCollateral` / `blueWithdrawCollateral`).
|
|
59
69
|
* @throws {ConsumerLimitViolationError} when a declared `limits` bound is
|
|
60
70
|
* violated by the observed effects.
|
|
@@ -68,20 +78,21 @@ const run_simulation_js_1 = require("./run-simulation.js");
|
|
|
68
78
|
* @throws {InvalidSimulationResponseError} when the node response cannot be
|
|
69
79
|
* trusted (bad shape, call-count mismatch, block that is neither the pinned
|
|
70
80
|
* state block nor its immediate successor, a successor with a mismatched
|
|
71
|
-
* `parentHash
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
81
|
+
* `parentHash` (unless `parentHashCheck` is off; off by default on Stable,
|
|
82
|
+
* chain 988), a block timestamp earlier than the pinned block's, a
|
|
83
|
+
* malformed per-call result, or a quoted balance/position read whose
|
|
84
|
+
* non-empty return data cannot be decoded).
|
|
85
|
+
* @throws {InvalidChainIdError} when the node rejects the request `chainId`
|
|
86
|
+
* because the endpoint serves another chain (a subclass of
|
|
87
|
+
* `InvalidSimulationResponseError` sharing its `name`/`code`;
|
|
88
|
+
* `instanceof InvalidChainIdError` tells them apart). Never bypassable.
|
|
77
89
|
* @throws {BlacklistViolationError} when the simulation leaves value retained
|
|
78
90
|
* beyond the dust threshold by a `bundles` periphery contract
|
|
79
|
-
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1,
|
|
91
|
+
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV2).
|
|
80
92
|
* Never bypassable.
|
|
81
93
|
* @throws {ExternalServiceError} when the RPC is unavailable within the
|
|
82
|
-
* timeout budget or returns a malformed JSON-RPC envelope.
|
|
83
|
-
*
|
|
84
|
-
* `InvalidSimulationResponseError`.
|
|
94
|
+
* timeout budget or returns a malformed JSON-RPC envelope. A looked-up state
|
|
95
|
+
* block without number/hash is reported as `InvalidSimulationResponseError`.
|
|
85
96
|
* @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
|
|
86
97
|
* `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
|
|
87
98
|
* stamped with `txIdx`), per-account net `assetChanges`, and per-operation
|
package/lib/cjs/types.d.ts
CHANGED
|
@@ -3,6 +3,21 @@ import type { Address, Hex } from "viem";
|
|
|
3
3
|
export interface ChainSimulationConfig {
|
|
4
4
|
/** JSON-RPC URL supporting `eth_simulateV1`. Required for every configured chain. */
|
|
5
5
|
readonly simulateV1Url: string;
|
|
6
|
+
/**
|
|
7
|
+
* Block overrides forwarded to `eth_simulateV1` as the simulated block's
|
|
8
|
+
* `blockOverrides`. Omitted fields leave the node's defaults untouched.
|
|
9
|
+
*/
|
|
10
|
+
readonly blockOverrides?: {
|
|
11
|
+
/** Gas limit of the simulated block. Omitted: no gas limit override is sent. */
|
|
12
|
+
readonly gasLimit?: bigint;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Whether a simulated successor block's `parentHash` must equal the pinned
|
|
16
|
+
* state block hash. Block number and timestamp checks always run.
|
|
17
|
+
* Omitted: on everywhere except Stable (988), whose nodes report a
|
|
18
|
+
* `parentHash` that never matches the pinned block.
|
|
19
|
+
*/
|
|
20
|
+
readonly parentHashCheck?: boolean;
|
|
6
21
|
}
|
|
7
22
|
/**
|
|
8
23
|
* Top-level configuration for `simulate`.
|
package/lib/esm/errors.d.ts
CHANGED
|
@@ -136,7 +136,7 @@ export interface RetainedAsset {
|
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
138
|
* Thrown when net value above the dust threshold is retained by a restricted bundles contract
|
|
139
|
-
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or
|
|
139
|
+
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV2) after simulation;
|
|
140
140
|
* pass-through flows and net outflows are allowed. Never bypassable.
|
|
141
141
|
*/
|
|
142
142
|
export declare class BlacklistViolationError extends SimulationPackageError {
|
|
@@ -175,11 +175,26 @@ export declare class UnsupportedOperationError extends SimulationPackageError {
|
|
|
175
175
|
readonly name = "UnsupportedOperationError";
|
|
176
176
|
readonly code = "UNSUPPORTED_OPERATION";
|
|
177
177
|
}
|
|
178
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* The simulation backend returned a response that cannot be parsed.
|
|
180
|
+
* {@link InvalidChainIdError} subclasses this class and shares its `name`
|
|
181
|
+
* and `code`; check `instanceof InvalidChainIdError` first to tell the two
|
|
182
|
+
* apart.
|
|
183
|
+
*/
|
|
179
184
|
export declare class InvalidSimulationResponseError extends SimulationPackageError {
|
|
180
185
|
readonly name = "InvalidSimulationResponseError";
|
|
181
186
|
readonly code = "INVALID_SIMULATION_RESPONSE";
|
|
182
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* The node rejected the request `chainId`: the endpoint is on a different
|
|
190
|
+
* chain than configured. This is misconfiguration, not an outage, so unlike
|
|
191
|
+
* {@link ExternalServiceError} callers must not bypass it.
|
|
192
|
+
* Extends {@link InvalidSimulationResponseError} and shares its `name` and
|
|
193
|
+
* `code`, so v5 callers that match on either still catch it;
|
|
194
|
+
* `instanceof InvalidChainIdError` is how to tell the two apart.
|
|
195
|
+
*/
|
|
196
|
+
export declare class InvalidChainIdError extends InvalidSimulationResponseError {
|
|
197
|
+
}
|
|
183
198
|
/** State needed to verify an operation could not be fetched or derived. */
|
|
184
199
|
export declare class MissingVerificationEvidenceError extends SimulationPackageError {
|
|
185
200
|
readonly name = "MissingVerificationEvidenceError";
|
package/lib/esm/errors.js
CHANGED
|
@@ -60,7 +60,7 @@ export class SimulationRevertedError extends SimulationPackageError {
|
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Thrown when net value above the dust threshold is retained by a restricted bundles contract
|
|
63
|
-
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or
|
|
63
|
+
* (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV2) after simulation;
|
|
64
64
|
* pass-through flows and net outflows are allowed. Never bypassable.
|
|
65
65
|
*/
|
|
66
66
|
export class BlacklistViolationError extends SimulationPackageError {
|
|
@@ -109,11 +109,26 @@ export class UnsupportedOperationError extends SimulationPackageError {
|
|
|
109
109
|
name = "UnsupportedOperationError";
|
|
110
110
|
code = "UNSUPPORTED_OPERATION";
|
|
111
111
|
}
|
|
112
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* The simulation backend returned a response that cannot be parsed.
|
|
114
|
+
* {@link InvalidChainIdError} subclasses this class and shares its `name`
|
|
115
|
+
* and `code`; check `instanceof InvalidChainIdError` first to tell the two
|
|
116
|
+
* apart.
|
|
117
|
+
*/
|
|
113
118
|
export class InvalidSimulationResponseError extends SimulationPackageError {
|
|
114
119
|
name = "InvalidSimulationResponseError";
|
|
115
120
|
code = "INVALID_SIMULATION_RESPONSE";
|
|
116
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The node rejected the request `chainId`: the endpoint is on a different
|
|
124
|
+
* chain than configured. This is misconfiguration, not an outage, so unlike
|
|
125
|
+
* {@link ExternalServiceError} callers must not bypass it.
|
|
126
|
+
* Extends {@link InvalidSimulationResponseError} and shares its `name` and
|
|
127
|
+
* `code`, so v5 callers that match on either still catch it;
|
|
128
|
+
* `instanceof InvalidChainIdError` is how to tell the two apart.
|
|
129
|
+
*/
|
|
130
|
+
export class InvalidChainIdError extends InvalidSimulationResponseError {
|
|
131
|
+
}
|
|
117
132
|
/** State needed to verify an operation could not be fetched or derived. */
|
|
118
133
|
export class MissingVerificationEvidenceError extends SimulationPackageError {
|
|
119
134
|
name = "MissingVerificationEvidenceError";
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export type { BlueAuthorization, BlueAuthorizationSignature, BlueAuthorizationTypedData, Eip712Domain, Eip712Field, Erc20ApprovalAuthorization, Erc2612PermitAuthorization, Erc2612PermitTypedData, Permit2TransferAuthorization, Permit2TransferTypedData, SimulationAuthorization, } from "./authorizations.js";
|
|
2
2
|
export type { RetainedAsset, SimulationErrorCode, SimulationErrorContext, SimulationExecutionContext, SimulationExecutionReason, SimulationPreparationContext, SimulationStage, SimulationTransportContext, SimulationValidationContext, SimulationVerificationContext, } from "./errors.js";
|
|
3
|
-
export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
|
|
3
|
+
export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidChainIdError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
|
|
4
4
|
export type { BlueMarketOperationSubject, BlueMarketOperationType, BlueRefinanceSubject, OperationLimit, OperationType, SimulationLimits, SimulationOperationSubject, SlippageLimits, SlippageQuote, VaultOperationSubject, VaultOperationType, VaultV1MigrateToV2Subject, } from "./limits.js";
|
|
5
5
|
export { BLUE_MARKET_OPERATION_TYPES, OPERATION_TYPES, VAULT_OPERATION_TYPES, } from "./limits.js";
|
|
6
|
-
export type { SimulateParams, SimulationMode, } from "./params.js";
|
|
6
|
+
export type { SimulateParams, SimulationMode, StateBlock, } from "./params.js";
|
|
7
7
|
export { SIMULATION_MODES } from "./params.js";
|
|
8
8
|
export { toSimulationAuthorizations } from "./requirements/index.js";
|
|
9
9
|
export type { AuthorizationPreparation, SimulatedOperation, SimulationVerification, VerifiedSimulationResult, } from "./result.js";
|
package/lib/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Public fns (via feature-folder barrels)
|
|
2
2
|
// Errors (for instanceof checks by consumers)
|
|
3
|
-
export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
|
|
3
|
+
export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidChainIdError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
|
|
4
4
|
export { BLUE_MARKET_OPERATION_TYPES, OPERATION_TYPES, VAULT_OPERATION_TYPES, } from "./limits.js";
|
|
5
5
|
export { SIMULATION_MODES } from "./params.js";
|
|
6
6
|
export { toSimulationAuthorizations } from "./requirements/index.js";
|
package/lib/esm/params.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BlockTag } from "viem";
|
|
1
|
+
import type { BlockTag, Hex } from "viem";
|
|
2
2
|
import type { SimulationAuthorization } from "./authorizations.js";
|
|
3
3
|
import type { SimulationLimits } from "./limits.js";
|
|
4
4
|
import type { SimulationTransaction } from "./types.js";
|
|
@@ -6,6 +6,15 @@ import type { SimulationTransaction } from "./types.js";
|
|
|
6
6
|
export declare const SIMULATION_MODES: readonly ["preview", "final"];
|
|
7
7
|
/** Simulation mode: `"preview"` accepts pending authorizations, `"final"` does not. */
|
|
8
8
|
export type SimulationMode = (typeof SIMULATION_MODES)[number];
|
|
9
|
+
/** A mined block the simulation runs on top of. */
|
|
10
|
+
export interface StateBlock {
|
|
11
|
+
/** Block number the simulation runs on top of. */
|
|
12
|
+
readonly number: bigint;
|
|
13
|
+
/** Hash of that block; compared to the next block's `parentHash`. */
|
|
14
|
+
readonly hash: Hex;
|
|
15
|
+
/** Block timestamp in Unix seconds; the simulated block must not be earlier. */
|
|
16
|
+
readonly timestamp: bigint;
|
|
17
|
+
}
|
|
9
18
|
/**
|
|
10
19
|
* Input to `simulate()`. `authorizations` is accepted only in "preview";
|
|
11
20
|
* `mode` defaults to "final".
|
|
@@ -21,6 +30,16 @@ export interface SimulateParams {
|
|
|
21
30
|
* "latest" block is not final). Only canonical (mined) blocks can be
|
|
22
31
|
* pinned; `pending` has no stable hash and is rejected at runtime. */
|
|
23
32
|
readonly blockNumber?: bigint | Exclude<BlockTag, "pending">;
|
|
33
|
+
/** State block supplied by the caller, so `simulate()` skips the block
|
|
34
|
+
* lookup; with no asset metadata reads it then sends only `eth_simulateV1`.
|
|
35
|
+
* Cannot be combined with `blockNumber`.
|
|
36
|
+
* Not checked against the endpoint: `hash` is compared only when the node
|
|
37
|
+
* reports the next block (its `parentHash`) and `parentHashCheck` is on (off
|
|
38
|
+
* by default on Stable, 988), and `timestamp` only bounds the simulated
|
|
39
|
+
* block's timestamp. On Monad (143), supply a finalized block: `latest` is
|
|
40
|
+
* not final there, so the block at that number can change and fail the
|
|
41
|
+
* `parentHash` check. */
|
|
42
|
+
readonly block?: StateBlock;
|
|
24
43
|
/** Caller-selected quotes and percentage tolerances. Omitted limits are unchecked. */
|
|
25
44
|
readonly limits?: SimulationLimits;
|
|
26
45
|
}
|
|
@@ -130,6 +130,7 @@ export function toSimulationAuthorizations(params) {
|
|
|
130
130
|
}
|
|
131
131
|
if (isRequirementSignature(requirement)) {
|
|
132
132
|
const { action } = requirement;
|
|
133
|
+
const actionType = action.type;
|
|
133
134
|
switch (action.type) {
|
|
134
135
|
case "permit":
|
|
135
136
|
return toErc2612Permit(action, ctx);
|
|
@@ -138,7 +139,7 @@ export function toSimulationAuthorizations(params) {
|
|
|
138
139
|
case "authorization":
|
|
139
140
|
return toBlueAuthorizationSignature(action, ctx);
|
|
140
141
|
default:
|
|
141
|
-
return unsupported(ctx)(`Signature requirement action type "${
|
|
142
|
+
return unsupported(ctx)(`Signature requirement action type "${actionType}" is not supported. Only permit, permit2SignatureTransfer, and authorization signatures are supported`);
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
return unsupported(ctx)(`Requirement action type "${requirement.action.type}" is not supported. Only erc20Approval, blueAuthorization, permit, permit2SignatureTransfer, and authorization requirements are supported`);
|