feat: remove manual parsing
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2585,6 +2585,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ required-features = ["evm"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
num-bigint = "0.4.6"
|
num-bigint = { version = "0.4.6", features = ["serde"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
|
|||||||
1
input.json
Normal file
1
input.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","check_amount":"990000000000000000","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00Z"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":1.0}],"router_address":"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D","direct_execution":true}
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use chrono::Utc;
|
|
||||||
use num_bigint::BigUint;
|
|
||||||
use serde_json::Value;
|
|
||||||
use tycho_core::{
|
|
||||||
dto::{Chain, ProtocolComponent},
|
|
||||||
Bytes,
|
|
||||||
};
|
|
||||||
use tycho_execution::encoding::models::{NativeAction, Solution, Swap};
|
|
||||||
|
|
||||||
pub fn parse_solution(input: Value) -> Result<Solution, Box<dyn std::error::Error>> {
|
|
||||||
let obj = input
|
|
||||||
.as_object()
|
|
||||||
.ok_or("Input must be a JSON object")?;
|
|
||||||
|
|
||||||
Ok(Solution {
|
|
||||||
sender: parse_bytes(
|
|
||||||
obj.get("sender")
|
|
||||||
.ok_or("sender is required")?,
|
|
||||||
)?,
|
|
||||||
receiver: parse_bytes(
|
|
||||||
obj.get("receiver")
|
|
||||||
.ok_or("receiver is required")?,
|
|
||||||
)?,
|
|
||||||
given_token: parse_bytes(
|
|
||||||
obj.get("given_token")
|
|
||||||
.ok_or("given_token is required")?,
|
|
||||||
)?,
|
|
||||||
given_amount: parse_biguint(
|
|
||||||
obj.get("given_amount")
|
|
||||||
.ok_or("given_amount is required")?,
|
|
||||||
)?,
|
|
||||||
checked_token: parse_bytes(
|
|
||||||
obj.get("checked_token")
|
|
||||||
.ok_or("checked_token is required")?,
|
|
||||||
)?,
|
|
||||||
exact_out: obj
|
|
||||||
.get("exact_out")
|
|
||||||
.and_then(|v| v.as_bool())
|
|
||||||
.unwrap_or(false),
|
|
||||||
slippage: obj
|
|
||||||
.get("slippage")
|
|
||||||
.and_then(|v| v.as_f64()),
|
|
||||||
expected_amount: obj
|
|
||||||
.get("expected_amount")
|
|
||||||
.map(parse_biguint)
|
|
||||||
.transpose()?,
|
|
||||||
check_amount: obj
|
|
||||||
.get("check_amount")
|
|
||||||
.map(parse_biguint)
|
|
||||||
.transpose()?,
|
|
||||||
swaps: parse_swaps(
|
|
||||||
obj.get("swaps")
|
|
||||||
.ok_or("swaps is required")?,
|
|
||||||
)?,
|
|
||||||
router_address: obj
|
|
||||||
.get("router_address")
|
|
||||||
.map(parse_bytes)
|
|
||||||
.transpose()?,
|
|
||||||
native_action: obj
|
|
||||||
.get("native_action")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.and_then(|s| match s.to_lowercase().as_str() {
|
|
||||||
"wrap" => Some(NativeAction::Wrap),
|
|
||||||
"unwrap" => Some(NativeAction::Unwrap),
|
|
||||||
_ => None, // Default to None
|
|
||||||
}),
|
|
||||||
direct_execution: obj
|
|
||||||
.get("direct_execution")
|
|
||||||
.and_then(|v| v.as_bool())
|
|
||||||
.unwrap_or(false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_bytes(value: &Value) -> Result<Bytes, Box<dyn std::error::Error>> {
|
|
||||||
let s = value
|
|
||||||
.as_str()
|
|
||||||
.ok_or("Expected string for bytes")?;
|
|
||||||
Ok(Bytes::from_str(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_biguint(value: &Value) -> Result<BigUint, Box<dyn std::error::Error>> {
|
|
||||||
let s = value
|
|
||||||
.as_str()
|
|
||||||
.ok_or("Expected string for BigUint")?;
|
|
||||||
Ok(BigUint::from_str(s)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_swaps(value: &Value) -> Result<Vec<Swap>, Box<dyn std::error::Error>> {
|
|
||||||
let arr = value
|
|
||||||
.as_array()
|
|
||||||
.ok_or("Expected array for swaps")?;
|
|
||||||
let mut swaps = Vec::new();
|
|
||||||
|
|
||||||
for swap in arr {
|
|
||||||
let obj = swap
|
|
||||||
.as_object()
|
|
||||||
.ok_or("Swap must be an object")?;
|
|
||||||
swaps.push(Swap {
|
|
||||||
component: parse_protocol_component(
|
|
||||||
obj.get("component")
|
|
||||||
.ok_or("component is required")?,
|
|
||||||
)?,
|
|
||||||
token_in: parse_bytes(
|
|
||||||
obj.get("token_in")
|
|
||||||
.ok_or("token_in is required")?,
|
|
||||||
)?,
|
|
||||||
token_out: parse_bytes(
|
|
||||||
obj.get("token_out")
|
|
||||||
.ok_or("token_out is required")?,
|
|
||||||
)?,
|
|
||||||
split: obj
|
|
||||||
.get("split")
|
|
||||||
.and_then(|v| v.as_f64())
|
|
||||||
.unwrap_or(0.0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(swaps)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_protocol_component(obj: &Value) -> Result<ProtocolComponent, Box<dyn std::error::Error>> {
|
|
||||||
let obj = obj
|
|
||||||
.as_object()
|
|
||||||
.ok_or("Expected object for ProtocolComponent")?;
|
|
||||||
|
|
||||||
Ok(ProtocolComponent {
|
|
||||||
id: obj
|
|
||||||
.get("id")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or("id is required")?
|
|
||||||
.to_string(),
|
|
||||||
protocol_system: obj
|
|
||||||
.get("protocol_system")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or("protocol_system is required")?
|
|
||||||
.to_string(),
|
|
||||||
protocol_type_name: obj
|
|
||||||
.get("protocol_type_name")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or("protocol_type_name is required")?
|
|
||||||
.to_string(),
|
|
||||||
chain: obj
|
|
||||||
.get("chain")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.map(|s| match s.to_lowercase().as_str() {
|
|
||||||
"ethereum" => Chain::Ethereum,
|
|
||||||
"starknet" => Chain::Starknet,
|
|
||||||
"zksync" => Chain::ZkSync,
|
|
||||||
"arbitrum" => Chain::Arbitrum,
|
|
||||||
_ => Chain::Ethereum, // Default to Ethereum
|
|
||||||
})
|
|
||||||
.unwrap_or(Chain::Ethereum),
|
|
||||||
tokens: obj
|
|
||||||
.get("tokens")
|
|
||||||
.and_then(|v| v.as_array())
|
|
||||||
.ok_or("tokens is required")?
|
|
||||||
.iter()
|
|
||||||
.map(|v| {
|
|
||||||
v.as_str()
|
|
||||||
.map(|s| Bytes::from_str(s).unwrap())
|
|
||||||
})
|
|
||||||
.collect::<Option<Vec<_>>>()
|
|
||||||
.ok_or("Invalid token address format")?,
|
|
||||||
contract_ids: obj
|
|
||||||
.get("contract_ids")
|
|
||||||
.and_then(|v| v.as_array())
|
|
||||||
.ok_or("contract_ids is required")?
|
|
||||||
.iter()
|
|
||||||
.map(|v| {
|
|
||||||
v.as_str()
|
|
||||||
.map(|s| Bytes::from_str(s).unwrap())
|
|
||||||
})
|
|
||||||
.collect::<Option<Vec<_>>>()
|
|
||||||
.ok_or("Invalid contract address format")?,
|
|
||||||
static_attributes: obj
|
|
||||||
.get("static_attributes")
|
|
||||||
.and_then(|v| v.as_object())
|
|
||||||
.ok_or("static_attributes is required")?
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| {
|
|
||||||
Ok((
|
|
||||||
k.clone(),
|
|
||||||
Bytes::from_str(
|
|
||||||
v.as_str()
|
|
||||||
.ok_or("Invalid attribute value")?,
|
|
||||||
)?,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.collect::<Result<_, Box<dyn std::error::Error>>>()?,
|
|
||||||
change: obj
|
|
||||||
.get("change")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.map(|s| match s.to_lowercase().as_str() {
|
|
||||||
"update" => tycho_core::dto::ChangeType::Update,
|
|
||||||
"deletion" => tycho_core::dto::ChangeType::Deletion,
|
|
||||||
"creation" => tycho_core::dto::ChangeType::Creation,
|
|
||||||
_ => tycho_core::dto::ChangeType::Unspecified,
|
|
||||||
})
|
|
||||||
.unwrap_or(tycho_core::dto::ChangeType::Update),
|
|
||||||
creation_tx: Bytes::from_str(
|
|
||||||
obj.get("creation_tx")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("0x"),
|
|
||||||
)?,
|
|
||||||
created_at: obj
|
|
||||||
.get("created_at")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.map(|s| {
|
|
||||||
chrono::DateTime::parse_from_rfc3339(s)
|
|
||||||
.unwrap()
|
|
||||||
.naive_utc()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| Utc::now().naive_utc()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -7,13 +7,13 @@ use tycho_execution::encoding::{
|
|||||||
strategy_encoder::strategy_encoder_registry::EVMStrategyEncoderRegistry,
|
strategy_encoder::strategy_encoder_registry::EVMStrategyEncoderRegistry,
|
||||||
tycho_encoder::EVMTychoEncoder,
|
tycho_encoder::EVMTychoEncoder,
|
||||||
},
|
},
|
||||||
|
models::Solution,
|
||||||
strategy_encoder::StrategyEncoderRegistry,
|
strategy_encoder::StrategyEncoderRegistry,
|
||||||
tycho_encoder::TychoEncoder,
|
tycho_encoder::TychoEncoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod lib {
|
mod lib {
|
||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod parse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_ROUTER_ADDRESS: &str = "0x1234567890123456789012345678901234567890";
|
const DEFAULT_ROUTER_ADDRESS: &str = "0x1234567890123456789012345678901234567890";
|
||||||
@@ -70,8 +70,7 @@ fn encode_swaps(
|
|||||||
router_address: &str,
|
router_address: &str,
|
||||||
private_key: Option<String>,
|
private_key: Option<String>,
|
||||||
) -> Result<Value, Box<dyn std::error::Error>> {
|
) -> Result<Value, Box<dyn std::error::Error>> {
|
||||||
let input_json: Value = serde_json::from_str(input)?;
|
let solution: Solution = serde_json::from_str(input)?;
|
||||||
let solution = lib::parse::parse_solution(input_json)?;
|
|
||||||
|
|
||||||
let strategy_selector =
|
let strategy_selector =
|
||||||
EVMStrategyEncoderRegistry::new(Chain::Ethereum, DEFAULT_EXECUTORS_FILE_PATH, private_key)?;
|
EVMStrategyEncoderRegistry::new(Chain::Ethereum, DEFAULT_EXECUTORS_FILE_PATH, private_key)?;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tycho_core::{dto::ProtocolComponent, Bytes};
|
use tycho_core::{dto::ProtocolComponent, Bytes};
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct Solution {
|
pub struct Solution {
|
||||||
/// Address of the sender.
|
/// Address of the sender.
|
||||||
pub sender: Bytes,
|
pub sender: Bytes,
|
||||||
@@ -10,18 +12,22 @@ pub struct Solution {
|
|||||||
/// The token being sold (exact in) or bought (exact out).
|
/// The token being sold (exact in) or bought (exact out).
|
||||||
pub given_token: Bytes,
|
pub given_token: Bytes,
|
||||||
/// Amount of the given token.
|
/// Amount of the given token.
|
||||||
|
#[serde(with = "biguint_string")]
|
||||||
pub given_amount: BigUint,
|
pub given_amount: BigUint,
|
||||||
/// The token being bought (exact in) or sold (exact out).
|
/// The token being bought (exact in) or sold (exact out).
|
||||||
pub checked_token: Bytes,
|
pub checked_token: Bytes,
|
||||||
/// False if the solution is an exact input solution. Currently only exact input solutions are
|
/// False if the solution is an exact input solution. Currently only exact input solutions are
|
||||||
/// supported.
|
/// supported.
|
||||||
|
#[serde(default)]
|
||||||
pub exact_out: bool,
|
pub exact_out: bool,
|
||||||
// If set, it will be applied to expected_amount
|
// If set, it will be applied to expected_amount
|
||||||
pub slippage: Option<f64>,
|
pub slippage: Option<f64>,
|
||||||
/// Expected amount of the bought token (exact in) or sold token (exact out).
|
/// Expected amount of the bought token (exact in) or sold token (exact out).
|
||||||
|
#[serde(with = "biguint_string_option")]
|
||||||
pub expected_amount: Option<BigUint>,
|
pub expected_amount: Option<BigUint>,
|
||||||
/// Minimum amount to be checked for the solution to be valid.
|
/// Minimum amount to be checked for the solution to be valid.
|
||||||
/// If not set, the check will not be performed.
|
/// If not set, the check will not be performed.
|
||||||
|
#[serde(with = "biguint_string_option")]
|
||||||
pub check_amount: Option<BigUint>,
|
pub check_amount: Option<BigUint>,
|
||||||
/// List of swaps to fulfill the solution.
|
/// List of swaps to fulfill the solution.
|
||||||
pub swaps: Vec<Swap>,
|
pub swaps: Vec<Swap>,
|
||||||
@@ -32,16 +38,18 @@ pub struct Solution {
|
|||||||
/// If set to true, the solution will be encoded to be sent directly to the Executor and
|
/// If set to true, the solution will be encoded to be sent directly to the Executor and
|
||||||
/// skip the router. The user is responsible for managing necessary approvals and token
|
/// skip the router. The user is responsible for managing necessary approvals and token
|
||||||
/// transfers.
|
/// transfers.
|
||||||
|
#[serde(default)]
|
||||||
pub direct_execution: bool,
|
pub direct_execution: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum NativeAction {
|
pub enum NativeAction {
|
||||||
Wrap,
|
Wrap,
|
||||||
Unwrap,
|
Unwrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Swap {
|
pub struct Swap {
|
||||||
/// Protocol component from tycho indexer
|
/// Protocol component from tycho indexer
|
||||||
pub component: ProtocolComponent,
|
pub component: ProtocolComponent,
|
||||||
@@ -50,6 +58,7 @@ pub struct Swap {
|
|||||||
/// Token being output from the pool.
|
/// Token being output from the pool.
|
||||||
pub token_out: Bytes,
|
pub token_out: Bytes,
|
||||||
/// Percentage of the amount to be swapped in this operation (for example, 0.5 means 50%)
|
/// Percentage of the amount to be swapped in this operation (for example, 0.5 means 50%)
|
||||||
|
#[serde(default)]
|
||||||
pub split: f64,
|
pub split: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,3 +77,57 @@ pub struct EncodingContext {
|
|||||||
pub exact_out: bool,
|
pub exact_out: bool,
|
||||||
pub router_address: Bytes,
|
pub router_address: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom serialization for BigUint as string
|
||||||
|
mod biguint_string {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(value: &BigUint, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<BigUint, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
BigUint::from_str(&s).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom serialization for Option<BigUint> as string
|
||||||
|
mod biguint_string_option {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(value: &Option<BigUint>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
Some(v) => serializer.serialize_str(&v.to_string()),
|
||||||
|
None => serializer.serialize_none(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<BigUint>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt = Option::<String>::deserialize(deserializer)?;
|
||||||
|
match opt {
|
||||||
|
Some(s) => BigUint::from_str(&s)
|
||||||
|
.map(Some)
|
||||||
|
.map_err(serde::de::Error::custom),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user