Skip to content
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

Solver crate forwards flashloans back to driver #3283

Merged
merged 4 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/driver/src/infra/config/file/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ pub async fn load(chain: Chain, path: &Path) -> infra::Config {
},
settle_queue_size: solver_config.settle_queue_size,
flashloans_enabled: config.flashloans_enabled,
flashloan_default_lender: eth::Address(config.flashloans_default_lender),
}
}))
.await,
Expand Down
11 changes: 10 additions & 1 deletion crates/driver/src/infra/config/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use {
serde::{Deserialize, Deserializer, Serialize},
serde_with::serde_as,
solver::solver::Arn,
std::{collections::HashMap, time::Duration},
std::{collections::HashMap, str::FromStr, time::Duration},
};

mod load;
Expand Down Expand Up @@ -81,6 +81,10 @@ struct Config {
/// Whether the flashloans feature is enabled.
#[serde(default)]
flashloans_enabled: bool,

/// If no lender is specified in flashloan hint, use this default lender.
#[serde(default = "default_flashloans_lender")]
flashloans_default_lender: eth::H160,
}

#[serde_as]
Expand Down Expand Up @@ -684,6 +688,11 @@ fn default_simulation_bad_token_max_age() -> Duration {
Duration::from_secs(600)
}

// SKY lending DAI token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I wasn't aware that the contract @fedgiac used in the e2e test for the contract only worked with Dai. I'm fine to make this the default for the start where it's only relevant for the e2e test but we should have at least 1 supported general purpose flashloan lender.
Also this could indicate that we might need a module or component that suggests a good lending provider based on the token that needs to be borrowed. Should happen later, though (if at all).

Copy link
Contributor Author

@sunce86 sunce86 Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. There are A LOT of assumptions made at the moment, but I have them in my list. Before going live, these will have to be addressed and some kind of validation for flashloan hints will be implemented to avoid weird cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle there are wrappers for incompatible flash-loan lenders (some examples here from this EIP discussion).
But for the e2e test I wanted to avoid the extra mental overhead of the wrapper and I used a protocol that natively supports the standard (but unfortunately only for DAI).
Another assumption in the e2e test is that the flash-loan lender pulls back the token from the borrower by executing transferFrom, but this isn't necessarily the case for other protocols, some other expect the tokens to be transferred back and check that the balance at the end is as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we will have to implement a lender specific encoding once we finish this first version that works only with Sky.

