From 8436f537dcaa4761aa955374622b381fabdaba10 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sun, 14 Mar 2021 17:59:32 -0500 Subject: [PATCH] Deployed contracts and added candidate removal code (#9) --- ethereum/.env.example | 8 +- ethereum/migrations/2_initial_deployment.js | 9 +- ethereum/test/treasury.test.js | 19 +++ ethereum/truffle-config.js | 9 +- services/delegator/src/Borrower.ts | 124 +---------------- services/delegator/src/PriceLedger.ts | 4 +- services/delegator/src/StatefulBorrower.ts | 6 +- services/delegator/src/StatefulBorrowers.ts | 130 +++++++----------- services/delegator/src/StatefulComptroller.ts | 6 +- .../delegator/src/StatefulPricesCoinbase.ts | 6 +- .../delegator/src/StatefulPricesOnChain.ts | 4 +- services/delegator/src/contracts/CToken.ts | 8 +- services/delegator/src/start.ts | 47 +++++-- services/delegator/src/types/CTokens.ts | 10 +- services/delegator/src/types/IBorrower.ts | 13 -- .../txmanager/src/contracts/Liquidator.ts | 4 +- services/txmanager/src/contracts/Treasury.ts | 4 +- services/txmanager/src/start.ts | 9 +- 18 files changed, 157 insertions(+), 263 deletions(-) delete mode 100644 services/delegator/src/types/IBorrower.ts diff --git a/ethereum/.env.example b/ethereum/.env.example index 83e4798..6a79777 100644 --- a/ethereum/.env.example +++ b/ethereum/.env.example @@ -2,4 +2,10 @@ PROVIDER_INFURA_ID= PROVIDER_ALCHEMY_KEY= ACCOUNT_ADDRESS_DEPLOY= -ACCOUNT_SECRET_DEPLOY= \ No newline at end of file +ACCOUNT_SECRET_DEPLOY= + +ACCOUNT_ADDRESS_VANITY= +ACCOUNT_SECRET_VANITY= + +ACCOUNT_ADDRESS_OWNER +ACCOUNT_SECRET_OWNER \ No newline at end of file diff --git a/ethereum/migrations/2_initial_deployment.js b/ethereum/migrations/2_initial_deployment.js index d7f2ce2..9cc750a 100644 --- a/ethereum/migrations/2_initial_deployment.js +++ b/ethereum/migrations/2_initial_deployment.js @@ -4,26 +4,31 @@ const Liquidator = artifacts.require("Liquidator"); module.exports = (deployer, network, accounts) => { let owner; let comptroller; + let vanityDeployer; switch (network) { + case "ganache-fork": case "ganache": owner = accounts[1]; comptroller = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B"; + vanityDeployer = accounts[0]; break; case "production-fork": case "production": owner = "0xF1c73bb23934127A2C1Fa4bA7520822574fE9bA7"; comptroller = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B"; + vanityDeployer = accounts[1]; break; default: console.error("Unknown network -- constructor args unspecified"); } - deployer.deploy(Treasury, owner).then((treasury) => { + deployer.deploy(Treasury, owner, { from: vanityDeployer }).then((treasury) => { return deployer.deploy( Liquidator, treasury.address, - comptroller + comptroller, + { from: vanityDeployer } ); }); }; diff --git a/ethereum/test/treasury.test.js b/ethereum/test/treasury.test.js index c399652..34948e3 100644 --- a/ethereum/test/treasury.test.js +++ b/ethereum/test/treasury.test.js @@ -43,6 +43,25 @@ contract("Treasury Test", (accounts) => { assert.equal(balanceStored.toString(10, 0), three); }); + it("should unfund and send back to owner @latest-block", async () => { + const treasury = await Treasury.deployed(); + + // manually read owner addresses from storage (since they're private) + const owner = await web3.eth.getStorageAt(treasury.address, "0"); + + // send 4 ETH to the Treasury for owner + const four = "4" + "0".repeat(18); + await treasury.fund(owner, { value: four }); + // retrieve 3 ETH from Treasury for owner + const three = "3" + "0".repeat(18); + await treasury.unfund(three, { from: owner }); + + // check that stored balances match expectations + const balanceStored = await treasury.balanceStored(); + + assert.equal(balanceStored.toString(10, 0), four); + }); + it("should set liquidator from owner account @latest-block", async () => { const treasury = await Treasury.deployed(); const liquidator = await Liquidator.deployed(); diff --git a/ethereum/truffle-config.js b/ethereum/truffle-config.js index e41e8e7..88ee3ce 100644 --- a/ethereum/truffle-config.js +++ b/ethereum/truffle-config.js @@ -68,11 +68,16 @@ module.exports = { production: { provider: () => new HDWalletProvider( - process.env.ACCOUNT_SECRET_DEPLOY, + [ + process.env.ACCOUNT_SECRET_DEPLOY, + process.env.ACCOUNT_SECRET_VANITY, + process.env.ACCOUNT_SECRET_OWNER, + ], "https://mainnet.infura.io/v3/" + process.env.PROVIDER_INFURA_ID ), network_id: "*", - gasPrice: 100e9, + gasPrice: 175e9, + gas: 1000000, }, }, diff --git a/services/delegator/src/Borrower.ts b/services/delegator/src/Borrower.ts index 6dec547..d222955 100644 --- a/services/delegator/src/Borrower.ts +++ b/services/delegator/src/Borrower.ts @@ -1,5 +1,3 @@ -import Web3 from 'web3'; - import { Big } from '@goldenagellc/web3-blocks'; import { @@ -7,10 +5,9 @@ import { CTokenSymbol, cTokenSymbols, CTokenVersion, - CTokenVersions, - CTokenUnderlyingDecimals as decimals, + cTokenVersions, + cTokenUnderlyingDecimals as decimals, } from './types/CTokens'; -import { CToken } from './contracts/CToken'; import PriceLedger from './PriceLedger'; import StatefulComptroller from './StatefulComptroller'; @@ -20,13 +17,6 @@ export interface IBorrowerPosition { borrowIndex: Big; } -interface ILiquidity { - liquidity: Big; - shortfall: Big; - symbols: CTokenSymbol[]; - edges: ('min' | 'max')[]; -} - interface ILiquidationInformation { health: Big; repayCToken: CTokens; @@ -47,114 +37,6 @@ export default class Borrower { ) as { [_ in CTokenSymbol]: IBorrowerPosition }; } - public async verify( - provider: Web3, - cTokens: { [_ in CTokenSymbol]: CToken }, - borrowIndices: { [_ in CTokenSymbol]: Big }, - threshold: number, - ): Promise { - for (let symbol of cTokenSymbols) { - const snapshot = await cTokens[symbol].getAccountSnapshot(this.address)(provider); - if (snapshot.error !== '0') { - console.error(`Failed to get account snapshot for ${this.address}: ${snapshot.error}`); - return false; - } - - const position = this.positions[symbol]; - if (position.borrowIndex.eq('0')) { - console.error(`${this.address} invalid due to 0 borrow index`); - return false; - } - const supply = position.supply; - const borrow = position.borrow.times(borrowIndices[symbol]).div(position.borrowIndex); - - if (supply.eq('0')) { - if (!snapshot.cTokenBalance.eq('0')) { - console.error(`${this.address} invalid due to 0 supply mismatch`); - return false; - } - } else { - const supplyError = supply.minus(snapshot.cTokenBalance).div(snapshot.cTokenBalance).abs(); - if (supplyError.toNumber() > threshold) { - console.error(`${this.address} invalid due to high supply error (${supplyError.toFixed(5)})`); - return false; - } - } - - if (borrow.eq('0')) { - if (!snapshot.borrowBalance.eq('0')) { - console.error(`${this.address} invalid due to 0 borrow mismatch`); - return false; - } - } else { - const borrowError = borrow.minus(snapshot.borrowBalance).div(snapshot.borrowBalance).abs(); - if (borrowError.toNumber() > threshold) { - console.error(`${this.address} invalid due to high borrow error (${borrowError.toFixed(5)})`); - return false; - } - } - } - return true; - } - - public liquidity( - comptroller: StatefulComptroller, - priceLedger: PriceLedger, - exchangeRates: { [_ in CTokenSymbol]: Big }, - borrowIndices: { [_ in CTokenSymbol]: Big }, - ): ILiquidity | null { - let supply: Big = new Big('0'); - let borrow: Big = new Big('0'); - const symbols: CTokenSymbol[] = []; - const edges: ('min' | 'max')[] = []; - - for (let symbol of cTokenSymbols) { - const position = this.positions[symbol]; - if (position.supply.eq('0') || position.borrow.eq('0') || position.borrowIndex.eq('0')) continue; - - const collateralFactor = comptroller.getCollateralFactor(symbol); - const pricesUSD = priceLedger.getPrices(symbol); - if (collateralFactor === null || pricesUSD.min === null || pricesUSD.max === null) return null; - - const edge: 'min' | 'max' = position.supply.gt('0') ? 'min' : 'max'; - supply = supply.plus( - position.supply - .times(exchangeRates[symbol]) - .div('1e+18') - .times(collateralFactor) - .div('1e+18') - .times(pricesUSD[edge]!) - .div(`1e+${decimals[symbol]}`), - ); - borrow = borrow.plus( - position.borrow - .times(borrowIndices[symbol]) - .div(position.borrowIndex) - .times(pricesUSD[edge]!) - .div(`1e+${decimals[symbol]}`), - ); - symbols.push(symbol); - edges.push(edge); - } - - let liquidity: Big; - let shortfall: Big; - if (supply.gt(borrow)) { - liquidity = supply.minus(borrow); - shortfall = new Big('0'); - } else { - liquidity = new Big('0'); - shortfall = borrow.minus(supply); - } - - return { - liquidity: liquidity, - shortfall: shortfall, - symbols: symbols, - edges: edges, - }; - } - public expectedRevenue( comptroller: StatefulComptroller, priceLedger: PriceLedger, @@ -250,7 +132,7 @@ export default class Borrower { if (top2RepayAssets[0] !== null && top2SeizeAssets[0] !== null) { const ableToPickBest = - top2RepayAssets[0] !== top2SeizeAssets[0] || CTokenVersions[top2RepayAssets[0]] === CTokenVersion.V2; + top2RepayAssets[0] !== top2SeizeAssets[0] || cTokenVersions[top2RepayAssets[0]] === CTokenVersion.V2; const repayIdx = Number(!ableToPickBest && top2RepayAmounts[1].gt(top2SeizeAmounts[1])); const seizeIdx = Number(ableToPickBest ? false : !repayIdx); diff --git a/services/delegator/src/PriceLedger.ts b/services/delegator/src/PriceLedger.ts index 350327f..441895e 100644 --- a/services/delegator/src/PriceLedger.ts +++ b/services/delegator/src/PriceLedger.ts @@ -1,7 +1,7 @@ import { Big } from '@goldenagellc/web3-blocks'; import { CoinbaseKey } from './types/CoinbaseKeys'; -import { CTokenSymbol, CTokenCoinbaseKeys } from './types/CTokens'; +import { CTokenSymbol, cTokenCoinbaseKeys } from './types/CTokens'; import IPostablePriceFormat from './types/IPostablePriceFormat'; import IPrice from './types/IPrice'; import IPriceRange from './types/IPriceRange'; @@ -86,7 +86,7 @@ export default class PriceLedger { }; symbols.forEach((symbol, i) => { - const key = CTokenCoinbaseKeys[symbol]; + const key = cTokenCoinbaseKeys[symbol]; if (key === null) return; const prices = this.prices[key]; diff --git a/services/delegator/src/StatefulBorrower.ts b/services/delegator/src/StatefulBorrower.ts index 2232d44..ec1874c 100644 --- a/services/delegator/src/StatefulBorrower.ts +++ b/services/delegator/src/StatefulBorrower.ts @@ -2,7 +2,7 @@ import { EventData } from 'web3-eth-contract'; import Web3 from 'web3'; import winston from 'winston'; -import { CTokenReversed, CTokenSymbol, cTokenSymbols } from './types/CTokens'; +import { CTokensReversed, CTokenSymbol, cTokenSymbols } from './types/CTokens'; import { CToken } from './contracts/CToken'; import Borrower from './Borrower'; @@ -17,7 +17,7 @@ export default class StatefulBorrower extends Borrower { } public fetchAll(block: number): Promise[] { - return cTokenSymbols.map(async (symbol) => this.fetch(symbol, block)); + return cTokenSymbols.map((symbol) => this.fetch(symbol, block)); } public async fetch(symbol: CTokenSymbol, block: number): Promise { @@ -89,7 +89,7 @@ export default class StatefulBorrower extends Borrower { } private getSymbolFor(address: string): CTokenSymbol | null { - const symbol = CTokenReversed[address]; + const symbol = CTokensReversed[address]; if (symbol === undefined) { console.warn(`Address ${address} wasn't found in reverse lookup table!`); return null; diff --git a/services/delegator/src/StatefulBorrowers.ts b/services/delegator/src/StatefulBorrowers.ts index 48700b6..7b5c7bc 100644 --- a/services/delegator/src/StatefulBorrowers.ts +++ b/services/delegator/src/StatefulBorrowers.ts @@ -40,21 +40,16 @@ export default class StatefulBorrowers { this.subscribe(block); } - public async push(addresses: string[]): Promise { + public async push(addresses: string[]): Promise { const block = await this.provider.eth.getBlockNumber(); + const promises = []>[]; addresses.forEach((address) => { this.borrowers[address] = new StatefulBorrower(address, this.provider, this.cTokens); - this.borrowers[address].fetchAll(block); + promises.push(...this.borrowers[address].fetchAll(block)); }); + return Promise.all(promises); } - // public async randomCheck(): Promise { - // const keys = Object.keys(this.borrowers); - // const borrower = this.borrowers[keys[(keys.length * Math.random()) << 0]]; - // const valid = await borrower.verify(this.provider, this.cTokens, this.borrowIndices, 0.01); - // if (!valid) console.log(`${borrower.address} has invalid state`); - // } - public async scan(comptroller: StatefulComptroller, priceLedger: PriceLedger): Promise { const exchangeRateArray = await Promise.all(this.fetchExchangeRates()); const exchangeRates = Object.fromEntries(cTokenSymbols.map((symbol, i) => [symbol, exchangeRateArray[i]])) as { @@ -97,88 +92,57 @@ export default class StatefulBorrowers { cTokenSymbols.forEach((symbol) => { const subscribeTo = this.cTokens[symbol].bindTo(this.provider).subscribeTo; - subscribeTo - .AccrueInterest(block) - .on('data', (ev: EventData) => { - this.borrowIndices[symbol] = new Big(ev.returnValues.borrowIndex); - }) - .on('error', console.log); - - subscribeTo - .Mint(block) - .on('data', (ev: EventData) => { - const minter: string = ev.returnValues.minter; - if (minter in this.borrowers) this.borrowers[minter].onMint(ev); - }) - .on('changed', (ev: EventData) => { - const minter: string = ev.returnValues.minter; - if (minter in this.borrowers) this.borrowers[minter].onMint(ev); - }) - .on('error', console.log); - - subscribeTo - .Redeem(block) - .on('data', (ev: EventData) => { - const redeemer: string = ev.returnValues.redeemer; - if (redeemer in this.borrowers) this.borrowers[redeemer].onRedeem(ev); - }) - .on('changed', (ev: EventData) => { - const redeemer: string = ev.returnValues.redeemer; - if (redeemer in this.borrowers) this.borrowers[redeemer].onRedeem(ev); - }) - .on('error', console.log); - - subscribeTo - .Borrow(block) - .on('data', (ev: EventData) => { - const borrower: string = ev.returnValues.borrower; - if (borrower in this.borrowers) this.borrowers[borrower].onBorrow(ev); - }) - .on('changed', (ev: EventData) => { - const borrower: string = ev.returnValues.borrower; - if (borrower in this.borrowers) this.borrowers[borrower].onBorrow(ev); - }) - .on('error', console.log); - + const respondToMint = (ev: EventData) => { + const minter: string = ev.returnValues.minter; + if (minter in this.borrowers) this.borrowers[minter].onMint(ev); + }; + const respondToRedeem = (ev: EventData) => { + const redeemer: string = ev.returnValues.redeemer; + if (redeemer in this.borrowers) this.borrowers[redeemer].onRedeem(ev); + }; + const respondToBorrow = (ev: EventData) => { + const borrower: string = ev.returnValues.borrower; + if (borrower in this.borrowers) this.borrowers[borrower].onBorrow(ev); + }; + const respondToRepay = (ev: EventData) => { + const borrower: string = ev.returnValues.borrower; + if (borrower in this.borrowers) this.borrowers[borrower].onRepayBorrow(ev); + }; + const respondToLiquidate = (ev: EventData) => { + const borrower: string = ev.returnValues.borrower; + if (borrower in this.borrowers) this.borrowers[borrower].onLiquidateBorrow(ev); + }; + const respondToTransfer = (ev: EventData) => { + const from: string = ev.returnValues.from; + if (from in this.borrowers) this.borrowers[from].onTransfer(ev); + const to: string = ev.returnValues.to; + if (to in this.borrowers) this.borrowers[to].onTransfer(ev); + }; + + subscribeTo.Mint(block).on('data', respondToMint).on('changed', respondToMint).on('error', console.error); + subscribeTo.Redeem(block).on('data', respondToRedeem).on('changed', respondToRedeem).on('error', console.error); + subscribeTo.Borrow(block).on('data', respondToBorrow).on('changed', respondToBorrow).on('error', console.error); subscribeTo .RepayBorrow(block) - .on('data', (ev: EventData) => { - const borrower: string = ev.returnValues.borrower; - if (borrower in this.borrowers) this.borrowers[borrower].onRepayBorrow(ev); - }) - .on('changed', (ev: EventData) => { - const borrower: string = ev.returnValues.borrower; - if (borrower in this.borrowers) this.borrowers[borrower].onRepayBorrow(ev); - }) - .on('error', console.log); - + .on('data', respondToRepay) + .on('changed', respondToRepay) + .on('error', console.error); subscribeTo .LiquidateBorrow(block) - .on('data', (ev: EventData) => { - const borrower: string = ev.returnValues.borrower; - if (borrower in this.borrowers) this.borrowers[borrower].onLiquidateBorrow(ev); - }) - .on('changed', (ev: EventData) => { - const borrower: string = ev.returnValues.borrower; - if (borrower in this.borrowers) this.borrowers[borrower].onLiquidateBorrow(ev); - }) - .on('error', console.log); - + .on('data', respondToLiquidate) + .on('changed', respondToLiquidate) + .on('error', console.error); subscribeTo .Transfer(block) + .on('data', respondToTransfer) + .on('changed', respondToTransfer) + .on('error', console.error); + subscribeTo + .AccrueInterest(block) .on('data', (ev: EventData) => { - const from: string = ev.returnValues.from; - if (from in this.borrowers) this.borrowers[from].onTransfer(ev); - const to: string = ev.returnValues.to; - if (to in this.borrowers) this.borrowers[to].onTransfer(ev); - }) - .on('changed', (ev: EventData) => { - const from: string = ev.returnValues.from; - if (from in this.borrowers) this.borrowers[from].onTransfer(ev); - const to: string = ev.returnValues.to; - if (to in this.borrowers) this.borrowers[to].onTransfer(ev); + this.borrowIndices[symbol] = new Big(ev.returnValues.borrowIndex); }) - .on('error', console.log); + .on('error', console.error); }); } } diff --git a/services/delegator/src/StatefulComptroller.ts b/services/delegator/src/StatefulComptroller.ts index 7602867..4620b6d 100644 --- a/services/delegator/src/StatefulComptroller.ts +++ b/services/delegator/src/StatefulComptroller.ts @@ -124,7 +124,7 @@ export default class StatefulComptroller { logIndex: ev.logIndex, }; }) - .on('error', console.log); + .on('error', console.error); } private subscribeToLiquidationIncentive(block: number): void { @@ -152,7 +152,7 @@ export default class StatefulComptroller { logIndex: ev.logIndex, }; }) - .on('error', console.log); + .on('error', console.error); } private subscribeToCollateralFactors(block: number): void { @@ -190,6 +190,6 @@ export default class StatefulComptroller { } }); }) - .on('error', console.log); + .on('error', console.error); } } diff --git a/services/delegator/src/StatefulPricesCoinbase.ts b/services/delegator/src/StatefulPricesCoinbase.ts index d8ba857..8707018 100644 --- a/services/delegator/src/StatefulPricesCoinbase.ts +++ b/services/delegator/src/StatefulPricesCoinbase.ts @@ -41,7 +41,7 @@ export default class StatefulPricesCoinbase extends CoinbaseReporter { const report = await this.fetchCoinbasePrices(); if (report.message === 'request timestamp expired') { - console.log('Coinbase fetch failed -- request timestamp outdated'); + console.warn('Coinbase fetch failed -- request timestamp outdated'); return []; } for (let i = 0; i < report.messages.length; i += 1) { @@ -59,8 +59,8 @@ export default class StatefulPricesCoinbase extends CoinbaseReporter { } return updatedKeys; } catch (e) { - if (e instanceof FetchError) console.log('Coinbase fetch failed -- probably lost internet'); - else console.log(e); + if (e instanceof FetchError) console.warn('Coinbase fetch failed -- probably lost internet'); + else console.warn(e); return []; } } diff --git a/services/delegator/src/StatefulPricesOnChain.ts b/services/delegator/src/StatefulPricesOnChain.ts index 22e3615..a5d1a38 100644 --- a/services/delegator/src/StatefulPricesOnChain.ts +++ b/services/delegator/src/StatefulPricesOnChain.ts @@ -7,7 +7,7 @@ import { Big } from '@goldenagellc/web3-blocks'; import { OpenOraclePriceData } from './contracts/OpenOraclePriceData'; import { UniswapAnchoredView } from './contracts/UniswapAnchoredView'; import { CoinbaseKey, coinbaseKeyMap } from './types/CoinbaseKeys'; -import { CTokens, CTokenUnderlyingDecimals as decimals } from './types/CTokens'; +import { CTokens, cTokenUnderlyingDecimals as decimals } from './types/CTokens'; import IPrice from './types/IPrice'; import PriceLedger from './PriceLedger'; @@ -111,7 +111,7 @@ export default class StatefulPricesOnChain { const summaryAfter = this.ledger.summaryTextFor(knownKey)?.replace(knownKey, 'After'); winston.info(`⚠️ ${knownKey} price suffered chain reorganization!\n${summaryBefore}\n${summaryAfter}`); }) - .on('error', console.log); + .on('error', console.error); } private propogateToLedger(key: CoinbaseKey): void { diff --git a/services/delegator/src/contracts/CToken.ts b/services/delegator/src/contracts/CToken.ts index c06a598..7fc3053 100644 --- a/services/delegator/src/contracts/CToken.ts +++ b/services/delegator/src/contracts/CToken.ts @@ -2,7 +2,7 @@ import Web3Utils from 'web3-utils'; import { Big, BindableContract, ContractCaller } from '@goldenagellc/web3-blocks'; -import { CTokens, CTokenSymbol, CTokenVersion, CTokenCreationBlocks, CTokenVersions } from '../types/CTokens'; +import { CTokens, CTokenSymbol, CTokenVersion, cTokenCreationBlocks, cTokenVersions } from '../types/CTokens'; import abiEth from './abis/cether.json'; import abiV1 from './abis/ctokenv1.json'; @@ -31,7 +31,7 @@ export class CToken extends BindableContract { constructor(symbol: CTokenSymbol) { let abi: Web3Utils.AbiItem[]; - switch (CTokenVersions[symbol]) { + switch (cTokenVersions[symbol]) { case CTokenVersion.V1: abi = abiV1 as Web3Utils.AbiItem[]; break; @@ -42,9 +42,9 @@ export class CToken extends BindableContract { abi = abiEth as Web3Utils.AbiItem[]; break; } - super(CTokens[symbol], abi, CTokenEvents, CTokenCreationBlocks[symbol]); + super(CTokens[symbol], abi, CTokenEvents, cTokenCreationBlocks[symbol]); this.symbol = symbol; - this.version = CTokenVersions[symbol]; + this.version = cTokenVersions[symbol]; } public exchangeRateStored(): ContractCaller { diff --git a/services/delegator/src/start.ts b/services/delegator/src/start.ts index 977ad25..37d925c 100644 --- a/services/delegator/src/start.ts +++ b/services/delegator/src/start.ts @@ -60,25 +60,47 @@ const statefulPricesCoinbase = new StatefulPricesCoinbase( process.env.CB_ACCESS_PASSPHRASE!, ); +let candidatesObj = { + previous: [], +}; + async function start(ipc: any) { await statefulBorrowers.init(); await statefulComptroller.init(); await statefulPricesOnChain.init(); await statefulPricesCoinbase.init(4000); - console.log('Searching for borrowers using the Compound API...'); + winston.log('info', 'Searching for borrowers using the Compound API...'); const borrowers = await getBorrowers('10'); - console.log(`Found ${borrowers.length} borrowers using the Compound API`); + winston.log('info', `Found ${borrowers.length} borrowers using the Compound API`); + const borrowersPushStart = Date.now(); statefulBorrowers.push(borrowers.map((x) => Web3Utils.toChecksumAddress(x))); + winston.log('info', `Fetched all borrower data in ${Date.now() - borrowersPushStart} ms`); setInterval(async () => { const candidates = await statefulBorrowers.scan(statefulComptroller, priceLedger); + const candidatesSet = new Set() + candidates.forEach((candidate) => { + candidatesSet.add(candidate.address); ipc.emit('liquidation-candidate-add', candidate); }); + + candidatesObj.previous.forEach((address) => { + if (candidatesSet.has(address)) return; + ipc.emit('liquidation-candidate-remove', address); + }) + + candidatesObj.previous = Array.from(candidatesSet); }, 4000); - // setInterval(() => provider.eth.isSyncing((e, s) => console.log(s)), 1000); +} + +function stop() { + // @ts-expect-error: Web3 typings are incorrect for `clearSubscriptions()` + provider.eth.clearSubscriptions(); + // @ts-expect-error: We already checked that type is valid + provider.eth.currentProvider.connection.destroy(); } ipc.config.appspace = 'newbedford.'; @@ -86,24 +108,21 @@ ipc.config.id = 'delegator'; ipc.config.silent = true; ipc.connectTo('txmanager', '/tmp/newbedford.txmanager', () => { ipc.of['txmanager'].on('connect', () => { - console.log('Connected'); - + console.log('Connected to TxManager\'s IPC'); start(ipc.of['txmanager']); - - // ipc.of['txmanager'].emit('liquidation-candidate-add', 'My message'); }); + + ipc.of['txmanager'].on('disconnect', () => { + console.log('Disconnected from TxManager\'s IPC'); + stop(); + process.exit(); + }) }); process.on('SIGINT', () => { console.log('\nCaught interrupt signal'); - - // @ts-expect-error: Web3 typings are incorrect for `clearSubscriptions()` - provider.eth.clearSubscriptions(); - // @ts-expect-error: We already checked that type is valid - provider.eth.currentProvider.connection.destroy(); - ipc.disconnect('txmanager'); - + stop(); console.log('Exited cleanly'); process.exit(); }); diff --git a/services/delegator/src/types/CTokens.ts b/services/delegator/src/types/CTokens.ts index 5acf1d7..59a3f48 100644 --- a/services/delegator/src/types/CTokens.ts +++ b/services/delegator/src/types/CTokens.ts @@ -23,7 +23,7 @@ export enum CTokenVersion { export type CTokenSymbol = keyof typeof CTokens; export const cTokenSymbols = Object.keys(CTokens); -export const CTokenCreationBlocks: { [_ in CTokenSymbol]: number } = { +export const cTokenCreationBlocks: { [_ in CTokenSymbol]: number } = { cBAT: 7710735, cCOMP: 10960099, cDAI: 8983575, @@ -37,7 +37,7 @@ export const CTokenCreationBlocks: { [_ in CTokenSymbol]: number } = { cZRX: 7710733, }; -export const CTokenUnderlyingDecimals: { [_ in CTokenSymbol]: number } = { +export const cTokenUnderlyingDecimals: { [_ in CTokenSymbol]: number } = { cBAT: 18, cCOMP: 18, cDAI: 18, @@ -51,7 +51,7 @@ export const CTokenUnderlyingDecimals: { [_ in CTokenSymbol]: number } = { cZRX: 18, }; -export const CTokenVersions: { [_ in CTokenSymbol]: CTokenVersion } = { +export const cTokenVersions: { [_ in CTokenSymbol]: CTokenVersion } = { cBAT: CTokenVersion.V1, cCOMP: CTokenVersion.V2, cDAI: CTokenVersion.V2, @@ -65,7 +65,7 @@ export const CTokenVersions: { [_ in CTokenSymbol]: CTokenVersion } = { cZRX: CTokenVersion.V1, }; -export const CTokenCoinbaseKeys: { [_ in CTokenSymbol]: CoinbaseKey | null } = { +export const cTokenCoinbaseKeys: { [_ in CTokenSymbol]: CoinbaseKey | null } = { cBAT: 'BAT', cCOMP: 'COMP', cDAI: 'DAI', @@ -79,6 +79,6 @@ export const CTokenCoinbaseKeys: { [_ in CTokenSymbol]: CoinbaseKey | null } = { cZRX: 'ZRX', }; -export const CTokenReversed: { [i: string]: CTokenSymbol } = Object.fromEntries( +export const CTokensReversed: { [i: string]: CTokenSymbol } = Object.fromEntries( cTokenSymbols.map((symbol) => [CTokens[symbol], symbol]), ); diff --git a/services/delegator/src/types/IBorrower.ts b/services/delegator/src/types/IBorrower.ts deleted file mode 100644 index 9f60bae..0000000 --- a/services/delegator/src/types/IBorrower.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Big } from '@goldenagellc/web3-blocks'; - -import { CTokenSymbol } from './CTokens'; - -export interface IBorrowerPosition { - supply: Big; - borrow: Big; -} - -export default interface IBorrower { - address: string; - positions: { [_ in CTokenSymbol]: IBorrowerPosition }; -} diff --git a/services/txmanager/src/contracts/Liquidator.ts b/services/txmanager/src/contracts/Liquidator.ts index de576e2..342d51e 100644 --- a/services/txmanager/src/contracts/Liquidator.ts +++ b/services/txmanager/src/contracts/Liquidator.ts @@ -95,13 +95,15 @@ export enum Instances { // v1, // v2, // ... + adam, latest, } type InstanceMap = { [d in keyof typeof Instances]: T }; const liquidators: InstanceMap = { - latest: new Liquidator('0x29FAe933BE0186605f0Aca29A2387AcDB9B5EECC'), + adam: new Liquidator('0x29FAe933BE0186605f0Aca29A2387AcDB9B5EECC'), + latest: new Liquidator('0x0A4132Af8e92cBB4ffC218627a889c088926FF49'), }; export default liquidators; diff --git a/services/txmanager/src/contracts/Treasury.ts b/services/txmanager/src/contracts/Treasury.ts index c9ee229..2a5ec48 100644 --- a/services/txmanager/src/contracts/Treasury.ts +++ b/services/txmanager/src/contracts/Treasury.ts @@ -44,13 +44,15 @@ export enum Instances { // v1, // v2, // ... + adam, latest, } type InstanceMap = { [d in keyof typeof Instances]: T }; const treasuries: InstanceMap = { - latest: new Treasury('0x6d21F25029A462B5aEEC2d4772de674fbD908d1e'), + adam: new Treasury('0x6d21F25029A462B5aEEC2d4772de674fbD908d1e'), + latest: new Treasury('0xbe11AC1a02DDfb6d8e8393218164f9Ece884fcE0'), }; export default treasuries; diff --git a/services/txmanager/src/start.ts b/services/txmanager/src/start.ts index d71f7cb..400421a 100644 --- a/services/txmanager/src/start.ts +++ b/services/txmanager/src/start.ts @@ -72,13 +72,16 @@ ipc.config.appspace = 'newbedford.'; ipc.config.id = 'txmanager'; ipc.config.silent = true; ipc.serve('/tmp/newbedford.txmanager', () => { - ipc.server.on('liquidation-candidate-add', (message) => { + ipc.server.on('liquidation-candidate-add', async (message) => { const candidate = message as ILiquidationCandidate; if (candidate.expectedRevenue < 0.1) return; - txmanager.addLiquidationCandidate(message as ILiquidationCandidate) + + const syncing = await provider.eth.isSyncing(); + if (typeof syncing === 'boolean' || syncing.CurrentBlock < syncing.HighestBlock - 10) return; + txmanager.addLiquidationCandidate(candidate); }); ipc.server.on('liquidation-candidate-remove', (message) => { - console.log(message); + txmanager.removeLiquidationCandidate((message as ILiquidationCandidate).address); }); }); ipc.server.start();