Skip to content

Commit

Permalink
Merge pull request #99 from White-Whale-Defi-Platform/feat/liquidations
Browse files Browse the repository at this point in the history
Feat/liquidations
  • Loading branch information
SirTLB authored Jul 10, 2023
2 parents fbd8070 + 2fd50e0 commit c99c06b
Show file tree
Hide file tree
Showing 21 changed files with 2,348 additions and 6,855 deletions.
7,655 changes: 1,149 additions & 6,506 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions src/chains/defaults/messages/getLiquidationMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { toUtf8 } from "@cosmjs/encoding";
import { EncodeObject } from "@cosmjs/proto-signing";
import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx";

import { LiquidationMessage } from "../../../core/types/messages/liquidationmessages";

/**
*
*/
export function getliqudationMessage(sender: string, overseerAddress: string, borrowerAddress: string) {
const message: LiquidationMessage = {
liquidate_collateral: {
borrower: borrowerAddress,
},
};
const encodedMsgObject: EncodeObject = {
typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract",
value: MsgExecuteContract.fromPartial({
sender: sender,
contract: overseerAddress,
msg: toUtf8(JSON.stringify(message)),
funds: [],
}),
};
return encodedMsgObject;
}
152 changes: 152 additions & 0 deletions src/chains/defaults/queries/getLoans.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { ChainOperator } from "../../../core/chainOperator/chainoperator";
import { AnchorOverseer, Loan, Loans } from "../../../core/types/base/overseer";

/**
* Queries all Loans connected to one Overseeraddress.
*/
export async function setLoans(overseer: AnchorOverseer, chainOperator: ChainOperator) {
const allBorrowers = await getAllBorrowers(overseer, chainOperator);
const allCollateral = await getAllCollaterals(overseer, chainOperator);
const allLoans = await getAllLoans(overseer.marketAddress, chainOperator);

const loans: Loans = {};
for (const collateral of allCollateral) {
const ltv = overseer.whitelist.elems.filter((elem) => elem.collateral_token === collateral.collaterals[0][0])[0]
.max_ltv;
// const e = allCollaterLs[o];
if (allBorrowers.has(collateral.borrower)) {
const loan: Loan = {
borrowerAddress: collateral.borrower,
collaterals: {},
borrowLimit: 0,
riskRatio: 0,
loanAmt: 0,
};
collateral.collaterals.forEach((elem: [string, string]) => {
loan.collaterals![elem[0]] = { amount: Number(elem[1]), ltv: +ltv };
});

loan.loanAmt = allLoans.get(collateral.borrower) ?? 0;
loans[collateral.borrower] = loan;
} else {
console.log("Borrower not found");
}
}
overseer.loans = loans;
}

/**
*
*/
async function getAllLoans(marketAddress: string, chainOperator: ChainOperator): Promise<Map<string, number>> {
let tmploans = await await chainOperator.queryContractSmart(marketAddress, {
borrower_infos: { limit: 30 },
});
let allLoans = tmploans.borrower_infos;
while (tmploans.borrower_infos.length == 30) {
tmploans = await await chainOperator.queryContractSmart(marketAddress, {
borrower_infos: {
limit: 30,
start_after: tmploans.borrower_infos[tmploans.borrower_infos.length - 1].borrower,
},
});
allLoans = allLoans.concat(tmploans.borrower_infos);
}

const tmpMapLoans: Map<string, number> = new Map();

allLoans.forEach((elem: any) => tmpMapLoans.set(elem.borrower, Number(elem.loan_amount)));
return tmpMapLoans;
}

/**
*
*/
async function getAllCollaterals(
overseer: AnchorOverseer,
chainOperator: ChainOperator,
): Promise<CollateralResponse["all_collaterals"]> {
const collQuery = {
all_collaterals: { limit: 30 },
};
let collateralResponse: CollateralResponse = await chainOperator.queryContractSmart(
overseer.overseerAddress,
collQuery,
);
const allCollaterals = collateralResponse.all_collaterals;
let currentCollaterals = collateralResponse.all_collaterals;
while (currentCollaterals.length === 30) {
const msg = {
all_collaterals: {
limit: 30,
start_after: currentCollaterals[currentCollaterals.length - 1].borrower,
},
};
collateralResponse = await chainOperator.queryContractSmart(overseer.overseerAddress, msg);
currentCollaterals = collateralResponse.all_collaterals;
allCollaterals.push(...currentCollaterals);
}
return allCollaterals;
}
/**
*
*/
async function getAllBorrowers(overseer: AnchorOverseer, chainOperator: ChainOperator): Promise<Map<string, number>> {
const borrowerList: Map<string, number> = new Map();

for (const asset of overseer.whitelist.elems) {
const borrowersResponse: BorrowersResponse = await chainOperator.queryContractSmart(asset.custody_contract, {
borrowers: { limit: 30 },
});
const borrowersCustody = borrowersResponse.borrowers;

let borrowers = borrowersResponse.borrowers;
while (borrowers.length === 30) {
const msg = {
borrowers: { limit: 30, start_after: borrowers[borrowers.length - 1].borrower },
};
const borrowersResponseNext: BorrowersResponse = await chainOperator.queryContractSmart(
asset.custody_contract,
msg,
);
borrowersCustody.push(...borrowersResponseNext.borrowers);
borrowers = borrowersResponseNext.borrowers;
}
processBorrowers(borrowerList, borrowersCustody);
}
return borrowerList;
}
/**
*
*/
function processBorrowers(borrowerList: Map<string, number>, borrowers: BorrowersResponse["borrowers"]) {
for (const borrow of borrowers) {
const existingBorrow = borrowerList.get(borrow.borrower);
if (existingBorrow === undefined) {
borrowerList.set(borrow.borrower, Number(borrow.balance) - Number(borrow.spendable));
} else {
borrowerList.set(borrow.borrower, existingBorrow + Number(borrow.balance) - Number(borrow.spendable));
}
}
}

interface BorrowersResponse {
borrowers: Array<{
borrower: string;
balance: string;
spendable: string;
}>;
}
interface CollateralResponse {
all_collaterals: Array<{
borrower: string;
collaterals: Array<[string, string]>;
}>;
}

/**
*
*/
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
125 changes: 125 additions & 0 deletions src/chains/defaults/queries/initOverseers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { ChainOperator } from "../../../core/chainOperator/chainoperator";
import { AnchorOverseer, AnchorWhitelist, setBorrowLimits } from "../../../core/types/base/overseer";
import { setLoans } from "./getLoans";

/**
*
*/
export async function initLiquidationOverseers(
overseerAddresses: string | Array<string>,
chainOperator: ChainOperator,
) {
// export async function getliqudationinfos(overseer: Array<string>, operator: ChainOperator): Promise<Liquidate> {
let overseerAddresssArray: Array<string>;
if (typeof overseerAddresses === "string") {
overseerAddresssArray = [overseerAddresses];
} else {
overseerAddresssArray = overseerAddresses;
}

const overseers: Array<AnchorOverseer> = [];
for (const overseerAddress of overseerAddresssArray) {
const overseer: AnchorOverseer | undefined = await initLiquidationOverseer(overseerAddress, chainOperator);
if (!overseer) {
console.log("Overseer cannot be found: ", overseerAddress);
process.exit(1);
}
overseer.priceFeed = await initPriceFeeds(overseer, chainOperator);
await setLoans(overseer, chainOperator);
setBorrowLimits(overseer);
overseers.push(overseer);
}
return overseers;
}

/**
*
*/
async function initLiquidationOverseer(
overseer: string,
chainOperator: ChainOperator,
): Promise<AnchorOverseer | undefined> {
const overseerConfig: AnchorOverseerConfig = await chainOperator.queryContractSmart(overseer, {
config: { limit: 100 },
});
await delay(500);
if (overseerConfig) {
const whitelist: AnchorWhitelist = await chainOperator.queryContractSmart(overseer, {
whitelist: { limit: 100 },
});
await delay(200);
const priceFeeders: Set<string> = new Set();

for (const whitelisted of whitelist.elems) {
const feeder: AnchorAssetFeeder = await chainOperator.queryContractSmart(overseerConfig.oracle_contract, {
feeder: { asset: whitelisted.collateral_token },
});
await delay(200);
priceFeeders.add(feeder.feeder);
}
const anchorOverseer: AnchorOverseer = {
overseerAddress: overseer,
oracleAddress: overseerConfig.oracle_contract,
marketAddress: overseerConfig.market_contract,
liquidatorAddress: overseerConfig.liquidation_contract,
priceFeeders: Array.from(priceFeeders),
priceFeed: new Map(),
whitelist: whitelist,
loans: {},
stableDenom: overseerConfig.stable_denom,
};
return anchorOverseer;
}
console.log("cannot find overseer config for: ", overseer);
return undefined;
}
/**
*
*/
async function initPriceFeeds(overseer: AnchorOverseer, chainOperator: ChainOperator) {
const priceFeed: (typeof overseer)["priceFeed"] = new Map();
const priceFeedRes: PriceFeedResult = await chainOperator.queryContractSmart(overseer.oracleAddress, {
prices: { limit: 1000 },
});
for (const price of priceFeedRes.prices) {
priceFeed.set(price.asset, +price.price);
}
return priceFeed;
}
interface AnchorOverseerConfig {
owner_addr: string;
oracle_contract: string;
market_contract: string;
liquidation_contract: string;
borrow_reserves_bucket_contract: string;
threshold_deposit_rate: string;
target_deposit_rate: string;
buffer_distribution_factor: string;
stable_denom: string;
epoch_period: number;
price_timeframe: number;
dyn_rate_epoch: number;
dyn_rate_maxchange: string;
dyn_rate_yr_increase_expectation: string;
dyn_rate_min: string;
dyn_rate_max: string;
}

