Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for priority orders and v2 #12

Merged
merged 32 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2d49f38
Add new shared Order trait and add PriorityOrder structs
zhongeric Jul 11, 2024
a5e7f2c
remove uneeded typing
zhongeric Jul 11, 2024
b916ad9
fix build error
zhongeric Jul 11, 2024
6a73685
fix to use fill contract
zhongeric Jul 11, 2024
6a37170
Add v2 order (#2)
zhongeric Jul 23, 2024
cca40a5
feat: add priority strategy and new order enums/types (#1)
zhongeric Jul 23, 2024
5b1f096
add header for request
zhongeric Jul 23, 2024
56ca606
Add new collector plus strategies to engine
zhongeric Jul 23, 2024
3cef6b5
do not batch orders for prioroity
zhongeric Jul 23, 2024
e5b3f62
add public tx executor
zhongeric Jul 23, 2024
18e1df3
support tokens to approve to router and reactor
zhongeric Jul 23, 2024
96d8767
fix routing calls
zhongeric Jul 24, 2024
eccc6db
fix multicall decoding
zhongeric Jul 24, 2024
6a02f9a
use quote not quote gas adjusted
zhongeric Jul 24, 2024
22fa407
Move strategy shared functions to shared
zhongeric Jul 25, 2024
30fadaa
use shraed logic in priority strategy
zhongeric Jul 25, 2024
30d582b
Add base addresses
zhongeric Aug 5, 2024
ca51ea3
Merge branch 'update-bindings' into priority-orders
zhongeric Aug 5, 2024
1259f7b
add chain_id as an argument
zhongeric Aug 5, 2024
452c826
pass in executor address as arg
zhongeric Aug 13, 2024
ece59a3
Add new public 1559 executor
zhongeric Aug 14, 2024
996b485
resolve priority order based on block number
zhongeric Aug 16, 2024
50b1498
Add notFillableYet
zhongeric Aug 19, 2024
546a2f8
Set gas usage in txn
zhongeric Aug 19, 2024
47dc359
feedback remove unwrap
zhongeric Aug 20, 2024
4090d56
Fix orderData enum
zhongeric Aug 20, 2024
9b2101c
dedupe collectors and strategies and take in order type
zhongeric Aug 20, 2024
e0610b0
fix event link
zhongeric Aug 20, 2024
1757b1f
sub 2
zhongeric Aug 20, 2024
14df76e
Add contexts
zhongeric Aug 22, 2024
772f3a1
feedback
zhongeric Aug 23, 2024
1656de8
remove shared impl resolve
zhongeric Aug 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 141 additions & 27 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,78 @@ 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)?;

Ok(ExclusiveDutchOrder::decode(&order_hex, false)?)
pub enum Order {
V2DutchOrder(V2DutchOrder),
PriorityOrder(PriorityOrder),
}

pub fn encode_order(order: &ExclusiveDutchOrder) -> Vec<u8> {
ExclusiveDutchOrder::encode(order)
impl Order {
pub fn resolve(&self, block_number: u64, timestamp: u64, priority_fee: Uint<256, 4>) -> OrderResolution {
match self {
Order::V2DutchOrder(order) => order.resolve(timestamp),
Order::PriorityOrder(order) => order.resolve(block_number, timestamp, priority_fee),
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
}
}

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 +130,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 +150,34 @@ impl ExclusiveDutchOrder {
};

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

let input = ResolvedInput {
token: self.input.token.to_string(),
let input: ResolvedInput = ResolvedInput {
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
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 +194,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(1))) {
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(1e7).wrapping_add(priority_fee.wrapping_mul(self.mpsPerPriorityFeeWei))).wrapping_div(Uint::from(1e7));
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
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(1e7).saturating_sub(priority_fee.wrapping_mul(self.mpsPerPriorityFeeWei))).wrapping_div(Uint::from(1e7));
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
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
76 changes: 71 additions & 5 deletions src/collectors/uniswapx_order_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,36 @@ 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, PartialEq, Eq)]
pub enum OrderType {
Dutch,
zhongeric marked this conversation as resolved.
Show resolved Hide resolved
Priority,
}

impl OrderType {
pub fn as_str(&self) -> &'static str {
match self {
OrderType::Dutch => "Dutch_V2",
OrderType::Priority => "Priority",
}
}

pub fn from_str(s: &str) -> Option<OrderType> {
match s {
"Dutch_V2" => Some(OrderType::Dutch),
"Priority" => Some(OrderType::Priority),
_ => None,
}
}
}

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

#[derive(Debug, Clone, Deserialize)]
pub struct UniswapXOrder {
Expand Down Expand Up @@ -38,13 +66,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 +88,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.as_str()
);

// stream that polls the UniswapX API every 5 seconds
Expand Down Expand Up @@ -94,8 +128,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 +148,8 @@ mod tests {
let res = UniswapXOrderCollector {
client: reqwest::Client::new(),
base_url: url.clone(),
chain_id: 1,
order_type: super::OrderType::Dutch,
};

(res, server, mock)
Expand Down Expand Up @@ -140,4 +178,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
Loading