-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdebug-settlements.js
More file actions
329 lines (278 loc) · 14.8 KB
/
debug-settlements.js
File metadata and controls
329 lines (278 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
require('dotenv').config();
const { ethers } = require('ethers');
// Configuration
const ORACLE_ADDRESS = '0xa935db8cb0f45256e3df77cbff1980cf7aeffbe4';
const BEACON_ADDRESS = '0x0d623183d4a0de6e4871dfdd1350312c3b1538b6';
// Use multiple RPCs for reliability
const RPC_URLS = [
'https://arb1.arbitrum.io/rpc',
'https://arbitrum-one-rpc.publicnode.com',
'https://rpc.ankr.com/arbitrum'
];
// Full ABIs for detailed analysis
const ORACLE_ABI = [
"function nextReportId() view returns (uint256)",
"function reportMeta(uint256) view returns (tuple(address token1, address token2, uint256 feePercentage, uint256 multiplier, uint256 settlementTime, uint256 exactToken1Report, uint256 fee, uint256 escalationHalt, uint256 disputeDelay, uint256 protocolFee, uint256 settlerReward, uint256 requestBlock, bool timeType))",
"function reportStatus(uint256) view returns (tuple(uint256 currentAmount1, uint256 currentAmount2, address currentReporter, address initialReporter, uint256 reportTimestamp, uint256 settlementTimestamp, uint256 price, uint256 lastDisputeBlock, bool isSettled, bool disputeOccurred, bool isDistributed, uint256 initialReportTimestamp, uint256 lastReportTrueTime))",
"function extraData(uint256) view returns (address creator, uint256 requestTrueTime, address callbackContract, bytes4 callbackSelector, bool trackDisputes, uint256 numReports, uint256 callbackGasLimit, bool keepFee)",
"function settle(uint256) returns (uint256 price, uint256 settlementTimestamp)",
"function SETTLEMENT_WINDOW() view returns (uint256)",
"function SETTLEMENT_WINDOW_BLOCKS() view returns (uint256)"
];
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
async function getProvider() {
for (const rpc of RPC_URLS) {
try {
const provider = new ethers.providers.JsonRpcProvider({
url: rpc,
timeout: 10000, // 10 second timeout
throttleLimit: 1
});
// Test with quick timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 sec test
await provider.getBlockNumber();
clearTimeout(timeoutId);
console.log(`✅ Connected to: ${rpc.split('/')[2]}`); // Show domain only
return provider;
} catch (e) {
console.log(`❌ Failed to connect to: ${rpc.split('/')[2]} - ${e.message.substring(0, 50)}`);
}
}
throw new Error('No working RPC found');
}
async function getArbBlockNumber(provider) {
try {
const ARB_SYS = '0x0000000000000000000000000000000000000064';
const result = await provider.call({
to: ARB_SYS,
data: '0xa3b1b31d' // arbBlockNumber()
});
return ethers.BigNumber.from(result);
} catch (e) {
return await provider.getBlockNumber();
}
}
async function debugReport(oracle, reportId, currentBlock, currentTime, provider) {
console.log(`\n${colors.cyan}=== Debugging Report #${reportId} ===${colors.reset}`);
try {
// Fetch all data
const [meta, status, extraData] = await Promise.all([
oracle.reportMeta(reportId),
oracle.reportStatus(reportId),
oracle.extraData(reportId)
]);
// Basic info
console.log(`\n${colors.blue}Report Details:${colors.reset}`);
console.log(`- Token1: ${meta.token1}`);
console.log(`- Token2: ${meta.token2}`);
console.log(`- Settler Reward: ${ethers.utils.formatEther(meta.settlerReward)} ETH`);
console.log(`- Time Type: ${meta.timeType ? 'Timestamp-based' : 'Block-based'}`);
console.log(`- Settlement Time: ${meta.settlementTime} ${meta.timeType ? 'seconds' : 'blocks'}`);
// Status
console.log(`\n${colors.blue}Status:${colors.reset}`);
console.log(`- Is Settled: ${status.isSettled}`);
console.log(`- Is Distributed: ${status.isDistributed}`);
console.log(`- Current Reporter: ${status.currentReporter}`);
console.log(`- Report Timestamp: ${status.reportTimestamp}`);
// Skip conditions
if (status.isSettled || status.isDistributed) {
console.log(`${colors.yellow}❌ Already settled/distributed${colors.reset}`);
return null;
}
if (status.currentReporter === ethers.constants.AddressZero) {
console.log(`${colors.yellow}❌ No report submitted yet${colors.reset}`);
return null;
}
if (extraData.callbackContract !== ethers.constants.AddressZero) {
console.log(`${colors.yellow}❌ Has callback contract${colors.reset}`);
return null;
}
// Settlement timing
console.log(`\n${colors.blue}Settlement Timing:${colors.reset}`);
const checkTime = meta.timeType ? currentTime : currentBlock;
const reportTime = meta.timeType ? status.reportTimestamp.toNumber() : status.reportTimestamp.toNumber();
const settlementDeadline = reportTime + meta.settlementTime.toNumber();
const windowEnd = meta.timeType ?
settlementDeadline + 60 : // SETTLEMENT_WINDOW = 60 seconds
settlementDeadline + 1350; // SETTLEMENT_WINDOW_BLOCKS = 1350 blocks
console.log(`- Current ${meta.timeType ? 'Time' : 'Block'}: ${checkTime}`);
console.log(`- Report ${meta.timeType ? 'Time' : 'Block'}: ${reportTime}`);
console.log(`- Settlement Deadline: ${settlementDeadline}`);
console.log(`- Window End: ${windowEnd}`);
console.log(`- Time Until Settlement: ${settlementDeadline - checkTime} ${meta.timeType ? 'seconds' : 'blocks'}`);
console.log(`- Time Until Window End: ${windowEnd - checkTime} ${meta.timeType ? 'seconds' : 'blocks'}`);
// Check if settleable
const canSettle = checkTime >= settlementDeadline;
const inWindow = checkTime <= windowEnd;
const stillProfitable = checkTime > windowEnd; // Can still claim even after window
console.log(`\n${colors.blue}Settlement Status:${colors.reset}`);
console.log(`- Can Settle: ${canSettle ? '✅' : '❌'}`);
console.log(`- In Window: ${inWindow ? '✅' : '❌'}`);
console.log(`- Still Profitable (past window): ${stillProfitable && canSettle ? '✅' : '❌'}`);
if (!canSettle) {
console.log(`${colors.yellow}⏰ Not ready for settlement yet${colors.reset}`);
return null;
}
// Profitability check
console.log(`\n${colors.blue}Profitability Analysis:${colors.reset}`);
const gasPrice = await provider.getGasPrice();
const estimatedGas = 150000;
const gasCost = gasPrice.mul(estimatedGas);
const profit = meta.settlerReward.sub(gasCost);
console.log(`- Gas Price: ${ethers.utils.formatUnits(gasPrice, 'gwei')} gwei`);
console.log(`- Estimated Gas: ${estimatedGas}`);
console.log(`- Gas Cost: ${ethers.utils.formatEther(gasCost)} ETH`);
console.log(`- Expected Profit: ${ethers.utils.formatEther(profit)} ETH`);
console.log(`- Profitable: ${profit.gt(0) ? '✅' : '❌'}`);
// Simulate settlement
if (canSettle && profit.gt(0)) {
console.log(`\n${colors.green}✅ SETTLEMENT OPPORTUNITY FOUND!${colors.reset}`);
// Try to estimate gas for actual settlement
try {
const estimatedSettleGas = await oracle.estimateGas.settle(reportId);
console.log(`- Actual Gas Estimate: ${estimatedSettleGas.toString()}`);
const actualGasCost = gasPrice.mul(estimatedSettleGas);
const actualProfit = meta.settlerReward.sub(actualGasCost);
console.log(`- Actual Gas Cost: ${ethers.utils.formatEther(actualGasCost)} ETH`);
console.log(`- Actual Profit: ${ethers.utils.formatEther(actualProfit)} ETH`);
return {
reportId,
reward: meta.settlerReward,
estimatedGas: estimatedSettleGas,
gasPrice,
profit: actualProfit,
canSettle: true,
inWindow
};
} catch (error) {
console.log(`${colors.red}❌ Settlement would revert: ${error.reason || error.message}${colors.reset}`);
return null;
}
}
return null;
} catch (error) {
console.log(`${colors.red}Error analyzing report ${reportId}: ${error.message}${colors.reset}`);
return null;
}
}
async function trySettlement(oracle, wallet, opportunity) {
console.log(`\n${colors.bright}${colors.green}Attempting settlement of report #${opportunity.reportId}...${colors.reset}`);
try {
// Create transaction
const tx = await wallet.sendTransaction({
to: oracle.address,
data: oracle.interface.encodeFunctionData('settle', [opportunity.reportId]),
gasLimit: opportunity.estimatedGas.mul(120).div(100), // 20% buffer
gasPrice: opportunity.gasPrice.mul(110).div(100), // 10% higher for priority
value: 0
});
console.log(`${colors.green}✅ Transaction sent: ${tx.hash}${colors.reset}`);
console.log(`Waiting for confirmation...`);
const receipt = await tx.wait();
console.log(`${colors.green}✅ Transaction confirmed in block ${receipt.blockNumber}${colors.reset}`);
console.log(`Gas used: ${receipt.gasUsed.toString()}`);
const actualGasCost = receipt.gasUsed.mul(receipt.effectiveGasPrice);
const actualProfit = opportunity.reward.sub(actualGasCost);
console.log(`${colors.green}💰 Actual profit: ${ethers.utils.formatEther(actualProfit)} ETH${colors.reset}`);
return true;
} catch (error) {
console.log(`${colors.red}❌ Settlement failed: ${error.reason || error.message}${colors.reset}`);
// Try to decode revert reason
if (error.error && error.error.data) {
try {
const reason = ethers.utils.toUtf8String('0x' + error.error.data.substr(138));
console.log(`Revert reason: ${reason}`);
} catch (e) {
// Ignore decoding errors
}
}
return false;
}
}
async function main() {
console.log(`${colors.bright}${colors.cyan}OpenOracle Settlement Debugger${colors.reset}\n`);
// Connect to provider
const provider = await getProvider();
const oracle = new ethers.Contract(ORACLE_ADDRESS, ORACLE_ABI, provider);
// Get current state
const [currentBlock, currentTime, arbBlock, nextId, settlementWindow, settlementWindowBlocks] = await Promise.all([
provider.getBlockNumber(),
provider.getBlock('latest').then(b => b.timestamp),
getArbBlockNumber(provider),
oracle.nextReportId(),
oracle.SETTLEMENT_WINDOW(),
oracle.SETTLEMENT_WINDOW_BLOCKS()
]);
console.log(`\n${colors.blue}Chain State:${colors.reset}`);
console.log(`- L2 Block: ${currentBlock}`);
console.log(`- L1 Block (Arbitrum): ${arbBlock}`);
console.log(`- Timestamp: ${currentTime}`);
console.log(`- Next Report ID: ${nextId}`);
console.log(`- Settlement Window: ${settlementWindow} seconds`);
console.log(`- Settlement Window Blocks: ${settlementWindowBlocks}`);
// Scan reports
console.log(`\n${colors.blue}Scanning last 30 reports for opportunities...${colors.reset}`);
const opportunities = [];
const scanStart = Math.max(1, nextId - 30);
for (let id = nextId - 1; id >= scanStart; id--) {
const opportunity = await debugReport(oracle, id, arbBlock.toNumber(), currentTime, provider);
if (opportunity) {
opportunities.push(opportunity);
}
}
// Summary
console.log(`\n${colors.bright}${colors.cyan}=== SUMMARY ===${colors.reset}`);
console.log(`Total reports scanned: ${nextId - scanStart}`);
console.log(`Settlement opportunities found: ${opportunities.length}`);
if (opportunities.length > 0) {
console.log(`\n${colors.green}Profitable settlements:${colors.reset}`);
opportunities.forEach(opp => {
console.log(`- Report #${opp.reportId}: ${ethers.utils.formatEther(opp.profit)} ETH profit ${opp.inWindow ? '(in window)' : '(past window)'}`);
});
// If wallet is provided, try to settle
if (process.env.BOT_PRIVATE_KEY) {
console.log(`\n${colors.yellow}Wallet detected. Attempting settlements...${colors.reset}`);
const wallet = new ethers.Wallet(process.env.BOT_PRIVATE_KEY, provider);
const balance = await wallet.getBalance();
console.log(`Wallet balance: ${ethers.utils.formatEther(balance)} ETH`);
if (balance.gt(ethers.utils.parseEther('0.001'))) {
const oracleWithSigner = oracle.connect(wallet);
for (const opp of opportunities.slice(0, 3)) { // Try up to 3
await trySettlement(oracleWithSigner, wallet, opp);
await new Promise(r => setTimeout(r, 2000)); // Wait 2s between attempts
}
} else {
console.log(`${colors.red}Insufficient balance for settlements${colors.reset}`);
}
} else {
console.log(`\n${colors.yellow}No wallet configured. Run with BOT_PRIVATE_KEY in .env to execute settlements.${colors.reset}`);
}
} else {
console.log(`\n${colors.yellow}No profitable settlement opportunities found at this time.${colors.reset}`);
console.log(`This could be because:`);
console.log(`- All recent reports are not yet ready for settlement`);
console.log(`- All settleable reports have already been settled`);
console.log(`- Gas prices are too high for profitable settlements`);
console.log(`- Reports have callback contracts attached`);
}
// Test SmartAutoBeacon
console.log(`\n${colors.blue}Testing SmartAutoBeacon contract...${colors.reset}`);
try {
const beacon = new ethers.Contract(BEACON_ADDRESS, ['function freeMoneyLight() external'], provider);
const gasEstimate = await beacon.estimateGas.freeMoneyLight();
console.log(`${colors.green}✅ SmartAutoBeacon.freeMoneyLight() would succeed with gas: ${gasEstimate}${colors.reset}`);
} catch (error) {
console.log(`${colors.yellow}❌ SmartAutoBeacon.freeMoneyLight() would revert${colors.reset}`);
}
}
main().catch(console.error);