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

Solvers solution pre/post interactions #2679

Merged
merged 14 commits into from
May 13, 2024
58 changes: 37 additions & 21 deletions crates/driver/src/domain/competition/solution/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use {
competition::{
self,
order::{self, Partial},
solution::Interaction,
},
eth::{self, allowance, Ether},
liquidity::{self, ExactOutput, MaxInput},
Expand Down Expand Up @@ -44,10 +45,12 @@ pub fn tx(
let mut clearing_prices =
Vec::with_capacity(solution.prices.len() + (solution.trades().len() * 2));
let mut trades: Vec<Trade> = Vec::with_capacity(solution.trades().len());
let mut pre_interactions = Vec::new();
let mut pre_interactions =
encode_interactions(&solution.pre_interactions, internalization, contract)?;
let mut post_interactions =
encode_interactions(&solution.post_interactions, internalization, contract)?;
let mut interactions =
Vec::with_capacity(approvals.size_hint().0 + solution.interactions().len());
let mut post_interactions = Vec::new();

// Encode uniform clearing price vector
for (token, price) in solution.prices.clone() {
Expand Down Expand Up @@ -150,25 +153,11 @@ pub fn tx(
interactions.push(approve(&approval.0))
}

// Encode interaction
for interaction in solution.interactions() {
if matches!(internalization, settlement::Internalization::Enable)
&& interaction.internalize()
{
continue;
}

interactions.push(match interaction {
competition::solution::Interaction::Custom(interaction) => eth::Interaction {
value: interaction.value,
target: interaction.target.0.into(),
call_data: interaction.call_data.clone(),
},
competition::solution::Interaction::Liquidity(liquidity) => {
liquidity_interaction(liquidity, contract)?
}
})
}
interactions.extend(encode_interactions(
solution.interactions(),
internalization,
contract,
)?);

let tx = contract
.settle(
Expand Down Expand Up @@ -196,6 +185,33 @@ pub fn tx(
})
}

fn encode_interactions(
interactions: &[Interaction],
internalization: settlement::Internalization,
contract: &contracts::GPv2Settlement,
) -> Result<Vec<eth::Interaction>, Error> {
let mut encoded = vec![];
for interaction in interactions {
if matches!(internalization, settlement::Internalization::Enable)
&& interaction.internalize()
{
continue;
}

encoded.push(match interaction {
competition::solution::Interaction::Custom(interaction) => eth::Interaction {
value: interaction.value,
target: interaction.target.0.into(),
squadgazzz marked this conversation as resolved.
Show resolved Hide resolved
call_data: interaction.call_data.clone(),
},
competition::solution::Interaction::Liquidity(liquidity) => {
liquidity_interaction(liquidity, contract)?
}
})
}
Ok(encoded)
}

fn liquidity_interaction(
liquidity: &Liquidity,
settlement: &contracts::GPv2Settlement,
Expand Down
45 changes: 35 additions & 10 deletions crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ pub struct Solution {
id: Id,
trades: Vec<Trade>,
prices: Prices,
pre_interactions: Vec<Interaction>,
interactions: Vec<Interaction>,
post_interactions: Vec<Interaction>,
solver: Solver,
weth: eth::WethAddress,
gas: Option<eth::Gas>,
Expand All @@ -55,7 +57,9 @@ impl Solution {
id: Id,
trades: Vec<Trade>,
prices: Prices,
pre_interactions: Vec<Interaction>,
interactions: Vec<Interaction>,
post_interactions: Vec<Interaction>,
solver: Solver,
weth: eth::WethAddress,
gas: Option<eth::Gas>,
Expand All @@ -65,7 +69,9 @@ impl Solution {
id,
trades,
prices,
pre_interactions,
interactions,
post_interactions,
solver,
weth,
gas,
Expand Down Expand Up @@ -245,7 +251,17 @@ impl Solution {
id: Id::Merged([self.id.ids(), other.id.ids()].concat()),
trades: [self.trades.clone(), other.trades.clone()].concat(),
prices,
pre_interactions: [
self.pre_interactions.clone(),
other.pre_interactions.clone(),
]
.concat(),
interactions: [self.interactions.clone(), other.interactions.clone()].concat(),
post_interactions: [
self.post_interactions.clone(),
other.post_interactions.clone(),
]
.concat(),
solver: self.solver.clone(),
weth: self.weth,
// Same solver are guaranteed to have the same fee handler
Expand Down Expand Up @@ -278,15 +294,20 @@ impl Solution {
internalization: settlement::Internalization,
) -> impl Iterator<Item = eth::allowance::Required> {
let mut normalized = HashMap::new();
let allowances = self.interactions.iter().flat_map(|interaction| {
if interaction.internalize()
&& matches!(internalization, settlement::Internalization::Enable)
{
vec![]
} else {
interaction.allowances()
}
});
let allowances = self
.pre_interactions
.iter()
.chain(self.interactions.iter())
.chain(self.post_interactions.iter())
.flat_map(|interaction| {
if interaction.internalize()
&& matches!(internalization, settlement::Internalization::Enable)
{
vec![]
} else {
interaction.allowances()
}
});
for allowance in allowances {
let amount = normalized
.entry((allowance.0.token, allowance.0.spender))
Expand Down Expand Up @@ -374,8 +395,10 @@ impl Solution {

/// Whether there is a reasonable risk of this solution reverting on chain.
pub fn revertable(&self) -> bool {
self.interactions
self.pre_interactions
.iter()
.chain(self.interactions.iter())
.chain(self.post_interactions.iter())
.any(|interaction| !interaction.internalize())
|| self.user_trades().any(|trade| {
matches!(
Expand All @@ -392,7 +415,9 @@ impl std::fmt::Debug for Solution {
.field("id", &self.id)
.field("trades", &self.trades)
.field("prices", &self.prices)
.field("pre_interactions", &self.pre_interactions)
.field("interactions", &self.interactions)
.field("post_interactions", &self.post_interactions)
.field("solver", &self.solver.name())
.finish()
}
Expand Down
4 changes: 3 additions & 1 deletion crates/driver/src/domain/competition/solution/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ impl Settlement {
// Internalization rule: check that internalized interactions only use trusted
// tokens.
let untrusted_tokens = solution
.interactions
.pre_interactions
.iter()
.chain(solution.interactions.iter())
.chain(solution.post_interactions.iter())
.filter(|interaction| interaction.internalize())
.flat_map(|interaction| interaction.inputs())
.filter(|asset| !auction.tokens().get(asset.token).trusted)
Expand Down
144 changes: 77 additions & 67 deletions crates/driver/src/infra/solver/dto/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,73 +121,9 @@ impl Solutions {
.into_iter()
.map(|(address, price)| (address.into(), price))
.collect(),
solution
.interactions
.into_iter()
.map(|interaction| match interaction {
Interaction::Custom(interaction) => {
Ok(competition::solution::Interaction::Custom(
competition::solution::interaction::Custom {
target: interaction.target.into(),
value: interaction.value.into(),
call_data: interaction.call_data.into(),
allowances: interaction
.allowances
.into_iter()
.map(|allowance| {
eth::Allowance {
token: allowance.token.into(),
spender: allowance.spender.into(),
amount: allowance.amount,
}
.into()
})
.collect(),
inputs: interaction
.inputs
.into_iter()
.map(|input| eth::Asset {
amount: input.amount.into(),
token: input.token.into(),
})
.collect(),
outputs: interaction
.outputs
.into_iter()
.map(|input| eth::Asset {
amount: input.amount.into(),
token: input.token.into(),
})
.collect(),
internalize: interaction.internalize,
},
))
}
Interaction::Liquidity(interaction) => {
let liquidity = liquidity
.iter()
.find(|liquidity| liquidity.id == interaction.id)
.ok_or(super::Error(
"invalid liquidity ID specified in interaction".to_owned(),
))?
.to_owned();
Ok(competition::solution::Interaction::Liquidity(
competition::solution::interaction::Liquidity {
liquidity,
input: eth::Asset {
amount: interaction.input_amount.into(),
token: interaction.input_token.into(),
},
output: eth::Asset {
amount: interaction.output_amount.into(),
token: interaction.output_token.into(),
},
internalize: interaction.internalize,
},
))
}
})
.try_collect()?,
Self::interactions_into_domain(solution.pre_interactions, liquidity)?,
Self::interactions_into_domain(solution.interactions, liquidity)?,
Self::interactions_into_domain(solution.post_interactions, liquidity)?,
solver.clone(),
weth,
solution.gas.map(|gas| eth::Gas(gas.into())),
Expand All @@ -204,6 +140,76 @@ impl Solutions {
})
.collect()
}

fn interactions_into_domain(
interactions: Vec<Interaction>,
liquidity: &[liquidity::Liquidity],
) -> Result<Vec<competition::solution::Interaction>, super::Error> {
interactions
.into_iter()
.map(|interaction| match interaction {
Interaction::Custom(interaction) => Ok(competition::solution::Interaction::Custom(
competition::solution::interaction::Custom {
target: interaction.target.into(),
value: interaction.value.into(),
call_data: interaction.call_data.into(),
allowances: interaction
.allowances
.into_iter()
.map(|allowance| {
eth::Allowance {
token: allowance.token.into(),
spender: allowance.spender.into(),
amount: allowance.amount,
}
.into()
})
.collect(),
inputs: interaction
.inputs
.into_iter()
.map(|input| eth::Asset {
amount: input.amount.into(),
token: input.token.into(),
})
.collect(),
outputs: interaction
.outputs
.into_iter()
.map(|input| eth::Asset {
amount: input.amount.into(),
token: input.token.into(),
})
.collect(),
internalize: interaction.internalize,
},
)),
Interaction::Liquidity(interaction) => {
let liquidity = liquidity
.iter()
.find(|liquidity| liquidity.id == interaction.id)
.ok_or(super::Error(
"invalid liquidity ID specified in interaction".to_owned(),
))?
.to_owned();
Ok(competition::solution::Interaction::Liquidity(
competition::solution::interaction::Liquidity {
liquidity,
input: eth::Asset {
amount: interaction.input_amount.into(),
token: interaction.input_token.into(),
},
output: eth::Asset {
amount: interaction.output_amount.into(),
token: interaction.output_token.into(),
},
internalize: interaction.internalize,
},
))
}
})
.try_collect()
}
}

#[serde_as]
Expand All @@ -221,7 +227,11 @@ pub struct Solution {
#[serde_as(as = "HashMap<_, serialize::U256>")]
prices: HashMap<eth::H160, eth::U256>,
trades: Vec<Trade>,
#[serde(default)]
pre_interactions: Vec<Interaction>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is the wrong type. It doesn't make sense for solvers to specify Liquidity interactions as pre interactions. It also doesn't make sense for those to be internalizable nor specify input/output amounts. I think they should simply be

struct {
  target: eth::H160,
  value: eth::U256,
  call_data: Vec<u8>,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to InteractionData

interactions: Vec<Interaction>,
#[serde(default)]
post_interactions: Vec<Interaction>,
// TODO: remove this once all solvers are updated to not return the score
// https://github.com/cowprotocol/services/issues/2588
#[allow(dead_code)]
Expand Down
4 changes: 4 additions & 0 deletions crates/solvers-dto/src/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ pub struct Solution {
#[serde_as(as = "HashMap<_, HexOrDecimalU256>")]
pub prices: HashMap<H160, U256>,
pub trades: Vec<Trade>,
#[serde(default)]
pub pre_interactions: Vec<Interaction>,
pub interactions: Vec<Interaction>,
#[serde(default)]
pub post_interactions: Vec<Interaction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas: Option<u64>,
}
Expand Down
Loading
Loading