lib.rs (67111B)
1 #![cfg_attr(coverage_nightly, feature(coverage_attribute))] 2 #![forbid(unsafe_code)] 3 4 use serde::{Deserialize, Serialize}; 5 use sha2::{Digest, Sha256}; 6 use std::collections::BTreeMap; 7 use thiserror::Error; 8 9 pub const RADROOTS_SP1_TRADE_PUBLIC_VALUES_SCHEMA_VERSION: u32 = 1; 10 pub const RADROOTS_SP1_TRADE_WITNESS_VERSION: u32 = 1; 11 pub const RADROOTS_SP1_TRADE_PROTOCOL_VERSION: &str = "radroots.trade.v1"; 12 pub const RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH: &str = 13 "0x3d8f7f463904d71f2d0d14b1551450756697e51c7b658e10c6d5c20a7bc61f08"; 14 pub const RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET: &str = "trade.order_acceptance.v1"; 15 pub const RADROOTS_SP1_TRADE_KIND_LISTING: u32 = 30402; 16 pub const RADROOTS_SP1_TRADE_KIND_LISTING_DRAFT: u32 = 30403; 17 pub const RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST: u32 = 3422; 18 pub const RADROOTS_SP1_TRADE_KIND_ORDER_DECISION: u32 = 3423; 19 20 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 21 #[serde(rename_all = "snake_case")] 22 pub enum RadrootsSp1TradeProofStatementType { 23 TradeTransition, 24 } 25 26 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 27 #[serde(rename_all = "snake_case")] 28 pub enum RadrootsSp1TradeProofTransitionKind { 29 OrderAccepted, 30 } 31 32 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 33 #[serde(rename_all = "snake_case")] 34 pub enum RadrootsSp1TradeProofResult { 35 Valid, 36 Invalid, 37 } 38 39 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 40 #[serde(deny_unknown_fields)] 41 pub struct RadrootsSp1TradeProofPublicValues { 42 pub schema_version: u32, 43 pub witness_version: u32, 44 pub statement_type: RadrootsSp1TradeProofStatementType, 45 pub proof_target: String, 46 pub radroots_protocol_version: String, 47 pub reducer_program_hash: String, 48 pub sp1_program_hash: Option<String>, 49 pub sp1_verifying_key_hash: Option<String>, 50 pub event_set_root: String, 51 pub listing_addr_hash: Option<String>, 52 pub listing_event_id: Option<String>, 53 pub order_id_hash: Option<String>, 54 pub root_event_id: Option<String>, 55 pub target_event_id: Option<String>, 56 pub previous_state_root: String, 57 pub new_state_root: String, 58 pub transition: Option<RadrootsSp1TradeProofTransitionKind>, 59 pub result: RadrootsSp1TradeProofResult, 60 pub error_bitmap: String, 61 pub inventory_delta_root: Option<String>, 62 pub inventory_sequence: Option<u128>, 63 pub inventory_prev_root: Option<String>, 64 pub inventory_new_root: Option<String>, 65 pub changed_records_root: String, 66 } 67 68 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 69 #[serde(deny_unknown_fields)] 70 pub struct RadrootsSp1TradeInventoryBinWitness { 71 pub bin_id: String, 72 pub listing_capacity: u64, 73 pub previous_reserved: u64, 74 } 75 76 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 77 #[serde(deny_unknown_fields)] 78 pub struct RadrootsSp1TradeOrderItemWitness { 79 pub bin_id: String, 80 pub bin_count: u32, 81 } 82 83 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 84 #[serde(deny_unknown_fields)] 85 pub struct RadrootsSp1TradeOrderRequestWitness { 86 pub order_id: String, 87 pub listing_addr: String, 88 pub buyer_pubkey: String, 89 pub seller_pubkey: String, 90 pub items: Vec<RadrootsSp1TradeOrderItemWitness>, 91 } 92 93 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 94 #[serde(deny_unknown_fields)] 95 pub struct RadrootsSp1TradeInventoryCommitmentWitness { 96 pub bin_id: String, 97 pub bin_count: u32, 98 } 99 100 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 101 pub enum RadrootsSp1TradeOrderDecisionWitness { 102 Accepted { 103 inventory_commitments: Vec<RadrootsSp1TradeInventoryCommitmentWitness>, 104 }, 105 Declined { 106 reason: String, 107 }, 108 } 109 110 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 111 #[serde(deny_unknown_fields)] 112 pub struct RadrootsSp1TradeOrderDecisionEventWitness { 113 pub order_id: String, 114 pub listing_addr: String, 115 pub buyer_pubkey: String, 116 pub seller_pubkey: String, 117 pub decision: RadrootsSp1TradeOrderDecisionWitness, 118 } 119 120 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 121 #[serde(rename_all = "snake_case")] 122 pub enum RadrootsSp1TradeEventEvidenceRole { 123 Buyer, 124 Seller, 125 } 126 127 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 128 #[serde(rename_all = "snake_case")] 129 pub enum RadrootsSp1TradeEventWorkflowPosition { 130 Listing, 131 OrderRequest, 132 OrderDecision, 133 } 134 135 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 136 #[serde(deny_unknown_fields)] 137 pub struct RadrootsSp1TradeCanonicalEventEvidence { 138 pub event_id: String, 139 pub signer_pubkey: String, 140 pub kind: u32, 141 pub canonical_event_hash: String, 142 pub signature_hash: String, 143 pub preverified_signature: bool, 144 pub role: RadrootsSp1TradeEventEvidenceRole, 145 pub workflow_position: RadrootsSp1TradeEventWorkflowPosition, 146 pub content_hash: String, 147 pub tags_hash: String, 148 pub ordering_key: String, 149 } 150 151 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 152 #[serde(deny_unknown_fields)] 153 pub struct RadrootsSp1TradeOrderAcceptanceWitness { 154 pub witness_version: u32, 155 pub proof_target: String, 156 pub listing_event_id: String, 157 pub request_event_id: String, 158 pub decision_event_id: String, 159 pub event_evidence: Vec<RadrootsSp1TradeCanonicalEventEvidence>, 160 pub request: RadrootsSp1TradeOrderRequestWitness, 161 pub decision: RadrootsSp1TradeOrderDecisionEventWitness, 162 pub inventory_bins: Vec<RadrootsSp1TradeInventoryBinWitness>, 163 pub inventory_sequence: u128, 164 pub previous_state_root: Option<String>, 165 pub reducer_program_hash: String, 166 pub radroots_protocol_version: String, 167 pub sp1_program_hash: Option<String>, 168 pub sp1_verifying_key_hash: Option<String>, 169 } 170 171 #[derive(Clone, Debug, PartialEq, Eq)] 172 pub struct RadrootsSp1TradePublicValuesExecution { 173 pub public_values: RadrootsSp1TradeProofPublicValues, 174 pub canonical_public_values: Vec<u8>, 175 pub public_values_hash: String, 176 } 177 178 #[derive(Debug, Error, PartialEq, Eq)] 179 pub enum RadrootsSp1TradeGuestError { 180 #[error("{0} cannot be empty")] 181 EmptyField(&'static str), 182 #[error("invalid event id field {0}")] 183 InvalidEventId(&'static str), 184 #[error("invalid hash field {0}")] 185 InvalidHash(&'static str), 186 #[error("invalid order request")] 187 InvalidOrderRequest, 188 #[error("invalid order decision")] 189 InvalidOrderDecision, 190 #[error("unsupported witness version")] 191 UnsupportedWitnessVersion, 192 #[error("unsupported proof target")] 193 UnsupportedProofTarget, 194 #[error("unsupported protocol version")] 195 UnsupportedProtocolVersion, 196 #[error("unsupported reducer program hash")] 197 UnsupportedReducerProgramHash, 198 #[error("invalid event evidence field {0}")] 199 InvalidEventEvidence(&'static str), 200 #[error("missing event evidence {0}")] 201 MissingEventEvidence(&'static str), 202 #[error("duplicate event evidence {0}")] 203 DuplicateEventEvidence(&'static str), 204 #[error("event evidence signature is not preverified")] 205 SignatureNotPreverified, 206 #[error("unsupported event evidence kind {0}")] 207 UnsupportedEventEvidenceKind(u32), 208 #[error("event evidence field {0} does not match")] 209 EventEvidenceBindingMismatch(&'static str), 210 #[error("order decision is not accepted")] 211 DecisionNotAccepted, 212 #[error("order field {0} does not match")] 213 OrderBindingMismatch(&'static str), 214 #[error("inventory bin {0} is missing")] 215 MissingInventoryBin(String), 216 #[error("inventory bin {0} is duplicated")] 217 DuplicateInventoryBin(String), 218 #[error("inventory commitment does not match order request")] 219 InventoryCommitmentMismatch, 220 #[error("inventory bin {0} would overcommit listing capacity")] 221 InventoryOvercommit(String), 222 #[error("inventory quantity overflow")] 223 InventoryOverflow, 224 #[error("public values encoding failed")] 225 PublicValuesEncoding, 226 } 227 228 pub fn reduce_order_acceptance_public_values( 229 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 230 ) -> Result<RadrootsSp1TradePublicValuesExecution, RadrootsSp1TradeGuestError> { 231 validate_witness_header(witness)?; 232 validate_order_request_shape(&witness.request)?; 233 validate_order_decision_shape(&witness.decision)?; 234 validate_order_binding(witness)?; 235 validate_event_evidence(witness)?; 236 237 let request_counts = aggregate_requested_counts(&witness.request)?; 238 let accepted_counts = aggregate_accepted_counts(&witness.decision)?; 239 if request_counts != accepted_counts { 240 return Err(RadrootsSp1TradeGuestError::InventoryCommitmentMismatch); 241 } 242 243 let inventory_bins = inventory_bins_by_id(&witness.inventory_bins)?; 244 let next_inventory = apply_inventory_delta(&request_counts, &inventory_bins)?; 245 let previous_state_root = witness 246 .previous_state_root 247 .clone() 248 .unwrap_or_else(empty_state_root); 249 validate_hash32(&previous_state_root, "previous_state_root")?; 250 251 let event_set_root = event_evidence_set_root(&witness.event_evidence); 252 let inventory_delta_root = hash_json("radroots:inventory-delta:v1", &request_counts); 253 let inventory_prev_root = hash_json("radroots:inventory-prev:v1", &inventory_bins); 254 let inventory_new_root = hash_json("radroots:inventory-new:v1", &next_inventory); 255 let changed_records_root = hash_json( 256 "radroots:changed-records:v1", 257 &ChangedRecordsMaterial { 258 order_id: &witness.request.order_id, 259 listing_addr: &witness.request.listing_addr, 260 target_event_id: &witness.decision_event_id, 261 inventory_new_root: &inventory_new_root, 262 }, 263 ); 264 let new_state_root = hash_json( 265 "radroots:state-root:v1", 266 &StateRootMaterial { 267 previous_state_root: &previous_state_root, 268 event_set_root: &event_set_root, 269 changed_records_root: &changed_records_root, 270 inventory_new_root: &inventory_new_root, 271 }, 272 ); 273 274 let public_values = RadrootsSp1TradeProofPublicValues { 275 schema_version: RADROOTS_SP1_TRADE_PUBLIC_VALUES_SCHEMA_VERSION, 276 witness_version: witness.witness_version, 277 statement_type: RadrootsSp1TradeProofStatementType::TradeTransition, 278 proof_target: witness.proof_target.clone(), 279 radroots_protocol_version: witness.radroots_protocol_version.clone(), 280 reducer_program_hash: witness.reducer_program_hash.clone(), 281 sp1_program_hash: witness.sp1_program_hash.clone(), 282 sp1_verifying_key_hash: witness.sp1_verifying_key_hash.clone(), 283 event_set_root, 284 listing_addr_hash: Some(hash_bytes( 285 "radroots:listing-addr:v1", 286 witness.request.listing_addr.as_bytes(), 287 )), 288 listing_event_id: Some(witness.listing_event_id.clone()), 289 order_id_hash: Some(hash_bytes( 290 "radroots:order-id:v1", 291 witness.request.order_id.as_bytes(), 292 )), 293 root_event_id: Some(witness.request_event_id.clone()), 294 target_event_id: Some(witness.decision_event_id.clone()), 295 previous_state_root, 296 new_state_root, 297 transition: Some(RadrootsSp1TradeProofTransitionKind::OrderAccepted), 298 result: RadrootsSp1TradeProofResult::Valid, 299 error_bitmap: zero_error_bitmap().to_string(), 300 inventory_delta_root: Some(inventory_delta_root), 301 inventory_sequence: Some(witness.inventory_sequence), 302 inventory_prev_root: Some(inventory_prev_root), 303 inventory_new_root: Some(inventory_new_root), 304 changed_records_root, 305 }; 306 let canonical_public_values = canonical_public_values_bytes(&public_values)?; 307 let public_values_hash = validation_receipt_public_values_hash_hex(&canonical_public_values); 308 Ok(RadrootsSp1TradePublicValuesExecution { 309 public_values, 310 canonical_public_values, 311 public_values_hash, 312 }) 313 } 314 315 pub fn canonical_public_values_bytes( 316 public_values: &RadrootsSp1TradeProofPublicValues, 317 ) -> Result<Vec<u8>, RadrootsSp1TradeGuestError> { 318 validate_public_values(public_values)?; 319 serde_json::to_vec(public_values).map_err(|_| RadrootsSp1TradeGuestError::PublicValuesEncoding) 320 } 321 322 pub fn reduce_order_acceptance_canonical_public_values( 323 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 324 ) -> Result<Vec<u8>, RadrootsSp1TradeGuestError> { 325 Ok(reduce_order_acceptance_public_values(witness)?.canonical_public_values) 326 } 327 328 pub fn public_values_hash_hex( 329 public_values: &RadrootsSp1TradeProofPublicValues, 330 ) -> Result<String, RadrootsSp1TradeGuestError> { 331 let bytes = canonical_public_values_bytes(public_values)?; 332 Ok(validation_receipt_public_values_hash_hex(&bytes)) 333 } 334 335 pub fn validation_receipt_public_values_hash_hex(public_values: &[u8]) -> String { 336 let mut hasher = Sha256::new(); 337 hasher.update(b"radroots:sp1-public-values:v1"); 338 hasher.update(public_values); 339 format!("0x{}", hex_lower(hasher.finalize().as_slice())) 340 } 341 342 pub fn empty_state_root() -> String { 343 hash_bytes("radroots:state-empty:v1", &[]) 344 } 345 346 fn validate_witness_header( 347 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 348 ) -> Result<(), RadrootsSp1TradeGuestError> { 349 if witness.witness_version != RADROOTS_SP1_TRADE_WITNESS_VERSION { 350 return Err(RadrootsSp1TradeGuestError::UnsupportedWitnessVersion); 351 } 352 validate_required_str(&witness.proof_target, "proof_target")?; 353 if witness.proof_target != RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET { 354 return Err(RadrootsSp1TradeGuestError::UnsupportedProofTarget); 355 } 356 validate_event_id(&witness.listing_event_id, "listing_event_id")?; 357 validate_event_id(&witness.request_event_id, "request_event_id")?; 358 validate_event_id(&witness.decision_event_id, "decision_event_id")?; 359 validate_required_str(&witness.reducer_program_hash, "reducer_program_hash")?; 360 validate_hash32(&witness.reducer_program_hash, "reducer_program_hash")?; 361 if witness.reducer_program_hash != RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH { 362 return Err(RadrootsSp1TradeGuestError::UnsupportedReducerProgramHash); 363 } 364 validate_required_str( 365 &witness.radroots_protocol_version, 366 "radroots_protocol_version", 367 )?; 368 if witness.radroots_protocol_version != RADROOTS_SP1_TRADE_PROTOCOL_VERSION { 369 return Err(RadrootsSp1TradeGuestError::UnsupportedProtocolVersion); 370 } 371 if let Some(hash) = &witness.sp1_verifying_key_hash { 372 validate_hash32(hash, "sp1_verifying_key_hash")?; 373 } 374 if let Some(hash) = &witness.sp1_program_hash { 375 validate_hash32(hash, "sp1_program_hash")?; 376 } 377 Ok(()) 378 } 379 380 fn validate_event_evidence( 381 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 382 ) -> Result<(), RadrootsSp1TradeGuestError> { 383 if witness.event_evidence.is_empty() { 384 return Err(RadrootsSp1TradeGuestError::MissingEventEvidence( 385 "event_evidence", 386 )); 387 } 388 if witness.event_evidence.len() != 3 { 389 return Err(RadrootsSp1TradeGuestError::InvalidEventEvidence( 390 "event_evidence.len", 391 )); 392 } 393 394 let mut evidence_by_position = BTreeMap::new(); 395 for evidence in &witness.event_evidence { 396 validate_event_id(&evidence.event_id, "event_evidence.event_id")?; 397 validate_hex64(&evidence.signer_pubkey, "event_evidence.signer_pubkey")?; 398 validate_hash32( 399 &evidence.canonical_event_hash, 400 "event_evidence.canonical_event_hash", 401 )?; 402 validate_hash32(&evidence.signature_hash, "event_evidence.signature_hash")?; 403 validate_hash32(&evidence.content_hash, "event_evidence.content_hash")?; 404 validate_hash32(&evidence.tags_hash, "event_evidence.tags_hash")?; 405 validate_required_str(&evidence.ordering_key, "event_evidence.ordering_key")?; 406 if !evidence.preverified_signature { 407 return Err(RadrootsSp1TradeGuestError::SignatureNotPreverified); 408 } 409 if evidence_by_position 410 .insert(evidence.workflow_position, evidence) 411 .is_some() 412 { 413 return Err(RadrootsSp1TradeGuestError::DuplicateEventEvidence( 414 evidence.workflow_position.as_str(), 415 )); 416 } 417 } 418 419 let listing = evidence_by_position[&RadrootsSp1TradeEventWorkflowPosition::Listing]; 420 validate_evidence_binding( 421 listing, 422 &witness.listing_event_id, 423 &witness.request.seller_pubkey, 424 RadrootsSp1TradeEventEvidenceRole::Seller, 425 &[ 426 RADROOTS_SP1_TRADE_KIND_LISTING, 427 RADROOTS_SP1_TRADE_KIND_LISTING_DRAFT, 428 ], 429 "listing", 430 )?; 431 432 let request = evidence_by_position[&RadrootsSp1TradeEventWorkflowPosition::OrderRequest]; 433 validate_evidence_binding( 434 request, 435 &witness.request_event_id, 436 &witness.request.buyer_pubkey, 437 RadrootsSp1TradeEventEvidenceRole::Buyer, 438 &[RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST], 439 "order_request", 440 )?; 441 442 let decision = evidence_by_position[&RadrootsSp1TradeEventWorkflowPosition::OrderDecision]; 443 validate_evidence_binding( 444 decision, 445 &witness.decision_event_id, 446 &witness.decision.seller_pubkey, 447 RadrootsSp1TradeEventEvidenceRole::Seller, 448 &[RADROOTS_SP1_TRADE_KIND_ORDER_DECISION], 449 "order_decision", 450 )?; 451 452 Ok(()) 453 } 454 455 fn validate_evidence_binding( 456 evidence: &RadrootsSp1TradeCanonicalEventEvidence, 457 expected_event_id: &str, 458 expected_signer_pubkey: &str, 459 expected_role: RadrootsSp1TradeEventEvidenceRole, 460 allowed_kinds: &[u32], 461 label: &'static str, 462 ) -> Result<(), RadrootsSp1TradeGuestError> { 463 if evidence.event_id != expected_event_id { 464 return Err(RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch( 465 label, 466 )); 467 } 468 if evidence.signer_pubkey != expected_signer_pubkey { 469 return Err(RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch( 470 "signer_pubkey", 471 )); 472 } 473 if evidence.role != expected_role { 474 return Err(RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch( 475 "role", 476 )); 477 } 478 if !allowed_kinds.contains(&evidence.kind) { 479 return Err(RadrootsSp1TradeGuestError::UnsupportedEventEvidenceKind( 480 evidence.kind, 481 )); 482 } 483 Ok(()) 484 } 485 486 fn validate_order_request_shape( 487 request: &RadrootsSp1TradeOrderRequestWitness, 488 ) -> Result<(), RadrootsSp1TradeGuestError> { 489 validate_required_str(&request.order_id, "request.order_id")?; 490 validate_required_str(&request.listing_addr, "request.listing_addr")?; 491 validate_required_str(&request.buyer_pubkey, "request.buyer_pubkey")?; 492 validate_required_str(&request.seller_pubkey, "request.seller_pubkey")?; 493 if request.items.is_empty() { 494 return Err(RadrootsSp1TradeGuestError::InvalidOrderRequest); 495 } 496 for item in &request.items { 497 validate_required_str(&item.bin_id, "request.items.bin_id")?; 498 if item.bin_count == 0 { 499 return Err(RadrootsSp1TradeGuestError::InvalidOrderRequest); 500 } 501 } 502 Ok(()) 503 } 504 505 fn validate_order_decision_shape( 506 decision: &RadrootsSp1TradeOrderDecisionEventWitness, 507 ) -> Result<(), RadrootsSp1TradeGuestError> { 508 validate_required_str(&decision.order_id, "decision.order_id")?; 509 validate_required_str(&decision.listing_addr, "decision.listing_addr")?; 510 validate_required_str(&decision.buyer_pubkey, "decision.buyer_pubkey")?; 511 validate_required_str(&decision.seller_pubkey, "decision.seller_pubkey")?; 512 match &decision.decision { 513 RadrootsSp1TradeOrderDecisionWitness::Accepted { 514 inventory_commitments, 515 } => { 516 if inventory_commitments.is_empty() { 517 return Err(RadrootsSp1TradeGuestError::InvalidOrderDecision); 518 } 519 for commitment in inventory_commitments { 520 validate_required_str(&commitment.bin_id, "decision.inventory_commitments.bin_id")?; 521 if commitment.bin_count == 0 { 522 return Err(RadrootsSp1TradeGuestError::InvalidOrderDecision); 523 } 524 } 525 Ok(()) 526 } 527 RadrootsSp1TradeOrderDecisionWitness::Declined { reason } => { 528 validate_required_str(reason, "decision.reason")?; 529 Ok(()) 530 } 531 } 532 } 533 534 fn validate_order_binding( 535 witness: &RadrootsSp1TradeOrderAcceptanceWitness, 536 ) -> Result<(), RadrootsSp1TradeGuestError> { 537 if !matches!( 538 witness.decision.decision, 539 RadrootsSp1TradeOrderDecisionWitness::Accepted { .. } 540 ) { 541 return Err(RadrootsSp1TradeGuestError::DecisionNotAccepted); 542 } 543 if witness.request.order_id != witness.decision.order_id { 544 return Err(RadrootsSp1TradeGuestError::OrderBindingMismatch("order_id")); 545 } 546 if witness.request.listing_addr != witness.decision.listing_addr { 547 return Err(RadrootsSp1TradeGuestError::OrderBindingMismatch( 548 "listing_addr", 549 )); 550 } 551 if witness.request.buyer_pubkey != witness.decision.buyer_pubkey { 552 return Err(RadrootsSp1TradeGuestError::OrderBindingMismatch( 553 "buyer_pubkey", 554 )); 555 } 556 if witness.request.seller_pubkey != witness.decision.seller_pubkey { 557 return Err(RadrootsSp1TradeGuestError::OrderBindingMismatch( 558 "seller_pubkey", 559 )); 560 } 561 Ok(()) 562 } 563 564 fn aggregate_requested_counts( 565 request: &RadrootsSp1TradeOrderRequestWitness, 566 ) -> Result<BTreeMap<String, u64>, RadrootsSp1TradeGuestError> { 567 let mut counts = BTreeMap::new(); 568 for item in &request.items { 569 let entry = counts.entry(item.bin_id.clone()).or_insert(0u64); 570 *entry = entry 571 .checked_add(u64::from(item.bin_count)) 572 .ok_or(RadrootsSp1TradeGuestError::InventoryOverflow)?; 573 } 574 Ok(counts) 575 } 576 577 fn aggregate_accepted_counts( 578 decision: &RadrootsSp1TradeOrderDecisionEventWitness, 579 ) -> Result<BTreeMap<String, u64>, RadrootsSp1TradeGuestError> { 580 let RadrootsSp1TradeOrderDecisionWitness::Accepted { 581 inventory_commitments, 582 } = &decision.decision 583 else { 584 return Err(RadrootsSp1TradeGuestError::DecisionNotAccepted); 585 }; 586 let mut counts = BTreeMap::new(); 587 for commitment in inventory_commitments { 588 let entry = counts.entry(commitment.bin_id.clone()).or_insert(0u64); 589 *entry = entry 590 .checked_add(u64::from(commitment.bin_count)) 591 .ok_or(RadrootsSp1TradeGuestError::InventoryOverflow)?; 592 } 593 Ok(counts) 594 } 595 596 fn inventory_bins_by_id( 597 bins: &[RadrootsSp1TradeInventoryBinWitness], 598 ) -> Result<BTreeMap<String, RadrootsSp1TradeInventoryBinWitness>, RadrootsSp1TradeGuestError> { 599 let mut result = BTreeMap::new(); 600 for bin in bins { 601 validate_required_str(&bin.bin_id, "inventory_bins.bin_id")?; 602 if result.insert(bin.bin_id.clone(), bin.clone()).is_some() { 603 return Err(RadrootsSp1TradeGuestError::DuplicateInventoryBin( 604 bin.bin_id.clone(), 605 )); 606 } 607 } 608 Ok(result) 609 } 610 611 fn apply_inventory_delta( 612 request_counts: &BTreeMap<String, u64>, 613 bins: &BTreeMap<String, RadrootsSp1TradeInventoryBinWitness>, 614 ) -> Result<BTreeMap<String, u64>, RadrootsSp1TradeGuestError> { 615 let mut next = BTreeMap::new(); 616 for (bin_id, requested) in request_counts { 617 let bin = bins 618 .get(bin_id) 619 .ok_or_else(|| RadrootsSp1TradeGuestError::MissingInventoryBin(bin_id.clone()))?; 620 let reserved = bin 621 .previous_reserved 622 .checked_add(*requested) 623 .ok_or(RadrootsSp1TradeGuestError::InventoryOverflow)?; 624 if reserved > bin.listing_capacity { 625 return Err(RadrootsSp1TradeGuestError::InventoryOvercommit( 626 bin_id.clone(), 627 )); 628 } 629 next.insert(bin_id.clone(), reserved); 630 } 631 Ok(next) 632 } 633 634 fn validate_public_values( 635 public_values: &RadrootsSp1TradeProofPublicValues, 636 ) -> Result<(), RadrootsSp1TradeGuestError> { 637 if public_values.schema_version != RADROOTS_SP1_TRADE_PUBLIC_VALUES_SCHEMA_VERSION { 638 return Err(RadrootsSp1TradeGuestError::InvalidHash("schema_version")); 639 } 640 if public_values.witness_version != RADROOTS_SP1_TRADE_WITNESS_VERSION { 641 return Err(RadrootsSp1TradeGuestError::UnsupportedWitnessVersion); 642 } 643 validate_required_str(&public_values.proof_target, "proof_target")?; 644 if public_values.proof_target != RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET { 645 return Err(RadrootsSp1TradeGuestError::UnsupportedProofTarget); 646 } 647 validate_required_str( 648 &public_values.radroots_protocol_version, 649 "radroots_protocol_version", 650 )?; 651 if public_values.radroots_protocol_version != RADROOTS_SP1_TRADE_PROTOCOL_VERSION { 652 return Err(RadrootsSp1TradeGuestError::UnsupportedProtocolVersion); 653 } 654 validate_hash32(&public_values.reducer_program_hash, "reducer_program_hash")?; 655 if public_values.reducer_program_hash != RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH { 656 return Err(RadrootsSp1TradeGuestError::UnsupportedReducerProgramHash); 657 } 658 if let Some(hash) = &public_values.sp1_program_hash { 659 validate_hash32(hash, "sp1_program_hash")?; 660 } 661 if let Some(hash) = &public_values.sp1_verifying_key_hash { 662 validate_hash32(hash, "sp1_verifying_key_hash")?; 663 } 664 validate_hash32(&public_values.event_set_root, "event_set_root")?; 665 if let Some(hash) = &public_values.listing_addr_hash { 666 validate_hash32(hash, "listing_addr_hash")?; 667 } 668 if let Some(event_id) = &public_values.listing_event_id { 669 validate_event_id(event_id, "listing_event_id")?; 670 } 671 if let Some(hash) = &public_values.order_id_hash { 672 validate_hash32(hash, "order_id_hash")?; 673 } 674 if let Some(event_id) = &public_values.root_event_id { 675 validate_event_id(event_id, "root_event_id")?; 676 } 677 if let Some(event_id) = &public_values.target_event_id { 678 validate_event_id(event_id, "target_event_id")?; 679 } 680 validate_hash32(&public_values.previous_state_root, "previous_state_root")?; 681 validate_hash32(&public_values.new_state_root, "new_state_root")?; 682 validate_hash32(&public_values.changed_records_root, "changed_records_root")?; 683 if public_values.error_bitmap != zero_error_bitmap() { 684 return Err(RadrootsSp1TradeGuestError::InvalidHash("error_bitmap")); 685 } 686 if let Some(hash) = &public_values.inventory_delta_root { 687 validate_hash32(hash, "inventory_delta_root")?; 688 } 689 if let Some(hash) = &public_values.inventory_prev_root { 690 validate_hash32(hash, "inventory_prev_root")?; 691 } 692 if let Some(hash) = &public_values.inventory_new_root { 693 validate_hash32(hash, "inventory_new_root")?; 694 } 695 Ok(()) 696 } 697 698 fn event_evidence_set_root(evidence: &[RadrootsSp1TradeCanonicalEventEvidence]) -> String { 699 let mut sorted = evidence.to_vec(); 700 sorted.sort_by(compare_event_evidence_order); 701 hash_json("radroots:event-evidence-set:v1", &sorted) 702 } 703 704 fn compare_event_evidence_order( 705 left: &RadrootsSp1TradeCanonicalEventEvidence, 706 right: &RadrootsSp1TradeCanonicalEventEvidence, 707 ) -> std::cmp::Ordering { 708 let ordering = left.ordering_key.cmp(&right.ordering_key); 709 if ordering == std::cmp::Ordering::Equal { 710 left.event_id.cmp(&right.event_id) 711 } else { 712 ordering 713 } 714 } 715 716 fn hash_json<T: Serialize>(domain: &'static str, value: &T) -> String { 717 let bytes = serde_json::to_vec(value).expect("hash material serialization"); 718 hash_bytes(domain, &bytes) 719 } 720 721 fn hash_bytes(domain: &'static str, bytes: &[u8]) -> String { 722 let mut hasher = Sha256::new(); 723 hasher.update(domain.as_bytes()); 724 hasher.update(bytes); 725 format!("0x{}", hex_lower(hasher.finalize().as_slice())) 726 } 727 728 fn validate_required_str( 729 value: &str, 730 field: &'static str, 731 ) -> Result<(), RadrootsSp1TradeGuestError> { 732 if value.trim().is_empty() { 733 return Err(RadrootsSp1TradeGuestError::EmptyField(field)); 734 } 735 Ok(()) 736 } 737 738 fn validate_hash32(value: &str, field: &'static str) -> Result<(), RadrootsSp1TradeGuestError> { 739 if value.len() != 66 || !value.starts_with("0x") || !is_lower_hex(&value[2..]) { 740 return Err(RadrootsSp1TradeGuestError::InvalidHash(field)); 741 } 742 Ok(()) 743 } 744 745 fn validate_event_id(value: &str, field: &'static str) -> Result<(), RadrootsSp1TradeGuestError> { 746 if value.len() != 64 || !is_lower_hex(value) { 747 return Err(RadrootsSp1TradeGuestError::InvalidEventId(field)); 748 } 749 Ok(()) 750 } 751 752 fn validate_hex64(value: &str, field: &'static str) -> Result<(), RadrootsSp1TradeGuestError> { 753 if value.len() != 64 || !is_lower_hex(value) { 754 return Err(RadrootsSp1TradeGuestError::InvalidEventEvidence(field)); 755 } 756 Ok(()) 757 } 758 759 impl RadrootsSp1TradeEventWorkflowPosition { 760 const fn as_str(self) -> &'static str { 761 match self { 762 Self::Listing => "listing", 763 Self::OrderRequest => "order_request", 764 Self::OrderDecision => "order_decision", 765 } 766 } 767 } 768 769 fn is_lower_hex(value: &str) -> bool { 770 value 771 .bytes() 772 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) 773 } 774 775 fn hex_lower(bytes: &[u8]) -> String { 776 const HEX: &[u8; 16] = b"0123456789abcdef"; 777 let mut out = String::with_capacity(bytes.len() * 2); 778 for byte in bytes { 779 out.push(HEX[(byte >> 4) as usize] as char); 780 out.push(HEX[(byte & 0x0f) as usize] as char); 781 } 782 out 783 } 784 785 fn zero_error_bitmap() -> &'static str { 786 "0x00000000000000000000000000000000" 787 } 788 789 #[derive(Serialize)] 790 struct ChangedRecordsMaterial<'a> { 791 order_id: &'a str, 792 listing_addr: &'a str, 793 target_event_id: &'a str, 794 inventory_new_root: &'a str, 795 } 796 797 #[derive(Serialize)] 798 struct StateRootMaterial<'a> { 799 previous_state_root: &'a str, 800 event_set_root: &'a str, 801 changed_records_root: &'a str, 802 inventory_new_root: &'a str, 803 } 804 805 #[cfg(test)] 806 #[cfg_attr(coverage_nightly, coverage(off))] 807 mod tests { 808 use super::{ 809 RADROOTS_SP1_TRADE_KIND_LISTING, RADROOTS_SP1_TRADE_KIND_LISTING_DRAFT, 810 RADROOTS_SP1_TRADE_KIND_ORDER_DECISION, RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST, 811 RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET, RADROOTS_SP1_TRADE_PROTOCOL_VERSION, 812 RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH, RADROOTS_SP1_TRADE_WITNESS_VERSION, 813 RadrootsSp1TradeCanonicalEventEvidence, RadrootsSp1TradeEventEvidenceRole, 814 RadrootsSp1TradeEventWorkflowPosition, RadrootsSp1TradeGuestError, 815 RadrootsSp1TradeInventoryBinWitness, RadrootsSp1TradeInventoryCommitmentWitness, 816 RadrootsSp1TradeOrderAcceptanceWitness, RadrootsSp1TradeOrderDecisionEventWitness, 817 RadrootsSp1TradeOrderDecisionWitness, RadrootsSp1TradeOrderItemWitness, 818 RadrootsSp1TradeOrderRequestWitness, RadrootsSp1TradeProofResult, 819 RadrootsSp1TradeProofTransitionKind, canonical_public_values_bytes, public_values_hash_hex, 820 reduce_order_acceptance_canonical_public_values, reduce_order_acceptance_public_values, 821 }; 822 823 fn witness() -> RadrootsSp1TradeOrderAcceptanceWitness { 824 RadrootsSp1TradeOrderAcceptanceWitness { 825 witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION, 826 proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_string(), 827 listing_event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 828 .to_string(), 829 request_event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 830 .to_string(), 831 decision_event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" 832 .to_string(), 833 event_evidence: event_evidence(), 834 request: request(2), 835 decision: decision(2), 836 inventory_bins: vec![RadrootsSp1TradeInventoryBinWitness { 837 bin_id: "bin-1".to_string(), 838 listing_capacity: 5, 839 previous_reserved: 1, 840 }], 841 inventory_sequence: 7, 842 previous_state_root: None, 843 reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_string(), 844 radroots_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_string(), 845 sp1_program_hash: Some( 846 "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(), 847 ), 848 sp1_verifying_key_hash: Some( 849 "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(), 850 ), 851 } 852 } 853 854 fn event_evidence() -> Vec<RadrootsSp1TradeCanonicalEventEvidence> { 855 vec![ 856 RadrootsSp1TradeCanonicalEventEvidence { 857 event_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 858 .to_string(), 859 signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 860 .to_string(), 861 kind: RADROOTS_SP1_TRADE_KIND_LISTING, 862 canonical_event_hash: 863 "0x1010101010101010101010101010101010101010101010101010101010101010".to_string(), 864 signature_hash: 865 "0x1111111111111111111111111111111111111111111111111111111111111111".to_string(), 866 preverified_signature: true, 867 role: RadrootsSp1TradeEventEvidenceRole::Seller, 868 workflow_position: RadrootsSp1TradeEventWorkflowPosition::Listing, 869 content_hash: "0x1212121212121212121212121212121212121212121212121212121212121212" 870 .to_string(), 871 tags_hash: "0x1313131313131313131313131313131313131313131313131313131313131313" 872 .to_string(), 873 ordering_key: "001:listing".to_string(), 874 }, 875 RadrootsSp1TradeCanonicalEventEvidence { 876 event_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" 877 .to_string(), 878 signer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222" 879 .to_string(), 880 kind: RADROOTS_SP1_TRADE_KIND_ORDER_REQUEST, 881 canonical_event_hash: 882 "0x2020202020202020202020202020202020202020202020202020202020202020".to_string(), 883 signature_hash: 884 "0x2121212121212121212121212121212121212121212121212121212121212121".to_string(), 885 preverified_signature: true, 886 role: RadrootsSp1TradeEventEvidenceRole::Buyer, 887 workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderRequest, 888 content_hash: "0x2222222222222222222222222222222222222222222222222222222222222222" 889 .to_string(), 890 tags_hash: "0x2323232323232323232323232323232323232323232323232323232323232323" 891 .to_string(), 892 ordering_key: "002:order_request".to_string(), 893 }, 894 RadrootsSp1TradeCanonicalEventEvidence { 895 event_id: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" 896 .to_string(), 897 signer_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 898 .to_string(), 899 kind: RADROOTS_SP1_TRADE_KIND_ORDER_DECISION, 900 canonical_event_hash: 901 "0x3030303030303030303030303030303030303030303030303030303030303030".to_string(), 902 signature_hash: 903 "0x3131313131313131313131313131313131313131313131313131313131313131".to_string(), 904 preverified_signature: true, 905 role: RadrootsSp1TradeEventEvidenceRole::Seller, 906 workflow_position: RadrootsSp1TradeEventWorkflowPosition::OrderDecision, 907 content_hash: "0x3232323232323232323232323232323232323232323232323232323232323232" 908 .to_string(), 909 tags_hash: "0x3333333333333333333333333333333333333333333333333333333333333333" 910 .to_string(), 911 ordering_key: "003:order_decision".to_string(), 912 }, 913 ] 914 } 915 916 fn request(bin_count: u32) -> RadrootsSp1TradeOrderRequestWitness { 917 RadrootsSp1TradeOrderRequestWitness { 918 order_id: "order-1".to_string(), 919 listing_addr: 920 "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1" 921 .to_string(), 922 buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222" 923 .to_string(), 924 seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 925 .to_string(), 926 items: vec![RadrootsSp1TradeOrderItemWitness { 927 bin_id: "bin-1".to_string(), 928 bin_count, 929 }], 930 } 931 } 932 933 fn decision(bin_count: u32) -> RadrootsSp1TradeOrderDecisionEventWitness { 934 RadrootsSp1TradeOrderDecisionEventWitness { 935 order_id: "order-1".to_string(), 936 listing_addr: 937 "30402:1111111111111111111111111111111111111111111111111111111111111111:listing-1" 938 .to_string(), 939 buyer_pubkey: "2222222222222222222222222222222222222222222222222222222222222222" 940 .to_string(), 941 seller_pubkey: "1111111111111111111111111111111111111111111111111111111111111111" 942 .to_string(), 943 decision: RadrootsSp1TradeOrderDecisionWitness::Accepted { 944 inventory_commitments: vec![RadrootsSp1TradeInventoryCommitmentWitness { 945 bin_id: "bin-1".to_string(), 946 bin_count, 947 }], 948 }, 949 } 950 } 951 952 #[test] 953 fn order_acceptance_public_values_are_deterministic() { 954 let left = reduce_order_acceptance_public_values(&witness()).expect("left execution"); 955 let right = reduce_order_acceptance_public_values(&witness()).expect("right execution"); 956 assert_eq!(left.public_values, right.public_values); 957 assert_eq!(left.canonical_public_values, right.canonical_public_values); 958 assert_eq!(left.public_values_hash, right.public_values_hash); 959 assert_eq!( 960 left.public_values.transition, 961 Some(RadrootsSp1TradeProofTransitionKind::OrderAccepted) 962 ); 963 assert_eq!( 964 left.public_values.result, 965 RadrootsSp1TradeProofResult::Valid 966 ); 967 assert_eq!( 968 left.public_values.root_event_id.as_deref(), 969 Some("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") 970 ); 971 assert_eq!( 972 left.public_values.target_event_id.as_deref(), 973 Some("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc") 974 ); 975 } 976 977 #[test] 978 fn public_values_canonical_bytes_reencode_identically() { 979 let execution = reduce_order_acceptance_public_values(&witness()).expect("execution"); 980 let decoded: super::RadrootsSp1TradeProofPublicValues = 981 serde_json::from_slice(&execution.canonical_public_values).expect("decode"); 982 let encoded = canonical_public_values_bytes(&decoded).expect("reencode"); 983 assert_eq!(execution.canonical_public_values, encoded); 984 assert_eq!( 985 public_values_hash_hex(&decoded).expect("hash"), 986 execution.public_values_hash 987 ); 988 } 989 990 #[test] 991 fn guest_public_values_output_is_canonical_bytes() { 992 let execution = reduce_order_acceptance_public_values(&witness()).expect("execution"); 993 let bytes = 994 reduce_order_acceptance_canonical_public_values(&witness()).expect("guest bytes"); 995 assert_eq!(bytes, execution.canonical_public_values); 996 } 997 998 #[test] 999 fn overcommitted_inventory_is_rejected() { 1000 let mut input = witness(); 1001 input.inventory_bins[0].listing_capacity = 2; 1002 let err = reduce_order_acceptance_public_values(&input).expect_err("overcommit"); 1003 assert_eq!( 1004 err, 1005 RadrootsSp1TradeGuestError::InventoryOvercommit("bin-1".to_string()) 1006 ); 1007 } 1008 1009 #[test] 1010 fn mismatched_commitment_is_rejected() { 1011 let mut input = witness(); 1012 input.decision = decision(1); 1013 let err = reduce_order_acceptance_public_values(&input).expect_err("mismatch"); 1014 assert_eq!(err, RadrootsSp1TradeGuestError::InventoryCommitmentMismatch); 1015 } 1016 1017 #[test] 1018 fn parsed_only_witness_is_rejected() { 1019 let mut input = witness(); 1020 input.event_evidence.clear(); 1021 let err = reduce_order_acceptance_public_values(&input).expect_err("missing evidence"); 1022 assert_eq!( 1023 err, 1024 RadrootsSp1TradeGuestError::MissingEventEvidence("event_evidence") 1025 ); 1026 } 1027 1028 #[test] 1029 fn event_evidence_must_be_preverified() { 1030 let mut input = witness(); 1031 input.event_evidence[1].preverified_signature = false; 1032 let err = reduce_order_acceptance_public_values(&input).expect_err("preverified"); 1033 assert_eq!(err, RadrootsSp1TradeGuestError::SignatureNotPreverified); 1034 } 1035 1036 #[test] 1037 fn unsupported_event_evidence_kind_is_rejected() { 1038 let mut input = witness(); 1039 input.event_evidence[1].kind = 1; 1040 let err = reduce_order_acceptance_public_values(&input).expect_err("kind"); 1041 assert_eq!( 1042 err, 1043 RadrootsSp1TradeGuestError::UnsupportedEventEvidenceKind(1) 1044 ); 1045 } 1046 1047 #[test] 1048 fn event_evidence_signer_must_match_payload_binding() { 1049 let mut input = witness(); 1050 input.event_evidence[1].signer_pubkey = 1051 "3333333333333333333333333333333333333333333333333333333333333333".to_string(); 1052 let err = reduce_order_acceptance_public_values(&input).expect_err("signer"); 1053 assert_eq!( 1054 err, 1055 RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch("signer_pubkey") 1056 ); 1057 } 1058 1059 #[test] 1060 fn noncanonical_reducer_identity_is_rejected() { 1061 let mut input = witness(); 1062 input.reducer_program_hash = 1063 "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(); 1064 let err = reduce_order_acceptance_public_values(&input).expect_err("reducer"); 1065 assert_eq!( 1066 err, 1067 RadrootsSp1TradeGuestError::UnsupportedReducerProgramHash 1068 ); 1069 } 1070 1071 #[test] 1072 fn noncanonical_protocol_identity_is_rejected() { 1073 let mut input = witness(); 1074 input.radroots_protocol_version = "radroots.trade.legacy".to_string(); 1075 let err = reduce_order_acceptance_public_values(&input).expect_err("protocol"); 1076 assert_eq!(err, RadrootsSp1TradeGuestError::UnsupportedProtocolVersion); 1077 } 1078 1079 #[test] 1080 fn event_evidence_commitment_changes_public_values() { 1081 let left = reduce_order_acceptance_public_values(&witness()).expect("left"); 1082 let mut input = witness(); 1083 input.event_evidence[1].canonical_event_hash = 1084 "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(); 1085 let right = reduce_order_acceptance_public_values(&input).expect("right"); 1086 assert_ne!( 1087 left.public_values.event_set_root, 1088 right.public_values.event_set_root 1089 ); 1090 assert_ne!(left.public_values_hash, right.public_values_hash); 1091 1092 let mut input = witness(); 1093 input.event_evidence.reverse(); 1094 let reordered = reduce_order_acceptance_public_values(&input).expect("reordered"); 1095 assert_eq!( 1096 left.public_values.event_set_root, 1097 reordered.public_values.event_set_root 1098 ); 1099 assert_eq!(left.public_values_hash, reordered.public_values_hash); 1100 1101 let mut input = witness(); 1102 input.event_evidence[0].ordering_key = "same".to_string(); 1103 input.event_evidence[1].ordering_key = "same".to_string(); 1104 reduce_order_acceptance_public_values(&input).expect("same ordering key"); 1105 } 1106 1107 #[test] 1108 fn event_evidence_binding_labels_are_position_specific() { 1109 let mut input = witness(); 1110 input.event_evidence[1].event_id = 1111 "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(); 1112 assert_eq!( 1113 reduce_order_acceptance_public_values(&input).expect_err("order request binding"), 1114 RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch("order_request") 1115 ); 1116 1117 let mut input = witness(); 1118 input.event_evidence[2].event_id = 1119 "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(); 1120 assert_eq!( 1121 reduce_order_acceptance_public_values(&input).expect_err("order decision binding"), 1122 RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch("order_decision") 1123 ); 1124 } 1125 1126 #[test] 1127 fn witness_header_validation_rejects_noncanonical_fields() { 1128 let mut input = witness(); 1129 input.witness_version = RADROOTS_SP1_TRADE_WITNESS_VERSION + 1; 1130 assert_eq!( 1131 reduce_order_acceptance_public_values(&input).expect_err("witness version"), 1132 RadrootsSp1TradeGuestError::UnsupportedWitnessVersion 1133 ); 1134 1135 let mut input = witness(); 1136 input.proof_target = "trade.order_legacy.v1".to_string(); 1137 assert_eq!( 1138 reduce_order_acceptance_public_values(&input).expect_err("proof target"), 1139 RadrootsSp1TradeGuestError::UnsupportedProofTarget 1140 ); 1141 1142 let mut input = witness(); 1143 input.proof_target = " ".to_string(); 1144 assert_eq!( 1145 reduce_order_acceptance_public_values(&input).expect_err("empty proof target"), 1146 RadrootsSp1TradeGuestError::EmptyField("proof_target") 1147 ); 1148 1149 let mut input = witness(); 1150 input.listing_event_id = "not-an-event-id".to_string(); 1151 assert_eq!( 1152 reduce_order_acceptance_public_values(&input).expect_err("listing event id"), 1153 RadrootsSp1TradeGuestError::InvalidEventId("listing_event_id") 1154 ); 1155 1156 let mut input = witness(); 1157 input.reducer_program_hash = "not-a-hash".to_string(); 1158 assert_eq!( 1159 reduce_order_acceptance_public_values(&input).expect_err("reducer hash"), 1160 RadrootsSp1TradeGuestError::InvalidHash("reducer_program_hash") 1161 ); 1162 1163 let mut input = witness(); 1164 input.radroots_protocol_version = String::new(); 1165 assert_eq!( 1166 reduce_order_acceptance_public_values(&input).expect_err("empty protocol"), 1167 RadrootsSp1TradeGuestError::EmptyField("radroots_protocol_version") 1168 ); 1169 1170 let mut input = witness(); 1171 input.sp1_program_hash = Some("not-a-hash".to_string()); 1172 assert_eq!( 1173 reduce_order_acceptance_public_values(&input).expect_err("sp1 program hash"), 1174 RadrootsSp1TradeGuestError::InvalidHash("sp1_program_hash") 1175 ); 1176 1177 let mut input = witness(); 1178 input.sp1_verifying_key_hash = Some("not-a-hash".to_string()); 1179 assert_eq!( 1180 reduce_order_acceptance_public_values(&input).expect_err("sp1 verifying key hash"), 1181 RadrootsSp1TradeGuestError::InvalidHash("sp1_verifying_key_hash") 1182 ); 1183 1184 let mut input = witness(); 1185 input.sp1_program_hash = None; 1186 input.sp1_verifying_key_hash = None; 1187 reduce_order_acceptance_public_values(&input).expect("optional sp1 hashes"); 1188 } 1189 1190 #[test] 1191 fn event_evidence_validation_rejects_shape_and_binding_errors() { 1192 let mut input = witness(); 1193 input.event_evidence.pop(); 1194 assert_eq!( 1195 reduce_order_acceptance_public_values(&input).expect_err("evidence len"), 1196 RadrootsSp1TradeGuestError::InvalidEventEvidence("event_evidence.len") 1197 ); 1198 1199 let mut input = witness(); 1200 input.event_evidence[2].workflow_position = 1201 RadrootsSp1TradeEventWorkflowPosition::OrderRequest; 1202 assert_eq!( 1203 reduce_order_acceptance_public_values(&input).expect_err("duplicate evidence"), 1204 RadrootsSp1TradeGuestError::DuplicateEventEvidence("order_request") 1205 ); 1206 1207 let mut input = witness(); 1208 input.event_evidence[0].event_id = "not-an-event-id".to_string(); 1209 assert_eq!( 1210 reduce_order_acceptance_public_values(&input).expect_err("evidence event id"), 1211 RadrootsSp1TradeGuestError::InvalidEventId("event_evidence.event_id") 1212 ); 1213 1214 let mut input = witness(); 1215 input.event_evidence[0].signer_pubkey = "not-a-pubkey".to_string(); 1216 assert_eq!( 1217 reduce_order_acceptance_public_values(&input).expect_err("evidence signer"), 1218 RadrootsSp1TradeGuestError::InvalidEventEvidence("event_evidence.signer_pubkey") 1219 ); 1220 1221 let mut input = witness(); 1222 input.event_evidence[0].canonical_event_hash = "not-a-hash".to_string(); 1223 assert_eq!( 1224 reduce_order_acceptance_public_values(&input).expect_err("canonical hash"), 1225 RadrootsSp1TradeGuestError::InvalidHash("event_evidence.canonical_event_hash") 1226 ); 1227 1228 let mut input = witness(); 1229 input.event_evidence[0].signature_hash = "not-a-hash".to_string(); 1230 assert_eq!( 1231 reduce_order_acceptance_public_values(&input).expect_err("signature hash"), 1232 RadrootsSp1TradeGuestError::InvalidHash("event_evidence.signature_hash") 1233 ); 1234 1235 let mut input = witness(); 1236 input.event_evidence[0].content_hash = "not-a-hash".to_string(); 1237 assert_eq!( 1238 reduce_order_acceptance_public_values(&input).expect_err("content hash"), 1239 RadrootsSp1TradeGuestError::InvalidHash("event_evidence.content_hash") 1240 ); 1241 1242 let mut input = witness(); 1243 input.event_evidence[0].tags_hash = "not-a-hash".to_string(); 1244 assert_eq!( 1245 reduce_order_acceptance_public_values(&input).expect_err("tags hash"), 1246 RadrootsSp1TradeGuestError::InvalidHash("event_evidence.tags_hash") 1247 ); 1248 1249 let mut input = witness(); 1250 input.event_evidence[0].ordering_key = " ".to_string(); 1251 assert_eq!( 1252 reduce_order_acceptance_public_values(&input).expect_err("ordering key"), 1253 RadrootsSp1TradeGuestError::EmptyField("event_evidence.ordering_key") 1254 ); 1255 1256 let mut input = witness(); 1257 input.event_evidence[0].event_id = 1258 "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(); 1259 assert_eq!( 1260 reduce_order_acceptance_public_values(&input).expect_err("listing binding"), 1261 RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch("listing") 1262 ); 1263 1264 let mut input = witness(); 1265 input.event_evidence[0].role = RadrootsSp1TradeEventEvidenceRole::Buyer; 1266 assert_eq!( 1267 reduce_order_acceptance_public_values(&input).expect_err("role binding"), 1268 RadrootsSp1TradeGuestError::EventEvidenceBindingMismatch("role") 1269 ); 1270 1271 let mut input = witness(); 1272 input.event_evidence[0].kind = RADROOTS_SP1_TRADE_KIND_LISTING_DRAFT; 1273 reduce_order_acceptance_public_values(&input).expect("listing draft evidence"); 1274 } 1275 1276 #[test] 1277 fn order_shape_and_binding_validation_rejects_edge_cases() { 1278 let mut request_case = request(1); 1279 request_case.items.clear(); 1280 assert_eq!( 1281 super::validate_order_request_shape(&request_case).expect_err("empty request"), 1282 RadrootsSp1TradeGuestError::InvalidOrderRequest 1283 ); 1284 1285 let mut request_case = request(1); 1286 request_case.items[0].bin_id = " ".to_string(); 1287 assert_eq!( 1288 super::validate_order_request_shape(&request_case).expect_err("empty request bin"), 1289 RadrootsSp1TradeGuestError::EmptyField("request.items.bin_id") 1290 ); 1291 1292 let mut request_case = request(0); 1293 assert_eq!( 1294 super::validate_order_request_shape(&request_case).expect_err("zero request bin count"), 1295 RadrootsSp1TradeGuestError::InvalidOrderRequest 1296 ); 1297 request_case.items[0].bin_count = 1; 1298 super::validate_order_request_shape(&request_case).expect("request shape"); 1299 1300 let mut decision_case = decision(1); 1301 let RadrootsSp1TradeOrderDecisionWitness::Accepted { 1302 inventory_commitments, 1303 } = &mut decision_case.decision 1304 else { 1305 unreachable!(); 1306 }; 1307 inventory_commitments.clear(); 1308 assert_eq!( 1309 super::validate_order_decision_shape(&decision_case).expect_err("empty commitments"), 1310 RadrootsSp1TradeGuestError::InvalidOrderDecision 1311 ); 1312 1313 let mut decision_case = decision(1); 1314 let RadrootsSp1TradeOrderDecisionWitness::Accepted { 1315 inventory_commitments, 1316 } = &mut decision_case.decision 1317 else { 1318 unreachable!(); 1319 }; 1320 inventory_commitments[0].bin_id = " ".to_string(); 1321 assert_eq!( 1322 super::validate_order_decision_shape(&decision_case).expect_err("empty commitment bin"), 1323 RadrootsSp1TradeGuestError::EmptyField("decision.inventory_commitments.bin_id") 1324 ); 1325 1326 let mut decision_case = decision(0); 1327 assert_eq!( 1328 super::validate_order_decision_shape(&decision_case).expect_err("zero commitment"), 1329 RadrootsSp1TradeGuestError::InvalidOrderDecision 1330 ); 1331 decision_case.decision = RadrootsSp1TradeOrderDecisionWitness::Declined { 1332 reason: " ".to_string(), 1333 }; 1334 assert_eq!( 1335 super::validate_order_decision_shape(&decision_case).expect_err("empty decline reason"), 1336 RadrootsSp1TradeGuestError::EmptyField("decision.reason") 1337 ); 1338 decision_case.decision = RadrootsSp1TradeOrderDecisionWitness::Declined { 1339 reason: "sold out".to_string(), 1340 }; 1341 super::validate_order_decision_shape(&decision_case).expect("decline shape"); 1342 assert_eq!( 1343 super::aggregate_accepted_counts(&decision_case).expect_err("declined aggregate"), 1344 RadrootsSp1TradeGuestError::DecisionNotAccepted 1345 ); 1346 1347 let mut input = witness(); 1348 input.decision.decision = RadrootsSp1TradeOrderDecisionWitness::Declined { 1349 reason: "sold out".to_string(), 1350 }; 1351 assert_eq!( 1352 reduce_order_acceptance_public_values(&input).expect_err("declined order"), 1353 RadrootsSp1TradeGuestError::DecisionNotAccepted 1354 ); 1355 1356 let binding_cases: [(&str, fn(&mut RadrootsSp1TradeOrderAcceptanceWitness)); 4] = [ 1357 ( 1358 "order_id", 1359 |input: &mut RadrootsSp1TradeOrderAcceptanceWitness| { 1360 input.decision.order_id = "other-order".to_string(); 1361 }, 1362 ), 1363 ( 1364 "listing_addr", 1365 |input: &mut RadrootsSp1TradeOrderAcceptanceWitness| { 1366 input.decision.listing_addr = "other-listing".to_string(); 1367 }, 1368 ), 1369 ( 1370 "buyer_pubkey", 1371 |input: &mut RadrootsSp1TradeOrderAcceptanceWitness| { 1372 input.decision.buyer_pubkey = 1373 "3333333333333333333333333333333333333333333333333333333333333333" 1374 .to_string(); 1375 }, 1376 ), 1377 ( 1378 "seller_pubkey", 1379 |input: &mut RadrootsSp1TradeOrderAcceptanceWitness| { 1380 input.decision.seller_pubkey = 1381 "3333333333333333333333333333333333333333333333333333333333333333" 1382 .to_string(); 1383 }, 1384 ), 1385 ]; 1386 for (field, apply) in binding_cases { 1387 let mut input = witness(); 1388 apply(&mut input); 1389 assert_eq!( 1390 reduce_order_acceptance_public_values(&input).expect_err("binding mismatch"), 1391 RadrootsSp1TradeGuestError::OrderBindingMismatch(field) 1392 ); 1393 } 1394 } 1395 1396 #[test] 1397 fn inventory_validation_rejects_duplicate_missing_and_overflow() { 1398 let mut input = witness(); 1399 input.inventory_bins.push(input.inventory_bins[0].clone()); 1400 assert_eq!( 1401 reduce_order_acceptance_public_values(&input).expect_err("duplicate bin"), 1402 RadrootsSp1TradeGuestError::DuplicateInventoryBin("bin-1".to_string()) 1403 ); 1404 1405 let mut input = witness(); 1406 input.inventory_bins.clear(); 1407 assert_eq!( 1408 reduce_order_acceptance_public_values(&input).expect_err("missing bin"), 1409 RadrootsSp1TradeGuestError::MissingInventoryBin("bin-1".to_string()) 1410 ); 1411 1412 let mut input = witness(); 1413 input.inventory_bins[0].listing_capacity = u64::MAX; 1414 input.inventory_bins[0].previous_reserved = u64::MAX; 1415 assert_eq!( 1416 reduce_order_acceptance_public_values(&input).expect_err("inventory overflow"), 1417 RadrootsSp1TradeGuestError::InventoryOverflow 1418 ); 1419 } 1420 1421 #[test] 1422 fn public_values_validation_rejects_noncanonical_fields() { 1423 let execution = reduce_order_acceptance_public_values(&witness()).expect("execution"); 1424 1425 let mut optional = execution.public_values.clone(); 1426 optional.sp1_program_hash = None; 1427 optional.sp1_verifying_key_hash = None; 1428 optional.listing_addr_hash = None; 1429 optional.listing_event_id = None; 1430 optional.order_id_hash = None; 1431 optional.root_event_id = None; 1432 optional.target_event_id = None; 1433 optional.inventory_delta_root = None; 1434 optional.inventory_sequence = None; 1435 optional.inventory_prev_root = None; 1436 optional.inventory_new_root = None; 1437 canonical_public_values_bytes(&optional).expect("optional public values"); 1438 1439 let mut public_values = execution.public_values.clone(); 1440 public_values.schema_version += 1; 1441 assert_eq!( 1442 canonical_public_values_bytes(&public_values).expect_err("schema version"), 1443 RadrootsSp1TradeGuestError::InvalidHash("schema_version") 1444 ); 1445 1446 let mut public_values = execution.public_values.clone(); 1447 public_values.witness_version += 1; 1448 assert_eq!( 1449 canonical_public_values_bytes(&public_values).expect_err("witness version"), 1450 RadrootsSp1TradeGuestError::UnsupportedWitnessVersion 1451 ); 1452 1453 let mut public_values = execution.public_values.clone(); 1454 public_values.proof_target = "legacy".to_string(); 1455 assert_eq!( 1456 canonical_public_values_bytes(&public_values).expect_err("proof target"), 1457 RadrootsSp1TradeGuestError::UnsupportedProofTarget 1458 ); 1459 1460 let mut public_values = execution.public_values.clone(); 1461 public_values.radroots_protocol_version = String::new(); 1462 assert_eq!( 1463 canonical_public_values_bytes(&public_values).expect_err("protocol"), 1464 RadrootsSp1TradeGuestError::EmptyField("radroots_protocol_version") 1465 ); 1466 1467 let mut public_values = execution.public_values.clone(); 1468 public_values.radroots_protocol_version = "radroots.trade.legacy".to_string(); 1469 assert_eq!( 1470 canonical_public_values_bytes(&public_values).expect_err("unsupported protocol"), 1471 RadrootsSp1TradeGuestError::UnsupportedProtocolVersion 1472 ); 1473 1474 let hash_fields: [(&str, fn(&mut super::RadrootsSp1TradeProofPublicValues)); 12] = [ 1475 ( 1476 "reducer_program_hash", 1477 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1478 public_values.reducer_program_hash = "not-a-hash".to_string(); 1479 }, 1480 ), 1481 ( 1482 "sp1_program_hash", 1483 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1484 public_values.sp1_program_hash = Some("not-a-hash".to_string()); 1485 }, 1486 ), 1487 ( 1488 "sp1_verifying_key_hash", 1489 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1490 public_values.sp1_verifying_key_hash = Some("not-a-hash".to_string()); 1491 }, 1492 ), 1493 ( 1494 "event_set_root", 1495 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1496 public_values.event_set_root = "not-a-hash".to_string(); 1497 }, 1498 ), 1499 ( 1500 "listing_addr_hash", 1501 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1502 public_values.listing_addr_hash = Some("not-a-hash".to_string()); 1503 }, 1504 ), 1505 ( 1506 "order_id_hash", 1507 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1508 public_values.order_id_hash = Some("not-a-hash".to_string()); 1509 }, 1510 ), 1511 ( 1512 "previous_state_root", 1513 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1514 public_values.previous_state_root = "not-a-hash".to_string(); 1515 }, 1516 ), 1517 ( 1518 "new_state_root", 1519 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1520 public_values.new_state_root = "not-a-hash".to_string(); 1521 }, 1522 ), 1523 ( 1524 "changed_records_root", 1525 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1526 public_values.changed_records_root = "not-a-hash".to_string(); 1527 }, 1528 ), 1529 ( 1530 "inventory_delta_root", 1531 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1532 public_values.inventory_delta_root = Some("not-a-hash".to_string()); 1533 }, 1534 ), 1535 ( 1536 "inventory_prev_root", 1537 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1538 public_values.inventory_prev_root = Some("not-a-hash".to_string()); 1539 }, 1540 ), 1541 ( 1542 "inventory_new_root", 1543 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1544 public_values.inventory_new_root = Some("not-a-hash".to_string()); 1545 }, 1546 ), 1547 ]; 1548 for (field, apply) in hash_fields { 1549 let mut public_values = execution.public_values.clone(); 1550 apply(&mut public_values); 1551 assert_eq!( 1552 canonical_public_values_bytes(&public_values).expect_err("hash field"), 1553 RadrootsSp1TradeGuestError::InvalidHash(field) 1554 ); 1555 } 1556 1557 let event_id_fields: [(&str, fn(&mut super::RadrootsSp1TradeProofPublicValues)); 3] = [ 1558 ( 1559 "listing_event_id", 1560 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1561 public_values.listing_event_id = Some("not-an-event-id".to_string()); 1562 }, 1563 ), 1564 ( 1565 "root_event_id", 1566 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1567 public_values.root_event_id = Some("not-an-event-id".to_string()); 1568 }, 1569 ), 1570 ( 1571 "target_event_id", 1572 |public_values: &mut super::RadrootsSp1TradeProofPublicValues| { 1573 public_values.target_event_id = Some("not-an-event-id".to_string()); 1574 }, 1575 ), 1576 ]; 1577 for (field, apply) in event_id_fields { 1578 let mut public_values = execution.public_values.clone(); 1579 apply(&mut public_values); 1580 assert_eq!( 1581 canonical_public_values_bytes(&public_values).expect_err("event id field"), 1582 RadrootsSp1TradeGuestError::InvalidEventId(field) 1583 ); 1584 } 1585 1586 let mut public_values = execution.public_values.clone(); 1587 public_values.error_bitmap = "0x1".to_string(); 1588 assert_eq!( 1589 canonical_public_values_bytes(&public_values).expect_err("error bitmap"), 1590 RadrootsSp1TradeGuestError::InvalidHash("error_bitmap") 1591 ); 1592 1593 let mut public_values = execution.public_values; 1594 public_values.reducer_program_hash = 1595 "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd".to_string(); 1596 assert_eq!( 1597 canonical_public_values_bytes(&public_values).expect_err("unsupported reducer"), 1598 RadrootsSp1TradeGuestError::UnsupportedReducerProgramHash 1599 ); 1600 } 1601 1602 #[test] 1603 fn scalar_validators_cover_hex_and_workflow_edges() { 1604 assert_eq!( 1605 super::validate_hash32( 1606 "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 1607 "upper_hash" 1608 ) 1609 .expect_err("upper hash"), 1610 RadrootsSp1TradeGuestError::InvalidHash("upper_hash") 1611 ); 1612 assert_eq!( 1613 super::validate_hash32( 1614 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1615 "missing_prefix" 1616 ) 1617 .expect_err("missing prefix"), 1618 RadrootsSp1TradeGuestError::InvalidHash("missing_prefix") 1619 ); 1620 assert_eq!( 1621 super::validate_hash32( 1622 "zzaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1623 "bad_prefix" 1624 ) 1625 .expect_err("bad prefix"), 1626 RadrootsSp1TradeGuestError::InvalidHash("bad_prefix") 1627 ); 1628 assert_eq!( 1629 super::validate_event_id( 1630 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 1631 "upper_event" 1632 ) 1633 .expect_err("upper event id"), 1634 RadrootsSp1TradeGuestError::InvalidEventId("upper_event") 1635 ); 1636 assert_eq!( 1637 super::validate_hex64( 1638 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 1639 "upper_hex64" 1640 ) 1641 .expect_err("upper hex64"), 1642 RadrootsSp1TradeGuestError::InvalidEventEvidence("upper_hex64") 1643 ); 1644 assert_eq!( 1645 RadrootsSp1TradeEventWorkflowPosition::Listing.as_str(), 1646 "listing" 1647 ); 1648 assert_eq!( 1649 RadrootsSp1TradeEventWorkflowPosition::OrderRequest.as_str(), 1650 "order_request" 1651 ); 1652 assert_eq!( 1653 RadrootsSp1TradeEventWorkflowPosition::OrderDecision.as_str(), 1654 "order_decision" 1655 ); 1656 } 1657 }