@@ -39,6 +39,12 @@ async fn local_node_quote_timeout() {
3939 run_test ( quote_timeout) . await ;
4040}
4141
42+ #[ tokio:: test]
43+ #[ ignore]
44+ async fn local_node_volume_fee ( ) {
45+ run_test ( volume_fee) . await ;
46+ }
47+
4248// Test that quoting works as expected, specifically, that we can quote for a
4349// token pair and additional gas from ERC-1271 and hooks are included in the
4450// quoted fee amount.
@@ -403,3 +409,106 @@ async fn quote_timeout(web3: Web3) {
403409 assert ! ( res. unwrap_err( ) . 1 . contains( "NoLiquidity" ) ) ;
404410 assert_within_variance ( start, MAX_QUOTE_TIME_MS ) ;
405411}
412+
413+ /// Test that volume fees are correctly applied to quotes.
414+ async fn volume_fee ( web3 : Web3 ) {
415+ tracing:: info!( "Setting up chain state." ) ;
416+ let mut onchain = OnchainComponents :: deploy ( web3) . await ;
417+
418+ let [ solver] = onchain. make_solvers ( to_wei ( 10 ) ) . await ;
419+ let [ trader] = onchain. make_accounts ( to_wei ( 10 ) ) . await ;
420+ let [ token] = onchain
421+ . deploy_tokens_with_weth_uni_v2_pools ( to_wei ( 1_000 ) , to_wei ( 1_000 ) )
422+ . await ;
423+
424+ onchain
425+ . contracts ( )
426+ . weth
427+ . approve ( onchain. contracts ( ) . allowance . into_alloy ( ) , eth ( 3 ) )
428+ . from ( trader. address ( ) . into_alloy ( ) )
429+ . send_and_watch ( )
430+ . await
431+ . unwrap ( ) ;
432+ onchain
433+ . contracts ( )
434+ . weth
435+ . deposit ( )
436+ . from ( trader. address ( ) . into_alloy ( ) )
437+ . value ( eth ( 3 ) )
438+ . send_and_watch ( )
439+ . await
440+ . unwrap ( ) ;
441+
442+ tracing:: info!( "Starting services with volume fee." ) ;
443+ let services = Services :: new ( & onchain) . await ;
444+ // Start API with 0.02% (2 bps) volume fee
445+ let args = ExtraServiceArgs {
446+ api : vec ! [ "--volume-fee=0.0002" . to_string( ) ] ,
447+ ..Default :: default ( )
448+ } ;
449+ services. start_protocol_with_args ( args, solver) . await ;
450+
451+ tracing:: info!( "Testing SELL quote with volume fee" ) ;
452+ let sell_request = OrderQuoteRequest {
453+ from : trader. address ( ) ,
454+ sell_token : onchain. contracts ( ) . weth . address ( ) . into_legacy ( ) ,
455+ buy_token : token. address ( ) . into_legacy ( ) ,
456+ side : OrderQuoteSide :: Sell {
457+ sell_amount : SellAmount :: BeforeFee {
458+ value : NonZeroU256 :: try_from ( to_wei ( 1 ) ) . unwrap ( ) ,
459+ } ,
460+ } ,
461+ ..Default :: default ( )
462+ } ;
463+
464+ let sell_quote = services. submit_quote ( & sell_request) . await . unwrap ( ) ;
465+
466+ // Verify protocol fee fields are present
467+ assert ! ( sell_quote. protocol_fee_bps. is_some( ) ) ;
468+ assert_eq ! ( sell_quote. protocol_fee_bps. as_ref( ) . unwrap( ) , "2" ) ;
469+ assert ! ( sell_quote. protocol_fee_sell_amount. is_some( ) ) ;
470+
471+ // For SELL orders: buy_amount should be reduced by the protocol fee
472+ let protocol_fee_sell_amount = sell_quote
473+ . protocol_fee_sell_amount
474+ . unwrap ( )
475+ . to_string ( )
476+ . parse :: < u128 > ( )
477+ . unwrap ( ) ;
478+ assert ! (
479+ protocol_fee_sell_amount > 0 ,
480+ "Protocol fee should be non-zero"
481+ ) ;
482+
483+ tracing:: info!( "Testing BUY quote with volume fee" ) ;
484+ let buy_request = OrderQuoteRequest {
485+ from : trader. address ( ) ,
486+ sell_token : onchain. contracts ( ) . weth . address ( ) . into_legacy ( ) ,
487+ buy_token : token. address ( ) . into_legacy ( ) ,
488+ side : OrderQuoteSide :: Buy {
489+ buy_amount_after_fee : NonZeroU256 :: try_from ( to_wei ( 1 ) ) . unwrap ( ) ,
490+ } ,
491+ ..Default :: default ( )
492+ } ;
493+
494+ let buy_quote = services. submit_quote ( & buy_request) . await . unwrap ( ) ;
495+
496+ // Verify protocol fee fields are present
497+ assert ! ( buy_quote. protocol_fee_bps. is_some( ) ) ;
498+ assert_eq ! ( buy_quote. protocol_fee_bps. as_ref( ) . unwrap( ) , "2" ) ;
499+ assert ! ( buy_quote. protocol_fee_sell_amount. is_some( ) ) ;
500+
501+ // For BUY orders: sell_amount should be increased by the protocol fee
502+ let protocol_fee_sell_amount_buy = buy_quote
503+ . protocol_fee_sell_amount
504+ . unwrap ( )
505+ . to_string ( )
506+ . parse :: < u128 > ( )
507+ . unwrap ( ) ;
508+ assert ! (
509+ protocol_fee_sell_amount_buy > 0 ,
510+ "Protocol fee should be non-zero for buy orders"
511+ ) ;
512+
513+ tracing:: info!( "Volume fee test passed" ) ;
514+ }
0 commit comments