From a612eacb22cfa2127c9ffb8fcfc653773f33da3b Mon Sep 17 00:00:00 2001 From: heytdep Date: Mon, 9 Dec 2024 19:34:57 +0100 Subject: [PATCH] (untested) add flash lending funcitonality --- Cargo.lock | 9 +++++ Cargo.toml | 4 ++ pool/Cargo.toml | 2 +- pool/src/pool/actions.rs | 83 ++++++++++++++++++++++++++++++++++++---- pool/src/pool/submit.rs | 20 ++++++++++ 5 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3619507..aebf465 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -775,6 +775,14 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "moderc3156" +version = "0.1.0" +source = "git+https://github.com/xycloo/xycloans?rev=d9a7ae1#d9a7ae163e6c7120000532bf4b15b6a96b3a82bc" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -858,6 +866,7 @@ dependencies = [ "cast", "emitter", "mock-pool-factory", + "moderc3156", "sep-40-oracle", "sep-41-token", "soroban-fixed-point-math", diff --git a/Cargo.toml b/Cargo.toml index 2165c79..22525d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,7 @@ version = "1.2.0" [workspace.dependencies.sep-41-token] version = "1.2.0" + +[workspace.dependencies.moderc3156] +git = "https://github.com/xycloo/xycloans" +rev = "d9a7ae1" diff --git a/pool/Cargo.toml b/pool/Cargo.toml index a481113..b84bdb4 100644 --- a/pool/Cargo.toml +++ b/pool/Cargo.toml @@ -20,7 +20,7 @@ soroban-fixed-point-math = { workspace = true } cast = { workspace = true } sep-40-oracle = { workspace = true } sep-41-token = { workspace = true} - +moderc3156 = { workspace = true} [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/pool/src/pool/actions.rs b/pool/src/pool/actions.rs index f55ebe4..02adf34 100644 --- a/pool/src/pool/actions.rs +++ b/pool/src/pool/actions.rs @@ -29,6 +29,7 @@ pub enum RequestType { FillBadDebtAuction = 7, FillInterestAuction = 8, DeleteLiquidationAuction = 9, + FlashBorrow = 10, } impl RequestType { @@ -48,6 +49,7 @@ impl RequestType { 7 => RequestType::FillBadDebtAuction, 8 => RequestType::FillInterestAuction, 9 => RequestType::DeleteLiquidationAuction, + 10 => RequestType::FlashBorrow, _ => panic_with_error!(e, PoolError::BadRequest), } } @@ -57,6 +59,7 @@ impl RequestType { pub struct Actions { pub spender_transfer: Map, pub pool_transfer: Map, + pub flash_borrow: Vec<(Address, i128)>, // we expect flash borrows not to be dynamic. } impl Actions { @@ -65,6 +68,7 @@ impl Actions { Actions { spender_transfer: Map::new(e), pool_transfer: Map::new(e), + flash_borrow: Vec::new(e), } } @@ -83,6 +87,10 @@ impl Actions { amount + self.pool_transfer.get(asset.clone()).unwrap_or(0), ); } + + pub fn add_flash_borrow(&mut self, asset: &Address, amount: i128) { + self.flash_borrow.push_back((asset.clone(), amount)); + } } /// Build a set of pool actions and the new positions from the supplied requests. Validates that the requests @@ -190,13 +198,16 @@ pub fn build_actions_from_request( ); } RequestType::Borrow => { - let mut reserve = pool.load_reserve(e, &request.address, true); - let d_tokens_minted = reserve.to_d_token_up(request.amount); - from_state.add_liabilities(e, &mut reserve, d_tokens_minted); - reserve.require_utilization_below_max(e); - actions.add_for_pool_transfer(&reserve.asset, request.amount); - check_health = true; - pool.cache_reserve(reserve); + let d_tokens_minted = build_borrow_generic( + e, + pool, + &mut from_state, + &mut actions, + &mut check_health, + request.clone(), + false, + ); + e.events().publish( ( Symbol::new(e, "borrow"), @@ -307,6 +318,32 @@ pub fn build_actions_from_request( (), ); } + // This is pretty much just a standard borrow that however immediately transfers + // the borrowed amount to [`user`] instead of adding it to the transfer actions. + // It also calls a contract ([`user`]). This step will panic if the provided address + // is not a contract or does not respect the flash loan interface. Both the transfer + // and the invocation don't need to happen within this code block, just before the + // other actions. + RequestType::FlashBorrow => { + let d_tokens_minted = build_borrow_generic( + e, + pool, + &mut from_state, + &mut actions, + &mut check_health, + request.clone(), + true, + ); + + e.events().publish( + ( + Symbol::new(e, "flash_borrow"), + request.address.clone(), + from.clone(), + ), + (request.amount, d_tokens_minted), + ); + } } } @@ -316,6 +353,38 @@ pub fn build_actions_from_request( (actions, from_state, check_health) } +fn build_borrow_generic( + e: &Env, + pool: &mut Pool, + from_state: &mut User, + actions: &mut Actions, + check_health: &mut bool, + request: Request, + flash: bool, +) -> i128 { + let transfer_amount = request.amount; + let token_address = &request.address; + let mut reserve = pool.load_reserve(e, token_address, true); + let d_tokens_minted = reserve.to_d_token_up(transfer_amount); + from_state.add_liabilities(e, &mut reserve, d_tokens_minted); + reserve.require_utilization_below_max(e); + + // the actual difference between the flash borrow and standard borrow. + if !flash { + actions.add_for_pool_transfer(token_address, transfer_amount); + } else { + actions.add_flash_borrow(token_address, transfer_amount); + } + *check_health = true; + pool.cache_reserve(reserve); + + // we don't directly emit the event here because we may want flashborrow + // and borrow to have distinct event structure. Note that the additional clone + // introduced to support this logic has no effect on cost since it's just a stub. + + d_tokens_minted +} + #[cfg(test)] mod tests { diff --git a/pool/src/pool/submit.rs b/pool/src/pool/submit.rs index e4818d2..68e7a92 100644 --- a/pool/src/pool/submit.rs +++ b/pool/src/pool/submit.rs @@ -1,3 +1,4 @@ +use moderc3156::FlashLoanClient; use sep_41_token::TokenClient; use soroban_sdk::{panic_with_error, Address, Env, Vec}; @@ -48,6 +49,25 @@ pub fn execute_submit( panic_with_error!(e, PoolError::InvalidHf); } + // deal with potential flash lending before dealing with the other transfers. + // tbd: do we want to allow for multiple flash loans? There doesn't seem to be a risk in it so currently enabled. + for (address, amount) in actions.flash_borrow { + // note: TBD if `from` is indeed the receiver contract (it probably isn't? better to not add new args) + TokenClient::new(e, &address).transfer(&e.current_contract_address(), from, &amount); + // calls the receiver contract. + FlashLoanClient::new(&e, from).exec_op( + &e.current_contract_address(), + &address, + &amount, + &0, + ); + } + + // note: at this point, the pool has sum_by_asset(actions.flash_borrow.1) for each involed asset, but the user also has + // increased liabilities. These will have to be either fully repaid by now in the requests following the flash borrow + // or the user needs to have some previously added collateral to cover the borrow, i.e user is already healthy at this point, + // we just have to make sure that they have the balances they are claiming to have through the transfers. + // transfer tokens from sender to pool for (address, amount) in actions.spender_transfer.iter() { TokenClient::new(e, &address).transfer(spender, &e.current_contract_address(), &amount);