Skip to main content

rustev_runtime/
spend.rs

1//! Synchronous, host-supplied accounting for live attempts (spec 043).
2
3use std::any::Any;
4
5use rustev_contract::{
6    definition::Operation,
7    ids::ArtifactId,
8    run::{Charge, CostBound},
9};
10
11/// Host declaration of the binding's cost units.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SpendUnit {
14    NanoUsd,
15    None,
16}
17
18/// An exact binding and its host-declared cost unit.
19#[derive(Debug, Clone)]
20pub struct SpendBinding {
21    pub backend_id: String,
22    pub artifact: ArtifactId,
23    pub cost_unit: SpendUnit,
24}
25
26#[derive(Debug, Clone)]
27pub struct SpendAttempt {
28    pub decision_id: String,
29    pub attempt_id: String,
30    pub backend_id: String,
31    pub target: u32,
32    pub artifact: ArtifactId,
33    pub operation: Operation,
34    pub bound: CostBound,
35    pub cost_unit: SpendUnit,
36}
37
38/// In-process accounting state, consumed exactly once by its gate.
39/// This is never authorization for an action executor.
40pub type SpendTicket = Box<dyn Any + Send>;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct SpendRefusal {
44    pub code: String,
45}
46
47impl SpendRefusal {
48    pub fn new(code: impl Into<String>) -> Self {
49        Self { code: code.into() }
50    }
51}
52
53/// A reservation must be durable before returning success. Settlement
54/// failures must retain liability. Implementations must not panic.
55pub trait SpendGate: Send + Sync {
56    fn reserve(&self, attempt: &SpendAttempt) -> Result<SpendTicket, SpendRefusal>;
57    fn settle(&self, ticket: SpendTicket, charge: &Charge);
58    fn release(&self, ticket: SpendTicket);
59}
60
61/// Gate and explicit binding declarations for one live decision.
62#[derive(Clone, Copy)]
63pub struct LiveSpend<'a> {
64    pub gate: &'a dyn SpendGate,
65    pub bindings: &'a [SpendBinding],
66}
67
68pub(crate) struct HeldSpend<'a> {
69    gate: &'a dyn SpendGate,
70    ticket: Option<SpendTicket>,
71}
72
73impl<'a> HeldSpend<'a> {
74    pub(crate) fn new(gate: &'a dyn SpendGate, ticket: SpendTicket) -> Self {
75        Self {
76            gate,
77            ticket: Some(ticket),
78        }
79    }
80
81    pub(crate) fn release(mut self) {
82        self.gate.release(self.ticket.take().expect("held ticket"));
83    }
84
85    pub(crate) fn settle(mut self, charge: &Charge) {
86        self.gate
87            .settle(self.ticket.take().expect("held ticket"), charge);
88    }
89}
90
91impl Drop for HeldSpend<'_> {
92    fn drop(&mut self) {
93        if let Some(ticket) = self.ticket.take() {
94            self.gate.settle(ticket, &Charge::Unknown);
95        }
96    }
97}