@gearbox-protocol/sdk 15.1.0-next.10 → 15.1.0-next.11
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/dist/cjs/dev/compareOpportunities.js +45 -126
- package/dist/cjs/dev/comparePositions.js +183 -0
- package/dist/cjs/dev/fieldDiff.js +241 -0
- package/dist/cjs/sdk/market/math.js +8 -1
- package/dist/cjs/sdk/pools/PoolService.js +1 -1
- package/dist/cjs/sdk/positions/PositionsService.js +6 -1
- package/dist/esm/dev/AccountOpener.js +1 -1
- package/dist/esm/dev/compareOpportunities.js +44 -125
- package/dist/esm/dev/comparePositions.js +181 -0
- package/dist/esm/dev/fieldDiff.js +224 -0
- package/dist/esm/dev/withdrawalUtils.js +1 -1
- package/dist/esm/plugins/adapters/contracts/ERC4626AdapterContract.js +1 -1
- package/dist/esm/preview/simulate/simulatePoolOperation.js +1 -1
- package/dist/esm/preview/trace/extractTransfers.js +1 -1
- package/dist/esm/sdk/accounts/CreditAccountsServiceV310.js +2 -2
- package/dist/esm/sdk/accounts/liquidations/LiquidationsService.js +1 -1
- package/dist/esm/sdk/accounts/withdrawal-compressor/RedemptionLoggerV310Contract.js +1 -1
- package/dist/esm/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV310Contract.js +1 -1
- package/dist/esm/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV311Contract.js +1 -1
- package/dist/esm/sdk/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +1 -1
- package/dist/esm/sdk/base/TokensMeta.js +3 -3
- package/dist/esm/sdk/chain/detectNetwork.js +1 -1
- package/dist/esm/sdk/core/createAddressProvider.js +1 -1
- package/dist/esm/sdk/market/credit/CreditFacadeV310BaseContract.js +1 -1
- package/dist/esm/sdk/market/math.js +8 -1
- package/dist/esm/sdk/market/pool/PoolV310Contract.js +1 -1
- package/dist/esm/sdk/market/zapper/IETHZapperContract.js +1 -1
- package/dist/esm/sdk/market/zapper/ZapperContract.js +1 -1
- package/dist/esm/sdk/pools/PoolService.js +2 -2
- package/dist/esm/sdk/positions/PositionsService.js +6 -1
- package/dist/esm/sdk/utils/viem/simulateWithPriceUpdates.js +1 -1
- package/dist/types/dev/compareOpportunities.d.ts +13 -61
- package/dist/types/dev/comparePositions.d.ts +149 -0
- package/dist/types/dev/fieldDiff.d.ts +167 -0
- package/dist/types/sdk/market/math.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_model_opportunities = require("../model/opportunities.js");
|
|
3
3
|
require("../model/index.js");
|
|
4
|
+
const require_dev_fieldDiff = require("./fieldDiff.js");
|
|
4
5
|
//#region src/dev/compareOpportunities.ts
|
|
5
6
|
/**
|
|
6
7
|
* Matches two opportunity listings by {@link opportunityId} and reports every
|
|
7
8
|
* field the two sources disagree on.
|
|
8
9
|
*
|
|
9
|
-
* Nothing is filtered out
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Nothing is filtered out. A field only the backend can fill, or a USD value
|
|
11
|
+
* that drifted within snapshot-lag noise, is still reported — tagged
|
|
12
|
+
* {@link FieldDiff.expected} so that {@link CompareCounts.clean} can ignore it
|
|
13
|
+
* while {@link CompareCounts.identical} stays strict.
|
|
13
14
|
**/
|
|
14
15
|
function compareOpportunities(input) {
|
|
15
16
|
const onchainRows = indexById(input.onchain.data);
|
|
@@ -31,6 +32,7 @@ function compareOpportunities(input) {
|
|
|
31
32
|
onchainName: row.name,
|
|
32
33
|
offchainName: counterpart.name,
|
|
33
34
|
identical: diffs.length === 0,
|
|
35
|
+
clean: diffs.every((diff) => diff.expected),
|
|
34
36
|
diffs
|
|
35
37
|
});
|
|
36
38
|
}
|
|
@@ -76,143 +78,60 @@ function toRef(row) {
|
|
|
76
78
|
* Every field two versions of one opportunity disagree on.
|
|
77
79
|
**/
|
|
78
80
|
function diffOpportunity(onchain, offchain) {
|
|
79
|
-
|
|
80
|
-
diffValue("", onchain, offchain, diffs);
|
|
81
|
-
return diffs;
|
|
82
|
-
}
|
|
83
|
-
function diffValue(path, onchain, offchain, out) {
|
|
84
|
-
if (isAbsent(onchain) && isAbsent(offchain)) return;
|
|
85
|
-
if (isAbsent(onchain) || isAbsent(offchain)) {
|
|
86
|
-
out.push({
|
|
87
|
-
path,
|
|
88
|
-
onchain,
|
|
89
|
-
offchain,
|
|
90
|
-
kind: "presence"
|
|
91
|
-
});
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (Array.isArray(onchain) && Array.isArray(offchain)) {
|
|
95
|
-
diffArray(path, onchain, offchain, out);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (isRecord(onchain) && isRecord(offchain)) {
|
|
99
|
-
for (const key of union(Object.keys(onchain), Object.keys(offchain))) diffValue(join(path, key), onchain[key], offchain[key], out);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!sameScalar(onchain, offchain)) out.push({
|
|
103
|
-
path,
|
|
104
|
-
onchain,
|
|
105
|
-
offchain,
|
|
106
|
-
kind: scalarKind(path, onchain)
|
|
107
|
-
});
|
|
81
|
+
return require_dev_fieldDiff.diffObjects(onchain, offchain).map((diff) => tagDiff(diff, onchain.kind));
|
|
108
82
|
}
|
|
109
83
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* is reported as such rather than shifting every later element into a diff.
|
|
84
|
+
* Paths whose values are basis-point rates that routinely differ by ±1 from
|
|
85
|
+
* truncation vs rounding, plus pool `utilization` for the same reason.
|
|
113
86
|
**/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
path,
|
|
121
|
-
onchain,
|
|
122
|
-
offchain,
|
|
123
|
-
kind: "other"
|
|
124
|
-
});
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
onchain.forEach((element, index) => {
|
|
128
|
-
diffValue(`${path}[${index}]`, element, offchain[index], out);
|
|
129
|
-
});
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
for (const key of union([...onchainKeyed.keys()], [...offchainKeyed.keys()])) diffValue(`${path}[${key}]`, onchainKeyed.get(key), offchainKeyed.get(key), out);
|
|
133
|
-
}
|
|
87
|
+
const BPS_RATE_PATHS = /* @__PURE__ */ new Set([
|
|
88
|
+
"borrowApy",
|
|
89
|
+
"supplyApy.organicApy",
|
|
90
|
+
"additionalBorrowApy",
|
|
91
|
+
"utilization"
|
|
92
|
+
]);
|
|
134
93
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
94
|
+
* Amount fields whose bigint `value` moves with expected-liquidity accrual
|
|
95
|
+
* between the backend's last sync and the current block. Strategy
|
|
96
|
+
* `totalBorrow.value` and `maxBorrowAmount.value` are not in this set: those
|
|
97
|
+
* disagreements are formula bugs, not lag.
|
|
137
98
|
**/
|
|
138
|
-
|
|
139
|
-
const keyed = /* @__PURE__ */ new Map();
|
|
140
|
-
for (const value of values) {
|
|
141
|
-
if (!isRecord(value)) return;
|
|
142
|
-
const identity = value.address ?? value.id ?? value.token;
|
|
143
|
-
if (typeof identity !== "string") return;
|
|
144
|
-
keyed.set(identity.toLowerCase(), value);
|
|
145
|
-
}
|
|
146
|
-
return keyed.size === values.length ? keyed : void 0;
|
|
147
|
-
}
|
|
148
|
-
const ADDRESS = /^0x[0-9a-f]{40}$/i;
|
|
99
|
+
const LAG_AMOUNT_PATHS = /* @__PURE__ */ new Set(["totalSupply.value", "availableLiquidity.value"]);
|
|
149
100
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
101
|
+
* Fields documented `@mode offchain` in the model: the chain has nothing to
|
|
102
|
+
* put there, so a presence (or nested) mismatch is expected. Strategy
|
|
103
|
+
* `utilization` is in this set; pool `utilization` is not.
|
|
153
104
|
**/
|
|
154
|
-
function
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
105
|
+
function isModeScoped(path, kind) {
|
|
106
|
+
if (path === "curator.url") return true;
|
|
107
|
+
if (path === "totalApy" || path.endsWith(".totalApy")) return true;
|
|
108
|
+
if (path === "rewards" || path.startsWith("rewards[") || path.includes(".rewards[") || path.endsWith(".rewards")) return true;
|
|
109
|
+
if (kind !== "strategy") return false;
|
|
110
|
+
return path === "utilization" || path === "collateralApy" || path.startsWith("collateralApy.") || path === "maxLeverageApy" || path.startsWith("maxLeverageApy.") || path === "totalValue" || path.startsWith("totalValue.");
|
|
111
|
+
}
|
|
112
|
+
function tagDiff(diff, kind) {
|
|
113
|
+
if (isModeScoped(diff.path, kind)) return require_dev_fieldDiff.withExpected(diff, "mode-scoped");
|
|
114
|
+
if (withinTolerance(diff)) return require_dev_fieldDiff.withExpected(diff, "tolerance");
|
|
115
|
+
return diff;
|
|
116
|
+
}
|
|
117
|
+
function withinTolerance(diff) {
|
|
118
|
+
if (diff.kind === "usd") return require_dev_fieldDiff.isUsdWithinTolerance(diff.onchain, diff.offchain);
|
|
119
|
+
if (diff.kind !== "numeric") return false;
|
|
120
|
+
if (BPS_RATE_PATHS.has(diff.path)) return require_dev_fieldDiff.isBpsWithinTolerance(diff.onchain, diff.offchain);
|
|
121
|
+
if (LAG_AMOUNT_PATHS.has(diff.path)) return require_dev_fieldDiff.isAmountWithinTolerance(diff.onchain, diff.offchain);
|
|
122
|
+
return false;
|
|
161
123
|
}
|
|
162
124
|
function summarize(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
|
|
163
|
-
const byChain = union(onchain.map((row) => String(row.chainId)), offchain.map((row) => String(row.chainId))).map((chainId) => ({
|
|
125
|
+
const byChain = require_dev_fieldDiff.union(onchain.map((row) => String(row.chainId)), offchain.map((row) => String(row.chainId))).map((chainId) => ({
|
|
164
126
|
chainId: Number(chainId),
|
|
165
|
-
...
|
|
127
|
+
...require_dev_fieldDiff.toCompareCounts(onchain.filter((row) => String(row.chainId) === chainId).length, offchain.filter((row) => String(row.chainId) === chainId).length, onlyOnchain.filter((ref) => String(ref.chainId) === chainId).length, onlyOffchain.filter((ref) => String(ref.chainId) === chainId).length, matched.filter((match) => String(match.chainId) === chainId))
|
|
166
128
|
})).sort((a, b) => a.chainId - b.chainId);
|
|
167
129
|
return {
|
|
168
|
-
...
|
|
130
|
+
...require_dev_fieldDiff.toCompareCounts(onchain.length, offchain.length, onlyOnchain.length, onlyOffchain.length, matched),
|
|
169
131
|
byChain,
|
|
170
|
-
diffsByPath: countPaths(matched)
|
|
132
|
+
diffsByPath: require_dev_fieldDiff.countPaths(matched.flatMap((match) => match.diffs))
|
|
171
133
|
};
|
|
172
134
|
}
|
|
173
|
-
function count(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
|
|
174
|
-
const identical = matched.filter((match) => match.identical).length;
|
|
175
|
-
return {
|
|
176
|
-
onchainRows: onchain.length,
|
|
177
|
-
offchainRows: offchain.length,
|
|
178
|
-
matched: matched.length,
|
|
179
|
-
identical,
|
|
180
|
-
differing: matched.length - identical,
|
|
181
|
-
onlyOnchain: onlyOnchain.length,
|
|
182
|
-
onlyOffchain: onlyOffchain.length
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* How often each field differed, with array keys collapsed so that the same
|
|
187
|
-
* field of a hundred collateral tokens counts as one path.
|
|
188
|
-
**/
|
|
189
|
-
function countPaths(matched) {
|
|
190
|
-
const counts = /* @__PURE__ */ new Map();
|
|
191
|
-
for (const match of matched) for (const diff of match.diffs) {
|
|
192
|
-
const path = diff.path.replace(/\[[^\]]*\]/g, "[]");
|
|
193
|
-
const entry = counts.get(path) ?? {
|
|
194
|
-
path,
|
|
195
|
-
kinds: [],
|
|
196
|
-
count: 0
|
|
197
|
-
};
|
|
198
|
-
entry.count += 1;
|
|
199
|
-
if (!entry.kinds.includes(diff.kind)) entry.kinds.push(diff.kind);
|
|
200
|
-
counts.set(path, entry);
|
|
201
|
-
}
|
|
202
|
-
return [...counts.values()].sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
|
|
203
|
-
}
|
|
204
|
-
function isAbsent(value) {
|
|
205
|
-
return value === void 0 || value === null;
|
|
206
|
-
}
|
|
207
|
-
function isRecord(value) {
|
|
208
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
209
|
-
}
|
|
210
|
-
function union(left, right) {
|
|
211
|
-
return [.../* @__PURE__ */ new Set([...left, ...right])];
|
|
212
|
-
}
|
|
213
|
-
function join(path, key) {
|
|
214
|
-
return path ? `${path}.${key}` : key;
|
|
215
|
-
}
|
|
216
135
|
//#endregion
|
|
217
136
|
exports.compareOpportunities = compareOpportunities;
|
|
218
137
|
exports.diffOpportunity = diffOpportunity;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_model_positions = require("../model/positions.js");
|
|
3
|
+
require("../model/index.js");
|
|
4
|
+
const require_dev_fieldDiff = require("./fieldDiff.js");
|
|
5
|
+
//#region src/dev/comparePositions.ts
|
|
6
|
+
/**
|
|
7
|
+
* Matches two position listings per wallet by {@link positionId} and reports
|
|
8
|
+
* every field the two sources disagree on.
|
|
9
|
+
*
|
|
10
|
+
* Nothing is filtered out. A field only one mode can fill, or a USD value that
|
|
11
|
+
* drifted within snapshot-lag noise, is still reported — tagged
|
|
12
|
+
* {@link FieldDiff.expected} so that {@link CompareCounts.clean} can ignore it
|
|
13
|
+
* while {@link CompareCounts.identical} stays strict.
|
|
14
|
+
**/
|
|
15
|
+
function comparePositions(input) {
|
|
16
|
+
const failures = input.failures ?? [];
|
|
17
|
+
const wallets = [...input.wallets.map(compareWallet), ...failures.map((failure) => ({
|
|
18
|
+
wallet: failure.wallet,
|
|
19
|
+
error: failure.error,
|
|
20
|
+
onlyOnchain: [],
|
|
21
|
+
onlyOffchain: [],
|
|
22
|
+
matched: []
|
|
23
|
+
}))];
|
|
24
|
+
wallets.sort((a, b) => a.wallet.localeCompare(b.wallet));
|
|
25
|
+
return {
|
|
26
|
+
generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
|
+
backendUrl: input.backendUrl,
|
|
28
|
+
networks: [...input.networks],
|
|
29
|
+
onchainChains: collectChains(input.wallets, "onchain"),
|
|
30
|
+
offchainChains: collectChains(input.wallets, "offchain"),
|
|
31
|
+
summary: summarize(input.wallets, wallets, failures.length),
|
|
32
|
+
wallets
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Every field two versions of one position disagree on.
|
|
37
|
+
**/
|
|
38
|
+
function diffPosition(onchain, offchain) {
|
|
39
|
+
return require_dev_fieldDiff.diffObjects(onchain, offchain, { keyOf: keyOfPositionArray }).map((diff) => tagDiff(diff, onchain.kind));
|
|
40
|
+
}
|
|
41
|
+
function compareWallet(input) {
|
|
42
|
+
const onchainRows = indexById(input.onchain.data);
|
|
43
|
+
const offchainRows = indexById(input.offchain.data);
|
|
44
|
+
const onlyOnchain = [];
|
|
45
|
+
const onlyOffchain = [];
|
|
46
|
+
const matched = [];
|
|
47
|
+
for (const [id, row] of onchainRows) {
|
|
48
|
+
const counterpart = offchainRows.get(id);
|
|
49
|
+
if (!counterpart) {
|
|
50
|
+
onlyOnchain.push(toRef(row));
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const diffs = diffPosition(row, counterpart);
|
|
54
|
+
matched.push({
|
|
55
|
+
id,
|
|
56
|
+
kind: row.kind,
|
|
57
|
+
chainId: row.chainId,
|
|
58
|
+
onchainName: row.name,
|
|
59
|
+
offchainName: counterpart.name,
|
|
60
|
+
identical: diffs.length === 0,
|
|
61
|
+
clean: diffs.every((diff) => diff.expected),
|
|
62
|
+
diffs
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
for (const [id, row] of offchainRows) if (!onchainRows.has(id)) onlyOffchain.push(toRef(row));
|
|
66
|
+
onlyOnchain.sort((a, b) => a.id.localeCompare(b.id));
|
|
67
|
+
onlyOffchain.sort((a, b) => a.id.localeCompare(b.id));
|
|
68
|
+
matched.sort((a, b) => a.id.localeCompare(b.id));
|
|
69
|
+
return {
|
|
70
|
+
wallet: input.wallet,
|
|
71
|
+
onlyOnchain,
|
|
72
|
+
onlyOffchain,
|
|
73
|
+
matched
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function indexById(rows) {
|
|
77
|
+
return new Map(rows.map((row) => [require_model_positions.positionId(row), row]));
|
|
78
|
+
}
|
|
79
|
+
function toRef(row) {
|
|
80
|
+
const base = {
|
|
81
|
+
id: require_model_positions.positionId(row),
|
|
82
|
+
kind: row.kind,
|
|
83
|
+
chainId: row.chainId,
|
|
84
|
+
name: row.name
|
|
85
|
+
};
|
|
86
|
+
switch (row.kind) {
|
|
87
|
+
case "pool": return {
|
|
88
|
+
...base,
|
|
89
|
+
pool: row.pool
|
|
90
|
+
};
|
|
91
|
+
case "strategy": return {
|
|
92
|
+
...base,
|
|
93
|
+
creditAccount: row.creditAccount,
|
|
94
|
+
creditManager: row.creditManager
|
|
95
|
+
};
|
|
96
|
+
case "liquidation": return base;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `collaterals[]` is keyed by the collateral token, not by a top-level
|
|
101
|
+
* address. Rewards-points programs already identify themselves by `id`.
|
|
102
|
+
**/
|
|
103
|
+
function keyOfPositionArray(path, value) {
|
|
104
|
+
if (path !== "collaterals" || !require_dev_fieldDiff.isRecord(value) || !require_dev_fieldDiff.isRecord(value.collateral)) return;
|
|
105
|
+
const token = value.collateral.token;
|
|
106
|
+
return require_dev_fieldDiff.isRecord(token) && typeof token.address === "string" ? token.address : void 0;
|
|
107
|
+
}
|
|
108
|
+
const BPS_RATE_PATHS = /* @__PURE__ */ new Set([
|
|
109
|
+
"borrowApy",
|
|
110
|
+
"apy.organicApy",
|
|
111
|
+
"healthFactor"
|
|
112
|
+
]);
|
|
113
|
+
/**
|
|
114
|
+
* Amount fields whose bigint `value` moves with interest accrual between the
|
|
115
|
+
* backend's last sync and the current block.
|
|
116
|
+
**/
|
|
117
|
+
function isLagAmountPath(path) {
|
|
118
|
+
return path === "totalDebt.value" || path === "totalValue.value" || path === "netValue.value" || path.endsWith(".collateral.value") || path.endsWith(".quota.value");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Fields documented `@mode offchain` or `@mode onchain` in the model.
|
|
122
|
+
**/
|
|
123
|
+
function isModeScoped(path, kind) {
|
|
124
|
+
if (path === "pnl" || path.startsWith("pnl.")) return true;
|
|
125
|
+
if (kind === "pool") return path === "apy.totalApy" || path.startsWith("apy.rewards");
|
|
126
|
+
if (kind === "strategy") return path === "netApy" || path.startsWith("netApy.") || path === "borrowRate" || path.startsWith("borrowRate.") || path === "timeToLiquidation" || path === "liquidationPrice";
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
function tagDiff(diff, kind) {
|
|
130
|
+
if (isModeScoped(diff.path, kind)) return require_dev_fieldDiff.withExpected(diff, "mode-scoped");
|
|
131
|
+
if (withinTolerance(diff)) return require_dev_fieldDiff.withExpected(diff, "tolerance");
|
|
132
|
+
return diff;
|
|
133
|
+
}
|
|
134
|
+
function withinTolerance(diff) {
|
|
135
|
+
if (diff.kind === "usd") return require_dev_fieldDiff.isUsdWithinTolerance(diff.onchain, diff.offchain);
|
|
136
|
+
if (diff.kind !== "numeric") return false;
|
|
137
|
+
if (diff.path === "leverage") return require_dev_fieldDiff.withinRelative(require_dev_fieldDiff.asFiniteNumber(diff.onchain), require_dev_fieldDiff.asFiniteNumber(diff.offchain), require_dev_fieldDiff.USD_RELATIVE_EPSILON);
|
|
138
|
+
if (BPS_RATE_PATHS.has(diff.path)) return require_dev_fieldDiff.isBpsWithinTolerance(diff.onchain, diff.offchain);
|
|
139
|
+
if (isLagAmountPath(diff.path)) return require_dev_fieldDiff.isAmountWithinTolerance(diff.onchain, diff.offchain);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
function summarize(inputs, wallets, failed) {
|
|
143
|
+
const compared = wallets.filter((wallet) => !wallet.error);
|
|
144
|
+
const allOnchain = inputs.flatMap((input) => input.onchain.data);
|
|
145
|
+
const allOffchain = inputs.flatMap((input) => input.offchain.data);
|
|
146
|
+
const onlyOnchain = compared.flatMap((wallet) => wallet.onlyOnchain);
|
|
147
|
+
const onlyOffchain = compared.flatMap((wallet) => wallet.onlyOffchain);
|
|
148
|
+
const matched = compared.flatMap((wallet) => wallet.matched);
|
|
149
|
+
const byChain = require_dev_fieldDiff.union(allOnchain.map((row) => String(row.chainId)), allOffchain.map((row) => String(row.chainId))).map((chainId) => ({
|
|
150
|
+
chainId: Number(chainId),
|
|
151
|
+
...require_dev_fieldDiff.toCompareCounts(allOnchain.filter((row) => String(row.chainId) === chainId).length, allOffchain.filter((row) => String(row.chainId) === chainId).length, onlyOnchain.filter((ref) => String(ref.chainId) === chainId).length, onlyOffchain.filter((ref) => String(ref.chainId) === chainId).length, matched.filter((match) => String(match.chainId) === chainId))
|
|
152
|
+
})).sort((a, b) => a.chainId - b.chainId);
|
|
153
|
+
const byWallet = wallets.map((wallet) => ({
|
|
154
|
+
wallet: wallet.wallet,
|
|
155
|
+
...wallet.error ? { error: wallet.error } : {},
|
|
156
|
+
...require_dev_fieldDiff.toCompareCounts(wallet.matched.length + wallet.onlyOnchain.length, wallet.matched.length + wallet.onlyOffchain.length, wallet.onlyOnchain.length, wallet.onlyOffchain.length, wallet.matched)
|
|
157
|
+
}));
|
|
158
|
+
const walletsClean = compared.filter((wallet) => wallet.onlyOnchain.length === 0 && wallet.onlyOffchain.length === 0 && wallet.matched.every((match) => match.clean)).length;
|
|
159
|
+
return {
|
|
160
|
+
...require_dev_fieldDiff.toCompareCounts(allOnchain.length, allOffchain.length, onlyOnchain.length, onlyOffchain.length, matched),
|
|
161
|
+
wallets: wallets.length,
|
|
162
|
+
walletsClean,
|
|
163
|
+
walletsFailed: failed,
|
|
164
|
+
byChain,
|
|
165
|
+
byWallet,
|
|
166
|
+
diffsByPath: require_dev_fieldDiff.countPaths(matched.flatMap((match) => match.diffs))
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* One metadata entry per chain, preferring a success so the report names the
|
|
171
|
+
* block. Later wallets of the same chain are ignored.
|
|
172
|
+
**/
|
|
173
|
+
function collectChains(wallets, side) {
|
|
174
|
+
const byChain = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const wallet of wallets) for (const chain of wallet[side].meta.chains) {
|
|
176
|
+
const existing = byChain.get(chain.chainId);
|
|
177
|
+
if (!existing || existing.status !== "success" && chain.status === "success") byChain.set(chain.chainId, chain);
|
|
178
|
+
}
|
|
179
|
+
return [...byChain.values()].sort((a, b) => a.chainId - b.chainId);
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
exports.comparePositions = comparePositions;
|
|
183
|
+
exports.diffPosition = diffPosition;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/dev/fieldDiff.ts
|
|
3
|
+
/**
|
|
4
|
+
* Relative drift allowed on {@link Amount.valueUsd} before a USD float is a
|
|
5
|
+
* real disagreement: 0.1%.
|
|
6
|
+
**/
|
|
7
|
+
const USD_RELATIVE_EPSILON = .001;
|
|
8
|
+
/**
|
|
9
|
+
* Relative drift allowed on lag-bounded bigint amounts: 0.05%.
|
|
10
|
+
**/
|
|
11
|
+
const AMOUNT_RELATIVE_EPSILON = 5e-4;
|
|
12
|
+
/**
|
|
13
|
+
* Absolute drift allowed on bps rates before rounding and a one-block lag are
|
|
14
|
+
* no longer enough to explain it.
|
|
15
|
+
**/
|
|
16
|
+
const BPS_ABSOLUTE_EPSILON = 1;
|
|
17
|
+
/**
|
|
18
|
+
* Every field two versions of one value disagree on. Nothing is filtered or
|
|
19
|
+
* tagged: the caller marks expected diffs afterwards.
|
|
20
|
+
**/
|
|
21
|
+
function diffValue(path, onchain, offchain, out, options) {
|
|
22
|
+
if (isAbsent(onchain) && isAbsent(offchain)) return;
|
|
23
|
+
if (isAbsent(onchain) || isAbsent(offchain)) {
|
|
24
|
+
out.push({
|
|
25
|
+
path,
|
|
26
|
+
onchain,
|
|
27
|
+
offchain,
|
|
28
|
+
kind: "presence"
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(onchain) && Array.isArray(offchain)) {
|
|
33
|
+
diffArray(path, onchain, offchain, out, options);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (isRecord(onchain) && isRecord(offchain)) {
|
|
37
|
+
for (const key of union(Object.keys(onchain), Object.keys(offchain))) diffValue(join(path, key), onchain[key], offchain[key], out, options);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!sameScalar(onchain, offchain)) out.push({
|
|
41
|
+
path,
|
|
42
|
+
onchain,
|
|
43
|
+
offchain,
|
|
44
|
+
kind: scalarKind(path, onchain)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Deep-diff two values and return the disagreements, in path order of
|
|
49
|
+
* discovery.
|
|
50
|
+
**/
|
|
51
|
+
function diffObjects(onchain, offchain, options) {
|
|
52
|
+
const diffs = [];
|
|
53
|
+
diffValue("", onchain, offchain, diffs, options);
|
|
54
|
+
return diffs;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Mark a diff expected, so a later `clean` count can ignore it.
|
|
58
|
+
**/
|
|
59
|
+
function withExpected(diff, reason) {
|
|
60
|
+
return {
|
|
61
|
+
...diff,
|
|
62
|
+
expected: true,
|
|
63
|
+
reason
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* How often each field differed, with array keys collapsed so that the same
|
|
68
|
+
* field of a hundred collateral tokens counts as one path. Sorted so the
|
|
69
|
+
* unexpected disagreements come first.
|
|
70
|
+
**/
|
|
71
|
+
function countPaths(diffs) {
|
|
72
|
+
const counts = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const diff of diffs) {
|
|
74
|
+
const path = collapseArrayKeys(diff.path);
|
|
75
|
+
const entry = counts.get(path) ?? {
|
|
76
|
+
path,
|
|
77
|
+
kinds: [],
|
|
78
|
+
count: 0,
|
|
79
|
+
expected: 0,
|
|
80
|
+
unexpected: 0
|
|
81
|
+
};
|
|
82
|
+
entry.count += 1;
|
|
83
|
+
if (diff.expected) entry.expected += 1;
|
|
84
|
+
else entry.unexpected += 1;
|
|
85
|
+
if (!entry.kinds.includes(diff.kind)) entry.kinds.push(diff.kind);
|
|
86
|
+
counts.set(path, entry);
|
|
87
|
+
}
|
|
88
|
+
return [...counts.values()].sort((a, b) => b.unexpected - a.unexpected || b.count - a.count || a.path.localeCompare(b.path));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Collapse `collateralTokens[0xa0b8...].symbol` to `collateralTokens[].symbol`.
|
|
92
|
+
**/
|
|
93
|
+
function collapseArrayKeys(path) {
|
|
94
|
+
return path.replace(/\[[^\]]*\]/g, "[]");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Membership and match totals of one comparison, from already-built lists.
|
|
98
|
+
**/
|
|
99
|
+
function toCompareCounts(onchainRows, offchainRows, onlyOnchain, onlyOffchain, matched) {
|
|
100
|
+
const identical = matched.filter((match) => match.identical).length;
|
|
101
|
+
const clean = matched.filter((match) => match.clean).length;
|
|
102
|
+
return {
|
|
103
|
+
onchainRows,
|
|
104
|
+
offchainRows,
|
|
105
|
+
matched: matched.length,
|
|
106
|
+
identical,
|
|
107
|
+
clean,
|
|
108
|
+
differing: matched.length - identical,
|
|
109
|
+
onlyOnchain,
|
|
110
|
+
onlyOffchain
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* USD floats within {@link USD_RELATIVE_EPSILON}.
|
|
115
|
+
**/
|
|
116
|
+
function isUsdWithinTolerance(onchain, offchain) {
|
|
117
|
+
return withinRelative(asFiniteNumber(onchain), asFiniteNumber(offchain), USD_RELATIVE_EPSILON);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Bps rates that differ by at most {@link BPS_ABSOLUTE_EPSILON}.
|
|
121
|
+
**/
|
|
122
|
+
function isBpsWithinTolerance(onchain, offchain) {
|
|
123
|
+
const left = asFiniteNumber(onchain);
|
|
124
|
+
const right = asFiniteNumber(offchain);
|
|
125
|
+
return left !== void 0 && right !== void 0 && Math.abs(left - right) <= 1;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Bigint amounts within {@link AMOUNT_RELATIVE_EPSILON}.
|
|
129
|
+
**/
|
|
130
|
+
function isAmountWithinTolerance(onchain, offchain) {
|
|
131
|
+
return typeof onchain === "bigint" && typeof offchain === "bigint" && withinRelativeBigint(onchain, offchain, 5e-4);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Finite numbers within a relative epsilon of each other.
|
|
135
|
+
**/
|
|
136
|
+
function withinRelative(onchain, offchain, epsilon) {
|
|
137
|
+
if (onchain === void 0 || offchain === void 0) return false;
|
|
138
|
+
const scale = Math.max(Math.abs(onchain), Math.abs(offchain));
|
|
139
|
+
return scale === 0 ? true : Math.abs(onchain - offchain) / scale <= epsilon;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* `diff / max(|a|, |b|) <= epsilon`, computed in integer arithmetic so a
|
|
143
|
+
* 1e18-scale amount does not round through `Number`.
|
|
144
|
+
**/
|
|
145
|
+
function withinRelativeBigint(onchain, offchain, epsilon) {
|
|
146
|
+
if (onchain === offchain) return true;
|
|
147
|
+
const diff = onchain > offchain ? onchain - offchain : offchain - onchain;
|
|
148
|
+
const scale = abs(onchain) > abs(offchain) ? abs(onchain) : abs(offchain);
|
|
149
|
+
if (scale === 0n) return true;
|
|
150
|
+
return diff * BigInt(Math.round(1 / epsilon)) <= scale;
|
|
151
|
+
}
|
|
152
|
+
function asFiniteNumber(value) {
|
|
153
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
154
|
+
}
|
|
155
|
+
function union(left, right) {
|
|
156
|
+
return [.../* @__PURE__ */ new Set([...left, ...right])];
|
|
157
|
+
}
|
|
158
|
+
function isRecord(value) {
|
|
159
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
160
|
+
}
|
|
161
|
+
function diffArray(path, onchain, offchain, out, options) {
|
|
162
|
+
const onchainKeyed = keyElements(path, onchain, options?.keyOf);
|
|
163
|
+
const offchainKeyed = keyElements(path, offchain, options?.keyOf);
|
|
164
|
+
if (!onchainKeyed || !offchainKeyed) {
|
|
165
|
+
if (onchain.length !== offchain.length) {
|
|
166
|
+
out.push({
|
|
167
|
+
path,
|
|
168
|
+
onchain,
|
|
169
|
+
offchain,
|
|
170
|
+
kind: "other"
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
onchain.forEach((element, index) => {
|
|
175
|
+
diffValue(`${path}[${index}]`, element, offchain[index], out, options);
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
for (const key of union([...onchainKeyed.keys()], [...offchainKeyed.keys()])) diffValue(`${path}[${key}]`, onchainKeyed.get(key), offchainKeyed.get(key), out, options);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The array indexed by each element's own identity, or `undefined` when its
|
|
183
|
+
* elements have none and order is all there is to go by.
|
|
184
|
+
**/
|
|
185
|
+
function keyElements(path, values, keyOf) {
|
|
186
|
+
const keyed = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const value of values) {
|
|
188
|
+
const identity = keyOf?.(path, value) ?? defaultIdentity(value);
|
|
189
|
+
if (typeof identity !== "string") return;
|
|
190
|
+
keyed.set(identity.toLowerCase(), value);
|
|
191
|
+
}
|
|
192
|
+
return keyed.size === values.length ? keyed : void 0;
|
|
193
|
+
}
|
|
194
|
+
function defaultIdentity(value) {
|
|
195
|
+
if (!isRecord(value)) return;
|
|
196
|
+
if (typeof value.address === "string") return value.address;
|
|
197
|
+
if (typeof value.id === "string") return value.id;
|
|
198
|
+
if (typeof value.token === "string") return value.token;
|
|
199
|
+
if (isRecord(value.token) && typeof value.token.address === "string") return value.token.address;
|
|
200
|
+
}
|
|
201
|
+
const ADDRESS = /^0x[0-9a-f]{40}$/i;
|
|
202
|
+
/**
|
|
203
|
+
* Only addresses are compared case-insensitively: the backend lowercases them
|
|
204
|
+
* while the chain hands out checksummed ones, which is not a disagreement. A
|
|
205
|
+
* symbol or a name spelled differently is.
|
|
206
|
+
**/
|
|
207
|
+
function sameScalar(onchain, offchain) {
|
|
208
|
+
if (typeof onchain === "string" && typeof offchain === "string" && ADDRESS.test(onchain) && ADDRESS.test(offchain)) return onchain.toLowerCase() === offchain.toLowerCase();
|
|
209
|
+
return onchain === offchain;
|
|
210
|
+
}
|
|
211
|
+
function scalarKind(path, onchain) {
|
|
212
|
+
if (path.endsWith("valueUsd")) return "usd";
|
|
213
|
+
return typeof onchain === "number" || typeof onchain === "bigint" ? "numeric" : "other";
|
|
214
|
+
}
|
|
215
|
+
function isAbsent(value) {
|
|
216
|
+
return value === void 0 || value === null;
|
|
217
|
+
}
|
|
218
|
+
function join(path, key) {
|
|
219
|
+
return path ? `${path}.${key}` : key;
|
|
220
|
+
}
|
|
221
|
+
function abs(value) {
|
|
222
|
+
return value < 0n ? -value : value;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
exports.AMOUNT_RELATIVE_EPSILON = AMOUNT_RELATIVE_EPSILON;
|
|
226
|
+
exports.BPS_ABSOLUTE_EPSILON = BPS_ABSOLUTE_EPSILON;
|
|
227
|
+
exports.USD_RELATIVE_EPSILON = USD_RELATIVE_EPSILON;
|
|
228
|
+
exports.asFiniteNumber = asFiniteNumber;
|
|
229
|
+
exports.collapseArrayKeys = collapseArrayKeys;
|
|
230
|
+
exports.countPaths = countPaths;
|
|
231
|
+
exports.diffObjects = diffObjects;
|
|
232
|
+
exports.diffValue = diffValue;
|
|
233
|
+
exports.isAmountWithinTolerance = isAmountWithinTolerance;
|
|
234
|
+
exports.isBpsWithinTolerance = isBpsWithinTolerance;
|
|
235
|
+
exports.isRecord = isRecord;
|
|
236
|
+
exports.isUsdWithinTolerance = isUsdWithinTolerance;
|
|
237
|
+
exports.toCompareCounts = toCompareCounts;
|
|
238
|
+
exports.union = union;
|
|
239
|
+
exports.withExpected = withExpected;
|
|
240
|
+
exports.withinRelative = withinRelative;
|
|
241
|
+
exports.withinRelativeBigint = withinRelativeBigint;
|
|
@@ -24,8 +24,15 @@ function rayToBps(ray) {
|
|
|
24
24
|
return Number(ray * require_sdk_constants_math.PERCENTAGE_FACTOR / require_sdk_constants_math.RAY);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
+
* Raw 8-decimal USD values below this are compressor leftover dust (typically
|
|
28
|
+
* 1-wei balances on empty accounts) and report as `0`.
|
|
29
|
+
**/
|
|
30
|
+
const USD_DUST_THRESHOLD = 1000n;
|
|
31
|
+
/**
|
|
27
32
|
* Converts a USD value in the oracle's 8-decimal fixed point to a float.
|
|
28
33
|
*
|
|
34
|
+
* Values below {@link USD_DUST_THRESHOLD} report as `0`.
|
|
35
|
+
*
|
|
29
36
|
* @example
|
|
30
37
|
* ```ts
|
|
31
38
|
* // usd: $1500.50 in 8-decimal fixed point
|
|
@@ -33,7 +40,7 @@ function rayToBps(ray) {
|
|
|
33
40
|
* ```
|
|
34
41
|
**/
|
|
35
42
|
function usdToNumber(usd) {
|
|
36
|
-
return Number(usd) / Number(require_sdk_constants_math.PRICE_DECIMALS);
|
|
43
|
+
return usd < USD_DUST_THRESHOLD ? 0 : Number(usd) / Number(require_sdk_constants_math.PRICE_DECIMALS);
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
39
46
|
* Share of capital currently borrowed, in basis points. Returns `0` when there
|
|
@@ -415,7 +415,7 @@ var PoolService = class extends require_sdk_base_SDKConstruct.SDKConstruct {
|
|
|
415
415
|
const { pool } = market.pool;
|
|
416
416
|
return {
|
|
417
417
|
kind: "pool",
|
|
418
|
-
name:
|
|
418
|
+
name: this.sdk.tokensMeta.mustGetToken(pool.address).name,
|
|
419
419
|
chainId: this.chainId,
|
|
420
420
|
pool: pool.address,
|
|
421
421
|
netValue: {
|