fn default_flashloans_lender() -> eth::H160 {
eth::H160::from_str("0x60744434d6339a6B27d73d9Eda62b6F66a0a04FA").unwrap()
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
Expand Down
27 changes: 13 additions & 14 deletions crates/driver/src/infra/solver/dto/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ impl Auction {
fee_handler: FeeHandler,
solver_native_token: ManageNativeToken,
flashloans_enabled: bool,
flashloan_default_lender: eth::Address,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By now this constructor has quite a few arguments that will not change during the runtime of the driver.
Might be useful to have a builder component for those pieces of information. 🤔
(follow up task if at all)

) -> Self {
let mut tokens: HashMap<eth::H160, _> = auction
.tokens()
Expand Down Expand Up @@ -161,7 +162,16 @@ impl Auction {
),
app_data: AppDataHash(order.app_data.hash().0.into()),
flashloan_hint: flashloans_enabled
.then(|| order.app_data.flashloan().cloned().map(Into::into))
.then(|| {
order.app_data.flashloan().map(|flashloan| FlashloanHint {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if filling in these default values makes more sense in the component that fetches the app data in the first place. 🤔
No strong opinion, though since I can see why one would want to have access to the pure unaltered appdata.

Copy link
Contributor Author

@sunce86 sunce86 Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. For now, flashloan hints in their final shape are not needed/used in the driver domain. But we can move this logic out to driver domain when the need comes.

lender: flashloan.lender.unwrap_or(flashloan_default_lender.0),
borrower: flashloan
.borrower
.unwrap_or(eth::H160::from_slice(&order.uid.0 .0[32..52])),
token: flashloan.token,
amount: flashloan.amount,
})
})
.flatten(),
signature: order.signature.data.clone().into(),
signing_scheme: match order.signature.scheme {
Expand Down Expand Up @@ -609,24 +619,13 @@ struct ForeignLimitOrder {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FlashloanHint {
pub lender: Option<eth::H160>,
pub borrower: Option<eth::H160>,
pub lender: eth::H160,
pub borrower: eth::H160,
pub token: eth::H160,
#[serde_as(as = "serialize::U256")]
pub amount: eth::U256,
}

impl From<app_data::Flashloan> for FlashloanHint {
fn from(value: app_data::Flashloan) -> Self {
Self {
lender: value.lender,
borrower: value.borrower,
token: value.token,
amount: value.amount,
}
}
}

fn fee_to_decimal(fee: liquidity::balancer::v2::Fee) -> bigdecimal::BigDecimal {
bigdecimal::BigDecimal::new(fee.as_raw().to_big_int(), 18)
}
Expand Down
3 changes: 3 additions & 0 deletions crates/driver/src/infra/solver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ pub struct Config {
pub settle_queue_size: usize,
/// Whether flashloan hints should be sent to the solver.
pub flashloans_enabled: bool,
/// If no lender is specified in flashloan hint, use default one
pub flashloan_default_lender: eth::Address,
}

impl Solver {
Expand Down Expand Up @@ -231,6 +233,7 @@ impl Solver {
self.config.fee_handler,
self.config.solver_native_token,
self.config.flashloans_enabled,
self.config.flashloan_default_lender,
);
// Only auctions with IDs are real auctions (/quote requests don't have an ID,
// and it makes no sense to store them)
Expand Down
7 changes: 6 additions & 1 deletion crates/driver/src/tests/setup/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ impl Solver {
"signingScheme": if config.quote { "eip1271" } else { "eip712" },
});
if let Some(flashloan) = quote.order.app_data.flashloan() {
order["flashloanHint"] = json!(Into::<FlashloanHint>::into(flashloan.clone()));
order["flashloanHint"] = json!(FlashloanHint {
lender: flashloan.lender.unwrap(),
borrower: flashloan.borrower.unwrap(),
token: flashloan.token,
amount: flashloan.amount
});
}
if config.fee_handler == FeeHandler::Solver {
order.as_object_mut().unwrap().insert(
Expand Down
4 changes: 2 additions & 2 deletions crates/solvers-dto/src/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ pub struct ForeignLimitOrder {
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FlashloanHint {
pub lender: Option<H160>,
pub borrower: Option<H160>,
pub lender: H160,
pub borrower: H160,
pub token: H160,
#[serde_as(as = "HexOrDecimalU256")]
pub amount: U256,
Expand Down
2 changes: 2 additions & 0 deletions crates/solvers/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ components:
Contains information to hint at how how to make use of flashloans to settle the associated order.
type: object
required:
- lender
- borrower
- token
- amount
properties:
Expand Down
4 changes: 2 additions & 2 deletions crates/solvers/src/api/routes/solve/dto/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ pub fn to_domain(auction: &Auction) -> Result<auction::Auction, Error> {
.flashloan_hint
.clone()
.map(|hint| order::FlashloanHint {
lender: hint.lender.map(eth::Address),
borrower: hint.borrower.map(eth::Address),
lender: eth::Address(hint.lender),
borrower: eth::Address(hint.borrower),
token: eth::TokenAddress(hint.token),
amount: hint.amount,
}),
Expand Down
4 changes: 2 additions & 2 deletions crates/solvers/src/domain/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ impl Debug for AppData {
/// A hint for the solver to use a flashloan for this order.
#[derive(Debug, Clone)]
pub struct FlashloanHint {
pub lender: Option<eth::Address>,
pub borrower: Option<eth::Address>,
pub lender: eth::Address,
pub borrower: eth::Address,
pub token: eth::TokenAddress,
pub amount: eth::U256,
}
16 changes: 7 additions & 9 deletions crates/solvers/src/domain/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,14 @@ impl Single {
flashloans: order
.flashloan_hint
.clone()
.map(|hint| match (hint.lender, hint.borrower) {
(Some(lender), Some(borrower)) => vec![Flashloan {
lender,
borrower,
token: hint.token,
amount: hint.amount,
}],
_ => vec![],
.map(|hint| Flashloan {
lender: hint.lender,
borrower: hint.borrower,
token: hint.token,
amount: hint.amount,
})
.unwrap_or_default(),
.into_iter()
.collect(),
trades: vec![Trade::Fulfillment(Fulfillment::new(order, executed, fee)?)],
})
}
Expand Down
Loading