Skip to content

Commit

Permalink
Deployed contracts and added candidate removal code (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenshively authored Mar 14, 2021
1 parent 9067ceb commit 8436f53
Show file tree
Hide file tree
Showing 18 changed files with 157 additions and 263 deletions.
8 changes: 7 additions & 1 deletion ethereum/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ PROVIDER_INFURA_ID=
PROVIDER_ALCHEMY_KEY=

ACCOUNT_ADDRESS_DEPLOY=
ACCOUNT_SECRET_DEPLOY=
ACCOUNT_SECRET_DEPLOY=

ACCOUNT_ADDRESS_VANITY=
ACCOUNT_SECRET_VANITY=

ACCOUNT_ADDRESS_OWNER
ACCOUNT_SECRET_OWNER
9 changes: 7 additions & 2 deletions ethereum/migrations/2_initial_deployment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
);
});
};
19 changes: 19 additions & 0 deletions ethereum/test/treasury.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
9 changes: 7 additions & 2 deletions ethereum/truffle-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},

Expand Down
124 changes: 3 additions & 121 deletions services/delegator/src/Borrower.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import Web3 from 'web3';

import { Big } from '@goldenagellc/web3-blocks';

import {
CTokens,
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';

Expand All @@ -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;
Expand All @@ -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<boolean> {
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,
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions services/delegator/src/PriceLedger.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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];
Expand Down
6 changes: 3 additions & 3 deletions services/delegator/src/StatefulBorrower.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -17,7 +17,7 @@ export default class StatefulBorrower extends Borrower {
}

public fetchAll(block: number): Promise<void>[] {
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<void> {
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 8436f53

Please sign in to comment.