diff --git a/crates/autopilot-svm/src/domain/auction.rs b/crates/autopilot-svm/src/domain/auction.rs index c7c871d717..c3f1bad9da 100644 --- a/crates/autopilot-svm/src/domain/auction.rs +++ b/crates/autopilot-svm/src/domain/auction.rs @@ -3,7 +3,7 @@ use { crate::run_loop::AuctionInfo, - chain_types::solana::{IntentHash, Pubkey}, + chain_types::solana::{AppData, IntentHash, Pubkey}, }; /// Whether the order sells an exact amount or buys an exact amount. @@ -30,6 +30,10 @@ pub struct Order { pub valid_to: u32, pub kind: OrderKind, pub partially_fillable: bool, + /// The 32-byte app-data hash of the on-chain intent. An input to the + /// intent hash (the uid), so the driver needs it verbatim to rebuild the + /// intent at settlement. + pub app_data: AppData, pub order_pda: Pubkey, } @@ -71,6 +75,7 @@ mod tests { valid_to: 42, kind: OrderKind::Sell, partially_fillable: false, + app_data: chain_types::solana::AppData([8; 32]), order_pda: chain_types::solana::Pubkey([7; 32]), } } diff --git a/crates/autopilot-svm/src/infra/db.rs b/crates/autopilot-svm/src/infra/db.rs index d905c791cf..6438454f15 100644 --- a/crates/autopilot-svm/src/infra/db.rs +++ b/crates/autopilot-svm/src/infra/db.rs @@ -4,7 +4,7 @@ use { crate::domain::auction::{Auction, Order, OrderKind}, anyhow::{Context, Result, bail}, bigdecimal::{BigDecimal, ToPrimitive}, - chain_types::solana::{IntentHash, Pubkey}, + chain_types::solana::{AppData, IntentHash, Pubkey}, database::byte_array::ByteArray, sqlx::PgExecutor, }; @@ -23,6 +23,7 @@ pub struct OrderRow { pub valid_to: i64, pub kind: String, pub partially_fillable: bool, + pub app_data: ByteArray<32>, pub order_pda: ByteArray<32>, } @@ -35,7 +36,7 @@ pub async fn open_orders(ex: impl PgExecutor<'_>, now_unix: i64) -> Result= $1 @@ -145,6 +146,7 @@ impl TryFrom for Order { other => bail!("unknown order kind {other:?}"), }, partially_fillable: row.partially_fillable, + app_data: AppData(row.app_data.0), order_pda: Pubkey(row.order_pda.0), }) } @@ -179,6 +181,7 @@ mod tests { valid_to: 42, kind: "sell".to_owned(), partially_fillable: false, + app_data: ByteArray([8; 32]), order_pda: ByteArray([7; 32]), } } diff --git a/crates/autopilot-svm/src/infra/driver/dto.rs b/crates/autopilot-svm/src/infra/driver/dto.rs index 6e68a2baaa..68669fa537 100644 --- a/crates/autopilot-svm/src/infra/driver/dto.rs +++ b/crates/autopilot-svm/src/infra/driver/dto.rs @@ -6,7 +6,7 @@ use { crate::domain::auction, - chain_types::solana::{IntentHash, Pubkey, Signature}, + chain_types::solana::{AppData, IntentHash, Pubkey, Signature}, serde::{Deserialize, Serialize}, serde_with::{DisplayFromStr, serde_as}, std::collections::HashMap, @@ -49,6 +49,10 @@ pub struct Order { pub valid_to: u32, pub kind: Kind, pub partially_fillable: bool, + /// The 32-byte app-data hash of the on-chain intent, `0x`-hex on the + /// wire like the uid. + #[serde_as(as = "DisplayFromStr")] + pub app_data: AppData, #[serde_as(as = "DisplayFromStr")] pub order_pda: Pubkey, } @@ -78,6 +82,7 @@ impl From<&auction::Order> for Order { auction::OrderKind::Buy => Kind::Buy, }, partially_fillable: order.partially_fillable, + app_data: order.app_data, order_pda: order.order_pda, } } @@ -156,6 +161,7 @@ mod tests { valid_to: 42, kind: auction::OrderKind::Sell, partially_fillable: false, + app_data: AppData([0x88; 32]), order_pda: Pubkey([0x77; 32]), } } @@ -179,6 +185,10 @@ mod tests { // u64::MAX survives as a decimal string. assert_eq!(json["orders"][0]["sellAmount"], "18446744073709551615"); assert_eq!(json["orders"][0]["kind"], "sell"); + assert_eq!( + json["orders"][0]["appData"], + "0x8888888888888888888888888888888888888888888888888888888888888888" + ); // Base58 of 32 bytes of 0x22, precomputed so this does not just // compare the Display impl against itself. assert_eq!( diff --git a/crates/chain-types/src/solana.rs b/crates/chain-types/src/solana.rs index 23a1c14fbc..64bd7754ac 100644 --- a/crates/chain-types/src/solana.rs +++ b/crates/chain-types/src/solana.rs @@ -35,6 +35,29 @@ impl FromStr for IntentHash { } } +/// The 32-byte app-data hash of an order intent. An input to the intent +/// hash, so settlement encoding must carry it verbatim. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AppData(pub [u8; 32]); + +/// `0x`-prefixed hex, the wire and log rendering of an app-data hash. +impl fmt::Display for AppData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut buffer = const_hex::Buffer::<32, true>::new(); + f.write_str(buffer.format(&self.0)) + } +} + +impl FromStr for AppData { + type Err = const_hex::FromHexError; + + fn from_str(s: &str) -> Result { + let mut bytes = [0u8; 32]; + const_hex::decode_to_slice(s.strip_prefix("0x").unwrap_or(s), &mut bytes)?; + Ok(Self(bytes)) + } +} + /// Base58, the canonical Solana address rendering. impl fmt::Display for Pubkey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {