chio-credit 0.1.2

Chio credit, capital, and bonded execution contracts
Documentation
use super::*;

pub const CLEARING_ZERO_INTENT_RECONCILIATION_SCHEMA: &str =
    "chio.clearing.zero-intent-reconciliation.v1";

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClearingZeroIntentOutcomeV1 {
    NettedWithoutRail,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ClearingZeroIntentAtomBindingV1 {
    pub source_sequence: u64,
    pub obligation_id: String,
    pub expected_head_digest: String,
    pub expected_resource_version: u64,
    pub expected_lifecycle_fence: u64,
}

impl ClearingZeroIntentAtomBindingV1 {
    fn validate(&self) -> Result<(), ClearingError> {
        validate_positive("zero_intent_source_sequence", self.source_sequence)?;
        validate_digest("zero_intent_obligation_id", &self.obligation_id)?;
        validate_digest(
            "zero_intent_expected_head_digest",
            &self.expected_head_digest,
        )?;
        validate_positive(
            "zero_intent_expected_resource_version",
            self.expected_resource_version,
        )?;
        validate_positive(
            "zero_intent_expected_lifecycle_fence",
            self.expected_lifecycle_fence,
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ClearingZeroIntentReconciliationBodyV1 {
    pub schema: String,
    pub round_id: String,
    pub round_core_digest: String,
    pub output_manifest_digest: String,
    pub finalization_digest: String,
    pub empty_intent_root: String,
    pub settlement_intent_count: u64,
    pub reservation_root: String,
    pub reservation_count: u64,
    pub atom_bindings: Vec<ClearingZeroIntentAtomBindingV1>,
    pub outcome: ClearingZeroIntentOutcomeV1,
    pub source_lifecycle_head_digest: String,
    pub source_lifecycle_version: u64,
    pub source_lifecycle_fence: u64,
    pub next_lifecycle_version: u64,
    pub next_lifecycle_fence: u64,
    pub authority_digest: String,
    pub disposition_authority_id: String,
    pub disposition_authority_key_epoch: u64,
    pub reconciled_at_unix_ms: u64,
}

impl ClearingZeroIntentReconciliationBodyV1 {
    pub fn validate(&self) -> Result<(), ClearingError> {
        if self.schema != CLEARING_ZERO_INTENT_RECONCILIATION_SCHEMA {
            return Err(ClearingError::InvalidField(
                "zero_intent_reconciliation_schema",
            ));
        }
        validate_text("zero_intent_round_id", &self.round_id)?;
        for (field, value) in [
            ("zero_intent_round_core_digest", &self.round_core_digest),
            (
                "zero_intent_output_manifest_digest",
                &self.output_manifest_digest,
            ),
            ("zero_intent_finalization_digest", &self.finalization_digest),
            ("zero_intent_empty_intent_root", &self.empty_intent_root),
            ("zero_intent_reservation_root", &self.reservation_root),
            (
                "zero_intent_source_lifecycle_head_digest",
                &self.source_lifecycle_head_digest,
            ),
            ("zero_intent_authority_digest", &self.authority_digest),
        ] {
            validate_digest(field, value)?;
        }
        if self.settlement_intent_count != 0 {
            return Err(ClearingError::InvalidField(
                "zero_intent_settlement_intent_count",
            ));
        }
        validate_positive("zero_intent_reservation_count", self.reservation_count)?;
        if usize::try_from(self.reservation_count).map_err(|_| ClearingError::ArithmeticOverflow)?
            > MAX_CLEARING_INPUTS
        {
            return Err(ClearingError::InvalidField("zero_intent_reservation_count"));
        }
        let source_sequences = self
            .atom_bindings
            .iter()
            .map(|binding| binding.source_sequence)
            .collect::<BTreeSet<_>>();
        if u64::try_from(self.atom_bindings.len()).map_err(|_| ClearingError::ArithmeticOverflow)?
            != self.reservation_count
            || source_sequences.len() != self.atom_bindings.len()
            || !self
                .atom_bindings
                .windows(2)
                .all(|pair| pair[0].obligation_id < pair[1].obligation_id)
        {
            return Err(ClearingError::IncompleteLifecycleProjection);
        }
        for binding in &self.atom_bindings {
            binding.validate()?;
        }
        validate_positive(
            "zero_intent_source_lifecycle_version",
            self.source_lifecycle_version,
        )?;
        validate_positive(
            "zero_intent_source_lifecycle_fence",
            self.source_lifecycle_fence,
        )?;
        validate_positive(
            "zero_intent_next_lifecycle_version",
            self.next_lifecycle_version,
        )?;
        validate_positive(
            "zero_intent_next_lifecycle_fence",
            self.next_lifecycle_fence,
        )?;
        validate_text(
            "zero_intent_disposition_authority_id",
            &self.disposition_authority_id,
        )?;
        validate_positive(
            "zero_intent_disposition_authority_key_epoch",
            self.disposition_authority_key_epoch,
        )?;
        validate_positive(
            "zero_intent_reconciled_at_unix_ms",
            self.reconciled_at_unix_ms,
        )?;
        let next = increment(self.source_lifecycle_version)?;
        if self.source_lifecycle_version != self.source_lifecycle_fence
            || self.next_lifecycle_version != next
            || self.next_lifecycle_fence != next
        {
            return Err(ClearingError::InvalidField("zero_intent_fence"));
        }
        Ok(())
    }
}

pub type SignedClearingZeroIntentReconciliationV1 =
    SignedClearingEnvelopeV1<ClearingZeroIntentReconciliationBodyV1>;

pub fn prepare_clearing_zero_intent_reconciliation(
    current_round_head: &EconomicResourceHeadV1,
    reservations: &[AnchoredClearingObligationV1],
    request: &ClearingRoundRequestV1,
    signed_output: &SignedClearingRoundOutputV1,
    trust: &ClearingAuthorityTrustV1,
    authority_digest: String,
    reconciled_at_unix_ms: u64,
) -> Result<ClearingZeroIntentReconciliationBodyV1, ClearingError> {
    trust.validate()?;
    current_round_head
        .validate()
        .map_err(|_| ClearingError::InvalidField("current_round_head"))?;
    let record: ClearingRoundLifecycleRecordV1 = decode_inline(current_round_head)?;
    record.validate()?;
    validate_round_head(current_round_head, &record)?;
    if record.state != ClearingRoundLifecycleStateV1::Finalized
        || !record.intent_progress.is_empty()
        || record.first_dispatch_operation_id.is_some()
        || reconciled_at_unix_ms < current_round_head.trusted_clock_high_water
        || reconciled_at_unix_ms > trust.trusted_time_unix_ms
        || reservations
            .iter()
            .any(|reservation| reservation.head.trusted_clock_high_water > reconciled_at_unix_ms)
    {
        return Err(ClearingError::IllegalLifecycleTransition);
    }
    let mut output_trust = trust.clone();
    output_trust.trusted_time_unix_ms = request.generated_at_unix_ms;
    let output = verify_signed_netting_round(request, &output_trust, signed_output)?;
    let output_manifest_digest = output.output_manifest.digest()?;
    if !output.intents.is_empty()
        || output.output_manifest.settlement_intent_count != 0
        || record.round_id != output.core.round_id
        || record.round_core_digest != output.core.digest()?
        || record.output_manifest_digest.as_deref() != Some(output_manifest_digest.as_str())
        || record.reservation_root != output.core.reservation_root
        || record.reservation_count != output.core.input_count
    {
        return Err(ClearingError::AuthorityVerification);
    }
    let mut atom_bindings = reservations
        .iter()
        .map(|reservation| {
            reservation_binding(&record, reservation)?;
            Ok(ClearingZeroIntentAtomBindingV1 {
                source_sequence: reservation.input.source_sequence,
                obligation_id: reservation.input.atom.obligation_id().to_owned(),
                expected_head_digest: reservation
                    .head
                    .digest()
                    .map_err(|_| ClearingError::InvalidField("obligation_head"))?,
                expected_resource_version: reservation.head.resource_version,
                expected_lifecycle_fence: reservation.head.lifecycle_fence,
            })
        })
        .collect::<Result<Vec<_>, ClearingError>>()?;
    atom_bindings.sort_by(|left, right| left.obligation_id.cmp(&right.obligation_id));
    let body = ClearingZeroIntentReconciliationBodyV1 {
        schema: CLEARING_ZERO_INTENT_RECONCILIATION_SCHEMA.to_owned(),
        round_id: record.round_id.clone(),
        round_core_digest: record.round_core_digest.clone(),
        output_manifest_digest,
        finalization_digest: record
            .finalization_digest
            .clone()
            .ok_or(ClearingError::IllegalLifecycleTransition)?,
        empty_intent_root: output.output_manifest.settlement_intent_root.clone(),
        settlement_intent_count: 0,
        reservation_root: record.reservation_root.clone(),
        reservation_count: record.reservation_count,
        atom_bindings,
        outcome: ClearingZeroIntentOutcomeV1::NettedWithoutRail,
        source_lifecycle_head_digest: current_round_head
            .digest()
            .map_err(|_| ClearingError::InvalidField("current_round_head"))?,
        source_lifecycle_version: record.row_version,
        source_lifecycle_fence: record.fence,
        next_lifecycle_version: increment(record.row_version)?,
        next_lifecycle_fence: increment(record.fence)?,
        authority_digest,
        disposition_authority_id: trust.obligation_authority_id.clone(),
        disposition_authority_key_epoch: trust.obligation_key_epoch,
        reconciled_at_unix_ms,
    };
    body.validate()?;
    Ok(body)
}

pub fn compose_clearing_zero_intent_reconciliation_transition(
    current_round_head: &EconomicResourceHeadV1,
    reservations: &[AnchoredClearingObligationV1],
    request: &ClearingRoundRequestV1,
    signed_output: &SignedClearingRoundOutputV1,
    signed_reconciliation: &SignedClearingZeroIntentReconciliationV1,
    trust: &ClearingAuthorityTrustV1,
) -> Result<ClearingLifecycleProjectionV1, ClearingError> {
    let body = &signed_reconciliation.body;
    let expected = prepare_clearing_zero_intent_reconciliation(
        current_round_head,
        reservations,
        request,
        signed_output,
        trust,
        body.authority_digest.clone(),
        body.reconciled_at_unix_ms,
    )?;
    if expected != *body
        || signed_reconciliation.signer_key != trust.obligation_authority_key
        || !signed_reconciliation.verify_signature()?
    {
        return Err(ClearingError::AuthorityVerification);
    }
    compose_zero_intent_lifecycle_transition(
        current_round_head,
        reservations,
        ClearingRoundTransitionV1::Satisfy {
            satisfaction_digest: signed_reconciliation.digest()?,
            authority_digest: body.authority_digest.clone(),
        },
        body.reconciled_at_unix_ms,
    )
}