feat: Add encoding to test suite (#259)
* feat: Add encoding to test suite Use execution from simulation Add protocol_system to all test files and pass it to run tycho Add encoding_utils.rs #time 5h 0m #time 1m #time 7m * refactor: Move encoding swap to its own method to simplify main code Rename encoding_utils.rs to encoding.rs #time 20m #time 0m #time 0m
This commit is contained in:
158
protocol-testing/src/encoding.rs
Normal file
158
protocol-testing/src/encoding.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use alloy::{primitives::Keccak256, sol_types::SolValue};
|
||||
use num_bigint::BigUint;
|
||||
use tycho_common::{dto::Chain, Bytes};
|
||||
use tycho_simulation::{
|
||||
evm::protocol::u256_num::biguint_to_u256,
|
||||
protocol::models::ProtocolComponent,
|
||||
tycho_execution::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::{encoder_builders::TychoRouterEncoderBuilder, utils::bytes_to_address},
|
||||
models::{
|
||||
EncodedSolution, NativeAction, Solution, SwapBuilder, Transaction, UserTransferType,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Encodes swap data for the Tycho router.
|
||||
///
|
||||
/// Assumes a single swap solution and encodes the data ready to be used by the Tycho router directly.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `component`: The protocol component to swap through
|
||||
/// - `token_in`: Input token address
|
||||
/// - `token_out`: Output token address
|
||||
/// - `amount_in`: Amount of input token to swap
|
||||
/// - `amount_out`: Expected amount of output token
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Result<Transaction, EncodingError>` containing the encoded transaction data for the Tycho router,
|
||||
/// or an error if encoding fails.
|
||||
pub fn encode_swap(
|
||||
component: ProtocolComponent,
|
||||
token_in: Bytes,
|
||||
token_out: Bytes,
|
||||
amount_in: BigUint,
|
||||
amount_out: BigUint,
|
||||
) -> Result<Transaction, EncodingError> {
|
||||
let chain: tycho_common::models::Chain = Chain::Ethereum.into();
|
||||
let alice_address =
|
||||
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").map_err(|_| {
|
||||
EncodingError::FatalError("Alice's address can't be converted to Bytes".to_string())
|
||||
})?;
|
||||
let encoder = TychoRouterEncoderBuilder::new()
|
||||
.chain(chain)
|
||||
.user_transfer_type(UserTransferType::TransferFrom)
|
||||
.build()
|
||||
.expect("Failed to build encoder");
|
||||
|
||||
let swap = SwapBuilder::new(component, token_in.clone(), token_out.clone()).build();
|
||||
|
||||
let slippage = 0.0025; // 0.25% slippage
|
||||
let bps = BigUint::from(10_000u32);
|
||||
let slippage_percent = BigUint::from((slippage * 10000.0) as u32);
|
||||
let multiplier = &bps - slippage_percent;
|
||||
let min_amount_out = (amount_out * &multiplier) / &bps;
|
||||
|
||||
let solution = Solution {
|
||||
sender: alice_address.clone(),
|
||||
receiver: alice_address.clone(),
|
||||
given_token: token_in,
|
||||
given_amount: amount_in,
|
||||
checked_token: token_out,
|
||||
exact_out: false,
|
||||
checked_amount: min_amount_out,
|
||||
swaps: vec![swap],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let encoded_solution = encoder
|
||||
.encode_solutions(vec![solution.clone()])
|
||||
.expect("Failed to encode router calldata")[0]
|
||||
.clone();
|
||||
|
||||
encode_tycho_router_call(encoded_solution, &solution, &chain.wrapped_native_token().address)
|
||||
}
|
||||
|
||||
/// Encodes a transaction for the Tycho Router using `singleSwap` method and regular token
|
||||
/// transfers.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `encoded_solution`: The solution already encoded by Tycho.
|
||||
/// - `solution`: The high-level solution including tokens, amounts, and receiver info.
|
||||
/// - `native_address`: The address used to represent the native token
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Result<Transaction, EncodingError>` that either contains the full transaction data (to,
|
||||
/// value, data), or an error if the inputs are invalid.
|
||||
pub fn encode_tycho_router_call(
|
||||
encoded_solution: EncodedSolution,
|
||||
solution: &Solution,
|
||||
native_address: &Bytes,
|
||||
) -> Result<Transaction, EncodingError> {
|
||||
let (mut unwrap, mut wrap) = (false, false);
|
||||
if let Some(action) = solution.native_action.clone() {
|
||||
match action {
|
||||
NativeAction::Wrap => wrap = true,
|
||||
NativeAction::Unwrap => unwrap = true,
|
||||
}
|
||||
}
|
||||
|
||||
let given_amount = biguint_to_u256(&solution.given_amount);
|
||||
let min_amount_out = biguint_to_u256(&solution.checked_amount);
|
||||
let given_token = bytes_to_address(&solution.given_token)?;
|
||||
let checked_token = bytes_to_address(&solution.checked_token)?;
|
||||
let receiver = bytes_to_address(&solution.receiver)?;
|
||||
|
||||
let method_calldata = if encoded_solution
|
||||
.function_signature
|
||||
.contains("singleSwap")
|
||||
{
|
||||
(
|
||||
given_amount,
|
||||
given_token,
|
||||
checked_token,
|
||||
min_amount_out,
|
||||
wrap,
|
||||
unwrap,
|
||||
receiver,
|
||||
true,
|
||||
encoded_solution.swaps,
|
||||
)
|
||||
.abi_encode()
|
||||
} else {
|
||||
Err(EncodingError::FatalError("Invalid function signature for Tycho router".to_string()))?
|
||||
};
|
||||
|
||||
let contract_interaction = encode_input(&encoded_solution.function_signature, method_calldata);
|
||||
let value = if solution.given_token == *native_address {
|
||||
solution.given_amount.clone()
|
||||
} else {
|
||||
BigUint::ZERO
|
||||
};
|
||||
Ok(Transaction { to: encoded_solution.interacting_with, value, data: contract_interaction })
|
||||
}
|
||||
|
||||
/// Encodes the input data for a function call to the given function signature (e.g.
|
||||
/// transfer(address,uint256))
|
||||
pub fn encode_input(selector: &str, mut encoded_args: Vec<u8>) -> Vec<u8> {
|
||||
let mut hasher = Keccak256::new();
|
||||
hasher.update(selector.as_bytes());
|
||||
let selector_bytes = &hasher.finalize()[..4];
|
||||
let mut call_data = selector_bytes.to_vec();
|
||||
// Remove extra prefix if present (32 bytes for dynamic data)
|
||||
// Alloy encoding is including a prefix for dynamic data indicating the offset or length
|
||||
// but at this point we don't want that
|
||||
if encoded_args.len() > 32 &&
|
||||
encoded_args[..32] ==
|
||||
[0u8; 31]
|
||||
.into_iter()
|
||||
.chain([32].to_vec())
|
||||
.collect::<Vec<u8>>()
|
||||
{
|
||||
encoded_args = encoded_args[32..].to_vec();
|
||||
}
|
||||
call_data.extend(encoded_args);
|
||||
call_data
|
||||
}
|
||||
Reference in New Issue
Block a user