-
Notifications
You must be signed in to change notification settings - Fork 97
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
Baseline solver limit orders support #2326
Changes from 9 commits
2e18b36
4b42119
c3d45a2
2a2a8aa
cffd9c0
381416d
0eb67d2
dce04b6
a830885
621ca08
ec9887d
67dba54
c86ae17
b8cfde1
bea923c
f848362
fca90f4
9ad39cd
a3167b1
3a6b13e
de418ea
e818b6d
49df1fb
f10fed0
6bcfbac
c27296d
fd7880f
68e7da3
4de0d47
7e061d8
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 |
---|---|---|
@@ -0,0 +1,159 @@ | ||
use { | ||
crate::domain::liquidity::limit_order::{LimitOrder, TakerAmount}, | ||
contracts::ethcontract::{H160, U256}, | ||
shared::{baseline_solver::BaselineSolvable, price_estimation::gas::GAS_PER_ZEROEX_ORDER}, | ||
}; | ||
|
||
impl BaselineSolvable for LimitOrder { | ||
fn get_amount_out(&self, out_token: H160, (in_amount, in_token): (U256, H160)) -> Option<U256> { | ||
if in_token != self.taker.token.0 | ||
|| out_token != self.maker.token.0 | ||
|| in_amount > self.taker.amount | ||
{ | ||
return None; | ||
} | ||
|
||
let fee_adjusted_amount = in_amount.checked_sub(self.fee.0)?; | ||
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. Looks like this fee scales with gas price. I'm not sure we have taken that into account anywhere: https://docs.0xprotocol.org/en/latest/basics/protocol_fees.html#protocol-fees 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. Ah, does it mean we have to fetch the gas price each time we fetch 0x's limit orders from the orderbook? 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. Btw, taker's token fee is not scaled AFAIU. And the protocol fee should not be considered while calculating in/out amounts. 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. If I understand the contract code correctly the That being said I always find solidity code particularly hard to reason about so I might be wrong here. If there is still doubt about this checking out a tenderly simulation for a 0x fill would help. Also if this math is off we should detect that with the e2e tests. 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. Ok, simplified the formula to align with the one used in the smart contract. Added this temp test with the values from this tx. The second assertion fails due to precision issue, which is expected, I believe. |
||
if self.maker.amount > self.taker.amount { | ||
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. Why does it make a difference here whether Same thought on the other function. 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 do not operate with floating point numbers here. Consider an example where the ratio is 0.5, where in fact, we will get 0. As an alternative to avoid that, we should multiply the amounts that could lead to U256 overflow. 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 see. 👍 fee_adjusted_amount * make.amount / taker.amount If you want to be super careful in order to avoid overflows on the multiplication you could even do 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. Also let's add a small comment in the code explaining this non trivial rationale... 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. True, tried to follow the contract's implementation: https://github.com/0xProject/protocol/blob/e66307ba319e8c3e2a456767403298b576abc85e/contracts/zero-ex/contracts/src/features/native_orders/NativeOrdersSettlement.sol#L469-L509 |
||
let price_ratio = self.maker.amount.checked_div(self.taker.amount)?; | ||
fee_adjusted_amount.checked_mul(price_ratio) | ||
} else { | ||
let inverse_price_ratio = self.taker.amount.checked_div(self.maker.amount)?; | ||
fee_adjusted_amount.checked_div(inverse_price_ratio) | ||
} | ||
} | ||
|
||
fn get_amount_in(&self, in_token: H160, (out_amount, out_token): (U256, H160)) -> Option<U256> { | ||
if out_token != self.maker.token.0 | ||
|| in_token != self.taker.token.0 | ||
|| out_amount > self.maker.amount | ||
{ | ||
return None; | ||
} | ||
|
||
let amount_before_fee = if self.maker.amount > self.taker.amount { | ||
let inverse_price_ratio = self.maker.amount.checked_div(self.taker.amount)?; | ||
out_amount.checked_div(inverse_price_ratio)? | ||
} else { | ||
let price_ratio = self.taker.amount.checked_div(self.maker.amount)?; | ||
out_amount.checked_mul(price_ratio)? | ||
}; | ||
amount_before_fee.checked_add(self.fee.0) | ||
} | ||
|
||
fn gas_cost(&self) -> usize { | ||
GAS_PER_ZEROEX_ORDER as usize | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use {super::*, crate::domain::eth, contracts::ethcontract::U256, shared::addr}; | ||
|
||
fn create_limit_order(maker_amount: U256, taker_amount: U256, fee_amount: U256) -> LimitOrder { | ||
let maker = eth::Asset { | ||
amount: maker_amount, | ||
token: eth::TokenAddress(addr!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")), | ||
}; | ||
let taker = eth::Asset { | ||
amount: taker_amount, | ||
token: eth::TokenAddress(addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")), | ||
}; | ||
let fee = TakerAmount(fee_amount); | ||
|
||
LimitOrder { maker, taker, fee } | ||
} | ||
|
||
#[test] | ||
fn amount_out_in_round_trip() { | ||
let maker_amount = to_wei(321); | ||
let taker_amount = to_wei(123); | ||
let fee_amount = to_wei(10); | ||
let desired_in_amount = to_wei(50); | ||
|
||
let order = create_limit_order(maker_amount, taker_amount, fee_amount); | ||
let out_token = order.maker.token.0; | ||
let in_token = order.taker.token.0; | ||
|
||
let amount_out = order | ||
.get_amount_out(out_token, (desired_in_amount, in_token)) | ||
.unwrap(); | ||
let amount_in = order | ||
.get_amount_in(in_token, (amount_out, out_token)) | ||
.unwrap(); | ||
|
||
assert_eq!(amount_in, desired_in_amount); | ||
} | ||
|
||
#[test] | ||
fn amount_in_out_round_trip() { | ||
let maker_amount = to_wei(123); | ||
let taker_amount = to_wei(321); | ||
let fee_amount = to_wei(10); | ||
let desired_out_amount = to_wei(50); | ||
|
||
let order = create_limit_order(maker_amount, taker_amount, fee_amount); | ||
let out_token = order.maker.token.0; | ||
let in_token = order.taker.token.0; | ||
|
||
let amount_in = order | ||
.get_amount_in(in_token, (desired_out_amount, out_token)) | ||
.unwrap(); | ||
let amount_out = order | ||
.get_amount_out(out_token, (amount_in, in_token)) | ||
.unwrap(); | ||
|
||
assert_eq!(amount_out, desired_out_amount); | ||
} | ||
|
||
#[test] | ||
fn too_high_in_amount() { | ||
let maker_amount = to_wei(300); | ||
let taker_amount = to_wei(100); | ||
let fee_amount = to_wei(10); | ||
|
||
let order = create_limit_order(maker_amount, taker_amount, fee_amount); | ||
let out_token = order.maker.token.0; | ||
let in_token = order.taker.token.0; | ||
let amount_in = taker_amount.checked_mul(U256::from(2)).unwrap(); | ||
let amount_out = order.get_amount_out(out_token, (amount_in, in_token)); | ||
|
||
assert!(amount_out.is_none()); | ||
} | ||
|
||
#[test] | ||
fn too_high_out_amount() { | ||
let maker_amount = to_wei(100); | ||
let taker_amount = to_wei(300); | ||
let fee_amount = to_wei(10); | ||
|
||
let order = create_limit_order(maker_amount, taker_amount, fee_amount); | ||
let out_token = order.maker.token.0; | ||
let in_token = order.taker.token.0; | ||
let amount_out = maker_amount.checked_mul(U256::from(2)).unwrap(); | ||
let amount_in = order.get_amount_in(in_token, (amount_out, out_token)); | ||
|
||
assert!(amount_in.is_none()); | ||
} | ||
|
||
#[test] | ||
fn wrong_tokens() { | ||
let maker_amount = to_wei(100); | ||
let taker_amount = to_wei(100); | ||
let fee_amount = to_wei(10); | ||
|
||
let order = create_limit_order(maker_amount, taker_amount, fee_amount); | ||
let out_token = order.maker.token.0; | ||
let in_token = order.taker.token.0; | ||
let amount = to_wei(1); | ||
let amount_in = order.get_amount_in(out_token, (amount, in_token)); | ||
let amount_out = order.get_amount_out(in_token, (amount, out_token)); | ||
|
||
assert!(amount_in.is_none()); | ||
assert!(amount_out.is_none()); | ||
} | ||
|
||
fn to_wei(base: u32) -> U256 { | ||
U256::from(base) * U256::exp10(18) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
pub mod constant_product; | ||
mod limit_order; | ||
pub mod stable; | ||
pub mod weighted_product; |
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.
What's the difference between amms and liquidity? Does it make sense to combine the two somehow (maybe in a separate PR)? Also
amms
should probably be renamed.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.
Renamed and created an issue #2332