@morpho-org/evm-simulation 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -17
- package/lib/cjs/authorizations.d.ts +95 -0
- package/lib/cjs/authorizations.js +2 -0
- package/lib/cjs/errors.d.ts +207 -13
- package/lib/cjs/errors.js +197 -13
- package/lib/cjs/index.d.ts +10 -2
- package/lib/cjs/index.js +19 -3
- package/lib/cjs/limits.d.ts +112 -0
- package/lib/cjs/limits.js +50 -0
- package/lib/cjs/params.d.ts +25 -0
- package/lib/cjs/params.js +5 -0
- package/lib/cjs/requirements/index.d.ts +1 -0
- package/lib/cjs/requirements/index.js +5 -0
- package/lib/cjs/requirements/requirements.d.ts +64 -0
- package/lib/cjs/requirements/requirements.js +149 -0
- package/lib/cjs/result.d.ts +49 -0
- package/lib/cjs/result.js +2 -0
- package/lib/cjs/simulate/asset-changes.d.ts +7 -5
- package/lib/cjs/simulate/asset-changes.js +6 -5
- package/lib/cjs/simulate/backends/client.d.ts +16 -0
- package/lib/cjs/simulate/backends/client.js +27 -0
- package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +66 -20
- package/lib/cjs/simulate/backends/eth-simulate-v1.js +172 -98
- package/lib/cjs/simulate/backends/index.d.ts +1 -2
- package/lib/cjs/simulate/backends/index.js +3 -4
- package/lib/cjs/simulate/backends/parse-response.d.ts +69 -0
- package/lib/cjs/simulate/backends/parse-response.js +168 -0
- package/lib/cjs/simulate/backends/resolve-assets.d.ts +26 -0
- package/lib/cjs/simulate/backends/resolve-assets.js +111 -0
- package/lib/cjs/simulate/backends/resolve-pinned-block.d.ts +33 -0
- package/lib/cjs/simulate/backends/resolve-pinned-block.js +46 -0
- package/lib/cjs/simulate/context.d.ts +14 -0
- package/lib/cjs/simulate/context.js +2 -0
- package/lib/cjs/simulate/measurement-plan.d.ts +29 -0
- package/lib/cjs/simulate/measurement-plan.js +115 -0
- package/lib/cjs/simulate/parsing/transfers.d.ts +4 -3
- package/lib/cjs/simulate/parsing/transfers.js +4 -6
- package/lib/cjs/simulate/pipeline/bundles-retention.d.ts +7 -10
- package/lib/cjs/simulate/pipeline/bundles-retention.js +11 -14
- package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +5 -11
- package/lib/cjs/simulate/pipeline/resolve-chain.js +5 -10
- package/lib/cjs/simulate/plan/plan-execution.d.ts +58 -0
- package/lib/cjs/simulate/plan/plan-execution.js +75 -0
- package/lib/cjs/simulate/prepare.d.ts +30 -0
- package/lib/cjs/simulate/prepare.js +80 -0
- package/lib/cjs/simulate/request/index.d.ts +1 -0
- package/lib/cjs/simulate/request/index.js +5 -0
- package/lib/cjs/simulate/request/parse-request.d.ts +44 -0
- package/lib/cjs/simulate/request/parse-request.js +759 -0
- package/lib/cjs/simulate/result.d.ts +19 -0
- package/lib/cjs/simulate/result.js +29 -0
- package/lib/cjs/simulate/run-simulation.d.ts +18 -0
- package/lib/cjs/simulate/run-simulation.js +151 -0
- package/lib/cjs/simulate/simulate.d.ts +100 -50
- package/lib/cjs/simulate/simulate.js +99 -81
- package/lib/cjs/simulate/slippage.d.ts +18 -0
- package/lib/cjs/simulate/slippage.js +41 -0
- package/lib/cjs/simulate/state/contract.d.ts +19 -0
- package/lib/cjs/simulate/state/contract.js +2 -0
- package/lib/cjs/simulate/state/read-state.d.ts +60 -0
- package/lib/cjs/simulate/state/read-state.js +148 -0
- package/lib/cjs/simulate/verify-slippage.d.ts +24 -0
- package/lib/cjs/simulate/verify-slippage.js +85 -0
- package/lib/cjs/types.d.ts +35 -95
- package/lib/esm/authorizations.d.ts +95 -0
- package/lib/esm/authorizations.js +1 -0
- package/lib/esm/errors.d.ts +207 -13
- package/lib/esm/errors.js +189 -12
- package/lib/esm/index.d.ts +10 -2
- package/lib/esm/index.js +4 -1
- package/lib/esm/limits.d.ts +112 -0
- package/lib/esm/limits.js +47 -0
- package/lib/esm/params.d.ts +25 -0
- package/lib/esm/params.js +2 -0
- package/lib/esm/requirements/index.d.ts +1 -0
- package/lib/esm/requirements/index.js +1 -0
- package/lib/esm/requirements/requirements.d.ts +64 -0
- package/lib/esm/requirements/requirements.js +146 -0
- package/lib/esm/result.d.ts +49 -0
- package/lib/esm/result.js +1 -0
- package/lib/esm/simulate/asset-changes.d.ts +7 -5
- package/lib/esm/simulate/asset-changes.js +6 -5
- package/lib/esm/simulate/backends/client.d.ts +16 -0
- package/lib/esm/simulate/backends/client.js +24 -0
- package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +66 -20
- package/lib/esm/simulate/backends/eth-simulate-v1.js +170 -99
- package/lib/esm/simulate/backends/index.d.ts +1 -2
- package/lib/esm/simulate/backends/index.js +1 -2
- package/lib/esm/simulate/backends/parse-response.d.ts +69 -0
- package/lib/esm/simulate/backends/parse-response.js +165 -0
- package/lib/esm/simulate/backends/resolve-assets.d.ts +26 -0
- package/lib/esm/simulate/backends/resolve-assets.js +108 -0
- package/lib/esm/simulate/backends/resolve-pinned-block.d.ts +33 -0
- package/lib/esm/simulate/backends/resolve-pinned-block.js +43 -0
- package/lib/esm/simulate/context.d.ts +14 -0
- package/lib/esm/simulate/context.js +1 -0
- package/lib/esm/simulate/measurement-plan.d.ts +29 -0
- package/lib/esm/simulate/measurement-plan.js +112 -0
- package/lib/esm/simulate/parsing/transfers.d.ts +4 -3
- package/lib/esm/simulate/parsing/transfers.js +4 -6
- package/lib/esm/simulate/pipeline/bundles-retention.d.ts +7 -10
- package/lib/esm/simulate/pipeline/bundles-retention.js +11 -14
- package/lib/esm/simulate/pipeline/resolve-chain.d.ts +5 -11
- package/lib/esm/simulate/pipeline/resolve-chain.js +5 -10
- package/lib/esm/simulate/plan/plan-execution.d.ts +58 -0
- package/lib/esm/simulate/plan/plan-execution.js +72 -0
- package/lib/esm/simulate/prepare.d.ts +30 -0
- package/lib/esm/simulate/prepare.js +77 -0
- package/lib/esm/simulate/request/index.d.ts +1 -0
- package/lib/esm/simulate/request/index.js +1 -0
- package/lib/esm/simulate/request/parse-request.d.ts +44 -0
- package/lib/esm/simulate/request/parse-request.js +756 -0
- package/lib/esm/simulate/result.d.ts +19 -0
- package/lib/esm/simulate/result.js +26 -0
- package/lib/esm/simulate/run-simulation.d.ts +18 -0
- package/lib/esm/simulate/run-simulation.js +148 -0
- package/lib/esm/simulate/simulate.d.ts +100 -50
- package/lib/esm/simulate/simulate.js +99 -81
- package/lib/esm/simulate/slippage.d.ts +18 -0
- package/lib/esm/simulate/slippage.js +38 -0
- package/lib/esm/simulate/state/contract.d.ts +19 -0
- package/lib/esm/simulate/state/contract.js +1 -0
- package/lib/esm/simulate/state/read-state.d.ts +60 -0
- package/lib/esm/simulate/state/read-state.js +144 -0
- package/lib/esm/simulate/verify-slippage.d.ts +24 -0
- package/lib/esm/simulate/verify-slippage.js +82 -0
- package/lib/esm/types.d.ts +35 -95
- package/package.json +7 -6
- package/lib/cjs/simulate/authorizations/index.d.ts +0 -2
- package/lib/cjs/simulate/authorizations/index.js +0 -7
- package/lib/cjs/simulate/authorizations/resolve.d.ts +0 -9
- package/lib/cjs/simulate/authorizations/resolve.js +0 -29
- package/lib/cjs/simulate/authorizations/validate.d.ts +0 -6
- package/lib/cjs/simulate/authorizations/validate.js +0 -23
- package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +0 -23
- package/lib/cjs/simulate/backends/tenderly-rpc.js +0 -234
- package/lib/cjs/simulate/pipeline/build-simulation-txs.d.ts +0 -17
- package/lib/cjs/simulate/pipeline/build-simulation-txs.js +0 -50
- package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +0 -22
- package/lib/cjs/simulate/pipeline/execute-simulation.js +0 -82
- package/lib/cjs/simulate/pipeline/index.d.ts +0 -4
- package/lib/cjs/simulate/pipeline/index.js +0 -11
- package/lib/cjs/simulate/pipeline/validate-input.d.ts +0 -10
- package/lib/cjs/simulate/pipeline/validate-input.js +0 -55
- package/lib/esm/simulate/authorizations/index.d.ts +0 -2
- package/lib/esm/simulate/authorizations/index.js +0 -2
- package/lib/esm/simulate/authorizations/resolve.d.ts +0 -9
- package/lib/esm/simulate/authorizations/resolve.js +0 -26
- package/lib/esm/simulate/authorizations/validate.d.ts +0 -6
- package/lib/esm/simulate/authorizations/validate.js +0 -20
- package/lib/esm/simulate/backends/tenderly-rpc.d.ts +0 -23
- package/lib/esm/simulate/backends/tenderly-rpc.js +0 -231
- package/lib/esm/simulate/pipeline/build-simulation-txs.d.ts +0 -17
- package/lib/esm/simulate/pipeline/build-simulation-txs.js +0 -47
- package/lib/esm/simulate/pipeline/execute-simulation.d.ts +0 -22
- package/lib/esm/simulate/pipeline/execute-simulation.js +0 -79
- package/lib/esm/simulate/pipeline/index.d.ts +0 -4
- package/lib/esm/simulate/pipeline/index.js +0 -4
- package/lib/esm/simulate/pipeline/validate-input.d.ts +0 -10
- package/lib/esm/simulate/pipeline/validate-input.js +0 -52
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkSlippage = checkSlippage;
|
|
4
|
+
const morpho_ts_1 = require("@morpho-org/morpho-ts");
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
6
|
+
/**
|
|
7
|
+
* Compare observed amounts against the caller's quote and percentage tolerance.
|
|
8
|
+
* Minimum outputs round up and maximum inputs round down, so rounding never
|
|
9
|
+
* permits a deviation greater than the requested percentage.
|
|
10
|
+
* @param params - Parsed quote/tolerance, matching observations, and error context.
|
|
11
|
+
* @returns The checked quote and tolerance, deep-frozen.
|
|
12
|
+
* @throws {ConsumerLimitViolationError} When adverse slippage exceeds tolerance.
|
|
13
|
+
* @throws {MissingVerificationEvidenceError} When a quoted amount has no observation.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
function checkSlippage(params) {
|
|
17
|
+
const { limits, observed, context, debtShares = false } = params;
|
|
18
|
+
const quote = {};
|
|
19
|
+
for (const [field, output] of [
|
|
20
|
+
["assetsReceived", true],
|
|
21
|
+
["sharesMinted", !debtShares],
|
|
22
|
+
["assetsPaid", false],
|
|
23
|
+
["sharesBurned", debtShares],
|
|
24
|
+
]) {
|
|
25
|
+
const expected = limits.quote[field];
|
|
26
|
+
if (expected === undefined)
|
|
27
|
+
continue;
|
|
28
|
+
const amount = observed[field];
|
|
29
|
+
if (amount === undefined) {
|
|
30
|
+
throw new errors_js_1.MissingVerificationEvidenceError(`Missing observation for "${field}". Supply the matching asset or position.`, { context: { ...context, field } });
|
|
31
|
+
}
|
|
32
|
+
const bound = output
|
|
33
|
+
? morpho_ts_1.MathLib.wMulUp(expected, morpho_ts_1.MathLib.WAD - limits.slippageTolerance)
|
|
34
|
+
: morpho_ts_1.MathLib.wMulDown(expected, morpho_ts_1.MathLib.WAD + limits.slippageTolerance);
|
|
35
|
+
if (output ? amount < bound : amount > bound) {
|
|
36
|
+
throw new errors_js_1.ConsumerLimitViolationError(`Slippage for "${field}" exceeds tolerance "${limits.slippageTolerance}" against quote "${expected}"; allowed bound "${bound}", got "${amount}". Adjust the transaction or tolerance.`, { context: { ...context, field, expected: bound, observed: amount } });
|
|
37
|
+
}
|
|
38
|
+
quote[field] = expected;
|
|
39
|
+
}
|
|
40
|
+
return (0, morpho_ts_1.deepFreeze)({ quote, slippageTolerance: limits.slippageTolerance });
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MarketId } from "@morpho-org/blue-sdk";
|
|
2
|
+
import type { Address, Hex } from "viem";
|
|
3
|
+
/** Minimal view call replayed before and after the user bundle. @internal */
|
|
4
|
+
export type StateRead = {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly to: Address;
|
|
7
|
+
readonly data: Hex;
|
|
8
|
+
} & ({
|
|
9
|
+
readonly kind: "erc20.balance";
|
|
10
|
+
readonly token: Address;
|
|
11
|
+
readonly account: Address;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "morpho.position";
|
|
14
|
+
readonly morpho: Address;
|
|
15
|
+
readonly marketId: MarketId;
|
|
16
|
+
readonly owner: Address;
|
|
17
|
+
});
|
|
18
|
+
/** Phase at which a slippage observation is read. @internal */
|
|
19
|
+
export type ReadPhase = "before" | "after";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Address, type Hex } from "viem";
|
|
2
|
+
import type { OperationLimit, SlippageQuote } from "../../limits.js";
|
|
3
|
+
import type { SimulationMode } from "../../params.js";
|
|
4
|
+
import type { ResolvedSlippageOperation } from "../backends/resolve-assets.js";
|
|
5
|
+
import type { StateRead } from "./contract.js";
|
|
6
|
+
/** One quoted amount's observation source. @internal */
|
|
7
|
+
type SlippageMeasurement = {
|
|
8
|
+
readonly field: keyof SlippageQuote;
|
|
9
|
+
} & ({
|
|
10
|
+
readonly type: "native";
|
|
11
|
+
readonly account: Address;
|
|
12
|
+
} | {
|
|
13
|
+
readonly type: "balance";
|
|
14
|
+
readonly readId: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly type: "position";
|
|
17
|
+
readonly readId: string;
|
|
18
|
+
readonly shares: "supplyShares" | "borrowShares";
|
|
19
|
+
});
|
|
20
|
+
/** Caller quote paired with exactly the observations it needs. @internal */
|
|
21
|
+
export interface SlippageOperationReads {
|
|
22
|
+
readonly limit: OperationLimit;
|
|
23
|
+
readonly measurements: readonly SlippageMeasurement[];
|
|
24
|
+
}
|
|
25
|
+
/** Scalar balance or the two share counts returned by Morpho.position. @internal */
|
|
26
|
+
export type StateValue = bigint | {
|
|
27
|
+
readonly supplyShares: bigint;
|
|
28
|
+
readonly borrowShares: bigint;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Plan only quoted balance and position reads, deduplicating contract calls.
|
|
32
|
+
* Native deltas use the execution's transfer traces and require no view call.
|
|
33
|
+
* @param params - Resolved caller operations and the transaction sender/Morpho address.
|
|
34
|
+
* @returns Read calls and their mapping to each quoted amount; empty without limits.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare function planStateReads(params: {
|
|
38
|
+
readonly operations: readonly ResolvedSlippageOperation[];
|
|
39
|
+
readonly owner: Address;
|
|
40
|
+
readonly morpho: Address;
|
|
41
|
+
}): {
|
|
42
|
+
readonly reads: readonly StateRead[];
|
|
43
|
+
readonly operations: readonly SlippageOperationReads[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Decode one requested balance or position without reconstructing protocol entities.
|
|
47
|
+
* @param read - Planned view call.
|
|
48
|
+
* @param data - Return data from its simulated execution.
|
|
49
|
+
* @param context - Request chain, mode and pinned block, attached to thrown errors.
|
|
50
|
+
* @returns Raw balance or supply/borrow share counts.
|
|
51
|
+
* @throws {MissingVerificationEvidenceError} When the selected subject returned no data.
|
|
52
|
+
* @throws {InvalidSimulationResponseError} When non-empty return data cannot be decoded.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export declare function decodeStateRead(read: StateRead, data: Hex, context: {
|
|
56
|
+
readonly chainId: number;
|
|
57
|
+
readonly mode: SimulationMode;
|
|
58
|
+
readonly blockNumber: bigint;
|
|
59
|
+
}): StateValue;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planStateReads = planStateReads;
|
|
4
|
+
exports.decodeStateRead = decodeStateRead;
|
|
5
|
+
const abis_1 = require("@morpho-org/morpho-sdk/abis");
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const errors_js_1 = require("../../errors.js");
|
|
8
|
+
const measurement_plan_js_1 = require("../measurement-plan.js");
|
|
9
|
+
function planBalanceRead(params) {
|
|
10
|
+
const { reads, token, account } = params;
|
|
11
|
+
const id = `balance:${token}:${account}`.toLowerCase();
|
|
12
|
+
reads.set(id, {
|
|
13
|
+
kind: "erc20.balance",
|
|
14
|
+
id,
|
|
15
|
+
to: token,
|
|
16
|
+
token,
|
|
17
|
+
account,
|
|
18
|
+
data: (0, viem_1.encodeFunctionData)({
|
|
19
|
+
abi: viem_1.erc20Abi,
|
|
20
|
+
functionName: "balanceOf",
|
|
21
|
+
args: [account],
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
function planPositionRead(params) {
|
|
27
|
+
const { reads, morpho, marketId, account } = params;
|
|
28
|
+
const id = `position:${marketId}:${account}`.toLowerCase();
|
|
29
|
+
reads.set(id, {
|
|
30
|
+
kind: "morpho.position",
|
|
31
|
+
id,
|
|
32
|
+
to: morpho,
|
|
33
|
+
morpho,
|
|
34
|
+
marketId,
|
|
35
|
+
owner: account,
|
|
36
|
+
data: (0, viem_1.encodeFunctionData)({
|
|
37
|
+
abi: abis_1.blueAbi,
|
|
38
|
+
functionName: "position",
|
|
39
|
+
args: [marketId, account],
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Plan only quoted balance and position reads, deduplicating contract calls.
|
|
46
|
+
* Native deltas use the execution's transfer traces and require no view call.
|
|
47
|
+
* @param params - Resolved caller operations and the transaction sender/Morpho address.
|
|
48
|
+
* @returns Read calls and their mapping to each quoted amount; empty without limits.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
function planStateReads(params) {
|
|
52
|
+
const reads = new Map();
|
|
53
|
+
const operations = params.operations.map(({ limit, assetsPaid, assetsReceived }) => {
|
|
54
|
+
const measurements = [];
|
|
55
|
+
const account = limit.account ?? params.owner;
|
|
56
|
+
const plan = (0, measurement_plan_js_1.operationMeasurementPlan)(limit);
|
|
57
|
+
for (const field of [
|
|
58
|
+
"assetsReceived",
|
|
59
|
+
"assetsPaid",
|
|
60
|
+
"sharesMinted",
|
|
61
|
+
"sharesBurned",
|
|
62
|
+
]) {
|
|
63
|
+
if (limit.quote[field] === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
const assetField = field === "assetsReceived" || field === "assetsPaid";
|
|
66
|
+
if (!assetField) {
|
|
67
|
+
const source = field === "sharesMinted" ? plan.sharesMinted : plan.sharesBurned;
|
|
68
|
+
if (source === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
if (source.type === "position") {
|
|
71
|
+
measurements.push({
|
|
72
|
+
field,
|
|
73
|
+
type: "position",
|
|
74
|
+
readId: planPositionRead({
|
|
75
|
+
reads,
|
|
76
|
+
morpho: params.morpho,
|
|
77
|
+
marketId: source.marketId,
|
|
78
|
+
account,
|
|
79
|
+
}),
|
|
80
|
+
shares: source.shares,
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
measurements.push({
|
|
85
|
+
field,
|
|
86
|
+
type: "balance",
|
|
87
|
+
readId: planBalanceRead({
|
|
88
|
+
reads,
|
|
89
|
+
token: source.token,
|
|
90
|
+
account,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const token = field === "assetsReceived" ? assetsReceived : assetsPaid;
|
|
96
|
+
const holder = field === "assetsReceived"
|
|
97
|
+
? (limit.receiver ?? params.owner)
|
|
98
|
+
: params.owner;
|
|
99
|
+
if (token === undefined) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if ((0, viem_1.isAddressEqual)(token, viem_1.ethAddress)) {
|
|
103
|
+
measurements.push({ field, type: "native", account: holder });
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
measurements.push({
|
|
107
|
+
field,
|
|
108
|
+
type: "balance",
|
|
109
|
+
readId: planBalanceRead({ reads, token, account: holder }),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return { limit, measurements };
|
|
113
|
+
});
|
|
114
|
+
return { reads: [...reads.values()], operations };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Decode one requested balance or position without reconstructing protocol entities.
|
|
118
|
+
* @param read - Planned view call.
|
|
119
|
+
* @param data - Return data from its simulated execution.
|
|
120
|
+
* @param context - Request chain, mode and pinned block, attached to thrown errors.
|
|
121
|
+
* @returns Raw balance or supply/borrow share counts.
|
|
122
|
+
* @throws {MissingVerificationEvidenceError} When the selected subject returned no data.
|
|
123
|
+
* @throws {InvalidSimulationResponseError} When non-empty return data cannot be decoded.
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
// biome-ignore lint/complexity/useMaxParams: the call mirrors the failure context it attaches
|
|
127
|
+
function decodeStateRead(read, data, context) {
|
|
128
|
+
if (data === "0x") {
|
|
129
|
+
throw new errors_js_1.MissingVerificationEvidenceError(`Cannot observe slippage read "${read.id}": the selected subject returned no data (likely not a contract).`, { context: { stage: "verification", ...context, field: read.id } });
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
if (read.kind === "erc20.balance")
|
|
133
|
+
return (0, viem_1.decodeFunctionResult)({
|
|
134
|
+
abi: viem_1.erc20Abi,
|
|
135
|
+
functionName: "balanceOf",
|
|
136
|
+
data,
|
|
137
|
+
});
|
|
138
|
+
const [supplyShares, borrowShares] = (0, viem_1.decodeFunctionResult)({
|
|
139
|
+
abi: abis_1.blueAbi,
|
|
140
|
+
functionName: "position",
|
|
141
|
+
data,
|
|
142
|
+
});
|
|
143
|
+
return { supplyShares, borrowShares };
|
|
144
|
+
}
|
|
145
|
+
catch (cause) {
|
|
146
|
+
throw new errors_js_1.InvalidSimulationResponseError(`Cannot decode slippage observation "${read.id}". Check the selected contract and RPC response.`, { cause });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Address } from "viem";
|
|
2
|
+
import type { SimulatedOperation } from "../result.js";
|
|
3
|
+
import type { Transfer } from "../types.js";
|
|
4
|
+
import type { CheckContext } from "./context.js";
|
|
5
|
+
import type { SlippageOperationReads, StateValue } from "./state/read-state.js";
|
|
6
|
+
/**
|
|
7
|
+
* Measure only caller-quoted amounts and compare their percentage slippage.
|
|
8
|
+
* @param params - Planned observations, decoded before/after values, and user transfer traces.
|
|
9
|
+
* @returns Each caller subject and its checked quote/tolerance.
|
|
10
|
+
* @throws {ConsumerLimitViolationError} When adverse slippage exceeds tolerance.
|
|
11
|
+
* @throws {MissingVerificationEvidenceError} When a requested observation is absent.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function verifySlippage(params: {
|
|
15
|
+
readonly ctx: CheckContext;
|
|
16
|
+
readonly operations: readonly SlippageOperationReads[];
|
|
17
|
+
readonly before: ReadonlyMap<string, StateValue>;
|
|
18
|
+
readonly after: ReadonlyMap<string, StateValue>;
|
|
19
|
+
readonly transfers: readonly Transfer[];
|
|
20
|
+
readonly requestTransactions: readonly {
|
|
21
|
+
readonly from: Address;
|
|
22
|
+
readonly value?: bigint;
|
|
23
|
+
}[];
|
|
24
|
+
}): readonly SimulatedOperation[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifySlippage = verifySlippage;
|
|
4
|
+
const blue_sdk_1 = require("@morpho-org/blue-sdk");
|
|
5
|
+
const viem_1 = require("viem");
|
|
6
|
+
const errors_js_1 = require("../errors.js");
|
|
7
|
+
const measurement_plan_js_1 = require("./measurement-plan.js");
|
|
8
|
+
const slippage_js_1 = require("./slippage.js");
|
|
9
|
+
/**
|
|
10
|
+
* Measure only caller-quoted amounts and compare their percentage slippage.
|
|
11
|
+
* @param params - Planned observations, decoded before/after values, and user transfer traces.
|
|
12
|
+
* @returns Each caller subject and its checked quote/tolerance.
|
|
13
|
+
* @throws {ConsumerLimitViolationError} When adverse slippage exceeds tolerance.
|
|
14
|
+
* @throws {MissingVerificationEvidenceError} When a requested observation is absent.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
function verifySlippage(params) {
|
|
18
|
+
const { ctx, before, after, transfers, requestTransactions } = params;
|
|
19
|
+
return params.operations.map(({ limit, measurements }) => {
|
|
20
|
+
const subject = (0, measurement_plan_js_1.operationMeasurementPlan)(limit).subject;
|
|
21
|
+
const context = {
|
|
22
|
+
stage: "verification",
|
|
23
|
+
chainId: ctx.chainId,
|
|
24
|
+
mode: ctx.mode,
|
|
25
|
+
blockNumber: ctx.block.stateBlockNumber,
|
|
26
|
+
...subject,
|
|
27
|
+
};
|
|
28
|
+
const observed = {};
|
|
29
|
+
for (const measurement of measurements) {
|
|
30
|
+
let delta;
|
|
31
|
+
if (measurement.type === "native") {
|
|
32
|
+
const tracedOutgoing = transfers.reduce((total, transfer) => (0, viem_1.isAddressEqual)(transfer.token, viem_1.ethAddress) &&
|
|
33
|
+
(0, viem_1.isAddressEqual)(transfer.from, measurement.account)
|
|
34
|
+
? total + transfer.amount
|
|
35
|
+
: total, 0n);
|
|
36
|
+
const sentValue = requestTransactions.reduce((total, transaction) => (0, viem_1.isAddressEqual)(transaction.from, measurement.account)
|
|
37
|
+
? total + (transaction.value ?? 0n)
|
|
38
|
+
: total, 0n);
|
|
39
|
+
if (tracedOutgoing < sentValue) {
|
|
40
|
+
throw new errors_js_1.MissingVerificationEvidenceError(`Missing native transfer evidence for "${measurement.field}" from a transaction with value.`, { context: { ...context, field: measurement.field } });
|
|
41
|
+
}
|
|
42
|
+
delta = 0n;
|
|
43
|
+
for (const transfer of transfers) {
|
|
44
|
+
if (!(0, viem_1.isAddressEqual)(transfer.token, viem_1.ethAddress))
|
|
45
|
+
continue;
|
|
46
|
+
if ((0, viem_1.isAddressEqual)(transfer.to, measurement.account))
|
|
47
|
+
delta += transfer.amount;
|
|
48
|
+
if ((0, viem_1.isAddressEqual)(transfer.from, measurement.account))
|
|
49
|
+
delta -= transfer.amount;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const b = before.get(measurement.readId);
|
|
54
|
+
const a = after.get(measurement.readId);
|
|
55
|
+
if (measurement.type === "balance" &&
|
|
56
|
+
typeof b === "bigint" &&
|
|
57
|
+
typeof a === "bigint")
|
|
58
|
+
delta = a - b;
|
|
59
|
+
else if (measurement.type === "position" &&
|
|
60
|
+
typeof b === "object" &&
|
|
61
|
+
typeof a === "object")
|
|
62
|
+
delta = a[measurement.shares] - b[measurement.shares];
|
|
63
|
+
else
|
|
64
|
+
throw new errors_js_1.MissingVerificationEvidenceError(`Missing observation for "${measurement.field}". Check the selected subject.`, { context: { ...context, field: measurement.field } });
|
|
65
|
+
}
|
|
66
|
+
observed[measurement.field] =
|
|
67
|
+
measurement.field === "assetsPaid" ||
|
|
68
|
+
measurement.field === "sharesBurned"
|
|
69
|
+
? blue_sdk_1.MathLib.max(0n, -delta)
|
|
70
|
+
: delta;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
...subject,
|
|
74
|
+
account: limit.account ?? ctx.owner,
|
|
75
|
+
receiver: limit.receiver ?? ctx.owner,
|
|
76
|
+
checkedLimits: (0, slippage_js_1.checkSlippage)({
|
|
77
|
+
limits: limit,
|
|
78
|
+
observed,
|
|
79
|
+
context,
|
|
80
|
+
debtShares: measurements.some((measurement) => measurement.type === "position" &&
|
|
81
|
+
measurement.shares === "borrowShares"),
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
package/lib/cjs/types.d.ts
CHANGED
|
@@ -1,41 +1,21 @@
|
|
|
1
|
-
import type { Address,
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*/
|
|
7
|
-
export interface TenderlyRpcConfig {
|
|
8
|
-
rpcUrl: string;
|
|
1
|
+
import type { Address, Hex } from "viem";
|
|
2
|
+
/** Per-chain endpoint for the sole supported simulation method, `eth_simulateV1`. */
|
|
3
|
+
export interface ChainSimulationConfig {
|
|
4
|
+
/** JSON-RPC URL supporting `eth_simulateV1`. Required for every configured chain. */
|
|
5
|
+
readonly simulateV1Url: string;
|
|
9
6
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Per-chain backend capabilities. Exactly one configuration shape per chain;
|
|
12
|
-
* the discriminated union enforces that **at least one** of `tenderlyRpc`
|
|
13
|
-
* (primary) or `simulateV1Url` (fallback) is supplied.
|
|
14
|
-
*/
|
|
15
|
-
export type ChainSimulationConfig = {
|
|
16
|
-
/** Tenderly RPC config — `tenderly_simulateTransaction` / `tenderly_simulateBundle`. */
|
|
17
|
-
tenderlyRpc: TenderlyRpcConfig;
|
|
18
|
-
/** JSON-RPC URL supporting `eth_simulateV1`. Optional when Tenderly is set. */
|
|
19
|
-
simulateV1Url?: string;
|
|
20
|
-
} | {
|
|
21
|
-
tenderlyRpc?: TenderlyRpcConfig;
|
|
22
|
-
simulateV1Url: string;
|
|
23
|
-
};
|
|
24
7
|
/**
|
|
25
8
|
* Top-level configuration for `simulate`.
|
|
26
9
|
*
|
|
27
|
-
* Every chain entry must
|
|
28
|
-
*
|
|
29
|
-
*
|
|
10
|
+
* Every chain entry must supply an `eth_simulateV1` endpoint. An absent chain
|
|
11
|
+
* or missing endpoint throws `UnsupportedChainError`; execution never selects
|
|
12
|
+
* another provider after a failure.
|
|
30
13
|
*/
|
|
31
14
|
export interface SimulationConfig {
|
|
32
|
-
/** Per-chain simulation
|
|
15
|
+
/** Per-chain simulation endpoints. */
|
|
33
16
|
chains: Map<number, ChainSimulationConfig>;
|
|
34
17
|
logger?: SimulationLogger;
|
|
35
|
-
/**
|
|
36
|
-
* Overall timeout budget in ms (default 5000). Tenderly gets ~60% of budget.
|
|
37
|
-
* On timeout/failure, fallback gets remaining time (deadline - now).
|
|
38
|
-
*/
|
|
18
|
+
/** Overall execution timeout budget in ms (default 5000). */
|
|
39
19
|
timeoutMs?: number;
|
|
40
20
|
}
|
|
41
21
|
/**
|
|
@@ -43,31 +23,15 @@ export interface SimulationConfig {
|
|
|
43
23
|
* in a bundle — the orchestrator rejects mixed senders with `SimulationValidationError`.
|
|
44
24
|
*/
|
|
45
25
|
export interface SimulationTransaction {
|
|
46
|
-
from: Address;
|
|
47
|
-
to: Address;
|
|
48
|
-
data: Hex;
|
|
49
|
-
value?: bigint;
|
|
26
|
+
readonly from: Address;
|
|
27
|
+
readonly to: Address;
|
|
28
|
+
readonly data: Hex;
|
|
29
|
+
readonly value?: bigint;
|
|
50
30
|
}
|
|
51
|
-
/**
|
|
52
|
-
* How the caller expresses a token authorization that must be in place before the
|
|
53
|
-
* main transactions run. The package decides HOW to simulate each one:
|
|
54
|
-
* - "approval" → prepend tx as-is
|
|
55
|
-
* - "signature" → today: encode approve(spender, amount); future: ecrecover override?
|
|
56
|
-
*/
|
|
57
|
-
export type SimulationAuthorization = {
|
|
58
|
-
type: "approval";
|
|
59
|
-
transaction: SimulationTransaction;
|
|
60
|
-
} | {
|
|
61
|
-
type: "signature";
|
|
62
|
-
token: Address;
|
|
63
|
-
spender: Address;
|
|
64
|
-
/** Defaults to maxUint256. Caller can set explicit amount for tokens that don't support max approval. */
|
|
65
|
-
amount?: bigint;
|
|
66
|
-
};
|
|
67
31
|
/**
|
|
68
32
|
* Net balance change for a single asset (one token) within an account's entry.
|
|
69
33
|
* Native ETH uses viem's `ethAddress` sentinel as `token`. `symbol`/`decimals`
|
|
70
|
-
* are
|
|
34
|
+
* are optional metadata; the log-derived `eth_simulateV1` output omits them.
|
|
71
35
|
*/
|
|
72
36
|
export interface AssetChange {
|
|
73
37
|
readonly token: Address;
|
|
@@ -80,12 +44,11 @@ export interface AssetChange {
|
|
|
80
44
|
* Net per-token balance changes for one account over the whole bundle. Returned
|
|
81
45
|
* for every account that nets a non-zero change, the sender and counterparties
|
|
82
46
|
* alike (the zero address is kept for mints/burns). Accounts and their `changes`
|
|
83
|
-
* are sorted by address for deterministic
|
|
47
|
+
* are sorted by address for deterministic output.
|
|
84
48
|
*
|
|
85
|
-
*
|
|
86
|
-
* internal calls (e.g. a `WETH.withdraw` refund)
|
|
87
|
-
*
|
|
88
|
-
* node synthesizes native moves as transfer logs under viem's `ethAddress`.
|
|
49
|
+
* The backend reports the full net native-ETH delta, including ETH moved via
|
|
50
|
+
* internal calls (e.g. a `WETH.withdraw` refund). `traceTransfers` makes the
|
|
51
|
+
* node synthesize native moves as transfer logs under viem's `ethAddress`.
|
|
89
52
|
*/
|
|
90
53
|
export interface AccountAssetChanges {
|
|
91
54
|
readonly account: Address;
|
|
@@ -101,29 +64,27 @@ export interface Transfer {
|
|
|
101
64
|
readonly to: Address;
|
|
102
65
|
readonly amount: bigint;
|
|
103
66
|
/**
|
|
104
|
-
* Index into `SimulationResult.simulationTxs` of the transaction that
|
|
105
|
-
* emitted the underlying log.
|
|
106
|
-
*
|
|
107
|
-
* are authorization txs; the remainder are caller-supplied.
|
|
67
|
+
* Index into `SimulationResult.simulationTxs` of the user transaction that
|
|
68
|
+
* emitted the underlying log. `txIdx` only ever indexes caller-supplied
|
|
69
|
+
* transactions.
|
|
108
70
|
*/
|
|
109
71
|
readonly txIdx: number;
|
|
110
72
|
}
|
|
111
73
|
/**
|
|
112
74
|
* Happy-path return of `simulate`. All failures throw typed errors.
|
|
113
75
|
*
|
|
114
|
-
* - `simulationTxs`
|
|
115
|
-
*
|
|
76
|
+
* - `simulationTxs` are exactly the caller's ordered transactions, normalized
|
|
77
|
+
* (checksummed `from`/`to`, `value` defaulted to `0n`).
|
|
116
78
|
* - `calls[i]` corresponds 1:1 with `simulationTxs[i]` — read raw logs,
|
|
117
79
|
* status, returnData/gasUsed.
|
|
118
80
|
* - `assetChanges` is the net per-asset balance change over the whole bundle,
|
|
119
|
-
* grouped by account (sender and counterparties)
|
|
120
|
-
* shape across backends — see `AccountAssetChanges`.
|
|
81
|
+
* grouped by account (sender and counterparties) — see `AccountAssetChanges`.
|
|
121
82
|
* - `transfers[k].txIdx` indexes into `simulationTxs` to attribute each
|
|
122
|
-
* transfer to its emitting transaction.
|
|
83
|
+
* transfer to its emitting user transaction.
|
|
123
84
|
*/
|
|
124
85
|
export interface SimulationResult {
|
|
125
|
-
/**
|
|
126
|
-
readonly simulationTxs: readonly SimulationTransaction[];
|
|
86
|
+
/** Exactly the caller's ordered transactions, normalized. */
|
|
87
|
+
readonly simulationTxs: readonly Readonly<SimulationTransaction>[];
|
|
127
88
|
/**
|
|
128
89
|
* Per-transaction normalized output. `calls[i]` corresponds 1:1 with
|
|
129
90
|
* `simulationTxs[i]`. Use this to read raw logs, status, return data, gas used.
|
|
@@ -140,29 +101,9 @@ export interface SimulationLogger {
|
|
|
140
101
|
warn(message: string, data?: Record<string, unknown>): void;
|
|
141
102
|
error(message: string, data?: Record<string, unknown>): void;
|
|
142
103
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Input to `simulate`. Pin a `blockNumber` for deterministic / historical
|
|
145
|
-
* simulation; omit to simulate against `latest`.
|
|
146
|
-
*/
|
|
147
|
-
export interface SimulateParams {
|
|
148
|
-
chainId: number;
|
|
149
|
-
transactions: SimulationTransaction[];
|
|
150
|
-
authorizations?: SimulationAuthorization[];
|
|
151
|
-
blockNumber?: bigint | BlockTag;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Internal raw result from a simulation adapter before normalization.
|
|
155
|
-
* `calls[i]` corresponds 1:1 with the i-th transaction passed to the
|
|
156
|
-
* backend; `assetChanges` is the bundle-level aggregate grouped by account.
|
|
157
|
-
*/
|
|
158
|
-
export interface RawSimulationResult {
|
|
159
|
-
calls: RawCall[];
|
|
160
|
-
assetChanges: AccountAssetChanges[];
|
|
161
|
-
}
|
|
162
104
|
/**
|
|
163
105
|
* Normalized EVM log emitted by a single simulated call. The shape is the
|
|
164
|
-
*
|
|
165
|
-
* produce after schema validation. Returned indirectly via
|
|
106
|
+
* log shape produced by `eth_simulateV1` via viem. Returned indirectly via
|
|
166
107
|
* `SimulationCall.logs` and consumed by the SDK's transfer parser.
|
|
167
108
|
*/
|
|
168
109
|
export interface RawLog {
|
|
@@ -171,16 +112,15 @@ export interface RawLog {
|
|
|
171
112
|
readonly data: Hex;
|
|
172
113
|
}
|
|
173
114
|
/**
|
|
174
|
-
*
|
|
175
|
-
* simulation backends.
|
|
115
|
+
* Input-side mirror of `SimulationCall` accepted by the transfer parser.
|
|
176
116
|
*
|
|
177
117
|
* @internal
|
|
178
118
|
*/
|
|
179
119
|
export interface RawCall {
|
|
180
|
-
logs: RawLog[];
|
|
181
|
-
status: boolean;
|
|
182
|
-
returnData: Hex;
|
|
183
|
-
gasUsed: bigint;
|
|
120
|
+
readonly logs: readonly RawLog[];
|
|
121
|
+
readonly status: boolean;
|
|
122
|
+
readonly returnData: Hex;
|
|
123
|
+
readonly gasUsed: bigint;
|
|
184
124
|
}
|
|
185
125
|
/**
|
|
186
126
|
* Per-transaction normalized output from the simulation backend.
|
|
@@ -196,7 +136,7 @@ export interface SimulationCall {
|
|
|
196
136
|
* True iff the call succeeded. The bundle as a whole reverts via
|
|
197
137
|
* `SimulationRevertedError` before the result is returned, so on a
|
|
198
138
|
* successful `simulate()` every entry is `true` today. Field kept for
|
|
199
|
-
*
|
|
139
|
+
* per-call inspection alongside logs and return data.
|
|
200
140
|
*/
|
|
201
141
|
readonly status: boolean;
|
|
202
142
|
/** Return data from the top-level call. */
|