From 5fbc198b89f5b0ac3f43e8e66280f5667ab4f1d8 Mon Sep 17 00:00:00 2001 From: Hayden Shively <17186559+haydenshively@users.noreply.github.com> Date: Sat, 13 Mar 2021 06:38:16 -0600 Subject: [PATCH] Stateful delegator (#7) --- services/delegator/package.json | 1 + services/delegator/src/Borrower.ts | 136 ++++ services/delegator/src/CompoundAPI.ts | 79 +++ services/delegator/src/PriceLedger.ts | 72 +- services/delegator/src/StatefulBorrower.ts | 167 +++++ services/delegator/src/StatefulBorrowers.ts | 153 +++++ services/delegator/src/StatefulComptroller.ts | 18 +- .../delegator/src/StatefulPricesCoinbase.ts | 8 +- .../delegator/src/StatefulPricesOnChain.ts | 10 +- services/delegator/src/_borrowers.json | 630 ------------------ services/delegator/src/contracts/CToken.ts | 15 +- .../delegator/src/contracts/Comptroller.ts | 4 +- services/delegator/src/start.ts | 30 +- services/delegator/src/types/CTokens.ts | 18 + services/delegator/src/types/IBorrower.ts | 13 + .../delegator/src/types/ICompoundBorrower.ts | 15 - .../src/types/ILiquidationCandidate.ts | 4 +- ...lePriceData.ts => IPostablePriceFormat.ts} | 2 +- services/delegator/yarn.lock | 5 + services/txmanager/src/IncognitoQueue.ts | 4 +- services/txmanager/src/TxManager.ts | 8 +- .../txmanager/src/contracts/Liquidator.ts | 2 +- services/txmanager/src/contracts/Treasury.ts | 4 +- .../txmanager/test/IncognitoQueue.test.ts | 6 +- .../txmanager/test/contracts/Treasury.test.ts | 2 +- 25 files changed, 693 insertions(+), 713 deletions(-) create mode 100644 services/delegator/src/Borrower.ts create mode 100644 services/delegator/src/CompoundAPI.ts create mode 100644 services/delegator/src/StatefulBorrower.ts create mode 100644 services/delegator/src/StatefulBorrowers.ts delete mode 100644 services/delegator/src/_borrowers.json create mode 100644 services/delegator/src/types/IBorrower.ts delete mode 100644 services/delegator/src/types/ICompoundBorrower.ts rename services/delegator/src/types/{IOpenOraclePriceData.ts => IPostablePriceFormat.ts} (58%) diff --git a/services/delegator/package.json b/services/delegator/package.json index ae1fec8..3eb2bb9 100644 --- a/services/delegator/package.json +++ b/services/delegator/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@tsconfig/node14": "^1.0.0", + "@types/big.js": "^6.0.2", "@types/chai": "^4.2.14", "@types/dotenv-safe": "^8.1.1", "@types/mocha": "^8.0.4", diff --git a/services/delegator/src/Borrower.ts b/services/delegator/src/Borrower.ts new file mode 100644 index 0000000..f30146f --- /dev/null +++ b/services/delegator/src/Borrower.ts @@ -0,0 +1,136 @@ +import Web3 from 'web3'; + +import { Big } from '@goldenagellc/web3-blocks'; + +import { CTokenSymbol, cTokenSymbols } from './types/CTokens'; +import { CToken } from './contracts/CToken'; +import PriceLedger from './PriceLedger'; +import StatefulComptroller from './StatefulComptroller'; + +export interface IBorrowerPosition { + supply: Big; + borrow: Big; + borrowIndex: Big; +} + +interface ILiquidity { + liquidity: Big; + shortfall: Big; + symbols: CTokenSymbol[]; + edges: ('min' | 'max')[]; +} + +export default class Borrower { + public readonly address: string; + protected readonly positions: { readonly [_ in CTokenSymbol]: IBorrowerPosition }; + + constructor(address: string) { + this.address = address; + this.positions = Object.fromEntries( + cTokenSymbols.map((symbol) => [symbol, { supply: new Big('0'), borrow: Big('0'), borrowIndex: Big('0') }]), + ) 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} invalud 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 collat: 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'; + collat = collat.plus( + position.supply + .times(exchangeRates[symbol]) + .div('1e+18') + .times(collateralFactor) + .div('1e+18') + .times(pricesUSD[edge]!), + ); + borrow = borrow.plus( + position.borrow.times(borrowIndices[symbol]).div(position.borrowIndex).times(pricesUSD[edge]!), + ); + symbols.push(symbol); + edges.push(edge); + } + + let liquidity: Big; + let shortfall: Big; + if (collat.gt(borrow)) { + liquidity = collat.minus(borrow); + shortfall = new Big('0'); + } else { + liquidity = new Big('0'); + shortfall = borrow.minus(collat); + } + + return { + liquidity: liquidity, + shortfall: shortfall, + symbols: symbols, + edges: edges, + }; + } +} diff --git a/services/delegator/src/CompoundAPI.ts b/services/delegator/src/CompoundAPI.ts new file mode 100644 index 0000000..0ae66b5 --- /dev/null +++ b/services/delegator/src/CompoundAPI.ts @@ -0,0 +1,79 @@ +import nfetch from 'node-fetch'; + +type CompoundAPIRequestValue = { value: string }; + +interface ICompoundAPIRequest { + addresses?: string[]; + block_number?: number; + max_health?: CompoundAPIRequestValue; + min_borrow_value_in_eth: CompoundAPIRequestValue; + page_number?: number; + page_size?: number; + network?: string; +} + +const fetch = async (r: ICompoundAPIRequest) => { + const method = 'GET'; + const headers = { + 'Content-Type': 'application/json', + Accept: 'application/json', + }; + + const path = 'https://api.compound.finance/api/v2/account?'; + const params = Object.keys(r) + .map((key) => { + const knownKey = key as keyof ICompoundAPIRequest; + let uri; + switch (knownKey) { + case 'max_health': + case 'min_borrow_value_in_eth': + uri = `${knownKey}[value]=${r[knownKey]!.value}`; + return encodeURIComponent(uri).replace('%3D', '='); + case 'addresses': + uri = `${knownKey}=${r[knownKey]!.join(',')}`; + return encodeURIComponent(uri); + default: + return `${knownKey}=${r[knownKey]}`; + } + }) + .join('&'); + + const res = await nfetch(path + params, { + method: method, + headers: headers, + }); + return res.json(); +}; + +async function sleep(millis: number) { + return new Promise((resolve) => setTimeout(resolve, millis)); +} + +const getBorrowers = async (minBorrow_Eth: string) => { + let borrowers = []; + + let i = 1; + let pageCount = 0; + + let result; + do { + result = await fetch({ + min_borrow_value_in_eth: { value: minBorrow_Eth }, + page_size: 100, + page_number: i, + }); + if (result.error) { + console.warn(result.error.toString()); + continue; + } + borrowers = borrowers.concat(result.accounts.map((account: any) => account.address)); + pageCount = result.pagination_summary.total_pages; + i++; + + await sleep(100); // Avoid rate limiting + } while (i <= pageCount); + + return borrowers; +}; + +export default getBorrowers; diff --git a/services/delegator/src/PriceLedger.ts b/services/delegator/src/PriceLedger.ts index 34d324e..7787a82 100644 --- a/services/delegator/src/PriceLedger.ts +++ b/services/delegator/src/PriceLedger.ts @@ -1,7 +1,8 @@ import { Big } from '@goldenagellc/web3-blocks'; import { CoinbaseKey } from './types/CoinbaseKeys'; -import { CTokenSymbol } from './types/CTokens'; +import { CTokenSymbol, CTokenCoinbaseKeys } from './types/CTokens'; +import IPostablePriceFormat from './types/IPostablePriceFormat'; import IPrice from './types/IPrice'; import IPriceRange from './types/IPriceRange'; @@ -13,7 +14,7 @@ interface PostableDatum { type TimestampMap = { [i: string]: PostableDatum }; -const USD_VALUE: Big = Big('1000000'); +const USD_VALUE: Big = new Big('1000000'); const SAI_PER_ETH = '0.005285'; export default class PriceLedger { @@ -75,42 +76,73 @@ export default class PriceLedger { return `*${key}:*\n\tmin: $${min} (${minAge} min ago)\n\tmax: $${max} (${maxAge} min ago)`; } + public getPostableFormat(symbols: CTokenSymbol[], edges: ('min' | 'max')[]): IPostablePriceFormat | null { + let didFindNull = false; + + const formatted: IPostablePriceFormat = { + messages: [], + signatures: [], + symbols: [], + }; + + symbols.forEach((symbol, i) => { + const key = CTokenCoinbaseKeys[symbol]; + if (key === null) return; + + const prices = this.prices[key]; + if (prices === null) { + didFindNull = true; + return; + } + + const timestamp = prices[edges[i]].timestamp; + const postableData = this.postableData[key][timestamp]; + + formatted.messages.push(postableData.message); + formatted.signatures.push(postableData.signature); + formatted.signatures.push(postableData.key); // should equal local `key` + }); + + if (didFindNull) return null; + return formatted; + } + public getPrices(symbol: CTokenSymbol): { min: Big | null; max: Big | null } { switch (symbol) { case 'cBAT': return { - min: this.prices.BAT?.min.value, - max: this.prices.BAT?.max.value, + min: this.prices.BAT?.min.value || null, + max: this.prices.BAT?.max.value || null, }; case 'cCOMP': return { - min: this.prices.COMP?.min.value, - max: this.prices.COMP?.max.value, + min: this.prices.COMP?.min.value || null, + max: this.prices.COMP?.max.value || null, }; case 'cDAI': return { - min: this.prices.DAI?.min.value, - max: this.prices.DAI?.max.value, + min: this.prices.DAI?.min.value || null, + max: this.prices.DAI?.max.value || null, }; case 'cETH': return { - min: this.prices.ETH?.min.value, - max: this.prices.ETH?.max.value, + min: this.prices.ETH?.min.value || null, + max: this.prices.ETH?.max.value || null, }; case 'cREP': return { - min: this.prices.REP?.min.value, - max: this.prices.REP?.max.value, + min: this.prices.REP?.min.value || null, + max: this.prices.REP?.max.value || null, }; case 'cSAI': return { - min: this.prices.ETH?.min.value.mul(SAI_PER_ETH), - max: this.prices.ETH?.max.value.mul(SAI_PER_ETH), + min: this.prices.ETH?.min.value.mul(SAI_PER_ETH) || null, + max: this.prices.ETH?.max.value.mul(SAI_PER_ETH) || null, }; case 'cUNI': return { - min: this.prices.UNI?.min.value, - max: this.prices.UNI?.max.value, + min: this.prices.UNI?.min.value || null, + max: this.prices.UNI?.max.value || null, }; case 'cUSDC': case 'cUSDT': @@ -120,13 +152,13 @@ export default class PriceLedger { }; case 'cWBTC': return { - min: this.prices.BTC?.min.value, - max: this.prices.BTC?.max.value, + min: this.prices.BTC?.min.value || null, + max: this.prices.BTC?.max.value || null, }; case 'cZRX': return { - min: this.prices.ZRX?.min.value, - max: this.prices.ZRX?.max.value, + min: this.prices.ZRX?.min.value || null, + max: this.prices.ZRX?.max.value || null, }; } } diff --git a/services/delegator/src/StatefulBorrower.ts b/services/delegator/src/StatefulBorrower.ts new file mode 100644 index 0000000..d7a89be --- /dev/null +++ b/services/delegator/src/StatefulBorrower.ts @@ -0,0 +1,167 @@ +import { EventData } from 'web3-eth-contract'; +import Web3 from 'web3'; +import winston from 'winston'; + +import { Big } from '@goldenagellc/web3-blocks'; + +import { CTokenReversed, CTokenSymbol, cTokenSymbols } from './types/CTokens'; +import { CToken } from './contracts/CToken'; +import Borrower, { IBorrowerPosition } from './Borrower'; + +export default class StatefulBorrower extends Borrower { + protected readonly fetchBlock: number = 0; + private _didInit: boolean = false; + + constructor(address: string, fetchBlock: number) { + super(address); + this.fetchBlock = fetchBlock; + } + + public get didInit(): boolean { + return this._didInit; + } + + public async init(provider: Web3, cTokens: { [_ in CTokenSymbol]: CToken }): Promise { + if (this._didInit) { + console.warn('Already initialized borrower. Aborting!'); + return; + } + + let didInit = true; + for (let symbol of cTokenSymbols) { + const snapshot = await cTokens[symbol].getAccountSnapshot(this.address)(provider, this.fetchBlock); + if (snapshot.error !== '0') { + didInit = false; + continue; + } + + const position = this.positions[symbol]; + position.supply = position.supply.plus(snapshot.cTokenBalance); + position.borrow = position.borrow.plus(snapshot.borrowBalance); + + const borrowIndex = await cTokens[symbol].borrowIndex()(provider, this.fetchBlock); + if (borrowIndex.gt(position.borrowIndex)) position.borrowIndex = borrowIndex; + } + this._didInit = didInit; + } + + public onMint(event: EventData, undo = false): void { + if (event.blockNumber <= this.fetchBlock) return; + + const symbol = this.getSymbolFor(event.address); + if (symbol === null) return; + const position = this.positions[symbol]; + + if (undo) { + position.supply = position.supply.minus(event.returnValues.mintTokens); + winston.info(`🪙 *${symbol} Mint* by ${this.address.slice(2, 8)} removed from chain`); + } else { + position.supply = position.supply.plus(event.returnValues.mintTokens); + winston.info(`🪙 *${symbol} Mint* by ${this.address.slice(2, 8)}`); + } + } + + public onRedeem(event: EventData, undo = false): void { + if (event.blockNumber <= this.fetchBlock) return; + + const symbol = this.getSymbolFor(event.address); + if (symbol === null) return; + const position = this.positions[symbol]; + + if (undo) { + position.supply = position.supply.plus(event.returnValues.redeemTokens); + winston.info(`🪙 *${symbol} Redeem* by ${this.address.slice(2, 8)} removed from chain`); + } else { + position.supply = position.supply.minus(event.returnValues.redeemTokens); + winston.info(`🪙 *${symbol} Redeem* by ${this.address.slice(2, 8)}`); + } + } + + public onBorrow(event: EventData, undo = false, currentBorrowIndex: Big): void { + if (event.blockNumber <= this.fetchBlock) return; + + const symbol = this.getSymbolFor(event.address); + if (symbol === null) return; + const position = this.positions[symbol]; + + if (undo) { + position.borrow = position.borrow.minus(event.returnValues.borrowAmount); + winston.info(`🪙 *${symbol} Borrow* by ${this.address.slice(2, 8)} removed from chain`); + } else { + position.borrow = new Big(event.returnValues.accountBorrows); + position.borrowIndex = currentBorrowIndex; + winston.info(`🪙 *${symbol} Borrow* by ${this.address.slice(2, 8)}`); + } + } + + public onRepayBorrow(event: EventData, undo = false, currentBorrowIndex: Big): void { + if (event.blockNumber <= this.fetchBlock) return; + + const symbol = this.getSymbolFor(event.address); + if (symbol === null) return; + const position = this.positions[symbol]; + + if (undo) { + position.borrow = position.borrow.plus(event.returnValues.repayAmount); + winston.info(`🪙 *${symbol} Repay* by ${this.address.slice(2, 8)} removed from chain`); + } else { + position.borrow = new Big(event.returnValues.accountBorrows); + position.borrowIndex = currentBorrowIndex; + winston.info(`🪙 *${symbol} Repay* by ${this.address.slice(2, 8)}`); + } + } + + public onLiquidateBorrow(event: EventData, undo = false): void { + if (event.blockNumber <= this.fetchBlock) return; + + const symbolA = this.getSymbolFor(event.address); + if (symbolA === null) return; + const positionA = this.positions[symbolA]; + const symbolB = this.getSymbolFor(event.returnValues.cTokenCollateral); + if (symbolB === null) return; + const positionB = this.positions[symbolB]; + if (positionA === null || positionB === null) return; + + if (undo) { + positionA.borrow = positionA.borrow.plus(event.returnValues.repayAmount); + positionB.supply = positionB.supply.plus(event.returnValues.seizeTokens); + winston.info(`💦 Liquidation ${event.transactionHash.slice(0, 10)} removed from chain`); + } else { + positionA.borrow = positionA.borrow.minus(event.returnValues.repayAmount); + positionB.supply = positionB.supply.minus(event.returnValues.seizeTokens); + winston.info( + `💦 ${this.address.slice( + 2, + 8, + )} had their *${symbolA} liquidated* and ${symbolB} seized by ${event.returnValues.liquidator.slice(2, 8)}`, + ); + } + } + + public onTransfer(event: EventData, undo = false): void { + if (event.blockNumber <= this.fetchBlock) return; + + const symbol = this.getSymbolFor(event.address); + if (symbol === null) return; + const position = this.positions[symbol]; + + // Make sure that this borrower is the `to` address, and that this wasn't a Mint/Redeem side-effect + const shouldAdd = this.address === event.returnValues.to && event.address !== event.returnValues.from; + if (shouldAdd) { + if (undo) position.supply = position.supply.minus(event.returnValues.amount); + else position.supply = position.supply.plus(event.returnValues.amount); + } else { + if (undo) position.supply = position.supply.plus(event.returnValues.amount); + else position.supply = position.supply.minus(event.returnValues.amount); + } + } + + private getSymbolFor(address: string): CTokenSymbol | null { + const symbol = CTokenReversed[address]; + if (symbol === undefined) { + console.warn(`Address ${address} wasn't found in reverse lookup table!`); + return null; + } + return symbol; + } +} diff --git a/services/delegator/src/StatefulBorrowers.ts b/services/delegator/src/StatefulBorrowers.ts new file mode 100644 index 0000000..8f8518e --- /dev/null +++ b/services/delegator/src/StatefulBorrowers.ts @@ -0,0 +1,153 @@ +import { EventData } from 'web3-eth-contract'; +import Web3 from 'web3'; + +import { Big } from '@goldenagellc/web3-blocks'; + +import { CTokenSymbol, cTokenSymbols } from './types/CTokens'; +import { CToken } from './contracts/CToken'; +import StatefulBorrower from './StatefulBorrower'; + +export default class StatefulBorrowers { + private readonly provider: Web3; + private readonly cTokens: { [_ in CTokenSymbol]: CToken }; + + private readonly borrowers: { [address: string]: StatefulBorrower } = {}; + private readonly borrowIndices: { -readonly [_ in CTokenSymbol]: Big } = { + cBAT: new Big('0'), + cCOMP: new Big('0'), + cDAI: new Big('0'), + cETH: new Big('0'), + cREP: new Big('0'), + cSAI: new Big('0'), + cUNI: new Big('0'), + cUSDC: new Big('0'), + cUSDT: new Big('0'), + cWBTC: new Big('0'), + cZRX: new Big('0'), + }; + + constructor(provider: Web3, cTokens: { [_ in CTokenSymbol]: CToken }) { + this.provider = provider; + this.cTokens = cTokens; + } + + public async init(): Promise { + const block = await this.provider.eth.getBlockNumber(); + await Promise.all(this.fetchBorrowIndices(block)); + this.subscribe(block); + } + + public async push(addresses: string[]): Promise { + const block = await this.provider.eth.getBlockNumber(); + addresses.forEach((address) => { + this.borrowers[address] = new StatefulBorrower(address, block); + this.borrowers[address].init(this.provider, this.cTokens); + }); + } + + public async randomCheck(): Promise { + const keys = Object.keys(this.borrowers); + const borrower = this.borrowers[keys[(keys.length * Math.random()) << 0]]; + if (!borrower.didInit) { + console.log(`${borrower.address} hasn't been initialized yet`); + } else { + const valid = await borrower.verify(this.provider, this.cTokens, this.borrowIndices, 0.01); + if (!valid) console.log(`${borrower.address} has invalid state`); + } + } + + private fetchBorrowIndices(block: number): Promise[] { + return cTokenSymbols.map(async (symbol) => { + this.borrowIndices[symbol] = await this.cTokens[symbol].borrowIndex()(this.provider, block); + }); + } + + private subscribe(block: number): void { + 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, true); + }) + .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, true); + }) + .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, false, this.borrowIndices[symbol]); + }) + .on('changed', (ev: EventData) => { + const borrower: string = ev.returnValues.borrower; + if (borrower in this.borrowers) this.borrowers[borrower].onBorrow(ev, true, this.borrowIndices[symbol]); + }) + .on('error', console.log); + + subscribeTo + .RepayBorrow(block) + .on('data', (ev: EventData) => { + const borrower: string = ev.returnValues.borrower; + if (borrower in this.borrowers) this.borrowers[borrower].onRepayBorrow(ev, false, this.borrowIndices[symbol]); + }) + .on('changed', (ev: EventData) => { + const borrower: string = ev.returnValues.borrower; + if (borrower in this.borrowers) this.borrowers[borrower].onRepayBorrow(ev, true, this.borrowIndices[symbol]); + }) + .on('error', console.log); + + 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, true); + }) + .on('error', console.log); + + subscribeTo + .Transfer(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, true); + const to: string = ev.returnValues.to; + if (to in this.borrowers) this.borrowers[to].onTransfer(ev, true); + }) + .on('error', console.log); + }); + } +} diff --git a/services/delegator/src/StatefulComptroller.ts b/services/delegator/src/StatefulComptroller.ts index 4b2a14b..7602867 100644 --- a/services/delegator/src/StatefulComptroller.ts +++ b/services/delegator/src/StatefulComptroller.ts @@ -54,15 +54,15 @@ export default class StatefulComptroller { } public getCloseFactor(): Big | null { - return this.closeFactor?.value; + return this.closeFactor?.value || null; } public getLiquidationIncentive(): Big | null { - return this.liquidationIncentive?.value; + return this.liquidationIncentive?.value || null; } public getCollateralFactor(symbol: CTokenSymbol): Big | null { - return this.collateralFactors[symbol]?.value; + return this.collateralFactors[symbol]?.value || null; } private async fetchCloseFactor(block: number): Promise { @@ -110,7 +110,7 @@ export default class StatefulComptroller { if (!StatefulComptroller.shouldAllowData(ev, this.closeFactor!)) return; this.closeFactor = { - value: Big(ev.returnValues.newCloseFactorMantissa), + value: new Big(ev.returnValues.newCloseFactorMantissa), block: ev.blockNumber, logIndex: ev.logIndex, }; @@ -119,7 +119,7 @@ export default class StatefulComptroller { if (!StatefulComptroller.shouldAllowDataChange(ev, this.closeFactor!)) return; this.closeFactor = { - value: Big(ev.returnValues.oldCloseFactorMantissa), + value: new Big(ev.returnValues.oldCloseFactorMantissa), block: ev.blockNumber, logIndex: ev.logIndex, }; @@ -138,7 +138,7 @@ export default class StatefulComptroller { if (!StatefulComptroller.shouldAllowData(ev, this.liquidationIncentive!)) return; this.liquidationIncentive = { - value: Big(ev.returnValues.newLiquidationIncentiveMantissa), + value: new Big(ev.returnValues.newLiquidationIncentiveMantissa), block: ev.blockNumber, logIndex: ev.logIndex, }; @@ -147,7 +147,7 @@ export default class StatefulComptroller { if (!StatefulComptroller.shouldAllowDataChange(ev, this.liquidationIncentive!)) return; this.liquidationIncentive = { - value: Big(ev.returnValues.oldLiquidationIncentiveMantissa), + value: new Big(ev.returnValues.oldLiquidationIncentiveMantissa), block: ev.blockNumber, logIndex: ev.logIndex, }; @@ -170,7 +170,7 @@ export default class StatefulComptroller { const collateralFactor = this.collateralFactors[symbol]!; if (!StatefulComptroller.shouldAllowData(ev, collateralFactor)) return; - collateralFactor.value = Big(ev.returnValues.newCollateralFactorMantissa); + collateralFactor.value = new Big(ev.returnValues.newCollateralFactorMantissa); collateralFactor.block = ev.blockNumber; collateralFactor.logIndex = ev.logIndex; } @@ -184,7 +184,7 @@ export default class StatefulComptroller { const collateralFactor = this.collateralFactors[symbol]!; if (!StatefulComptroller.shouldAllowDataChange(ev, collateralFactor)) return; - collateralFactor.value = Big(ev.returnValues.oldCollateralFactorMantissa); + collateralFactor.value = new Big(ev.returnValues.oldCollateralFactorMantissa); collateralFactor.block = ev.blockNumber; collateralFactor.logIndex = ev.logIndex; } diff --git a/services/delegator/src/StatefulPricesCoinbase.ts b/services/delegator/src/StatefulPricesCoinbase.ts index 95952db..1b77232 100644 --- a/services/delegator/src/StatefulPricesCoinbase.ts +++ b/services/delegator/src/StatefulPricesCoinbase.ts @@ -1,5 +1,4 @@ import { FetchError } from 'node-fetch'; -import winston from 'winston'; import { Big } from '@goldenagellc/web3-blocks'; @@ -33,7 +32,7 @@ export default class StatefulPricesCoinbase extends CoinbaseReporter { const updatedKeys = await this.fetch(); // TODO: trigger callbacks - if (updatedKeys.length > 0) winston.info(this.ledger.summaryText); + // if (updatedKeys.length > 0) winston.info(this.ledger.summaryText); } private async fetch(): Promise { @@ -41,6 +40,9 @@ export default class StatefulPricesCoinbase extends CoinbaseReporter { const updatedKeys: CoinbaseKey[] = []; const report = await this.fetchCoinbasePrices(); + if (report.messages === undefined) { + console.log(report); + } for (let i = 0; i < report.messages.length; i += 1) { const message = report.messages[i]; const signature = report.signatures[i]; @@ -51,7 +53,7 @@ export default class StatefulPricesCoinbase extends CoinbaseReporter { const knownKey = key as CoinbaseKey; // Store - const price: IPrice = { value: Big(value), timestamp: timestamp }; + const price: IPrice = { value: new Big(value), timestamp: timestamp }; if (this.ledger.append(knownKey, price, message, signature)) updatedKeys.push(knownKey); } return updatedKeys; diff --git a/services/delegator/src/StatefulPricesOnChain.ts b/services/delegator/src/StatefulPricesOnChain.ts index abf0cd6..22e3615 100644 --- a/services/delegator/src/StatefulPricesOnChain.ts +++ b/services/delegator/src/StatefulPricesOnChain.ts @@ -80,7 +80,7 @@ export default class StatefulPricesOnChain { // Store the new price const newPrice = { - value: Big(ev.returnValues.value), + value: new Big(ev.returnValues.value), timestamp: ev.returnValues.timestamp, block: ev.blockNumber, logIndex: ev.logIndex, @@ -94,8 +94,10 @@ export default class StatefulPricesOnChain { const idx = this.prices[knownKey].findIndex((p) => newPrice.block - p.block > 12); if (idx !== -1) this.prices[knownKey].splice(Math.max(idx, 2)); + const summaryBefore = this.ledger.summaryTextFor(knownKey)?.replace(knownKey, 'Before'); this.propogateToLedger(knownKey); - winston.info(`📈 ${knownKey} price posted to chain!\n${this.ledger.summaryTextFor(knownKey)}`); + const summaryAfter = this.ledger.summaryTextFor(knownKey)?.replace(knownKey, 'After'); + winston.info(`📈 ${knownKey} price posted to chain!\n${summaryBefore}\n${summaryAfter}`); }) .on('changed', (ev: EventData) => { if (!Object.keys(coinbaseKeyMap).includes(ev.returnValues.key)) return; @@ -104,8 +106,10 @@ export default class StatefulPricesOnChain { const idx = this.prices[knownKey].findIndex((p) => p.block === ev.blockNumber && p.logIndex === ev.logIndex); if (idx !== -1) this.prices[knownKey].splice(idx, 1); + const summaryBefore = this.ledger.summaryTextFor(knownKey)?.replace(knownKey, 'Before'); this.propogateToLedger(knownKey); - winston.info(`⚠️ ${knownKey} price suffered chain reorganization!\n${this.ledger.summaryTextFor(knownKey)}`); + const summaryAfter = this.ledger.summaryTextFor(knownKey)?.replace(knownKey, 'After'); + winston.info(`⚠️ ${knownKey} price suffered chain reorganization!\n${summaryBefore}\n${summaryAfter}`); }) .on('error', console.log); } diff --git a/services/delegator/src/_borrowers.json b/services/delegator/src/_borrowers.json deleted file mode 100644 index d1e89be..0000000 --- a/services/delegator/src/_borrowers.json +++ /dev/null @@ -1,630 +0,0 @@ -{ - "high_value": [ - "b58c4358669bb70df715b6747447eb63afea8916", - "5d0afe4d683edb633a36a3d280bb6b30ea4f4646", - "f3d9b8f6af674e82943b4685728b00d13a62fc9e", - "534ac20e8ae44bdd22cf4975e0e0316c5790ca9e", - "7d790d3fca1232e6d7d7643cad2b27951e20378a", - "4f4e0f2cb72e718fc0433222768c57e823162152", - "fb626333099a91ab677bcd5e9c71bc4dbe0238a8", - "97f958bea42f40f1229f310de5479a9a3e418944", - "07320deb2713370a3d7b49189fc2f99906e1ae8e", - "e8e8f41ed29e46f34e206d7d2a7d6f735a3ff2cb", - "49b357c9acaa84a0cb978571b642b825e964b2cc", - "edcc13d25e23032b61d30c298334f92d7c0ba84e", - "9c5083dd4838e120dbeac44c052179692aa5dac5", - "742fb193517619eecd6595ff106fce2f45488ebf", - "f9e11762d522ea29dd78178c9baf83b7b093aacc", - "6dc328941ca84da4f1432ec44ef7539dda644f4f", - "04ce8a23fde59ef0b35bc1805dc0e465cf03c874", - "e9105df0be753e804faeca8cfb88ff1524f62c7d", - "13837bcfabc62198112b6dd655e825dcdf0392ea", - "187318f330e9e6e8a560b44a62577b5ff0e6bc1e", - "ea61f3052753ea2c6a1c208583ad9b0394ed2f28", - "ac888c754546ea45b168fbfa605253deb25a3879", - "8304210d13e75526114e7347bf7976635e403e29", - "4086e3e1e99a563989a9390facff553a4f29b6ee", - "cd2b49b9974376736b3c102d14f5e469bd3e5d98", - "fffc347842e55acb49cc0885eec29d268b4a4a20", - "414a13329d426280567603c310f87b35eacb7e21", - "45f754bf6598e539ab97bc981044665095708568", - "ba49d17b41e5abc20e33f917a0a66770fc1d8d07", - "6fe0866b6f442a8ef2ec9e8f461b1219fb415bf2", - "fa7ec9653e53d1e192a7c3659c2134d2c778c029", - "1c11ba15939e1c16ec7ca1678df6160ea2063bc5", - "d6ab5dba9a3697cf2e6e860e4fc0f441a8351889", - "8b479b4db794e933604a43ec9fc291258d99ea23", - "53704f4f3dd160248921d2d527a6bedf78324e9c", - "5c8bce502f2fdd329e4378f02a3c580796f20cca", - "7a452490981d28ef554fbb563dd07f3b21056f60", - "346f1d297c98c28574742b067b67a80cda2dc0d9", - "5b0a91e4dbb9fd133e2c912b4e0e37ba4277397e", - "9d11b1e1445b017902e561ceb201f009b3d53a42", - "6ef3454215fe322dc72e55c0a622c10418c625e7", - "b5bf7288dc8d9fafa7add5706de251d7530e4dee", - "009dbff7ba1fe691f39142b5f6284cc01c3ea5e8", - "8fffcd4c45467144e37be379da3e794060c6da9c", - "7bda16073b3ffe2ba4cf06784515d6fa21439aa3", - "af289894855061fed86ab7713981a9310fd0c1cf", - "8e68cd8c40efd63d89151288277309bff92a4383", - "0b7cac4ad9973ffa0867408a930e81edc08acaeb", - "5d9a905f95777a7b923bc3ce2678b6e76ba0fbdb", - "5c7efb1067ef301046f25210720c161490017a8a", - "a0f75491720835b36edc92d06ddc468d201e9b73", - "2cc308d515a73690ba58ed637d1b20b4b7324fcd", - "b71cd2a879c8d887ea8d75155ff51116178641c0", - "2f7cda069e575635e832d042042e2f99f4532b93", - "8c4af5d87549355ea5fd4a85c7b96a3f243c13e6", - "8e949b62a177452f5b327b539eaa63802a3ab6a4", - "5bd87adb554702e535aa74431dda68eaf9a8f548", - "7329e4350d7381c6203bfb37f531230108ac3fa0", - "dd709cae362972cb3b92dcead77127f7b8d58202", - "78b58e4197c04d320a33c582ecc3e7e885f26a58", - "e6410ecbaa041a5dcde79164ca66de42a8d72bee", - "e84a061897afc2e7ff5fb7e3686717c528617487", - "485773ab2c5b060380a4c826c08cde45b00f8792", - "338f3ec014d7edacb98506881d1e18dc00980fdc", - "dbf66494751f9bb52feab916c87763585775bf10", - "3ada347a2c817d4dae26535f817a4141bce34378", - "d856007b58a97d2f96b558a49cd3e7d3b1e96c0e", - "58485ea7106891bdd94c37ced30c6fdbc5293b16", - "78f6303f7d49166f2da1bb9c679b675234a645bf", - "6513b81d8240cb94abc089f2cba60be6fda2f805", - "285306442cd985cab2c30515cfdab106fca7bc44", - "1f84ca2c0a406b32e083e4e41032fd148364df48", - "240edc6143e1859f4c06bc3d7f7a7962be1ebbd7", - "57ca561798413a20508b6bc997481e784f3e6e5f", - "96d47c6190472307a37e34e12acd05873f131b6c", - "7d411873898ab53ee8260273ee9232213524ad62", - "c590e25afb8d871b227b4f1d22eee2214ac0bf06", - "2f93524b327100fba3dc685204f94c7a86c28a8b", - "f31ff73d9875523e53ec2813cfe669555592faaa", - "a5b65d4a68c1c71aae3910199f4ab266f726afbf", - "6d61861bd52285d9b3f6a81039b92478d40e806d", - "3bbec29ab82db1f0be3f67261cc902c4e35ab70d", - "e8f99654dd99fc1861c46a4328ce47e297e64884", - "77b7cd137dd9d94e7056f78308d7f65d2ce68910", - "a726c00cda1f60aaab19bc095d02a46556837f31", - "1f6b9fb3a37aef4b815f58d84d55779e42b19482", - "7146d797a9e8eb8c1b153c01aebccba2dc695044", - "52185a2bbcfd67c1d07963e3575175ee9f95a551", - "8b349d17392f266d87d518bf71426b2921cc35ee", - "c756c024f48f4570001cc9421ea5aa1e80a6d2d3", - "08c8f3bc4cb803ac2cbd10d26ff8ad6370110138", - "186cf5714316f47bc59e30a850615a3f938d7d79", - "bc79855178842fdba0c353494895deef509e26bb", - "847956f7f7ff49714fb2d70a0d0cd44a6376990f", - "6b82246aaec31195ceb76c3185381a522622222c", - "b94164a7a89102725c82d61e6b7a8db1d2cd00e8", - "5c00a7bc35fd1edc597e157c194ac233bcb42c61", - "3a7f8731eb208a598afbae43299449f25bdc3699", - "20f6689f9abb8075560ffe016a97b5ed35e91547", - "1d80d36125950efb29573e99af9fc1489001eca7", - "78bc49be7bae5e0eec08780c86f0e8278b8b035b", - "2448816a0c8dc46eef28d08bf3997d83cce4fc0b", - "ff0b0d8927814ee537b7fc54567a48c8546e1cca", - "b1adceddb2941033a090dd166a462fe1c2029484", - "e0b5b1dddba731771419cf087528b08f26002fe1", - "6774305359234f12e766e07f36462104f00ab0b2", - "b6e7026de41558d3a4bdd6026337c93e8ba3af88", - "d09a6dd8ff69c6269922df90b7756df3d0950506", - "10bf1dcb5ab7860bab1c3320163c6dddf8dcc0e4", - "3532d6733992da9c69d46824032c4d680d0aa46f", - "d5c69ec78dc59aceb14842d0228b13e181dea9ae", - "034a5f8678455ea078021e7a5b34ab7ef210674e", - "72a916702bd97923e55d78ea5a3f413dec7f7f85", - "55b9c56668365d11f5af18e8b7232bc6e4d20658", - "b8e4f6dedfa4d4063d465536bcb5926744319c69", - "53bd12031e4cd6af14c91d4122152ead80ccb342", - "691d4172331a11912c6d0e6d1a002e3d7ced6a66", - "2c101e1cfe5cc0d0a948f802059c43723ad90175", - "9fe9e92012e97f6f0c884fee5436d4f07b505517", - "ac284c9d2e9a5f0f20a4379524120dc7d9cd7e1d", - "f1f0115613963b47b5f1f82019135e641449702f", - "5fd1964d2afd013a88f9cf51ff921affac8fe5f6", - "fd3034fafd950e87687e08d74e0614022e762609", - "b73840dca717ec949ec3dccf0cf3322fd5308a88", - "5d3183cb8967e3c9b605dc35081e5778ee462328", - "554bd2947df1c8d8d38897bdc92b3b97692b2845", - "15ecace8c817378abfb025a1aca90e3f460f765e", - "d026bfdb74fe1baf1e1f1058f0d008cd1eeed8b5", - "0392783a52fea3a7c91ea0e1e78b7721c8cb3253", - "e81ef15f525c82e49d6df6d113b5fac35fa8c559", - "1d2a2527474d1a25638fadfbf9e8d19fc978722c", - "6e164d3b3d2c5c515d84d59bb944c724800a587e", - "964d9d1a532b5a5daeacbac71d46320de313ae9c", - "f69ea6646cf682262e84cd7c67133eac59cef07b", - "51bd04c3afd3ba190e40b9b12db998bf765695ed", - "805c559b43565505cec1273801d5aab30fb91004", - "ffa91a325a48df4dd1b2fbf7b68f5187faeae5cc", - "e1a59cdd826c4460a30a77cc0526cd19351f670b", - "ed99c6929bba505a2f1a65b9ca156a068fab6427", - "25a033316752ac9e443a10be07fa56c125b93c29", - "99fd1378ca799ed6772fe7bcdc9b30b389518962", - "f7bcd7bb00f4d081775fedef64e92017a10d4b25", - "eb3795c44dbb18ea05a0f0a9501d3ce97b243803", - "de8589960da34eefb00ca879d8cc12b11f52cb12", - "c1d84507b43cacb53348d04e5c283b9e4c22a2cf", - "f675112b86d7a573baf4839c12de4d1988fd0077", - "39c09fdc4e5c5ab72f6319ddbc2cae40e67b2a60", - "62494b3ed9663334e57f23532155ea0575c487c5", - "befdf308e77987ddcb36ec18d757b04f2de8da67", - "38e481367e0c50f4166ad2a1c9fde0e3c662cfba", - "f03a7e9c31d0593127e405265ce8c526f55d9352", - "9f74cb355c640de21c4312a9551e18add4fd2828", - "fe8054e14f1a2878293f1b18b4339d0c0503204e", - "0c9f1f250d5e77637a32f4da085eb708ed31f08a", - "4b6f4cc24a805c0a3878f9734a246e762f47ad65", - "d4ffd5bdc785fe5f8b8d3568ab43b8da8492ca4d", - "ccb06b8026cb33ee501476af87d5ccaf56883112", - "3b8247040a6627aba98d9d9905870c44d91dd80d", - "5a53373d2a3672be8fdd8874b75f9cc88cf8ea0c", - "189c2c1834b1414a6aee9eba5dc4b4d547c9a44c", - "b83c15a93b65a57c59a568068dfee5da911146b8", - "0eb4add4ba497357546da7f5d12d39587ca24606", - "35d632d331af5ff49467495cf85bd3597846979d", - "544d13855bcb1515e5a1cf3d23ac22ad596447c6", - "1c055b02cdf8fcb1db4fca3dc5a8a5a2057c5222", - "65b1b96bd01926d3d60dd3c8bc452f22819443a9", - "508b01d1597379ed0f1fb84a00297e193e8daa7a", - "5921c191fe58175b1fb8943ed3f17345e615ff02", - "3b0a30c3889996d66e2e27d61ac05ef0c734c6db", - "2b6eb6d9495af90f2a6ba5660905d4daa2b1b81c", - "853a8cf2cdcb79076f05a557a1064a705ecb7773", - "2bdded18e2ca464355091266b7616956944ee7ee", - "be51e10349a5deee9111fc92377f156208d79ae2", - "ff6f7e421cac8b488634c7a8275093a62075a1c6", - "adeeb9d09b8bcee10943198fb6f6a4229bab3675", - "78e96be52e38b3fc3445a2ed34a6e586ffab9631", - "cb0c6765944d7d78611f0b3434ddef6af2ca310f", - "ddacc636784316e291f8e8ddca4b4385832c8a62", - "a89fe6a6f7ae1a6aab9bbbc171e7b233eb9e7f07", - "a7317d49e7be8f40673a927b45b3a0303552a360", - "96d480f112fcb02f0be233bfbdac12434412148c", - "b4982f934db398cd4e1f24a581ebdf5df9ba4069", - "17d4febc12b039b7341a4df6a956a52d6a2b7a39", - "6790fc4ddacb1581bfc461bdf4807225b4718c7c", - "6d2af065ccb60c0f7e8ec5907c961c42a3447127", - "825e92422c8f42a7c8e7273ab8503b8ebd615c8e", - "3ba21b6477f48273f41d241aa3722ffb9e07e247", - "4fb685826a37c4d607059c53cac1b9142f295f01", - "04c88746d52f9d1d94a4cf346c31f1c0b761a508", - "402a75f3500ca1fba17741ec916f07a0c9db195d", - "46eea8d5b37d2db51f35c1bc8c50cbf80fb0ffe5", - "65eab5ec71cec12f38829fbb14c98ce4bad28c46", - "f6f3ff48268efe682deafd9ab968241b91c323bf", - "3ee505ba316879d246a8fd2b3d7ee63b51b44fab", - "ab0aed73cdf40af99fd578f8fd72c295b45a9cb1", - "702a39a9d7d84c6b269efaa024dff4037499bba9", - "55d78fd9d5efe4dcd7595aaed1a4185a42bf58d7", - "9437806725631c0b209b6c0b5fd4198a77a57073", - "3f3e305c4ad49271ebda489dd43d2c8f027d2d41", - "25599dcbd434af9a17d52444f71c92987fa97cfc", - "bbe3188a1e6bfe7874f069a9164a923725b8bd68", - "ee2826453a4fd5afeb7ceffeef3ffa2320081268", - "3ce6cd3098821decb9b2769e1390d74821e349f0", - "60c2b424188e35f5c883d10de560241202170c91", - "1f2c30d67ea40b39b396d91ff5cb3c487e4f7079", - "f8edb8466905c970ec5a60544facdb88ce81468e", - "0c5096391134727f4c4d5fb9134061a70f029431", - "b0b655d1dc882a4adc1a6291b77cbb3b6d570e2a", - "12b64f9918a131d7b5b0f4190de35ee04f78ac87", - "d910d7cdf629493c5c3b13faa6ef0f9a7a12838f", - "018a82c70f689aeedb05cbc55631c5cfa807a25c", - "2acc99c33ed65d89504eb2040fcadd29e93640fa", - "6a815735471dbd0f85c51b115b728a247abe0cf4", - "15fdb1002ef716e9e552e007f1a588654aa7038b", - "14e9e9f0a8d9bac4cad8c1ce339826f42924e542", - "6ef1251e47513386ce6df1a2f17dca6bdf22afd5", - "179b7f5d9a5845c785afd67c72d00c8f54ece19c", - "57bb6525c489ee43bee420f90379b047d7c40f31", - "b02e5c98c715585bf682fcf7a8e5810148cb8a71", - "41aec353f7ba7982271938d60a996034e13ff18b", - "701bd63938518d7db7e0f00945110c80c67df532", - "91391b956f3180098aee67a0a708c7082fc7b6fb", - "944a81e4018439b8ce00391fe6b9fd0e81e9ce20", - "057518153ed7f25dd237a0d0052ae8cc5c428ee3", - "b15fd1808c936d27ee72e52d1ecf106d8f4830a6", - "d6d6869333c5cbf3af8c973b17193d921dbe2df3", - "c6bbfe0ce06f85ed6edbfd015cd5920e17b128da", - "604ea85b112964bd75f5ed784610c816b3e3856b", - "a2556ca3c699a10f9ef64af3876e422d58beccf5", - "fab90d837b82ec306257115c022a624049d2ec21", - "826c3064d4f5b9507152f5cb440ca9326e1ec8fa", - "ff4944f8809ec48d8acc655ff83770cbd4c3a02a", - "375f2e62350d9970d8261914812d08afed6ab794", - "614ad783725023cb3e31efa2aeb5412123654097", - "22fa8cc33a42320385cbd3690ed60a021891cb32", - "ac6559df1f410feba9a6cbf395272189461d8463", - "86af94e5e8d3d583575bbafdd2dcb6b898a555e4", - "9250b9b5c67618b4753abfa3ee52bc7a13e2b814", - "3060502029f142829ba62da227543dab09a98128", - "8888a1cdbf7c9e60129e87954ed574183b045443", - "eccb2874656973620ce269fe73635569316d9d25", - "431e81e5dfb5a24541b5ff8762bdef3f32f96354", - "a20413ad36a06a1e2571b57e0802d772c59c8737", - "4fc126b084fd491cf417c306717019e9c0d6d087", - "e0e484dfa7f3aa36733a915d6f07eb5a57a74a11", - "89261103fa88a913c8d0debd00574fd16895407d", - "7a8edc710ddeadddb0b539de83f3a306a621e823", - "57ef012861c4937a76b5d6061be800199a2b9100", - "d31ab5ab8cd0f482f5728888519b5c39b5a4a6a0", - "5666c032d7efc9a3a5bd2a190de9f9b88501acda", - "da3fb33db42a60c16a69a9e00e0780f5d68e6c84", - "b00ba6778cf84100da676101e011b3d229458270", - "2ee9ca25ee0d96dcfb33235a7886bb239d7ffb29", - "e462eae2aef5defbcddc43995b7f593e6f0ae22f", - "fe1cb94e42cccf82a45040c714ee42b506d82fab", - "7d6149ad9a573a6e2ca6ebf7d4897c1b766841b4", - "a42e82c8cb31068b240772ec69685ffc59b7fd11", - "733428f811d11940ed1168dcc57e21a5660c8f68", - "25ae7b45d8646580dfcae403d29164729eb8642f", - "51463a9bf35c7374cab5bf559a49b3833f306f65", - "4960170d73f32f6e28302cd3f356e284034f2880", - "a5376b8028ca39d157b93815f18603e6023202a5", - "0ffeb87106910eefc69c1902f411b431ffc424ff", - "ba3b8e947a2d3013ba69a9497bf55fff4f08a28e", - "503dbc89ec3012dd65fa4140d8dcdfeb42e91046", - "7dd738e463ede2f4735968799b851cc5787391ec", - "7f29af7083031878450380fec8c8ab7d485d7344", - "18a7726db7acec07e00f0542ec22aeba610beb9f", - "4807a63eab0811c23599c61d71c9b6d564f3baa1", - "108a8b7200d044bbbe95bef6f671baec5473e05f", - "cca71809e8870afeb72c4720d0fe50d5c3230e05", - "e458eaca2b9b2eb0a79ed899a80574eadddae308", - "52f0feaa424fc209625f87f6c39ee1c1fa2440a9", - "c55c7f66c7ffd1f639aeb11763a480e705cee617", - "ecd06992384094377bd582ad35869aa47646f797", - "75c0c0591cdf9af7cb826a65ac213cff31916668", - "32e00ba0370f63eb157d7222e36a36705ff9ac5b", - "f55d84504217542d4f39d9bf9a70342744836007", - "54b5ae5ebe86d2d86134f3bb7e36e7c83295cbcb", - "b0ff496df3860504ebdff61590a13c1d810c97cc", - "50177685221fd7780da6e0ad01e3ec912e366ba0", - "2e3884107746c9019de9726f91f80514ee1d0b54", - "d0e528518283b54121fefe7d5c956acb7b121e86", - "ab6f4e136b2145afd2611cc711f50d89c5de0b1d", - "775842eeede73f443b06744f8cc98ac4011ef62a", - "0c8a8dd439069690a5722d5fbb18359a68e279f1", - "eb6572f25c89b45775e46ab934a36f24bc25f44a", - "a7ad402ac8b1a27633ad5f667e237e1d5e823317", - "c9493738f07ddc43f1a004d4bb461fa42de23225", - "99a7a29bf0e86e578ed3334c7647bc5d04524619", - "e44799ef334df157e0f8e2855e5ebebdbc02b299", - "e8cad67b1beef4cc00479d107b785d0734dc8146", - "a4195329d89ac68d8076e2f8e6e2b71286bfbfa3", - "ba1b3303cf8d64cd6df6e93462d68a83add01636", - "c33d98e88682c883fe32b8f6620660692092d39f", - "47a5b6c45561279ef8f4088031d2c1801bbdc385", - "0bbd4687661da82ed1c0e0a295559934d7eee70b", - "1753758423d19d5ba583e99294b51c86b3f7e512", - "741841d166234beca20c0dd889537451f790d522", - "1a25831d8ece971ab43b4427177bde9cd9acd04a", - "53fdaba24a8388ada7a9a631b4e34c72900773ea", - "a7b6f7f3e3aa2d9d33249e755f62d7e5ae19ef13", - "04e921c430f41d3692d959b457fe82cfe73538c6", - "a8b97a131989109a60dd7abfb3ed5e1df8905f35", - "5fc8c4e0647fd62281901b9fc30c6eaaa6e749d3", - "a35d70cb20d209fc691ccdbc1f778c01c02a0140", - "9bf5533bede7de66cd5a32dcd8d24b6e73f8eb08", - "c5b0d67b004563d633ba58a198441e992a12b04d", - "13e252df0cafe34116cec052177b7540afc75f76", - "78d196056e1f369ec2d563aaac504ea53462b30e", - "b7904059e7f4863ad9efe7778dbe5f663e379501", - "4c81ac8a069122d2a7146b08818fbaddcb2ff1f0", - "db0c804547229312a6db7fbc0754f2d823e711ab", - "4c8133051b300ccd66b6b35c5a0af15b6a97012a", - "f96da4775776ea43c42795b116c7a6eccd6e71b5", - "7988e38636559eb52001aa682cdad4e5d8747e84", - "64bca4dcafb3f889f2df9f97e7a6a6b297acb8e1", - "cb1096e77d6eab734ffceced1fcd2d35ee6b8d15", - "3991adbdf461d6817734555efdc8ef056fefbf21", - "42de72bd5a806f136369668ef9c7f7ebfb5f66be", - "5985f6af4d9224c243328a7d41b9414873bf43be", - "797c89b95122844131bad581e7cebaa7b57c4a98", - "1d364d906574d0178d8236a7e26c27e3240571b1", - "767ecb395def19ab8d1b2fcc89b3ddfbed28fd6b", - "d438f6951fc209e9c38b6f8f706a1abed1d6d325", - "22e29deb1109f5602c02c187b5b37faf5481ea99", - "9ec8e8f64ec6bb47d944b4b830130b5fcf2da182", - "e0b2026e3db1606ef0beb764ccdf7b3cee30db4a", - "99739fa525c0a98384430235d278fd08938997f9", - "09732c94654f77e3650424942a1e7e05181f1420", - "0bcbab2fecc30b7341132b4ebb36d352e035f1bd", - "31a47094c6325d357c7331c621d6768ba041916e", - "82a29a5932dc160997074a355366e03c32612d24", - "1bf366828f388dc504a11082caabdeb1e81567ed", - "2b67a3c0b90f6ae4394210692f69968d02970126", - "7e040ff3082fb4f545e0f94c78c1b0615c072a01", - "944a45b9c842aea385c5555b0fcb522f6fccec3f", - "0192044f4e90cd0499d0307b1de53dc7e64b5a0e", - "4d7d4485fd600c61d840ccbec328bfd76a050f87", - "643527d903701eb9f1e4e95eb51dc54f0fef253e", - "73a82365500bd04bebb260e90d74d8c371df5f15", - "ca5a2d40e507b8ee9ff1d50146cdd1726cc4143c", - "41122f2bf6edc3dfb6bf148000e3900c28d38004", - "d74f186194ab9219fafac5c2fe4b3270169666db", - "d84101ae36b83e64b52ac9a61efa4497f8fd2560", - "c5884f82fcaa8e1bcce4cce47f5a2c1b7ba2ed52", - "c30ac52374479361bd3cc1663f8e1750c32f1d7a", - "e2ff15ba5714cb1a7d4d61ed13c171cf762c69b7", - "ee439ee079ac05d9d33a6926a16e0c820fb2713a", - "719043a8fe188afe5768096eca23043666b9c681", - "efaaae01613e3efff382e4b2ad411d3a287915c4", - "75e4975db2cb8d4861aa83371c1b4aff833358b7", - "04f806b3a5d670901a19879a626e0e025b7991f3", - "c22b743adaeefc584b9e35da302d0a6ce7bb35d7", - "3607b06fc06ca0c98720a8be96620835974024e7", - "5d6437d2e2dfc55384055cee5536448764a898b9", - "3d1fe6a32d4c62bc50cbdbbd3f6c0cb1d95909bb", - "0bb74c5a3d1e90ae5d5ebf04a363650167b577e5", - "052564eb0fd8b340803df55def89c25c432f43f4", - "df83d6a1f2ee7c91daa5e26b3b85977dcfe95203", - "54f57c4337553f5736f3cf7f63257a4923351818", - "ce280f586671d3c09a772d3848040cee08676d29", - "55e1490a1878d0b61811726e2cb96560022e764c", - "b6799f729ff7bf37043fd68a364e56adcee78644", - "926e78b8df67e129011750dd7b975f8e50d3d7ad", - "29664e652ca8f8f2d95de3b33cc0ae7247fc0aa9", - "0464e2993a24abeca84dc18ff16221e40d630df8", - "ed3c4c5d7a9abfd74f33c1042793dfd6a6daef42", - "35493737f3b0a653b1bf5acba87e991c87b5fb09", - "7578e676b3706aa65a4d7b9bd621922a305552db", - "5893f8aa9c265e4b88bb59ff053b3b1f0270d2a5", - "5d7e36e7813e82614b6bfa73560167d2ea547311", - "6dab3bcbfb336b29d06b9c793aef7eaa57888922", - "c546e5c769503fbab1326d9bb94ba200fe2d835f", - "97d4b02ce33c399ffec618bfd2d5bf7108e556ac", - "8bd210fff94c41640f1fd3e6a6063d04e2f10eeb", - "7b52f8029640b3d8a1a7e7e2dead673db4f290fb", - "9fe9dc57bf733bdafd0d6d4610d2d671f8dc974f", - "587eb67c533b58ada96a9f0c12f4ad89cbb50aa8", - "3c6691417617eb504dc87fb8a028727ea6c0d3ce", - "0c256328b8d9668b7ce14ca86d43933b2c52aa2c", - "79dbd1baf124edd4205b2aba56c29bf3914c8ed0", - "1b356172e1532a3e476c05035b46eb1e6f3e9d79", - "cd892a97951d46615484359355e3ed88131f829d", - "88889bd5e47a7e1c587273e0d496ce60ed5d6338", - "8efb0c37179955caa0f71e73e318edc0e8788eaf", - "8792dcf57d3a9b88bc9169d6d6a7a732c2e00ff0", - "40b97ec9a615cc8e71640d0323b6d95b1b7275e4", - "985cb8d1db2db971071aacf6222df421538332d7", - "da3059e065781976845359154cc3aae1d0e99289", - "daef20ea4708fcff06204a4fe9ddf41db056ba18", - "68030330e8158be3fa5b3ec3c94bf07e42824b9b", - "ab925f99ad463d619f6ba0788843ab8da50302a8", - "ae817090449e05952a050500babf02074973ccf1", - "5cfd0cddf989959a6a6c3ad985ce324460d46dfd", - "be1236f1f60619aea8e5636075cc8de30f9c95c8", - "eeaa0d372dbc1a7ab10cadc4ad3b5b8f4096497f", - "4d17676309cb16fa991e6ae43181d08203b781f8", - "3a567303b207c6d906a8fcc380a2c307ece7051d", - "a4214cab2b92900f82a32ee881d15a8809f764ee", - "4c232fdbe114a597531ff29bc79c72faa0698a55", - "889abdd2bc0f3a884e607279ba132501698fbcd5", - "043f8524f87efb25990b65ff4918f9128acf742e", - "7cc0540ca8270e03318a74f0c3643522e7561f5f", - "22b564646e3d22319bcdce31ab6beab270c0be6c", - "53fb0162bc8d5eec2fb1532923c4f8997bace111", - "48d93dabf29aa5d86424a90ee60f419f1837649f", - "1a1cd9c606727a7400bb2da6e4d5c70db5b4cade", - "b769eb6ea3df2f30a7316e07a2d7a831e8c4f795", - "909b443761bbd7fbb876ecde71a37e1433f6af6f", - "ef764bac8a438e7e498c2e5fccf0f174c3e3f8db", - "35e3564c86bc0b5548a3be3a9a1e71eb1455fad2", - "a2ea752925be0e2f21463d6acd0f69b64ba7c0ec", - "42c6ee8a64cd9e0c19b0e2c3db6ed3acb7823adb", - "3e76113c6d79060f84cf351d63dd28b8361cbe5d", - "2341fb17fe2435760ac531a3a411ee16116351f2", - "bbbc35dfac3a00a03a8fde3540eca4f0e15c5e64", - "ef9f5d6ab172e89462687bdb16633f20ef8198a0", - "3e6a8275789dc2b23229ab2042e2adc3acbd3587", - "e4bed3988b25eb625466102f2d0bea1c9fafcd86", - "e5350e927b904fdb4d2af55c566e269bb3df1941", - "e9947f9d6137be1efc8525384c49e964aeb4bcd9", - "4583ac3a97c87bf767ba0ff2ce49ce228076c8aa", - "125c335a6d53544ef67c4393bf4e102f7e59bff6", - "c190dcca8c65e47592d80ec4678c24e658de1544", - "c691d74aa756ebd94800253f1674fda9ba612529", - "f8c42927a60cbd4a536ce24ef8bed00b16a9b44b", - "3ad8d7465e88d826de82ea49984d8d6f0ee71db4", - "a700cce427a386a918e2309b3555055794c50b1f", - "2bd7d71390ed6946159b1cb49bc270cbe3ef1a53", - "d61f71c075d84f279c9d4d274b1bba2d3566e458", - "d9989d54316a21f4d5e74dede82e1b95d1f8a64f", - "ce43257dd236ca0aa59a70861ebc902f3b27b27f", - "22ce5d1ff793590e2bcef70be3d5c7f88b3063cb", - "e0497d23c68118db8f1e8439864c6774dfe0e825", - "84c35f982496982b916f15d21026bc8d1d3cbc59", - "7e6f6621388047c8a481d963210b514dbd5ea1b9", - "cfc50541c3deaf725ce738ef87ace2ad778ba0c5", - "a70960deb4f472340c2815d3be07e95c2631acc4", - "84cf2b37fbf6b39241cdda2a26372ab6d372468c", - "97d87c77aaabb0ee7faa1921f9a651d4e0fc85dd", - "32b2d4ec46d76fc6dabfe958fb0e0bd8db740c84", - "388b93c535b5c3ccdb14770516d7caf5590ed009", - "b93ddeff2959e0d3fa43db65c1fb0d29dc75ccca", - "2974f8a9c290c53ca2ad2bc9749f34d75145ac07", - "faafa857640ddfe924d5f710323f26675e5cfaaf", - "4becca336d9d7ab7aabddabc7985b26d683f15ea", - "c354c702513d5984ef77bc3d1974cfb3fdac3134", - "274d9e726844ab52e351e8f1272e7fc3f58b7e5f", - "2efc8e5efa159a9bf2dd5d9b30d85a1c9f13048f", - "b29bca5465d87d2582a5fda4e65626108b7f4bf9", - "444e01dcb3a1ec1b1aa1344505ed7c8690d53281", - "4740fa6b32c5b41ebbf631fe1af41e6fff6e2388", - "4deb3edd991cfd2fcdaa6dcfe5f1743f6e7d16a6", - "3a189ba2a93c3ba331828906399015067f2ac072", - "ee8ad6769fe89ecb8fee0d981ad709e08e6d1c06", - "341ad6b9239462f687b5f1b5996fe93c78ac3904", - "50c57f86b1e0f766e458838a2e13eb75e91d7692", - "c066d23d9d492df3821fc05b8868791912cd1683", - "126309e3318beaef29979da1ec6e4661953a9a21", - "df63be2e473ba04c26b1609e51d08cf0d78e0913", - "273e05a3d93e2d091b0aa836e68d8696aaaaaaaa", - "2f89f40485f32276434a4ebe29a5d3a4b04cc1b0", - "a489e9daf10ced86811d59e4d00ce1b0dec95f5e", - "c873b2f1ee1a8f9a08590553e36f92ab67b32297", - "06cb7c24990cbe6b9f99982f975f9147c000fec6", - "db043d37333c54fad10fe9fb00a11f4d71f3192b", - "97137466bc8018531795217f0ecc4ba24dcba5c1", - "08cfd293d687b6cee139219a607acbbc10a6eb25", - "2baba0cba8241fda56871589835e0b05ec64ca41", - "635b230c3fdf6a466bb6dc3b9b51a8ceb0659b67", - "58c0a5f11469ea49ad1bf0ad0d25a5cae582dd0a", - "04b0b0e460c9fc583d9c93bc9ae25b353390645e", - "8579e8ae97113d3e3973348c4f50fe9f400faf46", - "b42d6d92080885a9b6166f4ea960733da75cf36b", - "047ad85acfb13da6cbde4b6bed1dd5e97b8d2e71", - "efbc900ecef736e0310f1f1f9dd6d4c29d3fb4c4", - "38720d56899d46cad253d08f7cd6cc89d2c83190", - "8fe5c824f7c167d81538b13a6e1e308216b0fc47", - "9b66137da669f2e6277832a3d2c91edfe963e64c", - "f7537d7dfdbba4042ddb919e9225d5da1aa10d65", - "65b0bf8ee4947edd2a500d74e50a3d757dc79de0", - "a5d223c176daab154a3134369d1c0478c5e6fecf", - "c67e37e9a7c3d6ca4462263b47f91ee52c6e8378", - "7320b0bfcdc29d4bff6334481e6a58912fd53035", - "04caf8ce2b221ad29da18ccdfdbb7c7544f59999", - "d779bb0f68f54f7521aa5b35dd88352771843764", - "6c6d03a20613867fefe4df04bd103fa544e3f1df", - "85ba79273d64f5cfc113f2c7b2da60f584fdf032", - "7aa9d09a6d283f5b5ec724d7dd8fa70673553183", - "fff9182d3b128d28b2abd4cae102a9e031b3e1f6", - "f39d30fa570db7940e5b3a3e42694665a1449e4b", - "4031afd3b0f71bace9181e554a9e680ee4abe7df", - "88886841cfccbf54adbbc0b6c9cbaceabec42b8b", - "1f2091d9d5d79d0b0d4752cd458c08920e80e814", - "e0b4716d52f0d185dc6f3cbf0c69f3aa8f5ca28d", - "d7600603356a5da560d1a59f70194c58cd28e7ad" - ], - "previously_liquidated": [ - "8bfc07a92520f3aeff9e66a82687db1963e0b0c6", - "60a71287353f3ac632f3e75fd1be532850aa5f4d", - "2565f8e0b33f2d3bb1c0e2dd5fbb972df48654c8", - "4c6ab26af01b63b0c421f569c569d65bbb2b3a97", - "1f5be3c931deb102a9e2c489c8abd074a6450e1a", - "ed3c4c5d7a9abfd74f33c1042793dfd6a6daef42", - "339dab47bdd20b4c05950c4306821896cfb1ff1a", - "c9459bf2e0a1f89ec29c77b3944581cf590f9a7e", - "543657598f7b237ab7289c2ced5d5418551cd057", - "c78ce4e51611ed720ec96bf584bf1b1658fd2379", - "9d60dbf81faa358f4809906f21f1e16ce983d578", - "141f59a0283303a6b882b4d6973e418f8d75f9b3", - "5467277a95e153d7d052f147d28aa01d95d52c9b", - "4f4e0f2cb72e718fc0433222768c57e823162152", - "8983acf83c5eec243622cc341c45857ccbf22b3e", - "7b52f8029640b3d8a1a7e7e2dead673db4f290fb", - "d062eeb318295a09d4262135ef0092979552afe6", - "d50f649a2c9fd7ae88c223da80e985d71de45593", - "468578b44a786ff71d507bd201a9f5ce49140490", - "20a37a8e185e48a2690f339b6d0792e9383511ab", - "e003d21fd416b11e1d16ce2ac3339980caa15105", - "db16bb1e9208c46fa0cd1d64fd290d017958f476", - "f776d80ffae3870a0a1c11e6fef0770bbb64f8e4", - "cc60721776b70023ae3196b3a9994e911aa66854", - "2fb5ba1766c44d9568ccb1dfc50e4426c8e3ce3d", - "2c4ef3a24b1f38d848283c65b05b00987ee43fd8", - "d3a56886eed6c1599381f3035f12843d492387b6", - "57adad5729e839acd4019fc9e79c2685a42ed489", - "2f5f2223ba5756eb5403d05fd0b9df9fffed253f", - "889abdd2bc0f3a884e607279ba132501698fbcd5", - "7f29af7083031878450380fec8c8ab7d485d7344", - "161fac24d54698755dab0fcd65e2c883928ca724", - "d60c419e408f0a80b5d7139c5741b1ca00d57456", - "39c09fdc4e5c5ab72f6319ddbc2cae40e67b2a60", - "909b443761bbd7fbb876ecde71a37e1433f6af6f", - "9759c78812015d078ea73f695cc326f00ad002b2", - "38e481367e0c50f4166ad2a1c9fde0e3c662cfba", - "b144bd2873e4f3f70433c0fa8fdb73cd0fb9f536", - "dbfc163ae96a0e6f53e0296b9e03589871bbbf97", - "bf7abb7636ad59f47568ced4903169593dda6e09", - "7a452490981d28ef554fbb563dd07f3b21056f60", - "ef15902a5e70961738b8fff0caa49e555f4bcad0", - "3c2714b19cc07feb1fde1e2674a19d045f02d255", - "01adb5a14196d302004e3a1970a8bb3183dd2565", - "189c2c1834b1414a6aee9eba5dc4b4d547c9a44c", - "0e1212b04daa889686d7e39361ae33d9afa8b79b", - "c25290aba43cba9ac89af1fbda280866fc561cfe", - "7093f8979a945bcdc52284027929defa99977671", - "915fb54cf1bbae77dd1ae2c8987350784220861e", - "065ad8ae434ad88cf67c031a9748b6719301cedb", - "73372fd360663a0688c7db7017331495a192f28a", - "bfebe09d854272c3aeeb1e4411c2b02a83d0339e", - "c9493738f07ddc43f1a004d4bb461fa42de23225", - "807a74f40f8f41c9d46b6543951c615af66bb77a", - "c2f61a6eeec48d686901d325cde9233b81c793f3", - "8313722178211283cf5b2e9a07987ff26574b37d", - "8877889ff4a0fa0415d7572cbc871ede200e2f78", - "d3ebaef07a2cd63bd99017394e89a9fa241be5eb", - "f2df969f59b2c86e4b230da88918cdebcfc4ccbc", - "8626e72944f62aad5d85e60b8d34471f7d5ef980", - "f3d5e8f1cb8cc717661d6a8e692b60cdd1b45ca2", - "1821a39c87e4d8a3dc5a8391a1ba7057e4eadd06", - "2e4ae4d3bd0af45040b4f17e0bb7e6dc548626b1", - "b6c0276ad1d87c6cf6dfa323d0c3f6840121c0ba", - "d9989d54316a21f4d5e74dede82e1b95d1f8a64f", - "1233e098c9a87f610ec3a4354e4d6c8878b116c8", - "e21623cdf2a6f464ad21c920521980ebde28ba91", - "bd3d51680121a2ebf9a02ad1e15b494aec7aa6a5", - "a7317d49e7be8f40673a927b45b3a0303552a360", - "5c7c6d069ba232718f37c27a9549b547c359e31c", - "e7da19d08df73c9371080a14b8e6fdca9243e16e", - "04e921c430f41d3692d959b457fe82cfe73538c6", - "22fb3590ca2b6a876d7fb655fc6e598abf28d3e1", - "dac0db00fd0953d8731f86c6908366388bfcc1f8", - "2988983d105436843757ce8e45be5b3af3736445", - "9ca910038faea585b3fb52374b5557d14b2b32c3", - "240edc6143e1859f4c06bc3d7f7a7962be1ebbd7", - "4918151bfa7ad842f6f941aaac4052b671580bdb", - "7d411873898ab53ee8260273ee9232213524ad62", - "e71adfe265233ccfd84ddc2e4ab13d64b7f075b4", - "3ee505ba316879d246a8fd2b3d7ee63b51b44fab", - "22cebaeeb60fb8208586168414126819a18888cb", - "7461f3b3d064138f1cb963f0d1322c6568c3c091", - "d438f6951fc209e9c38b6f8f706a1abed1d6d325", - "7e3474dfb1f9510ed314d11afa6c6f395b2ded61", - "dd6029397c7dd04b538f1c98d0be725d7afb1782", - "25599dcbd434af9a17d52444f71c92987fa97cfc", - "d43c4b29917d3ab5d48e3074c8f43291ff77e309", - "ee2826453a4fd5afeb7ceffeef3ffa2320081268", - "df9924a483d946e8e2cc50652a6b3907e64edd5d", - "be569c65bae96df7fb767cf1d1492597bbabc22a", - "d166575ea55b934819d58729e68738124b8f6354", - "df63be2e473ba04c26b1609e51d08cf0d78e0913", - "52185a2bbcfd67c1d07963e3575175ee9f95a551", - "e2b532765a71caf7c0258290d0e4a3c7de74aa09", - "8b349d17392f266d87d518bf71426b2921cc35ee", - "586e32930ac05127de429bd566eaa2758fcbd9bc", - "1dbb874bf06f7a178cab1a3f334d8c35b4c56138", - "6c153feae296dd6f0249323cf597724a9ebfff33", - "6a815735471dbd0f85c51b115b728a247abe0cf4", - "136d9aade0fabb66dfaf7fba1d450c447d8d9d3d", - "18121de1d43cd6a7db27c9813eca6c2c1387b5b6", - "847956f7f7ff49714fb2d70a0d0cd44a6376990f", - "ea93c16b2ed1cd73e6f9d5b5a92c36e504e8dc72", - "6b0db47d32039b1993ba4a14d28eaf26cc02b147", - "39bde2f9254cfef7d0487a27e107ef6c1685e44c", - "b1adceddb2941033a090dd166a462fe1c2029484", - "ef034eaab7e403e091cb9d1f3d1e6a0411c27f88", - "e73e76e3ddba532a3583888df330c82df1568e07", - "bc8d089824461048a06d300dff88bb7357d88b3b", - "6774305359234f12e766e07f36462104f00ab0b2", - "22ad0012040baf64f5af6d2d3f94d98a01137a9b", - "fdde7ad56320b9cc691ad319a8fec2c8d6b993e6", - "b6e7026de41558d3a4bdd6026337c93e8ba3af88", - "506bf27c20d5e1608c43f044d9cf982fb0164123", - "72a916702bd97923e55d78ea5a3f413dec7f7f85", - "3af015f6e3ac79d217198f00ef36af099d223e29", - "00e8e22d1d0b338bb8ef7fa37eb2f08512ab8a4a", - "03324cfffabc10193de63186a374d7cfe932b162", - "e24286adfc053f76888aa51d9a94f6c1519b4cba", - "0bb74c5a3d1e90ae5d5ebf04a363650167b577e5", - "a14bdde6aecb530bac3a3a8e76d06e280dc6b03e", - "4940b1e80f5e60dd35c6c36ad012801fca75458f", - "2755f169627369e30a9cdfd30f43c80e6e4c1905", - "f39d30fa570db7940e5b3a3e42694665a1449e4b", - "6c5e32fa097e2c05f3e4bf539671cdcae0e22e0a", - "1f2091d9d5d79d0b0d4752cd458c08920e80e814" - ] -} diff --git a/services/delegator/src/contracts/CToken.ts b/services/delegator/src/contracts/CToken.ts index 7018923..858e86c 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, CTokenCreationBlocks } from '../types/CTokens'; +import { CTokens, CTokenSymbol, CTokenCreationBlocks } from '../types/CTokens'; import abiEth from './abis/cether.json'; import abiV1 from './abis/ctokenv1.json'; @@ -45,20 +45,25 @@ export class CToken extends BindableContract { return this.callerForUint256(method); } + public borrowIndex(): ContractCaller { + const method = this.inner.methods.borrowIndex(); + return this.callerForUint256(method); + } + public getAccountSnapshot(account: string): ContractCaller { const method = this.inner.methods.getAccountSnapshot(account); return this.callerFor(method, ['uint256', 'uint256', 'uint256', 'uint256'], (x) => { return { error: x['0'], - cTokenBalance: Big(x['1']), - borrowBalance: Big(x['2']), - exchangeRate: Big(x['3']), + cTokenBalance: new Big(x['1']), + borrowBalance: new Big(x['2']), + exchangeRate: new Big(x['3']), } as AccountSnapshot; }); } } -type InstanceMap = { [d in keyof typeof CTokens]: T }; +type InstanceMap = { [_ in CTokenSymbol]: T }; const cTokens: InstanceMap = { cBAT: new CToken(CTokens.cBAT, abiV1, CTokenCreationBlocks.cBAT), diff --git a/services/delegator/src/contracts/Comptroller.ts b/services/delegator/src/contracts/Comptroller.ts index 0a0e7a9..fc4f6a5 100644 --- a/services/delegator/src/contracts/Comptroller.ts +++ b/services/delegator/src/contracts/Comptroller.ts @@ -32,12 +32,12 @@ export class Comptroller extends BindableContract { public collateralFactorOf(cToken: CTokens): ContractCaller { const method = this.inner.methods.markets(cToken); - return this.callerFor(method, ['bool', 'uint256', 'bool'], (x) => Big(x['1'])); + return this.callerFor(method, ['bool', 'uint256', 'bool'], (x) => new Big(x['1'])); } public getAssetsIn(account: string): ContractCaller { const method = this.inner.methods.getAssetsIn(account); - return this.callerFor(method, ['address[]'], (x) => Big(x['0'])); + return this.callerFor(method, ['address[]'], (x) => x['0']); } } diff --git a/services/delegator/src/start.ts b/services/delegator/src/start.ts index 49b8780..ca8e6ea 100644 --- a/services/delegator/src/start.ts +++ b/services/delegator/src/start.ts @@ -1,22 +1,25 @@ import ipc from 'node-ipc'; import { EventData } from 'web3-eth-contract'; +import Web3Utils from 'web3-utils'; import winston from 'winston'; import { providerFor } from '@goldenagellc/web3-blocks'; -import { CTokens } from './types/CTokens'; - import SlackHook from './logging/SlackHook'; +import cTokens from './contracts/CToken'; import comptroller from './contracts/Comptroller'; import openOraclePriceData from './contracts/OpenOraclePriceData'; import uniswapAnchoredView from './contracts/UniswapAnchoredView'; import PriceLedger from './PriceLedger'; +import StatefulBorrowers from './StatefulBorrowers'; import StatefulComptroller from './StatefulComptroller'; import StatefulPricesOnChain from './StatefulPricesOnChain'; import StatefulPricesCoinbase from './StatefulPricesCoinbase'; +import getBorrowers from './CompoundAPI'; + require('dotenv-safe').config(); // configure providers @@ -40,15 +43,16 @@ winston.configure({ exitOnError: false, }); -const symbols: (keyof typeof CTokens)[] = <(keyof typeof CTokens)[]>Object.keys(CTokens); - -import addressesJSON from './_borrowers.json'; -const addressesList = new Set([...addressesJSON.high_value, ...addressesJSON.previously_liquidated]); - const priceLedger = new PriceLedger(); +const statefulBorrowers = new StatefulBorrowers(provider, cTokens); const statefulComptroller = new StatefulComptroller(provider, comptroller); -const statefulPricesOnChain = new StatefulPricesOnChain(provider, priceLedger, openOraclePriceData, uniswapAnchoredView); +const statefulPricesOnChain = new StatefulPricesOnChain( + provider, + priceLedger, + openOraclePriceData, + uniswapAnchoredView, +); const statefulPricesCoinbase = new StatefulPricesCoinbase( priceLedger, process.env.COINBASE_ENDPOINT!, @@ -58,12 +62,18 @@ const statefulPricesCoinbase = new StatefulPricesCoinbase( ); async function start() { + await statefulBorrowers.init(); await statefulComptroller.init(); await statefulPricesOnChain.init(); await statefulPricesCoinbase.init(4000); - console.log(statefulComptroller.getCloseFactor().toFixed(0)); - console.log(statefulComptroller.getLiquidationIncentive().toFixed(0)); + console.log('Searching for borrowers using the Compound API...'); + const borrowers = await getBorrowers('10'); + console.log(`Found ${borrowers.length} borrowers using the Compound API`); + + statefulBorrowers.push(borrowers.map((x) => Web3Utils.toChecksumAddress(x))); + + setInterval(statefulBorrowers.randomCheck.bind(statefulBorrowers), 500); } // const borrowers: ICompoundBorrower[] = []; diff --git a/services/delegator/src/types/CTokens.ts b/services/delegator/src/types/CTokens.ts index 375daf8..14b0031 100644 --- a/services/delegator/src/types/CTokens.ts +++ b/services/delegator/src/types/CTokens.ts @@ -1,3 +1,5 @@ +import { CoinbaseKey } from './CoinbaseKeys'; + export enum CTokens { cBAT = '0x6C8c6b02E7b2BE14d4fA6022Dfd6d75921D90E4E', cCOMP = '0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4', @@ -42,3 +44,19 @@ export const CTokenUnderlyingDecimals: { [_ in CTokenSymbol]: number } = { cWBTC: 8, cZRX: 18, }; + +export const CTokenCoinbaseKeys: { [_ in CTokenSymbol]: CoinbaseKey | null } = { + cBAT: 'BAT', + cCOMP: 'COMP', + cDAI: 'DAI', + cETH: 'ETH', + cREP: 'REP', + cSAI: null, + cUNI: 'UNI', + cUSDC: null, + cUSDT: null, + cWBTC: 'BTC', + cZRX: 'ZRX', +}; + +export const CTokenReversed: { [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 new file mode 100644 index 0000000..9f60bae --- /dev/null +++ b/services/delegator/src/types/IBorrower.ts @@ -0,0 +1,13 @@ +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/delegator/src/types/ICompoundBorrower.ts b/services/delegator/src/types/ICompoundBorrower.ts deleted file mode 100644 index 3661cac..0000000 --- a/services/delegator/src/types/ICompoundBorrower.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Big } from '@goldenagellc/web3-blocks'; - -import { CTokens } from './CTokens'; - -type CTokensMap = { [d in keyof typeof CTokens]: T }; - -interface ICompoundPosition { - supply: Big; - borrow: Big; -} - -export default interface ICompoundBorrower { - address: string; - balances: CTokensMap; -} diff --git a/services/delegator/src/types/ILiquidationCandidate.ts b/services/delegator/src/types/ILiquidationCandidate.ts index 142f7a8..ce871b3 100644 --- a/services/delegator/src/types/ILiquidationCandidate.ts +++ b/services/delegator/src/types/ILiquidationCandidate.ts @@ -1,10 +1,10 @@ -import IOpenOraclePriceData from './IOpenOraclePriceData'; +import IPostablePriceFormat from './IPostablePriceFormat'; import { CTokens } from './CTokens'; export default interface ILiquidationCandidate { address: string; repayCToken: CTokens; seizeCToken: CTokens; - pricesToReport: IOpenOraclePriceData; + pricesToReport: IPostablePriceFormat; expectedRevenue: number; } diff --git a/services/delegator/src/types/IOpenOraclePriceData.ts b/services/delegator/src/types/IPostablePriceFormat.ts similarity index 58% rename from services/delegator/src/types/IOpenOraclePriceData.ts rename to services/delegator/src/types/IPostablePriceFormat.ts index 5c533e9..41c26c7 100644 --- a/services/delegator/src/types/IOpenOraclePriceData.ts +++ b/services/delegator/src/types/IPostablePriceFormat.ts @@ -1,4 +1,4 @@ -export default interface OpenOraclePriceData { +export default interface IPostablePriceFormat { messages: string[]; signatures: string[]; symbols: string[]; diff --git a/services/delegator/yarn.lock b/services/delegator/yarn.lock index 652c216..77a1caa 100644 --- a/services/delegator/yarn.lock +++ b/services/delegator/yarn.lock @@ -302,6 +302,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.0.tgz#5bd046e508b1ee90bc091766758838741fdefd6e" integrity sha512-RKkL8eTdPv6t5EHgFKIVQgsDapugbuOptNd9OOunN/HAkzmmTnZELx1kNCK0rSdUYGmiFMM3rRQMAWiyp023LQ== +"@types/big.js@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/big.js/-/big.js-6.0.2.tgz#a86938c1bb4511e69d91f69823cacc3f48ff1fa3" + integrity sha512-7NdmOT3zjtghMofDwP1nAJCJWVjc/96V5msXRAZ4lPrvpGlajA95VQec7OXwA2wQaVmhjt+F5ko8pjvQU1tTFA== + "@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" diff --git a/services/txmanager/src/IncognitoQueue.ts b/services/txmanager/src/IncognitoQueue.ts index 6a1a214..3c6a218 100644 --- a/services/txmanager/src/IncognitoQueue.ts +++ b/services/txmanager/src/IncognitoQueue.ts @@ -45,7 +45,7 @@ export default class IncognitoQueue implements IEthSubscriptionConsumer { this.active = new TxQueue(initialWallet); this.staged = null; - this.gasPrice = Big('0'); + this.gasPrice = new Big('0'); } public get queue(): TxQueue { @@ -88,7 +88,7 @@ export default class IncognitoQueue implements IEthSubscriptionConsumer { // TODO other options available on this append transition -- could set this up // so that we test latency on every transition (since tx speed isn't as // crucial on these, it's fine to send with Infura to test latency) - const balance = Big(await this.active.wallet.getBalance()); + const balance = new Big(await this.active.wallet.getBalance()); // Note: `this.staged` is known to be non-null because of `this.transitioning` const tx = Treasury.latest.changeIdentity(this.staged!.wallet.address, balance, this.gasPrice); this.active.append(tx); diff --git a/services/txmanager/src/TxManager.ts b/services/txmanager/src/TxManager.ts index 307856a..f384102 100644 --- a/services/txmanager/src/TxManager.ts +++ b/services/txmanager/src/TxManager.ts @@ -18,7 +18,7 @@ import competitors from './_competitors.json'; const competitorsFrom = new Set(competitors.from as string[]); const competitorsTo = new Set(competitors.to as string[]); -const INITIAL_GAS_PRICE: Big = Big('100000000000'); +const INITIAL_GAS_PRICE: Big = new Big('100000000000'); const DEADLINE_CUSHION = 500; export default class TxManager extends CandidatePool implements IEthSubscriptionConsumer { @@ -30,7 +30,7 @@ export default class TxManager extends CandidatePool implements IEthSubscription private tx: ITx | null = null; - private gasPriceMax: Big = Big('0'); + private gasPriceMax: Big = new Big('0'); private liquidatorWrapper: string | null = null; @@ -112,7 +112,7 @@ export default class TxManager extends CandidatePool implements IEthSubscription tx.to = this.liquidatorWrapper!; // Assume expectedRevenue is just plain ETH (no extra zeros or anything) - this.gasPriceMax = Big(target.expectedRevenue * 1e18).div(tx.gasLimit); + this.gasPriceMax = new Big(target.expectedRevenue * 1e18).div(tx.gasLimit); this.tx = tx; this.resetGasPrice(); this.sendIfDeadlineIsApproaching(this.tx); @@ -130,7 +130,7 @@ export default class TxManager extends CandidatePool implements IEthSubscription if (tx.from === this.queue.wallet.address) return; // Self if (!(competitorsTo.has(tx.to.slice(2)) || competitorsFrom.has(tx.from.slice(2)))) return; - const gasPrice = Big(tx.gasPrice); + const gasPrice = new Big(tx.gasPrice); if (gasPrice.lt(this.tx!.gasPrice)) return; if (gasPrice.gte(this.gasPriceMax)) return; diff --git a/services/txmanager/src/contracts/Liquidator.ts b/services/txmanager/src/contracts/Liquidator.ts index f091726..de576e2 100644 --- a/services/txmanager/src/contracts/Liquidator.ts +++ b/services/txmanager/src/contracts/Liquidator.ts @@ -5,7 +5,7 @@ import { Big, Contract, ITx } from '@goldenagellc/web3-blocks'; import abi from './abis/liquidator.json'; export class Liquidator extends Contract { - static readonly gasLimit = Big('2200000'); + static readonly gasLimit = new Big('2200000'); constructor(address: string) { super(address, abi as Web3Utils.AbiItem[]); diff --git a/services/txmanager/src/contracts/Treasury.ts b/services/txmanager/src/contracts/Treasury.ts index 8b3ce9a..c9ee229 100644 --- a/services/txmanager/src/contracts/Treasury.ts +++ b/services/txmanager/src/contracts/Treasury.ts @@ -26,7 +26,7 @@ export class Treasury extends Contract { } public changeIdentity(newEOA: string, currentEOABalance: Big, gasPrice: Big): ITx { - const gasLimit = Big('400000'); + const gasLimit = new Big('400000'); const maxTxFee = gasLimit.mul(gasPrice); const tx = this.txFor(this.inner.methods.changeIdentity(newEOA), gasLimit, gasPrice); @@ -36,7 +36,7 @@ export class Treasury extends Contract { } public refillCaller(currentEOA: string): ITx { - return this.changeIdentity(currentEOA, Big('0'), Big('0')); + return this.changeIdentity(currentEOA, new Big('0'), Big('0')); } } diff --git a/services/txmanager/test/IncognitoQueue.test.ts b/services/txmanager/test/IncognitoQueue.test.ts index 93f815a..f44df5c 100644 --- a/services/txmanager/test/IncognitoQueue.test.ts +++ b/services/txmanager/test/IncognitoQueue.test.ts @@ -116,13 +116,13 @@ describe('IncognitoQueue Test', function () { { to: caller, value: Web3Utils.toHex(`1${'0'.repeat(18)}`), // 1 ETH - gasPrice: Big('9000000000'), - gasLimit: Big('21000'), + gasPrice: new Big('9000000000'), + gasLimit: new Big('21000'), }, 0, ); // Make initial wallet the caller - const tx = Treasury.latest.changeIdentity(initialWallet.address, Big('0'), Big('9000000000')); + const tx = Treasury.latest.changeIdentity(initialWallet.address, new Big('0'), Big('9000000000')); // @ts-expect-error Extended Web3 await ganacheProvider.unlockUnknownAccount(caller); await ganacheProvider.eth.sendTransaction({ diff --git a/services/txmanager/test/contracts/Treasury.test.ts b/services/txmanager/test/contracts/Treasury.test.ts index 0f71c2b..dd20263 100644 --- a/services/txmanager/test/contracts/Treasury.test.ts +++ b/services/txmanager/test/contracts/Treasury.test.ts @@ -82,7 +82,7 @@ describe('Treasury Test', () => { const newEOA = ganacheProvider.eth.accounts.create().address; const gasPrice = new Big(Web3Utils.toWei('30', 'gwei')); - const tx = treasury.changeIdentity(newEOA, Big(currentEOABalance), gasPrice); + const tx = treasury.changeIdentity(newEOA, new Big(currentEOABalance), gasPrice); const receipt = await ganacheProvider.eth.sendTransaction({ from: caller, to: tx.to,