@morpho-org/evm-simulation 4.1.9 → 4.2.0-next.1

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.
@@ -19,7 +19,7 @@ interface RetainedAsset {
19
19
  token: string | undefined;
20
20
  netRetained: string;
21
21
  }
22
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
22
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
23
23
  export declare class BlacklistViolationError extends SimulationPackageError {
24
24
  readonly assetChanges?: RetainedAsset[] | undefined;
25
25
  readonly code = "BLACKLIST_ERROR";
package/lib/cjs/errors.js CHANGED
@@ -24,7 +24,7 @@ class SimulationRevertedError extends SimulationPackageError {
24
24
  }
25
25
  }
26
26
  exports.SimulationRevertedError = SimulationRevertedError;
27
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
27
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
28
28
  class BlacklistViolationError extends SimulationPackageError {
29
29
  assetChanges;
30
30
  code = "BLACKLIST_ERROR";
@@ -13,7 +13,7 @@ export interface AssetChangeEntry {
13
13
  * any real token address. `eth_simulateV1` synthesizes native moves from the
14
14
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
15
15
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
16
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
16
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
17
17
  * sentinel would land on a separate map key and silently escape retention checks.
18
18
  * This is the single source of truth for token normalization shared by the
19
19
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -8,7 +8,7 @@ const viem_1 = require("viem");
8
8
  * any real token address. `eth_simulateV1` synthesizes native moves from the
9
9
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
10
10
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
11
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
11
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
12
12
  * sentinel would land on a separate map key and silently escape retention checks.
13
13
  * This is the single source of truth for token normalization shared by the
14
14
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -36,7 +36,7 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
36
36
  * atomically inside a single call frame. A **non-canonical wrapped-native**
37
37
  * that splits these emissions across two txs in the same bundle would leave
38
38
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
39
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
39
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
40
40
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
41
41
  * `warn` so the assumption break is observable before it reaches retention.
42
42
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
@@ -45,7 +45,7 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
45
45
  * atomically inside a single call frame. A **non-canonical wrapped-native**
46
46
  * that splits these emissions across two txs in the same bundle would leave
47
47
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
48
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
48
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
49
49
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
50
50
  * `warn` so the assumption break is observable before it reaches retention.
51
51
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
@@ -1,22 +1,22 @@
1
1
  import type { AccountAssetChanges, SimulationLogger, Transfer } from "../../types.js";
2
- interface AssertNoBundlerRetentionParams {
2
+ interface AssertNoBundlesRetentionParams {
3
3
  chainId: number;
4
4
  transfers: Transfer[];
5
5
  assetChanges: readonly AccountAssetChanges[];
6
6
  logger?: SimulationLogger;
7
7
  }
8
8
  /**
9
- * Assert that no value is retained by restricted contract addresses — the
10
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
11
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
9
+ * Assert that no value is retained by standalone `bundles` periphery contracts
10
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
12
11
  *
13
12
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
14
13
  * These contracts legitimately receive tokens as an intermediary (user →
15
- * bundler → vault), so gross inbound would fire false positives. We only block
16
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
17
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
18
- * state-overridden balance. That is useful telemetry, but not current-bundle
19
- * retention, so it is warned instead of raising a blacklist violation.
14
+ * bundles contract → vault), so gross inbound would fire false positives. We
15
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
16
+ * restricted address. Negative net flow means the simulated bundle sweeps a
17
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
18
+ * current-bundle retention, so it is warned instead of raising a blacklist
19
+ * violation.
20
20
  *
21
21
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
22
22
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -25,19 +25,19 @@ interface AssertNoBundlerRetentionParams {
25
25
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
26
26
  * truth for native value: Tenderly derives it from its trace, and
27
27
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
28
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
29
- * Tenderly path (Cantina finding 1440).
28
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
29
+ * on the Tenderly path (Cantina finding 1440).
30
30
  *
31
31
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
32
32
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
33
33
  * `assetChanges` derived from it — native transfer logs are only used as a
34
- * fallback for bundler addresses that carry no native `assetChanges` entry.
34
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
35
35
  *
36
36
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
37
37
  * package's public API (only re-exported from the internal pipeline barrel).
38
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
39
- * registries define the restricted address set. Chains cataloging neither are
40
- * skipped (a `logger.warn` records the skip).
38
+ * @param params.chainId - Chain whose blue-sdk `bundles` registry defines the
39
+ * restricted address set. Chains without one are skipped (a `logger.warn`
40
+ * records the skip).
41
41
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
42
42
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
43
43
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -47,7 +47,7 @@ interface AssertNoBundlerRetentionParams {
47
47
  * @returns Nothing. Returns silently when no restricted contract retains value
48
48
  * above `DUST_THRESHOLD`; otherwise throws.
49
49
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
50
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
50
+ * `bundles` contract exceeds `DUST_THRESHOLD` for any token.
51
51
  */
52
- export declare function assertNoBundlerRetention(params: AssertNoBundlerRetentionParams): void;
52
+ export declare function assertNoBundlesRetention(params: AssertNoBundlesRetentionParams): void;
53
53
  export {};
@@ -1,32 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertNoBundlerRetention = assertNoBundlerRetention;
3
+ exports.assertNoBundlesRetention = assertNoBundlesRetention;
4
4
  const blue_sdk_1 = require("@morpho-org/blue-sdk");
5
5
  const morpho_ts_1 = require("@morpho-org/morpho-ts");
6
6
  const viem_1 = require("viem");
7
7
  const errors_js_1 = require("../../errors.js");
8
8
  /**
9
- * Dust tolerance for bundler retention, in raw token units.
9
+ * Dust tolerance for standalone bundles retention, in raw token units.
10
10
  *
11
11
  * 100 units is negligible for 18-decimal tokens (~1e-16). For low-decimal
12
12
  * tokens (e.g., 2-decimal stablecoins) this represents ~1.00 token — worth
13
13
  * keeping in mind. The threshold is intentionally token-agnostic: its purpose
14
- * is to absorb rounding noise from bundler3 routing, not to define an
15
- * acceptable retention amount.
14
+ * is to absorb routing rounding noise, not to define an acceptable retention
15
+ * amount.
16
16
  */
17
17
  const DUST_THRESHOLD = 100n;
18
18
  /**
19
- * Assert that no value is retained by restricted contract addresses — the
20
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
21
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
19
+ * Assert that no value is retained by standalone `bundles` periphery contracts
20
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
22
21
  *
23
22
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
24
23
  * These contracts legitimately receive tokens as an intermediary (user →
25
- * bundler → vault), so gross inbound would fire false positives. We only block
26
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
27
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
28
- * state-overridden balance. That is useful telemetry, but not current-bundle
29
- * retention, so it is warned instead of raising a blacklist violation.
24
+ * bundles contract → vault), so gross inbound would fire false positives. We
25
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
26
+ * restricted address. Negative net flow means the simulated bundle sweeps a
27
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
28
+ * current-bundle retention, so it is warned instead of raising a blacklist
29
+ * violation.
30
30
  *
31
31
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
32
32
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -35,19 +35,19 @@ const DUST_THRESHOLD = 100n;
35
35
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
36
36
  * truth for native value: Tenderly derives it from its trace, and
37
37
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
38
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
39
- * Tenderly path (Cantina finding 1440).
38
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
39
+ * on the Tenderly path (Cantina finding 1440).
40
40
  *
41
41
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
42
42
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
43
43
  * `assetChanges` derived from it — native transfer logs are only used as a
44
- * fallback for bundler addresses that carry no native `assetChanges` entry.
44
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
45
45
  *
46
46
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
47
47
  * package's public API (only re-exported from the internal pipeline barrel).
48
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
49
- * registries define the restricted address set. Chains cataloging neither are
50
- * skipped (a `logger.warn` records the skip).
48
+ * @param params.chainId - Chain whose blue-sdk `bundles` registry defines the
49
+ * restricted address set. Chains without one are skipped (a `logger.warn`
50
+ * records the skip).
51
51
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
52
52
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
53
53
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -57,15 +57,12 @@ const DUST_THRESHOLD = 100n;
57
57
  * @returns Nothing. Returns silently when no restricted contract retains value
58
58
  * above `DUST_THRESHOLD`; otherwise throws.
59
59
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
60
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
60
+ * `bundles` contract exceeds `DUST_THRESHOLD` for any token.
61
61
  */
62
- function assertNoBundlerRetention(params) {
62
+ function assertNoBundlesRetention(params) {
63
63
  const { chainId, transfers, assetChanges, logger } = params;
64
- // Restricted address set: the full `bundler3` sub-registry (executor + every
65
- // adapter) plus the standalone `bundles` periphery contracts
66
- // (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`). Both families are
67
- // transient intermediaries that route user value and must never retain it, so
68
- // both are guarded identically.
64
+ // Standalone bundles contracts are transient intermediaries that route user
65
+ // value and must never retain it.
69
66
  let addresses;
70
67
  try {
71
68
  addresses = (0, blue_sdk_1.getChainAddresses)(chainId);
@@ -82,18 +79,14 @@ function assertNoBundlerRetention(params) {
82
79
  throw error;
83
80
  }
84
81
  const restrictedAddresses = new Set();
85
- if (addresses.bundler3) {
86
- for (const addr of Object.values(addresses.bundler3).filter(morpho_ts_1.isDefined))
87
- restrictedAddresses.add((0, viem_1.getAddress)(addr));
88
- }
89
82
  if (addresses.bundles) {
90
83
  for (const addr of Object.values(addresses.bundles).filter(morpho_ts_1.isDefined))
91
84
  restrictedAddresses.add((0, viem_1.getAddress)(addr));
92
85
  }
93
86
  if (restrictedAddresses.size === 0) {
94
- // blue-sdk knows the chain but cataloged neither bundler3 nor bundles for
95
- // it. Treat the same as UnsupportedChainIdError — retention check skipped.
96
- logger?.warn("Chain known to blue-sdk but has no bundler3 or bundles config, retention check skipped", { chainId });
87
+ // blue-sdk knows the chain but cataloged no bundles contracts. Treat the
88
+ // same as UnsupportedChainIdError — retention check skipped.
89
+ logger?.warn("Chain known to blue-sdk but has no bundles config, retention check skipped", { chainId });
97
90
  return;
98
91
  }
99
92
  // Map keyed by (address, token) → structured entry. Avoids string parse-back.
@@ -110,8 +103,8 @@ function assertNoBundlerRetention(params) {
110
103
  }
111
104
  };
112
105
  // Native ETH from `assetChanges` (authoritative, cross-backend). Track which
113
- // bundlers already have a native entry here so the transfer-log pass below
114
- // does not re-add the same native move on `eth_simulateV1`.
106
+ // restricted contracts already have a native entry here so the transfer-log
107
+ // pass below does not re-add the same native move on `eth_simulateV1`.
115
108
  const nativeFromAssetChanges = new Set();
116
109
  for (const { account, changes } of assetChanges) {
117
110
  if (!restrictedAddresses.has(account))
@@ -124,9 +117,9 @@ function assertNoBundlerRetention(params) {
124
117
  }
125
118
  }
126
119
  // ERC20 / WETH9 retention from transfer logs. Native ETH transfers (the
127
- // `eth_simulateV1` synthetic sentinel) only backfill a bundler endpoint that
128
- // is absent from `nativeFromAssetChanges`, so native is never summed on top
129
- // of the `assetChanges` value already recorded for that address.
120
+ // `eth_simulateV1` synthetic sentinel) only backfill a restricted endpoint
121
+ // that is absent from `nativeFromAssetChanges`, so native is never summed on
122
+ // top of the `assetChanges` value already recorded for that address.
130
123
  const usesAssetChangeNative = (t, addr) => t.token === viem_1.ethAddress && nativeFromAssetChanges.has(addr.toLowerCase());
131
124
  for (const t of transfers) {
132
125
  if (restrictedAddresses.has(t.to) && !usesAssetChangeNative(t, t.to))
@@ -137,7 +130,7 @@ function assertNoBundlerRetention(params) {
137
130
  const entries = [...flow.values()];
138
131
  const drained = entries.filter((e) => e.netChange < -DUST_THRESHOLD);
139
132
  if (drained.length > 0) {
140
- logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundler or bundles contract", {
133
+ logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundles contract", {
141
134
  changes: drained.map((e) => ({
142
135
  address: e.address,
143
136
  token: e.token,
@@ -147,7 +140,7 @@ function assertNoBundlerRetention(params) {
147
140
  }
148
141
  const retained = entries.filter((e) => e.netChange > DUST_THRESHOLD);
149
142
  if (retained.length > 0) {
150
- throw new errors_js_1.BlacklistViolationError("Simulation detected asset transfers retained by restricted bundler or bundles contracts", retained.map((e) => ({
143
+ throw new errors_js_1.BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
151
144
  address: e.address,
152
145
  token: e.token,
153
146
  netRetained: e.netChange.toString(),
@@ -1,4 +1,4 @@
1
1
  export { buildSimulationTxs } from "./build-simulation-txs.js";
2
- export { assertNoBundlerRetention } from "./bundler-retention.js";
2
+ export { assertNoBundlesRetention } from "./bundles-retention.js";
3
3
  export { executeSimulation } from "./execute-simulation.js";
4
4
  export { validateInput } from "./validate-input.js";
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateInput = exports.executeSimulation = exports.assertNoBundlerRetention = exports.buildSimulationTxs = void 0;
3
+ exports.validateInput = exports.executeSimulation = exports.assertNoBundlesRetention = exports.buildSimulationTxs = void 0;
4
4
  var build_simulation_txs_js_1 = require("./build-simulation-txs.js");
5
5
  Object.defineProperty(exports, "buildSimulationTxs", { enumerable: true, get: function () { return build_simulation_txs_js_1.buildSimulationTxs; } });
6
- var bundler_retention_js_1 = require("./bundler-retention.js");
7
- Object.defineProperty(exports, "assertNoBundlerRetention", { enumerable: true, get: function () { return bundler_retention_js_1.assertNoBundlerRetention; } });
6
+ var bundles_retention_js_1 = require("./bundles-retention.js");
7
+ Object.defineProperty(exports, "assertNoBundlesRetention", { enumerable: true, get: function () { return bundles_retention_js_1.assertNoBundlesRetention; } });
8
8
  var execute_simulation_js_1 = require("./execute-simulation.js");
9
9
  Object.defineProperty(exports, "executeSimulation", { enumerable: true, get: function () { return execute_simulation_js_1.executeSimulation; } });
10
10
  var validate_input_js_1 = require("./validate-input.js");
@@ -7,7 +7,7 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
7
7
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
8
8
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
9
9
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
10
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
10
+ * by standalone `bundles` periphery contracts → returns the full result
11
11
  * set. The caller reads whichever fields they need:
12
12
  *
13
13
  * - `transfers` → user-facing preview / server-side verification.
@@ -34,8 +34,8 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
34
34
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
35
35
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
36
36
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
37
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
38
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
37
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
38
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
39
39
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
40
40
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
41
41
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -13,7 +13,7 @@ const index_js_2 = require("./pipeline/index.js");
13
13
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
14
14
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
15
15
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
16
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
16
+ * by standalone `bundles` periphery contracts → returns the full result
17
17
  * set. The caller reads whichever fields they need:
18
18
  *
19
19
  * - `transfers` → user-facing preview / server-side verification.
@@ -40,8 +40,8 @@ const index_js_2 = require("./pipeline/index.js");
40
40
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
41
41
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
42
42
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
43
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
44
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
43
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
44
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
45
45
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
46
46
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
47
47
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -88,7 +88,7 @@ async function simulate(config, params) {
88
88
  wNative,
89
89
  logger: config.logger,
90
90
  });
91
- (0, index_js_2.assertNoBundlerRetention)({
91
+ (0, index_js_2.assertNoBundlesRetention)({
92
92
  chainId: params.chainId,
93
93
  transfers,
94
94
  assetChanges: result.assetChanges,
@@ -19,7 +19,7 @@ interface RetainedAsset {
19
19
  token: string | undefined;
20
20
  netRetained: string;
21
21
  }
22
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
22
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
23
23
  export declare class BlacklistViolationError extends SimulationPackageError {
24
24
  readonly assetChanges?: RetainedAsset[] | undefined;
25
25
  readonly code = "BLACKLIST_ERROR";
package/lib/esm/errors.js CHANGED
@@ -19,7 +19,7 @@ export class SimulationRevertedError extends SimulationPackageError {
19
19
  this.details = details;
20
20
  }
21
21
  }
22
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
22
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
23
23
  export class BlacklistViolationError extends SimulationPackageError {
24
24
  assetChanges;
25
25
  code = "BLACKLIST_ERROR";
@@ -13,7 +13,7 @@ export interface AssetChangeEntry {
13
13
  * any real token address. `eth_simulateV1` synthesizes native moves from the
14
14
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
15
15
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
16
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
16
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
17
17
  * sentinel would land on a separate map key and silently escape retention checks.
18
18
  * This is the single source of truth for token normalization shared by the
19
19
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -4,7 +4,7 @@ import { ethAddress, getAddress } from "viem";
4
4
  * any real token address. `eth_simulateV1` synthesizes native moves from the
5
5
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
6
6
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
7
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
7
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
8
8
  * sentinel would land on a separate map key and silently escape retention checks.
9
9
  * This is the single source of truth for token normalization shared by the
10
10
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -36,7 +36,7 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
36
36
  * atomically inside a single call frame. A **non-canonical wrapped-native**
37
37
  * that splits these emissions across two txs in the same bundle would leave
38
38
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
39
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
39
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
40
40
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
41
41
  * `warn` so the assumption break is observable before it reaches retention.
42
42
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
@@ -41,7 +41,7 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
41
41
  * atomically inside a single call frame. A **non-canonical wrapped-native**
42
42
  * that splits these emissions across two txs in the same bundle would leave
43
43
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
44
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
44
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
45
45
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
46
46
  * `warn` so the assumption break is observable before it reaches retention.
47
47
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
@@ -1,22 +1,22 @@
1
1
  import type { AccountAssetChanges, SimulationLogger, Transfer } from "../../types.js";
2
- interface AssertNoBundlerRetentionParams {
2
+ interface AssertNoBundlesRetentionParams {
3
3
  chainId: number;
4
4
  transfers: Transfer[];
5
5
  assetChanges: readonly AccountAssetChanges[];
6
6
  logger?: SimulationLogger;
7
7
  }
8
8
  /**
9
- * Assert that no value is retained by restricted contract addresses — the
10
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
11
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
9
+ * Assert that no value is retained by standalone `bundles` periphery contracts
10
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
12
11
  *
13
12
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
14
13
  * These contracts legitimately receive tokens as an intermediary (user →
15
- * bundler → vault), so gross inbound would fire false positives. We only block
16
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
17
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
18
- * state-overridden balance. That is useful telemetry, but not current-bundle
19
- * retention, so it is warned instead of raising a blacklist violation.
14
+ * bundles contract → vault), so gross inbound would fire false positives. We
15
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
16
+ * restricted address. Negative net flow means the simulated bundle sweeps a
17
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
18
+ * current-bundle retention, so it is warned instead of raising a blacklist
19
+ * violation.
20
20
  *
21
21
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
22
22
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -25,19 +25,19 @@ interface AssertNoBundlerRetentionParams {
25
25
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
26
26
  * truth for native value: Tenderly derives it from its trace, and
27
27
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
28
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
29
- * Tenderly path (Cantina finding 1440).
28
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
29
+ * on the Tenderly path (Cantina finding 1440).
30
30
  *
31
31
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
32
32
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
33
33
  * `assetChanges` derived from it — native transfer logs are only used as a
34
- * fallback for bundler addresses that carry no native `assetChanges` entry.
34
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
35
35
  *
36
36
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
37
37
  * package's public API (only re-exported from the internal pipeline barrel).
38
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
39
- * registries define the restricted address set. Chains cataloging neither are
40
- * skipped (a `logger.warn` records the skip).
38
+ * @param params.chainId - Chain whose blue-sdk `bundles` registry defines the
39
+ * restricted address set. Chains without one are skipped (a `logger.warn`
40
+ * records the skip).
41
41
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
42
42
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
43
43
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -47,7 +47,7 @@ interface AssertNoBundlerRetentionParams {
47
47
  * @returns Nothing. Returns silently when no restricted contract retains value
48
48
  * above `DUST_THRESHOLD`; otherwise throws.
49
49
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
50
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
50
+ * `bundles` contract exceeds `DUST_THRESHOLD` for any token.
51
51
  */
52
- export declare function assertNoBundlerRetention(params: AssertNoBundlerRetentionParams): void;
52
+ export declare function assertNoBundlesRetention(params: AssertNoBundlesRetentionParams): void;
53
53
  export {};
@@ -3,27 +3,27 @@ import { isDefined } from "@morpho-org/morpho-ts";
3
3
  import { ethAddress, getAddress } from "viem";
4
4
  import { BlacklistViolationError } from "../../errors.js";
5
5
  /**
6
- * Dust tolerance for bundler retention, in raw token units.
6
+ * Dust tolerance for standalone bundles retention, in raw token units.
7
7
  *
8
8
  * 100 units is negligible for 18-decimal tokens (~1e-16). For low-decimal
9
9
  * tokens (e.g., 2-decimal stablecoins) this represents ~1.00 token — worth
10
10
  * keeping in mind. The threshold is intentionally token-agnostic: its purpose
11
- * is to absorb rounding noise from bundler3 routing, not to define an
12
- * acceptable retention amount.
11
+ * is to absorb routing rounding noise, not to define an acceptable retention
12
+ * amount.
13
13
  */
14
14
  const DUST_THRESHOLD = 100n;
15
15
  /**
16
- * Assert that no value is retained by restricted contract addresses — the
17
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
18
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
16
+ * Assert that no value is retained by standalone `bundles` periphery contracts
17
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
19
18
  *
20
19
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
21
20
  * These contracts legitimately receive tokens as an intermediary (user →
22
- * bundler → vault), so gross inbound would fire false positives. We only block
23
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
24
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
25
- * state-overridden balance. That is useful telemetry, but not current-bundle
26
- * retention, so it is warned instead of raising a blacklist violation.
21
+ * bundles contract → vault), so gross inbound would fire false positives. We
22
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
23
+ * restricted address. Negative net flow means the simulated bundle sweeps a
24
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
25
+ * current-bundle retention, so it is warned instead of raising a blacklist
26
+ * violation.
27
27
  *
28
28
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
29
29
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -32,19 +32,19 @@ const DUST_THRESHOLD = 100n;
32
32
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
33
33
  * truth for native value: Tenderly derives it from its trace, and
34
34
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
35
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
36
- * Tenderly path (Cantina finding 1440).
35
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
36
+ * on the Tenderly path (Cantina finding 1440).
37
37
  *
38
38
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
39
39
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
40
40
  * `assetChanges` derived from it — native transfer logs are only used as a
41
- * fallback for bundler addresses that carry no native `assetChanges` entry.
41
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
42
42
  *
43
43
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
44
44
  * package's public API (only re-exported from the internal pipeline barrel).
45
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
46
- * registries define the restricted address set. Chains cataloging neither are
47
- * skipped (a `logger.warn` records the skip).
45
+ * @param params.chainId - Chain whose blue-sdk `bundles` registry defines the
46
+ * restricted address set. Chains without one are skipped (a `logger.warn`
47
+ * records the skip).
48
48
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
49
49
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
50
50
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -54,15 +54,12 @@ const DUST_THRESHOLD = 100n;
54
54
  * @returns Nothing. Returns silently when no restricted contract retains value
55
55
  * above `DUST_THRESHOLD`; otherwise throws.
56
56
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
57
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
57
+ * `bundles` contract exceeds `DUST_THRESHOLD` for any token.
58
58
  */
59
- export function assertNoBundlerRetention(params) {
59
+ export function assertNoBundlesRetention(params) {
60
60
  const { chainId, transfers, assetChanges, logger } = params;
61
- // Restricted address set: the full `bundler3` sub-registry (executor + every
62
- // adapter) plus the standalone `bundles` periphery contracts
63
- // (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`). Both families are
64
- // transient intermediaries that route user value and must never retain it, so
65
- // both are guarded identically.
61
+ // Standalone bundles contracts are transient intermediaries that route user
62
+ // value and must never retain it.
66
63
  let addresses;
67
64
  try {
68
65
  addresses = getChainAddresses(chainId);
@@ -79,18 +76,14 @@ export function assertNoBundlerRetention(params) {
79
76
  throw error;
80
77
  }
81
78
  const restrictedAddresses = new Set();
82
- if (addresses.bundler3) {
83
- for (const addr of Object.values(addresses.bundler3).filter(isDefined))
84
- restrictedAddresses.add(getAddress(addr));
85
- }
86
79
  if (addresses.bundles) {
87
80
  for (const addr of Object.values(addresses.bundles).filter(isDefined))
88
81
  restrictedAddresses.add(getAddress(addr));
89
82
  }
90
83
  if (restrictedAddresses.size === 0) {
91
- // blue-sdk knows the chain but cataloged neither bundler3 nor bundles for
92
- // it. Treat the same as UnsupportedChainIdError — retention check skipped.
93
- logger?.warn("Chain known to blue-sdk but has no bundler3 or bundles config, retention check skipped", { chainId });
84
+ // blue-sdk knows the chain but cataloged no bundles contracts. Treat the
85
+ // same as UnsupportedChainIdError — retention check skipped.
86
+ logger?.warn("Chain known to blue-sdk but has no bundles config, retention check skipped", { chainId });
94
87
  return;
95
88
  }
96
89
  // Map keyed by (address, token) → structured entry. Avoids string parse-back.
@@ -107,8 +100,8 @@ export function assertNoBundlerRetention(params) {
107
100
  }
108
101
  };
109
102
  // Native ETH from `assetChanges` (authoritative, cross-backend). Track which
110
- // bundlers already have a native entry here so the transfer-log pass below
111
- // does not re-add the same native move on `eth_simulateV1`.
103
+ // restricted contracts already have a native entry here so the transfer-log
104
+ // pass below does not re-add the same native move on `eth_simulateV1`.
112
105
  const nativeFromAssetChanges = new Set();
113
106
  for (const { account, changes } of assetChanges) {
114
107
  if (!restrictedAddresses.has(account))
@@ -121,9 +114,9 @@ export function assertNoBundlerRetention(params) {
121
114
  }
122
115
  }
123
116
  // ERC20 / WETH9 retention from transfer logs. Native ETH transfers (the
124
- // `eth_simulateV1` synthetic sentinel) only backfill a bundler endpoint that
125
- // is absent from `nativeFromAssetChanges`, so native is never summed on top
126
- // of the `assetChanges` value already recorded for that address.
117
+ // `eth_simulateV1` synthetic sentinel) only backfill a restricted endpoint
118
+ // that is absent from `nativeFromAssetChanges`, so native is never summed on
119
+ // top of the `assetChanges` value already recorded for that address.
127
120
  const usesAssetChangeNative = (t, addr) => t.token === ethAddress && nativeFromAssetChanges.has(addr.toLowerCase());
128
121
  for (const t of transfers) {
129
122
  if (restrictedAddresses.has(t.to) && !usesAssetChangeNative(t, t.to))
@@ -134,7 +127,7 @@ export function assertNoBundlerRetention(params) {
134
127
  const entries = [...flow.values()];
135
128
  const drained = entries.filter((e) => e.netChange < -DUST_THRESHOLD);
136
129
  if (drained.length > 0) {
137
- logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundler or bundles contract", {
130
+ logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundles contract", {
138
131
  changes: drained.map((e) => ({
139
132
  address: e.address,
140
133
  token: e.token,
@@ -144,7 +137,7 @@ export function assertNoBundlerRetention(params) {
144
137
  }
145
138
  const retained = entries.filter((e) => e.netChange > DUST_THRESHOLD);
146
139
  if (retained.length > 0) {
147
- throw new BlacklistViolationError("Simulation detected asset transfers retained by restricted bundler or bundles contracts", retained.map((e) => ({
140
+ throw new BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
148
141
  address: e.address,
149
142
  token: e.token,
150
143
  netRetained: e.netChange.toString(),
@@ -1,4 +1,4 @@
1
1
  export { buildSimulationTxs } from "./build-simulation-txs.js";
2
- export { assertNoBundlerRetention } from "./bundler-retention.js";
2
+ export { assertNoBundlesRetention } from "./bundles-retention.js";
3
3
  export { executeSimulation } from "./execute-simulation.js";
4
4
  export { validateInput } from "./validate-input.js";
@@ -1,4 +1,4 @@
1
1
  export { buildSimulationTxs } from "./build-simulation-txs.js";
2
- export { assertNoBundlerRetention } from "./bundler-retention.js";
2
+ export { assertNoBundlesRetention } from "./bundles-retention.js";
3
3
  export { executeSimulation } from "./execute-simulation.js";
4
4
  export { validateInput } from "./validate-input.js";
@@ -7,7 +7,7 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
7
7
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
8
8
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
9
9
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
10
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
10
+ * by standalone `bundles` periphery contracts → returns the full result
11
11
  * set. The caller reads whichever fields they need:
12
12
  *
13
13
  * - `transfers` → user-facing preview / server-side verification.
@@ -34,8 +34,8 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
34
34
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
35
35
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
36
36
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
37
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
38
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
37
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
38
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
39
39
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
40
40
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
41
41
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -1,7 +1,7 @@
1
1
  import { _try, getChainAddresses, UnsupportedChainIdError, } from "@morpho-org/blue-sdk";
2
2
  import { ExternalServiceError } from "../errors.js";
3
3
  import { parseTransfers } from "./parsing/index.js";
4
- import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, validateInput, } from "./pipeline/index.js";
4
+ import { assertNoBundlesRetention, buildSimulationTxs, executeSimulation, validateInput, } from "./pipeline/index.js";
5
5
  /**
6
6
  * Simulate a bundle of EVM transactions.
7
7
  *
@@ -10,7 +10,7 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
10
10
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
11
11
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
12
12
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
13
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
13
+ * by standalone `bundles` periphery contracts → returns the full result
14
14
  * set. The caller reads whichever fields they need:
15
15
  *
16
16
  * - `transfers` → user-facing preview / server-side verification.
@@ -37,8 +37,8 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
37
37
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
38
38
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
39
39
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
40
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
41
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
40
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
41
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
42
42
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
43
43
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
44
44
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -85,7 +85,7 @@ export async function simulate(config, params) {
85
85
  wNative,
86
86
  logger: config.logger,
87
87
  });
88
- assertNoBundlerRetention({
88
+ assertNoBundlesRetention({
89
89
  chainId: params.chainId,
90
90
  transfers,
91
91
  assetChanges: result.assetChanges,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morpho-org/evm-simulation",
3
3
  "description": "EVM simulation engine for Morpho transactions.",
4
- "version": "4.1.9",
4
+ "version": "4.2.0-next.1",
5
5
  "author": "Morpho Association <contact@morpho.org>",
6
6
  "contributors": [
7
7
  "Rubilmax <rmilon@gmail.com>"
@@ -25,8 +25,8 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "zod": "^4.4.3",
28
- "@morpho-org/blue-sdk": "^6.10.0",
29
- "@morpho-org/morpho-ts": "^2.14.0"
28
+ "@morpho-org/blue-sdk": "^7.0.0-next.1",
29
+ "@morpho-org/morpho-ts": "^3.0.0-next.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "viem": "^2.0.0"