feat: Support Bebop quote through IndicativelyPriced state
Made a new feature gate (test-utils) for MockRFQState Took 2 hours 19 minutes
This commit is contained in:
@@ -5,5 +5,7 @@ mod encoding_utils;
|
||||
mod group_swaps;
|
||||
pub mod strategy_encoder;
|
||||
mod swap_encoder;
|
||||
#[cfg(feature = "test-utils")]
|
||||
pub mod testing_utils;
|
||||
pub mod tycho_encoders;
|
||||
pub mod utils;
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
||||
|
||||
use alloy::{
|
||||
primitives::{Address, Bytes as AlloyBytes, U256, U8},
|
||||
sol_types::SolValue,
|
||||
};
|
||||
use serde_json::from_str;
|
||||
use tycho_common::{models::Chain, Bytes};
|
||||
use tokio::{
|
||||
runtime::{Handle, Runtime},
|
||||
task::block_in_place,
|
||||
};
|
||||
use tycho_common::{
|
||||
models::{protocol::GetAmountOutParams, Chain},
|
||||
Bytes,
|
||||
};
|
||||
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::{
|
||||
approvals::protocol_approvals_manager::ProtocolApprovalsManager,
|
||||
utils::{bytes_to_address, get_static_attribute, pad_to_fixed_size},
|
||||
utils::{
|
||||
biguint_to_u256, bytes_to_address, get_runtime, get_static_attribute, pad_to_fixed_size,
|
||||
},
|
||||
},
|
||||
models::{EncodingContext, Swap},
|
||||
swap_encoder::SwapEncoder,
|
||||
@@ -652,12 +661,21 @@ impl SwapEncoder for BalancerV3SwapEncoder {
|
||||
pub struct BebopSwapEncoder {
|
||||
executor_address: String,
|
||||
settlement_address: String,
|
||||
native_token_bebop_address: String,
|
||||
native_token_address: Bytes,
|
||||
runtime_handle: Handle,
|
||||
// Store the runtime to prevent it from being dropped before use.
|
||||
// This is required since tycho-execution does not have a pre-existing runtime.
|
||||
// However, if the library is used in a context where a runtime already exists, it is not
|
||||
// necessary to store it.
|
||||
#[allow(dead_code)]
|
||||
runtime: Option<Arc<Runtime>>,
|
||||
}
|
||||
|
||||
impl SwapEncoder for BebopSwapEncoder {
|
||||
fn new(
|
||||
executor_address: String,
|
||||
_chain: Chain,
|
||||
chain: Chain,
|
||||
config: Option<HashMap<String, String>>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let config = config.ok_or(EncodingError::FatalError(
|
||||
@@ -669,7 +687,21 @@ impl SwapEncoder for BebopSwapEncoder {
|
||||
"Missing bebop settlement address in config".to_string(),
|
||||
))?
|
||||
.to_string();
|
||||
Ok(Self { executor_address, settlement_address })
|
||||
let native_token_bebop_address = config
|
||||
.get("native_token_address")
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Missing native token bebop address in config".to_string(),
|
||||
))?
|
||||
.to_string();
|
||||
let (runtime_handle, runtime) = get_runtime()?;
|
||||
Ok(Self {
|
||||
executor_address,
|
||||
settlement_address,
|
||||
runtime_handle,
|
||||
runtime,
|
||||
native_token_bebop_address,
|
||||
native_token_address: chain.native_token().address,
|
||||
})
|
||||
}
|
||||
|
||||
fn encode_swap(
|
||||
@@ -685,16 +717,15 @@ impl SwapEncoder for BebopSwapEncoder {
|
||||
|
||||
if let Some(router_address) = &encoding_context.router_address {
|
||||
let tycho_router_address = bytes_to_address(router_address)?;
|
||||
let token_to_approve = token_in;
|
||||
let settlement_address = Address::from_str(&self.settlement_address)
|
||||
.map_err(|_| EncodingError::FatalError("Invalid settlement address".to_string()))?;
|
||||
|
||||
// Native ETH doesn't need approval, only ERC20 tokens do
|
||||
if token_to_approve == Address::ZERO {
|
||||
if swap.token_in == self.native_token_address {
|
||||
approval_needed = false;
|
||||
} else {
|
||||
approval_needed = token_approvals_manager.approval_needed(
|
||||
token_to_approve,
|
||||
token_in,
|
||||
tycho_router_address,
|
||||
settlement_address,
|
||||
)?;
|
||||
@@ -703,25 +734,98 @@ impl SwapEncoder for BebopSwapEncoder {
|
||||
approval_needed = true;
|
||||
}
|
||||
|
||||
// The user data required for Bebop is
|
||||
// partial_fill_offset (u8) | original_taker_amount (U256) | calldata (bytes (selector ABI
|
||||
// encoded params))
|
||||
let user_data = swap.user_data.clone().ok_or_else(|| {
|
||||
EncodingError::InvalidInput("Bebop swaps require user_data with calldata".to_string())
|
||||
})?;
|
||||
let (partial_fill_offset, original_filled_taker_amount, bebop_calldata) =
|
||||
if let Some(state) = swap.protocol_state {
|
||||
let indicatively_priced_state = state
|
||||
.as_indicatively_priced()
|
||||
.map_err(|e| {
|
||||
EncodingError::FatalError(format!("State is not indicatively priced {e}"))
|
||||
})?;
|
||||
let estimated_amount_in =
|
||||
swap.estimated_amount_in
|
||||
.clone()
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Estimated amount in is mandatory for a Bebop swap".to_string(),
|
||||
))?;
|
||||
// Bebop uses another address for the native token than the zero address
|
||||
let bebop_native_address = Bytes::from_str(&self.native_token_bebop_address)
|
||||
.map_err(|_| {
|
||||
EncodingError::FatalError(
|
||||
"Invalid Bebop native token curve address".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if user_data.len() < 37 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain offset and Bebop calldata".to_string(),
|
||||
));
|
||||
}
|
||||
let mut token_in = swap.token_in.clone();
|
||||
if swap.token_in == self.native_token_address {
|
||||
token_in = bebop_native_address.clone()
|
||||
}
|
||||
let mut token_out = swap.token_out.clone();
|
||||
if swap.token_out == self.native_token_address {
|
||||
token_out = bebop_native_address
|
||||
}
|
||||
|
||||
let partial_fill_offset = user_data[0];
|
||||
let original_filled_taker_amount = U256::from_be_slice(&user_data[1..33]);
|
||||
let params = GetAmountOutParams {
|
||||
amount_in: estimated_amount_in,
|
||||
token_in,
|
||||
token_out,
|
||||
sender: encoding_context
|
||||
.router_address
|
||||
.clone()
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"The router address is needed to perform a Bebop swap".to_string(),
|
||||
))?,
|
||||
receiver: encoding_context.receiver.clone(),
|
||||
};
|
||||
let signed_quote = block_in_place(|| {
|
||||
self.runtime_handle.block_on(async {
|
||||
indicatively_priced_state
|
||||
.request_signed_quote(params)
|
||||
.await
|
||||
})
|
||||
})
|
||||
.map_err(|e| EncodingError::FatalError(format!("Failed to get Bebop quote {e}")))?;
|
||||
let bebop_calldata = signed_quote
|
||||
.quote_attributes
|
||||
.get("calldata")
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Bebop quote must have a calldata attribute".to_string(),
|
||||
))?;
|
||||
let partial_fill_offset = signed_quote
|
||||
.quote_attributes
|
||||
.get("partial_fill_offset")
|
||||
.ok_or(EncodingError::FatalError(
|
||||
"Bebop quote must have a partial_fill_offset attribute".to_string(),
|
||||
))?;
|
||||
let original_filled_taker_amount = biguint_to_u256(&signed_quote.amount_out);
|
||||
(
|
||||
// we are only interested in the last byte to get a u8
|
||||
partial_fill_offset[partial_fill_offset.len() - 1],
|
||||
original_filled_taker_amount,
|
||||
bebop_calldata.to_vec(),
|
||||
)
|
||||
} else {
|
||||
// The user data required for Bebop is
|
||||
// partial_fill_offset (u8) | original_taker_amount (U256) | calldata (bytes
|
||||
// (selector ABI encoded params))
|
||||
let user_data = swap.user_data.clone().ok_or_else(|| {
|
||||
EncodingError::InvalidInput(
|
||||
"Bebop swaps require user_data with calldata".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// The calldata should be for either swapSingle or swapAggregate
|
||||
let bebop_calldata = user_data[33..].to_vec();
|
||||
if user_data.len() < 37 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain offset and Bebop calldata".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let partial_fill_offset = user_data[0];
|
||||
let original_filled_taker_amount = U256::from_be_slice(&user_data[1..33]);
|
||||
|
||||
// The calldata should be for either swapSingle or swapAggregate
|
||||
let bebop_calldata = user_data[33..].to_vec();
|
||||
(partial_fill_offset, original_filled_taker_amount, bebop_calldata)
|
||||
};
|
||||
let receiver = bytes_to_address(&encoding_context.receiver)?;
|
||||
|
||||
// Encode packed data for the executor
|
||||
@@ -1723,10 +1827,26 @@ mod tests {
|
||||
}
|
||||
|
||||
mod bebop {
|
||||
use num_bigint::BigUint;
|
||||
|
||||
use super::*;
|
||||
use crate::encoding::evm::testing_utils::MockRFQState;
|
||||
|
||||
fn bebop_config() -> HashMap<String, String> {
|
||||
HashMap::from([
|
||||
(
|
||||
"bebop_settlement_address".to_string(),
|
||||
"0xbbbbbBB520d69a9775E85b458C58c648259FAD5F".to_string(),
|
||||
),
|
||||
(
|
||||
"native_token_address".to_string(),
|
||||
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE".to_string(),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_single() {
|
||||
fn test_encode_bebop_single_with_user_data() {
|
||||
// 200 USDC -> ONDO
|
||||
let bebop_calldata= Bytes::from_str("0x4dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000").unwrap();
|
||||
let original_taker_amount = U256::from_str("200000000").unwrap();
|
||||
@@ -1761,10 +1881,7 @@ mod tests {
|
||||
let encoder = BebopSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
Some(HashMap::from([(
|
||||
"bebop_settlement_address".to_string(),
|
||||
"0xbbbbbBB520d69a9775E85b458C58c648259FAD5F".to_string(),
|
||||
)])),
|
||||
Some(bebop_config()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1793,7 +1910,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_aggregate() {
|
||||
fn test_encode_bebop_aggregate_with_user_data() {
|
||||
// 20k USDC -> ONDO
|
||||
let bebop_calldata= Bytes::from_str("0xa2f7489300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000689b78880000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000005a060a5c2aaaaa2fe2cda34423cac76a84c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000ce79b081c0c924cb67848723ed3057234d10fc6b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000002901f2d62bb356ca0000000000000000000000000000000000000000000000002901f2d62bb356cb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000044f83c726000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000589400da00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000003aa5f96046644f6e37a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000004b51a26526ddbeec60000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000417ab4332f2b091d87d56d04eee35dd49452782c782de71608c0425c5ae41f1d7e147173851c870d76720ce07d45cd8622352716b1c7965819ee2bf8c573c499ae1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410c8da2637aa929e11caff9afdfc4c489320c6dba77cc934d88ba8956e365fd1d48983087c6e474bbb828181cdfdd17317c4c9c3ee4bc98e3769d0c05cc7a285e1c00000000000000000000000000000000000000000000000000000000000000").unwrap();
|
||||
let original_taker_amount = U256::from_str("20000000000").unwrap();
|
||||
@@ -1829,10 +1946,7 @@ mod tests {
|
||||
let encoder = BebopSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
Some(HashMap::from([(
|
||||
"bebop_settlement_address".to_string(),
|
||||
"0xbbbbbBB520d69a9775E85b458C58c648259FAD5F".to_string(),
|
||||
)])),
|
||||
Some(bebop_config()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1860,5 +1974,71 @@ mod tests {
|
||||
|
||||
assert_eq!(hex_swap, expected_swap + &bebop_calldata.to_string()[2..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_single_with_protocol_state() {
|
||||
// 3000 USDC -> 1 WETH using a mocked RFQ state to get a quote
|
||||
let bebop_calldata = Bytes::from_str("0x123456").unwrap();
|
||||
let partial_fill_offset = 12u64;
|
||||
let quote_amount_out = BigUint::from_str("1000000000000000000").unwrap();
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
protocol_system: String::from("rfq:bebop"),
|
||||
..Default::default()
|
||||
};
|
||||
let bebop_state = MockRFQState {
|
||||
quote_amount_out,
|
||||
quote_calldata: bebop_calldata.clone(),
|
||||
quote_partial_fill_offset: partial_fill_offset,
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); // WETH
|
||||
|
||||
let swap = SwapBuilder::new(bebop_component, token_in.clone(), token_out.clone())
|
||||
.estimated_amount_in(BigUint::from_str("3000000000").unwrap())
|
||||
.protocol_state(&bebop_state)
|
||||
.build();
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"),
|
||||
exact_out: false,
|
||||
router_address: Some(Bytes::zero(20)),
|
||||
group_token_in: token_in.clone(),
|
||||
group_token_out: token_out.clone(),
|
||||
transfer_type: TransferType::Transfer,
|
||||
};
|
||||
|
||||
let encoder = BebopSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
Chain::Ethereum,
|
||||
Some(bebop_config()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoded_swap = encoder
|
||||
.encode_swap(&swap, &encoding_context)
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
let expected_swap = String::from(concat!(
|
||||
// token in
|
||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
// token out
|
||||
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||
// transfer type
|
||||
"01",
|
||||
// partiall filled offset
|
||||
"0c",
|
||||
// original taker amount
|
||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000",
|
||||
// approval needed
|
||||
"01",
|
||||
//receiver,
|
||||
"c5564c13a157e6240659fb81882a28091add8670",
|
||||
));
|
||||
assert_eq!(hex_swap, expected_swap + &bebop_calldata.to_string()[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
src/encoding/evm/testing_utils.rs
Normal file
104
src/encoding/evm/testing_utils.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// This module is used in integration tests as well
|
||||
use std::{any::Any, collections::HashMap};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use num_bigint::BigUint;
|
||||
use tycho_common::{
|
||||
dto::ProtocolStateDelta,
|
||||
models::{protocol::GetAmountOutParams, token::Token},
|
||||
simulation::{
|
||||
errors::{SimulationError, TransitionError},
|
||||
indicatively_priced::{IndicativelyPriced, SignedQuote},
|
||||
protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
|
||||
},
|
||||
Bytes,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockRFQState {
|
||||
pub quote_amount_out: BigUint,
|
||||
pub quote_calldata: Bytes,
|
||||
pub quote_partial_fill_offset: u64,
|
||||
}
|
||||
impl ProtocolSim for MockRFQState {
|
||||
fn fee(&self) -> f64 {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn spot_price(&self, _base: &Token, _quote: &Token) -> Result<f64, SimulationError> {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn get_amount_out(
|
||||
&self,
|
||||
_amount_in: BigUint,
|
||||
_token_in: &Token,
|
||||
_token_out: &Token,
|
||||
) -> Result<GetAmountOutResult, SimulationError> {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn get_limits(
|
||||
&self,
|
||||
_sell_token: Bytes,
|
||||
_buy_token: Bytes,
|
||||
) -> Result<(BigUint, BigUint), SimulationError> {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn delta_transition(
|
||||
&mut self,
|
||||
_delta: ProtocolStateDelta,
|
||||
_tokens: &HashMap<Bytes, Token>,
|
||||
_balances: &Balances,
|
||||
) -> Result<(), TransitionError<String>> {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn ProtocolSim> {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn eq(&self, _other: &dyn ProtocolSim) -> bool {
|
||||
panic!("MockRFQState does not implement fee")
|
||||
}
|
||||
|
||||
fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IndicativelyPriced for MockRFQState {
|
||||
async fn request_signed_quote(
|
||||
&self,
|
||||
params: GetAmountOutParams,
|
||||
) -> Result<SignedQuote, SimulationError> {
|
||||
let mut quote_attributes: HashMap<String, Bytes> = HashMap::new();
|
||||
quote_attributes.insert("calldata".to_string(), self.quote_calldata.clone());
|
||||
quote_attributes.insert(
|
||||
"partial_fill_offset".to_string(),
|
||||
Bytes::from(
|
||||
self.quote_partial_fill_offset
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
),
|
||||
);
|
||||
|
||||
Ok(SignedQuote {
|
||||
base_token: params.token_in,
|
||||
quote_token: params.token_out,
|
||||
amount_in: params.amount_in,
|
||||
amount_out: self.quote_amount_out.clone(),
|
||||
quote_attributes,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user