use std::sync::Arc;
#[cfg(feature = "evm")]
use alloy::primitives::{Address, U256};
use clap::ValueEnum;
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
use tycho_common::{
models::{protocol::ProtocolComponent, token::Token},
simulation::protocol_sim::ProtocolSim,
Bytes,
};
use crate::encoding::serde_primitives::biguint_string;
#[derive(Clone, Debug, PartialEq, ValueEnum, Serialize, Deserialize, Default)]
pub enum UserTransferType {
TransferFromPermit2,
#[default]
TransferFrom,
UseVaultsFunds,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ClientFeeParams {
client_fee_bps: u16,
client_fee_receiver: Bytes,
#[serde(with = "biguint_string")]
max_client_contribution: BigUint,
#[serde(with = "biguint_string")]
deadline: BigUint,
client_signature: Bytes,
}
impl ClientFeeParams {
pub fn new(
client_fee_receiver: Bytes,
client_signature: Bytes,
deadline: BigUint,
client_fee_bps: u16,
) -> Self {
Self {
client_fee_bps,
client_fee_receiver,
max_client_contribution: BigUint::ZERO,
deadline,
client_signature,
}
}
pub fn new_without_fee(
client_fee_receiver: Bytes,
client_signature: Bytes,
deadline: BigUint,
) -> Self {
Self {
client_fee_bps: 0,
client_fee_receiver,
max_client_contribution: BigUint::ZERO,
deadline,
client_signature,
}
}
pub fn with_max_client_contribution(mut self, max_client_contribution: BigUint) -> Self {
self.max_client_contribution = max_client_contribution;
self
}
}
#[cfg(feature = "evm")]
impl ClientFeeParams {
pub fn into_abi_params(self) -> (u16, Address, U256, U256, Vec<u8>) {
let receiver = if self.client_fee_receiver.is_empty() {
Address::ZERO
} else {
Address::from_slice(&self.client_fee_receiver)
};
(
self.client_fee_bps,
receiver,
U256::from_be_slice(
&self
.max_client_contribution
.to_bytes_be(),
),
U256::from_be_slice(&self.deadline.to_bytes_be()),
self.client_signature.to_vec(),
)
}
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct Solution {
sender: Bytes,
receiver: Bytes,
token_in: Bytes,
#[serde(with = "biguint_string")]
amount_in: BigUint,
token_out: Bytes,
#[serde(with = "biguint_string")]
min_amount_out: BigUint,
swaps: Vec<Swap>,
user_transfer_type: UserTransferType,
}
impl Solution {
pub fn new(
sender: Bytes,
receiver: Bytes,
token_in: Bytes,
token_out: Bytes,
amount_in: BigUint,
min_amount_out: BigUint,
swaps: Vec<Swap>,
) -> Self {
Self {
sender,
receiver,
token_in,
token_out,
amount_in,
min_amount_out,
swaps,
user_transfer_type: UserTransferType::TransferFrom,
}
}
pub fn sender(&self) -> &Bytes {
&self.sender
}
pub fn receiver(&self) -> &Bytes {
&self.receiver
}
pub fn token_in(&self) -> &Bytes {
&self.token_in
}
pub fn amount_in(&self) -> &BigUint {
&self.amount_in
}
pub fn token_out(&self) -> &Bytes {
&self.token_out
}
pub fn min_amount_out(&self) -> &BigUint {
&self.min_amount_out
}
pub fn swaps(&self) -> &[Swap] {
&self.swaps
}
pub fn user_transfer_type(&self) -> &UserTransferType {
&self.user_transfer_type
}
pub fn with_sender(mut self, sender: Bytes) -> Self {
self.sender = sender;
self
}
pub fn with_receiver(mut self, receiver: Bytes) -> Self {
self.receiver = receiver;
self
}
pub fn with_token_in(mut self, token_in: Bytes) -> Self {
self.token_in = token_in;
self
}
pub fn with_amount_in(mut self, amount_in: BigUint) -> Self {
self.amount_in = amount_in;
self
}
pub fn with_token_out(mut self, token_out: Bytes) -> Self {
self.token_out = token_out;
self
}
pub fn with_min_amount_out(mut self, min_amount_out: BigUint) -> Self {
self.min_amount_out = min_amount_out;
self
}
pub fn with_swaps(mut self, swaps: Vec<Swap>) -> Self {
self.swaps = swaps;
self
}
pub fn with_user_transfer_type(mut self, user_transfer_type: UserTransferType) -> Self {
self.user_transfer_type = user_transfer_type;
self
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Swap {
component: ProtocolComponent,
token_in: Token,
token_out: Token,
#[serde(default)]
split: f64,
user_data: Option<Bytes>,
#[serde(skip)]
protocol_state: Option<Arc<dyn ProtocolSim>>,
estimated_amount_in: Option<BigUint>,
estimated_gas: BigUint,
}
impl Swap {
pub fn new<T: Into<ProtocolComponent>>(
component: T,
token_in: Token,
token_out: Token,
estimated_gas: BigUint,
) -> Self {
Self {
component: component.into(),
token_in,
token_out,
split: 0.0,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
estimated_gas,
}
}
pub fn with_split(mut self, split: f64) -> Self {
self.split = split;
self
}
pub fn with_user_data(mut self, user_data: Bytes) -> Self {
self.user_data = Some(user_data);
self
}
pub fn with_protocol_state(mut self, protocol_state: Arc<dyn ProtocolSim>) -> Self {
self.protocol_state = Some(protocol_state);
self
}
pub fn with_estimated_amount_in(mut self, estimated_amount_in: BigUint) -> Self {
self.estimated_amount_in = Some(estimated_amount_in);
self
}
pub fn component(&self) -> &ProtocolComponent {
&self.component
}
pub fn token_in(&self) -> &Token {
&self.token_in
}
pub fn token_out(&self) -> &Token {
&self.token_out
}
pub fn split(&self) -> f64 {
self.split
}
pub fn user_data(&self) -> &Option<Bytes> {
&self.user_data
}
pub fn protocol_state(&self) -> &Option<Arc<dyn ProtocolSim>> {
&self.protocol_state
}
pub fn estimated_amount_in(&self) -> &Option<BigUint> {
&self.estimated_amount_in
}
pub fn estimated_gas(&self) -> &BigUint {
&self.estimated_gas
}
}
impl PartialEq for Swap {
fn eq(&self, other: &Self) -> bool {
self.component() == other.component() &&
self.token_in().address == other.token_in().address &&
self.token_out().address == other.token_out().address &&
self.split() == other.split() &&
self.user_data() == other.user_data() &&
self.estimated_amount_in() == other.estimated_amount_in() &&
self.estimated_gas() == other.estimated_gas()
}
}
#[derive(Clone, Debug)]
pub struct EncodedSolution {
swaps: Vec<u8>,
interacting_with: Bytes,
function_signature: String,
n_tokens: usize,
estimated_gas: BigUint,
}
impl EncodedSolution {
pub(crate) fn new(
swaps: Vec<u8>,
interacting_with: Bytes,
function_signature: String,
n_tokens: usize,
estimated_gas: BigUint,
) -> Self {
Self { swaps, interacting_with, function_signature, n_tokens, estimated_gas }
}
pub fn swaps(&self) -> &[u8] {
&self.swaps
}
pub fn interacting_with(&self) -> &Bytes {
&self.interacting_with
}
pub fn function_signature(&self) -> &str {
&self.function_signature
}
pub fn n_tokens(&self) -> usize {
self.n_tokens
}
pub fn estimated_gas(&self) -> &BigUint {
&self.estimated_gas
}
pub fn client_fee_signature_offset(&self) -> usize {
let name = self
.function_signature
.split('(')
.next()
.unwrap_or("");
let head_params = match name {
"singleSwap" |
"singleSwapUsingVault" |
"sequentialSwap" |
"sequentialSwapUsingVault" => 7,
"splitSwap" | "splitSwapUsingVault" => 8,
"singleSwapPermit2" | "sequentialSwapPermit2" => 14,
"splitSwapPermit2" => 15,
_ => 0,
};
4 + head_params * 32 + 192
}
}
#[derive(Debug, Clone)]
pub struct PermitSingle {
details: PermitDetails,
spender: Bytes,
sig_deadline: BigUint,
}
impl PermitSingle {
pub fn new(details: PermitDetails, spender: Bytes, sig_deadline: BigUint) -> Self {
Self { details, spender, sig_deadline }
}
pub fn details(&self) -> &PermitDetails {
&self.details
}
pub fn spender(&self) -> &Bytes {
&self.spender
}
pub fn sig_deadline(&self) -> &BigUint {
&self.sig_deadline
}
}
#[derive(Debug, Clone)]
pub struct PermitDetails {
token: Bytes,
amount: BigUint,
expiration: BigUint,
nonce: BigUint,
}
impl PermitDetails {
pub fn new(token: Bytes, amount: BigUint, expiration: BigUint, nonce: BigUint) -> Self {
Self { token, amount, expiration, nonce }
}
pub fn token(&self) -> &Bytes {
&self.token
}
pub fn amount(&self) -> &BigUint {
&self.amount
}
pub fn expiration(&self) -> &BigUint {
&self.expiration
}
pub fn nonce(&self) -> &BigUint {
&self.nonce
}
}
impl PartialEq for PermitSingle {
fn eq(&self, other: &Self) -> bool {
self.details == other.details && self.spender == other.spender
}
}
impl PartialEq for PermitDetails {
fn eq(&self, other: &Self) -> bool {
self.token == other.token && self.amount == other.amount && self.nonce == other.nonce
}
}
#[derive(Clone, Debug)]
pub struct EncodingContext {
pub router_address: Option<Bytes>,
pub group_token_in: Bytes,
pub group_token_out: Bytes,
}
#[derive(PartialEq)]
pub enum Strategy {
Single,
Sequential,
Split,
}
#[cfg(any(test, feature = "test-utils"))]
pub fn default_token(address: Bytes) -> Token {
Token::new(&address, "", 0, 0, &[Some(60_000u64)], Default::default(), 100)
}
mod tests {
use super::*;
struct MockProtocolComponent {
id: String,
protocol_system: String,
}
impl From<MockProtocolComponent> for ProtocolComponent {
fn from(component: MockProtocolComponent) -> Self {
ProtocolComponent {
id: component.id,
protocol_system: component.protocol_system,
tokens: vec![],
protocol_type_name: "".to_string(),
chain: Default::default(),
contract_addresses: vec![],
static_attributes: Default::default(),
change: Default::default(),
creation_tx: Default::default(),
created_at: Default::default(),
}
}
}
#[test]
fn test_swap_new() {
let component = MockProtocolComponent {
id: "i-am-an-id".to_string(),
protocol_system: "uniswap_v2".to_string(),
};
let user_data = Bytes::from("0x1234");
let swap = Swap::new(
component,
default_token(Bytes::from("0x12")),
default_token(Bytes::from("0x34")),
BigUint::ZERO,
)
.with_split(0.5)
.with_user_data(user_data.clone());
assert_eq!(swap.token_in().address, Bytes::from("0x12"));
assert_eq!(swap.token_out().address, Bytes::from("0x34"));
assert_eq!(swap.component().protocol_system, "uniswap_v2");
assert_eq!(swap.component().id, "i-am-an-id");
assert_eq!(swap.split(), 0.5);
assert_eq!(swap.user_data(), &Some(user_data));
}
}