@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.
- package/lib/cjs/errors.d.ts +1 -1
- package/lib/cjs/errors.js +1 -1
- package/lib/cjs/simulate/asset-changes.d.ts +1 -1
- package/lib/cjs/simulate/asset-changes.js +1 -1
- package/lib/cjs/simulate/parsing/transfers.d.ts +1 -1
- package/lib/cjs/simulate/parsing/transfers.js +1 -1
- package/lib/cjs/simulate/pipeline/{bundler-retention.d.ts → bundles-retention.d.ts} +17 -17
- package/lib/cjs/simulate/pipeline/{bundler-retention.js → bundles-retention.js} +32 -39
- package/lib/cjs/simulate/pipeline/index.d.ts +1 -1
- package/lib/cjs/simulate/pipeline/index.js +3 -3
- package/lib/cjs/simulate/simulate.d.ts +3 -3
- package/lib/cjs/simulate/simulate.js +4 -4
- package/lib/esm/errors.d.ts +1 -1
- package/lib/esm/errors.js +1 -1
- package/lib/esm/simulate/asset-changes.d.ts +1 -1
- package/lib/esm/simulate/asset-changes.js +1 -1
- package/lib/esm/simulate/parsing/transfers.d.ts +1 -1
- package/lib/esm/simulate/parsing/transfers.js +1 -1
- package/lib/esm/simulate/pipeline/{bundler-retention.d.ts → bundles-retention.d.ts} +17 -17
- package/lib/esm/simulate/pipeline/{bundler-retention.js → bundles-retention.js} +31 -38
- package/lib/esm/simulate/pipeline/index.d.ts +1 -1
- package/lib/esm/simulate/pipeline/index.js +1 -1
- package/lib/esm/simulate/simulate.d.ts +3 -3
- package/lib/esm/simulate/simulate.js +5 -5
- package/package.json +3 -3
package/lib/cjs/errors.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ interface RetainedAsset {
|
|
|
19
19
|
token: string | undefined;
|
|
20
20
|
netRetained: string;
|
|
21
21
|
}
|
|
22
|
-
/** Funds would flow to restricted
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
|
10
|
-
* `
|
|
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
|
-
*
|
|
16
|
-
* positive net flow above `DUST_THRESHOLD` — value stuck in a
|
|
17
|
-
* address. Negative net flow means the simulated bundle sweeps a
|
|
18
|
-
* state-overridden balance. That is useful telemetry, but not
|
|
19
|
-
* retention, so it is warned instead of raising a blacklist
|
|
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
|
|
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
|
|
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 `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
-
* `
|
|
50
|
+
* `bundles` contract exceeds `DUST_THRESHOLD` for any token.
|
|
51
51
|
*/
|
|
52
|
-
export declare function
|
|
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.
|
|
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
|
|
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
|
|
15
|
-
*
|
|
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
|
|
20
|
-
* `
|
|
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
|
-
*
|
|
26
|
-
* positive net flow above `DUST_THRESHOLD` — value stuck in a
|
|
27
|
-
* address. Negative net flow means the simulated bundle sweeps a
|
|
28
|
-
* state-overridden balance. That is useful telemetry, but not
|
|
29
|
-
* retention, so it is warned instead of raising a blacklist
|
|
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
|
|
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
|
|
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 `
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
-
* `
|
|
60
|
+
* `bundles` contract exceeds `DUST_THRESHOLD` for any token.
|
|
61
61
|
*/
|
|
62
|
-
function
|
|
62
|
+
function assertNoBundlesRetention(params) {
|
|
63
63
|
const { chainId, transfers, assetChanges, logger } = params;
|
|
64
|
-
//
|
|
65
|
-
//
|
|
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
|
|
95
|
-
//
|
|
96
|
-
logger?.warn("Chain known to blue-sdk but has no
|
|
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
|
-
//
|
|
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
|
|
128
|
-
// is absent from `nativeFromAssetChanges`, so native is never summed on
|
|
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
|
|
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
|
|
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 {
|
|
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.
|
|
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
|
|
7
|
-
Object.defineProperty(exports, "
|
|
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
|
|
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
|
|
38
|
-
*
|
|
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
|
|
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
|
|
44
|
-
*
|
|
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.
|
|
91
|
+
(0, index_js_2.assertNoBundlesRetention)({
|
|
92
92
|
chainId: params.chainId,
|
|
93
93
|
transfers,
|
|
94
94
|
assetChanges: result.assetChanges,
|
package/lib/esm/errors.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ interface RetainedAsset {
|
|
|
19
19
|
token: string | undefined;
|
|
20
20
|
netRetained: string;
|
|
21
21
|
}
|
|
22
|
-
/** Funds would flow to restricted
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
|
10
|
-
* `
|
|
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
|
-
*
|
|
16
|
-
* positive net flow above `DUST_THRESHOLD` — value stuck in a
|
|
17
|
-
* address. Negative net flow means the simulated bundle sweeps a
|
|
18
|
-
* state-overridden balance. That is useful telemetry, but not
|
|
19
|
-
* retention, so it is warned instead of raising a blacklist
|
|
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
|
|
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
|
|
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 `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
-
* `
|
|
50
|
+
* `bundles` contract exceeds `DUST_THRESHOLD` for any token.
|
|
51
51
|
*/
|
|
52
|
-
export declare function
|
|
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
|
|
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
|
|
12
|
-
*
|
|
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
|
|
17
|
-
* `
|
|
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
|
-
*
|
|
23
|
-
* positive net flow above `DUST_THRESHOLD` — value stuck in a
|
|
24
|
-
* address. Negative net flow means the simulated bundle sweeps a
|
|
25
|
-
* state-overridden balance. That is useful telemetry, but not
|
|
26
|
-
* retention, so it is warned instead of raising a blacklist
|
|
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
|
|
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
|
|
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 `
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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
|
-
* `
|
|
57
|
+
* `bundles` contract exceeds `DUST_THRESHOLD` for any token.
|
|
58
58
|
*/
|
|
59
|
-
export function
|
|
59
|
+
export function assertNoBundlesRetention(params) {
|
|
60
60
|
const { chainId, transfers, assetChanges, logger } = params;
|
|
61
|
-
//
|
|
62
|
-
//
|
|
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
|
|
92
|
-
//
|
|
93
|
-
logger?.warn("Chain known to blue-sdk but has no
|
|
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
|
-
//
|
|
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
|
|
125
|
-
// is absent from `nativeFromAssetChanges`, so native is never summed on
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
38
|
-
*
|
|
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 {
|
|
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
|
|
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
|
|
41
|
-
*
|
|
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
|
-
|
|
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
|
|
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": "^
|
|
29
|
-
"@morpho-org/morpho-ts": "^
|
|
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"
|