Skip to content

Commit

Permalink
Merge pull request #12 from marktoda/priority-orders
Browse files Browse the repository at this point in the history
feat: add support for priority orders and v2
  • Loading branch information
zhongeric authored Aug 23, 2024
2 parents 4550acc + 1656de8 commit f703161
Show file tree
Hide file tree
Showing 12 changed files with 1,066 additions and 141 deletions.
161 changes: 135 additions & 26 deletions crates/uniswapx-rs/src/order.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::error::Error;

use alloy_dyn_abi::SolType;
use alloy_primitives::Uint;
use alloy_sol_types::{sol, SolType};
use alloy_sol_types::sol;
use anyhow::Result;

sol! {
Expand Down Expand Up @@ -29,30 +32,73 @@ sol! {
}

#[derive(Debug)]
struct ExclusiveDutchOrder {
OrderInfo info;
struct CosignerData {
uint256 decayStartTime;
uint256 decayEndTime;
address exclusiveFiller;
uint256 exclusivityOverrideBps;
DutchInput input;
DutchOutput[] outputs;
uint256 inputAmount;
uint256[] outputAmounts;
}

#[derive(Debug)]
struct V2DutchOrder {
OrderInfo info;
address cosigner;
DutchInput baseInput;
DutchOutput[] baseOutputs;
CosignerData cosignerData;
bytes cosignature;
}

#[derive(Debug)]
struct PriorityInput {
address token;
uint256 amount;
uint256 mpsPerPriorityFeeWei;
}

#[derive(Debug)]
struct PriorityOutput {
address token;
uint256 amount;
uint256 mpsPerPriorityFeeWei;
address recipient;
}

#[derive(Debug)]
struct PriorityCosignerData {
uint256 auctionTargetBlock;
}

#[derive(Debug)]
struct PriorityOrder {
OrderInfo info;
address cosigner;
uint256 auctionStartBlock;
uint256 baselinePriorityFeeWei;
PriorityInput input;
PriorityOutput[] outputs;
PriorityCosignerData cosignerData;
bytes cosignature;
}
}

pub fn decode_order(encoded_order: &str) -> Result<ExclusiveDutchOrder> {
let encoded_order = if encoded_order.starts_with("0x") {
&encoded_order[2..]
} else {
encoded_order
};
let order_hex = hex::decode(encoded_order)?;
pub const MPS: u64 = 1e7 as u64;

Ok(ExclusiveDutchOrder::decode(&order_hex, false)?)
#[derive(Debug, Clone)]
pub enum Order {
V2DutchOrder(V2DutchOrder),
PriorityOrder(PriorityOrder),
}

pub fn encode_order(order: &ExclusiveDutchOrder) -> Vec<u8> {
ExclusiveDutchOrder::encode(order)
impl Order {
pub fn encode(&self) -> Vec<u8> {
match self {
Order::V2DutchOrder(order) => order.encode_inner(),
Order::PriorityOrder(order) => order.encode_inner(),
}
}
}

#[derive(Debug, Clone)]
Expand All @@ -79,9 +125,18 @@ pub enum OrderResolution {
Resolved(ResolvedOrder),
Expired,
Invalid,
NotFillableYet
}

impl ExclusiveDutchOrder {
impl V2DutchOrder {
pub fn decode_inner(order_hex: &[u8], validate: bool) -> Result<Self, Box<dyn Error>> {
Ok(V2DutchOrder::decode_single(order_hex, validate)?)
}

pub fn encode_inner(&self) -> Vec<u8> {
V2DutchOrder::encode_single(self)
}

pub fn resolve(&self, timestamp: u64) -> OrderResolution {
let timestamp = Uint::from(timestamp);

Expand All @@ -90,33 +145,34 @@ impl ExclusiveDutchOrder {
};

// resolve over the decay curve
// TODO: apply cosigner logic

let input = ResolvedInput {
token: self.input.token.to_string(),
token: self.baseInput.token.to_string(),
amount: resolve_decay(
timestamp,
self.decayStartTime,
self.decayEndTime,
self.input.startAmount,
self.input.endAmount,
self.cosignerData.decayStartTime,
self.cosignerData.decayEndTime,
self.baseInput.startAmount,
self.baseInput.endAmount,
),
};

let outputs = self
.outputs
.baseOutputs
.iter()
.map(|output| {
let mut amount = resolve_decay(
timestamp,
self.decayStartTime,
self.decayEndTime,
self.cosignerData.decayStartTime,
self.cosignerData.decayEndTime,
output.startAmount,
output.endAmount,
);

// add exclusivity override to amount
if self.decayStartTime.gt(&timestamp) && !self.exclusiveFiller.is_zero() {
let exclusivity = self.exclusivityOverrideBps.wrapping_add(Uint::from(10000));
if self.cosignerData.decayStartTime.gt(&timestamp) && !self.cosignerData.exclusiveFiller.is_zero() {
let exclusivity = self.cosignerData.exclusivityOverrideBps.wrapping_add(Uint::from(10000));
let exclusivity = exclusivity.wrapping_mul(amount);
amount = exclusivity.wrapping_div(Uint::from(10000));
};
Expand All @@ -133,6 +189,59 @@ impl ExclusiveDutchOrder {
}
}

impl PriorityOrder {
pub fn decode_inner(order_hex: &[u8], validate: bool) -> Result<Self, Box<dyn Error>> {
Ok(PriorityOrder::decode_single(order_hex, validate)?)
}

pub fn encode_inner(&self) -> Vec<u8> {
PriorityOrder::encode_single(self)
}

pub fn resolve(&self, block_number: u64, timestamp: u64, priority_fee: Uint<256, 4>) -> OrderResolution {
let timestamp = Uint::from(timestamp);

if self.info.deadline.lt(&timestamp) {
return OrderResolution::Expired;
};

let input = self.input.scale(priority_fee);
let outputs = self
.outputs
.iter()
.map(|output| output.scale(priority_fee))
.collect();

if Uint::from(block_number).lt(&self.cosignerData.auctionTargetBlock.saturating_sub(Uint::from(2))) {
return OrderResolution::NotFillableYet;
};

OrderResolution::Resolved(ResolvedOrder { input, outputs })
}
}

impl PriorityInput {
pub fn scale(&self, priority_fee: Uint<256, 4>) -> ResolvedInput {
let amount = self.amount.wrapping_mul(Uint::from(MPS).wrapping_add(priority_fee.wrapping_mul(self.mpsPerPriorityFeeWei))).wrapping_div(Uint::from(MPS));
ResolvedInput {
token: self.token.to_string(),
amount,
}
}
}

impl PriorityOutput {
pub fn scale(&self, priority_fee: Uint<256, 4>) -> ResolvedOutput {
let amount = self.amount.wrapping_mul(Uint::from(MPS).saturating_sub(priority_fee.wrapping_mul(self.mpsPerPriorityFeeWei))).wrapping_div(Uint::from(MPS));
ResolvedOutput {
token: self.token.to_string(),
amount,
recipient: self.recipient.to_string(),
}
}

}

fn resolve_decay(
at_time: Uint<256, 4>,
start_time: Uint<256, 4>,
Expand Down
96 changes: 91 additions & 5 deletions src/collectors/uniswapx_order_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,60 @@ use async_trait::async_trait;
use futures::{stream, StreamExt};
use reqwest::Client;
use serde::Deserialize;
use std::fmt;
use std::str::FromStr;
use std::string::ToString;
use tokio::time::Duration;
use tokio_stream::wrappers::IntervalStream;

static UNISWAPX_API_URL: &str = "https://api.uniswap.org/v2";
static POLL_INTERVAL_SECS: u64 = 5;
pub const CHAIN_ID: u64 = 1;
static POLL_INTERVAL_SECS: u64 = 1;

#[derive(Debug)]
pub enum OrderTypeError {
InvalidOrderType,
}

impl fmt::Display for OrderTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Invalid order type")
}
}

impl std::error::Error for OrderTypeError {}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OrderType {
DutchV2,
Priority,
}

impl FromStr for OrderType {
type Err = OrderTypeError;

fn from_str(s: &str) -> Result<OrderType, OrderTypeError> {
match s {
"Dutch_V2" => Ok(OrderType::DutchV2),
"Priority" => Ok(OrderType::Priority),
_ => Err(OrderTypeError::InvalidOrderType),
}
}
}

impl ToString for OrderType {
fn to_string(&self) -> String {
match self {
OrderType::DutchV2 => "Dutch_V2".to_string(),
OrderType::Priority => "Priority".to_string(),
}
}
}

impl Default for OrderType {
fn default() -> Self {
OrderType::DutchV2
}
}

#[derive(Debug, Clone, Deserialize)]
pub struct UniswapXOrder {
Expand Down Expand Up @@ -38,13 +86,17 @@ pub struct UniswapXOrderResponse {
pub struct UniswapXOrderCollector {
pub client: Client,
pub base_url: String,
pub chain_id: u64,
pub order_type: OrderType,
}

impl UniswapXOrderCollector {
pub fn new() -> Self {
pub fn new(chain_id: u64, order_type: OrderType) -> Self {
Self {
client: Client::new(),
base_url: UNISWAPX_API_URL.to_string(),
chain_id,
order_type,
}
}
}
Expand All @@ -56,8 +108,10 @@ impl UniswapXOrderCollector {
impl Collector<UniswapXOrder> for UniswapXOrderCollector {
async fn get_event_stream(&self) -> Result<CollectorStream<'_, UniswapXOrder>> {
let url = format!(
"{}/orders?orderStatus=open&chainId={}",
self.base_url, CHAIN_ID
"{}/orders?orderStatus=open&chainId={}&orderType={}",
self.base_url,
self.chain_id,
self.order_type.to_string()
);

// stream that polls the UniswapX API every 5 seconds
Expand Down Expand Up @@ -94,8 +148,10 @@ impl Collector<UniswapXOrder> for UniswapXOrderCollector {
mod tests {
use crate::collectors::uniswapx_order_collector::UniswapXOrderCollector;
use artemis_core::types::Collector;
use ethers::utils::hex;
use futures::StreamExt;
use mockito::{Mock, Server, ServerGuard};
use uniswapx_rs::order::V2DutchOrder;

async fn get_collector(mock_response: &str) -> (UniswapXOrderCollector, ServerGuard, Mock) {
let mut server = Server::new_async().await;
Expand All @@ -112,6 +168,8 @@ mod tests {
let res = UniswapXOrderCollector {
client: reqwest::Client::new(),
base_url: url.clone(),
chain_id: 1,
order_type: super::OrderType::DutchV2,
};

(res, server, mock)
Expand Down Expand Up @@ -140,4 +198,32 @@ mod tests {
);
mock.assert_async().await;
}

#[tokio::test]
async fn decodes_v2_order() {
let response = r#"
{"orders":[{"type":"Dutch_V2","orderStatus":"open","signature":"0x6eb32e7912d333e9c1ab162db02ed1656cdc8fbea2e21e70cd3634e8a3bd85d0582b46cacb584412ef3e035837b005b70f67897969426f9795128ea52de3a8cf1b","encodedOrder":"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000004449cd34d1eb1fedcf02a1be3834ffde8e6a61800000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000000422ca8b0a00a4250000000000000000000000000000000000000000000000000422ca8b0a00a42500000000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000011f84b9aa48e5f8aa8b9897600006289be000000000000000000000000c9838bbf85ad068136e8da07021e9e131201901904683298fe8b71446644eba514e387688690bde85b7bcaf8de44455a6aaf7a3000000000000000000000000000000000000000000000000000000000669adac5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c330a127f1ec70000000000000000000000000000000000000000000000000034be9ca1484989000000000000000000000000c9838bbf85ad068136e8da07021e9e131201901900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000269fc8de5047000000000000000000000000000000000000000000000000000021d754744fbe000000000000000000000000000000fee13a103a10d593b9ae06b3e05f2e7e1c00000000000000000000000000000000000000000000000000000000669ad9b600000000000000000000000000000000000000000000000000000000669ad9f20000000000000000000000006f1cdbbb4d53d226cf4b917bf768b94acbab61680000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000003c64146542c1fd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041d90e87f6f9e84487bfbb5170e856a332769359664c72f90250ee8917baf3a5920e87d331fcf97456e5d4d88761c552a9115569861aa96120b56d882339bbaac91c00000000000000000000000000000000000000000000000000000000000000","chainId":1,"nonce":"1993352701105935839386570705396248068916924096291549856616269381900329515568","orderHash":"0x382f612930c2121ed91fcdc00972f76b4adbef8d111830e1d135ac944a144876","swapper":"0xC9838Bbf85Ad068136E8DA07021E9e1312019019","input":{"token":"0x6982508145454Ce325dDbE47a25d4ec3d2311933","startAmount":"5000000000000000000000000","endAmount":"5000000000000000000000000"},"outputs":[{"token":"0x0000000000000000000000000000000000000000","startAmount":"16944616955649735","endAmount":"14846278718998921","recipient":"0xC9838Bbf85Ad068136E8DA07021E9e1312019019"},{"token":"0x0000000000000000000000000000000000000000","startAmount":"42467711668295","endAmount":"37208718593982","recipient":"0x000000fee13a103A10D593b9AE06b3e05F2E7E1c"}],"cosignerData":{"decayStartTime":1721424310,"decayEndTime":1721424370,"exclusiveFiller":"0x6F1cDbBb4d53d226CF4B917bF768B94acbAB6168","inputOverride":"0","outputOverrides":["16998537363636733","0"]},"cosignature":"0xd90e87f6f9e84487bfbb5170e856a332769359664c72f90250ee8917baf3a5920e87d331fcf97456e5d4d88761c552a9115569861aa96120b56d882339bbaac91c","quoteId":"221f421a-455d-4358-8376-6b4fb0ffb0f1","requestId":"775eea31-3173-4f1c-b7d2-bcd6fbcf2301","createdAt":1721424286}]} "#;
let (collector, _server, _) = get_collector(response).await;
// get event stream and parse events
let stream = collector.get_event_stream().await.unwrap();
let (first_order, _) = stream.into_future().await;
assert!(first_order.is_some());
assert_eq!(
first_order.clone().unwrap().order_hash,
"0x382f612930c2121ed91fcdc00972f76b4adbef8d111830e1d135ac944a144876"
);
let encoded_order = &first_order.unwrap().encoded_order;
let encoded_order = if encoded_order.starts_with("0x") {
&encoded_order[2..]
} else {
encoded_order
};
let order_hex: Vec<u8> = hex::decode(encoded_order).unwrap();

let result = V2DutchOrder::decode_inner(&order_hex, false);
match result {
Err(e) => panic!("Error decoding order: {:?}", e),
_ => (),
}
}
}
Loading

0 comments on commit f703161

Please sign in to comment.