diff --git a/crates/model/src/bytes_hex.rs b/crates/model/src/bytes_hex.rs new file mode 100644 index 0000000000..4dffe2d5f0 --- /dev/null +++ b/crates/model/src/bytes_hex.rs @@ -0,0 +1,51 @@ +//! Serialization of Vec to 0x prefixed hex string + +use serde::{de::Error, Deserialize, Deserializer, Serializer}; +use std::borrow::Cow; + +pub fn serialize(bytes: T, serializer: S) -> Result +where + S: Serializer, + T: AsRef<[u8]>, +{ + let mut v = vec![0u8; 2 + bytes.as_ref().len() * 2]; + v[0] = b'0'; + v[1] = b'x'; + // Unwrap because only possible error is vector wrong size which cannot happen. + hex::encode_to_slice(bytes, &mut v[2..]).unwrap(); + // Unwrap because encoded data is always valid utf8. + serializer.serialize_str(&String::from_utf8(v).unwrap()) +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let prefixed_hex_str = Cow::::deserialize(deserializer)?; + let hex_str = prefixed_hex_str + .strip_prefix("0x") + .ok_or_else(|| D::Error::custom("missing '0x' prefix"))?; + hex::decode(hex_str).map_err(D::Error::custom) +} + +#[cfg(test)] +mod tests { + + #[derive(Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq)] + struct S { + #[serde(with = "super")] + b: Vec, + } + + #[test] + fn json() { + let orig = S { b: vec![0, 1] }; + let serialized = serde_json::to_value(&orig).unwrap(); + let expected = serde_json::json!({ + "b": "0x0001" + }); + assert_eq!(serialized, expected); + let deserialized: S = serde_json::from_value(expected).unwrap(); + assert_eq!(orig, deserialized); + } +} diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index d13116cf37..132f3d7fa1 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -2,6 +2,7 @@ pub mod app_id; pub mod auction; +pub mod bytes_hex; pub mod order; pub mod ratio_as_decimal; pub mod signature; diff --git a/crates/shared/src/balancer_sor_api.rs b/crates/shared/src/balancer_sor_api.rs index b00b748c60..2dd2274b4d 100644 --- a/crates/shared/src/balancer_sor_api.rs +++ b/crates/shared/src/balancer_sor_api.rs @@ -10,7 +10,6 @@ use model::u256_decimal; use num::BigInt; use reqwest::{Client, IntoUrl, Url}; use serde::{Deserialize, Serialize}; -use web3::types::Bytes; /// Trait for mockable Balancer SOR API. #[mockall::automock] @@ -149,7 +148,8 @@ pub struct Swap { #[serde(with = "u256_decimal")] pub amount: U256, /// Additional user data to pass to the pool. - pub user_data: Bytes, + #[serde(with = "model::bytes_hex")] + pub user_data: Vec, } impl Quote { diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 77d1e270b5..4168a72e45 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -41,7 +41,6 @@ use std::{ future::Future, time::{Duration, Instant}, }; -use web3::types::Bytes; pub type Web3Transport = DynTransport; pub type Web3 = DynWeb3; @@ -66,10 +65,10 @@ pub async fn measure_time(future: impl Future, timer: impl FnOnce } pub fn debug_bytes( - bytes: &Bytes, + bytes: impl AsRef<[u8]>, formatter: &mut std::fmt::Formatter, ) -> Result<(), std::fmt::Error> { - formatter.write_fmt(format_args!("0x{}", hex::encode(&bytes.0))) + formatter.write_fmt(format_args!("0x{}", hex::encode(bytes.as_ref()))) } /// anyhow errors are not clonable natively. This is a workaround that creates a new anyhow error diff --git a/crates/shared/src/oneinch_api.rs b/crates/shared/src/oneinch_api.rs index 6fe8eb5d99..7a2ddab384 100644 --- a/crates/shared/src/oneinch_api.rs +++ b/crates/shared/src/oneinch_api.rs @@ -4,7 +4,7 @@ //! //! Although there is no documentation about API v4.1, it exists and is identical to v4.0 except it //! uses EIP 1559 gas prices. -use crate::solver_utils::{deserialize_prefixed_hex, Slippage}; +use crate::solver_utils::Slippage; use anyhow::{ensure, Context, Result}; use cached::{Cached, TimedCache}; use ethcontract::{H160, U256}; @@ -360,7 +360,7 @@ pub struct ProtocolRouteSegment { pub struct Transaction { pub from: H160, pub to: H160, - #[serde(deserialize_with = "deserialize_prefixed_hex")] + #[serde(with = "model::bytes_hex")] pub data: Vec, #[serde(with = "u256_decimal")] pub value: U256, diff --git a/crates/shared/src/paraswap_api.rs b/crates/shared/src/paraswap_api.rs index 742e898f5e..396f0053d8 100644 --- a/crates/shared/src/paraswap_api.rs +++ b/crates/shared/src/paraswap_api.rs @@ -10,7 +10,6 @@ use serde::{ }; use serde_json::Value; use thiserror::Error; -use web3::types::Bytes; const BASE_URL: &str = "https://apiv5.paraswap.io"; @@ -325,7 +324,8 @@ pub struct TransactionBuilderResponse { pub value: U256, /// the calldata for the transaction #[derivative(Debug(format_with = "debug_bytes"))] - pub data: Bytes, + #[serde(with = "model::bytes_hex")] + pub data: Vec, /// the suggested gas price #[serde(with = "u256_decimal")] pub gas_price: U256, diff --git a/crates/shared/src/solver_utils.rs b/crates/shared/src/solver_utils.rs index 7e8e4f9dd2..b8799d2be7 100644 --- a/crates/shared/src/solver_utils.rs +++ b/crates/shared/src/solver_utils.rs @@ -49,14 +49,3 @@ where let decimal_str = Cow::::deserialize(deserializer)?; decimal_str.parse::().map_err(D::Error::custom) } - -pub fn deserialize_prefixed_hex<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let prefixed_hex_str = Cow::::deserialize(deserializer)?; - let hex_str = prefixed_hex_str - .strip_prefix("0x") - .ok_or_else(|| D::Error::custom("hex missing '0x' prefix"))?; - hex::decode(hex_str).map_err(D::Error::custom) -} diff --git a/crates/shared/src/univ3_router_api.rs b/crates/shared/src/univ3_router_api.rs index d698bb4c88..58ff3ff352 100644 --- a/crates/shared/src/univ3_router_api.rs +++ b/crates/shared/src/univ3_router_api.rs @@ -1,6 +1,5 @@ //! Bindings for an instance of https://github.com/cowprotocol/univ3-api . -use crate::solver_utils::deserialize_prefixed_hex; use anyhow::{Context, Result}; use model::u256_decimal; use primitive_types::{H160, U256}; @@ -32,7 +31,7 @@ pub struct Response { pub quote: U256, #[serde(with = "serde_with::rust::display_fromstr")] pub gas: u64, - #[serde(deserialize_with = "deserialize_prefixed_hex")] + #[serde(with = "model::bytes_hex")] pub call_data: Vec, } diff --git a/crates/shared/src/zeroex_api.rs b/crates/shared/src/zeroex_api.rs index 0156e3b0e3..8256ce90ac 100644 --- a/crates/shared/src/zeroex_api.rs +++ b/crates/shared/src/zeroex_api.rs @@ -15,7 +15,6 @@ use reqwest::{Client, IntoUrl, Url}; use serde::Deserialize; use std::collections::HashSet; use thiserror::Error; -use web3::types::Bytes; const ORDERS_MAX_PAGE_SIZE: usize = 1_000; @@ -137,7 +136,8 @@ impl Default for OrdersQuery { pub struct OrderMetadata { #[derivative(Default(value = "chrono::MIN_DATETIME"))] pub created_at: DateTime, - pub order_hash: Bytes, + #[serde(with = "model::bytes_hex")] + pub order_hash: Vec, #[serde(with = "serde_with::rust::display_fromstr")] pub remaining_fillable_taker_amount: u128, } @@ -261,7 +261,8 @@ pub struct SwapResponse { pub price: PriceResponse, pub to: H160, #[derivative(Debug(format_with = "debug_bytes"))] - pub data: Bytes, + #[serde(with = "model::bytes_hex")] + pub data: Vec, #[serde(with = "u256_decimal")] pub value: U256, } @@ -587,11 +588,10 @@ mod tests { per_page: 1000, records: vec![OrderRecord { metadata: OrderMetadata { - order_hash: Bytes( + order_hash: hex::decode( "003427369d4c2a6b0aceeb7b315bb9a6086bc6fc4c887aa51efc73b662c9d127" - ).unwrap() - ), + ).unwrap(), remaining_fillable_taker_amount: 262467000000000000u128, created_at: Utc.ymd(2022, 2, 26).and_hms_milli(6, 59, 0, 440) }, @@ -646,9 +646,9 @@ mod tests { estimated_gas: 111000, }, to: crate::addr!("def1c0ded9bec7f1a1670819833240f027b25eff"), - data: Bytes(hex::decode( + data: hex::decode( "d9627aa40000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000001206e6c0056936e100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006810e776880c02933d47db1b9fc05908e5386b96869584cd0000000000000000000000001000000000000000000000000000000000000011000000000000000000000000000000000000000000000092415e982f60d431ba" - ).unwrap()), + ).unwrap(), value: U256::from_dec_str("0").unwrap(), } ); diff --git a/crates/solver/src/liquidity/zeroex.rs b/crates/solver/src/liquidity/zeroex.rs index 86fb8aedcc..2187cd19b3 100644 --- a/crates/solver/src/liquidity/zeroex.rs +++ b/crates/solver/src/liquidity/zeroex.rs @@ -63,7 +63,7 @@ impl ZeroExLiquidity { } let limit_order = LimitOrder { - id: hex::encode(&record.metadata.order_hash.0), + id: hex::encode(&record.metadata.order_hash), sell_token: record.order.maker_token, buy_token: record.order.taker_token, sell_amount, diff --git a/crates/solver/src/settlement_access_list.rs b/crates/solver/src/settlement_access_list.rs index 6c5e74b885..19d8e11df8 100644 --- a/crates/solver/src/settlement_access_list.rs +++ b/crates/solver/src/settlement_access_list.rs @@ -157,6 +157,7 @@ impl AccessListEstimating for TenderlyAccessList { ) -> Result>> { Ok(futures::future::join_all(txs.iter().map(|tx| async { let (from, to, input) = resolve_call_request(tx)?; + let input = input.0; let block_number = self.tenderly.block_number(&self.network_id).await?; let request = TenderlyRequest { diff --git a/crates/solver/src/settlement_simulation.rs b/crates/solver/src/settlement_simulation.rs index 939a9cab1a..44c5cbf476 100644 --- a/crates/solver/src/settlement_simulation.rs +++ b/crates/solver/src/settlement_simulation.rs @@ -18,7 +18,7 @@ use reqwest::{ }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use shared::Web3; -use web3::types::{AccessList, BlockId, Bytes}; +use web3::types::{AccessList, BlockId}; const SIMULATE_BATCH_SIZE: usize = 10; @@ -169,7 +169,7 @@ pub async fn simulate_before_after_access_list( network_id, block_number, from, - input: transaction.input, + input: transaction.input.0, to, generate_access_list: false, transaction_index: Some(transaction_index), @@ -248,7 +248,8 @@ pub struct TenderlyRequest { pub network_id: String, pub block_number: u64, pub from: Address, - pub input: Bytes, + #[serde(with = "model::bytes_hex")] + pub input: Vec, pub to: Address, #[serde(skip_serializing_if = "Option::is_none")] pub transaction_index: Option, diff --git a/crates/solver/src/solver/balancer_sor_solver.rs b/crates/solver/src/solver/balancer_sor_solver.rs index 50bf2ea5e7..93982c8705 100644 --- a/crates/solver/src/solver/balancer_sor_solver.rs +++ b/crates/solver/src/solver/balancer_sor_solver.rs @@ -193,7 +193,7 @@ impl Interaction for BatchSwap { swap.asset_in_index.into(), swap.asset_out_index.into(), swap.amount, - Bytes(swap.user_data.0.clone()), + Bytes(swap.user_data.clone()), ) }) .collect(); diff --git a/crates/solver/src/solver/paraswap_solver.rs b/crates/solver/src/solver/paraswap_solver.rs index 64f5ddb0a2..9ea5c87565 100644 --- a/crates/solver/src/solver/paraswap_solver.rs +++ b/crates/solver/src/solver/paraswap_solver.rs @@ -196,7 +196,7 @@ fn decimals(token_info: &HashMap, token: &H160) -> Result Vec { - vec![(self.to, self.value, Bytes(self.data.0.clone()))] + vec![(self.to, self.value, Bytes(self.data.clone()))] } } diff --git a/crates/solver/src/solver/zeroex_solver.rs b/crates/solver/src/solver/zeroex_solver.rs index e3a0dac010..1541c99e2b 100644 --- a/crates/solver/src/solver/zeroex_solver.rs +++ b/crates/solver/src/solver/zeroex_solver.rs @@ -143,7 +143,7 @@ impl From for SettlementError { impl Interaction for SwapResponse { fn encode(&self) -> Vec { - vec![(self.to, self.value, Bytes(self.data.0.clone()))] + vec![(self.to, self.value, Bytes(self.data.clone()))] } } @@ -269,7 +269,7 @@ mod tests { estimated_gas: Default::default(), }, to: shared::addr!("0000000000000000000000000000000000000000"), - data: web3::types::Bytes(hex::decode("00").unwrap()), + data: hex::decode("00").unwrap(), value: U256::from_dec_str("0").unwrap(), }) }); @@ -407,7 +407,7 @@ mod tests { estimated_gas: Default::default(), }, to: shared::addr!("0000000000000000000000000000000000000000"), - data: web3::types::Bytes(hex::decode("").unwrap()), + data: hex::decode("").unwrap(), value: U256::from_dec_str("0").unwrap(), }) }); @@ -483,7 +483,7 @@ mod tests { estimated_gas: Default::default(), }, to: shared::addr!("0000000000000000000000000000000000000000"), - data: web3::types::Bytes(vec![]), + data: vec![], value: 0.into(), }) });