-
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
Protocol fee e2e tests clean-up #2561
Conversation
4753ef3
to
fc910f3
Compare
.await; | ||
} | ||
|
||
async fn partner_fee_with_not_default_cap_sell_order_test(web3: Web3) { |
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.
Is it intended that this fee still has a fee policy for market orders enabled?
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.
Partner fee should replace any configured protocol fee. Why not keep the Market fee here?
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.
Partner fee should replace any configured protocol fee.
Well, not really. It's just an implementation detail right now that it does. I'd hope that we can support multiple fee policies very soon so I'm not sure we want to explicitly test for this yet (but also no strong feeling).
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 have this task for it: #2495, hopefully we will implement it soon
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
let protocol_fees = vec![ | ||
ProtocolFee { | ||
policy: fee_policy.clone(), | ||
policy_order_class: FeePolicyOrderClass::Market, | ||
}, | ||
// Tests ability of providing multiple fees in the config. In fact, the Market one is used | ||
// only since the order is in-market. | ||
ProtocolFee { | ||
policy: fee_policy, | ||
policy_order_class: FeePolicyOrderClass::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.
How about making a single test case that contains a limit order with surplus fee and a market order with price improvement fee (and potentially a third order with partner fee) in a batch to make sure all fee models that are used in practice work as expected?
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 tried to configure out-of-market order and couldn't make it work yet. Assuming it is more involved change, wanted to tackle it in scope of the #2468 as a separate PR(probably, after the market order fees release and 0x liquidity)
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, not sure how to keep this assertion with a single batch of multiple orders. Maybe execute them sequentially.
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, not sure how to keep this assertion with a single batch of multiple orders
How about using different tokens for the different orders?
I tried to configure out-of-market order and couldn't make it work yet.
The easiest way to make an out of market order "work" IMO is to create an AMM with balances A and B, get a quote and place an order this is "out of market" given that quote. Then you send a ton of A into the AMM (making the price change and the order now being executable).
async fn local_node_partner_fee_with_not_default_cap_sell_order_test() { | ||
run_test(partner_fee_with_not_default_cap_sell_order_test).await; | ||
} | ||
let mut onchain = OnchainComponents::deploy(web3.clone()).await; |
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.
GitHub shows a pretty ugly and misleading diff 🙈
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.
Generally I think the test looks good, but let's please get rid of those magic numbers at the bottom and use more meaningful assertions that relate to the actual logic we are testing.
]) | ||
.await; | ||
|
||
let market_price_improvement_order = OrderCreation { |
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.
How do I know this is a market order? I think it would be nice to quote this order and then use limits that are worse than the quote.
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.
Used a slightly decreased value from the quote.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
tracing::info!("Waiting for liquidity state to update"); | ||
wait_for_condition(TIMEOUT, || async { | ||
// Mint blocks until we evict the cached liquidity and fetch the new state. | ||
onchain.mint_block().await; | ||
let next = services.submit_quote("e_request).await.unwrap(); | ||
next.quote.buy_amount != quote.quote.buy_amount | ||
}) | ||
.await | ||
.unwrap(); | ||
|
||
tracing::info!("Waiting for trade."); | ||
wait_for_condition(TIMEOUT, || async { services.solvable_orders().await == 3 }) | ||
.await | ||
.unwrap(); | ||
|
||
wait_for_condition(TIMEOUT, || async { services.solvable_orders().await == 0 }) | ||
.await | ||
.unwrap(); | ||
|
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 are all of these waits important? Couldn't we just wait for the limit order to trade? This could make this section shorter to read.
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 suspect specifically the auction_size == 3
condition is there to make a batch happen. But couldn't this result in a flaky test? For example is it guaranteed that none of the other 2 orders can be settled while we wait for the rebalancing to happen. I believe to make this bullet proof we might have to have a driver endpoint used for quoting and a driver endpoint for solving and only enable the solving endpoint after the auction has the expected size.
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 starting the driver's solving endpoint separately isn't straightforward, replaced with the following assertion, which should probably wait until all the orders are executed and filled: 54507fc
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
.unwrap(); | ||
assert_approximately( | ||
market_price_improvement.metadata.executed_surplus_fee, | ||
202972993230628u128.into(), |
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.
Are we actually seeing any price improvement here? Nothing about the pool changed, so this order should be executed as expected. I think we should:
- Get a quote for the trade and use that as limit price (expected price)
- Place the order
- Inject more liquidity into the pool
- Get another quote (now with expected price improvement).
Assert that executed_surplus_fee
> 1/2 of the difference in the two quotes
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
.call() | ||
.await | ||
.unwrap(); | ||
assert_approximately(balance_after, 66641691187129028u128.into()); |
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 instead of using a magic number here (which we likely computed by running the test once and then blindly accepting its outcome as the right value to assert), I think a more robust test would be to assert that balance_after is > 2% of the sell amount.
This would give a more meaningful and robust assertion.
# Conflicts: # crates/e2e/tests/e2e/protocol_fee.rs
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.
Assertions look good. I think given the size of this test, the PR would benefit from doing another "clean-up" pass, where we try to minimize code that is not strictly necessary and put ourselves into the shoes of a future maintainer that has to understand what this test does in 6 months from now (adding more context and comments).
I commented with some suggestions in line.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
let services = Services::new(onchain.contracts()).await; | ||
let solver_endpoint = | ||
colocation::start_baseline_solver(onchain.contracts().weth.address()).await; | ||
colocation::start_driver( | ||
onchain.contracts(), | ||
vec![SolverEngine { | ||
name: "test_solver".into(), | ||
account: solver.clone(), | ||
endpoint: solver_endpoint, | ||
}], | ||
); | ||
services | ||
.start_api(vec![ | ||
"--price-estimation-drivers=test_quoter|http://localhost:11088/test_solver".to_string(), | ||
]) | ||
.await; |
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.
Any reason we cannot use start_protocol_with_args
(shorter)?
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.
If we want to batch all the orders, autopilot must be started after all the orders are created.
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.
Is there anything about the batching that is relevant to this test though?
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
vec![ProtocolFeesConfig(vec![protocol_fee])], | ||
.await | ||
.unwrap(); | ||
let partner_fee_quote_before = get_quote( |
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.
Does partner fee need a quote?
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, not. Removed as well as the AMM pool rebalancing for the partner fee order.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
config.extend(autopilot_config); | ||
services.start_autopilot(None, config); | ||
|
||
tracing::info!("Rebalancing limit order AMM pool."); |
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.
Can we not rebalance all pools in one go?
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 do you mean by that?
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.
Right now you have two blocks of tracing::info!("Rebalancing ...");
(line ~200 and line ~255). I think it would be cleaner to do all AMM rebalancing right after one another.
let market_quote_diff = market_quote_after | ||
.buy_amount | ||
.saturating_sub(market_quote_before.quote.buy_amount); | ||
assert!(market_executed_surplus_fee_in_buy_token >= market_quote_diff * 3 / 10); |
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 (* 3 / 10). Can you either comment this for future maintainer to know where this value comes from without having to read 300 lines of code or factor this in a variable that is shared between the config and here?
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.
Added comments.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
market_price_improvement_order.metadata.executed_surplus_fee | ||
* market_quote_after.buy_amount | ||
/ market_quote_after.sell_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.
nit: maybe factor into a method that can be reused.
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.
Refactored
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
/ partner_fee_quote_after.sell_amount; | ||
assert!( | ||
partner_fee_executed_surplus_fee_in_buy_token | ||
>= partner_fee_quote_after.buy_amount * 2 / 100 |
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.
If someone changes the fee policy above this test may pass because there is no upper bound check. Can we somehow approximate this?
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.
The difference here comes from the network fee, which is computed by the baseline solver. Not sure it is possible to predict the value based on the given data. The value can be hardcoded. Wdyt?
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.
After reading the test again, I think the fact that we exactly assert on the settlement balances ensures that even if we drastically overcomputed the fee, the test would fail, so I think it's fine.
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.
Code can probably be condensed a lot.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
async fn local_node_volume_fee_buy_order() { | ||
run_test(volume_fee_buy_order_test).await; | ||
} | ||
limit_order_token.mint(solver.address(), to_wei(1000)).await; |
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 slightly nicer to do sth like:
for token in [limit_order_token, market_order_token, partner_fee_order_token] {
// mint token to solver address
// approve router contract
}
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.
Thx, refactored.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
let market_price_improvement_order = OrderCreation { | ||
sell_token: onchain.contracts().weth.address(), | ||
sell_amount, | ||
buy_token: market_order_token.address(), |
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.
nit: Since we already have the quote it seems more error resistant to re-use the values from the quote whenever possible.
I.e. buy_token: market_quote_before.quote.buy_token
or maybe even better sth like:
OrderCreation {
buy_amount: market_quote_before.quote.buy_amount * 2 / 3,
..order_from_quote(market_quote_before)
}
That way it's absolutely clear that this order was created from a quote and explicitly overwrites a single value to make it work for the test.
Also since we have fixed size arrays we can probably do sth like:
let [market_quote, limit_quote, _parter_fee_quote] = [market_token, limit_token, partner_fee_token].map(|token| /*fetch quote*/);
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.
Refactored
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
.mint_token_to_weth_uni_v2_pool(&limit_order_token, to_wei(1000)) | ||
.await; | ||
|
||
tracing::info!("Waiting for liquidity state to update"); |
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.
Don't we still have the (maybe only theoretical) race condition that we start the autopilot before all liquidity pools are configured such that orders can be settled right away?
If so we don't have any guarantees that these orders actually get settled in the same batch like the test intends. Or am I missing something?
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.
Looks like Felix beat me, and now his commit addresses this.
crates/e2e/tests/e2e/protocol_fee.rs
Outdated
tracing::info!("Waiting for orders metadata update."); | ||
let metadata_updated = || async { | ||
onchain.mint_block().await; | ||
let market_order_updated = services |
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.
Can also use an iterator.
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.
Refactored
.saturating_sub(limit_surplus_order.data.buy_amount); | ||
assert!(limit_executed_surplus_fee_in_buy_token >= limit_quote_diff * 3 / 10); | ||
|
||
let balance_after = market_order_token |
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.
Could also use iterators.
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.
While iterators really help with preparing the data, it will be painful to find out which assertion fails.
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.
Yeah, I only meant to fetch the balances in a loop.
# Conflicts: # crates/e2e/tests/e2e/protocol_fee.rs
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.
LGTM (I pushed a minor cleanup on the latest PR)
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.
Description
Removes exhaustive protocol fee e2e tests since tests implemented in the driver crate cover many other cases. Each protocol fee now has a single e2e test.
Fixes #2468