protoscan 0.1.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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2648 -0
- package/package.json +45 -0
- package/protoscan.config.json +20 -0
- package/src/chains.ts +38 -0
- package/src/config.ts +36 -0
- package/src/diff.ts +113 -0
- package/src/index.ts +289 -0
- package/src/protocols/common.ts +66 -0
- package/src/protocols/index.ts +2 -0
- package/src/protocols/proxy.ts +50 -0
- package/src/registry/aave-v3.ts +242 -0
- package/src/registry/compound-v3.ts +238 -0
- package/src/registry/eigenlayer.ts +190 -0
- package/src/registry/index.ts +38 -0
- package/src/registry/lido.ts +557 -0
- package/src/registry/maker.ts +176 -0
- package/src/registry/morpho-blue.ts +347 -0
- package/src/registry/pendle.ts +120 -0
- package/src/registry/schema.ts +40 -0
- package/src/registry/uniswap-v3.ts +198 -0
- package/src/scanner.ts +181 -0
- package/src/snapshot.ts +70 -0
- package/src/types.ts +67 -0
- package/tsconfig.json +17 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2648 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
|
|
8
|
+
// src/config.ts
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
10
|
+
var CONFIG_FILE = "protoscan.config.json";
|
|
11
|
+
var DEFAULT_CONFIG = {
|
|
12
|
+
chains: {
|
|
13
|
+
ethereum: {
|
|
14
|
+
rpc: "https://eth.llamarpc.com",
|
|
15
|
+
name: "Ethereum Mainnet"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
contracts: {}
|
|
19
|
+
};
|
|
20
|
+
function loadConfig() {
|
|
21
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`No ${CONFIG_FILE} found. Run "protoscan init" to create one.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
function initConfig() {
|
|
30
|
+
if (existsSync(CONFIG_FILE)) {
|
|
31
|
+
throw new Error(`${CONFIG_FILE} already exists.`);
|
|
32
|
+
}
|
|
33
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf-8");
|
|
34
|
+
return CONFIG_FILE;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/scanner.ts
|
|
38
|
+
import { createPublicClient, http } from "viem";
|
|
39
|
+
|
|
40
|
+
// src/chains.ts
|
|
41
|
+
import {
|
|
42
|
+
mainnet,
|
|
43
|
+
arbitrum,
|
|
44
|
+
optimism,
|
|
45
|
+
polygon,
|
|
46
|
+
base,
|
|
47
|
+
avalanche,
|
|
48
|
+
bsc,
|
|
49
|
+
gnosis,
|
|
50
|
+
scroll,
|
|
51
|
+
linea,
|
|
52
|
+
blast,
|
|
53
|
+
zkSync
|
|
54
|
+
} from "viem/chains";
|
|
55
|
+
var chainMap = {
|
|
56
|
+
ethereum: mainnet,
|
|
57
|
+
arbitrum,
|
|
58
|
+
optimism,
|
|
59
|
+
polygon,
|
|
60
|
+
base,
|
|
61
|
+
avalanche,
|
|
62
|
+
bsc,
|
|
63
|
+
gnosis,
|
|
64
|
+
scroll,
|
|
65
|
+
linea,
|
|
66
|
+
blast,
|
|
67
|
+
zksync: zkSync
|
|
68
|
+
};
|
|
69
|
+
function resolveChain(name) {
|
|
70
|
+
return chainMap[name.toLowerCase()];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/protocols/proxy.ts
|
|
74
|
+
import { getAddress, pad, trim } from "viem";
|
|
75
|
+
var IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
76
|
+
var ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
77
|
+
var BEACON_SLOT = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50";
|
|
78
|
+
var OZ_IMPL_SLOT = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3";
|
|
79
|
+
async function readSlotAsAddress(client, address, slot) {
|
|
80
|
+
try {
|
|
81
|
+
const value = await client.getStorageAt({ address, slot });
|
|
82
|
+
if (!value || value === "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const trimmed = trim(value);
|
|
86
|
+
if (trimmed === "0x" || trimmed === "0x0") return null;
|
|
87
|
+
return getAddress(pad(trimmed, { size: 20 }));
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function detectProxy(client, address) {
|
|
93
|
+
const [implementation, admin, beacon] = await Promise.all([
|
|
94
|
+
readSlotAsAddress(client, address, IMPLEMENTATION_SLOT),
|
|
95
|
+
readSlotAsAddress(client, address, ADMIN_SLOT),
|
|
96
|
+
readSlotAsAddress(client, address, BEACON_SLOT)
|
|
97
|
+
]);
|
|
98
|
+
if (!implementation && !admin && !beacon) {
|
|
99
|
+
const ozImpl = await readSlotAsAddress(client, address, OZ_IMPL_SLOT);
|
|
100
|
+
if (ozImpl) {
|
|
101
|
+
return { implementation: ozImpl, admin: null, beacon: null };
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return { implementation, admin, beacon };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/protocols/common.ts
|
|
109
|
+
import { parseAbi } from "viem";
|
|
110
|
+
var ownershipAbi = parseAbi([
|
|
111
|
+
"function owner() view returns (address)",
|
|
112
|
+
"function admin() view returns (address)",
|
|
113
|
+
"function guardian() view returns (address)",
|
|
114
|
+
"function pendingOwner() view returns (address)",
|
|
115
|
+
"function paused() view returns (bool)",
|
|
116
|
+
"function timelock() view returns (address)",
|
|
117
|
+
"function governance() view returns (address)"
|
|
118
|
+
]);
|
|
119
|
+
var tokenAbi = parseAbi([
|
|
120
|
+
"function name() view returns (string)",
|
|
121
|
+
"function symbol() view returns (string)",
|
|
122
|
+
"function decimals() view returns (uint8)",
|
|
123
|
+
"function totalSupply() view returns (uint256)"
|
|
124
|
+
]);
|
|
125
|
+
async function tryRead(client, address, abi, functionName) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await client.readContract({
|
|
128
|
+
address,
|
|
129
|
+
abi,
|
|
130
|
+
functionName
|
|
131
|
+
});
|
|
132
|
+
return { key: functionName, value: String(result) };
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function readCommonState(client, address) {
|
|
138
|
+
const reads = await Promise.all([
|
|
139
|
+
tryRead(client, address, ownershipAbi, "owner"),
|
|
140
|
+
tryRead(client, address, ownershipAbi, "admin"),
|
|
141
|
+
tryRead(client, address, ownershipAbi, "guardian"),
|
|
142
|
+
tryRead(client, address, ownershipAbi, "pendingOwner"),
|
|
143
|
+
tryRead(client, address, ownershipAbi, "paused"),
|
|
144
|
+
tryRead(client, address, ownershipAbi, "timelock"),
|
|
145
|
+
tryRead(client, address, ownershipAbi, "governance"),
|
|
146
|
+
tryRead(client, address, tokenAbi, "name"),
|
|
147
|
+
tryRead(client, address, tokenAbi, "symbol"),
|
|
148
|
+
tryRead(client, address, tokenAbi, "decimals"),
|
|
149
|
+
tryRead(client, address, tokenAbi, "totalSupply")
|
|
150
|
+
]);
|
|
151
|
+
const result = {};
|
|
152
|
+
for (const read of reads) {
|
|
153
|
+
if (read) result[read.key] = read.value;
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/registry/aave-v3.ts
|
|
159
|
+
import { parseAbi as parseAbi2 } from "viem";
|
|
160
|
+
var poolAbi = parseAbi2([
|
|
161
|
+
"function getReservesList() view returns (address[])",
|
|
162
|
+
"function getReserveData(address asset) view returns ((uint256 configuration, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt))",
|
|
163
|
+
"function FLASHLOAN_PREMIUM_TOTAL() view returns (uint128)",
|
|
164
|
+
"function FLASHLOAN_PREMIUM_TO_PROTOCOL() view returns (uint128)",
|
|
165
|
+
"function MAX_NUMBER_RESERVES() view returns (uint16)",
|
|
166
|
+
"function BRIDGE_PROTOCOL_FEE() view returns (uint256)"
|
|
167
|
+
]);
|
|
168
|
+
var poolConfiguratorAbi = parseAbi2([
|
|
169
|
+
"function getConfiguratorLogic() view returns (address)"
|
|
170
|
+
]);
|
|
171
|
+
var aclManagerAbi = parseAbi2([
|
|
172
|
+
"function isPoolAdmin(address admin) view returns (bool)",
|
|
173
|
+
"function isEmergencyAdmin(address admin) view returns (bool)",
|
|
174
|
+
"function isRiskAdmin(address admin) view returns (bool)",
|
|
175
|
+
"function isFlashBorrower(address borrower) view returns (bool)",
|
|
176
|
+
"function isAssetListingAdmin(address admin) view returns (bool)",
|
|
177
|
+
"function isBridge(address bridge) view returns (bool)",
|
|
178
|
+
"function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
|
|
179
|
+
"function POOL_ADMIN_ROLE() view returns (bytes32)",
|
|
180
|
+
"function EMERGENCY_ADMIN_ROLE() view returns (bytes32)",
|
|
181
|
+
"function RISK_ADMIN_ROLE() view returns (bytes32)",
|
|
182
|
+
"function FLASH_BORROWER_ROLE() view returns (bytes32)",
|
|
183
|
+
"function ASSET_LISTING_ADMIN_ROLE() view returns (bytes32)",
|
|
184
|
+
"function getRoleAdmin(bytes32 role) view returns (bytes32)"
|
|
185
|
+
]);
|
|
186
|
+
var addressProviderAbi = parseAbi2([
|
|
187
|
+
"function getPool() view returns (address)",
|
|
188
|
+
"function getPoolConfigurator() view returns (address)",
|
|
189
|
+
"function getPriceOracle() view returns (address)",
|
|
190
|
+
"function getACLManager() view returns (address)",
|
|
191
|
+
"function getACLAdmin() view returns (address)",
|
|
192
|
+
"function getPoolDataProvider() view returns (address)",
|
|
193
|
+
"function owner() view returns (address)"
|
|
194
|
+
]);
|
|
195
|
+
var oracleAbi = parseAbi2([
|
|
196
|
+
"function getSourceOfAsset(address asset) view returns (address)",
|
|
197
|
+
"function getFallbackOracle() view returns (address)",
|
|
198
|
+
"function BASE_CURRENCY() view returns (address)",
|
|
199
|
+
"function BASE_CURRENCY_UNIT() view returns (uint256)"
|
|
200
|
+
]);
|
|
201
|
+
var ownerAbi = parseAbi2([
|
|
202
|
+
"function owner() view returns (address)"
|
|
203
|
+
]);
|
|
204
|
+
var aaveV3 = {
|
|
205
|
+
id: "aave-v3",
|
|
206
|
+
name: "Aave V3",
|
|
207
|
+
version: "3.0",
|
|
208
|
+
website: "https://aave.com",
|
|
209
|
+
description: "Decentralized non-custodial liquidity protocol",
|
|
210
|
+
contracts: {
|
|
211
|
+
PoolAddressesProvider: { name: "PoolAddressesProvider", role: "Registry for protocol addresses", type: "raw" },
|
|
212
|
+
Pool: { name: "Pool", role: "Main lending/borrowing pool", type: "proxy" },
|
|
213
|
+
PoolConfigurator: { name: "PoolConfigurator", role: "Protocol parameter configuration", type: "proxy" },
|
|
214
|
+
ACLManager: { name: "ACLManager", role: "Access control management", type: "raw" },
|
|
215
|
+
AaveOracle: { name: "AaveOracle", role: "Price oracle aggregator", type: "raw" },
|
|
216
|
+
PoolDataProvider: { name: "PoolDataProvider", role: "Data provider for protocol state", type: "raw" }
|
|
217
|
+
},
|
|
218
|
+
deployments: {
|
|
219
|
+
ethereum: {
|
|
220
|
+
PoolAddressesProvider: "0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e",
|
|
221
|
+
Pool: "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
|
|
222
|
+
PoolConfigurator: "0x64b761D848206f447Fe2dd461b0c635Ec39EbB27",
|
|
223
|
+
ACLManager: "0xc2aaCf6553D20d1e9571216fA22D988C6Cb18E6",
|
|
224
|
+
AaveOracle: "0x54586bE62E3c3580375aE3723C145253060Ca0C2",
|
|
225
|
+
PoolDataProvider: "0x7B4EB56E7CD4b454BA8ff71E4518426c8A0EB0e3"
|
|
226
|
+
},
|
|
227
|
+
arbitrum: {
|
|
228
|
+
PoolAddressesProvider: "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb",
|
|
229
|
+
Pool: "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
|
|
230
|
+
PoolConfigurator: "0x8145eddDf43f50276641b55bd3AD95944510021",
|
|
231
|
+
ACLManager: "0xa72636CbcAa8F5FF95B2cc47Ed3D1dED7EF16cc",
|
|
232
|
+
AaveOracle: "0xb56c2F0B653B2e0b10C9b928C8580Ac5Df02C7C7",
|
|
233
|
+
PoolDataProvider: "0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654"
|
|
234
|
+
},
|
|
235
|
+
base: {
|
|
236
|
+
PoolAddressesProvider: "0xe20fCBdBfFC4Dd138cE8b2E6FBb6CB49777ad64D",
|
|
237
|
+
Pool: "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
|
|
238
|
+
PoolConfigurator: "0x5731a04B1E775f0fdd454Bf70f3335886e9A96be",
|
|
239
|
+
ACLManager: "0x43955b0899Ab7232E3a454cf84AedD22Ad46FD33",
|
|
240
|
+
AaveOracle: "0x2Cc0Fc26eD4563A5ce5e8bdcfe1A2878676Ae156",
|
|
241
|
+
PoolDataProvider: "0x2d8A3C5677189723C4cB8873CfC9C8976FDF38Ac"
|
|
242
|
+
},
|
|
243
|
+
optimism: {
|
|
244
|
+
PoolAddressesProvider: "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb",
|
|
245
|
+
Pool: "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
|
|
246
|
+
PoolConfigurator: "0x8145eddDf43f50276641b55bd3AD95944510021",
|
|
247
|
+
ACLManager: "0xa72636CbcAa8F5FF95B2cc47Ed3D1dED7EF16cc",
|
|
248
|
+
AaveOracle: "0xD81eb3728a631871a7eBBaD631b5f424909f0c77",
|
|
249
|
+
PoolDataProvider: "0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654"
|
|
250
|
+
},
|
|
251
|
+
polygon: {
|
|
252
|
+
PoolAddressesProvider: "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb",
|
|
253
|
+
Pool: "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
|
|
254
|
+
PoolConfigurator: "0x8145eddDf43f50276641b55bd3AD95944510021",
|
|
255
|
+
ACLManager: "0xa72636CbcAa8F5FF95B2cc47Ed3D1dED7EF16cc",
|
|
256
|
+
AaveOracle: "0xb023e699F5a33916Ea823A16485e259257cA8Bd1",
|
|
257
|
+
PoolDataProvider: "0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
params: [
|
|
261
|
+
{
|
|
262
|
+
name: "flashloanPremiumTotal",
|
|
263
|
+
description: "Total flashloan fee in bps",
|
|
264
|
+
contract: "Pool",
|
|
265
|
+
functionName: "FLASHLOAN_PREMIUM_TOTAL",
|
|
266
|
+
abi: poolAbi,
|
|
267
|
+
decode: "bps",
|
|
268
|
+
category: "config",
|
|
269
|
+
severity: "warning"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "flashloanPremiumToProtocol",
|
|
273
|
+
description: "Flashloan fee portion going to protocol treasury",
|
|
274
|
+
contract: "Pool",
|
|
275
|
+
functionName: "FLASHLOAN_PREMIUM_TO_PROTOCOL",
|
|
276
|
+
abi: poolAbi,
|
|
277
|
+
decode: "bps",
|
|
278
|
+
category: "config",
|
|
279
|
+
severity: "warning"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "bridgeProtocolFee",
|
|
283
|
+
description: "Bridge protocol fee",
|
|
284
|
+
contract: "Pool",
|
|
285
|
+
functionName: "BRIDGE_PROTOCOL_FEE",
|
|
286
|
+
abi: poolAbi,
|
|
287
|
+
decode: "uint",
|
|
288
|
+
category: "config",
|
|
289
|
+
severity: "warning"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "maxReserves",
|
|
293
|
+
description: "Maximum number of supported reserves",
|
|
294
|
+
contract: "Pool",
|
|
295
|
+
functionName: "MAX_NUMBER_RESERVES",
|
|
296
|
+
abi: poolAbi,
|
|
297
|
+
decode: "uint",
|
|
298
|
+
category: "config",
|
|
299
|
+
severity: "info"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "priceOracle",
|
|
303
|
+
description: "Active price oracle address",
|
|
304
|
+
contract: "PoolAddressesProvider",
|
|
305
|
+
functionName: "getPriceOracle",
|
|
306
|
+
abi: addressProviderAbi,
|
|
307
|
+
decode: "address",
|
|
308
|
+
category: "config",
|
|
309
|
+
severity: "critical"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "aclAdmin",
|
|
313
|
+
description: "ACL admin \u2014 can grant/revoke all roles",
|
|
314
|
+
contract: "PoolAddressesProvider",
|
|
315
|
+
functionName: "getACLAdmin",
|
|
316
|
+
abi: addressProviderAbi,
|
|
317
|
+
decode: "address",
|
|
318
|
+
category: "access",
|
|
319
|
+
severity: "critical"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "addressProviderOwner",
|
|
323
|
+
description: "Owner of PoolAddressesProvider \u2014 can replace all core contracts",
|
|
324
|
+
contract: "PoolAddressesProvider",
|
|
325
|
+
functionName: "owner",
|
|
326
|
+
abi: addressProviderAbi,
|
|
327
|
+
decode: "address",
|
|
328
|
+
category: "access",
|
|
329
|
+
severity: "critical"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "fallbackOracle",
|
|
333
|
+
description: "Fallback oracle used when primary source fails",
|
|
334
|
+
contract: "AaveOracle",
|
|
335
|
+
functionName: "getFallbackOracle",
|
|
336
|
+
abi: oracleAbi,
|
|
337
|
+
decode: "address",
|
|
338
|
+
category: "config",
|
|
339
|
+
severity: "warning"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "baseCurrency",
|
|
343
|
+
description: "Base currency for price quotes",
|
|
344
|
+
contract: "AaveOracle",
|
|
345
|
+
functionName: "BASE_CURRENCY",
|
|
346
|
+
abi: oracleAbi,
|
|
347
|
+
decode: "address",
|
|
348
|
+
category: "config",
|
|
349
|
+
severity: "warning"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: "baseCurrencyUnit",
|
|
353
|
+
description: "Base currency unit (decimals)",
|
|
354
|
+
contract: "AaveOracle",
|
|
355
|
+
functionName: "BASE_CURRENCY_UNIT",
|
|
356
|
+
abi: oracleAbi,
|
|
357
|
+
decode: "uint",
|
|
358
|
+
category: "config",
|
|
359
|
+
severity: "info"
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
access: [
|
|
363
|
+
{
|
|
364
|
+
role: "AddressesProvider Owner",
|
|
365
|
+
description: "Can replace Pool, Configurator, Oracle, ACLManager \u2014 god key",
|
|
366
|
+
contract: "PoolAddressesProvider",
|
|
367
|
+
functionName: "owner",
|
|
368
|
+
abi: ownerAbi,
|
|
369
|
+
severity: "critical"
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
role: "ACL Admin",
|
|
373
|
+
description: "Can grant/revoke PoolAdmin, EmergencyAdmin, RiskAdmin roles",
|
|
374
|
+
contract: "PoolAddressesProvider",
|
|
375
|
+
functionName: "getACLAdmin",
|
|
376
|
+
abi: addressProviderAbi,
|
|
377
|
+
severity: "critical"
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
role: "Oracle Owner",
|
|
381
|
+
description: "Can change price feed sources \u2014 oracle manipulation vector",
|
|
382
|
+
contract: "AaveOracle",
|
|
383
|
+
functionName: "owner",
|
|
384
|
+
abi: ownerAbi,
|
|
385
|
+
severity: "critical"
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// src/registry/compound-v3.ts
|
|
391
|
+
import { parseAbi as parseAbi3 } from "viem";
|
|
392
|
+
var cometAbi = parseAbi3([
|
|
393
|
+
"function governor() view returns (address)",
|
|
394
|
+
"function pauseGuardian() view returns (address)",
|
|
395
|
+
"function baseToken() view returns (address)",
|
|
396
|
+
"function baseTokenPriceFeed() view returns (address)",
|
|
397
|
+
"function extensionDelegate() view returns (address)",
|
|
398
|
+
"function supplyKink() view returns (uint256)",
|
|
399
|
+
"function supplyPerSecondInterestRateSlopeLow() view returns (uint256)",
|
|
400
|
+
"function supplyPerSecondInterestRateSlopeHigh() view returns (uint256)",
|
|
401
|
+
"function supplyPerSecondInterestRateBase() view returns (uint256)",
|
|
402
|
+
"function borrowKink() view returns (uint256)",
|
|
403
|
+
"function borrowPerSecondInterestRateSlopeLow() view returns (uint256)",
|
|
404
|
+
"function borrowPerSecondInterestRateSlopeHigh() view returns (uint256)",
|
|
405
|
+
"function borrowPerSecondInterestRateBase() view returns (uint256)",
|
|
406
|
+
"function storeFrontPriceFactor() view returns (uint256)",
|
|
407
|
+
"function baseTrackingSupplySpeed() view returns (uint256)",
|
|
408
|
+
"function baseTrackingBorrowSpeed() view returns (uint256)",
|
|
409
|
+
"function baseMinForRewards() view returns (uint256)",
|
|
410
|
+
"function baseBorrowMin() view returns (uint256)",
|
|
411
|
+
"function targetReserves() view returns (uint256)",
|
|
412
|
+
"function totalSupply() view returns (uint256)",
|
|
413
|
+
"function totalBorrow() view returns (uint256)",
|
|
414
|
+
"function isSupplyPaused() view returns (bool)",
|
|
415
|
+
"function isTransferPaused() view returns (bool)",
|
|
416
|
+
"function isWithdrawPaused() view returns (bool)",
|
|
417
|
+
"function isAbsorbPaused() view returns (bool)",
|
|
418
|
+
"function isBuyPaused() view returns (bool)",
|
|
419
|
+
"function numAssets() view returns (uint8)",
|
|
420
|
+
"function getAssetInfo(uint8 i) view returns ((uint8 offset, address asset, address priceFeed, uint64 scale, uint64 borrowCollateralFactor, uint64 liquidateCollateralFactor, uint64 liquidationFactor, uint128 supplyCap))"
|
|
421
|
+
]);
|
|
422
|
+
var configuratorAbi = parseAbi3([
|
|
423
|
+
"function governor() view returns (address)",
|
|
424
|
+
"function factory() view returns (address)"
|
|
425
|
+
]);
|
|
426
|
+
var ownerAbi2 = parseAbi3([
|
|
427
|
+
"function owner() view returns (address)"
|
|
428
|
+
]);
|
|
429
|
+
var compoundV3 = {
|
|
430
|
+
id: "compound-v3",
|
|
431
|
+
name: "Compound V3 (Comet)",
|
|
432
|
+
version: "3.0",
|
|
433
|
+
website: "https://compound.finance",
|
|
434
|
+
description: "Algorithmic money market protocol",
|
|
435
|
+
contracts: {
|
|
436
|
+
CometUSDC: { name: "Comet USDC", role: "Main USDC lending market", type: "proxy" },
|
|
437
|
+
CometWETH: { name: "Comet WETH", role: "Main WETH lending market", type: "proxy" },
|
|
438
|
+
Configurator: { name: "Configurator", role: "Protocol configuration manager", type: "proxy" },
|
|
439
|
+
Timelock: { name: "Timelock", role: "Governance timelock", type: "raw" }
|
|
440
|
+
},
|
|
441
|
+
deployments: {
|
|
442
|
+
ethereum: {
|
|
443
|
+
CometUSDC: "0xc3d688B66703497DAA19211EEdff47f25384cdc3",
|
|
444
|
+
CometWETH: "0xA17581A9E3356d9A858b789D68B4d866e593aE94",
|
|
445
|
+
Configurator: "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3",
|
|
446
|
+
Timelock: "0x6d903f6003cca6255D85CcA4D3B5E5146dC33925"
|
|
447
|
+
},
|
|
448
|
+
arbitrum: {
|
|
449
|
+
CometUSDC: "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA",
|
|
450
|
+
CometWETH: "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf",
|
|
451
|
+
Configurator: "0xb21b06D71c75973babdE35b49fFDAc3F82Ad3775",
|
|
452
|
+
Timelock: "0x3fB4d38ea7EC20D91c150F5e41C5a4652c6AE4AA"
|
|
453
|
+
},
|
|
454
|
+
base: {
|
|
455
|
+
CometUSDC: "0xb125E6687d4313864e53df431d5425969c15Eb2F",
|
|
456
|
+
CometWETH: "0x46e6b214b524310239732D51387075E0e70970bf",
|
|
457
|
+
Configurator: "0x45939657d1CA34A8FA39A924B71D28Fe8431e581",
|
|
458
|
+
Timelock: "0xCC3E7c85Bb0EE4f09380e041fee95a0caeDD4a02"
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
params: [
|
|
462
|
+
{
|
|
463
|
+
name: "governor",
|
|
464
|
+
description: "Protocol governor \u2014 can change all parameters",
|
|
465
|
+
contract: "CometUSDC",
|
|
466
|
+
functionName: "governor",
|
|
467
|
+
abi: cometAbi,
|
|
468
|
+
decode: "address",
|
|
469
|
+
category: "access",
|
|
470
|
+
severity: "critical"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "pauseGuardian",
|
|
474
|
+
description: "Can pause supply/withdraw/transfer/absorb/buy",
|
|
475
|
+
contract: "CometUSDC",
|
|
476
|
+
functionName: "pauseGuardian",
|
|
477
|
+
abi: cometAbi,
|
|
478
|
+
decode: "address",
|
|
479
|
+
category: "access",
|
|
480
|
+
severity: "critical"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: "baseToken",
|
|
484
|
+
description: "Base token for this Comet market",
|
|
485
|
+
contract: "CometUSDC",
|
|
486
|
+
functionName: "baseToken",
|
|
487
|
+
abi: cometAbi,
|
|
488
|
+
decode: "address",
|
|
489
|
+
category: "config",
|
|
490
|
+
severity: "critical"
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: "baseTokenPriceFeed",
|
|
494
|
+
description: "Price feed for base token",
|
|
495
|
+
contract: "CometUSDC",
|
|
496
|
+
functionName: "baseTokenPriceFeed",
|
|
497
|
+
abi: cometAbi,
|
|
498
|
+
decode: "address",
|
|
499
|
+
category: "config",
|
|
500
|
+
severity: "critical"
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: "supplyKink",
|
|
504
|
+
description: "Utilization rate kink point for supply rate",
|
|
505
|
+
contract: "CometUSDC",
|
|
506
|
+
functionName: "supplyKink",
|
|
507
|
+
abi: cometAbi,
|
|
508
|
+
decode: "wad",
|
|
509
|
+
category: "rate",
|
|
510
|
+
severity: "warning"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "borrowKink",
|
|
514
|
+
description: "Utilization rate kink point for borrow rate",
|
|
515
|
+
contract: "CometUSDC",
|
|
516
|
+
functionName: "borrowKink",
|
|
517
|
+
abi: cometAbi,
|
|
518
|
+
decode: "wad",
|
|
519
|
+
category: "rate",
|
|
520
|
+
severity: "warning"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: "targetReserves",
|
|
524
|
+
description: "Target reserve amount",
|
|
525
|
+
contract: "CometUSDC",
|
|
526
|
+
functionName: "targetReserves",
|
|
527
|
+
abi: cometAbi,
|
|
528
|
+
decode: "uint",
|
|
529
|
+
category: "config",
|
|
530
|
+
severity: "warning"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: "isSupplyPaused",
|
|
534
|
+
description: "Whether supply operations are paused",
|
|
535
|
+
contract: "CometUSDC",
|
|
536
|
+
functionName: "isSupplyPaused",
|
|
537
|
+
abi: cometAbi,
|
|
538
|
+
decode: "bool",
|
|
539
|
+
category: "state",
|
|
540
|
+
severity: "critical"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: "isWithdrawPaused",
|
|
544
|
+
description: "Whether withdraw operations are paused",
|
|
545
|
+
contract: "CometUSDC",
|
|
546
|
+
functionName: "isWithdrawPaused",
|
|
547
|
+
abi: cometAbi,
|
|
548
|
+
decode: "bool",
|
|
549
|
+
category: "state",
|
|
550
|
+
severity: "critical"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "isTransferPaused",
|
|
554
|
+
description: "Whether transfer operations are paused",
|
|
555
|
+
contract: "CometUSDC",
|
|
556
|
+
functionName: "isTransferPaused",
|
|
557
|
+
abi: cometAbi,
|
|
558
|
+
decode: "bool",
|
|
559
|
+
category: "state",
|
|
560
|
+
severity: "warning"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "totalSupply",
|
|
564
|
+
description: "Total supplied amount",
|
|
565
|
+
contract: "CometUSDC",
|
|
566
|
+
functionName: "totalSupply",
|
|
567
|
+
abi: cometAbi,
|
|
568
|
+
decode: "uint",
|
|
569
|
+
category: "state",
|
|
570
|
+
severity: "info"
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: "totalBorrow",
|
|
574
|
+
description: "Total borrowed amount",
|
|
575
|
+
contract: "CometUSDC",
|
|
576
|
+
functionName: "totalBorrow",
|
|
577
|
+
abi: cometAbi,
|
|
578
|
+
decode: "uint",
|
|
579
|
+
category: "state",
|
|
580
|
+
severity: "info"
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "numAssets",
|
|
584
|
+
description: "Number of collateral assets",
|
|
585
|
+
contract: "CometUSDC",
|
|
586
|
+
functionName: "numAssets",
|
|
587
|
+
abi: cometAbi,
|
|
588
|
+
decode: "uint",
|
|
589
|
+
category: "config",
|
|
590
|
+
severity: "info"
|
|
591
|
+
}
|
|
592
|
+
],
|
|
593
|
+
access: [
|
|
594
|
+
{
|
|
595
|
+
role: "Governor",
|
|
596
|
+
description: "Full control \u2014 can change all parameters, pause, upgrade",
|
|
597
|
+
contract: "CometUSDC",
|
|
598
|
+
functionName: "governor",
|
|
599
|
+
abi: cometAbi,
|
|
600
|
+
severity: "critical"
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
role: "Pause Guardian",
|
|
604
|
+
description: "Can pause supply/withdraw/transfer/absorb/buy operations",
|
|
605
|
+
contract: "CometUSDC",
|
|
606
|
+
functionName: "pauseGuardian",
|
|
607
|
+
abi: cometAbi,
|
|
608
|
+
severity: "critical"
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
role: "Configurator Governor",
|
|
612
|
+
description: "Can update Comet market configurations",
|
|
613
|
+
contract: "Configurator",
|
|
614
|
+
functionName: "governor",
|
|
615
|
+
abi: configuratorAbi,
|
|
616
|
+
severity: "critical"
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// src/registry/uniswap-v3.ts
|
|
622
|
+
import { parseAbi as parseAbi4 } from "viem";
|
|
623
|
+
var factoryAbi = parseAbi4([
|
|
624
|
+
"function owner() view returns (address)",
|
|
625
|
+
"function feeAmountTickSpacing(uint24 fee) view returns (int24)",
|
|
626
|
+
"function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)"
|
|
627
|
+
]);
|
|
628
|
+
var swapRouterAbi = parseAbi4([
|
|
629
|
+
"function factory() view returns (address)",
|
|
630
|
+
"function factoryV2() view returns (address)",
|
|
631
|
+
"function positionManager() view returns (address)",
|
|
632
|
+
"function WETH9() view returns (address)"
|
|
633
|
+
]);
|
|
634
|
+
var positionManagerAbi = parseAbi4([
|
|
635
|
+
"function factory() view returns (address)",
|
|
636
|
+
"function WETH9() view returns (address)"
|
|
637
|
+
]);
|
|
638
|
+
var ownerAbi3 = parseAbi4([
|
|
639
|
+
"function owner() view returns (address)"
|
|
640
|
+
]);
|
|
641
|
+
var uniswapV3 = {
|
|
642
|
+
id: "uniswap-v3",
|
|
643
|
+
name: "Uniswap V3",
|
|
644
|
+
version: "3.0",
|
|
645
|
+
website: "https://uniswap.org",
|
|
646
|
+
description: "Concentrated liquidity automated market maker protocol",
|
|
647
|
+
contracts: {
|
|
648
|
+
Factory: { name: "UniswapV3Factory", role: "Deploys pools, controls fee tiers and protocol fee switch \u2014 sole admin surface in the protocol", type: "raw" },
|
|
649
|
+
SwapRouter: { name: "SwapRouter02", role: "Multicall swap router for exact-in/out swaps across V2 & V3 pools, immutable with no admin", type: "raw" },
|
|
650
|
+
NonfungiblePositionManager: { name: "NonfungiblePositionManager", role: "ERC-721 wrapper for concentrated liquidity positions, immutable with no admin", type: "raw" },
|
|
651
|
+
QuoterV2: { name: "QuoterV2", role: "Off-chain quote simulation via revert-based swap simulation, stateless", type: "raw" }
|
|
652
|
+
},
|
|
653
|
+
deployments: {
|
|
654
|
+
ethereum: {
|
|
655
|
+
Factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
656
|
+
SwapRouter: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
|
|
657
|
+
NonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
|
658
|
+
QuoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"
|
|
659
|
+
},
|
|
660
|
+
arbitrum: {
|
|
661
|
+
Factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
662
|
+
SwapRouter: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
|
|
663
|
+
NonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
|
664
|
+
QuoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"
|
|
665
|
+
},
|
|
666
|
+
optimism: {
|
|
667
|
+
Factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
668
|
+
SwapRouter: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
|
|
669
|
+
NonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
|
670
|
+
QuoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"
|
|
671
|
+
},
|
|
672
|
+
polygon: {
|
|
673
|
+
Factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
674
|
+
SwapRouter: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
|
|
675
|
+
NonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
|
676
|
+
QuoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"
|
|
677
|
+
},
|
|
678
|
+
base: {
|
|
679
|
+
Factory: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD",
|
|
680
|
+
SwapRouter: "0x2626664c2603336E57B271c5C0b26F421741e481",
|
|
681
|
+
NonfungiblePositionManager: "0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1",
|
|
682
|
+
QuoterV2: "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a"
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
params: [
|
|
686
|
+
{
|
|
687
|
+
name: "factoryOwner",
|
|
688
|
+
description: "Factory owner \u2014 can enable fee tiers, transfer ownership, and set/collect protocol fee on any pool",
|
|
689
|
+
contract: "Factory",
|
|
690
|
+
functionName: "owner",
|
|
691
|
+
abi: factoryAbi,
|
|
692
|
+
decode: "address",
|
|
693
|
+
category: "access",
|
|
694
|
+
severity: "critical"
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
name: "feeTier100TickSpacing",
|
|
698
|
+
description: "Tick spacing for the 0.01% fee tier (1 = enabled, stable-stable pairs)",
|
|
699
|
+
contract: "Factory",
|
|
700
|
+
functionName: "feeAmountTickSpacing",
|
|
701
|
+
args: [100],
|
|
702
|
+
abi: factoryAbi,
|
|
703
|
+
decode: "uint",
|
|
704
|
+
category: "config",
|
|
705
|
+
severity: "info"
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: "feeTier500TickSpacing",
|
|
709
|
+
description: "Tick spacing for the 0.05% fee tier (10 = enabled, correlated pairs)",
|
|
710
|
+
contract: "Factory",
|
|
711
|
+
functionName: "feeAmountTickSpacing",
|
|
712
|
+
args: [500],
|
|
713
|
+
abi: factoryAbi,
|
|
714
|
+
decode: "uint",
|
|
715
|
+
category: "config",
|
|
716
|
+
severity: "info"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
name: "feeTier3000TickSpacing",
|
|
720
|
+
description: "Tick spacing for the 0.3% fee tier (60 = enabled, default general pairs)",
|
|
721
|
+
contract: "Factory",
|
|
722
|
+
functionName: "feeAmountTickSpacing",
|
|
723
|
+
args: [3e3],
|
|
724
|
+
abi: factoryAbi,
|
|
725
|
+
decode: "uint",
|
|
726
|
+
category: "config",
|
|
727
|
+
severity: "info"
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: "feeTier10000TickSpacing",
|
|
731
|
+
description: "Tick spacing for the 1% fee tier (200 = enabled, exotic pairs)",
|
|
732
|
+
contract: "Factory",
|
|
733
|
+
functionName: "feeAmountTickSpacing",
|
|
734
|
+
args: [1e4],
|
|
735
|
+
abi: factoryAbi,
|
|
736
|
+
decode: "uint",
|
|
737
|
+
category: "config",
|
|
738
|
+
severity: "info"
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
name: "routerFactory",
|
|
742
|
+
description: "Factory address the SwapRouter02 is wired to \u2014 must match canonical Factory or swaps route to a rogue deployment",
|
|
743
|
+
contract: "SwapRouter",
|
|
744
|
+
functionName: "factory",
|
|
745
|
+
abi: swapRouterAbi,
|
|
746
|
+
decode: "address",
|
|
747
|
+
category: "config",
|
|
748
|
+
severity: "warning"
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
name: "routerPositionManager",
|
|
752
|
+
description: "NonfungiblePositionManager address referenced by SwapRouter02 (used for approve-and-call flows)",
|
|
753
|
+
contract: "SwapRouter",
|
|
754
|
+
functionName: "positionManager",
|
|
755
|
+
abi: swapRouterAbi,
|
|
756
|
+
decode: "address",
|
|
757
|
+
category: "config",
|
|
758
|
+
severity: "info"
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
name: "routerWETH9",
|
|
762
|
+
description: "Wrapped native token used by SwapRouter02 for ETH-in/out swaps",
|
|
763
|
+
contract: "SwapRouter",
|
|
764
|
+
functionName: "WETH9",
|
|
765
|
+
abi: swapRouterAbi,
|
|
766
|
+
decode: "address",
|
|
767
|
+
category: "config",
|
|
768
|
+
severity: "info"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
name: "positionManagerFactory",
|
|
772
|
+
description: "Factory address the NonfungiblePositionManager is wired to \u2014 must match canonical Factory",
|
|
773
|
+
contract: "NonfungiblePositionManager",
|
|
774
|
+
functionName: "factory",
|
|
775
|
+
abi: positionManagerAbi,
|
|
776
|
+
decode: "address",
|
|
777
|
+
category: "config",
|
|
778
|
+
severity: "warning"
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "positionManagerWETH9",
|
|
782
|
+
description: "Wrapped native token used by the NonfungiblePositionManager for mint/increase/decrease liquidity in native ETH",
|
|
783
|
+
contract: "NonfungiblePositionManager",
|
|
784
|
+
functionName: "WETH9",
|
|
785
|
+
abi: positionManagerAbi,
|
|
786
|
+
decode: "address",
|
|
787
|
+
category: "config",
|
|
788
|
+
severity: "info"
|
|
789
|
+
}
|
|
790
|
+
],
|
|
791
|
+
access: [
|
|
792
|
+
{
|
|
793
|
+
role: "Factory Owner \u2014 Fee Tier Governance",
|
|
794
|
+
description: "Can permanently enable new fee tiers via enableFeeAmount (irreversible once set) and transfer factory ownership via setOwner",
|
|
795
|
+
contract: "Factory",
|
|
796
|
+
functionName: "owner",
|
|
797
|
+
abi: ownerAbi3,
|
|
798
|
+
severity: "critical"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
role: "Factory Owner \u2014 Protocol Fee Switch",
|
|
802
|
+
description: "Can call the onlyFactoryOwner-gated setFeeProtocol and collectProtocol on ANY pool deployed by this factory \u2014 full economic control over the protocol-wide fee switch (up to 1/4 of LP swap fees) across every pool",
|
|
803
|
+
contract: "Factory",
|
|
804
|
+
functionName: "owner",
|
|
805
|
+
abi: ownerAbi3,
|
|
806
|
+
severity: "critical"
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/registry/maker.ts
|
|
812
|
+
import { parseAbi as parseAbi5 } from "viem";
|
|
813
|
+
var vatAbi = parseAbi5([
|
|
814
|
+
"function Line() view returns (uint256)",
|
|
815
|
+
"function live() view returns (uint256)",
|
|
816
|
+
"function debt() view returns (uint256)"
|
|
817
|
+
]);
|
|
818
|
+
var jugAbi = parseAbi5([
|
|
819
|
+
"function base() view returns (uint256)"
|
|
820
|
+
]);
|
|
821
|
+
var potAbi = parseAbi5([
|
|
822
|
+
"function dsr() view returns (uint256)",
|
|
823
|
+
"function chi() view returns (uint256)",
|
|
824
|
+
"function Pie() view returns (uint256)"
|
|
825
|
+
]);
|
|
826
|
+
var dogAbi = parseAbi5([
|
|
827
|
+
"function live() view returns (uint256)",
|
|
828
|
+
"function Hole() view returns (uint256)",
|
|
829
|
+
"function Dirt() view returns (uint256)"
|
|
830
|
+
]);
|
|
831
|
+
var pauseAbi = parseAbi5([
|
|
832
|
+
"function owner() view returns (address)",
|
|
833
|
+
"function authority() view returns (address)",
|
|
834
|
+
"function delay() view returns (uint256)"
|
|
835
|
+
]);
|
|
836
|
+
var ownerAbi4 = parseAbi5([
|
|
837
|
+
"function owner() view returns (address)"
|
|
838
|
+
]);
|
|
839
|
+
var maker = {
|
|
840
|
+
id: "maker",
|
|
841
|
+
name: "Maker (Sky)",
|
|
842
|
+
version: "MCD",
|
|
843
|
+
website: "https://makerdao.com",
|
|
844
|
+
description: "Multi-collateral DAI stablecoin system",
|
|
845
|
+
contracts: {
|
|
846
|
+
MCD_VAT: { name: "Vat", role: "Core vault engine \u2014 tracks all debt and collateral", type: "raw" },
|
|
847
|
+
MCD_JUG: { name: "Jug", role: "Stability fee accumulator", type: "raw" },
|
|
848
|
+
MCD_POT: { name: "Pot", role: "DAI Savings Rate module", type: "raw" },
|
|
849
|
+
MCD_DOG: { name: "Dog", role: "Liquidation engine", type: "raw" },
|
|
850
|
+
MCD_SPOT: { name: "Spotter", role: "Oracle and liquidation ratio manager", type: "raw" },
|
|
851
|
+
MCD_PAUSE: { name: "DSPause", role: "Governance timelock", type: "raw" },
|
|
852
|
+
MCD_ADM: { name: "DSChief", role: "Governance voting contract", type: "raw" }
|
|
853
|
+
},
|
|
854
|
+
deployments: {
|
|
855
|
+
ethereum: {
|
|
856
|
+
MCD_VAT: "0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B",
|
|
857
|
+
MCD_JUG: "0x19c0976f590D67707E62397C87829d896Dc0f1F1",
|
|
858
|
+
MCD_POT: "0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7",
|
|
859
|
+
MCD_DOG: "0x135954d155898D42C90D2a57824C690e0c7BEf1B",
|
|
860
|
+
MCD_SPOT: "0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3",
|
|
861
|
+
MCD_PAUSE: "0xbE286431454714F511008713973d3B053A2d38f3",
|
|
862
|
+
MCD_ADM: "0x0a3f6849f78076aefaDf113F5BED87720274dDC0"
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
params: [
|
|
866
|
+
{
|
|
867
|
+
name: "globalDebtCeiling",
|
|
868
|
+
description: "Global debt ceiling (Line) \u2014 max DAI that can be minted",
|
|
869
|
+
contract: "MCD_VAT",
|
|
870
|
+
functionName: "Line",
|
|
871
|
+
abi: vatAbi,
|
|
872
|
+
decode: "ray",
|
|
873
|
+
category: "risk",
|
|
874
|
+
severity: "critical"
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "totalDebt",
|
|
878
|
+
description: "Total system debt",
|
|
879
|
+
contract: "MCD_VAT",
|
|
880
|
+
functionName: "debt",
|
|
881
|
+
abi: vatAbi,
|
|
882
|
+
decode: "ray",
|
|
883
|
+
category: "state",
|
|
884
|
+
severity: "info"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: "vatLive",
|
|
888
|
+
description: "Vat live status (1=live, 0=caged/emergency shutdown)",
|
|
889
|
+
contract: "MCD_VAT",
|
|
890
|
+
functionName: "live",
|
|
891
|
+
abi: vatAbi,
|
|
892
|
+
decode: "uint",
|
|
893
|
+
category: "state",
|
|
894
|
+
severity: "critical"
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
name: "baseRate",
|
|
898
|
+
description: "Base stability fee rate applied to all vaults",
|
|
899
|
+
contract: "MCD_JUG",
|
|
900
|
+
functionName: "base",
|
|
901
|
+
abi: jugAbi,
|
|
902
|
+
decode: "ray",
|
|
903
|
+
category: "rate",
|
|
904
|
+
severity: "warning"
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: "dsr",
|
|
908
|
+
description: "DAI Savings Rate \u2014 per-second accumulation rate",
|
|
909
|
+
contract: "MCD_POT",
|
|
910
|
+
functionName: "dsr",
|
|
911
|
+
abi: potAbi,
|
|
912
|
+
decode: "ray",
|
|
913
|
+
category: "rate",
|
|
914
|
+
severity: "warning"
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
name: "dsrTotalDeposits",
|
|
918
|
+
description: "Total DAI deposited in DSR (Pie)",
|
|
919
|
+
contract: "MCD_POT",
|
|
920
|
+
functionName: "Pie",
|
|
921
|
+
abi: potAbi,
|
|
922
|
+
decode: "wad",
|
|
923
|
+
category: "state",
|
|
924
|
+
severity: "info"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
name: "dogLive",
|
|
928
|
+
description: "Liquidation engine live status",
|
|
929
|
+
contract: "MCD_DOG",
|
|
930
|
+
functionName: "live",
|
|
931
|
+
abi: dogAbi,
|
|
932
|
+
decode: "uint",
|
|
933
|
+
category: "state",
|
|
934
|
+
severity: "critical"
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
name: "globalLiquidationLimit",
|
|
938
|
+
description: "Max DAI needed to cover all active auctions (Hole)",
|
|
939
|
+
contract: "MCD_DOG",
|
|
940
|
+
functionName: "Hole",
|
|
941
|
+
abi: dogAbi,
|
|
942
|
+
decode: "ray",
|
|
943
|
+
category: "risk",
|
|
944
|
+
severity: "warning"
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: "timelockDelay",
|
|
948
|
+
description: "Governance timelock delay in seconds",
|
|
949
|
+
contract: "MCD_PAUSE",
|
|
950
|
+
functionName: "delay",
|
|
951
|
+
abi: pauseAbi,
|
|
952
|
+
decode: "uint",
|
|
953
|
+
category: "config",
|
|
954
|
+
severity: "critical"
|
|
955
|
+
}
|
|
956
|
+
],
|
|
957
|
+
access: [
|
|
958
|
+
{
|
|
959
|
+
role: "Pause Owner",
|
|
960
|
+
description: "Owner of governance timelock \u2014 can schedule spells",
|
|
961
|
+
contract: "MCD_PAUSE",
|
|
962
|
+
functionName: "owner",
|
|
963
|
+
abi: pauseAbi,
|
|
964
|
+
severity: "critical"
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
role: "Pause Authority",
|
|
968
|
+
description: "Authority contract \u2014 typically DSChief governance",
|
|
969
|
+
contract: "MCD_PAUSE",
|
|
970
|
+
functionName: "authority",
|
|
971
|
+
abi: pauseAbi,
|
|
972
|
+
severity: "critical"
|
|
973
|
+
}
|
|
974
|
+
]
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// src/registry/eigenlayer.ts
|
|
978
|
+
import { parseAbi as parseAbi6 } from "viem";
|
|
979
|
+
var strategyManagerAbi = parseAbi6([
|
|
980
|
+
"function owner() view returns (address)",
|
|
981
|
+
"function paused() view returns (uint256)",
|
|
982
|
+
"function strategyWhitelister() view returns (address)"
|
|
983
|
+
]);
|
|
984
|
+
var delegationManagerAbi = parseAbi6([
|
|
985
|
+
"function owner() view returns (address)",
|
|
986
|
+
"function paused() view returns (uint256)",
|
|
987
|
+
"function minWithdrawalDelayBlocks() view returns (uint256)",
|
|
988
|
+
"function domainSeparator() view returns (bytes32)"
|
|
989
|
+
]);
|
|
990
|
+
var eigenPodManagerAbi = parseAbi6([
|
|
991
|
+
"function owner() view returns (address)",
|
|
992
|
+
"function paused() view returns (uint256)"
|
|
993
|
+
]);
|
|
994
|
+
var slasherAbi = parseAbi6([
|
|
995
|
+
"function owner() view returns (address)",
|
|
996
|
+
"function paused() view returns (uint256)"
|
|
997
|
+
]);
|
|
998
|
+
var avsDirectoryAbi = parseAbi6([
|
|
999
|
+
"function owner() view returns (address)",
|
|
1000
|
+
"function paused() view returns (uint256)"
|
|
1001
|
+
]);
|
|
1002
|
+
var rewardsCoordinatorAbi = parseAbi6([
|
|
1003
|
+
"function owner() view returns (address)",
|
|
1004
|
+
"function paused() view returns (uint256)",
|
|
1005
|
+
"function activationDelay() view returns (uint32)",
|
|
1006
|
+
"function currRewardsCalculationEndTimestamp() view returns (uint32)",
|
|
1007
|
+
"function rewardsUpdater() view returns (address)"
|
|
1008
|
+
]);
|
|
1009
|
+
var eigenLayer = {
|
|
1010
|
+
id: "eigenlayer",
|
|
1011
|
+
name: "EigenLayer",
|
|
1012
|
+
version: "1.0",
|
|
1013
|
+
website: "https://eigenlayer.xyz",
|
|
1014
|
+
description: "Restaking protocol for Ethereum shared security",
|
|
1015
|
+
contracts: {
|
|
1016
|
+
StrategyManager: { name: "StrategyManager", role: "Manages staking strategies and deposits", type: "proxy" },
|
|
1017
|
+
DelegationManager: { name: "DelegationManager", role: "Manages operator delegation and withdrawals", type: "proxy" },
|
|
1018
|
+
EigenPodManager: { name: "EigenPodManager", role: "Manages native ETH restaking pods", type: "proxy" },
|
|
1019
|
+
Slasher: { name: "Slasher", role: "Handles slashing conditions", type: "proxy" },
|
|
1020
|
+
AVSDirectory: { name: "AVSDirectory", role: "Registry of Actively Validated Services", type: "proxy" },
|
|
1021
|
+
RewardsCoordinator: { name: "RewardsCoordinator", role: "Distributes rewards to operators/stakers", type: "proxy" }
|
|
1022
|
+
},
|
|
1023
|
+
deployments: {
|
|
1024
|
+
ethereum: {
|
|
1025
|
+
StrategyManager: "0x858646372CC42E1A627fcE94aa7A7033e7CF075A",
|
|
1026
|
+
DelegationManager: "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A",
|
|
1027
|
+
EigenPodManager: "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338",
|
|
1028
|
+
Slasher: "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd",
|
|
1029
|
+
AVSDirectory: "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF",
|
|
1030
|
+
RewardsCoordinator: "0x7750d328b314EfFa365A0402CcfD489B80B0adda"
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
params: [
|
|
1034
|
+
{
|
|
1035
|
+
name: "strategyManagerPaused",
|
|
1036
|
+
description: "Pause state bitmap (0=unpaused, non-zero=paused operations)",
|
|
1037
|
+
contract: "StrategyManager",
|
|
1038
|
+
functionName: "paused",
|
|
1039
|
+
abi: strategyManagerAbi,
|
|
1040
|
+
decode: "uint",
|
|
1041
|
+
category: "state",
|
|
1042
|
+
severity: "critical"
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
name: "strategyWhitelister",
|
|
1046
|
+
description: "Address authorized to whitelist new strategies",
|
|
1047
|
+
contract: "StrategyManager",
|
|
1048
|
+
functionName: "strategyWhitelister",
|
|
1049
|
+
abi: strategyManagerAbi,
|
|
1050
|
+
decode: "address",
|
|
1051
|
+
category: "access",
|
|
1052
|
+
severity: "critical"
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
name: "delegationPaused",
|
|
1056
|
+
description: "Delegation manager pause state",
|
|
1057
|
+
contract: "DelegationManager",
|
|
1058
|
+
functionName: "paused",
|
|
1059
|
+
abi: delegationManagerAbi,
|
|
1060
|
+
decode: "uint",
|
|
1061
|
+
category: "state",
|
|
1062
|
+
severity: "critical"
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
name: "minWithdrawalDelayBlocks",
|
|
1066
|
+
description: "Minimum blocks to wait for withdrawal completion",
|
|
1067
|
+
contract: "DelegationManager",
|
|
1068
|
+
functionName: "minWithdrawalDelayBlocks",
|
|
1069
|
+
abi: delegationManagerAbi,
|
|
1070
|
+
decode: "uint",
|
|
1071
|
+
category: "config",
|
|
1072
|
+
severity: "warning"
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "eigenPodPaused",
|
|
1076
|
+
description: "EigenPod manager pause state",
|
|
1077
|
+
contract: "EigenPodManager",
|
|
1078
|
+
functionName: "paused",
|
|
1079
|
+
abi: eigenPodManagerAbi,
|
|
1080
|
+
decode: "uint",
|
|
1081
|
+
category: "state",
|
|
1082
|
+
severity: "critical"
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
name: "rewardsUpdater",
|
|
1086
|
+
description: "Address authorized to submit reward merkle roots",
|
|
1087
|
+
contract: "RewardsCoordinator",
|
|
1088
|
+
functionName: "rewardsUpdater",
|
|
1089
|
+
abi: rewardsCoordinatorAbi,
|
|
1090
|
+
decode: "address",
|
|
1091
|
+
category: "access",
|
|
1092
|
+
severity: "critical"
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
name: "rewardsActivationDelay",
|
|
1096
|
+
description: "Delay before new rewards become claimable",
|
|
1097
|
+
contract: "RewardsCoordinator",
|
|
1098
|
+
functionName: "activationDelay",
|
|
1099
|
+
abi: rewardsCoordinatorAbi,
|
|
1100
|
+
decode: "uint",
|
|
1101
|
+
category: "config",
|
|
1102
|
+
severity: "warning"
|
|
1103
|
+
}
|
|
1104
|
+
],
|
|
1105
|
+
access: [
|
|
1106
|
+
{
|
|
1107
|
+
role: "StrategyManager Owner",
|
|
1108
|
+
description: "Can pause, unpause, add/remove strategies",
|
|
1109
|
+
contract: "StrategyManager",
|
|
1110
|
+
functionName: "owner",
|
|
1111
|
+
abi: strategyManagerAbi,
|
|
1112
|
+
severity: "critical"
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
role: "DelegationManager Owner",
|
|
1116
|
+
description: "Can pause delegation, modify withdrawal delays",
|
|
1117
|
+
contract: "DelegationManager",
|
|
1118
|
+
functionName: "owner",
|
|
1119
|
+
abi: delegationManagerAbi,
|
|
1120
|
+
severity: "critical"
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
role: "EigenPodManager Owner",
|
|
1124
|
+
description: "Controls native ETH restaking pod creation",
|
|
1125
|
+
contract: "EigenPodManager",
|
|
1126
|
+
functionName: "owner",
|
|
1127
|
+
abi: eigenPodManagerAbi,
|
|
1128
|
+
severity: "critical"
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
role: "Slasher Owner",
|
|
1132
|
+
description: "Controls slashing parameters",
|
|
1133
|
+
contract: "Slasher",
|
|
1134
|
+
functionName: "owner",
|
|
1135
|
+
abi: slasherAbi,
|
|
1136
|
+
severity: "critical"
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
role: "AVSDirectory Owner",
|
|
1140
|
+
description: "Controls AVS registration",
|
|
1141
|
+
contract: "AVSDirectory",
|
|
1142
|
+
functionName: "owner",
|
|
1143
|
+
abi: avsDirectoryAbi,
|
|
1144
|
+
severity: "critical"
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
role: "RewardsCoordinator Owner",
|
|
1148
|
+
description: "Controls reward distribution parameters",
|
|
1149
|
+
contract: "RewardsCoordinator",
|
|
1150
|
+
functionName: "owner",
|
|
1151
|
+
abi: rewardsCoordinatorAbi,
|
|
1152
|
+
severity: "critical"
|
|
1153
|
+
}
|
|
1154
|
+
]
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
// src/registry/pendle.ts
|
|
1158
|
+
import { parseAbi as parseAbi7 } from "viem";
|
|
1159
|
+
var marketFactoryAbi = parseAbi7([
|
|
1160
|
+
"function owner() view returns (address)",
|
|
1161
|
+
"function pendingOwner() view returns (address)",
|
|
1162
|
+
"function treasury() view returns (address)",
|
|
1163
|
+
"function reserveFeePercent() view returns (uint256)",
|
|
1164
|
+
"function vePendle() view returns (address)",
|
|
1165
|
+
"function interestFeeRate() view returns (uint256)"
|
|
1166
|
+
]);
|
|
1167
|
+
var vePendleAbi = parseAbi7([
|
|
1168
|
+
"function totalSupplyStored() view returns (uint128)",
|
|
1169
|
+
"function lastSlopeChangeAppliedAt() view returns (uint128)",
|
|
1170
|
+
"function pendle() view returns (address)"
|
|
1171
|
+
]);
|
|
1172
|
+
var routerAbi = parseAbi7([
|
|
1173
|
+
"function owner() view returns (address)"
|
|
1174
|
+
]);
|
|
1175
|
+
var pendle = {
|
|
1176
|
+
id: "pendle",
|
|
1177
|
+
name: "Pendle",
|
|
1178
|
+
version: "2.0",
|
|
1179
|
+
website: "https://pendle.finance",
|
|
1180
|
+
description: "Yield trading protocol \u2014 tokenize and trade future yield",
|
|
1181
|
+
contracts: {
|
|
1182
|
+
PendleMarketFactory: { name: "MarketFactory", role: "Creates PT/YT markets", type: "raw" },
|
|
1183
|
+
vePENDLE: { name: "vePENDLE", role: "Vote-escrowed PENDLE for governance", type: "raw" },
|
|
1184
|
+
PendleRouter: { name: "Router", role: "Main swap/liquidity router", type: "raw" }
|
|
1185
|
+
},
|
|
1186
|
+
deployments: {
|
|
1187
|
+
ethereum: {
|
|
1188
|
+
PendleMarketFactory: "0x1A6fCc85557BC4fB7B534ed835a03EF056c22E23",
|
|
1189
|
+
vePENDLE: "0x4f30A9D41B80ecC5B94306AB4364951AE3170210",
|
|
1190
|
+
PendleRouter: "0x888888888889758F76e7103c6CbF23ABbF58F946"
|
|
1191
|
+
},
|
|
1192
|
+
arbitrum: {
|
|
1193
|
+
PendleMarketFactory: "0x2FCb47B58350cD377f94d3a40c938fB1CF7e5Dd5",
|
|
1194
|
+
vePENDLE: "0x3209E9412cca80B18338f2a56ADA59c484c39644",
|
|
1195
|
+
PendleRouter: "0x888888888889758F76e7103c6CbF23ABbF58F946"
|
|
1196
|
+
}
|
|
1197
|
+
},
|
|
1198
|
+
params: [
|
|
1199
|
+
{
|
|
1200
|
+
name: "treasury",
|
|
1201
|
+
description: "Protocol treasury address \u2014 receives fees",
|
|
1202
|
+
contract: "PendleMarketFactory",
|
|
1203
|
+
functionName: "treasury",
|
|
1204
|
+
abi: marketFactoryAbi,
|
|
1205
|
+
decode: "address",
|
|
1206
|
+
category: "config",
|
|
1207
|
+
severity: "critical"
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
name: "reserveFeePercent",
|
|
1211
|
+
description: "Fee percentage taken from yield as protocol revenue",
|
|
1212
|
+
contract: "PendleMarketFactory",
|
|
1213
|
+
functionName: "reserveFeePercent",
|
|
1214
|
+
abi: marketFactoryAbi,
|
|
1215
|
+
decode: "uint",
|
|
1216
|
+
category: "config",
|
|
1217
|
+
severity: "warning"
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
name: "interestFeeRate",
|
|
1221
|
+
description: "Interest fee rate applied to markets",
|
|
1222
|
+
contract: "PendleMarketFactory",
|
|
1223
|
+
functionName: "interestFeeRate",
|
|
1224
|
+
abi: marketFactoryAbi,
|
|
1225
|
+
decode: "uint",
|
|
1226
|
+
category: "rate",
|
|
1227
|
+
severity: "warning"
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
name: "vePendleTotalSupply",
|
|
1231
|
+
description: "Total vePENDLE supply (governance power)",
|
|
1232
|
+
contract: "vePENDLE",
|
|
1233
|
+
functionName: "totalSupplyStored",
|
|
1234
|
+
abi: vePendleAbi,
|
|
1235
|
+
decode: "wad",
|
|
1236
|
+
category: "state",
|
|
1237
|
+
severity: "info"
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
name: "pendingOwner",
|
|
1241
|
+
description: "Pending ownership transfer target",
|
|
1242
|
+
contract: "PendleMarketFactory",
|
|
1243
|
+
functionName: "pendingOwner",
|
|
1244
|
+
abi: marketFactoryAbi,
|
|
1245
|
+
decode: "address",
|
|
1246
|
+
category: "access",
|
|
1247
|
+
severity: "critical"
|
|
1248
|
+
}
|
|
1249
|
+
],
|
|
1250
|
+
access: [
|
|
1251
|
+
{
|
|
1252
|
+
role: "MarketFactory Owner",
|
|
1253
|
+
description: "Can create markets, set fees, set treasury",
|
|
1254
|
+
contract: "PendleMarketFactory",
|
|
1255
|
+
functionName: "owner",
|
|
1256
|
+
abi: marketFactoryAbi,
|
|
1257
|
+
severity: "critical"
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
role: "Router Owner",
|
|
1261
|
+
description: "Can update router configuration",
|
|
1262
|
+
contract: "PendleRouter",
|
|
1263
|
+
functionName: "owner",
|
|
1264
|
+
abi: routerAbi,
|
|
1265
|
+
severity: "critical"
|
|
1266
|
+
}
|
|
1267
|
+
]
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/registry/lido.ts
|
|
1271
|
+
import { parseAbi as parseAbi8 } from "viem";
|
|
1272
|
+
var lidoAbi = parseAbi8([
|
|
1273
|
+
"function totalSupply() view returns (uint256)",
|
|
1274
|
+
"function getTotalPooledEther() view returns (uint256)",
|
|
1275
|
+
"function getTotalShares() view returns (uint256)",
|
|
1276
|
+
"function getBufferedEther() view returns (uint256)",
|
|
1277
|
+
"function isStakingPaused() view returns (bool)",
|
|
1278
|
+
"function getCurrentStakeLimit() view returns (uint256)",
|
|
1279
|
+
"function getStakeLimitFullInfo() view returns (bool isStakingPaused, bool isStakeLimitSet, uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, uint256 prevStakeBlockNumber)",
|
|
1280
|
+
"function getFee() view returns (uint16)",
|
|
1281
|
+
"function getFeeDistribution() view returns (uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints)",
|
|
1282
|
+
"function getWithdrawalCredentials() view returns (bytes32)",
|
|
1283
|
+
"function getTreasury() view returns (address)",
|
|
1284
|
+
"function getLidoLocator() view returns (address)",
|
|
1285
|
+
"function getBeaconStat() view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance)"
|
|
1286
|
+
]);
|
|
1287
|
+
var wstEthAbi = parseAbi8([
|
|
1288
|
+
"function stEthPerToken() view returns (uint256)",
|
|
1289
|
+
"function tokensPerStEth() view returns (uint256)",
|
|
1290
|
+
"function getStETHByWstETH(uint256 _wstETHAmount) view returns (uint256)",
|
|
1291
|
+
"function getWstETHByStETH(uint256 _stETHAmount) view returns (uint256)",
|
|
1292
|
+
"function stETH() view returns (address)"
|
|
1293
|
+
]);
|
|
1294
|
+
var norAbi = parseAbi8([
|
|
1295
|
+
"function getNodeOperatorsCount() view returns (uint256)",
|
|
1296
|
+
"function getActiveNodeOperatorsCount() view returns (uint256)",
|
|
1297
|
+
"function getNodeOperator(uint256 _nodeOperatorId, bool _fullInfo) view returns (bool active, string name, address rewardAddress, uint64 totalVettedValidators, uint64 totalExitedValidators, uint64 totalAddedValidators, uint64 totalDepositedValidators)",
|
|
1298
|
+
"function getNodeOperatorIsActive(uint256 _nodeOperatorId) view returns (bool)",
|
|
1299
|
+
"function getNonce() view returns (uint256)",
|
|
1300
|
+
"function getType() view returns (bytes32)",
|
|
1301
|
+
"function getLocator() view returns (address)"
|
|
1302
|
+
]);
|
|
1303
|
+
var withdrawalQueueAbi = parseAbi8([
|
|
1304
|
+
"function isBunkerModeActive() view returns (bool)",
|
|
1305
|
+
"function bunkerModeSinceTimestamp() view returns (uint256)",
|
|
1306
|
+
"function getLastRequestId() view returns (uint256)",
|
|
1307
|
+
"function getLastFinalizedRequestId() view returns (uint256)",
|
|
1308
|
+
"function getLastCheckpointIndex() view returns (uint256)",
|
|
1309
|
+
"function MIN_STETH_WITHDRAWAL_AMOUNT() view returns (uint256)",
|
|
1310
|
+
"function MAX_STETH_WITHDRAWAL_AMOUNT() view returns (uint256)"
|
|
1311
|
+
]);
|
|
1312
|
+
var dsmAbi = parseAbi8([
|
|
1313
|
+
"function getOwner() view returns (address)",
|
|
1314
|
+
"function getGuardianQuorum() view returns (uint256)",
|
|
1315
|
+
"function getGuardians() view returns (address[])",
|
|
1316
|
+
"function isGuardian(address addr) view returns (bool)",
|
|
1317
|
+
"function canDeposit(uint256 stakingModuleId) view returns (bool)",
|
|
1318
|
+
"function getPauseIntentValidityPeriodBlocks() view returns (uint256)",
|
|
1319
|
+
"function getMaxOperatorsPerUnvetting() view returns (uint256)",
|
|
1320
|
+
"function getLastDepositBlock() view returns (uint256)"
|
|
1321
|
+
]);
|
|
1322
|
+
var accountingOracleAbi = parseAbi8([
|
|
1323
|
+
"function getConsensusContract() view returns (address)",
|
|
1324
|
+
"function getConsensusVersion() view returns (uint256)",
|
|
1325
|
+
"function getLastProcessingRefSlot() view returns (uint256)",
|
|
1326
|
+
"function getConsensusReport() view returns (bytes32 hash, uint256 refSlot, uint256 processingDeadlineTime, bool processingStarted)",
|
|
1327
|
+
"function SECONDS_PER_SLOT() view returns (uint256)",
|
|
1328
|
+
"function GENESIS_TIME() view returns (uint256)"
|
|
1329
|
+
]);
|
|
1330
|
+
var hashConsensusAbi = parseAbi8([
|
|
1331
|
+
"function getQuorum() view returns (uint256)",
|
|
1332
|
+
"function getMembers() view returns (address[] addresses, uint256[] lastReportedRefSlots)",
|
|
1333
|
+
"function getFastLaneMembers() view returns (address[] addresses, uint256[] lastReportedRefSlots)",
|
|
1334
|
+
"function getCurrentFrame() view returns (uint256 refSlot, uint256 reportProcessingDeadlineSlot)",
|
|
1335
|
+
"function getChainConfig() view returns (uint256 slotsPerEpoch, uint256 secondsPerSlot, uint256 genesisTime)",
|
|
1336
|
+
"function getFrameConfig() view returns (uint256 initialEpoch, uint256 epochsPerFrame, uint256 fastLaneLengthSlots)",
|
|
1337
|
+
"function getReportProcessor() view returns (address)"
|
|
1338
|
+
]);
|
|
1339
|
+
var locatorAbi = parseAbi8([
|
|
1340
|
+
"function lido() view returns (address)",
|
|
1341
|
+
"function accountingOracle() view returns (address)",
|
|
1342
|
+
"function stakingRouter() view returns (address)",
|
|
1343
|
+
"function withdrawalQueue() view returns (address)",
|
|
1344
|
+
"function withdrawalVault() view returns (address)",
|
|
1345
|
+
"function elRewardsVault() view returns (address)",
|
|
1346
|
+
"function treasury() view returns (address)",
|
|
1347
|
+
"function oracleReportSanityChecker() view returns (address)",
|
|
1348
|
+
"function burner() view returns (address)",
|
|
1349
|
+
"function validatorsExitBusOracle() view returns (address)",
|
|
1350
|
+
"function depositSecurityModule() view returns (address)",
|
|
1351
|
+
"function oracleDaemonConfig() view returns (address)",
|
|
1352
|
+
"function postTokenRebaseReceiver() view returns (address)"
|
|
1353
|
+
]);
|
|
1354
|
+
var votingAbi = parseAbi8([
|
|
1355
|
+
"function supportRequiredPct() view returns (uint64)",
|
|
1356
|
+
"function minAcceptQuorumPct() view returns (uint64)",
|
|
1357
|
+
"function voteTime() view returns (uint64)",
|
|
1358
|
+
"function objectionPhaseTime() view returns (uint64)",
|
|
1359
|
+
"function votesLength() view returns (uint256)"
|
|
1360
|
+
]);
|
|
1361
|
+
var agentAbi = parseAbi8([
|
|
1362
|
+
"function kernel() view returns (address)",
|
|
1363
|
+
"function isForwarder() view returns (bool)"
|
|
1364
|
+
]);
|
|
1365
|
+
var lido = {
|
|
1366
|
+
id: "lido",
|
|
1367
|
+
name: "Lido",
|
|
1368
|
+
version: "2.0",
|
|
1369
|
+
website: "https://lido.fi",
|
|
1370
|
+
description: "Liquid staking protocol for Ethereum \u2014 stake ETH, receive rebasing stETH",
|
|
1371
|
+
contracts: {
|
|
1372
|
+
Lido: { name: "Lido (stETH)", role: "Core staking pool + rebasing stETH token \u2014 accepts ETH deposits, mints stETH, tracks total pooled ether", type: "proxy" },
|
|
1373
|
+
WstETH: { name: "wstETH", role: "Non-rebasing wrapped stETH \u2014 fixed balance, accrues value via exchange rate", type: "raw" },
|
|
1374
|
+
NodeOperatorsRegistry: { name: "NodeOperatorsRegistry", role: "Curated staking module \u2014 manages node operators and validator signing keys", type: "proxy" },
|
|
1375
|
+
WithdrawalQueue: { name: "WithdrawalQueueERC721", role: "stETH withdrawal request queue \u2014 NFT claim tickets, bunker mode tracking", type: "proxy" },
|
|
1376
|
+
DepositSecurityModule: { name: "DepositSecurityModule", role: "Guardian committee gate for validator deposits \u2014 prevents deposit front-running/DoS", type: "raw" },
|
|
1377
|
+
AccountingOracle: { name: "AccountingOracle", role: "Reports consensus-layer state (balances, exited validators) each epoch, drives stETH rebase", type: "proxy" },
|
|
1378
|
+
HashConsensus: { name: "HashConsensus (Accounting)", role: "Oracle committee consensus module \u2014 quorum + member management for AccountingOracle reports", type: "raw" },
|
|
1379
|
+
LidoLocator: { name: "LidoLocator", role: "DAO-controlled pointer registry for every core protocol component \u2014 repointing it repoints the whole protocol", type: "proxy" },
|
|
1380
|
+
Voting: { name: "Aragon Voting", role: "Lido DAO governance \u2014 token-weighted voting, approves everything Agent executes", type: "proxy" },
|
|
1381
|
+
Agent: { name: "Aragon Agent", role: "DAO treasury + privileged executor \u2014 holds funds, executes DAO-approved calls against any contract", type: "proxy" }
|
|
1382
|
+
},
|
|
1383
|
+
deployments: {
|
|
1384
|
+
ethereum: {
|
|
1385
|
+
Lido: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
|
|
1386
|
+
WstETH: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
|
|
1387
|
+
NodeOperatorsRegistry: "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5",
|
|
1388
|
+
WithdrawalQueue: "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1",
|
|
1389
|
+
DepositSecurityModule: "0xC77F8768774E1c9244BEed705C4354f2113CFc09",
|
|
1390
|
+
AccountingOracle: "0x852deD011285fe67063a08005c71a85690503Cee",
|
|
1391
|
+
HashConsensus: "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288",
|
|
1392
|
+
LidoLocator: "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb",
|
|
1393
|
+
Voting: "0x2e59A20f205bB85a89C53f1936454680651E618e",
|
|
1394
|
+
Agent: "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"
|
|
1395
|
+
}
|
|
1396
|
+
},
|
|
1397
|
+
params: [
|
|
1398
|
+
// -- core pool state --
|
|
1399
|
+
{
|
|
1400
|
+
name: "totalPooledEther",
|
|
1401
|
+
description: "Total ETH controlled by the protocol (buffered + beacon chain + EL rewards)",
|
|
1402
|
+
contract: "Lido",
|
|
1403
|
+
functionName: "getTotalPooledEther",
|
|
1404
|
+
abi: lidoAbi,
|
|
1405
|
+
decode: "uint",
|
|
1406
|
+
category: "state",
|
|
1407
|
+
severity: "info"
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: "totalShares",
|
|
1411
|
+
description: "Total stETH shares outstanding \u2014 denominator used to compute every account balance via rebase",
|
|
1412
|
+
contract: "Lido",
|
|
1413
|
+
functionName: "getTotalShares",
|
|
1414
|
+
abi: lidoAbi,
|
|
1415
|
+
decode: "uint",
|
|
1416
|
+
category: "state",
|
|
1417
|
+
severity: "info"
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
name: "bufferedEther",
|
|
1421
|
+
description: "Unstaked ETH sitting in the Lido contract awaiting deposit to validators",
|
|
1422
|
+
contract: "Lido",
|
|
1423
|
+
functionName: "getBufferedEther",
|
|
1424
|
+
abi: lidoAbi,
|
|
1425
|
+
decode: "uint",
|
|
1426
|
+
category: "state",
|
|
1427
|
+
severity: "info"
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
name: "isStakingPaused",
|
|
1431
|
+
description: "Whether new ETH deposits into Lido are paused",
|
|
1432
|
+
contract: "Lido",
|
|
1433
|
+
functionName: "isStakingPaused",
|
|
1434
|
+
abi: lidoAbi,
|
|
1435
|
+
decode: "bool",
|
|
1436
|
+
category: "state",
|
|
1437
|
+
severity: "critical"
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "currentStakeLimit",
|
|
1441
|
+
description: "Remaining ETH that can be staked right now before hitting the anti-frontrun stake rate limit",
|
|
1442
|
+
contract: "Lido",
|
|
1443
|
+
functionName: "getCurrentStakeLimit",
|
|
1444
|
+
abi: lidoAbi,
|
|
1445
|
+
decode: "uint",
|
|
1446
|
+
category: "risk",
|
|
1447
|
+
severity: "warning"
|
|
1448
|
+
},
|
|
1449
|
+
// -- fees --
|
|
1450
|
+
{
|
|
1451
|
+
name: "protocolFee",
|
|
1452
|
+
description: "Legacy total protocol fee taken from staking rewards (treasury + node operators), in bps",
|
|
1453
|
+
contract: "Lido",
|
|
1454
|
+
functionName: "getFee",
|
|
1455
|
+
abi: lidoAbi,
|
|
1456
|
+
decode: "bps",
|
|
1457
|
+
category: "config",
|
|
1458
|
+
severity: "warning"
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
name: "feeDistribution",
|
|
1462
|
+
description: "Legacy fee split between treasury, insurance and node operators (bps each) \u2014 module-level splits now live on StakingRouter",
|
|
1463
|
+
contract: "Lido",
|
|
1464
|
+
functionName: "getFeeDistribution",
|
|
1465
|
+
abi: lidoAbi,
|
|
1466
|
+
category: "config",
|
|
1467
|
+
severity: "info"
|
|
1468
|
+
},
|
|
1469
|
+
// -- withdrawal credentials / treasury / locator pointer --
|
|
1470
|
+
{
|
|
1471
|
+
name: "withdrawalCredentials",
|
|
1472
|
+
description: "Withdrawal credentials validators are deposited with \u2014 controls where exited/skimmed ETH lands",
|
|
1473
|
+
contract: "Lido",
|
|
1474
|
+
functionName: "getWithdrawalCredentials",
|
|
1475
|
+
abi: lidoAbi,
|
|
1476
|
+
decode: "string",
|
|
1477
|
+
category: "config",
|
|
1478
|
+
severity: "critical"
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
name: "treasury",
|
|
1482
|
+
description: "Address receiving the treasury share of staking rewards (Aragon Agent)",
|
|
1483
|
+
contract: "Lido",
|
|
1484
|
+
functionName: "getTreasury",
|
|
1485
|
+
abi: lidoAbi,
|
|
1486
|
+
decode: "address",
|
|
1487
|
+
category: "access",
|
|
1488
|
+
severity: "critical"
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
name: "lidoLocator",
|
|
1492
|
+
description: "LidoLocator address the core contract resolves every peer component through",
|
|
1493
|
+
contract: "Lido",
|
|
1494
|
+
functionName: "getLidoLocator",
|
|
1495
|
+
abi: lidoAbi,
|
|
1496
|
+
decode: "address",
|
|
1497
|
+
category: "config",
|
|
1498
|
+
severity: "info"
|
|
1499
|
+
},
|
|
1500
|
+
// -- wstETH --
|
|
1501
|
+
{
|
|
1502
|
+
name: "wstEthExchangeRate",
|
|
1503
|
+
description: "stETH redeemable for 1 wstETH \u2014 reflects accumulated staking rewards since wrap",
|
|
1504
|
+
contract: "WstETH",
|
|
1505
|
+
functionName: "stEthPerToken",
|
|
1506
|
+
abi: wstEthAbi,
|
|
1507
|
+
decode: "wad",
|
|
1508
|
+
category: "rate",
|
|
1509
|
+
severity: "info"
|
|
1510
|
+
},
|
|
1511
|
+
// -- node operators --
|
|
1512
|
+
{
|
|
1513
|
+
name: "nodeOperatorsCount",
|
|
1514
|
+
description: "Total registered node operators in the curated staking module",
|
|
1515
|
+
contract: "NodeOperatorsRegistry",
|
|
1516
|
+
functionName: "getNodeOperatorsCount",
|
|
1517
|
+
abi: norAbi,
|
|
1518
|
+
decode: "uint",
|
|
1519
|
+
category: "state",
|
|
1520
|
+
severity: "info"
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
name: "activeNodeOperatorsCount",
|
|
1524
|
+
description: "Node operators currently active and eligible for new deposits",
|
|
1525
|
+
contract: "NodeOperatorsRegistry",
|
|
1526
|
+
functionName: "getActiveNodeOperatorsCount",
|
|
1527
|
+
abi: norAbi,
|
|
1528
|
+
decode: "uint",
|
|
1529
|
+
category: "state",
|
|
1530
|
+
severity: "info"
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
name: "norNonce",
|
|
1534
|
+
description: "Increments on any change to operator keys/limits \u2014 used to detect stale/front-run deposit data",
|
|
1535
|
+
contract: "NodeOperatorsRegistry",
|
|
1536
|
+
functionName: "getNonce",
|
|
1537
|
+
abi: norAbi,
|
|
1538
|
+
decode: "uint",
|
|
1539
|
+
category: "state",
|
|
1540
|
+
severity: "info"
|
|
1541
|
+
},
|
|
1542
|
+
// -- withdrawal queue --
|
|
1543
|
+
{
|
|
1544
|
+
name: "isBunkerModeActive",
|
|
1545
|
+
description: "Whether the withdrawal queue is in bunker mode (CL-level slashing/penalty event) \u2014 changes stETH:ETH withdrawal accounting",
|
|
1546
|
+
contract: "WithdrawalQueue",
|
|
1547
|
+
functionName: "isBunkerModeActive",
|
|
1548
|
+
abi: withdrawalQueueAbi,
|
|
1549
|
+
decode: "bool",
|
|
1550
|
+
category: "risk",
|
|
1551
|
+
severity: "critical"
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
name: "bunkerModeSinceTimestamp",
|
|
1555
|
+
description: "Timestamp bunker mode was activated",
|
|
1556
|
+
contract: "WithdrawalQueue",
|
|
1557
|
+
functionName: "bunkerModeSinceTimestamp",
|
|
1558
|
+
abi: withdrawalQueueAbi,
|
|
1559
|
+
decode: "uint",
|
|
1560
|
+
category: "state",
|
|
1561
|
+
severity: "warning"
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
name: "lastRequestId",
|
|
1565
|
+
description: "Highest withdrawal request id issued",
|
|
1566
|
+
contract: "WithdrawalQueue",
|
|
1567
|
+
functionName: "getLastRequestId",
|
|
1568
|
+
abi: withdrawalQueueAbi,
|
|
1569
|
+
decode: "uint",
|
|
1570
|
+
category: "state",
|
|
1571
|
+
severity: "info"
|
|
1572
|
+
},
|
|
1573
|
+
{
|
|
1574
|
+
name: "lastFinalizedRequestId",
|
|
1575
|
+
description: "Highest withdrawal request id finalized and claimable \u2014 gap vs lastRequestId is the unfinalized withdrawal backlog",
|
|
1576
|
+
contract: "WithdrawalQueue",
|
|
1577
|
+
functionName: "getLastFinalizedRequestId",
|
|
1578
|
+
abi: withdrawalQueueAbi,
|
|
1579
|
+
decode: "uint",
|
|
1580
|
+
category: "state",
|
|
1581
|
+
severity: "warning"
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
name: "minWithdrawalAmount",
|
|
1585
|
+
description: "Minimum stETH amount per withdrawal request",
|
|
1586
|
+
contract: "WithdrawalQueue",
|
|
1587
|
+
functionName: "MIN_STETH_WITHDRAWAL_AMOUNT",
|
|
1588
|
+
abi: withdrawalQueueAbi,
|
|
1589
|
+
decode: "uint",
|
|
1590
|
+
category: "config",
|
|
1591
|
+
severity: "info"
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
name: "maxWithdrawalAmount",
|
|
1595
|
+
description: "Maximum stETH amount per withdrawal request",
|
|
1596
|
+
contract: "WithdrawalQueue",
|
|
1597
|
+
functionName: "MAX_STETH_WITHDRAWAL_AMOUNT",
|
|
1598
|
+
abi: withdrawalQueueAbi,
|
|
1599
|
+
decode: "uint",
|
|
1600
|
+
category: "config",
|
|
1601
|
+
severity: "info"
|
|
1602
|
+
},
|
|
1603
|
+
// -- deposit security module (guardian committee) --
|
|
1604
|
+
{
|
|
1605
|
+
name: "guardianQuorum",
|
|
1606
|
+
description: "Guardian signatures required for DepositSecurityModule to authorize a deposit or emergency pause",
|
|
1607
|
+
contract: "DepositSecurityModule",
|
|
1608
|
+
functionName: "getGuardianQuorum",
|
|
1609
|
+
abi: dsmAbi,
|
|
1610
|
+
decode: "uint",
|
|
1611
|
+
category: "access",
|
|
1612
|
+
severity: "critical"
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
name: "dsmOwner",
|
|
1616
|
+
description: "Can add/remove guardians and change the quorum \u2014 controls who gates validator deposits",
|
|
1617
|
+
contract: "DepositSecurityModule",
|
|
1618
|
+
functionName: "getOwner",
|
|
1619
|
+
abi: dsmAbi,
|
|
1620
|
+
decode: "address",
|
|
1621
|
+
category: "access",
|
|
1622
|
+
severity: "critical"
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
name: "pauseIntentValidityPeriodBlocks",
|
|
1626
|
+
description: "Block window during which a guardian pause signature remains valid",
|
|
1627
|
+
contract: "DepositSecurityModule",
|
|
1628
|
+
functionName: "getPauseIntentValidityPeriodBlocks",
|
|
1629
|
+
abi: dsmAbi,
|
|
1630
|
+
decode: "uint",
|
|
1631
|
+
category: "config",
|
|
1632
|
+
severity: "info"
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
name: "maxOperatorsPerUnvetting",
|
|
1636
|
+
description: "Max node operators guardians can unvet keys for in a single report",
|
|
1637
|
+
contract: "DepositSecurityModule",
|
|
1638
|
+
functionName: "getMaxOperatorsPerUnvetting",
|
|
1639
|
+
abi: dsmAbi,
|
|
1640
|
+
decode: "uint",
|
|
1641
|
+
category: "config",
|
|
1642
|
+
severity: "info"
|
|
1643
|
+
},
|
|
1644
|
+
// -- accounting oracle / consensus --
|
|
1645
|
+
{
|
|
1646
|
+
name: "accountingConsensusVersion",
|
|
1647
|
+
description: "Report schema version AccountingOracle expects from HashConsensus reports \u2014 bumping it invalidates old reporter software",
|
|
1648
|
+
contract: "AccountingOracle",
|
|
1649
|
+
functionName: "getConsensusVersion",
|
|
1650
|
+
abi: accountingOracleAbi,
|
|
1651
|
+
decode: "uint",
|
|
1652
|
+
category: "config",
|
|
1653
|
+
severity: "info"
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
name: "accountingLastProcessingRefSlot",
|
|
1657
|
+
description: "Last beacon chain reference slot processed by AccountingOracle \u2014 staleness indicator for the whole rebase pipeline",
|
|
1658
|
+
contract: "AccountingOracle",
|
|
1659
|
+
functionName: "getLastProcessingRefSlot",
|
|
1660
|
+
abi: accountingOracleAbi,
|
|
1661
|
+
decode: "uint",
|
|
1662
|
+
category: "state",
|
|
1663
|
+
severity: "warning"
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
name: "oracleQuorum",
|
|
1667
|
+
description: "Oracle committee members required to agree before an AccountingOracle report is considered final \u2014 directly controls the reported stETH rebase",
|
|
1668
|
+
contract: "HashConsensus",
|
|
1669
|
+
functionName: "getQuorum",
|
|
1670
|
+
abi: hashConsensusAbi,
|
|
1671
|
+
decode: "uint",
|
|
1672
|
+
category: "access",
|
|
1673
|
+
severity: "critical"
|
|
1674
|
+
},
|
|
1675
|
+
// -- LidoLocator pointer registry (god-pointer) --
|
|
1676
|
+
{
|
|
1677
|
+
name: "locatorLido",
|
|
1678
|
+
description: "Lido core contract the locator resolves to \u2014 every module trusts this pointer",
|
|
1679
|
+
contract: "LidoLocator",
|
|
1680
|
+
functionName: "lido",
|
|
1681
|
+
abi: locatorAbi,
|
|
1682
|
+
decode: "address",
|
|
1683
|
+
category: "access",
|
|
1684
|
+
severity: "critical"
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
name: "locatorAccountingOracle",
|
|
1688
|
+
description: "AccountingOracle the locator resolves to \u2014 repointing it swaps out the entire rebase reporting pipeline",
|
|
1689
|
+
contract: "LidoLocator",
|
|
1690
|
+
functionName: "accountingOracle",
|
|
1691
|
+
abi: locatorAbi,
|
|
1692
|
+
decode: "address",
|
|
1693
|
+
category: "access",
|
|
1694
|
+
severity: "critical"
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
name: "locatorStakingRouter",
|
|
1698
|
+
description: "StakingRouter the locator resolves to \u2014 owns module registration and per-module fee routing",
|
|
1699
|
+
contract: "LidoLocator",
|
|
1700
|
+
functionName: "stakingRouter",
|
|
1701
|
+
abi: locatorAbi,
|
|
1702
|
+
decode: "address",
|
|
1703
|
+
category: "access",
|
|
1704
|
+
severity: "critical"
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
name: "locatorDepositSecurityModule",
|
|
1708
|
+
description: "DepositSecurityModule the locator resolves to \u2014 repointing it swaps out the guardian committee gating deposits",
|
|
1709
|
+
contract: "LidoLocator",
|
|
1710
|
+
functionName: "depositSecurityModule",
|
|
1711
|
+
abi: locatorAbi,
|
|
1712
|
+
decode: "address",
|
|
1713
|
+
category: "access",
|
|
1714
|
+
severity: "critical"
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
name: "locatorTreasury",
|
|
1718
|
+
description: "Treasury address the locator resolves to \u2014 where protocol fee revenue ultimately flows",
|
|
1719
|
+
contract: "LidoLocator",
|
|
1720
|
+
functionName: "treasury",
|
|
1721
|
+
abi: locatorAbi,
|
|
1722
|
+
decode: "address",
|
|
1723
|
+
category: "access",
|
|
1724
|
+
severity: "critical"
|
|
1725
|
+
},
|
|
1726
|
+
// -- DAO governance parameters --
|
|
1727
|
+
{
|
|
1728
|
+
name: "votingSupportRequiredPct",
|
|
1729
|
+
description: "Percentage of yes-votes required for a DAO vote to pass",
|
|
1730
|
+
contract: "Voting",
|
|
1731
|
+
functionName: "supportRequiredPct",
|
|
1732
|
+
abi: votingAbi,
|
|
1733
|
+
decode: "percent",
|
|
1734
|
+
category: "access",
|
|
1735
|
+
severity: "critical"
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
name: "votingMinAcceptQuorumPct",
|
|
1739
|
+
description: "Minimum participation quorum required for a DAO vote to be valid",
|
|
1740
|
+
contract: "Voting",
|
|
1741
|
+
functionName: "minAcceptQuorumPct",
|
|
1742
|
+
abi: votingAbi,
|
|
1743
|
+
decode: "percent",
|
|
1744
|
+
category: "access",
|
|
1745
|
+
severity: "critical"
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
name: "voteTime",
|
|
1749
|
+
description: "Duration a vote remains open before it can be executed",
|
|
1750
|
+
contract: "Voting",
|
|
1751
|
+
functionName: "voteTime",
|
|
1752
|
+
abi: votingAbi,
|
|
1753
|
+
decode: "uint",
|
|
1754
|
+
category: "config",
|
|
1755
|
+
severity: "info"
|
|
1756
|
+
},
|
|
1757
|
+
{
|
|
1758
|
+
name: "objectionPhaseTime",
|
|
1759
|
+
description: 'Trailing window of a vote during which only "no" votes are accepted \u2014 guards against last-block vote flipping',
|
|
1760
|
+
contract: "Voting",
|
|
1761
|
+
functionName: "objectionPhaseTime",
|
|
1762
|
+
abi: votingAbi,
|
|
1763
|
+
decode: "uint",
|
|
1764
|
+
category: "config",
|
|
1765
|
+
severity: "warning"
|
|
1766
|
+
}
|
|
1767
|
+
],
|
|
1768
|
+
access: [
|
|
1769
|
+
{
|
|
1770
|
+
role: "Aragon Voting",
|
|
1771
|
+
description: "Token-weighted DAO vote \u2014 required to trigger any Agent-executed protocol change (fees, oracle members, guardians, LidoLocator updates)",
|
|
1772
|
+
contract: "Voting",
|
|
1773
|
+
functionName: "supportRequiredPct",
|
|
1774
|
+
abi: votingAbi,
|
|
1775
|
+
severity: "critical"
|
|
1776
|
+
},
|
|
1777
|
+
{
|
|
1778
|
+
role: "Aragon Agent (Treasury/Executor)",
|
|
1779
|
+
description: "Executes DAO-approved calls against every contract resolved via LidoLocator \u2014 holds treasury funds and is the ultimate admin of the protocol",
|
|
1780
|
+
contract: "Agent",
|
|
1781
|
+
functionName: "kernel",
|
|
1782
|
+
abi: agentAbi,
|
|
1783
|
+
severity: "critical"
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
role: "DepositSecurityModule Owner",
|
|
1787
|
+
description: "Can add/remove guardians and change the guardian quorum \u2014 controls who gates validator deposits and emergency pauses",
|
|
1788
|
+
contract: "DepositSecurityModule",
|
|
1789
|
+
functionName: "getOwner",
|
|
1790
|
+
abi: dsmAbi,
|
|
1791
|
+
severity: "critical"
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
role: "Oracle Committee (HashConsensus)",
|
|
1795
|
+
description: "Guardian-style committee whose quorum determines the accepted consensus-layer report \u2014 directly drives the stETH rebase rate every day",
|
|
1796
|
+
contract: "HashConsensus",
|
|
1797
|
+
functionName: "getQuorum",
|
|
1798
|
+
abi: hashConsensusAbi,
|
|
1799
|
+
severity: "critical"
|
|
1800
|
+
}
|
|
1801
|
+
]
|
|
1802
|
+
};
|
|
1803
|
+
|
|
1804
|
+
// src/registry/morpho-blue.ts
|
|
1805
|
+
import { parseAbi as parseAbi9 } from "viem";
|
|
1806
|
+
var morphoAbi = parseAbi9([
|
|
1807
|
+
"function owner() view returns (address)",
|
|
1808
|
+
"function feeRecipient() view returns (address)",
|
|
1809
|
+
"function isIrmEnabled(address irm) view returns (bool)",
|
|
1810
|
+
"function isLltvEnabled(uint256 lltv) view returns (bool)",
|
|
1811
|
+
"function isAuthorized(address authorizer, address authorized) view returns (bool)",
|
|
1812
|
+
"function nonce(address authorizer) view returns (uint256)",
|
|
1813
|
+
"function market(bytes32 id) view returns ((uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee))",
|
|
1814
|
+
"function idToMarketParams(bytes32 id) view returns ((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv))",
|
|
1815
|
+
"function position(bytes32 id, address user) view returns ((uint256 supplyShares, uint128 borrowShares, uint128 collateral))",
|
|
1816
|
+
"function DOMAIN_SEPARATOR() view returns (bytes32)"
|
|
1817
|
+
]);
|
|
1818
|
+
var irmAbi = parseAbi9([
|
|
1819
|
+
"function MORPHO() view returns (address)",
|
|
1820
|
+
"function rateAtTarget(bytes32 id) view returns (int256)"
|
|
1821
|
+
]);
|
|
1822
|
+
var metaMorphoFactoryAbi = parseAbi9([
|
|
1823
|
+
"function isMetaMorpho(address target) view returns (bool)"
|
|
1824
|
+
]);
|
|
1825
|
+
var publicAllocatorAbi = parseAbi9([
|
|
1826
|
+
"function admin(address vault) view returns (address)",
|
|
1827
|
+
"function fee(address vault) view returns (uint256)",
|
|
1828
|
+
"function accruedFee(address vault) view returns (uint256)"
|
|
1829
|
+
]);
|
|
1830
|
+
var chainlinkOracleFactoryAbi = parseAbi9([
|
|
1831
|
+
"function isMorphoChainlinkOracleV2(address target) view returns (bool)"
|
|
1832
|
+
]);
|
|
1833
|
+
var vaultAbi = parseAbi9([
|
|
1834
|
+
"function owner() view returns (address)",
|
|
1835
|
+
"function curator() view returns (address)",
|
|
1836
|
+
"function guardian() view returns (address)",
|
|
1837
|
+
"function isAllocator(address target) view returns (bool)",
|
|
1838
|
+
"function timelock() view returns (uint256)",
|
|
1839
|
+
"function fee() view returns (uint96)",
|
|
1840
|
+
"function feeRecipient() view returns (address)",
|
|
1841
|
+
"function skimRecipient() view returns (address)",
|
|
1842
|
+
"function asset() view returns (address)",
|
|
1843
|
+
"function totalAssets() view returns (uint256)",
|
|
1844
|
+
"function supplyQueueLength() view returns (uint256)",
|
|
1845
|
+
"function withdrawQueueLength() view returns (uint256)",
|
|
1846
|
+
"function config(bytes32 id) view returns ((uint184 cap, bool enabled, uint64 removableAt))",
|
|
1847
|
+
"function DECIMALS_OFFSET() view returns (uint8)",
|
|
1848
|
+
"function MORPHO() view returns (address)"
|
|
1849
|
+
]);
|
|
1850
|
+
var morphoBlue = {
|
|
1851
|
+
id: "morpho-blue",
|
|
1852
|
+
name: "Morpho Blue",
|
|
1853
|
+
version: "1.0",
|
|
1854
|
+
website: "https://morpho.org",
|
|
1855
|
+
description: "Immutable, oracle-agnostic isolated lending primitive with a permissionless MetaMorpho vault curation layer",
|
|
1856
|
+
contracts: {
|
|
1857
|
+
Morpho: { name: "Morpho", role: "Core immutable lending singleton \u2014 isolated markets, no upgradeability", type: "raw" },
|
|
1858
|
+
AdaptiveCurveIrm: { name: "AdaptiveCurveIrm", role: "Default interest rate model targeting ~90% utilization", type: "raw" },
|
|
1859
|
+
MetaMorphoFactory: { name: "MetaMorpho Factory (v1.0)", role: "Original factory deploying MetaMorpho vault instances", type: "raw" },
|
|
1860
|
+
MetaMorphoFactoryV1_1: { name: "MetaMorpho Factory v1.1", role: "Current factory deploying MetaMorphoV1_1 vault instances", type: "raw" },
|
|
1861
|
+
PublicAllocator: { name: "Public Allocator", role: "Permissionless reallocation of vault liquidity within curator-set flow caps", type: "raw" },
|
|
1862
|
+
ChainlinkOracleFactory: { name: "MorphoChainlinkOracleV2 Factory", role: "Deploys Chainlink-backed market oracle adaptors", type: "raw" },
|
|
1863
|
+
BundlerV2: { name: "Bundler V2", role: "Multicall router for atomic supply/borrow/flashloan bundles", type: "raw" },
|
|
1864
|
+
Bundler3: { name: "Bundler3", role: "Successor multicall router with generalized adapters", type: "raw" },
|
|
1865
|
+
FlagshipUsdcVault: { name: "Steakhouse USDC Vault", role: "Representative MetaMorpho vault instance (flagship USDC market)", type: "raw" }
|
|
1866
|
+
},
|
|
1867
|
+
deployments: {
|
|
1868
|
+
ethereum: {
|
|
1869
|
+
Morpho: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
|
|
1870
|
+
AdaptiveCurveIrm: "0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC",
|
|
1871
|
+
MetaMorphoFactory: "0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101",
|
|
1872
|
+
MetaMorphoFactoryV1_1: "0x1897A8997241C1cD4bD0698647e4EB7213535c24",
|
|
1873
|
+
PublicAllocator: "0xfd32fA2ca22c76dD6E550706Ad913FC6CE91c75D",
|
|
1874
|
+
ChainlinkOracleFactory: "0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766",
|
|
1875
|
+
BundlerV2: "0x4095F064B8d3c3548A3bebfd0Bbfd04750E30077",
|
|
1876
|
+
Bundler3: "0x6566194141eefa99Af43Bb5Aa71460Ca2Dc90245",
|
|
1877
|
+
FlagshipUsdcVault: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB"
|
|
1878
|
+
},
|
|
1879
|
+
base: {
|
|
1880
|
+
Morpho: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
|
|
1881
|
+
AdaptiveCurveIrm: "0x46415998764C29aB2a25CbeA6254146D50D22687",
|
|
1882
|
+
MetaMorphoFactoryV1_1: "0xFf62A7c278C62eD665133147129245053Bbf5918",
|
|
1883
|
+
PublicAllocator: "0xA090dD1a701408Df1d4d0B85b716c87565f90467",
|
|
1884
|
+
ChainlinkOracleFactory: "0x2DC205F24BCb6B311E5cdf0745B0741648Aebd3d",
|
|
1885
|
+
BundlerV2: "0x23055618898e202386e6c13955a58D3C68200BFB",
|
|
1886
|
+
Bundler3: "0x6BFd8137e702540E7A42B74178A4a49Ba43920C4",
|
|
1887
|
+
FlagshipUsdcVault: "0xCBeeF01994E24a60f7DCB8De98e75AD8BD4Ad60d"
|
|
1888
|
+
}
|
|
1889
|
+
},
|
|
1890
|
+
params: [
|
|
1891
|
+
{
|
|
1892
|
+
name: "morphoOwner",
|
|
1893
|
+
description: "Morpho core owner \u2014 can enable new IRMs/LLTVs and set the fee recipient, but cannot touch existing market params (immutable by design)",
|
|
1894
|
+
contract: "Morpho",
|
|
1895
|
+
functionName: "owner",
|
|
1896
|
+
abi: morphoAbi,
|
|
1897
|
+
decode: "address",
|
|
1898
|
+
category: "access",
|
|
1899
|
+
severity: "critical"
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
name: "morphoFeeRecipient",
|
|
1903
|
+
description: "Recipient of protocol fees accrued across all markets",
|
|
1904
|
+
contract: "Morpho",
|
|
1905
|
+
functionName: "feeRecipient",
|
|
1906
|
+
abi: morphoAbi,
|
|
1907
|
+
decode: "address",
|
|
1908
|
+
category: "config",
|
|
1909
|
+
severity: "warning"
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
name: "adaptiveCurveIrmEnabled",
|
|
1913
|
+
description: "Whether AdaptiveCurveIrm is whitelisted as an approved IRM on Ethereum mainnet",
|
|
1914
|
+
contract: "Morpho",
|
|
1915
|
+
functionName: "isIrmEnabled",
|
|
1916
|
+
args: ["0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC"],
|
|
1917
|
+
abi: morphoAbi,
|
|
1918
|
+
decode: "bool",
|
|
1919
|
+
category: "config",
|
|
1920
|
+
severity: "info"
|
|
1921
|
+
},
|
|
1922
|
+
{
|
|
1923
|
+
name: "lltv86PercentEnabled",
|
|
1924
|
+
description: "Whether the 86% LLTV tier is whitelisted (one of the standard risk tiers)",
|
|
1925
|
+
contract: "Morpho",
|
|
1926
|
+
functionName: "isLltvEnabled",
|
|
1927
|
+
args: [860000000000000000n],
|
|
1928
|
+
abi: morphoAbi,
|
|
1929
|
+
decode: "bool",
|
|
1930
|
+
category: "risk",
|
|
1931
|
+
severity: "info"
|
|
1932
|
+
},
|
|
1933
|
+
{
|
|
1934
|
+
name: "irmMorphoRef",
|
|
1935
|
+
description: "Morpho core address the AdaptiveCurveIrm is bound to \u2014 must match the deployed Morpho singleton",
|
|
1936
|
+
contract: "AdaptiveCurveIrm",
|
|
1937
|
+
functionName: "MORPHO",
|
|
1938
|
+
abi: irmAbi,
|
|
1939
|
+
decode: "address",
|
|
1940
|
+
category: "config",
|
|
1941
|
+
severity: "info"
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
name: "factoryRecognizesFlagshipVault",
|
|
1945
|
+
description: "Confirms the flagship USDC vault (deployed pre-v1.1) was created by the original MetaMorphoFactory \u2014 sanity check against spoofed vault UIs",
|
|
1946
|
+
contract: "MetaMorphoFactory",
|
|
1947
|
+
functionName: "isMetaMorpho",
|
|
1948
|
+
args: ["0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB"],
|
|
1949
|
+
abi: metaMorphoFactoryAbi,
|
|
1950
|
+
decode: "bool",
|
|
1951
|
+
category: "config",
|
|
1952
|
+
severity: "info"
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
name: "cbbtcUsdcOracleIsFactoryDeployed",
|
|
1956
|
+
description: "Confirms the oracle used by the flagship vault's first supply-queue market (cbBTC/USDC, 86% LLTV) was deployed by the canonical MorphoChainlinkOracleV2Factory \u2014 sanity check against a spoofed/malicious oracle",
|
|
1957
|
+
contract: "ChainlinkOracleFactory",
|
|
1958
|
+
functionName: "isMorphoChainlinkOracleV2",
|
|
1959
|
+
args: ["0xA6D6950c9F177F1De7f7757FB33539e3Ec60182a"],
|
|
1960
|
+
abi: chainlinkOracleFactoryAbi,
|
|
1961
|
+
decode: "bool",
|
|
1962
|
+
category: "risk",
|
|
1963
|
+
severity: "info"
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
name: "publicAllocatorAdmin",
|
|
1967
|
+
description: "Address allowed to set flow caps for the flagship vault on the Public Allocator \u2014 can trigger instant reallocation within curator-approved caps",
|
|
1968
|
+
contract: "PublicAllocator",
|
|
1969
|
+
functionName: "admin",
|
|
1970
|
+
args: ["0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB"],
|
|
1971
|
+
abi: publicAllocatorAbi,
|
|
1972
|
+
decode: "address",
|
|
1973
|
+
category: "access",
|
|
1974
|
+
severity: "warning"
|
|
1975
|
+
},
|
|
1976
|
+
{
|
|
1977
|
+
name: "publicAllocatorFee",
|
|
1978
|
+
description: "Fee (wei) charged by the Public Allocator for reallocating the flagship vault",
|
|
1979
|
+
contract: "PublicAllocator",
|
|
1980
|
+
functionName: "fee",
|
|
1981
|
+
args: ["0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB"],
|
|
1982
|
+
abi: publicAllocatorAbi,
|
|
1983
|
+
decode: "uint",
|
|
1984
|
+
category: "config",
|
|
1985
|
+
severity: "info"
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
name: "vaultOwner",
|
|
1989
|
+
description: "Vault owner \u2014 can set curator, guardian, timelock and allocators; ultimate control over vault configuration",
|
|
1990
|
+
contract: "FlagshipUsdcVault",
|
|
1991
|
+
functionName: "owner",
|
|
1992
|
+
abi: vaultAbi,
|
|
1993
|
+
decode: "address",
|
|
1994
|
+
category: "access",
|
|
1995
|
+
severity: "critical"
|
|
1996
|
+
},
|
|
1997
|
+
{
|
|
1998
|
+
name: "vaultCurator",
|
|
1999
|
+
description: "Curator \u2014 whitelists markets and sets/lowers supply caps for the vault",
|
|
2000
|
+
contract: "FlagshipUsdcVault",
|
|
2001
|
+
functionName: "curator",
|
|
2002
|
+
abi: vaultAbi,
|
|
2003
|
+
decode: "address",
|
|
2004
|
+
category: "access",
|
|
2005
|
+
severity: "critical"
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
name: "vaultGuardian",
|
|
2009
|
+
description: "Guardian \u2014 can revoke pending timelocked changes (cap increases, guardian/timelock changes, market removals)",
|
|
2010
|
+
contract: "FlagshipUsdcVault",
|
|
2011
|
+
functionName: "guardian",
|
|
2012
|
+
abi: vaultAbi,
|
|
2013
|
+
decode: "address",
|
|
2014
|
+
category: "access",
|
|
2015
|
+
severity: "critical"
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
name: "vaultTimelock",
|
|
2019
|
+
description: "Timelock delay (seconds) applied to risk-sensitive vault changes",
|
|
2020
|
+
contract: "FlagshipUsdcVault",
|
|
2021
|
+
functionName: "timelock",
|
|
2022
|
+
abi: vaultAbi,
|
|
2023
|
+
decode: "uint",
|
|
2024
|
+
category: "config",
|
|
2025
|
+
severity: "warning"
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
name: "vaultFee",
|
|
2029
|
+
description: "Performance fee taken from vault yield (WAD-scaled fraction)",
|
|
2030
|
+
contract: "FlagshipUsdcVault",
|
|
2031
|
+
functionName: "fee",
|
|
2032
|
+
abi: vaultAbi,
|
|
2033
|
+
decode: "wad",
|
|
2034
|
+
category: "config",
|
|
2035
|
+
severity: "warning"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
name: "vaultFeeRecipient",
|
|
2039
|
+
description: "Recipient of the vault performance fee",
|
|
2040
|
+
contract: "FlagshipUsdcVault",
|
|
2041
|
+
functionName: "feeRecipient",
|
|
2042
|
+
abi: vaultAbi,
|
|
2043
|
+
decode: "address",
|
|
2044
|
+
category: "config",
|
|
2045
|
+
severity: "warning"
|
|
2046
|
+
},
|
|
2047
|
+
{
|
|
2048
|
+
name: "vaultSkimRecipient",
|
|
2049
|
+
description: "Receives non-asset token dust skimmed from the vault",
|
|
2050
|
+
contract: "FlagshipUsdcVault",
|
|
2051
|
+
functionName: "skimRecipient",
|
|
2052
|
+
abi: vaultAbi,
|
|
2053
|
+
decode: "address",
|
|
2054
|
+
category: "config",
|
|
2055
|
+
severity: "info"
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
name: "vaultAsset",
|
|
2059
|
+
description: "Underlying ERC-4626 asset held by the vault",
|
|
2060
|
+
contract: "FlagshipUsdcVault",
|
|
2061
|
+
functionName: "asset",
|
|
2062
|
+
abi: vaultAbi,
|
|
2063
|
+
decode: "address",
|
|
2064
|
+
category: "config",
|
|
2065
|
+
severity: "info"
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
name: "vaultTotalAssets",
|
|
2069
|
+
description: "Total assets under management in the vault",
|
|
2070
|
+
contract: "FlagshipUsdcVault",
|
|
2071
|
+
functionName: "totalAssets",
|
|
2072
|
+
abi: vaultAbi,
|
|
2073
|
+
decode: "uint",
|
|
2074
|
+
category: "state",
|
|
2075
|
+
severity: "info"
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
name: "vaultSupplyQueueLength",
|
|
2079
|
+
description: "Number of markets in the vault supply queue",
|
|
2080
|
+
contract: "FlagshipUsdcVault",
|
|
2081
|
+
functionName: "supplyQueueLength",
|
|
2082
|
+
abi: vaultAbi,
|
|
2083
|
+
decode: "uint",
|
|
2084
|
+
category: "state",
|
|
2085
|
+
severity: "info"
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
name: "vaultWithdrawQueueLength",
|
|
2089
|
+
description: "Number of markets in the vault withdraw queue",
|
|
2090
|
+
contract: "FlagshipUsdcVault",
|
|
2091
|
+
functionName: "withdrawQueueLength",
|
|
2092
|
+
abi: vaultAbi,
|
|
2093
|
+
decode: "uint",
|
|
2094
|
+
category: "state",
|
|
2095
|
+
severity: "info"
|
|
2096
|
+
}
|
|
2097
|
+
],
|
|
2098
|
+
access: [
|
|
2099
|
+
{
|
|
2100
|
+
role: "Morpho Core Owner",
|
|
2101
|
+
description: "Can enable new IRMs/LLTVs and change the fee recipient. Cannot alter existing market params or seize funds \u2014 Morpho Blue markets are immutable once created",
|
|
2102
|
+
contract: "Morpho",
|
|
2103
|
+
functionName: "owner",
|
|
2104
|
+
abi: morphoAbi,
|
|
2105
|
+
severity: "critical"
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
role: "Vault Owner",
|
|
2109
|
+
description: "Full control over a MetaMorpho vault \u2014 sets curator, guardian, timelock, allocators, fee recipient",
|
|
2110
|
+
contract: "FlagshipUsdcVault",
|
|
2111
|
+
functionName: "owner",
|
|
2112
|
+
abi: vaultAbi,
|
|
2113
|
+
severity: "critical"
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
role: "Vault Curator",
|
|
2117
|
+
description: "Decides which markets the vault can allocate to and the supply cap per market \u2014 primary risk-management role",
|
|
2118
|
+
contract: "FlagshipUsdcVault",
|
|
2119
|
+
functionName: "curator",
|
|
2120
|
+
abi: vaultAbi,
|
|
2121
|
+
severity: "critical"
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
role: "Vault Guardian",
|
|
2125
|
+
description: "Can revoke pending timelocked changes (malicious cap increases, curator/guardian/timelock swaps) before they execute",
|
|
2126
|
+
contract: "FlagshipUsdcVault",
|
|
2127
|
+
functionName: "guardian",
|
|
2128
|
+
abi: vaultAbi,
|
|
2129
|
+
severity: "critical"
|
|
2130
|
+
}
|
|
2131
|
+
// Note: Public Allocator's admin/fee are per-vault (take an `address vault`
|
|
2132
|
+
// argument), so they're only tracked as `params` (which support `args`) —
|
|
2133
|
+
// `access` entries are always read with zero arguments by the scanner.
|
|
2134
|
+
]
|
|
2135
|
+
};
|
|
2136
|
+
|
|
2137
|
+
// src/registry/index.ts
|
|
2138
|
+
var protocols = {
|
|
2139
|
+
"aave-v3": aaveV3,
|
|
2140
|
+
"compound-v3": compoundV3,
|
|
2141
|
+
"uniswap-v3": uniswapV3,
|
|
2142
|
+
"maker": maker,
|
|
2143
|
+
"eigenlayer": eigenLayer,
|
|
2144
|
+
"pendle": pendle,
|
|
2145
|
+
"lido": lido,
|
|
2146
|
+
"morpho-blue": morphoBlue
|
|
2147
|
+
};
|
|
2148
|
+
function getProtocol(id) {
|
|
2149
|
+
return protocols[id];
|
|
2150
|
+
}
|
|
2151
|
+
function listProtocols() {
|
|
2152
|
+
return Object.values(protocols);
|
|
2153
|
+
}
|
|
2154
|
+
function getProtocolForChain(id, chain) {
|
|
2155
|
+
const protocol = protocols[id];
|
|
2156
|
+
if (!protocol) return void 0;
|
|
2157
|
+
const addresses = protocol.deployments[chain];
|
|
2158
|
+
if (!addresses) return void 0;
|
|
2159
|
+
return { protocol, addresses };
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/scanner.ts
|
|
2163
|
+
function createClient(chainName, config) {
|
|
2164
|
+
const chain = resolveChain(chainName);
|
|
2165
|
+
return createPublicClient({
|
|
2166
|
+
chain,
|
|
2167
|
+
transport: http(config.rpc),
|
|
2168
|
+
batch: { multicall: true }
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
async function scanContract(client, chainName, name, entry) {
|
|
2172
|
+
const address = entry.address;
|
|
2173
|
+
const [blockNumber, block, proxyInfo, publicReads] = await Promise.all([
|
|
2174
|
+
client.getBlockNumber(),
|
|
2175
|
+
client.getBlock({ blockTag: "latest" }),
|
|
2176
|
+
entry.type === "proxy" ? detectProxy(client, address) : Promise.resolve(null),
|
|
2177
|
+
readCommonState(client, address)
|
|
2178
|
+
]);
|
|
2179
|
+
if (proxyInfo?.implementation) {
|
|
2180
|
+
const implReads = await readCommonState(client, proxyInfo.implementation);
|
|
2181
|
+
for (const [key, value] of Object.entries(implReads)) {
|
|
2182
|
+
if (!(key in publicReads)) {
|
|
2183
|
+
publicReads[`impl.${key}`] = value;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return {
|
|
2188
|
+
address,
|
|
2189
|
+
chain: chainName,
|
|
2190
|
+
blockNumber,
|
|
2191
|
+
timestamp: Number(block.timestamp),
|
|
2192
|
+
proxyInfo,
|
|
2193
|
+
storageSlots: [],
|
|
2194
|
+
publicReads
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
async function readParam(client, address, param) {
|
|
2198
|
+
try {
|
|
2199
|
+
const result = await client.readContract({
|
|
2200
|
+
address,
|
|
2201
|
+
abi: param.abi,
|
|
2202
|
+
functionName: param.functionName,
|
|
2203
|
+
args: param.args || []
|
|
2204
|
+
});
|
|
2205
|
+
let value = String(result);
|
|
2206
|
+
if (param.decode === "bps" && typeof result === "bigint") {
|
|
2207
|
+
value = `${result} (${(Number(result) / 100).toFixed(2)}%)`;
|
|
2208
|
+
} else if (param.decode === "ray" && typeof result === "bigint") {
|
|
2209
|
+
value = `${(Number(result) / 1e27).toFixed(6)}`;
|
|
2210
|
+
} else if (param.decode === "wad" && typeof result === "bigint") {
|
|
2211
|
+
value = `${(Number(result) / 1e18).toFixed(6)}`;
|
|
2212
|
+
} else if (param.decode === "percent" && typeof result === "bigint") {
|
|
2213
|
+
value = `${Number(result)}%`;
|
|
2214
|
+
}
|
|
2215
|
+
return { name: param.name, value, description: param.description };
|
|
2216
|
+
} catch {
|
|
2217
|
+
return null;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
async function readAccess(client, address, access) {
|
|
2221
|
+
try {
|
|
2222
|
+
const result = await client.readContract({
|
|
2223
|
+
address,
|
|
2224
|
+
abi: access.abi,
|
|
2225
|
+
functionName: access.functionName
|
|
2226
|
+
});
|
|
2227
|
+
return { role: access.role, holder: String(result), description: access.description };
|
|
2228
|
+
} catch {
|
|
2229
|
+
return null;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
async function scanProtocol(client, protocolId, chainName) {
|
|
2233
|
+
const found = getProtocolForChain(protocolId, chainName);
|
|
2234
|
+
if (!found) return null;
|
|
2235
|
+
const { protocol, addresses } = found;
|
|
2236
|
+
const [blockNumber, block] = await Promise.all([
|
|
2237
|
+
client.getBlockNumber(),
|
|
2238
|
+
client.getBlock({ blockTag: "latest" })
|
|
2239
|
+
]);
|
|
2240
|
+
const contracts = {};
|
|
2241
|
+
for (const [key, def] of Object.entries(protocol.contracts)) {
|
|
2242
|
+
const addr = addresses[key];
|
|
2243
|
+
if (!addr) continue;
|
|
2244
|
+
const proxyInfo = def.type === "proxy" ? await detectProxy(client, addr) : null;
|
|
2245
|
+
contracts[key] = {
|
|
2246
|
+
address: addr,
|
|
2247
|
+
role: def.role,
|
|
2248
|
+
proxyInfo: proxyInfo ? {
|
|
2249
|
+
implementation: proxyInfo.implementation,
|
|
2250
|
+
admin: proxyInfo.admin
|
|
2251
|
+
} : null
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
const params = {};
|
|
2255
|
+
for (const paramDef of protocol.params) {
|
|
2256
|
+
const contractAddr = addresses[paramDef.contract];
|
|
2257
|
+
if (!contractAddr) continue;
|
|
2258
|
+
const result = await readParam(client, contractAddr, paramDef);
|
|
2259
|
+
if (result) {
|
|
2260
|
+
params[result.name] = {
|
|
2261
|
+
value: result.value,
|
|
2262
|
+
description: result.description,
|
|
2263
|
+
category: paramDef.category,
|
|
2264
|
+
severity: paramDef.severity
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
const access = [];
|
|
2269
|
+
for (const accessDef of protocol.access) {
|
|
2270
|
+
const contractAddr = addresses[accessDef.contract];
|
|
2271
|
+
if (!contractAddr) continue;
|
|
2272
|
+
const result = await readAccess(client, contractAddr, accessDef);
|
|
2273
|
+
if (result) {
|
|
2274
|
+
access.push({ ...result, severity: accessDef.severity });
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return {
|
|
2278
|
+
protocolId,
|
|
2279
|
+
protocolName: protocol.name,
|
|
2280
|
+
chain: chainName,
|
|
2281
|
+
blockNumber,
|
|
2282
|
+
timestamp: Number(block.timestamp),
|
|
2283
|
+
contracts,
|
|
2284
|
+
params,
|
|
2285
|
+
access
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/snapshot.ts
|
|
2290
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, readdirSync } from "fs";
|
|
2291
|
+
import { join } from "path";
|
|
2292
|
+
var SNAPSHOT_DIR = ".protoscan";
|
|
2293
|
+
function ensureDir(dir) {
|
|
2294
|
+
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
2295
|
+
}
|
|
2296
|
+
function snapshotPath(dir, chain, label) {
|
|
2297
|
+
return join(dir, `${chain}_${label}.json`);
|
|
2298
|
+
}
|
|
2299
|
+
function serializeSnapshot(snap) {
|
|
2300
|
+
return JSON.stringify(
|
|
2301
|
+
snap,
|
|
2302
|
+
(_, value) => typeof value === "bigint" ? value.toString() : value,
|
|
2303
|
+
2
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
function saveSnapshot(contracts, chain, label) {
|
|
2307
|
+
const dir = SNAPSHOT_DIR;
|
|
2308
|
+
ensureDir(dir);
|
|
2309
|
+
const now = /* @__PURE__ */ new Date();
|
|
2310
|
+
const autoLabel = label || now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2311
|
+
const firstContract = Object.values(contracts)[0];
|
|
2312
|
+
const snapshot = {
|
|
2313
|
+
version: "1",
|
|
2314
|
+
createdAt: now.toISOString(),
|
|
2315
|
+
chain,
|
|
2316
|
+
blockNumber: firstContract ? firstContract.blockNumber.toString() : "0",
|
|
2317
|
+
contracts
|
|
2318
|
+
};
|
|
2319
|
+
const filePath = snapshotPath(dir, chain, autoLabel);
|
|
2320
|
+
writeFileSync2(filePath, serializeSnapshot(snapshot), "utf-8");
|
|
2321
|
+
return filePath;
|
|
2322
|
+
}
|
|
2323
|
+
function loadSnapshot(filePath) {
|
|
2324
|
+
const raw = readFileSync2(filePath, "utf-8");
|
|
2325
|
+
return JSON.parse(raw);
|
|
2326
|
+
}
|
|
2327
|
+
function listSnapshots(chain) {
|
|
2328
|
+
if (!existsSync2(SNAPSHOT_DIR)) return [];
|
|
2329
|
+
const files = readdirSync(SNAPSHOT_DIR).filter((f) => f.endsWith(".json"));
|
|
2330
|
+
if (chain) return files.filter((f) => f.startsWith(`${chain}_`));
|
|
2331
|
+
return files;
|
|
2332
|
+
}
|
|
2333
|
+
function resolveSnapshotFile(nameOrPath, chain) {
|
|
2334
|
+
if (existsSync2(nameOrPath)) return nameOrPath;
|
|
2335
|
+
const inDir = join(SNAPSHOT_DIR, nameOrPath);
|
|
2336
|
+
if (existsSync2(inDir)) return inDir;
|
|
2337
|
+
if (chain) {
|
|
2338
|
+
const withChain = join(SNAPSHOT_DIR, `${chain}_${nameOrPath}.json`);
|
|
2339
|
+
if (existsSync2(withChain)) return withChain;
|
|
2340
|
+
}
|
|
2341
|
+
throw new Error(`Snapshot not found: ${nameOrPath}`);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// src/diff.ts
|
|
2345
|
+
var CRITICAL_FIELDS = /* @__PURE__ */ new Set([
|
|
2346
|
+
"erc1967.implementation",
|
|
2347
|
+
"erc1967.admin",
|
|
2348
|
+
"owner",
|
|
2349
|
+
"admin",
|
|
2350
|
+
"guardian",
|
|
2351
|
+
"governance",
|
|
2352
|
+
"timelock",
|
|
2353
|
+
"pendingOwner"
|
|
2354
|
+
]);
|
|
2355
|
+
var WARNING_FIELDS = /* @__PURE__ */ new Set([
|
|
2356
|
+
"paused",
|
|
2357
|
+
"erc1967.beacon"
|
|
2358
|
+
]);
|
|
2359
|
+
function severity(field) {
|
|
2360
|
+
if (CRITICAL_FIELDS.has(field)) return "critical";
|
|
2361
|
+
if (WARNING_FIELDS.has(field)) return "warning";
|
|
2362
|
+
return "info";
|
|
2363
|
+
}
|
|
2364
|
+
function diffSnapshots(from, to) {
|
|
2365
|
+
const entries = [];
|
|
2366
|
+
const allContracts = /* @__PURE__ */ new Set([
|
|
2367
|
+
...Object.keys(from.contracts),
|
|
2368
|
+
...Object.keys(to.contracts)
|
|
2369
|
+
]);
|
|
2370
|
+
for (const name of allContracts) {
|
|
2371
|
+
const fromContract = from.contracts[name];
|
|
2372
|
+
const toContract = to.contracts[name];
|
|
2373
|
+
if (!fromContract && toContract) {
|
|
2374
|
+
entries.push({
|
|
2375
|
+
contract: name,
|
|
2376
|
+
field: "_contract",
|
|
2377
|
+
before: "(not present)",
|
|
2378
|
+
after: toContract.address,
|
|
2379
|
+
severity: "warning"
|
|
2380
|
+
});
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
if (fromContract && !toContract) {
|
|
2384
|
+
entries.push({
|
|
2385
|
+
contract: name,
|
|
2386
|
+
field: "_contract",
|
|
2387
|
+
before: fromContract.address,
|
|
2388
|
+
after: "(removed)",
|
|
2389
|
+
severity: "critical"
|
|
2390
|
+
});
|
|
2391
|
+
continue;
|
|
2392
|
+
}
|
|
2393
|
+
if (!fromContract || !toContract) continue;
|
|
2394
|
+
if (fromContract.proxyInfo || toContract.proxyInfo) {
|
|
2395
|
+
const fp = fromContract.proxyInfo || { implementation: null, admin: null, beacon: null };
|
|
2396
|
+
const tp = toContract.proxyInfo || { implementation: null, admin: null, beacon: null };
|
|
2397
|
+
for (const key of ["implementation", "admin", "beacon"]) {
|
|
2398
|
+
const fromVal = fp[key] || "(none)";
|
|
2399
|
+
const toVal = tp[key] || "(none)";
|
|
2400
|
+
if (fromVal !== toVal) {
|
|
2401
|
+
entries.push({
|
|
2402
|
+
contract: name,
|
|
2403
|
+
field: `erc1967.${key}`,
|
|
2404
|
+
before: fromVal,
|
|
2405
|
+
after: toVal,
|
|
2406
|
+
severity: severity(`erc1967.${key}`)
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
2412
|
+
...Object.keys(fromContract.publicReads),
|
|
2413
|
+
...Object.keys(toContract.publicReads)
|
|
2414
|
+
]);
|
|
2415
|
+
for (const key of allKeys) {
|
|
2416
|
+
const fromVal = fromContract.publicReads[key] ?? "(none)";
|
|
2417
|
+
const toVal = toContract.publicReads[key] ?? "(none)";
|
|
2418
|
+
if (fromVal !== toVal) {
|
|
2419
|
+
entries.push({
|
|
2420
|
+
contract: name,
|
|
2421
|
+
field: key,
|
|
2422
|
+
before: fromVal,
|
|
2423
|
+
after: toVal,
|
|
2424
|
+
severity: severity(key)
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
entries.sort((a, b) => {
|
|
2430
|
+
const sev = { critical: 0, warning: 1, info: 2 };
|
|
2431
|
+
return sev[a.severity] - sev[b.severity];
|
|
2432
|
+
});
|
|
2433
|
+
return {
|
|
2434
|
+
from: from.createdAt,
|
|
2435
|
+
to: to.createdAt,
|
|
2436
|
+
chain: to.chain,
|
|
2437
|
+
entries
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/index.ts
|
|
2442
|
+
var program = new Command();
|
|
2443
|
+
program.name("protoscan").description("On-chain protocol state tracker and diff tool for DeFi teams").version("0.1.0");
|
|
2444
|
+
program.command("init").description("Initialize protoscan config in current directory").action(() => {
|
|
2445
|
+
try {
|
|
2446
|
+
const path = initConfig();
|
|
2447
|
+
console.log(chalk.green(`Created ${path}`));
|
|
2448
|
+
console.log(chalk.dim("Edit it to add your chains and contracts.\n"));
|
|
2449
|
+
console.log("Or use built-in protocol definitions:");
|
|
2450
|
+
console.log(chalk.cyan(" protoscan protocols # list supported protocols"));
|
|
2451
|
+
console.log(chalk.cyan(" protoscan scan aave-v3 -c ethereum # scan Aave V3"));
|
|
2452
|
+
} catch (e) {
|
|
2453
|
+
console.error(chalk.red(e.message));
|
|
2454
|
+
process.exit(1);
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
program.command("protocols").description("List supported protocols in the built-in registry").action(() => {
|
|
2458
|
+
const protos = listProtocols();
|
|
2459
|
+
console.log(chalk.bold("\nSupported protocols:\n"));
|
|
2460
|
+
for (const p of protos) {
|
|
2461
|
+
const chains = Object.keys(p.deployments).join(", ");
|
|
2462
|
+
const paramCount = p.params.length;
|
|
2463
|
+
const accessCount = p.access.length;
|
|
2464
|
+
console.log(` ${chalk.cyan(p.id.padEnd(16))} ${p.name} v${p.version}`);
|
|
2465
|
+
console.log(chalk.dim(`${"".padEnd(18)}chains: ${chains}`));
|
|
2466
|
+
console.log(chalk.dim(`${"".padEnd(18)}${paramCount} params, ${accessCount} access roles, ${Object.keys(p.contracts).length} contracts
|
|
2467
|
+
`));
|
|
2468
|
+
}
|
|
2469
|
+
console.log(chalk.dim("Usage: protoscan scan <protocol-id> -c <chain>"));
|
|
2470
|
+
});
|
|
2471
|
+
program.command("scan").description("Scan a known protocol using built-in definitions").argument("<protocol>", "Protocol ID (e.g. aave-v3, compound-v3)").option("-c, --chain <chain>", "Chain to scan", "ethereum").option("-l, --label <label>", "Label for saved snapshot").action(async (protocolId, opts) => {
|
|
2472
|
+
const proto = getProtocol(protocolId);
|
|
2473
|
+
if (!proto) {
|
|
2474
|
+
console.error(chalk.red(`Unknown protocol: ${protocolId}`));
|
|
2475
|
+
console.log(chalk.dim('Run "protoscan protocols" to see available protocols.'));
|
|
2476
|
+
process.exit(1);
|
|
2477
|
+
}
|
|
2478
|
+
const chainName = opts.chain;
|
|
2479
|
+
if (!proto.deployments[chainName]) {
|
|
2480
|
+
console.error(chalk.red(`${proto.name} is not deployed on ${chainName}`));
|
|
2481
|
+
console.log(chalk.dim(`Available: ${Object.keys(proto.deployments).join(", ")}`));
|
|
2482
|
+
process.exit(1);
|
|
2483
|
+
}
|
|
2484
|
+
const config = loadConfig();
|
|
2485
|
+
const chainConfig = config.chains[chainName];
|
|
2486
|
+
if (!chainConfig) {
|
|
2487
|
+
console.error(chalk.red(`Chain "${chainName}" not in config. Add an RPC URL first.`));
|
|
2488
|
+
process.exit(1);
|
|
2489
|
+
}
|
|
2490
|
+
const spinner = ora(`Scanning ${proto.name} on ${chainName}...`).start();
|
|
2491
|
+
const client = createClient(chainName, chainConfig);
|
|
2492
|
+
try {
|
|
2493
|
+
const result = await scanProtocol(client, protocolId, chainName);
|
|
2494
|
+
if (!result) {
|
|
2495
|
+
spinner.fail("Scan returned no results");
|
|
2496
|
+
process.exit(1);
|
|
2497
|
+
}
|
|
2498
|
+
spinner.succeed(`${proto.name} on ${chainName} @ block ${result.blockNumber}
|
|
2499
|
+
`);
|
|
2500
|
+
console.log(chalk.bold.underline("Contracts"));
|
|
2501
|
+
for (const [key, info] of Object.entries(result.contracts)) {
|
|
2502
|
+
const proxyTag = info.proxyInfo ? chalk.dim(" (proxy)") : "";
|
|
2503
|
+
console.log(` ${chalk.bold(key)}${proxyTag}: ${info.address}`);
|
|
2504
|
+
console.log(chalk.dim(` ${info.role}`));
|
|
2505
|
+
if (info.proxyInfo?.implementation) {
|
|
2506
|
+
console.log(` impl: ${chalk.cyan(info.proxyInfo.implementation)}`);
|
|
2507
|
+
}
|
|
2508
|
+
if (info.proxyInfo?.admin) {
|
|
2509
|
+
console.log(` admin: ${chalk.yellow(info.proxyInfo.admin)}`);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
if (result.access.length > 0) {
|
|
2513
|
+
console.log(chalk.bold.underline("\nAccess Control"));
|
|
2514
|
+
for (const a of result.access) {
|
|
2515
|
+
const icon = a.severity === "critical" ? chalk.red("\u2717") : chalk.yellow("!");
|
|
2516
|
+
console.log(` ${icon} ${chalk.bold(a.role)}`);
|
|
2517
|
+
console.log(` holder: ${chalk.yellow(a.holder)}`);
|
|
2518
|
+
console.log(chalk.dim(` ${a.description}`));
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
const categories = ["risk", "rate", "config", "state", "access"];
|
|
2522
|
+
for (const cat of categories) {
|
|
2523
|
+
const catParams = Object.entries(result.params).filter(([_, v]) => v.category === cat);
|
|
2524
|
+
if (catParams.length === 0) continue;
|
|
2525
|
+
console.log(chalk.bold.underline(`
|
|
2526
|
+
${cat.charAt(0).toUpperCase() + cat.slice(1)} Parameters`));
|
|
2527
|
+
for (const [name, info] of catParams) {
|
|
2528
|
+
const icon = info.severity === "critical" ? chalk.red("\u2717") : info.severity === "warning" ? chalk.yellow("!") : chalk.dim("~");
|
|
2529
|
+
console.log(` ${icon} ${name}: ${chalk.cyan(info.value)}`);
|
|
2530
|
+
console.log(chalk.dim(` ${info.description}`));
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
const snapData = {
|
|
2534
|
+
_protocol: { id: protocolId, name: proto.name, chain: chainName },
|
|
2535
|
+
_block: result.blockNumber.toString(),
|
|
2536
|
+
_timestamp: result.timestamp,
|
|
2537
|
+
contracts: result.contracts,
|
|
2538
|
+
params: result.params,
|
|
2539
|
+
access: result.access
|
|
2540
|
+
};
|
|
2541
|
+
const label = opts.label || (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2542
|
+
const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync2, existsSync: existsSync3 } = await import("fs");
|
|
2543
|
+
const dir = ".protoscan";
|
|
2544
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
2545
|
+
const filePath = `${dir}/${protocolId}_${chainName}_${label}.json`;
|
|
2546
|
+
writeFileSync3(filePath, JSON.stringify(snapData, null, 2), "utf-8");
|
|
2547
|
+
console.log(chalk.dim(`
|
|
2548
|
+
Saved: ${filePath}`));
|
|
2549
|
+
} catch (e) {
|
|
2550
|
+
spinner.fail(`Scan failed: ${e.message}`);
|
|
2551
|
+
process.exit(1);
|
|
2552
|
+
}
|
|
2553
|
+
});
|
|
2554
|
+
program.command("snapshot").description("Snapshot custom contracts from config").option("-c, --chain <chain>", "Chain to snapshot (default: all)").option("-l, --label <label>", "Snapshot label").action(async (opts) => {
|
|
2555
|
+
const config = loadConfig();
|
|
2556
|
+
const targetChains = opts.chain ? [opts.chain] : Object.keys(config.chains);
|
|
2557
|
+
for (const chainName of targetChains) {
|
|
2558
|
+
const chainConfig = config.chains[chainName];
|
|
2559
|
+
if (!chainConfig) {
|
|
2560
|
+
console.error(chalk.red(`Chain "${chainName}" not in config.`));
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
const spinner = ora(`Scanning ${chainName}...`).start();
|
|
2564
|
+
const client = createClient(chainName, chainConfig);
|
|
2565
|
+
const results = {};
|
|
2566
|
+
const contractEntries = Object.entries(config.contracts).filter(
|
|
2567
|
+
([_, entry]) => entry.chains.includes(chainName)
|
|
2568
|
+
);
|
|
2569
|
+
if (contractEntries.length === 0) {
|
|
2570
|
+
spinner.warn(`No contracts configured for ${chainName}`);
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
for (const [name, entry] of contractEntries) {
|
|
2574
|
+
spinner.text = `Scanning ${chainName} / ${name}...`;
|
|
2575
|
+
try {
|
|
2576
|
+
results[name] = await scanContract(client, chainName, name, entry);
|
|
2577
|
+
} catch (e) {
|
|
2578
|
+
spinner.warn(`Failed to scan ${name}: ${e.message}`);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
const filePath = saveSnapshot(results, chainName, opts.label);
|
|
2582
|
+
spinner.succeed(`${chainName}: ${Object.keys(results).length} contracts \u2192 ${filePath}`);
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2585
|
+
program.command("diff").description("Compare two snapshots").argument("<from>", "First snapshot").argument("<to>", "Second snapshot").option("-c, --chain <chain>", "Chain filter").action((fromArg, toArg, opts) => {
|
|
2586
|
+
try {
|
|
2587
|
+
const fromFile = resolveSnapshotFile(fromArg, opts.chain);
|
|
2588
|
+
const toFile = resolveSnapshotFile(toArg, opts.chain);
|
|
2589
|
+
const fromSnap = loadSnapshot(fromFile);
|
|
2590
|
+
const toSnap = loadSnapshot(toFile);
|
|
2591
|
+
const result = diffSnapshots(fromSnap, toSnap);
|
|
2592
|
+
if (result.entries.length === 0) {
|
|
2593
|
+
console.log(chalk.green("No changes detected."));
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
console.log(chalk.bold(`
|
|
2597
|
+
Changes: ${result.from} \u2192 ${result.to} (${result.chain})
|
|
2598
|
+
`));
|
|
2599
|
+
for (const entry of result.entries) {
|
|
2600
|
+
const icon = entry.severity === "critical" ? chalk.red("\u2717") : entry.severity === "warning" ? chalk.yellow("!") : chalk.dim("~");
|
|
2601
|
+
console.log(` ${icon} ${chalk.bold(`${entry.contract}.${entry.field}`)}`);
|
|
2602
|
+
console.log(chalk.dim(` ${entry.before}`));
|
|
2603
|
+
console.log(chalk.green(` ${entry.after}`));
|
|
2604
|
+
console.log();
|
|
2605
|
+
}
|
|
2606
|
+
const critCount = result.entries.filter((e) => e.severity === "critical").length;
|
|
2607
|
+
const warnCount = result.entries.filter((e) => e.severity === "warning").length;
|
|
2608
|
+
const infoCount = result.entries.filter((e) => e.severity === "info").length;
|
|
2609
|
+
console.log(`${chalk.red(`${critCount} critical`)} | ${chalk.yellow(`${warnCount} warning`)} | ${chalk.dim(`${infoCount} info`)}`);
|
|
2610
|
+
} catch (e) {
|
|
2611
|
+
console.error(chalk.red(e.message));
|
|
2612
|
+
process.exit(1);
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
program.command("list").description("List saved snapshots").option("-c, --chain <chain>", "Filter by chain").action((opts) => {
|
|
2616
|
+
const snapshots = listSnapshots(opts.chain);
|
|
2617
|
+
if (snapshots.length === 0) {
|
|
2618
|
+
console.log(chalk.dim("No snapshots found."));
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
for (const s of snapshots) console.log(` ${s}`);
|
|
2622
|
+
});
|
|
2623
|
+
program.command("show").description("Display a snapshot").argument("<snapshot>", "Snapshot file or label").option("-c, --chain <chain>", "Chain filter").action((snapArg, opts) => {
|
|
2624
|
+
try {
|
|
2625
|
+
const file = resolveSnapshotFile(snapArg, opts.chain);
|
|
2626
|
+
const snap = loadSnapshot(file);
|
|
2627
|
+
console.log(chalk.bold(`
|
|
2628
|
+
Snapshot: ${snap.chain} @ block ${snap.blockNumber}`));
|
|
2629
|
+
console.log(chalk.dim(`Created: ${snap.createdAt}
|
|
2630
|
+
`));
|
|
2631
|
+
for (const [name, contract] of Object.entries(snap.contracts)) {
|
|
2632
|
+
console.log(chalk.bold.underline(name));
|
|
2633
|
+
console.log(` address: ${contract.address}`);
|
|
2634
|
+
if (contract.proxyInfo) {
|
|
2635
|
+
if (contract.proxyInfo.implementation) console.log(` implementation: ${chalk.cyan(contract.proxyInfo.implementation)}`);
|
|
2636
|
+
if (contract.proxyInfo.admin) console.log(` proxy admin: ${chalk.yellow(contract.proxyInfo.admin)}`);
|
|
2637
|
+
}
|
|
2638
|
+
for (const [key, value] of Object.entries(contract.publicReads)) {
|
|
2639
|
+
console.log(` ${key}: ${value}`);
|
|
2640
|
+
}
|
|
2641
|
+
console.log();
|
|
2642
|
+
}
|
|
2643
|
+
} catch (e) {
|
|
2644
|
+
console.error(chalk.red(e.message));
|
|
2645
|
+
process.exit(1);
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
program.parse();
|