Skip to content

Commit

Permalink
Require input and output amounts for raw interactions (#194)
Browse files Browse the repository at this point in the history
This PR augments interaction data to also include input and output amounts. This will be required by the driver in order to verify token conservation across tokens and orders.

### Test Plan

Added unit test to verify de-serialization. For now, there is no new logic changes.

### Release notes

This PR introduces changes to the HTTP solver interface. Note that the API was kept backwards compatible to allow solver implementations time to adapt to the new API. These changes will be made required after a certain amount of time (since they are required for validating new solver rules around local and global token conservation).
  • Loading branch information
Nicholas Rodrigues Lordello authored May 24, 2022
1 parent 56ae162 commit 169ffe2
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 35 deletions.
14 changes: 7 additions & 7 deletions crates/shared/src/http_solver/gas_model.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::model::CostModel;
use super::model::TokenAmount;
use crate::price_estimation::gas::*;
use primitive_types::{H160, U256};

Expand All @@ -8,26 +8,26 @@ pub struct GasModel {
}

impl GasModel {
pub fn cost_for_gas(&self, gas: U256) -> CostModel {
CostModel {
pub fn cost_for_gas(&self, gas: U256) -> TokenAmount {
TokenAmount {
amount: U256::from_f64_lossy(self.gas_price) * gas,
token: self.native_token,
}
}

pub fn gp_order_cost(&self) -> CostModel {
pub fn gp_order_cost(&self) -> TokenAmount {
self.cost_for_gas(GAS_PER_ORDER.into())
}

pub fn zeroex_order_cost(&self) -> CostModel {
pub fn zeroex_order_cost(&self) -> TokenAmount {
self.cost_for_gas(GAS_PER_ZEROEX_ORDER.into())
}

pub fn uniswap_cost(&self) -> CostModel {
pub fn uniswap_cost(&self) -> TokenAmount {
self.cost_for_gas(GAS_PER_UNISWAP.into())
}

pub fn balancer_cost(&self) -> CostModel {
pub fn balancer_cost(&self) -> TokenAmount {
self.cost_for_gas(GAS_PER_BALANCER_SWAP.into())
}
}
164 changes: 143 additions & 21 deletions crates/shared/src/http_solver/model.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use derivative::Derivative;
use ethcontract::H160;
use model::{
ratio_as_decimal,
Expand Down Expand Up @@ -27,8 +28,8 @@ pub struct OrderModel {
pub buy_amount: U256,
pub allow_partial_fill: bool,
pub is_sell_order: bool,
pub fee: FeeModel,
pub cost: CostModel,
pub fee: TokenAmount,
pub cost: TokenAmount,
pub is_liquidity_order: bool,
#[serde(default)]
pub mandatory: bool,
Expand All @@ -45,7 +46,7 @@ pub struct AmmModel {
pub parameters: AmmParameters,
#[serde(with = "ratio_as_decimal")]
pub fee: BigRational,
pub cost: CostModel,
pub cost: TokenAmount,
pub mandatory: bool,
}

Expand Down Expand Up @@ -99,15 +100,8 @@ pub struct TokenInfoModel {
pub internal_buffer: Option<U256>,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct CostModel {
#[serde(with = "u256_decimal")]
pub amount: U256,
pub token: H160,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct FeeModel {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct TokenAmount {
#[serde(with = "u256_decimal")]
pub amount: U256,
pub token: H160,
Expand All @@ -121,14 +115,59 @@ pub struct ApprovalModel {
pub amount: U256,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[derive(Clone, Debug, Derivative, Deserialize, PartialEq)]
pub struct InteractionData {
pub target: H160,
pub value: U256,
#[derivative(Debug(format_with = "debug_bytes"))]
#[serde(with = "bytes_hex_or_array")]
pub call_data: Vec<u8>,
/// The input amounts into the AMM interaction - i.e. the amount of tokens
/// that are expected to be sent from the settlement contract into the AMM
/// for this calldata.
///
/// `GPv2Settlement -> AMM`
#[serde(default)]
pub inputs: Vec<TokenAmount>,
/// The output amounts from the AMM interaction - i.e. the amount of tokens
/// that are expected to be sent from the AMM into the settlement contract
/// for this calldata.
///
/// `AMM -> GPv2Settlement`
#[serde(default)]
pub outputs: Vec<TokenAmount>,
pub exec_plan: Option<ExecutionPlanCoordinatesModel>,
}

/// Module to allow for backwards compatibility with the HTTP solver API.
///
/// Specifically, the HTTP solver API used to expect calldata as a JSON array of
/// integers that fit in a `u8`. This changed to allow `0x-` prefixed hex
/// strings to be more consistent with how bytes are typically represented in
/// Ethereum-related APIs. This module implements JSON deserialization that
/// accepts either format.
mod bytes_hex_or_array {
use serde::{Deserialize, Deserializer};

#[derive(Deserialize)]
#[serde(untagged)]
enum HexOrArray {
Hex(#[serde(with = "model::bytes_hex")] Vec<u8>),
Array(Vec<u8>),
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let bytes = match HexOrArray::deserialize(deserializer)? {
HexOrArray::Hex(bytes) => bytes,
HexOrArray::Array(bytes) => bytes,
};
Ok(bytes)
}
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
pub struct SettledBatchAuctionModel {
Expand Down Expand Up @@ -176,8 +215,8 @@ pub struct ExecutedOrderModel {
pub exec_sell_amount: U256,
#[serde(with = "u256_decimal")]
pub exec_buy_amount: U256,
pub cost: Option<CostModel>,
pub fee: Option<FeeModel>,
pub cost: Option<TokenAmount>,
pub fee: Option<TokenAmount>,
// Orders which need to be executed in a specific order have an `exec_plan` (e.g. 0x limit orders)
pub exec_plan: Option<ExecutionPlanCoordinatesModel>,
}
Expand All @@ -186,7 +225,7 @@ pub struct ExecutedOrderModel {
pub struct UpdatedAmmModel {
/// We ignore additional incoming amm fields we don't need.
pub execution: Vec<ExecutedAmmModel>,
pub cost: Option<CostModel>,
pub cost: Option<TokenAmount>,
}

#[derive(Clone, Debug, Default, Deserialize)]
Expand Down Expand Up @@ -299,11 +338,11 @@ mod tests {
buy_amount: U256::from(2),
allow_partial_fill: false,
is_sell_order: true,
fee: FeeModel {
fee: TokenAmount {
amount: U256::from(2),
token: sell_token,
},
cost: CostModel {
cost: TokenAmount {
amount: U256::from(1),
token: native_token,
},
Expand All @@ -319,7 +358,7 @@ mod tests {
},
}),
fee: BigRational::new(3.into(), 1000.into()),
cost: CostModel {
cost: TokenAmount {
amount: U256::from(3),
token: native_token,
},
Expand All @@ -339,7 +378,7 @@ mod tests {
},
}),
fee: BigRational::new(2.into(), 1000.into()),
cost: CostModel {
cost: TokenAmount {
amount: U256::from(2),
token: native_token,
},
Expand All @@ -358,7 +397,7 @@ mod tests {
amplification_parameter: BigRational::new(1337.into(), 100.into()),
}),
fee: BigRational::new(3.into(), 1000.into()),
cost: CostModel {
cost: TokenAmount {
amount: U256::from(3),
token: native_token,
},
Expand Down Expand Up @@ -560,4 +599,87 @@ mod tests {
"#;
assert!(serde_json::from_str::<SettledBatchAuctionModel>(x).is_ok());
}

#[test]
fn decode_interaction_data() {
assert_eq!(
serde_json::from_str::<InteractionData>(
r#"
{
"target": "0xffffffffffffffffffffffffffffffffffffffff",
"value": "0",
"call_data": "0x01020304",
"inputs": [
{
"token": "0x0101010101010101010101010101010101010101",
"amount": "9999"
}
],
"outputs": [
{
"token": "0x0202020202020202020202020202020202020202",
"amount": "2000"
},
{
"token": "0x0303030303030303030303030303030303030303",
"amount": "3000"
}
],
"exec_plan": {
"sequence": 42,
"position": 1337
}
}
"#,
)
.unwrap(),
InteractionData {
target: H160([0xff; 20]),
value: 0.into(),
call_data: vec![1, 2, 3, 4],
inputs: vec![TokenAmount {
token: H160([1; 20]),
amount: 9999.into(),
}],
outputs: vec![
TokenAmount {
token: H160([2; 20]),
amount: 2000.into(),
},
TokenAmount {
token: H160([3; 20]),
amount: 3000.into(),
}
],
exec_plan: Some(ExecutionPlanCoordinatesModel {
sequence: 42,
position: 1337,
}),
},
);
}

#[test]
fn decode_interaction_data_backwards_compatibility() {
assert_eq!(
serde_json::from_str::<InteractionData>(
r#"
{
"target": "0xffffffffffffffffffffffffffffffffffffffff",
"value": "0",
"call_data": [1, 2, 3, 4]
}
"#,
)
.unwrap(),
InteractionData {
target: H160([0xff; 20]),
value: 0.into(),
call_data: vec![1, 2, 3, 4],
inputs: Vec::new(),
outputs: Vec::new(),
exec_plan: None,
},
);
}
}
10 changes: 5 additions & 5 deletions crates/shared/src/price_estimation/quasimodo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use crate::{
http_solver::{
gas_model::GasModel,
model::{
AmmModel, AmmParameters, BatchAuctionModel, ConstantProductPoolParameters, CostModel,
FeeModel, OrderModel, SettledBatchAuctionModel, StablePoolParameters, TokenInfoModel,
AmmModel, AmmParameters, BatchAuctionModel, ConstantProductPoolParameters, OrderModel,
SettledBatchAuctionModel, StablePoolParameters, TokenAmount, TokenInfoModel,
WeightedPoolTokenData, WeightedProductPoolParameters,
},
HttpSolverApi,
Expand Down Expand Up @@ -87,11 +87,11 @@ impl QuasimodoPriceEstimator {
buy_amount,
allow_partial_fill: false,
is_sell_order: query.kind == OrderKind::Sell,
fee: FeeModel {
fee: TokenAmount {
amount: U256::from(GAS_PER_ORDER) * gas_price,
token: self.native_token,
},
cost: CostModel {
cost: TokenAmount {
amount: U256::from(GAS_PER_ORDER) * gas_price,
token: self.native_token,
},
Expand Down Expand Up @@ -290,7 +290,7 @@ impl QuasimodoPriceEstimator {
Ok(models)
}

fn extract_cost(&self, cost: &Option<CostModel>) -> Result<U256, PriceEstimationError> {
fn extract_cost(&self, cost: &Option<TokenAmount>) -> Result<U256, PriceEstimationError> {
if let Some(cost) = cost {
if cost.token != self.native_token {
Err(anyhow::anyhow!("cost specified as an unknown token {}", cost.token).into())
Expand Down
4 changes: 2 additions & 2 deletions crates/solver/src/solver/http_solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,12 @@ fn map_tokens_for_solver(orders: &[LimitOrder], liquidity: &[Liquidity]) -> Vec<
Vec::from_iter(token_set)
}

fn order_fee(order: &LimitOrder) -> FeeModel {
fn order_fee(order: &LimitOrder) -> TokenAmount {
let amount = match order.is_liquidity_order {
true => order.unscaled_subsidized_fee,
false => order.scaled_unsubsidized_fee,
};
FeeModel {
TokenAmount {
amount,
token: order.sell_token,
}
Expand Down
2 changes: 2 additions & 0 deletions crates/solver/src/solver/http_solver/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,8 @@ mod tests {
target: H160::zero(),
value: U256::zero(),
call_data: Vec::new(),
inputs: vec![],
outputs: vec![],
exec_plan: Some(ExecutionPlanCoordinatesModel {
sequence: 1u32,
position: 1u32,
Expand Down

0 comments on commit 169ffe2

Please sign in to comment.