feat: Create non alloy specific Permit and PermitDetails structs

- Add try_from methods to convert them to the corresponding Solidity representations
- Update tests

Took 56 seconds
This commit is contained in:
Diana Carvalho
2025-05-23 11:31:05 +01:00
parent 52d3641436
commit 75f2c3a1c5
7 changed files with 177 additions and 90 deletions

View File

@@ -23,6 +23,7 @@ use crate::encoding::{
encoding_utils::encode_input,
utils::{biguint_to_u256, bytes_to_address, get_client, get_runtime},
},
models,
models::Chain,
};
@@ -68,6 +69,44 @@ sol! {
}
}
impl TryFrom<PermitSingle> for models::PermitSingle {
type Error = EncodingError;
fn try_from(sol: PermitSingle) -> Result<Self, EncodingError> {
Ok(models::PermitSingle {
details: models::PermitDetails {
token: Bytes::from(sol.details.token.to_vec()),
amount: BigUint::from_bytes_be(&sol.details.amount.to_be_bytes::<20>()),
expiration: BigUint::from_bytes_be(
&sol.details
.expiration
.to_be_bytes::<6>(),
),
nonce: BigUint::from_bytes_be(&sol.details.nonce.to_be_bytes::<6>()),
},
spender: Bytes::from(sol.spender.to_vec()),
sig_deadline: BigUint::from_bytes_be(&sol.sigDeadline.to_be_bytes::<32>()),
})
}
}
impl TryFrom<models::PermitSingle> for PermitSingle {
type Error = EncodingError;
fn try_from(p: models::PermitSingle) -> Result<Self, EncodingError> {
Ok(PermitSingle {
details: PermitDetails {
token: bytes_to_address(&p.details.token)?,
amount: U160::from(biguint_to_u256(&p.details.amount)),
expiration: U48::from(biguint_to_u256(&p.details.expiration)),
nonce: U48::from(biguint_to_u256(&p.details.nonce)),
},
spender: bytes_to_address(&p.spender)?,
sigDeadline: biguint_to_u256(&p.sig_deadline),
})
}
}
impl Permit2 {
pub fn new(swapper_pk: String, chain: Chain) -> Result<Self, EncodingError> {
let (handle, runtime) = get_runtime()?;
@@ -130,7 +169,7 @@ impl Permit2 {
owner: &Bytes,
token: &Bytes,
amount: &BigUint,
) -> Result<(PermitSingle, Signature), EncodingError> {
) -> Result<(models::PermitSingle, Signature), EncodingError> {
let current_time = Utc::now()
.naive_utc()
.and_utc()
@@ -163,7 +202,7 @@ impl Permit2 {
"Failed to sign permit2 approval with error: {e}"
))
})?;
Ok((permit_single, signature))
Ok((models::PermitSingle::try_from(permit_single)?, signature))
}
}
@@ -247,16 +286,16 @@ mod tests {
.get_permit(&spender, &owner, &token, &amount)
.unwrap();
let expected_details = PermitDetails {
token: bytes_to_address(&token).unwrap(),
amount: U160::from(biguint_to_u256(&amount)),
expiration: U48::from(Utc::now().timestamp() as u64 + PERMIT_EXPIRATION),
nonce: U48::from(0),
let expected_details = models::PermitDetails {
token,
amount,
expiration: BigUint::from(Utc::now().timestamp() as u64 + PERMIT_EXPIRATION),
nonce: BigUint::from(0u64),
};
let expected_permit_single = PermitSingle {
let expected_permit_single = models::PermitSingle {
details: expected_details,
spender: Address::from_str("0xba12222222228d8ba445958a75a0704d566bf2c8").unwrap(),
sigDeadline: U256::from(Utc::now().timestamp() as u64 + PERMIT_SIG_EXPIRATION),
spender: Bytes::from_str("0xba12222222228d8ba445958a75a0704d566bf2c8").unwrap(),
sig_deadline: BigUint::from(Utc::now().timestamp() as u64 + PERMIT_SIG_EXPIRATION),
};
assert_eq!(
@@ -310,8 +349,10 @@ mod tests {
let (permit, signature) = permit2
.get_permit(&spender, &anvil_account, &token, &amount)
.unwrap();
let sol_permit: PermitSingle =
PermitSingle::try_from(permit).expect("Failed to convert to PermitSingle");
let encoded =
(bytes_to_address(&anvil_account).unwrap(), permit, signature.as_bytes().to_vec())
(bytes_to_address(&anvil_account).unwrap(), sol_permit, signature.as_bytes().to_vec())
.abi_encode();
let function_signature =

View File

@@ -5,7 +5,10 @@ use tycho_common::Bytes;
use crate::encoding::{
errors::EncodingError,
evm::utils::{biguint_to_u256, bytes_to_address},
evm::{
approvals::permit2::PermitSingle,
utils::{biguint_to_u256, bytes_to_address},
},
models::{EncodedSolution, NativeAction, Solution, Transaction},
};
@@ -103,11 +106,23 @@ pub fn encode_tycho_router_call(
let checked_token = bytes_to_address(&solution.checked_token)?;
let receiver = bytes_to_address(&solution.receiver)?;
let n_tokens = U256::from(encoded_solution.n_tokens);
let permit = if let Some(p) = encoded_solution.permit {
Some(
PermitSingle::try_from(p)
.map_err(|_| EncodingError::InvalidInput("Invalid permit".to_string()))?,
)
} else {
None
};
let method_calldata = if encoded_solution
.selector
.contains("singleSwapPermit2")
{
let sig = encoded_solution
.signature
.ok_or(EncodingError::FatalError("Signature must be set to use permit2".to_string()))?;
println!("sig {:}", hex::encode(&sig));
(
given_amount,
given_token,
@@ -116,18 +131,10 @@ pub fn encode_tycho_router_call(
wrap,
unwrap,
receiver,
encoded_solution
.permit
.ok_or(EncodingError::FatalError(
"permit2 object must be set to use permit2".to_string(),
))?,
encoded_solution
.signature
.ok_or(EncodingError::FatalError(
"Signature must be set to use permit2".to_string(),
))?
.as_bytes()
.to_vec(),
permit.ok_or(EncodingError::FatalError(
"permit2 object must be set to use permit2".to_string(),
))?,
sig,
encoded_solution.swaps,
)
.abi_encode()
@@ -159,18 +166,14 @@ pub fn encode_tycho_router_call(
wrap,
unwrap,
receiver,
encoded_solution
.permit
.ok_or(EncodingError::FatalError(
"permit2 object must be set to use permit2".to_string(),
))?,
permit.ok_or(EncodingError::FatalError(
"permit2 object must be set to use permit2".to_string(),
))?,
encoded_solution
.signature
.ok_or(EncodingError::FatalError(
"Signature must be set to use permit2".to_string(),
))?
.as_bytes()
.to_vec(),
))?,
encoded_solution.swaps,
)
.abi_encode()
@@ -203,18 +206,14 @@ pub fn encode_tycho_router_call(
unwrap,
n_tokens,
receiver,
encoded_solution
.permit
.ok_or(EncodingError::FatalError(
"permit2 object must be set to use permit2".to_string(),
))?,
permit.ok_or(EncodingError::FatalError(
"permit2 object must be set to use permit2".to_string(),
))?,
encoded_solution
.signature
.ok_or(EncodingError::FatalError(
"Signature must be set to use permit2".to_string(),
))?
.as_bytes()
.to_vec(),
))?,
encoded_solution.swaps,
)
.abi_encode()

View File

@@ -527,11 +527,10 @@ mod tests {
use super::*;
use crate::encoding::{
evm::{
approvals::permit2::{Permit2, PermitSingle},
encoding_utils::encode_tycho_router_call,
approvals::permit2::Permit2, encoding_utils::encode_tycho_router_call,
utils::write_calldata_to_file,
},
models::Swap,
models::{PermitSingle, Swap},
};
fn eth_chain() -> Chain {
@@ -580,12 +579,12 @@ mod tests {
use alloy_sol_types::SolValue;
use super::*;
use crate::encoding::evm::utils::biguint_to_u256;
#[test]
fn test_single_swap_strategy_encoder() {
// Performs a single swap from WETH to DAI on a USV2 pool, with no grouping
// optimizations.
let checked_amount = BigUint::from_str("2659881924818443699787").unwrap();
let expected_min_amount = U256::from_str("2659881924818443699787").unwrap();
let checked_amount = BigUint::from_str("2018817438608734439720").unwrap();
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
@@ -625,12 +624,13 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
.data;
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_min_amount_encoded =
hex::encode(U256::abi_encode(&biguint_to_u256(&checked_amount)));
let expected_input = [
"30ace1b1", // Function selector
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
@@ -874,7 +874,7 @@ mod tests {
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -928,7 +928,7 @@ mod tests {
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -1004,7 +1004,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -1194,7 +1194,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -1716,7 +1716,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
.unwrap()
@@ -1817,7 +1817,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -1932,7 +1932,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -2096,7 +2096,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
.unwrap()
@@ -2339,7 +2339,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
.unwrap()
@@ -2412,7 +2412,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
.unwrap()
@@ -2503,7 +2503,7 @@ mod tests {
.unwrap();
let (permit, signature) = get_permit(eth_chain(), router_address(), &solution);
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
.unwrap()

View File

@@ -125,7 +125,7 @@ impl TychoRouterEncoder {
&solution.given_amount,
)?;
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
encoded_solution.signature = Some(signature.as_bytes().to_vec());
}
Ok(encoded_solution)
}

View File

@@ -1,4 +1,3 @@
use alloy_primitives::PrimitiveSignature as Signature;
use hex;
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
@@ -7,9 +6,7 @@ use tycho_common::{
Bytes,
};
use crate::encoding::{
errors::EncodingError, evm::approvals::permit2::PermitSingle, serde_primitives::biguint_string,
};
use crate::encoding::{errors::EncodingError, serde_primitives::biguint_string};
/// Represents a solution containing details describing an order, and instructions for filling
/// the order.
@@ -106,7 +103,49 @@ pub struct EncodedSolution {
pub selector: String,
pub n_tokens: usize,
pub permit: Option<PermitSingle>,
pub signature: Option<Signature>,
pub signature: Option<Vec<u8>>,
}
/// Represents a single permit for permit2.
///
/// # Fields
/// * `details`: The details of the permit, such as token, amount, expiration, and nonce.
/// * `spender`: The address authorized to spend the tokens.
/// * `sig_deadline`: The deadline (as a timestamp) for the permit signature
#[derive(Debug, Clone)]
pub struct PermitSingle {
pub details: PermitDetails,
pub spender: Bytes,
pub sig_deadline: BigUint,
}
/// Details of a permit.
///
/// # Fields
/// * `token`: The token address for which the permit is granted.
/// * `amount`: The amount of tokens approved for spending.
/// * `expiration`: The expiration time (as a timestamp) for the permit.
/// * `nonce`: The unique nonce to prevent replay attacks.
#[derive(Debug, Clone)]
pub struct PermitDetails {
pub token: Bytes,
pub amount: BigUint,
pub expiration: BigUint,
pub nonce: BigUint,
}
impl PartialEq for PermitSingle {
fn eq(&self, other: &Self) -> bool {
self.details == other.details && self.spender == other.spender
// sig_deadline is intentionally ignored
}
}
impl PartialEq for PermitDetails {
fn eq(&self, other: &Self) -> bool {
self.token == other.token && self.amount == other.amount && self.nonce == other.nonce
// expiration is intentionally ignored
}
}
/// Represents necessary attributes for encoding an order.