use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::error::{EscrowError, Result};
use crate::types::{AgentId, ConditionResult, EscrowRole, OracleId, ReleaseCondition};
#[async_trait]
pub trait ConditionEvaluator: Send + Sync {
async fn evaluate(&self, condition: &ReleaseCondition) -> Result<ConditionResult>;
}
pub struct StandardEvaluator {
oracles: HashMap<String, Box<dyn OracleProvider>>,
ai_validators: HashMap<String, Box<dyn AIDecisionValidator>>,
}
impl StandardEvaluator {
pub fn new() -> Self {
Self {
oracles: HashMap::new(),
ai_validators: HashMap::new(),
}
}
pub fn with_oracle(mut self, id: &str, provider: Box<dyn OracleProvider>) -> Self {
self.oracles.insert(id.to_string(), provider);
self
}
pub fn with_ai_validator(mut self, id: &str, validator: Box<dyn AIDecisionValidator>) -> Self {
self.ai_validators.insert(id.to_string(), validator);
self
}
async fn evaluate_ai_decision(
&self,
agent_id: &AgentId,
decision_hash: &str,
) -> Result<ConditionResult> {
let validator = self
.ai_validators
.get(&agent_id.0)
.ok_or_else(|| EscrowError::Condition(format!("Unknown AI agent: {}", agent_id)))?;
validator.validate(decision_hash).await
}
async fn evaluate_oracle(
&self,
oracle_id: &OracleId,
query: &str,
expected_value: &str,
) -> Result<ConditionResult> {
let oracle = self
.oracles
.get(&oracle_id.0)
.ok_or_else(|| EscrowError::Oracle(format!("Unknown oracle: {}", oracle_id)))?;
oracle.query(query, expected_value).await
}
fn evaluate_timelock(&self, unlock_after: &DateTime<Utc>) -> ConditionResult {
let now = Utc::now();
let satisfied = now >= *unlock_after;
ConditionResult {
satisfied,
reason: if satisfied {
format!("Timelock expired at {}", unlock_after)
} else {
format!("Timelock not yet expired (unlocks at {})", unlock_after)
},
evaluated_at: now,
data: Some(serde_json::json!({
"unlock_after": unlock_after.to_rfc3339(),
"current_time": now.to_rfc3339(),
})),
}
}
fn evaluate_manual_approval(&self, required_roles: &[EscrowRole]) -> ConditionResult {
ConditionResult {
satisfied: false, reason: format!(
"Manual approval required from roles: {:?}",
required_roles
),
evaluated_at: Utc::now(),
data: Some(serde_json::json!({
"required_roles": required_roles,
})),
}
}
}
impl Default for StandardEvaluator {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ConditionEvaluator for StandardEvaluator {
async fn evaluate(&self, condition: &ReleaseCondition) -> Result<ConditionResult> {
match condition {
ReleaseCondition::AIDecision { agent_id, decision_hash } => {
self.evaluate_ai_decision(agent_id, decision_hash).await
}
ReleaseCondition::OracleTrigger { oracle_id, query, expected_value } => {
self.evaluate_oracle(oracle_id, query, expected_value).await
}
ReleaseCondition::TimeLock { unlock_after } => {
Ok(self.evaluate_timelock(unlock_after))
}
ReleaseCondition::ManualApproval { required_roles } => {
Ok(self.evaluate_manual_approval(required_roles))
}
ReleaseCondition::AllOf { conditions } => {
let mut all_satisfied = true;
let mut reasons = Vec::new();
let mut data = HashMap::new();
for (i, cond) in conditions.iter().enumerate() {
let result = self.evaluate(cond).await?;
if !result.satisfied {
all_satisfied = false;
}
reasons.push(format!("Condition {}: {}", i + 1, result.reason));
data.insert(format!("condition_{}", i), result.data);
}
Ok(ConditionResult {
satisfied: all_satisfied,
reason: reasons.join("; "),
evaluated_at: Utc::now(),
data: Some(serde_json::to_value(data)?),
})
}
ReleaseCondition::AnyOf { conditions } => {
let mut any_satisfied = false;
let mut reasons = Vec::new();
let mut data = HashMap::new();
for (i, cond) in conditions.iter().enumerate() {
let result = self.evaluate(cond).await?;
if result.satisfied {
any_satisfied = true;
}
reasons.push(format!("Condition {}: {}", i + 1, result.reason));
data.insert(format!("condition_{}", i), result.data);
}
Ok(ConditionResult {
satisfied: any_satisfied,
reason: reasons.join("; "),
evaluated_at: Utc::now(),
data: Some(serde_json::to_value(data)?),
})
}
}
}
}
#[async_trait]
pub trait OracleProvider: Send + Sync {
async fn query(&self, query: &str, expected_value: &str) -> Result<ConditionResult>;
}
#[async_trait]
pub trait AIDecisionValidator: Send + Sync {
async fn validate(&self, decision_hash: &str) -> Result<ConditionResult>;
}
pub struct MockOracle {
responses: HashMap<String, String>,
}
impl MockOracle {
pub fn new(responses: HashMap<String, String>) -> Self {
Self { responses }
}
}
#[async_trait]
impl OracleProvider for MockOracle {
async fn query(&self, query: &str, expected_value: &str) -> Result<ConditionResult> {
let value = self
.responses
.get(query)
.cloned()
.unwrap_or_else(|| "unknown".to_string());
let satisfied = &value == expected_value;
Ok(ConditionResult {
satisfied,
reason: if satisfied {
format!("Oracle returned expected value: {}", value)
} else {
format!(
"Oracle returned '{}', expected '{}'",
value, expected_value
)
},
evaluated_at: Utc::now(),
data: Some(serde_json::json!({
"query": query,
"value": value,
"expected": expected_value,
})),
})
}
}
pub struct MockAIDecisionValidator {
valid_hashes: Vec<String>,
}
impl MockAIDecisionValidator {
pub fn new(valid_hashes: Vec<String>) -> Self {
Self { valid_hashes }
}
}
#[async_trait]
impl AIDecisionValidator for MockAIDecisionValidator {
async fn validate(&self, decision_hash: &str) -> Result<ConditionResult> {
let satisfied = self.valid_hashes.contains(&decision_hash.to_string());
Ok(ConditionResult {
satisfied,
reason: if satisfied {
format!("AI decision {} is valid", decision_hash)
} else {
format!("AI decision {} is not recognized", decision_hash)
},
evaluated_at: Utc::now(),
data: Some(serde_json::json!({
"decision_hash": decision_hash,
"valid": satisfied,
})),
})
}
}
pub struct ConditionBuilder {
conditions: Vec<ReleaseCondition>,
}
impl ConditionBuilder {
pub fn new() -> Self {
Self { conditions: Vec::new() }
}
pub fn ai_decision(mut self, agent_id: impl Into<String>, decision_hash: impl Into<String>) -> Self {
self.conditions.push(ReleaseCondition::AIDecision {
agent_id: AgentId::new(agent_id),
decision_hash: decision_hash.into(),
});
self
}
pub fn oracle(
mut self,
oracle_id: impl Into<String>,
query: impl Into<String>,
expected_value: impl Into<String>,
) -> Self {
self.conditions.push(ReleaseCondition::OracleTrigger {
oracle_id: OracleId::new(oracle_id),
query: query.into(),
expected_value: expected_value.into(),
});
self
}
pub fn timelock(mut self, unlock_after: DateTime<Utc>) -> Self {
self.conditions.push(ReleaseCondition::TimeLock { unlock_after });
self
}
pub fn manual_approval(mut self, required_roles: Vec<EscrowRole>) -> Self {
self.conditions.push(ReleaseCondition::ManualApproval { required_roles });
self
}
pub fn build_all(self) -> ReleaseCondition {
ReleaseCondition::AllOf {
conditions: self.conditions,
}
}
pub fn build_any(self) -> ReleaseCondition {
ReleaseCondition::AnyOf {
conditions: self.conditions,
}
}
pub fn build_single(self) -> Option<ReleaseCondition> {
if self.conditions.len() == 1 {
self.conditions.into_iter().next()
} else {
None
}
}
}
impl Default for ConditionBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[tokio::test]
async fn test_timelock_evaluation() {
let evaluator = StandardEvaluator::new();
let past_time = Utc::now() - chrono::Duration::hours(1);
let condition = ReleaseCondition::TimeLock { unlock_after: past_time };
let result = evaluator.evaluate(&condition).await.unwrap();
assert!(result.satisfied);
let future_time = Utc::now() + chrono::Duration::hours(1);
let condition = ReleaseCondition::TimeLock { unlock_after: future_time };
let result = evaluator.evaluate(&condition).await.unwrap();
assert!(!result.satisfied);
}
#[tokio::test]
async fn test_oracle_evaluation() {
let mut responses = HashMap::new();
responses.insert("btc_price".to_string(), "50000".to_string());
let evaluator = StandardEvaluator::new()
.with_oracle("price_oracle", Box::new(MockOracle::new(responses)));
let condition = ReleaseCondition::OracleTrigger {
oracle_id: OracleId::new("price_oracle"),
query: "btc_price".to_string(),
expected_value: "50000".to_string(),
};
let result = evaluator.evaluate(&condition).await.unwrap();
assert!(result.satisfied);
let condition = ReleaseCondition::OracleTrigger {
oracle_id: OracleId::new("price_oracle"),
query: "btc_price".to_string(),
expected_value: "60000".to_string(),
};
let result = evaluator.evaluate(&condition).await.unwrap();
assert!(!result.satisfied);
}
#[tokio::test]
async fn test_condition_builder() {
let condition = ConditionBuilder::new()
.ai_decision("agent-1", "hash123")
.timelock(Utc::now() - chrono::Duration::hours(1))
.build_all();
match condition {
ReleaseCondition::AllOf { conditions } => {
assert_eq!(conditions.len(), 2);
}
_ => panic!("Expected AllOf condition"),
}
}
}