use crate::protocol::asset::AssetRegistry;
use crate::protocol::rails::TrustTier;
use crate::protocol::settlement::{
SettlementManager, SettlementProposal, SettlementTrigger, TriggerSource,
};
use crate::ConclaveResult;
use async_trait::async_trait;
use std::sync::Arc;
#[async_trait(?Send)]
pub trait SettlementService: Send + Sync {
async fn process_external_trigger(
&self,
trigger: SettlementTrigger,
asset_chain: &str,
asset_symbol: &str,
amount: u64,
recipient: String,
current_height: u64,
) -> ConclaveResult<SettlementProposal>;
async fn verify_reconciliation(
&self,
proposal: &SettlementProposal,
trigger: &SettlementTrigger,
) -> ConclaveResult<bool>;
}
pub struct ConclaveSettlementService {
pub manager: SettlementManager,
}
impl ConclaveSettlementService {
pub fn new(asset_registry: Arc<AssetRegistry>) -> Self {
Self {
manager: SettlementManager::new(asset_registry),
}
}
pub fn resolve_trust_tier(&self, source: &TriggerSource) -> TrustTier {
match source {
TriggerSource::Iso20022 => TrustTier::T1, TriggerSource::Papss => TrustTier::T2, TriggerSource::Brics => TrustTier::T3, }
}
}
#[async_trait(?Send)]
impl SettlementService for ConclaveSettlementService {
async fn process_external_trigger(
&self,
trigger: SettlementTrigger,
asset_chain: &str,
asset_symbol: &str,
amount: u64,
recipient: String,
current_height: u64,
) -> ConclaveResult<SettlementProposal> {
if !self.manager.verify_trigger(&trigger)? {
return Err(crate::ConclaveError::InvalidPayload);
}
let tier = self.resolve_trust_tier(&trigger.source);
if tier == TrustTier::T4 {
return Err(crate::ConclaveError::RailError(
"Route trust tier T4 is forbidden in production".to_string(),
));
}
let proposal = self.manager.create_proposal(
&trigger,
asset_chain,
asset_symbol,
amount,
recipient,
current_height,
)?;
if proposal.timelock_height != current_height + 144 {
return Err(crate::ConclaveError::CryptoError(
"Mandatory 144-block timelock violation".to_string(),
));
}
Ok(proposal)
}
async fn verify_reconciliation(
&self,
proposal: &SettlementProposal,
trigger: &SettlementTrigger,
) -> ConclaveResult<bool> {
if proposal.trigger_id != trigger.trigger_id {
return Ok(false);
}
if trigger.source == TriggerSource::Iso20022 {
let payload = String::from_utf8_lossy(&trigger.raw_payload_bytes);
if !payload.contains("pacs.008.001.08") {
return Ok(false);
}
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::asset::{AssetRegistry, Chain};
use crate::protocol::settlement::TriggerSource;
#[tokio::test]
async fn test_settlement_service_trigger_to_proposal() {
let registry = Arc::new(AssetRegistry::new());
let svc = ConclaveSettlementService::new(registry);
let payload = b"<?xml version=\"1.0\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08\"><FIToFICstmrCdtTrf></FIToFICstmrCdtTrf></Document>".to_vec();
let trigger = SettlementTrigger::new(TriggerSource::Iso20022, payload);
let proposal = svc
.process_external_trigger(
trigger,
"STACKS",
"STX",
500000000, "SP...".to_string(),
120000,
)
.await
.unwrap();
assert_eq!(proposal.asset.chain, Chain::STACKS);
assert_eq!(proposal.timelock_height, 120000 + 144);
assert_eq!(proposal.amount, 500000000);
}
#[tokio::test]
async fn test_verify_reconciliation() {
let registry = Arc::new(AssetRegistry::new());
let svc = ConclaveSettlementService::new(registry);
let payload = b"<?xml version=\"1.0\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08\"><FIToFICstmrCdtTrf></FIToFICstmrCdtTrf></Document>".to_vec();
let trigger = SettlementTrigger::new(TriggerSource::Iso20022, payload);
let proposal = svc
.process_external_trigger(
trigger.clone(),
"BITCOIN",
"BTC",
1000000,
"bc1q...".to_string(),
840000,
)
.await
.unwrap();
let reconciled = svc
.verify_reconciliation(&proposal, &trigger)
.await
.unwrap();
assert!(reconciled);
let bad_trigger = SettlementTrigger::new(TriggerSource::Iso20022, b"tampered".to_vec());
let reconciled = svc
.verify_reconciliation(&proposal, &bad_trigger)
.await
.unwrap();
assert!(!reconciled);
}
#[test]
fn test_trust_tier_resolution() {
let registry = Arc::new(AssetRegistry::new());
let svc = ConclaveSettlementService::new(registry);
assert_eq!(
svc.resolve_trust_tier(&TriggerSource::Iso20022),
TrustTier::T1
);
assert_eq!(svc.resolve_trust_tier(&TriggerSource::Papss), TrustTier::T2);
assert_eq!(svc.resolve_trust_tier(&TriggerSource::Brics), TrustTier::T3);
}
}