@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,759 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseRequest = parseRequest;
|
|
4
|
+
const blue_sdk_1 = require("@morpho-org/blue-sdk");
|
|
5
|
+
const morpho_ts_1 = require("@morpho-org/morpho-ts");
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const errors_js_1 = require("../../errors.js");
|
|
8
|
+
const params_js_1 = require("../../params.js");
|
|
9
|
+
const measurement_plan_js_1 = require("../measurement-plan.js");
|
|
10
|
+
// ─── Scalar validators ────────────────────────────────────────────────────────
|
|
11
|
+
// `input` is the typed public request, but JS callers can still pass wrong
|
|
12
|
+
// values: every scalar is checked at runtime and reports into `fieldErrors`.
|
|
13
|
+
const readField = (object, key) => Reflect.get(object, key);
|
|
14
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
// "pending" is rejected: only canonical (mined) blocks can be pinned.
|
|
16
|
+
const BLOCK_TAGS = ["latest", "earliest", "safe", "finalized"];
|
|
17
|
+
// ─── Typed-data field lists ───────────────────────────────────────
|
|
18
|
+
const ERC2612_PERMIT_FIELDS = [
|
|
19
|
+
{ name: "owner", type: "address" },
|
|
20
|
+
{ name: "spender", type: "address" },
|
|
21
|
+
{ name: "value", type: "uint256" },
|
|
22
|
+
{ name: "nonce", type: "uint256" },
|
|
23
|
+
{ name: "deadline", type: "uint256" },
|
|
24
|
+
];
|
|
25
|
+
const PERMIT2_TRANSFER_FIELDS = [
|
|
26
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
27
|
+
{ name: "spender", type: "address" },
|
|
28
|
+
{ name: "nonce", type: "uint256" },
|
|
29
|
+
{ name: "deadline", type: "uint256" },
|
|
30
|
+
];
|
|
31
|
+
const PERMIT2_TOKEN_PERMISSIONS_FIELDS = [
|
|
32
|
+
{ name: "token", type: "address" },
|
|
33
|
+
{ name: "amount", type: "uint256" },
|
|
34
|
+
];
|
|
35
|
+
const BLUE_AUTHORIZATION_FIELDS = [
|
|
36
|
+
{ name: "authorizer", type: "address" },
|
|
37
|
+
{ name: "authorized", type: "address" },
|
|
38
|
+
{ name: "isAuthorized", type: "bool" },
|
|
39
|
+
{ name: "nonce", type: "uint256" },
|
|
40
|
+
{ name: "deadline", type: "uint256" },
|
|
41
|
+
];
|
|
42
|
+
const DOMAIN_KEYS = [
|
|
43
|
+
"name",
|
|
44
|
+
"version",
|
|
45
|
+
"chainId",
|
|
46
|
+
"verifyingContract",
|
|
47
|
+
"salt",
|
|
48
|
+
];
|
|
49
|
+
const TYPED_DATA_KEYS = ["domain", "primaryType", "types", "message"];
|
|
50
|
+
const ERC2612_MESSAGE_KEYS = ERC2612_PERMIT_FIELDS.map((field) => field.name);
|
|
51
|
+
const PERMIT2_MESSAGE_KEYS = PERMIT2_TRANSFER_FIELDS.map((field) => field.name);
|
|
52
|
+
const PERMIT2_PERMITTED_KEYS = ["token", "amount"];
|
|
53
|
+
const BLUE_AUTHORIZATION_MESSAGE_KEYS = BLUE_AUTHORIZATION_FIELDS.map((field) => field.name);
|
|
54
|
+
const TRANSACTION_KEYS = ["from", "to", "data", "value"];
|
|
55
|
+
const LIMITS_KEYS = ["operations"];
|
|
56
|
+
const AUTHORIZATION_KEYS = {
|
|
57
|
+
erc20Approval: ["type", "token", "owner", "spender", "amount"],
|
|
58
|
+
erc2612Permit: ["type", "typedData"],
|
|
59
|
+
permit2SignatureTransfer: ["type", "owner", "typedData"],
|
|
60
|
+
blueAuthorization: ["type", "authorizer", "authorized", "isAuthorized"],
|
|
61
|
+
blueAuthorizationSignature: ["type", "typedData"],
|
|
62
|
+
};
|
|
63
|
+
/** Field validators closing over one `errors` accumulator. @internal */
|
|
64
|
+
const createChecks = () => {
|
|
65
|
+
const errors = [];
|
|
66
|
+
const check = {
|
|
67
|
+
errors,
|
|
68
|
+
address: (value, path) => {
|
|
69
|
+
if (typeof value === "string" && (0, viem_1.isAddress)(value))
|
|
70
|
+
return (0, viem_1.getAddress)(value);
|
|
71
|
+
errors.push(`${path}: must be a valid address`);
|
|
72
|
+
return undefined;
|
|
73
|
+
},
|
|
74
|
+
hex: (value, path) => {
|
|
75
|
+
if ((0, viem_1.isHex)(value))
|
|
76
|
+
return value;
|
|
77
|
+
errors.push(`${path}: must be a hex data`);
|
|
78
|
+
return undefined;
|
|
79
|
+
},
|
|
80
|
+
bytes32: (value, path) => {
|
|
81
|
+
if (typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value))
|
|
82
|
+
return value;
|
|
83
|
+
errors.push(`${path}: must be 32-byte hex`);
|
|
84
|
+
return undefined;
|
|
85
|
+
},
|
|
86
|
+
marketId: (value, path) => {
|
|
87
|
+
const id = check.bytes32(value, path);
|
|
88
|
+
return id === undefined ? undefined : id;
|
|
89
|
+
},
|
|
90
|
+
uint256: (value, path) => {
|
|
91
|
+
if (typeof value !== "bigint") {
|
|
92
|
+
errors.push(`${path}: must be a bigint`);
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
if (value < 0n) {
|
|
96
|
+
errors.push(`${path}: must be non-negative`);
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
if (value > viem_1.maxUint256) {
|
|
100
|
+
errors.push(`${path}: exceeds uint256`);
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
},
|
|
105
|
+
bool: (value, path) => {
|
|
106
|
+
if (typeof value === "boolean")
|
|
107
|
+
return value;
|
|
108
|
+
errors.push(`${path}: must be a boolean`);
|
|
109
|
+
return undefined;
|
|
110
|
+
},
|
|
111
|
+
nonNegInt: (value, path) => {
|
|
112
|
+
if (typeof value === "number" &&
|
|
113
|
+
Number.isInteger(value) &&
|
|
114
|
+
Number.isSafeInteger(value) &&
|
|
115
|
+
value >= 0)
|
|
116
|
+
return value;
|
|
117
|
+
errors.push(`${path}: must be a non-negative integer`);
|
|
118
|
+
return undefined;
|
|
119
|
+
},
|
|
120
|
+
keys: (value, { allow, path }) => {
|
|
121
|
+
if (!isRecord(value))
|
|
122
|
+
return;
|
|
123
|
+
for (const key of Object.keys(value)) {
|
|
124
|
+
if (!allow.includes(key))
|
|
125
|
+
errors.push(`${path}.${key}: unknown field`);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
domain: (domain, path) => {
|
|
129
|
+
if (!isRecord(domain)) {
|
|
130
|
+
errors.push(`${path}: must be an object`);
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
check.keys(domain, {
|
|
134
|
+
allow: DOMAIN_KEYS,
|
|
135
|
+
path,
|
|
136
|
+
});
|
|
137
|
+
const name = readField(domain, "name");
|
|
138
|
+
if (name !== undefined && typeof name !== "string")
|
|
139
|
+
errors.push(`${path}.name: must be a string`);
|
|
140
|
+
const version = readField(domain, "version");
|
|
141
|
+
if (version !== undefined && typeof version !== "string")
|
|
142
|
+
errors.push(`${path}.version: must be a string`);
|
|
143
|
+
const chainId = readField(domain, "chainId");
|
|
144
|
+
const validChainId = (typeof chainId === "number" &&
|
|
145
|
+
Number.isInteger(chainId) &&
|
|
146
|
+
chainId > 0) ||
|
|
147
|
+
(typeof chainId === "bigint" && chainId >= 0n && chainId <= viem_1.maxUint256);
|
|
148
|
+
if (!validChainId)
|
|
149
|
+
errors.push(`${path}.chainId: must be a positive integer or uint256`);
|
|
150
|
+
const verifyingContract = check.address(readField(domain, "verifyingContract"), `${path}.verifyingContract`);
|
|
151
|
+
const rawSalt = readField(domain, "salt");
|
|
152
|
+
const salt = rawSalt !== undefined
|
|
153
|
+
? check.bytes32(rawSalt, `${path}.salt`)
|
|
154
|
+
: undefined;
|
|
155
|
+
if (!validChainId || verifyingContract === undefined)
|
|
156
|
+
return undefined;
|
|
157
|
+
return {
|
|
158
|
+
...(typeof name === "string" ? { name } : {}),
|
|
159
|
+
...(typeof version === "string" ? { version } : {}),
|
|
160
|
+
chainId: chainId,
|
|
161
|
+
verifyingContract,
|
|
162
|
+
...(salt !== undefined ? { salt } : {}),
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
fields: ({ actual, expected, path }) => {
|
|
166
|
+
const fields = actual;
|
|
167
|
+
const equal = Array.isArray(fields) &&
|
|
168
|
+
fields.length === expected.length &&
|
|
169
|
+
expected.every((field, i) => fields[i]?.name === field.name && fields[i]?.type === field.type);
|
|
170
|
+
if (!equal) {
|
|
171
|
+
errors.push(`${path}: must list exactly ${expected.map((f) => `${f.name}: ${f.type}`).join(", ")}`);
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
return fields;
|
|
175
|
+
},
|
|
176
|
+
authorization: (authorization, path) => {
|
|
177
|
+
const type = isRecord(authorization)
|
|
178
|
+
? readField(authorization, "type")
|
|
179
|
+
: undefined;
|
|
180
|
+
if (!isRecord(authorization) || typeof type !== "string") {
|
|
181
|
+
errors.push(`${path}.type: must name an authorization type`);
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
if (!Object.hasOwn(AUTHORIZATION_KEYS, type)) {
|
|
185
|
+
errors.push(`${path}.type: unsupported authorization type "${type}"`);
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
check.keys(authorization, { allow: AUTHORIZATION_KEYS[type], path });
|
|
189
|
+
const errorsBefore = errors.length;
|
|
190
|
+
switch (type) {
|
|
191
|
+
case "erc20Approval": {
|
|
192
|
+
const token = check.address(readField(authorization, "token"), `${path}.token`);
|
|
193
|
+
const owner = check.address(readField(authorization, "owner"), `${path}.owner`);
|
|
194
|
+
const spender = check.address(readField(authorization, "spender"), `${path}.spender`);
|
|
195
|
+
const amount = check.uint256(readField(authorization, "amount"), `${path}.amount`);
|
|
196
|
+
if (token === undefined ||
|
|
197
|
+
owner === undefined ||
|
|
198
|
+
spender === undefined ||
|
|
199
|
+
amount === undefined)
|
|
200
|
+
return undefined;
|
|
201
|
+
return { type: "erc20Approval", token, owner, spender, amount };
|
|
202
|
+
}
|
|
203
|
+
case "erc2612Permit": {
|
|
204
|
+
const envelope = typedDataEnvelope({
|
|
205
|
+
value: readField(authorization, "typedData"),
|
|
206
|
+
path,
|
|
207
|
+
primaryType: "Permit",
|
|
208
|
+
fields: ERC2612_PERMIT_FIELDS,
|
|
209
|
+
});
|
|
210
|
+
if (envelope === undefined)
|
|
211
|
+
return undefined;
|
|
212
|
+
const { domain, message, fields: permitFields } = envelope;
|
|
213
|
+
const messagePath = `${path}.typedData.message`;
|
|
214
|
+
check.keys(message, {
|
|
215
|
+
allow: ERC2612_MESSAGE_KEYS,
|
|
216
|
+
path: messagePath,
|
|
217
|
+
});
|
|
218
|
+
const owner = check.address(readField(message, "owner"), `${messagePath}.owner`);
|
|
219
|
+
const spender = check.address(readField(message, "spender"), `${messagePath}.spender`);
|
|
220
|
+
const value = check.uint256(readField(message, "value"), `${messagePath}.value`);
|
|
221
|
+
const nonce = check.uint256(readField(message, "nonce"), `${messagePath}.nonce`);
|
|
222
|
+
const deadline = check.uint256(readField(message, "deadline"), `${messagePath}.deadline`);
|
|
223
|
+
if (errors.length !== errorsBefore ||
|
|
224
|
+
domain === undefined ||
|
|
225
|
+
owner === undefined ||
|
|
226
|
+
spender === undefined ||
|
|
227
|
+
value === undefined ||
|
|
228
|
+
nonce === undefined ||
|
|
229
|
+
deadline === undefined ||
|
|
230
|
+
permitFields === undefined)
|
|
231
|
+
return undefined;
|
|
232
|
+
return {
|
|
233
|
+
type: "erc2612Permit",
|
|
234
|
+
typedData: {
|
|
235
|
+
domain,
|
|
236
|
+
primaryType: "Permit",
|
|
237
|
+
types: {
|
|
238
|
+
Permit: copyFields(permitFields),
|
|
239
|
+
},
|
|
240
|
+
message: { owner, spender, value, nonce, deadline },
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
case "permit2SignatureTransfer": {
|
|
245
|
+
const owner = check.address(readField(authorization, "owner"), `${path}.owner`);
|
|
246
|
+
const envelope = typedDataEnvelope({
|
|
247
|
+
value: readField(authorization, "typedData"),
|
|
248
|
+
path,
|
|
249
|
+
primaryType: "PermitTransferFrom",
|
|
250
|
+
fields: PERMIT2_TRANSFER_FIELDS,
|
|
251
|
+
messageError: "must carry permitted token and amount",
|
|
252
|
+
});
|
|
253
|
+
if (envelope === undefined)
|
|
254
|
+
return undefined;
|
|
255
|
+
const { domain, message, fields: transferFields } = envelope;
|
|
256
|
+
const rawTypedData = readField(authorization, "typedData");
|
|
257
|
+
const types = isRecord(rawTypedData)
|
|
258
|
+
? readField(rawTypedData, "types")
|
|
259
|
+
: undefined;
|
|
260
|
+
const permissionFields = isRecord(types)
|
|
261
|
+
? readField(types, "TokenPermissions")
|
|
262
|
+
: undefined;
|
|
263
|
+
const checkedPermissionFields = check.fields({
|
|
264
|
+
actual: permissionFields,
|
|
265
|
+
expected: PERMIT2_TOKEN_PERMISSIONS_FIELDS,
|
|
266
|
+
path: `${path}.typedData.types.TokenPermissions`,
|
|
267
|
+
});
|
|
268
|
+
const permitted = readField(message, "permitted");
|
|
269
|
+
if (!isRecord(permitted)) {
|
|
270
|
+
errors.push(`${path}.typedData.message: must carry permitted token and amount`);
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
const messagePath = `${path}.typedData.message`;
|
|
274
|
+
check.keys(message, {
|
|
275
|
+
allow: PERMIT2_MESSAGE_KEYS,
|
|
276
|
+
path: messagePath,
|
|
277
|
+
});
|
|
278
|
+
check.keys(permitted, {
|
|
279
|
+
allow: PERMIT2_PERMITTED_KEYS,
|
|
280
|
+
path: `${messagePath}.permitted`,
|
|
281
|
+
});
|
|
282
|
+
const permittedToken = check.address(readField(permitted, "token"), `${messagePath}.permitted.token`);
|
|
283
|
+
const permittedAmount = check.uint256(readField(permitted, "amount"), `${messagePath}.permitted.amount`);
|
|
284
|
+
const spender = check.address(readField(message, "spender"), `${messagePath}.spender`);
|
|
285
|
+
const nonce = check.uint256(readField(message, "nonce"), `${messagePath}.nonce`);
|
|
286
|
+
const deadline = check.uint256(readField(message, "deadline"), `${messagePath}.deadline`);
|
|
287
|
+
if (errors.length !== errorsBefore ||
|
|
288
|
+
domain === undefined ||
|
|
289
|
+
owner === undefined ||
|
|
290
|
+
permittedToken === undefined ||
|
|
291
|
+
permittedAmount === undefined ||
|
|
292
|
+
spender === undefined ||
|
|
293
|
+
nonce === undefined ||
|
|
294
|
+
deadline === undefined ||
|
|
295
|
+
transferFields === undefined ||
|
|
296
|
+
checkedPermissionFields === undefined)
|
|
297
|
+
return undefined;
|
|
298
|
+
return {
|
|
299
|
+
type: "permit2SignatureTransfer",
|
|
300
|
+
owner,
|
|
301
|
+
typedData: {
|
|
302
|
+
domain,
|
|
303
|
+
primaryType: "PermitTransferFrom",
|
|
304
|
+
types: {
|
|
305
|
+
PermitTransferFrom: copyFields(transferFields),
|
|
306
|
+
TokenPermissions: copyFields(checkedPermissionFields),
|
|
307
|
+
},
|
|
308
|
+
message: {
|
|
309
|
+
permitted: {
|
|
310
|
+
token: permittedToken,
|
|
311
|
+
amount: permittedAmount,
|
|
312
|
+
},
|
|
313
|
+
spender,
|
|
314
|
+
nonce,
|
|
315
|
+
deadline,
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
case "blueAuthorization": {
|
|
321
|
+
const authorizer = check.address(readField(authorization, "authorizer"), `${path}.authorizer`);
|
|
322
|
+
const authorized = check.address(readField(authorization, "authorized"), `${path}.authorized`);
|
|
323
|
+
const isAuthorized = check.bool(readField(authorization, "isAuthorized"), `${path}.isAuthorized`);
|
|
324
|
+
if (authorizer === undefined ||
|
|
325
|
+
authorized === undefined ||
|
|
326
|
+
isAuthorized === undefined)
|
|
327
|
+
return undefined;
|
|
328
|
+
return {
|
|
329
|
+
type: "blueAuthorization",
|
|
330
|
+
authorizer,
|
|
331
|
+
authorized,
|
|
332
|
+
isAuthorized,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
case "blueAuthorizationSignature": {
|
|
336
|
+
const envelope = typedDataEnvelope({
|
|
337
|
+
value: readField(authorization, "typedData"),
|
|
338
|
+
path,
|
|
339
|
+
primaryType: "Authorization",
|
|
340
|
+
fields: BLUE_AUTHORIZATION_FIELDS,
|
|
341
|
+
});
|
|
342
|
+
if (envelope === undefined)
|
|
343
|
+
return undefined;
|
|
344
|
+
const { domain, message, fields: authorizationFields } = envelope;
|
|
345
|
+
const messagePath = `${path}.typedData.message`;
|
|
346
|
+
check.keys(message, {
|
|
347
|
+
allow: BLUE_AUTHORIZATION_MESSAGE_KEYS,
|
|
348
|
+
path: messagePath,
|
|
349
|
+
});
|
|
350
|
+
const authorizer = check.address(readField(message, "authorizer"), `${messagePath}.authorizer`);
|
|
351
|
+
const authorized = check.address(readField(message, "authorized"), `${messagePath}.authorized`);
|
|
352
|
+
const isAuthorized = check.bool(readField(message, "isAuthorized"), `${messagePath}.isAuthorized`);
|
|
353
|
+
const nonce = check.uint256(readField(message, "nonce"), `${messagePath}.nonce`);
|
|
354
|
+
const deadline = check.uint256(readField(message, "deadline"), `${messagePath}.deadline`);
|
|
355
|
+
if (errors.length !== errorsBefore ||
|
|
356
|
+
domain === undefined ||
|
|
357
|
+
authorizer === undefined ||
|
|
358
|
+
authorized === undefined ||
|
|
359
|
+
isAuthorized === undefined ||
|
|
360
|
+
nonce === undefined ||
|
|
361
|
+
deadline === undefined ||
|
|
362
|
+
authorizationFields === undefined)
|
|
363
|
+
return undefined;
|
|
364
|
+
return {
|
|
365
|
+
type: "blueAuthorizationSignature",
|
|
366
|
+
typedData: {
|
|
367
|
+
domain,
|
|
368
|
+
primaryType: "Authorization",
|
|
369
|
+
types: {
|
|
370
|
+
Authorization: copyFields(authorizationFields),
|
|
371
|
+
},
|
|
372
|
+
message: {
|
|
373
|
+
authorizer,
|
|
374
|
+
authorized,
|
|
375
|
+
isAuthorized,
|
|
376
|
+
nonce,
|
|
377
|
+
deadline,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
default:
|
|
383
|
+
errors.push(`${path}.type: unsupported authorization type "${type}"`);
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
operation: (operation, path) => {
|
|
388
|
+
const type = isRecord(operation)
|
|
389
|
+
? readField(operation, "type")
|
|
390
|
+
: undefined;
|
|
391
|
+
if (!isRecord(operation) ||
|
|
392
|
+
typeof type !== "string" ||
|
|
393
|
+
!Object.hasOwn(OPERATION_SPECS, type)) {
|
|
394
|
+
errors.push(`${path}.type: unsupported operation type "${String(type)}"`);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
const spec = OPERATION_SPECS[type];
|
|
398
|
+
const requiredAddresses = new Set(spec.requiredAddresses);
|
|
399
|
+
check.keys(operation, {
|
|
400
|
+
allow: [
|
|
401
|
+
"type",
|
|
402
|
+
...spec.markets,
|
|
403
|
+
...spec.addresses,
|
|
404
|
+
"quote",
|
|
405
|
+
"slippageTolerance",
|
|
406
|
+
],
|
|
407
|
+
path,
|
|
408
|
+
});
|
|
409
|
+
const errorsBefore = errors.length;
|
|
410
|
+
const out = { type };
|
|
411
|
+
for (const field of spec.markets) {
|
|
412
|
+
const value = check.marketId(readField(operation, field), `${path}.${field}`);
|
|
413
|
+
if (value !== undefined)
|
|
414
|
+
Reflect.set(out, field, value);
|
|
415
|
+
}
|
|
416
|
+
for (const field of spec.addresses) {
|
|
417
|
+
const raw = readField(operation, field);
|
|
418
|
+
if (raw === undefined && !requiredAddresses.has(field))
|
|
419
|
+
continue;
|
|
420
|
+
const value = check.address(raw, `${path}.${field}`);
|
|
421
|
+
if (value !== undefined)
|
|
422
|
+
Reflect.set(out, field, value);
|
|
423
|
+
}
|
|
424
|
+
const tolerance = check.uint256(readField(operation, "slippageTolerance"), `${path}.slippageTolerance`);
|
|
425
|
+
if (tolerance !== undefined) {
|
|
426
|
+
if (tolerance > blue_sdk_1.MathLib.WAD)
|
|
427
|
+
errors.push(`${path}.slippageTolerance: must not exceed 100% (1e18)`);
|
|
428
|
+
Reflect.set(out, "slippageTolerance", tolerance);
|
|
429
|
+
}
|
|
430
|
+
const quote = readField(operation, "quote");
|
|
431
|
+
if (!isRecord(quote)) {
|
|
432
|
+
errors.push(`${path}.quote: must be an object containing at least one quoted amount`);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
check.keys(quote, { allow: QUOTE_FIELDS, path: `${path}.quote` });
|
|
436
|
+
const normalizedQuote = {};
|
|
437
|
+
for (const field of QUOTE_FIELDS) {
|
|
438
|
+
const raw = readField(quote, field);
|
|
439
|
+
if (raw === undefined)
|
|
440
|
+
continue;
|
|
441
|
+
const value = check.uint256(raw, `${path}.quote.${field}`);
|
|
442
|
+
if (value !== undefined)
|
|
443
|
+
normalizedQuote[field] = value;
|
|
444
|
+
}
|
|
445
|
+
if (Object.keys(normalizedQuote).length === 0)
|
|
446
|
+
errors.push(`${path}.quote: supply at least one quoted amount`);
|
|
447
|
+
const measurement = (0, measurement_plan_js_1.operationMeasurementPlan)(out);
|
|
448
|
+
for (const field of ["sharesMinted", "sharesBurned"]) {
|
|
449
|
+
if (normalizedQuote[field] !== undefined &&
|
|
450
|
+
measurement[field] === undefined)
|
|
451
|
+
errors.push(`${path}.quote.${field}: cannot be measured for "${type}"`);
|
|
452
|
+
}
|
|
453
|
+
Reflect.set(out, "quote", normalizedQuote);
|
|
454
|
+
}
|
|
455
|
+
return errors.length === errorsBefore ? out : undefined;
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
const copyFields = (fields) => fields.map(({ name, type: fieldType }) => ({ name, type: fieldType }));
|
|
459
|
+
/** Shared typed-data envelope guard: domain, primaryType, primary field list
|
|
460
|
+
* and message record; message-specific checks stay in each branch. */
|
|
461
|
+
const typedDataEnvelope = (options) => {
|
|
462
|
+
const typedDataPath = `${options.path}.typedData`;
|
|
463
|
+
const value = options.value;
|
|
464
|
+
if (!isRecord(value)) {
|
|
465
|
+
errors.push(`${typedDataPath}: must be an object`);
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
check.keys(value, { allow: TYPED_DATA_KEYS, path: typedDataPath });
|
|
469
|
+
const domain = check.domain(readField(value, "domain"), `${typedDataPath}.domain`);
|
|
470
|
+
if (readField(value, "primaryType") !== options.primaryType)
|
|
471
|
+
errors.push(`${typedDataPath}.primaryType: must be "${options.primaryType}" (got ${String(readField(value, "primaryType"))})`);
|
|
472
|
+
const types = readField(value, "types");
|
|
473
|
+
const fields = isRecord(types)
|
|
474
|
+
? readField(types, options.primaryType)
|
|
475
|
+
: undefined;
|
|
476
|
+
const checkedFields = check.fields({
|
|
477
|
+
actual: fields,
|
|
478
|
+
expected: options.fields,
|
|
479
|
+
path: `${typedDataPath}.types.${options.primaryType}`,
|
|
480
|
+
});
|
|
481
|
+
const message = readField(value, "message");
|
|
482
|
+
if (!isRecord(message)) {
|
|
483
|
+
errors.push(`${typedDataPath}.message: ${options.messageError ?? "must be an object"}`);
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
return { domain, message, fields: checkedFields };
|
|
487
|
+
};
|
|
488
|
+
return check;
|
|
489
|
+
};
|
|
490
|
+
const QUOTE_FIELDS = [
|
|
491
|
+
"assetsReceived",
|
|
492
|
+
"sharesMinted",
|
|
493
|
+
"assetsPaid",
|
|
494
|
+
"sharesBurned",
|
|
495
|
+
];
|
|
496
|
+
const MARKET_SUBJECT = {
|
|
497
|
+
markets: ["marketId"],
|
|
498
|
+
addresses: ["account", "receiver", "assetPaid", "assetReceived"],
|
|
499
|
+
requiredAddresses: [],
|
|
500
|
+
};
|
|
501
|
+
const VAULT_SUBJECT = {
|
|
502
|
+
markets: [],
|
|
503
|
+
addresses: ["vault", "account", "receiver", "assetPaid", "assetReceived"],
|
|
504
|
+
requiredAddresses: ["vault"],
|
|
505
|
+
};
|
|
506
|
+
const OPERATION_SPECS = {
|
|
507
|
+
blueSupply: MARKET_SUBJECT,
|
|
508
|
+
blueWithdraw: MARKET_SUBJECT,
|
|
509
|
+
blueSupplyCollateral: MARKET_SUBJECT,
|
|
510
|
+
blueBorrow: MARKET_SUBJECT,
|
|
511
|
+
blueSupplyCollateralBorrow: MARKET_SUBJECT,
|
|
512
|
+
blueRepay: MARKET_SUBJECT,
|
|
513
|
+
blueWithdrawCollateral: MARKET_SUBJECT,
|
|
514
|
+
blueRepayWithdrawCollateral: MARKET_SUBJECT,
|
|
515
|
+
blueRefinance: {
|
|
516
|
+
...MARKET_SUBJECT,
|
|
517
|
+
markets: ["sourceMarketId", "targetMarketId"],
|
|
518
|
+
},
|
|
519
|
+
vaultV1Deposit: VAULT_SUBJECT,
|
|
520
|
+
vaultV2Deposit: VAULT_SUBJECT,
|
|
521
|
+
vaultV1Withdraw: VAULT_SUBJECT,
|
|
522
|
+
vaultV2Withdraw: VAULT_SUBJECT,
|
|
523
|
+
vaultV1Redeem: VAULT_SUBJECT,
|
|
524
|
+
vaultV2Redeem: VAULT_SUBJECT,
|
|
525
|
+
vaultV2ForceWithdraw: VAULT_SUBJECT,
|
|
526
|
+
vaultV2ForceRedeem: VAULT_SUBJECT,
|
|
527
|
+
vaultV1InKindRedeem: VAULT_SUBJECT,
|
|
528
|
+
vaultV2InKindRedeem: VAULT_SUBJECT,
|
|
529
|
+
vaultV1MigrateToV2: {
|
|
530
|
+
markets: [],
|
|
531
|
+
addresses: [
|
|
532
|
+
"sourceVault",
|
|
533
|
+
"targetVault",
|
|
534
|
+
"account",
|
|
535
|
+
"receiver",
|
|
536
|
+
"assetPaid",
|
|
537
|
+
"assetReceived",
|
|
538
|
+
],
|
|
539
|
+
requiredAddresses: ["sourceVault", "targetVault"],
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
/**
|
|
543
|
+
* Parse and normalize raw `simulate` input into a {@link ParsedRequest}.
|
|
544
|
+
*
|
|
545
|
+
* The public types own the key shape; runtime validation rejects unknown keys
|
|
546
|
+
* (`<path>.<key>: unknown field`), checks values — address and hex formats,
|
|
547
|
+
* uint256 ranges, typed-data field lists — and rejects legacy `{type: "approval"}` / `{type: "signature"}` authorizations and Permit2
|
|
548
|
+
* `PermitSingle` payloads rather than silently reinterpreting them. Cross-field
|
|
549
|
+
* rules then pin a single owner: every transaction `from` and every
|
|
550
|
+
* authorization owner must be the same checksummed address, typed-data domains
|
|
551
|
+
* must bind to `chainId`, and `mode: "final"` rejects authorizations outright.
|
|
552
|
+
*
|
|
553
|
+
* @param input - Caller input (`SimulateParams`-shaped).
|
|
554
|
+
* @returns A deep-frozen, checksummed request: `mode` explicit (`"final"`
|
|
555
|
+
* default), `authorizations` always an array, `value` defaulted to `0n`.
|
|
556
|
+
* @throws {SimulationValidationError} On any unknown key, value or cross-field
|
|
557
|
+
* violation.
|
|
558
|
+
* @internal
|
|
559
|
+
*/
|
|
560
|
+
function parseRequest(input) {
|
|
561
|
+
const check = createChecks();
|
|
562
|
+
const fieldErrors = check.errors;
|
|
563
|
+
if (!isRecord(input)) {
|
|
564
|
+
throw new errors_js_1.SimulationValidationError("Invalid simulation input", [
|
|
565
|
+
"input: must be a request object",
|
|
566
|
+
]);
|
|
567
|
+
}
|
|
568
|
+
check.keys(input, {
|
|
569
|
+
allow: [
|
|
570
|
+
"chainId",
|
|
571
|
+
"transactions",
|
|
572
|
+
"blockNumber",
|
|
573
|
+
"mode",
|
|
574
|
+
"authorizations",
|
|
575
|
+
"limits",
|
|
576
|
+
],
|
|
577
|
+
path: "input",
|
|
578
|
+
});
|
|
579
|
+
// chainId
|
|
580
|
+
const chainId = input.chainId;
|
|
581
|
+
if (typeof chainId !== "number" ||
|
|
582
|
+
!Number.isInteger(chainId) ||
|
|
583
|
+
!Number.isSafeInteger(chainId) ||
|
|
584
|
+
chainId <= 0) {
|
|
585
|
+
fieldErrors.push("chainId: must be a positive safe integer");
|
|
586
|
+
}
|
|
587
|
+
// transactions
|
|
588
|
+
const rawTransactions = input.transactions;
|
|
589
|
+
const transactions = [];
|
|
590
|
+
// Raw index of each accepted transaction, so cross-field errors name the
|
|
591
|
+
// caller's index rather than the filtered position.
|
|
592
|
+
const transactionIndices = [];
|
|
593
|
+
if (!Array.isArray(rawTransactions) || rawTransactions.length === 0) {
|
|
594
|
+
fieldErrors.push("transactions: must be a non-empty array");
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
for (const [i, tx] of rawTransactions.entries()) {
|
|
598
|
+
const path = `transactions[${i}]`;
|
|
599
|
+
if (!isRecord(tx)) {
|
|
600
|
+
fieldErrors.push(`${path}: must be an object`);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
check.keys(tx, {
|
|
604
|
+
allow: TRANSACTION_KEYS,
|
|
605
|
+
path,
|
|
606
|
+
});
|
|
607
|
+
const from = check.address(readField(tx, "from"), `${path}.from`);
|
|
608
|
+
const to = check.address(readField(tx, "to"), `${path}.to`);
|
|
609
|
+
if (from !== undefined && (0, viem_1.isAddressEqual)(from, viem_1.zeroAddress)) {
|
|
610
|
+
fieldErrors.push(`${path}.from: must be a non-zero address`);
|
|
611
|
+
}
|
|
612
|
+
if (to !== undefined && (0, viem_1.isAddressEqual)(to, viem_1.zeroAddress)) {
|
|
613
|
+
fieldErrors.push(`${path}.to: must be a non-zero address`);
|
|
614
|
+
}
|
|
615
|
+
const data = check.hex(readField(tx, "data"), `${path}.data`);
|
|
616
|
+
const rawValue = readField(tx, "value");
|
|
617
|
+
const value = rawValue === undefined ? 0n : check.uint256(rawValue, `${path}.value`);
|
|
618
|
+
if (from !== undefined && to !== undefined && data !== undefined) {
|
|
619
|
+
transactions.push({ from, to, data, value: value ?? 0n });
|
|
620
|
+
transactionIndices.push(i);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
// mode
|
|
625
|
+
const rawMode = input.mode;
|
|
626
|
+
let mode = "final";
|
|
627
|
+
if (rawMode !== undefined) {
|
|
628
|
+
const found = params_js_1.SIMULATION_MODES.find((m) => m === rawMode);
|
|
629
|
+
if (found !== undefined) {
|
|
630
|
+
mode = found;
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
fieldErrors.push(`mode: must be one of ${params_js_1.SIMULATION_MODES.map((m) => `"${m}"`).join(", ")} (got ${String(rawMode)})`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// blockNumber
|
|
637
|
+
const rawBlockNumber = input.blockNumber;
|
|
638
|
+
let blockNumber;
|
|
639
|
+
if (rawBlockNumber !== undefined) {
|
|
640
|
+
if (typeof rawBlockNumber === "bigint" && rawBlockNumber >= 0n) {
|
|
641
|
+
blockNumber = rawBlockNumber;
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
const tag = typeof rawBlockNumber === "string"
|
|
645
|
+
? BLOCK_TAGS.find((t) => t === rawBlockNumber)
|
|
646
|
+
: undefined;
|
|
647
|
+
if (tag !== undefined) {
|
|
648
|
+
blockNumber = tag;
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
fieldErrors.push(`blockNumber: must be a non-negative bigint or one of ${BLOCK_TAGS.map((t) => `"${t}"`).join(", ")}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// authorizations
|
|
656
|
+
const rawAuthorizations = input.authorizations;
|
|
657
|
+
const authorizations = [];
|
|
658
|
+
if (rawAuthorizations !== undefined) {
|
|
659
|
+
if (!Array.isArray(rawAuthorizations)) {
|
|
660
|
+
fieldErrors.push("authorizations: must be an array");
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
if (mode === "final") {
|
|
664
|
+
fieldErrors.push("authorizations: are only accepted in preview mode; sign them and submit mode 'final' without authorizations");
|
|
665
|
+
}
|
|
666
|
+
for (const [i, authorization] of rawAuthorizations.entries()) {
|
|
667
|
+
const parsed = check.authorization(authorization, `authorizations[${i}]`);
|
|
668
|
+
if (parsed !== undefined)
|
|
669
|
+
authorizations.push(parsed);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// limits
|
|
674
|
+
const limits = input.limits;
|
|
675
|
+
const operations = [];
|
|
676
|
+
let normalizedLimits;
|
|
677
|
+
if (limits !== undefined) {
|
|
678
|
+
if (!isRecord(limits)) {
|
|
679
|
+
fieldErrors.push("limits: must be an object");
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
check.keys(limits, { allow: LIMITS_KEYS, path: "limits" });
|
|
683
|
+
const rawOperations = readField(limits, "operations");
|
|
684
|
+
if (rawOperations !== undefined) {
|
|
685
|
+
if (!Array.isArray(rawOperations)) {
|
|
686
|
+
fieldErrors.push("limits.operations: must be an array");
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
for (const [i, operation] of rawOperations.entries()) {
|
|
690
|
+
const parsedOperation = check.operation(operation, `limits.operations[${i}]`);
|
|
691
|
+
if (parsedOperation !== undefined)
|
|
692
|
+
operations.push(parsedOperation);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
normalizedLimits = {
|
|
697
|
+
...(Array.isArray(rawOperations) ? { operations } : {}),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// ─── Cross-field rules ──────────────────────────────────────────────────
|
|
702
|
+
const owner = transactions[0]?.from;
|
|
703
|
+
if (owner !== undefined) {
|
|
704
|
+
for (const [i, tx] of transactions.entries()) {
|
|
705
|
+
if (!(0, viem_1.isAddressEqual)(tx.from, owner)) {
|
|
706
|
+
fieldErrors.push(`transactions[${transactionIndices[i]}].from: all transactions must share the same from address (expected ${owner}, got ${tx.from})`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
for (const [i, authorization] of authorizations.entries()) {
|
|
711
|
+
const { owner: authorizationOwnerAddress, domainChainId } = (() => {
|
|
712
|
+
switch (authorization.type) {
|
|
713
|
+
case "erc20Approval":
|
|
714
|
+
return { owner: authorization.owner, domainChainId: undefined };
|
|
715
|
+
case "permit2SignatureTransfer":
|
|
716
|
+
return {
|
|
717
|
+
owner: authorization.owner,
|
|
718
|
+
domainChainId: authorization.typedData.domain.chainId,
|
|
719
|
+
};
|
|
720
|
+
case "erc2612Permit":
|
|
721
|
+
return {
|
|
722
|
+
owner: authorization.typedData.message.owner,
|
|
723
|
+
domainChainId: authorization.typedData.domain.chainId,
|
|
724
|
+
};
|
|
725
|
+
case "blueAuthorization":
|
|
726
|
+
return { owner: authorization.authorizer, domainChainId: undefined };
|
|
727
|
+
case "blueAuthorizationSignature":
|
|
728
|
+
return {
|
|
729
|
+
owner: authorization.typedData.message.authorizer,
|
|
730
|
+
domainChainId: authorization.typedData.domain.chainId,
|
|
731
|
+
};
|
|
732
|
+
default: {
|
|
733
|
+
const _exhaustive = authorization;
|
|
734
|
+
return _exhaustive;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
})();
|
|
738
|
+
if (owner !== undefined &&
|
|
739
|
+
!(0, viem_1.isAddressEqual)(authorizationOwnerAddress, owner)) {
|
|
740
|
+
fieldErrors.push(`authorizations[${i}]: owner must equal the bundle sender ${owner} (got ${authorizationOwnerAddress})`);
|
|
741
|
+
}
|
|
742
|
+
if (domainChainId !== undefined &&
|
|
743
|
+
typeof chainId === "number" &&
|
|
744
|
+
BigInt(domainChainId) !== BigInt(chainId)) {
|
|
745
|
+
fieldErrors.push(`authorizations[${i}].typedData.domain.chainId: must equal the request chainId ${chainId} (got ${domainChainId})`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (fieldErrors.length > 0) {
|
|
749
|
+
throw new errors_js_1.SimulationValidationError("Invalid simulation input", fieldErrors);
|
|
750
|
+
}
|
|
751
|
+
return (0, morpho_ts_1.deepFreeze)({
|
|
752
|
+
chainId: chainId,
|
|
753
|
+
mode,
|
|
754
|
+
transactions,
|
|
755
|
+
authorizations: mode === "preview" ? authorizations : [],
|
|
756
|
+
...(blockNumber !== undefined ? { blockNumber } : {}),
|
|
757
|
+
...(normalizedLimits !== undefined ? { limits: normalizedLimits } : {}),
|
|
758
|
+
});
|
|
759
|
+
}
|