-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Open
Labels
investigateUnder investigation and may be a bug.Under investigation and may be a bug.v6Issues regarding v6Issues regarding v6
Description
Ethers Version
6.4.0
Describe the Problem
I'm developing a Nodejs bot called app.js. The bot is a concurrent app that does two tasks simultaneously. Task A is listening to events of some contracts. Task B is executing checkLoans()
functions. But whenever function liqDetect()
called by checkLoans()
the below error thrown:
Error: missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x574f93cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060978c9c8fd998b7997ae854ad8cef8fb9b8a698f99d09e5f368b6ccf92be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda", "from": "0x1164E83a5313089422e0ef4428dE07520c746D79", "to": "0x8C2025e8d92c7411b6eb450FF886022f38b07487" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.15.0)
The bot runs on Sonic mainnet. What is the problem and what does data=null
refer to?
app.js:
/*_____________________ import modules _____________________________*/
import { ethers, NonceManager } from 'ethers';
import { createClient } from 'redis';
import fs from 'fs';
import cron from 'node-cron';
import path from 'path';
import { pathToFileURL } from 'url';
import { eventOf } from './utils/getEvent.js';
import dotenv from 'dotenv'
const outbound = fs.writeFileSync;
dotenv.config();
/*_____________________ set Redis client ___________________________*/
// Instantiate Redis client
const redisClient = createClient({
socket: {
host: '127.0.0.1',
port: 6379
},
password: process.env.REDIS_PASS
});
await redisClient.connect();
console.info(`\u{1F50C} Redis connected!`);
/*___________________________ constants ____________________________*/
const SONIC_WS = process.env.SONIC_WS;
const LOCAL = process.env.LOCAL_ENDPOINT;
const ADMIN_KEY = process.env.ADMIN_1_KEY;
const MODERATOR_KEY = process.env.MODERATOR_2_KEY;
const DIAMOND = process.env.DIAMOND;
const loanMgmtJson = './contracts/facets/lending/LoanManagment.sol/LoanManagement.json';
const loanEndJson = './contracts/utils/LoanEnd.sol/LoanEnd.json';
const loanLiqJson = './contracts/facets/liquidation/LoanLiquidation.sol/LoanLiquidation.json';
const oracleJson = './contracts/facets/oracle/OracleManager.sol/OracleManager.json';
const liqAmountJson = './contracts/utils/LiquidAmount.sol/LiquidAmount.json';
const COUNTER_FILE = path.join('./files', 'counter.json');
const BLOCK_FILE = path.join('./files', 'block.json');
/*____________________________ counter _____________________________*/
const counterUrl = pathToFileURL(path.resolve(COUNTER_FILE));
const counterObj = await import(counterUrl.href, {with: {type: 'json'}});
let counter = counterObj.default.startFrom;
/*______________________________ block _____________________________*/
const blockUrl = pathToFileURL(path.resolve(BLOCK_FILE));
const blockObj = await import(blockUrl.href, {with: {type: 'json'}});
let currBlock = blockObj.default.startFrom;
/*___________________________ premitives ___________________________*/
let provider = wsConnect(SONIC_WS);
const baseModerator = new ethers.Wallet(MODERATOR_KEY, provider);
const moderator = new NonceManager(baseModerator);
/*_____________________ instantiate contracts ______________________*/
const loanMgmt = await addContract(loanMgmtJson);
const loanLiq = await addContract(loanLiqJson);
const oracle = await addContract(oracleJson);
const liqAmount = await addContract(liqAmountJson);
console.info(`\u{1F5C3} Contracts added!`);
/*______________________ listen to provider ________________________*/
provider.on('error', (error) => {
console.error(`Provider error:\n ${error}`);
provider = wsConnect(SONIC_WS);
});
/*_____________ task A: listen to on-chain events __________________*/
loanMgmt.on('NewLoanAdded', async (realLoanId) => {
try{
currBlock = await provider.getBlockNumber();
await addLoan(realLoanId);
updateCounter();
updateBlock();
}catch(err){
console.error(`\u{1F6A8} Error on NewLoanAdded listener:\n${err}`);
}
})
loanMgmt.on('LoanRepaid', async (realLoanId) => {
try{
currBlock = await provider.getBlockNumber();
// Delete the loan
await delRepaidLoan(realLoanId);
// Update block file
updateBlock();
}catch(err){
console.error(`\u{1F6A8} Error on LoanRepaid listener:\n${err}`);
}
})
oracle.on('LoanStatus', async (realLoanId, loanId, isLiquid, healthFactor, lpToken, lpAmount, debtValue) => {
try{
// Get loan from storage
const hf = ethers.formatEther(healthFactor);
const index = await redisClient.get(realLoanId.toString());
const loan = JSON.parse(await redisClient.get(index));
// Loan state
const loanId = loan.id;
const isLiq = loan.isLiquidating;
// Check to liquidate
if(isLiquid && !isLiq){
// Update loan state on storage
loan.isLiquidating = true;
redisClient.set(index, JSON.stringify(loan));
// Update loan flag
await updateFlag(loanId);
// Liquidate partial or all
if(0.95 < hf && hf < 1){
const partValue = await liqAmount.closeFactor(loanId, debtValue, 1);
await liqPartial(loanId, partValue, index, loan);
await updateFlag(loanId);
}
if(hf <= 0.95){
await liqAll(loanId, index, loan);
await updateFlag(loanId);
}
// Update loan flag
}
}catch(err){
console.error(`\u{1F6A8} Error on LoanStatus listener:\n${err}`)
}
})
/*___________________ task B: detect liq loans _____________________*/
async function checkLoans() {
try{
console.info(`\u{23F1} Checking loans ... `);
for (let i = 0; i < counter; i++) {
const recordStr = await redisClient.get(i.toString());
if(!recordStr){
console.info(`loan[${i}] ignored`);
}else{
const record = JSON.parse(recordStr);
if(!record.isLiquidating){
await liqDetect(record.id);
}
}
}
}catch(err){
console.error(`\u{1F6A8} Error in checkLoans():\n${err}`)
}
}
/*______________________ helper functions __________________________*/
function httpConnect(endpoint) {
try{
const wsProvider = new ethers.JsonRpcProvider(endpoint);
return wsProvider;
}catch(err){
console.error(`\u{1F6A8} Error in wsConnect():\n${err}`);
}
}
function wsConnect(endpoint) {
try{
const wsProvider = new ethers.WebSocketProvider(endpoint);
return wsProvider;
}catch(err){
console.error(`\u{1F6A8} Error in wsConnect():\n${err}`);
}
}
async function addContract(jsonPath) {
try{
const jsonFile = await import(jsonPath, {with: {type: 'json'}});
const _abi = jsonFile.default.abi;
const contract = new ethers.Contract(DIAMOND, _abi, moderator);
return contract;
}catch(err){
console.error(`\u{1F6A8} Error in addContract():\n${err}`);
}
}
async function addLoan(loanId) {
try{
const loan = {
id: loanId,
isLiquidating: false,
}
redisClient.set(counter.toString(), JSON.stringify(loan));
redisClient.set(loanId.toString(), counter.toString());
console.info(`\u{1F4DD} block[${currBlock}]: loan[${counter}] ----> ${loanId.substring(0,10)}...`);
counter++;
}catch(err){
console.error(`\u{1F6A8} Error in addLoan():\n${err}`)
}
}
async function delRepaidLoan(loanId){
try{
// Get the index of loan to delete
const index = await redisClient.get(loanId.toString());
// Delete loan from storage
redisClient.del(index);
// Update storage
shiftLoans(index);
console.info(`\u{1F4DD} block[${currBlock}]: loan[${counter}] ----> ${loanId.substring(0,10)}... ----> \u{1F5D1}`);
}catch(err){
console.error(`\u{1F6A8} Error in delRepaidLoan():\n${err}`);
}
}
function updateCounter() {// Updates counter file
try{
const prevCounter = counterObj.default.startFrom;
counterObj.default.startFrom++;
outbound(COUNTER_FILE, JSON.stringify(counterObj.default, null, 2));
console.info(`Counter file: ${prevCounter} ----> ${counterObj.default.startFrom}`);
}catch(err){
console.error(`\u{1F6A8} Error in updateCounter():\n${err}`);
}
}
function updateBlock() {// Updates block file
try{
const prevBlock = blockObj.default.startFrom;
const blockFile = {
startFrom: currBlock
}
outbound(BLOCK_FILE, JSON.stringify(blockFile, null, 2));
console.info(`Block file: block[${prevBlock}] ----> block[${currBlock}] `);
}catch(err){
console.error(`\u{1F6A8} Error in updateBlock():\n${err}`);
}
}
async function shiftLoans(id) {
try{
for(let i = id + 1; i < counter; i++){
// Get loan
const loan = await redisClient.get(i.toString());
// Shift loan to left
redisClient.set((i - 1).toString(), loan);
// Delete loan previous location
redisClient.del(i.toString());
}
// Update counter file
counter--;
counterObj.default.startFrom = counter;
outbound(COUNTER_FILE, JSON.stringify(counterObj.default, null, 2));
}catch(err){
console.error(`\u{1F6A8} Error in shiftLoans():\n${err}`);
}
}
async function updateFlag(id) {
try{
const GAS_BUMP = ethers.parseUnits('1', 'gwei');
const updateTx = await loanMgmt.updateLoanFlag(id, {
maxPriorityFeePerGas: (await provider.getFeeData()).maxPriorityFeePerGas + GAS_BUMP
});
await updateTx.wait();
console.info(`\u{1F6A9} Loan flag updated`);
}catch(err){
console.error(`\u{1F6A8} Error in updateFlag():\n${err}`)
}
}
async function liqPartial(_id, _value, _index, _loan) {
try{
// Liquidation
const liqTx = await loanLiq.liquidWithAmount(_id, _value);
await liqTx.wait();
// Update loan state
_loan.isLiquidating = false;
redisClient.set(_index, _loan);
console.info(`\u{1F4B5} loan[${_index}] liquidated partially`);
}catch(err){
console.error(`\u{1F6A8} Error in liqPartial():\n${err}`)
}
}
async function liqAll(_id, _index, _loan) {
try{
// Liquidation
const liqTx = await loanLiq.liquidAll(_id);
await liqTx.wait();
console.info(`\u{1F4B0} loan[${_index}] liquidated completely`);
// Update storage
_loan.isLiquidating = false;
redisClient.del(_index);
shiftLoans(_index);
console.info(`\u{1F5C2} Storage: loan[${_index}] ----> \u{1F5D1}`)
}catch(err){
console.error(`\u{1F6A8} Error in liqPartial():\n${err}`)
}
}
async function liqDetect(id) {
try{
if(!id){
console.warn(`\u{26A0} undefined/null loan index detected`);
}
const GAS_BUMP = ethers.parseUnits('1', 'gwei');
const maxPriorityFeePerGas = (await provider.getFeeData()).maxPriorityFeePerGas + GAS_BUMP;
const detTx = await oracle.liquidationDetect(id, {maxPriorityFeePerGas});
await detTx.wait();
if(!detTx){
console.warn(`\u{26A0} Detect: \u{1F50E} loan[${id}] ---------> undefined/null`);
}
}catch(err){
console.error(`\u{1F6A8} Error in liqDetect():\n${err}`)
}
}
async function syncWithChain() {
try{
console.info(`\u{1F6A3} Syncing with chain ...`);
// Get from and to to check
const countFrom = counterObj.default.startFrom;
const start = blockObj.default.startFrom;
const latest = await provider.getBlockNumber();
const loanAdd = loanMgmt.filters.NewLoanAdded();
// Get all new loans
const batchSize = 20_000;
for(let from = start; from <= latest; from += batchSize){
const to = Math.min(from + batchSize - 1, latest);
const loans = await loanMgmt.queryFilter(loanAdd, from, to);
loans.forEach(async (loan) => {
if(!loan.args.isRepaid){
await addLoan(loan.args.realLoanId);
updateCounter();
updateBlock();
}
})
}
console.info(`\u{1F9CE} Syncing done!`);
}catch(err){
console.error(`\u{1F6A8} Error in syncWithChain():\n${err}`);
}
}
/*___________________________ run bot ______________________________*/
(async function () {
console.info(`\u{1F680} Bot launched ...`);
console.info(`\u{1F6F0} Is listening to chain ...`);
// Sync with added loan
await syncWithChain();
cron.schedule('* */2 * * * *', checkLoans);
})();
// Must be synced with chain by listening to blocks from startFrom to current block
Result:
[dotenv@17.2.0] injecting env (26) from .env (tip: 🔐 prevent building .env in docker: https://dotenvx.com/prebuild)
🔌 Redis connected!
🗃 Contracts added!
🚀 Bot launched ...
🛰 Is listening to chain ...
🚣 Syncing with chain ...
📝 block[39834487]: loan[4] ----> 0x978c9c8f...
🧎 Syncing done!
Counter file: 4 ----> 5
Block file: block[39834487] ----> block[39834487]
⏱ Checking loans ...
🚨 Error in liqDetect():
Error: missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x574f93cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060978c9c8fd998b7997ae854ad8cef8fb9b8a698f99d09e5f368b6ccf92be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda", "from": "0x1164E83a5313089422e0ef4428dE07520c746D79", "to": "0x8C2025e8d92c7411b6eb450FF886022f38b07487" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.15.0)
⏱ Checking loans ...
⏱ Checking loans ...
🚨 Error in liqDetect():
Error: missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x574f93cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060978c9c8fd998b7997ae854ad8cef8fb9b8a698f99d09e5f368b6ccf92be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda", "from": "0x1164E83a5313089422e0ef4428dE07520c746D79", "to": "0x8C2025e8d92c7411b6eb450FF886022f38b07487" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.15.0)
🚨 Error in liqDetect():
Error: missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x574f93cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060978c9c8fd998b7997ae854ad8cef8fb9b8a698f99d09e5f368b6ccf92be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda", "from": "0x1164E83a5313089422e0ef4428dE07520c746D79", "to": "0x8C2025e8d92c7411b6eb450FF886022f38b07487" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.15.0)
Code Snippet
async function liqDetect(id) {
try{
if(!id){
console.warn(`\u{26A0} undefined/null loan index detected`);
}
const GAS_BUMP = ethers.parseUnits('1', 'gwei');
const maxPriorityFeePerGas = (await provider.getFeeData()).maxPriorityFeePerGas + GAS_BUMP;
const detTx = await oracle.liquidationDetect(id, {maxPriorityFeePerGas});
await detTx.wait();
if(!detTx){
console.warn(`\u{26A0} Detect: \u{1F50E} loan[${id}] ---------> undefined/null`);
}
}catch(err){
console.error(`\u{1F6A8} Error in liqDetect():\n${err}`)
}
}
Contract ABI
[
{
"inputs": [
{
"internalType": "int256",
"name": "price",
"type": "int256"
}
],
"name": "InvalidPrice",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "lpTokenAddr",
"type": "address"
},
{
"internalType": "bool",
"name": "configuration",
"type": "bool"
}
],
"name": "LPTokenNotConfigured",
"type": "error"
},
{
"inputs": [],
"name": "OnlyAdminIsAllowedToDoThisJob",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "lastInterval",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "stalenessTreshold",
"type": "uint256"
}
],
"name": "PriceDataStale",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "feed",
"type": "address"
},
{
"internalType": "bool",
"name": "activation",
"type": "bool"
}
],
"name": "PriceFeedNotActive",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "liquidationThreshold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "liquidationBonus",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "volatilityDiscount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"name": "LPTokenConfigured",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes",
"name": "realLoanId",
"type": "bytes"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "loanId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bool",
"name": "isLiquid",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "healthFactor",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "lpAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "debtValue",
"type": "uint256"
}
],
"name": "LoanStatus",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token0Feed",
"type": "address"
},
{
"indexed": false,
"internalType": "uint8",
"name": "token0DeciDigit",
"type": "uint8"
},
{
"indexed": false,
"internalType": "bool",
"name": "token0Activation",
"type": "bool"
},
{
"indexed": false,
"internalType": "address",
"name": "token1Feed",
"type": "address"
},
{
"indexed": false,
"internalType": "uint8",
"name": "token1DeciDigit",
"type": "uint8"
},
{
"indexed": false,
"internalType": "bool",
"name": "token1Activation",
"type": "bool"
}
],
"name": "PriceFeedSet",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "lpAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "debtValue",
"type": "uint256"
}
],
"name": "calculateHealthFactor",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "liquidationThreshold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "liquidationBonus",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "volatilityDiscount",
"type": "uint256"
}
],
"name": "configureLPToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "getAssetPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "getLPTokenValue",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "lpAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "debtValue",
"type": "uint256"
}
],
"name": "isValidLiquidation",
"outputs": [
{
"internalType": "bool",
"name": "isLiquid_",
"type": "bool"
},
{
"internalType": "uint256",
"name": "healthFactor_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "realLoanId",
"type": "bytes"
}
],
"name": "liquidationDetect",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "lpToken",
"type": "address"
},
{
"internalType": "address",
"name": "feed0",
"type": "address"
},
{
"internalType": "address",
"name": "feed1",
"type": "address"
}
],
"name": "setPriceFeed",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
Errors
Error: missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x574f93cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060978c9c8fd998b7997ae854ad8cef8fb9b8a698f99d09e5f368b6ccf92be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda930c9c9fd582b1090ae834ad8cef8fb0b7a037f21d09e5f368b6ccf11be99bda", "from": "0x1164E83a5313089422e0ef4428dE07520c746D79", "to": "0x8C2025e8d92c7411b6eb450FF886022f38b07487" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.15.0)
Environment
Distributor ID: Ubuntu
Description: Ubuntu 24.04.2 LTS
Release: 24.04
Codename: noble
Environment (Other)
Nodejs v20.19.4
Metadata
Metadata
Assignees
Labels
investigateUnder investigation and may be a bug.Under investigation and may be a bug.v6Issues regarding v6Issues regarding v6