Skip to content

Commit

Permalink
Fix Onchain FP due to incorrect liquidation logic (#240)
Browse files Browse the repository at this point in the history
* fix #227

* more resilient

* real liquidation
  • Loading branch information
shouc authored Oct 10, 2023
1 parent e1acd26 commit 8f65555
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 93 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ sui_support = [
"dep:sui-types",
]
debug = []
flashloan_debug = []

[dependencies]
bytes = { version = "1.2.1", features = ["serde"] }
Expand Down
6 changes: 3 additions & 3 deletions src/evm/onchain/flashloan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,9 @@ where
S: HasCaller<EVMAddress>,
{
// if simply static call, we dont care
if unsafe { IS_FAST_CALL_STATIC } {
return;
}
// if unsafe { IS_FAST_CALL_STATIC } {
// return;
// }


match *interp.instruction_pointer {
Expand Down
194 changes: 106 additions & 88 deletions src/evm/oracles/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,107 +93,125 @@ impl Oracle<EVMState, EVMAddress, Bytecode, Bytes, EVMAddress, EVMU256, Vec<u8>,

#[cfg(feature = "flashloan_v2")]
fn oracle(&self, ctx: &mut EVMOracleCtx<'_>, _stage: u64) -> Vec<u64> {
let prev_reserves = ctx
.fuzz_state
.get_execution_result()
.new_state
.state
.flashloan_data
.prev_reserves
.clone();
let unliquidated_tokens = ctx
.fuzz_state
.get_execution_result()
.new_state
.state
.flashloan_data
.unliquidated_tokens
.clone();

let mut new_reserves = prev_reserves.clone();

for (pair_address, reserve) in &self.pair_producer.deref().borrow().reserves {
new_reserves.insert(*pair_address, *reserve);
}
use crate::evm::{uniswap::generate_uniswap_router_sell, types::EVMFuzzExecutor};

let mut liquidations_owed = Vec::new();
let liquidation_percent = ctx.input.get_liquidation_percent();
if liquidation_percent == 0 {
return vec![];
}
let liquidation_percent = EVMU256::from(liquidation_percent);
let mut liquidations_earned = Vec::new();

for ((caller, token), (prev_balance, new_balance)) in self.erc20_producer.deref().borrow().balances.iter() {
let token_info = self.known_tokens.get(token).expect("Token not found");
// println!("Balance: {} -> {} for {:?} @ {:?}\n", prev_balance, new_balance, caller, token);

if prev_balance > new_balance {
liquidations_owed.push((token_info, prev_balance - new_balance));
} else if prev_balance < new_balance {
let to_liquidate = (new_balance - prev_balance)
* EVMU256::from(ctx.input.get_liquidation_percent())
/ EVMU256::from(10);

let unliquidated = new_balance - prev_balance - to_liquidate;
if to_liquidate > EVMU256::from(0) {
liquidations_earned.push((token_info, to_liquidate));
}
// insert if not exists or increase if exists
if unliquidated > EVMU256::from(0) {
let entry = ctx
.fuzz_state
.get_execution_result_mut()
.new_state
.state
.flashloan_data
.unliquidated_tokens
.entry(*token)
.or_insert(EVMU256::from(0));
*entry += unliquidated;
}

#[cfg(feature = "flashloan_debug")]
println!("Balance: {} -> {} for {:?} @ {:?}", prev_balance, new_balance, caller, token);


if *new_balance > EVMU256::ZERO {
let liq_amount = *new_balance * liquidation_percent / EVMU256::from(10);
liquidations_earned.push((*caller, token_info, liq_amount));
}
}
let exec_res = ctx.fuzz_state.get_execution_result_mut();
// let exec_res = ctx.fuzz_state.get_execution_result_mut();

// let (liquidation_owed, _) =
// liquidate_all_token(liquidations_owed.clone(), prev_reserves.clone());


// #[cfg(feature = "flashloan_debug")]
// println!("Liquidation owed: {}", liquidation_owed);
// assert!(liquidation_owed == EVMU256::ZERO);

// unliquidated_tokens.iter().for_each(|(token, amount)| {
// let token_info = self.known_tokens.get(token).expect("Token not found");
// let liq = *amount * EVMU256::from(ctx.input.get_liquidation_percent()) / EVMU256::from(10);
// if liq != EVMU256::from(0) {
// liquidations_earned.push((token_info, liq));
// exec_res
// .new_state
// .state
// .flashloan_data
// .unliquidated_tokens
// .insert(*token, *amount - liq);
// }
// });

// let (liquidation_earned, adjusted_reserves) =
// liquidate_all_token(liquidations_earned, new_reserves);

// todo: fix this
let path_idx = ctx.input.get_randomness()[0] as usize;

let mut liquidation_txs = vec![];

// println!("Liquidations earned: {:?}", liquidations_earned);
for (caller, token_info, amount) in liquidations_earned {
let txs = generate_uniswap_router_sell(
&token_info, path_idx, amount, ctx.fuzz_state.callers_pool[0]);
if txs.is_none() {
continue;
}

let (liquidation_owed, _) =
liquidate_all_token(liquidations_owed.clone(), prev_reserves.clone());
liquidation_txs.extend(txs.unwrap().iter().map(
|(abi, _, addr)| {
(caller, *addr, Bytes::from(abi.get_bytes()))
}
));
}
// println!(
// "Liquidation txs: {:?}",
// liquidation_txs
// );

unliquidated_tokens.iter().for_each(|(token, amount)| {
let token_info = self.known_tokens.get(token).expect("Token not found");
let liq = *amount * EVMU256::from(ctx.input.get_liquidation_percent()) / EVMU256::from(10);
if liq != EVMU256::from(0) {
liquidations_earned.push((token_info, liq));
exec_res
.new_state
.state
.flashloan_data
.unliquidated_tokens
.insert(*token, *amount - liq);
}
});

let (liquidation_earned, adjusted_reserves) =
liquidate_all_token(liquidations_earned, new_reserves);
// println!("Earned before liquidation: {:?}", ctx.fuzz_state.get_execution_result().new_state.state.flashloan_data.earned);
let (out, state) = ctx.call_post_batch_dyn(&liquidation_txs);
// println!("results: {:?}", out);
// println!("result state: {:?}", state.flashloan_data);
ctx.fuzz_state.get_execution_result_mut().new_state.state = state;

// println!("Liquidation earned: {}", liquidation_earned);
exec_res.new_state.state.flashloan_data.prev_reserves = adjusted_reserves;
// println!("Earned after liquidation: {:?}", ctx.fuzz_state.get_execution_result().new_state.state.flashloan_data.earned);

if liquidation_earned > liquidation_owed {
exec_res.new_state.state.flashloan_data.earned +=
EVMU512::from(liquidation_earned - liquidation_owed);
} else {
exec_res.new_state.state.flashloan_data.owed +=
EVMU512::from(liquidation_owed - liquidation_earned);
}
// #[cfg(feature = "flashloan_debug")]
// println!("Liquidation earned: {}", liquidation_earned);

exec_res
.new_state
.state
.flashloan_data
.oracle_recheck_balance
.clear();
exec_res
.new_state
.state
.flashloan_data
.oracle_recheck_reserve
.clear();
// #[cfg(feature = "flashloan_debug")]
// println!("Adjusted reserves: {:?}", adjusted_reserves);

// update_reserve_on_state(&mut exec_res.new_state.state, &adjusted_reserves);

// #[cfg(feature = "flashloan_debug")]
// for (k, v) in &adjusted_reserves {
// println!("Adjusted reserve({:?}): {:?}", k, v);
// println!("Pair state: {:?}", exec_res.new_state.state.state.get(k).unwrap());
// }

// exec_res.new_state.state.flashloan_data.prev_reserves = adjusted_reserves;


// if liquidation_earned > liquidation_owed {
// exec_res.new_state.state.flashloan_data.earned +=
// EVMU512::from(liquidation_earned - liquidation_owed);
// } else {
// exec_res.new_state.state.flashloan_data.owed +=
// EVMU512::from(liquidation_owed - liquidation_earned);
// }

let exec_res = ctx.fuzz_state.get_execution_result();
// exec_res
// .new_state
// .state
// .flashloan_data
// .oracle_recheck_balance
// .clear();
// exec_res
// .new_state
// .state
// .flashloan_data
// .oracle_recheck_reserve
// .clear();

if exec_res.new_state.state.flashloan_data.earned
> exec_res.new_state.state.flashloan_data.owed
Expand Down
Loading

0 comments on commit 8f65555

Please sign in to comment.