interface PriceFeedResult {
prices: Array<{
asset: string;
price: string;
last_updated_time: 1686224223;
}>;
}

interface AnchorAssetFeeder {
asset: string;
feeder: string;
}
/**
*
*/
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
23 changes: 20 additions & 3 deletions src/core/arbitrage/arbitrage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Asset } from "../types/base/asset";
import { BotConfig } from "../types/base/botConfig";
import { DexConfig, LiquidationConfig } from "../types/base/configs";
import { AnchorOverseer } from "../types/base/overseer";
import { Path } from "../types/base/path";
import { getOptimalTrade } from "./optimizers/analyticalOptimizer";

Expand All @@ -11,7 +12,7 @@ export interface OptimalTrade {
/**
*
*/
export function trySomeArb(paths: Array<Path>, botConfig: BotConfig): OptimalTrade | undefined {
export function trySomeArb(paths: Array<Path>, botConfig: DexConfig): OptimalTrade | undefined {
const optimalTrade: OptimalTrade | undefined = getOptimalTrade(paths, botConfig.offerAssetInfo);

if (!optimalTrade) {
Expand All @@ -28,7 +29,23 @@ export function trySomeArb(paths: Array<Path>, botConfig: BotConfig): OptimalTra
/**
*
*/
function isAboveThreshold(botConfig: BotConfig, optimalTrade: OptimalTrade): boolean {
export function tryLiquidationArb(
overseers: Array<AnchorOverseer>,
botConfig: LiquidationConfig,
): [AnchorOverseer, string] | undefined {
for (const overseer of overseers) {
for (const loan of Object.entries(overseer.loans)) {
if (loan[1].riskRatio >= 1) {
return [overseer, loan[0]];
}
}
}
}

/**
*
*/
function isAboveThreshold(botConfig: DexConfig, optimalTrade: OptimalTrade): boolean {
// We dont know the number of message required to execute the trade, so the profit threshold will be set to the most conservative value: nr_of_pools*2-1
const profitThreshold =
botConfig.profitThresholds.get((optimalTrade.path.pools.length - 1) * 2 + 1) ??
Expand Down
3 changes: 1 addition & 2 deletions src/core/chainOperator/chainAdapters/cosmjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { HttpBatchClient, HttpClient } from "@cosmjs/tendermint-rpc/build/rpccli
import { SkipBundleClient } from "@skip-mev/skipjs";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";

import { BotConfig } from "../../types/base/botConfig";
import { BotConfig } from "../../types/base/configs";
import { Mempool } from "../../types/base/mempool";
import { ChainOperatorInterface, TxResponse } from "../chainOperatorInterface";

Expand Down Expand Up @@ -105,7 +105,6 @@ class CosmjsAdapter implements ChainOperatorInterface {
this._tmClient = await Tendermint34Client.create(this._httpClient);
this._wasmQueryClient = QueryClient.withExtensions(this._tmClient, setupWasmExtension, setupAuthExtension);
this._signingCWClient = await SigningCosmWasmClient.connectWithSigner(rpcUrl, this._signer, {
prefix: this._chainPrefix,
gasPrice: GasPrice.fromString(this._gasPrice + this._denom),
});
}
Expand Down
8 changes: 4 additions & 4 deletions src/core/chainOperator/chainAdapters/injective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { MsgSend as CosmJSMsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { MsgExecuteContract as CosmJSMsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx";

import { BotConfig } from "../../types/base/botConfig";
import { BotConfig } from "../../types/base/configs";
import { Mempool } from "../../types/base/mempool";
import { ChainOperatorInterface, TxResponse } from "../chainOperatorInterface";
/**
Expand Down Expand Up @@ -227,16 +227,16 @@ class InjectiveAdapter implements ChainOperatorInterface {
fee: fee,
memo: memo,
chainId: this._chainId,
message: preppedMsgs.map((msg) => msg.toDirectSign()),
message: preppedMsgs,
pubKey: this._publicKey.toBase64(),
sequence: this._sequence,
accountNumber: this._accountNumber,
});
const signature = await this._privateKey.sign(Buffer.from(signBytes));

txRaw.setSignaturesList([signature]);
txRaw.signatures = [signature];
const cosmTxRaw = {
signatures: txRaw.getSignaturesList_asU8(),
signatures: txRaw.signatures,
bodyBytes: bodyBytes,
authInfoBytes: authInfoBytes,
};
Expand Down
Loading

0 comments on commit c99c06b

Please sign in to comment.