use {
crate::{
domain::DomainSeparator,
order::OrderUid,
signature::{EcdsaSignature, SignatureError, ecdsa_recover, ecdsa_wire, sign_ecdsa},
signing_scheme::EcdsaSigningScheme,
},
alloy_primitives::{B256, Bytes},
serde::{Deserialize, Serialize},
};
mod eip712 {
use alloy_sol_types::sol;
sol! {
struct OrderCancellation {
bytes orderUid;
}
struct OrderCancellations {
bytes[] orderUids;
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedOrderCancellation {
pub order_uid: OrderUid,
#[serde(with = "ecdsa_wire")]
pub signature: EcdsaSignature,
pub signing_scheme: EcdsaSigningScheme,
}
impl SignedOrderCancellation {
pub fn hash_struct(uid: &OrderUid) -> B256 {
use alloy_sol_types::SolStruct;
eip712::OrderCancellation {
orderUid: Bytes::from(uid.0),
}
.eip712_hash_struct()
}
pub fn sign<S: alloy_signer::SignerSync>(
order_uid: OrderUid,
scheme: EcdsaSigningScheme,
domain: &DomainSeparator,
signer: &S,
) -> Result<Self, SignatureError> {
let payload = eip712::OrderCancellation {
orderUid: Bytes::from(order_uid.0),
};
let signature = sign_ecdsa(scheme, domain, &payload, signer)?;
Ok(Self {
order_uid,
signature,
signing_scheme: scheme,
})
}
pub fn recover_owner(
&self,
domain: &DomainSeparator,
) -> Result<alloy_primitives::Address, SignatureError> {
let payload = eip712::OrderCancellation {
orderUid: Bytes::from(self.order_uid.0),
};
Ok(ecdsa_recover(&self.signature, self.signing_scheme, domain, &payload)?.signer)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderCancellations {
pub order_uids: Vec<OrderUid>,
}
impl From<Vec<OrderUid>> for OrderCancellations {
fn from(order_uids: Vec<OrderUid>) -> Self {
Self { order_uids }
}
}
impl FromIterator<OrderUid> for OrderCancellations {
fn from_iter<I: IntoIterator<Item = OrderUid>>(iter: I) -> Self {
Self {
order_uids: iter.into_iter().collect(),
}
}
}
impl IntoIterator for OrderCancellations {
type Item = OrderUid;
type IntoIter = std::vec::IntoIter<OrderUid>;
fn into_iter(self) -> Self::IntoIter {
self.order_uids.into_iter()
}
}
impl OrderCancellations {
pub fn hash_struct(&self) -> B256 {
use alloy_sol_types::SolStruct;
eip712::OrderCancellations {
orderUids: self.order_uids.iter().map(|u| Bytes::from(u.0)).collect(),
}
.eip712_hash_struct()
}
pub fn sign<S: alloy_signer::SignerSync>(
self,
scheme: EcdsaSigningScheme,
domain: &DomainSeparator,
signer: &S,
) -> Result<SignedOrderCancellations, SignatureError> {
let payload = self.eip712_payload();
let signature = sign_ecdsa(scheme, domain, &payload, signer)?;
Ok(SignedOrderCancellations {
order_uids: self.order_uids,
signature,
signing_scheme: scheme,
})
}
fn eip712_payload(&self) -> eip712::OrderCancellations {
eip712::OrderCancellations {
orderUids: self.order_uids.iter().map(|u| Bytes::from(u.0)).collect(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedOrderCancellations {
pub order_uids: Vec<OrderUid>,
#[serde(with = "ecdsa_wire")]
pub signature: EcdsaSignature,
pub signing_scheme: EcdsaSigningScheme,
}
impl SignedOrderCancellations {
pub fn recover_owner(
&self,
domain: &DomainSeparator,
) -> Result<alloy_primitives::Address, SignatureError> {
let payload = OrderCancellations {
order_uids: self.order_uids.clone(),
}
.eip712_payload();
Ok(ecdsa_recover(&self.signature, self.signing_scheme, domain, &payload)?.signer)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
alloy_primitives::{U256, b256, keccak256},
alloy_signer_local::PrivateKeySigner,
};
#[test]
fn order_cancellation_type_hash_matches_canonical_signature() {
use alloy_sol_types::SolStruct;
let signature = b"OrderCancellation(bytes orderUid)";
let sol = eip712::OrderCancellation {
orderUid: Bytes::copy_from_slice(&[0u8; 56]),
};
assert_eq!(
<eip712::OrderCancellation as SolStruct>::eip712_type_hash(&sol),
keccak256(signature),
);
}
#[test]
fn order_cancellations_type_hash_matches_canonical_signature() {
use alloy_sol_types::SolStruct;
let signature = b"OrderCancellations(bytes[] orderUids)";
let sol = eip712::OrderCancellations { orderUids: vec![] };
assert_eq!(
<eip712::OrderCancellations as SolStruct>::eip712_type_hash(&sol),
keccak256(signature),
);
}
#[test]
fn order_cancellations_hash_struct_matches_services_golden() {
let empty = OrderCancellations::default();
assert_eq!(
empty.hash_struct(),
b256!("56acdb3034898c6c23971cb3f92c32a4739e89a13c85282547025583a93911bd")
);
let two = OrderCancellations {
order_uids: vec![OrderUid::from([0x11; 56]), OrderUid::from([0x22; 56])],
};
assert_eq!(
two.hash_struct(),
b256!("405f6cb53d87901a5385a824a99c94b43146547f5ea3623f8d2f50b925e97a8b")
);
}
#[test]
fn order_cancellation_hash_struct_matches_independent_eip712_derivation() {
let type_hash = keccak256(b"OrderCancellation(bytes orderUid)");
for uid in [OrderUid::from([0u8; 56]), OrderUid::from([0x42; 56])] {
let mut encoded = [0u8; 64];
encoded[0..32].copy_from_slice(type_hash.as_slice());
encoded[32..64].copy_from_slice(keccak256(uid.as_slice()).as_slice());
let expected = keccak256(encoded);
assert_eq!(SignedOrderCancellation::hash_struct(&uid), expected);
}
}
fn fixed_signer() -> PrivateKeySigner {
PrivateKeySigner::from_bytes(&U256::from(1u64).to_be_bytes().into()).unwrap()
}
fn fixed_domain() -> DomainSeparator {
crate::domain::settlement_domain(
1,
alloy_primitives::address!("9008D19f58AAbD9eD0D60971565AA8510560ab41"),
)
}
#[test]
fn order_cancellation_sign_recover_round_trip() {
let signer = fixed_signer();
let domain = fixed_domain();
let uid = OrderUid::from([0x42; 56]);
for scheme in [EcdsaSigningScheme::Eip712, EcdsaSigningScheme::EthSign] {
let cancellation =
SignedOrderCancellation::sign(uid, scheme, &domain, &signer).unwrap();
let recovered = cancellation.recover_owner(&domain).unwrap();
assert_eq!(recovered, signer.address());
}
}
#[test]
fn order_cancellations_sign_recover_round_trip() {
let signer = fixed_signer();
let domain = fixed_domain();
let cancellations = OrderCancellations {
order_uids: vec![OrderUid::from([0x11; 56]), OrderUid::from([0x22; 56])],
};
let signed = cancellations
.sign(EcdsaSigningScheme::Eip712, &domain, &signer)
.unwrap();
let recovered = signed.recover_owner(&domain).unwrap();
assert_eq!(recovered, signer.address());
}
#[test]
fn signed_cancellations_wire_format() {
let signed = SignedOrderCancellations {
order_uids: vec![OrderUid::from([0x11; 56])],
signature: EcdsaSignature::from_bytes_and_parity(&[0u8; 64], false),
signing_scheme: EcdsaSigningScheme::Eip712,
};
let body = serde_json::to_value(&signed).unwrap();
assert!(body["orderUids"].is_array());
assert_eq!(body["signingScheme"], "eip712");
assert!(body["signature"].as_str().unwrap().starts_with("0x"));
}
#[test]
fn order_cancellation_json_round_trip() {
let original = SignedOrderCancellation::sign(
OrderUid::from([0x77; 56]),
EcdsaSigningScheme::Eip712,
&fixed_domain(),
&fixed_signer(),
)
.unwrap();
let json = serde_json::to_string(&original).unwrap();
let parsed: SignedOrderCancellation = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, original);
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(value.get("orderUid").is_some());
assert!(value.get("signingScheme").is_some());
}
#[test]
fn order_cancellations_json_round_trip() {
let original = OrderCancellations {
order_uids: vec![OrderUid::from([0x01; 56]), OrderUid::from([0x02; 56])],
};
let json = serde_json::to_string(&original).unwrap();
let parsed: OrderCancellations = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, original);
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(value.get("orderUids").is_some());
}
#[test]
fn signed_order_cancellations_json_round_trip() {
let original = OrderCancellations {
order_uids: vec![OrderUid::from([0x33; 56]), OrderUid::from([0x44; 56])],
}
.sign(
EcdsaSigningScheme::EthSign,
&fixed_domain(),
&fixed_signer(),
)
.unwrap();
let json = serde_json::to_string(&original).unwrap();
let parsed: SignedOrderCancellations = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, original);
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(value.get("orderUids").is_some());
assert!(value.get("signature").is_some());
assert!(value.get("signingScheme").is_some());
}
}