use num_bigint::BigUint;
use crate::encoding::models::{Solution, Strategy, UserTransferType};
pub const DEFAULT_TOKEN_TRANSFER_GAS: u64 = 40_000;
pub const TOKEN_APPROVAL_GAS: u64 = 25_000;
pub const USER_PERMIT2_TRANSFER: u64 = 80_000;
pub const PROTOCOLS_CALLBACK: &[&str] = &[
"uniswap_v3",
"pancakeswap_v3",
"uniswap_v4",
"uniswap_v4_hooks",
"ekubo_v2",
"ekubo_v3",
"aerodrome_slipstreams",
"velodrome_slipstreams",
"vm:balancer_v3",
];
pub const PROTOCOLS_OPTIMIZABLE_TRANSFER_IN: &[&str] = &["erc4626", "maverick_v2", "uniswap_v2"];
pub const PROTOCOLS_NEEDING_APPROVAL: &[&str] =
&["vm:balancer_v2", "vm:curve", "rfq:bebop", "rfq:hashflow", "rfq:liquorice", "erc4626"];
pub const PROTOCOLS_OUTPUT_TO_ROUTER: &[&str] = &["vm:curve", "rocketpool", "fluid_v1", "weth"];
pub const ROUTER_FEES_ACTIVE: bool = true;
pub fn estimate_gas_usage(solution: &Solution, strategy: Strategy) -> BigUint {
let mut total_gas = BigUint::ZERO;
for swap in solution.swaps() {
let swap_transfer_overhead = estimate_transfer_overhead(
&swap.component().protocol_system,
swap.token_in(),
swap.token_out(),
&strategy,
);
total_gas += swap_transfer_overhead + swap.estimated_gas();
}
if strategy != Strategy::Split {
if let Some(first_swap) = solution.swaps().first() {
let protocol: &str = &first_swap.component().protocol_system;
if PROTOCOLS_CALLBACK.contains(&protocol) {
if *solution.user_transfer_type() == UserTransferType::TransferFromPermit2 {
total_gas += BigUint::from(USER_PERMIT2_TRANSFER - DEFAULT_TOKEN_TRANSFER_GAS);
}
} else {
total_gas += BigUint::from(match *solution.user_transfer_type() {
UserTransferType::TransferFromPermit2 => USER_PERMIT2_TRANSFER,
_ => DEFAULT_TOKEN_TRANSFER_GAS,
});
}
}
} else {
total_gas += BigUint::from(match *solution.user_transfer_type() {
UserTransferType::TransferFromPermit2 => USER_PERMIT2_TRANSFER,
_ => DEFAULT_TOKEN_TRANSFER_GAS,
});
}
if let Some(last_swap) = solution.swaps().last() {
let protocol: &str = &last_swap.component().protocol_system;
let final_swap_output_to_router = (ROUTER_FEES_ACTIVE || strategy == Strategy::Split) &&
!PROTOCOLS_OUTPUT_TO_ROUTER.contains(&protocol);
if final_swap_output_to_router {
total_gas += transfer_token_gas(last_swap.token_out());
}
}
total_gas
}
fn transfer_token_gas(token: &tycho_common::models::token::Token) -> BigUint {
let measured = token.gas_usage();
if measured == BigUint::ZERO {
BigUint::from(DEFAULT_TOKEN_TRANSFER_GAS)
} else {
measured
}
}
fn estimate_transfer_overhead(
protocol_system: &str,
token_in: &tycho_common::models::token::Token,
token_out: &tycho_common::models::token::Token,
strategy: &Strategy,
) -> BigUint {
let mut overhead = BigUint::ZERO;
if !PROTOCOLS_CALLBACK.contains(&protocol_system) &&
(!PROTOCOLS_OPTIMIZABLE_TRANSFER_IN.contains(&protocol_system) ||
*strategy == Strategy::Split)
{
overhead += transfer_token_gas(token_in);
}
if PROTOCOLS_NEEDING_APPROVAL.contains(&protocol_system) {
overhead += BigUint::from(TOKEN_APPROVAL_GAS);
}
if PROTOCOLS_OUTPUT_TO_ROUTER.contains(&protocol_system) {
overhead += transfer_token_gas(token_out);
}
overhead
}
#[cfg(test)]
mod tests {
use num_bigint::BigUint;
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::{default_token, Solution, Strategy, Swap, UserTransferType};
fn make_swap(protocol: &str) -> Swap {
Swap::new(
ProtocolComponent { protocol_system: protocol.to_string(), ..Default::default() },
default_token(Bytes::from(vec![0x01u8; 20])),
default_token(Bytes::from(vec![0x02u8; 20])),
BigUint::from(100_000u64), )
}
fn make_solution(swaps: Vec<Swap>) -> Solution {
Solution::new(
Bytes::from(vec![0x00u8; 20]),
Bytes::from(vec![0x00u8; 20]),
Bytes::from(vec![0x01u8; 20]),
Bytes::from(vec![0x02u8; 20]),
BigUint::from(1u64),
BigUint::from(0u64),
swaps,
)
}
#[test]
fn test_single_optimizable_transfer_in() {
let solution = make_solution(vec![make_swap("uniswap_v2")]);
let gas = estimate_gas_usage(&solution, Strategy::Single);
assert_eq!(gas, BigUint::from(200_000u64));
}
#[test]
fn test_single_callback_protocol() {
let solution = make_solution(vec![make_swap("uniswap_v3")]);
let gas = estimate_gas_usage(&solution, Strategy::Single);
assert_eq!(gas, BigUint::from(160_000u64));
}
#[test]
fn test_single_permit2_with_approval() {
let solution = make_solution(vec![make_swap("rfq:bebop")])
.with_user_transfer_type(UserTransferType::TransferFromPermit2);
let gas = estimate_gas_usage(&solution, Strategy::Single);
assert_eq!(gas, BigUint::from(325_000u64));
}
#[test]
fn test_sequential_two_hops() {
let solution = make_solution(vec![make_swap("uniswap_v2"), make_swap("uniswap_v3")]);
let gas = estimate_gas_usage(&solution, Strategy::Sequential);
assert_eq!(gas, BigUint::from(300_000u64));
}
#[test]
fn test_sequential_two_hops_output_to_router() {
let solution = make_solution(vec![make_swap("vm:curve"), make_swap("uniswap_v2")]);
let gas = estimate_gas_usage(&solution, Strategy::Sequential);
assert_eq!(gas, BigUint::from(445_000u64));
}
#[test]
fn test_split_two_legs() {
let solution = make_solution(vec![
make_swap("uniswap_v2").with_split(0.5),
make_swap("uniswap_v3").with_split(0.5),
]);
let gas = estimate_gas_usage(&solution, Strategy::Split);
assert_eq!(gas, BigUint::from(360_000u64));
}
}