-
Notifications
You must be signed in to change notification settings - Fork 148
Reimplement sell=buy PoC PR #3894
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
base: main
Are you sure you want to change the base?
Conversation
de09f52 to
858ac73
Compare
|
I think this PR needs to be converted to a draft, since more changes are expected. @m-sz , is that correct? |
|
Yep, had a bunch of 1:1s and will be adding feature flag to the whole change to control it in orderbook. |
|
I have updated the PR with CLI argument to orderbook to allow for same sell and buy tokens. |
|
This pull request has been marked as stale because it has been inactive a while. Please update this pull request or it will be automatically closed. |
c3a04a2 to
405acc6
Compare
|
What happens if we place a buy order with the same tokens? Do we need to support it? We should either decline such orders or cover this case with e2e tests. |
|
@squadgazzz for now sell order is enough for s&b, buy-orders only relevant for a later use-case of programmatic orders (see discussion). |
| let bad_token_detector = Arc::new(bad_token_detector); | ||
| let sanitized_estimator = SanitizedPriceEstimator { | ||
| inner: Arc::new(wrapped_estimator), | ||
| bad_token_detector: Arc::new(bad_token_detector), | ||
| bad_token_detector: bad_token_detector.clone(), |
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.
Why is this change required?
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.
Further down in the sell=buy unit tests I reuse the bad token detector.
| Some(route) if !route.is_empty() => { | ||
| // how many units of buy_token are bought for one unit of sell_token | ||
| // (buy_amount / sell_amount). | ||
| let price = self.native_token_price_estimation_amount.to_f64_lossy() | ||
| / route.input().amount.to_f64_lossy(); | ||
| / route | ||
| .input() | ||
| .expect("route is not empty") | ||
| .amount | ||
| .to_f64_lossy(); |
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.
Emptiness validation seems redundant here. It would make sense to check the route.input() value, or just map it.
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.
Sure! Thanks.
| if !route.is_empty() { | ||
| interactions = route | ||
| .segments | ||
| .iter() | ||
| .map(|segment| { | ||
| solution::Interaction::Liquidity(Box::new( | ||
| solution::LiquidityInteraction { | ||
| liquidity: segment.liquidity.clone(), | ||
| input: segment.input, | ||
| output: segment.output, | ||
| // TODO does the baseline solver know about this | ||
| // optimization? | ||
| internalize: false, | ||
| }, | ||
| )) | ||
| }) | ||
| .collect(); | ||
| gas = route.gas() + self.solution_gas_offset; | ||
| input = route.input().expect("route is not empty"); | ||
| output = route.output().expect("route is not empty"); | ||
| } else { | ||
| // Route is empty in case of sell and buy tokens being the same, as there | ||
| // is no need to figure out the liquidity for such pair. | ||
| // | ||
| // The input and output of the solution can be set directly to the | ||
| // respective sell and buy tokens 1 to 1. | ||
| interactions = Vec::default(); | ||
| gas = eth::Gas(U256::zero()) + self.solution_gas_offset; | ||
|
|
||
| (input, output) = match order.side { | ||
| order::Side::Sell => (order.sell, order.buy), | ||
| order::Side::Buy => (order.buy, order.sell), | ||
| }; | ||
| output.amount = input.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.
This logic also looks a bit awkward.
| if !route.is_empty() { | |
| interactions = route | |
| .segments | |
| .iter() | |
| .map(|segment| { | |
| solution::Interaction::Liquidity(Box::new( | |
| solution::LiquidityInteraction { | |
| liquidity: segment.liquidity.clone(), | |
| input: segment.input, | |
| output: segment.output, | |
| // TODO does the baseline solver know about this | |
| // optimization? | |
| internalize: false, | |
| }, | |
| )) | |
| }) | |
| .collect(); | |
| gas = route.gas() + self.solution_gas_offset; | |
| input = route.input().expect("route is not empty"); | |
| output = route.output().expect("route is not empty"); | |
| } else { | |
| // Route is empty in case of sell and buy tokens being the same, as there | |
| // is no need to figure out the liquidity for such pair. | |
| // | |
| // The input and output of the solution can be set directly to the | |
| // respective sell and buy tokens 1 to 1. | |
| interactions = Vec::default(); | |
| gas = eth::Gas(U256::zero()) + self.solution_gas_offset; | |
| (input, output) = match order.side { | |
| order::Side::Sell => (order.sell, order.buy), | |
| order::Side::Buy => (order.buy, order.sell), | |
| }; | |
| output.amount = input.amount; | |
| } | |
| interactions = route | |
| .segments | |
| .iter() | |
| .map(|segment| { | |
| solution::Interaction::Liquidity(Box::new( | |
| solution::LiquidityInteraction { | |
| liquidity: segment.liquidity.clone(), | |
| input: segment.input, | |
| output: segment.output, | |
| // TODO does the baseline solver know about this | |
| // optimization? | |
| internalize: false, | |
| }, | |
| )) | |
| }) | |
| .collect(); | |
| if !interactions.is_empty() { | |
| gas = route.gas() + self.solution_gas_offset; | |
| input = route.input().expect("route is not empty"); | |
| output = route.output().expect("route is not empty"); | |
| } else { | |
| // Route is empty in case of sell and buy tokens being the same, as there | |
| // is no need to figure out the liquidity for such pair. | |
| // | |
| // The input and output of the solution can be set directly to the | |
| // respective sell and buy tokens 1 to 1. | |
| gas = eth::Gas(U256::zero()) + self.solution_gas_offset; | |
| (input, output) = match order.side { | |
| order::Side::Sell => (order.sell, order.buy), | |
| order::Side::Buy => (order.buy, order.sell), | |
| }; | |
| output.amount = input.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.
Also, let's avoid this style with declaring empty variables. Not really an idiomatic approach.
let interactions;
let gas;
let (input, mut output);
| fn is_empty(&self) -> bool { | ||
| self.segments.is_empty() |
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.
This function can go away then.
MartinquaXD
left a comment
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.
Just a first look. I'll try to do another deeper review later.
| /// Creates a new instance of [`Tokens`], verifying that the input buy and | ||
| /// sell tokens are distinct. |
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.
I would like to get some feedback from solvers how they would expect this to work. Specifically to ask them about using the regular quoting logic vs. building something into the orderbook which only needs the native price of the tokens and a simulation to get the gas amount.
| .context("summary buy token is missing")?; | ||
|
|
||
| if *sell_token_lost >= sell_token_lost_limit || *buy_token_lost >= buy_token_lost_limit { | ||
| if (!sell_token_lost.is_zero() && *sell_token_lost >= sell_token_lost_limit) |
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.
This change is not needed anymore, right?
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.
When the whole E2E test suite is run, the test onchain_settlement_without_liquidity from buffers.rs seems to hit this, probably due to the change in quote verification?
| / route | ||
| .input() | ||
| .expect("route is not empty") | ||
| .amount | ||
| .to_f64_lossy(); |
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.
We can simply use order.buy.token, no?
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.
I think it ought to be the sell token, as per the comments.
| input = route.input().expect("route is not empty"); | ||
| output = route.output().expect("route is not empty"); | ||
| } else { | ||
| // Route is empty in case of sell and buy tokens being the same, as there |
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.
Probably nicer to make this an early return.
|
@m-sz please also update https://github.com/gnosis/solvers, so none of the solvers waste the third-party API requests on sending orders that can't be solved. So each solver needs to filter out such orders in advance. |
| let gas; | ||
| let (input, mut output); | ||
|
|
||
| if !route.is_empty() { |
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.
Since we probably need to have an if here anyway we can probably also leave the routing logic completely untouched (i.e. revert changes for allowing empty route segments). Rather than having the if after calling route() here we could instead have the if sell == buy and only call route() in the other case, right?
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.
That's correct. I am fine with making this change
Co-authored-by: ilya <[email protected]>
Co-authored-by: ilya <[email protected]>
Co-authored-by: ilya <[email protected]>
Co-authored-by: Martin Magnus <[email protected]>
Sell=buy token initiative
The ability to swap the same token pairs unlocks the potential for CoW to generally become a transaction relay service. This feature will allow users to execute pre- and post- interaction hooks as gasless transactions. The overall implementation has certain caveats:
Lift restrictions on sell=buy token
Based on input from @fhenneke:
The test case
test_execute_same_sell_and_buy_tokencurrently fails because despite creating an order with the same sell and buy token (the deployed token's address is0x68b1d87f95878fe05b998f19b66f4baba5de1aed), the buy token becomes0x5fbdb2315678afecb367f032d93f642f64180aa3when order is considered for solving in an auction. Working on resolving that.