Skip to content

Instantly share code, notes, and snippets.

@PaulRBerg
Last active January 15, 2021 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PaulRBerg/f5ccb309200416b941237cba3eebe8a7 to your computer and use it in GitHub Desktop.
Save PaulRBerg/f5ccb309200416b941237cba3eebe8a7 to your computer and use it in GitHub Desktop.
Source code for StackOverflow question
//! Liquidator Module
//!
//! This module is responsible for triggering liquidations.
use crate::{escalator::GeometricGasPrice, vault::Vault, HifiResult};
use ethers::{
core::abi::{self, Tokenize},
prelude::*,
};
use hifi_liquidator_bindings::{FyToken, UniswapV2Pair};
use std::{collections::HashMap, sync::Arc, time::Instant};
use tracing::{debug, info, trace};
pub struct Liquidator<'a, M, P> {
fy_token: FyToken<M>,
gas_escalator: GeometricGasPrice,
hifi_flash_swap: Address,
min_profit: U256,
multicall: Multicall<M>,
pending_liquidations: HashMap<Address, PendingTransaction<'a, P>>,
uniswap_v2_pair: UniswapV2Pair<M>,
}
impl<'a, M, P> Liquidator<'a, M, P>
where
M: Middleware + 'a,
P: JsonRpcClient + 'a,
{
/// Constructor
pub async fn new(
client: Arc<M>,
fy_token: Address,
hifi_flash_swap: Address,
gas_escalator: GeometricGasPrice,
min_profit: U256,
multicall: Option<Address>,
uniswap_v2_pair: Address,
) -> Liquidator<'a, M, P> {
let multicall = Multicall::new(client.clone(), multicall)
.await
.expect("Could not initialize Multicall");
Self {
fy_token: FyToken::new(fy_token, client.clone()),
hifi_flash_swap,
gas_escalator,
min_profit,
multicall,
pending_liquidations: HashMap::new(),
uniswap_v2_pair: UniswapV2Pair::new(uniswap_v2_pair, client.clone()),
}
}
/// Checks if any transactions which have been submitted are mined and removes them if they
/// were successful. Otherwise, it bumps their gas price.
pub async fn remove_or_bump(&mut self) -> HifiResult<(), M> {
let now = Instant::now();
// Check all pending liquidations.
self.remove_or_bump_inner(now).await?;
Ok(())
}
async fn remove_or_bump_inner(&mut self, now: Instant) -> HifiResult<(), M> {
let client = self.fy_token.client();
for (account, pending_tx) in self.pending_liquidations.iter_mut() {}
Ok(())
}
/// Triggers liquidations for any vulnerable positions which were fetched from the BalanceSheet.
/// It does this with capital sourced from Uniswap V2.
pub async fn trigger_liquidations(
&'a mut self,
gas_price: U256,
vaults: impl Iterator<Item = (&Address, &Vault)>,
) -> HifiResult<(), M> {
debug!("Checking for under-collateralized positions...");
let now = Instant::now();
for (borrower, vault) in vaults {
// Only iterate over (fyToken, borrower) pairs that do not have pending liquidations.
if let Some(pending_tx) = self.pending_liquidations.get(&borrower) {
trace!(pending_tx = ?pending_tx, borrower = ?borrower, "Liquidation tx not confirmed yet");
continue;
}
// Skip vaults that have no outstanding debt.
if vault.debt.is_zero() {
continue;
}
if vault.is_underwater {
info!(
borrower = ?borrower,
debt = %vault.debt,
"Found under-collateralized borrower. Triggering liquidation.",
);
// Craft the HifiFlashSwap contract's arguments.
let args = abi::encode(&(*borrower, self.min_profit).into_tokens());
// Call the Uniswap `swap` function which will optimistically let us borrow the underlying and
// make a callback to the HifiFlashSwap contract, which will execute the liquidation.
// TODO: convert `debt` from fyUSDC to USDC decimals.
let contract_call = self
.uniswap_v2_pair
.swap(0.into(), vault.debt.into(), self.hifi_flash_swap, args)
.gas_price(gas_price)
.block(BlockNumber::Pending);
let pending_tx: PendingTransaction<'_, <M as Middleware>::Provider> = contract_call.send().await?;
self.pending_liquidations.entry(*borrower).or_insert(pending_tx);
// trace!(pending_tx = ?pending_tx, borrower = ?borrower, "Submitted liquidation");
// self.pending_liquidations.entry(*borrower).or_insert(pending_tx);
}
}
Ok(())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment