Skip to content

Commit

Permalink
feat(liquidator): use close factor
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-savu committed Mar 9, 2023
1 parent fdc8ab9 commit 79700fb
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 74 deletions.
95 changes: 49 additions & 46 deletions bots/bridge-tester/test/_initialization/initialize.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ApiPromise, Keyring } from "@polkadot/api";
import { KeyringPair } from "@polkadot/keyring/types";
import { assert } from "chai";
import BN from "bn.js";

import {
BitcoinCoreClient,
setNumericStorage,
initializeStableConfirmations,
issueSingle,
InterBtcApi,
DefaultInterBtcApi,
Expand All @@ -24,19 +23,21 @@ import {
DEFAULT_BITCOIN_CORE_PASSWORD,
DEFAULT_BITCOIN_CORE_PORT,
DEFAULT_BITCOIN_CORE_WALLET,
DEFAULT_SUDO_URI,
DEFAULT_USER_1_URI,
} from "../config";

describe.skip("Initialize parachain state", () => {
let api: ApiPromise;
let bitcoinCoreClient: BitcoinCoreClient;
let keyring: Keyring;
let interBtcAPI: InterBtcApi;
let userInterBtcApi: InterBtcApi;
let wrappedCurrency: WrappedCurrency;
let collateralCurrency: CollateralCurrencyExt;
let vault_id: InterbtcPrimitivesVaultId;

let alice: KeyringPair;
let bob: KeyringPair;
let sudoAccount: KeyringPair;
let user1Account: KeyringPair;
let charlie_stash: KeyringPair;

function sleep(ms: number): Promise<void> {
Expand All @@ -47,9 +48,9 @@ describe.skip("Initialize parachain state", () => {
api = await createSubstrateAPI(DEFAULT_PARACHAIN_ENDPOINT);
keyring = new Keyring({ type: "sr25519" });
// Alice is also the root account
alice = keyring.addFromUri("//Alice");
bob = keyring.addFromUri("//Bob");
user1Account = keyring.addFromUri(DEFAULT_USER_1_URI);
charlie_stash = keyring.addFromUri("//Charlie//stash");
sudoAccount = keyring.addFromUri(DEFAULT_SUDO_URI);

bitcoinCoreClient = new BitcoinCoreClient(
DEFAULT_BITCOIN_CORE_NETWORK,
Expand All @@ -59,9 +60,9 @@ describe.skip("Initialize parachain state", () => {
DEFAULT_BITCOIN_CORE_PORT,
DEFAULT_BITCOIN_CORE_WALLET
);
interBtcAPI = new DefaultInterBtcApi(api, "regtest", alice);
wrappedCurrency = interBtcAPI.getWrappedCurrency();
collateralCurrency = interBtcAPI.api.consts.currency.getRelayChainCurrencyId;
userInterBtcApi = new DefaultInterBtcApi(api, "regtest", user1Account);
wrappedCurrency = userInterBtcApi.getWrappedCurrency();
collateralCurrency = userInterBtcApi.api.consts.currency.getRelayChainCurrencyId;
vault_id = newVaultId(
api,
charlie_stash.address,
Expand All @@ -76,47 +77,49 @@ describe.skip("Initialize parachain state", () => {
api.disconnect();
});

it("should set the stable confirmations and ready the Btc Relay", async () => {
// Speed up the process by only requiring 0 parachain and 0 bitcoin confirmations
const stableBitcoinConfirmationsToSet = 0;
const stableParachainConfirmationsToSet = 0;
await setNumericStorage(
api,
"BTCRelay",
"StableBitcoinConfirmations",
new BN(stableBitcoinConfirmationsToSet),
alice
);
await setNumericStorage(
api,
"BTCRelay",
"StableParachainConfirmations",
new BN(stableParachainConfirmationsToSet),
alice
);
const stableBitcoinConfirmations =
await interBtcAPI.btcRelay.getStableBitcoinConfirmations();
assert.equal(
stableBitcoinConfirmationsToSet,
stableBitcoinConfirmations,
"Setting the Bitcoin confirmations failed"
);
const stableParachainConfirmations =
await interBtcAPI.btcRelay.getStableParachainConfirmations();
assert.equal(
stableParachainConfirmationsToSet,
stableParachainConfirmations,
"Setting the Parachain confirmations failed"
);
await bitcoinCoreClient.mineBlocks(3);
});
it("should set the stable confirmations and ready the BTC-Relay", async () => {
// Speed up the process by only requiring 0 parachain and 0 bitcoin confirmations
const stableBitcoinConfirmationsToSet = 0;
const stableParachainConfirmationsToSet = 0;
let [stableBitcoinConfirmations, stableParachainConfirmations] = await Promise.all([
userInterBtcApi.btcRelay.getStableBitcoinConfirmations(),
userInterBtcApi.btcRelay.getStableParachainConfirmations(),
]);

if (stableBitcoinConfirmations != 0 || stableParachainConfirmations != 0) {
await initializeStableConfirmations(
api,
{
bitcoinConfirmations: stableBitcoinConfirmationsToSet,
parachainConfirmations: stableParachainConfirmationsToSet,
},
sudoAccount,
bitcoinCoreClient
);
[stableBitcoinConfirmations, stableParachainConfirmations] = await Promise.all([
userInterBtcApi.btcRelay.getStableBitcoinConfirmations(),
userInterBtcApi.btcRelay.getStableParachainConfirmations(),
]);
}
assert.equal(
stableBitcoinConfirmationsToSet,
stableBitcoinConfirmations,
"Setting the Bitcoin confirmations failed"
);
assert.equal(
stableParachainConfirmationsToSet,
stableParachainConfirmations,
"Setting the Parachain confirmations failed"
);
});


it("should issue 0.1 InterBTC", async () => {
const wrappedToIssue = newMonetaryAmount(0.00007, wrappedCurrency, true);
await issueSingle(
interBtcAPI,
userInterBtcApi,
bitcoinCoreClient,
alice,
user1Account,
wrappedToIssue,
vault_id
);
Expand Down
2 changes: 2 additions & 0 deletions bots/bridge-tester/test/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export const DEFAULT_BITCOIN_CORE_PASSWORD = "rpcpassword";
export const DEFAULT_BITCOIN_CORE_PORT = "18443";
export const DEFAULT_BITCOIN_CORE_WALLET = "Alice";
export const DEFAULT_ISSUE_TOP_UP_AMOUNT = "0.1";
export const DEFAULT_SUDO_URI = "//Alice";
export const DEFAULT_USER_1_URI = "//Dave";
57 changes: 31 additions & 26 deletions bots/lending-liquidator/src/liquidate.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
import { ChainBalance, CollateralPosition, CurrencyExt, InterBtcApi, newAccountId, newMonetaryAmount, UndercollateralizedPosition, addressOrPairAsAccountId, DefaultTransactionAPI } from "@interlay/interbtc-api";
import { ChainBalance, CollateralPosition, CurrencyExt, InterBtcApi, LoansMarket, newMonetaryAmount, UndercollateralizedPosition, addressOrPairAsAccountId, DefaultTransactionAPI, decodePermill } from "@interlay/interbtc-api";
import { Currency, ExchangeRate, MonetaryAmount } from "@interlay/monetary-js";
import { AccountId } from "@polkadot/types/interfaces";
import { APPROX_BLOCK_TIME_MS } from "./consts";

type CollateralAndValue = {
collateral: CollateralPosition,
collateral: MonetaryAmount<Currency>,
referenceValue: MonetaryAmount<Currency>
}

function referencePrice(balance: MonetaryAmount<CurrencyExt>, rate: ExchangeRate<Currency, CurrencyExt> | undefined): MonetaryAmount<Currency> {
function referenceValue(balance: MonetaryAmount<CurrencyExt>, rate: ExchangeRate<Currency, CurrencyExt> | undefined): MonetaryAmount<Currency> {
if (!rate) {
return new MonetaryAmount(balance.currency, 0);
}
// Convert to the reference currency (BTC)
return rate.toBase(balance)
}

function findHighestValueCollateral(positions: CollateralPosition[], rates: Map<Currency, ExchangeRate<Currency, CurrencyExt>>): CollateralAndValue | undefined {
function findHighestValueCollateral(
positions: CollateralPosition[],
rates: Map<Currency, ExchangeRate<Currency, CurrencyExt>>
): CollateralAndValue | undefined {
// It should be impossible to have no collateral currency locked, but just in case
if (positions.length == 0) {
return undefined;
}
const defaultValue = {
collateral: positions[0],
referenceValue: referencePrice(positions[0].amount, rates.get(positions[0].amount.currency))
collateral: positions[0].amount,
referenceValue: referenceValue(
newMonetaryAmount(0, positions[0].amount.currency),
rates.get(positions[0].amount.currency)
)
};
return positions.reduce(
(previous, current) => {
const currentReferencePrice = referencePrice(current.amount, rates.get(current.amount.currency));
if (previous.referenceValue.gt(currentReferencePrice)) {
const liquidatableValue = referenceValue(current.amount, rates.get(current.amount.currency));
if (previous.referenceValue.gt(liquidatableValue)) {
return previous;
}
return {
collateral: current,
referenceValue: currentReferencePrice
collateral: current.amount,
referenceValue: liquidatableValue
}
},
defaultValue
Expand All @@ -44,7 +50,8 @@ function liquidationStrategy(
interBtcApi: InterBtcApi,
liquidatorBalance: Map<Currency, ChainBalance>,
oracleRates: Map<Currency, ExchangeRate<Currency, CurrencyExt>>,
undercollateralizedBorrowers: UndercollateralizedPosition[]
undercollateralizedBorrowers: UndercollateralizedPosition[],
markets: Map<Currency, LoansMarket>
): [MonetaryAmount<CurrencyExt>, CurrencyExt, AccountId] | undefined {
let maxRepayableLoan = newMonetaryAmount(0, interBtcApi.getWrappedCurrency());
let result: [MonetaryAmount<CurrencyExt>, CurrencyExt, AccountId] | undefined;
Expand All @@ -55,19 +62,17 @@ function liquidationStrategy(
}
position.borrowPositions.forEach((loan) => {
const totalDebt = loan.amount.add(loan.accumulatedDebt);
console.log("debt currency", totalDebt.currency);
console.log("highest value collateral ", highestValueCollateral.collateral.amount.currency.ticker, highestValueCollateral.referenceValue.toHuman());
if (liquidatorBalance.has(totalDebt.currency)) {
const loansMarket = markets.get(totalDebt.currency) as LoansMarket;
const closeFactor = decodePermill(loansMarket.closeFactor);
const balance = liquidatorBalance.get(totalDebt.currency) as ChainBalance;
console.log("free bot balance ", balance.free.toHuman());
console.log("borrower debt", totalDebt.toHuman());
const rate = oracleRates.get(totalDebt.currency) as ExchangeRate<Currency, CurrencyExt>;
const repayableAmount = totalDebt.min(balance.free);
// TODO: Take close factor into account when consider the collateral's reference value
const referenceRepayable = referencePrice(repayableAmount, rate).min(highestValueCollateral.referenceValue);
// Can only repay a fraction of the total debt, defined by the `closeFactor`
const repayableAmount = totalDebt.mul(closeFactor).min(balance.free);
const referenceRepayable = referenceValue(repayableAmount, rate).min(highestValueCollateral.referenceValue);
if (referenceRepayable.gt(maxRepayableLoan)) {
maxRepayableLoan = referenceRepayable;
result = [repayableAmount, highestValueCollateral.collateral.amount.currency, position.accountId];
result = [repayableAmount, highestValueCollateral.collateral.currency, position.accountId];
}
}
})
Expand All @@ -90,12 +95,12 @@ export async function startLiquidator(interBtcApi: InterBtcApi): Promise<void> {
console.log(`Scanning block: #${header.number}`);
const liquidatorBalance: Map<Currency, ChainBalance> = new Map();
const oracleRates: Map<Currency, ExchangeRate<Currency, CurrencyExt>> = new Map();
console.log("awaiting big promise");
try {
const [_balancesPromise, _oraclePromise, undercollateralizedBorrowers, foreignAssets] = await Promise.all([
const [_balancesPromise, _oraclePromise, undercollateralizedBorrowers, marketsArray, foreignAssets] = await Promise.all([
Promise.all([...chainAssets].map((asset) => interBtcApi.tokens.balance(asset, accountId).then((balance) => liquidatorBalance.set(asset, balance)))),
Promise.all([...chainAssets].map((asset) => interBtcApi.oracle.getExchangeRate(asset).then((rate) => oracleRates.set(asset, rate)))),
interBtcApi.loans.getUndercollateralizedBorrowers(),
interBtcApi.loans.getLoansMarkets(),
interBtcApi.assetRegistry.getForeignAssets()
]);

Expand All @@ -104,7 +109,8 @@ export async function startLiquidator(interBtcApi: InterBtcApi): Promise<void> {
interBtcApi,
liquidatorBalance,
oracleRates,
undercollateralizedBorrowers
undercollateralizedBorrowers,
new Map(marketsArray)
) as [MonetaryAmount<CurrencyExt>, CurrencyExt, AccountId];
if (potentialLiquidation) {
const [amountToRepay, collateralToLiquidate, borrower] = potentialLiquidation;
Expand All @@ -115,12 +121,11 @@ export async function startLiquidator(interBtcApi: InterBtcApi): Promise<void> {
interBtcApi.loans.liquidateBorrowPosition(borrower, amountToRepay.currency, amountToRepay, collateralToLiquidate)
]);
}


// Add any new foreign assets to `chainAssets`
chainAssets = new Set([...Array.from(chainAssets), ...foreignAssets]);
} catch (error) {
console.log("found an error: ", error);
}

// Add any new foreign assets to `chainAssets`
chainAssets = new Set([...Array.from(chainAssets), ...foreignAssets]);
});
}
2 changes: 1 addition & 1 deletion bots/lending-liquidator/test/integration/liquidate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe("liquidate", () => {
const liquidationEventFoundPromise = DefaultTransactionAPI.waitForEvent(sudoInterBtcAPI.api, sudoInterBtcAPI.api.events.loans.LiquidatedBorrow, approx10Blocks);

// crash the collateral exchange rate
const newExchangeRate = "0x00000000000000000000001000000000";
const newExchangeRate = "0x00000000000000000000100000000000";
await setExchangeRate(sudoInterBtcAPI, depositAmount.currency, newExchangeRate);

// expect liquidation event to happen
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@ services:
- --bitcoin-relay-start-height=1
environment: *client-env
volumes:
- ./docker/vault_3-keyfile.json:/keyfile.json
- ./docker/vault_3-keyfile.json:/keyfile.json

0 comments on commit 79700fb

Please sign in to comment.