-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add flash lending functionality #10
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Address, i128>, | ||
pub pool_transfer: Map<Address, i128>, | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should leave this line in the switch statement as well, to follow suit with how other external functions are called, like |
||
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 { | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. The only real use case I can imagine for this would probably not benefit from multiple contracts being invoked, however. It seems more likely that a user would flash borrow multiple tokens to 1 contract to then invoke. However, this is would break IMO we can leave for now, but it's worth some additional thought. cc: @markuspluna |
||
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); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be clearer to have the individualized code within the switch statement instead of in this function