use alloy_primitives::{Address, U256};
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};
use crate::{
app_data::AppDataHash,
error::{Error, Result},
order::{BuyTokenDestination, OrderData, OrderKind, SellTokenSource},
signature::Signature,
signing_scheme::SigningScheme,
};
#[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", try_from = "OrderCreationWire")]
pub struct OrderCreation {
pub sell_token: Address,
pub buy_token: Address,
#[serde(skip_serializing_if = "Option::is_none")]
pub receiver: Option<Address>,
#[serde_as(as = "DisplayFromStr")]
pub sell_amount: U256,
#[serde_as(as = "DisplayFromStr")]
pub buy_amount: U256,
pub valid_to: u32,
pub app_data: String,
pub app_data_hash: AppDataHash,
#[serde_as(as = "DisplayFromStr")]
pub fee_amount: U256,
pub kind: OrderKind,
pub partially_fillable: bool,
pub sell_token_balance: SellTokenSource,
pub buy_token_balance: BuyTokenDestination,
pub signing_scheme: SigningScheme,
#[serde(serialize_with = "serialise_signature_bytes")]
pub signature: Signature,
pub from: Address,
#[serde(skip_serializing_if = "Option::is_none")]
pub quote_id: Option<i64>,
}
fn serialise_signature_bytes<S>(
signature: &Signature,
serializer: S,
) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
crate::bytes_hex::serialize(signature.to_bytes(), serializer)
}
#[serde_as]
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct OrderCreationWire {
sell_token: Address,
buy_token: Address,
#[serde(default)]
receiver: Option<Address>,
#[serde_as(as = "DisplayFromStr")]
sell_amount: U256,
#[serde_as(as = "DisplayFromStr")]
buy_amount: U256,
valid_to: u32,
app_data: String,
app_data_hash: AppDataHash,
#[serde_as(as = "DisplayFromStr")]
fee_amount: U256,
kind: OrderKind,
partially_fillable: bool,
sell_token_balance: SellTokenSource,
buy_token_balance: BuyTokenDestination,
signing_scheme: SigningScheme,
#[serde(deserialize_with = "crate::bytes_hex::deserialize")]
signature: Vec<u8>,
from: Address,
#[serde(default)]
quote_id: Option<i64>,
}
impl TryFrom<OrderCreationWire> for OrderCreation {
type Error = crate::error::Error;
fn try_from(wire: OrderCreationWire) -> std::result::Result<Self, Self::Error> {
let signature = Signature::from_bytes(wire.signing_scheme, &wire.signature)?;
let order_data = OrderData {
sell_token: wire.sell_token,
buy_token: wire.buy_token,
receiver: wire.receiver,
sell_amount: wire.sell_amount,
buy_amount: wire.buy_amount,
valid_to: wire.valid_to,
app_data: wire.app_data_hash,
fee_amount: wire.fee_amount,
kind: wire.kind,
partially_fillable: wire.partially_fillable,
sell_token_balance: wire.sell_token_balance,
buy_token_balance: wire.buy_token_balance,
};
Self::from_signed_order_data(
order_data,
signature,
wire.from,
wire.app_data,
wire.quote_id,
)
}
}
impl OrderCreation {
pub const fn order_data(&self) -> OrderData {
OrderData {
sell_token: self.sell_token,
buy_token: self.buy_token,
receiver: self.receiver,
sell_amount: self.sell_amount,
buy_amount: self.buy_amount,
valid_to: self.valid_to,
app_data: self.app_data_hash,
fee_amount: self.fee_amount,
kind: self.kind,
partially_fillable: self.partially_fillable,
sell_token_balance: self.sell_token_balance,
buy_token_balance: self.buy_token_balance,
}
}
pub fn verify_owner(
&self,
domain: &crate::domain::DomainSeparator,
) -> std::result::Result<Address, crate::signature::SignatureError> {
let struct_hash = self.order_data().hash_struct();
match self.signature.recover(domain, &struct_hash)? {
Some(recovered) if recovered.signer == self.from => Ok(self.from),
Some(recovered) => Err(crate::signature::SignatureError::SignerMismatch {
declared: self.from,
recovered: recovered.signer,
}),
None if self.from == Address::ZERO => {
Err(crate::signature::SignatureError::SignerMismatch {
declared: Address::ZERO,
recovered: Address::ZERO,
})
}
None => Ok(self.from),
}
}
pub fn from_signed_order_data(
order_data: OrderData,
signature: Signature,
from: Address,
app_data_json: String,
quote_id: Option<i64>,
) -> Result<Self> {
if from == Address::ZERO {
return Err(Error::OrderCreationInvalid {
field: "from",
reason: "owner address must be non-zero",
});
}
let json_digest = alloy_primitives::keccak256(app_data_json.as_bytes());
if AppDataHash(json_digest.0) != order_data.app_data {
return Err(Error::OrderCreationInvalid {
field: "app_data",
reason: "JSON digest does not match signed app_data hash",
});
}
let receiver = match order_data.receiver {
Some(addr) if addr == Address::ZERO => None,
other => other,
};
Ok(Self {
sell_token: order_data.sell_token,
buy_token: order_data.buy_token,
receiver,
sell_amount: order_data.sell_amount,
buy_amount: order_data.buy_amount,
valid_to: order_data.valid_to,
app_data: app_data_json,
app_data_hash: order_data.app_data,
fee_amount: order_data.fee_amount,
kind: order_data.kind,
partially_fillable: order_data.partially_fillable,
sell_token_balance: order_data.sell_token_balance,
buy_token_balance: order_data.buy_token_balance,
signing_scheme: signature.scheme(),
signature,
from,
quote_id,
})
}
}