use ethers::prelude::TxHash;
use ethers_core::types::Bytes;
use eyre::{eyre, Result};
use crate::bindings::endpoint;
use crate::eip712_structs;
use crate::engine::{PlaceOrder, PlaceOrderResponse};
use crate::math::ONE_X12;
use crate::trigger::{PlaceTriggerOrder, TriggerCriteria};
use crate::tx::get_eip712_digest;
use crate::utils::client_error::none_error;
use crate::core::execute::NadoExecute;
use crate::utils::nonce::order_nonce;
use crate::{build_and_call, fields_to_vars, nado_builder};
nado_builder!(
PlaceOrderBuilder,
NadoExecute,
product_id: u32,
price_x18: i128,
linked_sender: [u8; 32],
amount: i128,
expiration: u64,
order_type: eip712_structs::OrderType,
reduce_only: bool,
trigger_criteria: TriggerCriteria,
times: u32,
slippage_x6: u32,
nonce: u64,
appendix: u128,
recv_time: u64,
isolated: bool,
margin: i128,
spot_leverage: bool,
borrow_margin: bool,
mock_digest_and_signature: bool,
builder_info: (u32, u32),
id: u64;
build_and_call!(self, execute, place_order => Option<PlaceOrderResponse>);
pub async fn execute_trigger(&self) -> Result<Option<PlaceOrderResponse>> {
self.nado.place_trigger_order(self.build_trigger()?).await
}
pub fn build(&self) -> Result<PlaceOrder> {
self.assert_trigger_unset()?;
let order = self.order()?;
let id = self.id;
let signature = self.get_signature(self.get_product_id()?, &order)?;
let digest = self.get_digest(&order)?;
Ok(PlaceOrder {
order,
signature,
product_id: self.get_product_id()?,
digest: Some(digest),
id,
spot_leverage: self.spot_leverage,
borrow_margin: self.borrow_margin,
})
}
pub fn build_trigger(&self) -> Result<PlaceTriggerOrder> {
let order = self.order()?;
let product_id = self.get_product_id()?;
let trigger = self
.trigger_criteria
.clone()
.ok_or(none_error("trigger_criteria"))?;
let signature = self.get_signature(product_id, &order)?;
let digest = self.get_digest(&order)?;
Ok(PlaceTriggerOrder {
order,
signature: Bytes::from(signature),
product_id,
digest: Some(TxHash(digest)),
spot_leverage: self.spot_leverage,
borrow_margin: self.borrow_margin,
trigger,
id: self.id
})
}
fn get_signature(&self, product_id: u32, order: &eip712_structs::Order) -> Result<Vec<u8>> {
if self.should_mock_digest_and_signature() {
let mut signature = vec![0; 65];
signature.extend(self.nado.address()?);
Ok(signature)
} else {
self.nado.signer().order_signature(product_id, order)
}
}
fn get_digest(&self, order: &eip712_structs::Order) -> Result<[u8; 32]> {
if self.should_mock_digest_and_signature() {
Ok(random_digest())
} else {
self.encode_digest(order)
}
}
fn encode_digest(&self, order: &eip712_structs::Order) -> Result<[u8; 32]> {
let domain = self.nado.signer().order_domain(self.get_product_id()?)?;
let encoded = get_eip712_digest(order, &domain);
Ok(encoded.to_fixed_bytes())
}
fn assert_trigger_unset(&self) -> Result<()> {
if self.trigger_criteria.is_some() {
Err(eyre!("trigger_criteria set, use .build_triger to build trigger orders or clear trigger criteria"))
} else {
Ok(())
}
}
pub fn order(&self) -> Result<eip712_structs::Order> {
let mut builder = self.clone();
if self.expiration.is_none() {
builder = builder.expiration(u32::MAX as u64);
}
if self.order_type.is_none() {
builder = builder.order_type(eip712_structs::OrderType::Default);
}
if self.reduce_only.is_none() {
builder = builder.reduce_only(false);
}
builder.order_inner()
}
fn order_inner(&self) -> Result<eip712_structs::Order> {
fields_to_vars!(self, amount, price_x18, reduce_only, (order_type: clone));
let expiration = self.expiration.ok_or(none_error("expiration"))?;
let mut appendix = self.appendix.unwrap_or(ORDER_VERSION as u128);
if reduce_only {
appendix |= 1 << 11;
}
if self.isolated.is_some() && self.isolated.unwrap() {
appendix |= 1 << 8;
let margin_x6 = self.margin.unwrap() / ONE_X12;
appendix |= (margin_x6 as u128) << 64;
}
appendix |= order_type.appendix_bit();
let nonce = self.nonce.unwrap_or(order_nonce(self.recv_time));
if let Some(trigger) = self.trigger_criteria.as_ref() {
match trigger {
TriggerCriteria::PriceTrigger {..} => { appendix |= 1 << 12;},
TriggerCriteria::TimeTrigger {amounts, ..} => {
assert!(!self.isolated.unwrap_or(false), "time trigger orders cannot be isolated");
let times = self.times.unwrap();
let slippage_x6 = self.slippage_x6.ok_or(none_error("slippage_x6"))?;
appendix |= (times as u128) << 96;
appendix |= (slippage_x6 as u128) << 64;
appendix |= match amounts {
Some(_) => 3 << 12,
None => 2 << 12,
};
}
}
};
if let Some((builder_id, builder_fee_rate)) = self.builder_info {
appendix |= (builder_id as u128) << 48;
appendix |= (builder_fee_rate as u128) << 38;
}
let default_sender = self.nado.subaccount()?;
let sender = self.linked_sender.unwrap_or(default_sender);
Ok(eip712_structs::Order::from_binding(&endpoint::Order {
sender,
price_x18,
amount,
expiration,
nonce,
appendix,
}))
}
fn get_product_id(&self) -> Result<u32> {
fields_to_vars!(self, product_id);
Ok(product_id)
}
fn should_mock_digest_and_signature(&self) -> bool {
self.mock_digest_and_signature.unwrap_or(false)
}
);
fn random_digest() -> [u8; 32] {
let mut arr = [0u8; 32];
rand::Rng::fill(&mut rand::thread_rng(), &mut arr[..]);
arr
}
const ORDER_VERSION: u8 = 1;