Skip to main content

truthlinked_runtime/
types.rs

1//! Runtime domain types shared across state, execution, and protocol services.
2//!
3//! These records represent account state, NFTs, staking deltas, oracle state, and
4//! execution deltas that must serialize consistently across validator processes.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use truthlinked_core::pq_execution::AccountId;
10use truthlinked_governance::{
11    CellVisibility, PendingNameRegistration, SchemaProposal, TokenAuthorityProposal, UrlProposal,
12};
13use truthlinked_oracle::http_oracle::{OracleCommit, OracleRequest, OracleResult, OracleReveal};
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16pub struct AccountRecord {
17    pub pubkey_bytes: Vec<u8>,
18    pub balance: u128,
19    #[serde(default)]
20    pub compute_escrow_trth: u128,
21    #[serde(default)]
22    pub nonce: u64,
23    pub nfts: Vec<[u8; 32]>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct NFTRecord {
28    pub nft_id: [u8; 32],
29    pub owner: AccountId,
30    pub name: String,
31    pub metadata_uri: String,
32    pub minted_at: u64,
33    pub collection: Option<[u8; 32]>,
34    pub royalty_bps: u16,
35    pub royalty_recipient: Option<AccountId>,
36    pub approved: Option<AccountId>,
37}
38
39#[derive(Debug, Clone)]
40pub enum StakingUpdate {
41    Stake {
42        validator: Vec<u8>,
43        amount: u64,
44    },
45    Unstake {
46        validator: Vec<u8>,
47        amount: u64,
48    },
49    Withdraw {
50        validator: Vec<u8>,
51    },
52    Slash {
53        validator: Vec<u8>,
54        reason: truthlinked_staking::SlashReason,
55        amount: u64,
56        redistribution: Vec<(Vec<u8>, u64)>,
57    },
58    Unjail {
59        validator: Vec<u8>,
60    },
61}
62
63#[derive(Debug, Clone, PartialEq)]
64pub enum DeltaOp {
65    Add(i128),
66    Max(u128),
67    Or(bool),
68    Append(Vec<u8>),
69}
70
71impl DeltaOp {
72    pub fn compose(&self, other: &DeltaOp) -> Result<DeltaOp, String> {
73        match (self, other) {
74            (DeltaOp::Add(a), DeltaOp::Add(b)) => Ok(DeltaOp::Add(a + b)),
75            (DeltaOp::Max(a), DeltaOp::Max(b)) => Ok(DeltaOp::Max(*a.max(b))),
76            (DeltaOp::Or(a), DeltaOp::Or(b)) => Ok(DeltaOp::Or(*a || *b)),
77            (DeltaOp::Append(a), DeltaOp::Append(b)) => {
78                let mut result = a.clone();
79                result.extend_from_slice(b);
80                Ok(DeltaOp::Append(result))
81            }
82            _ => Err("Cannot compose different delta types".to_string()),
83        }
84    }
85
86    pub fn apply(&self, base: &[u8]) -> Vec<u8> {
87        match self {
88            DeltaOp::Add(delta) => {
89                let current = if base.len() >= 16 {
90                    i128::from_le_bytes(base[..16].try_into().unwrap_or([0u8; 16]))
91                } else {
92                    0
93                };
94                let new_val = current.saturating_add(*delta);
95                new_val.to_le_bytes().to_vec()
96            }
97            DeltaOp::Max(value) => {
98                let current = if base.len() >= 16 {
99                    u128::from_le_bytes(base[..16].try_into().unwrap_or([0u8; 16]))
100                } else {
101                    0
102                };
103                let new_val = current.max(*value);
104                new_val.to_le_bytes().to_vec()
105            }
106            DeltaOp::Or(value) => {
107                let current = !base.is_empty() && base[0] != 0;
108                let new_val = current || *value;
109                vec![if new_val { 1 } else { 0 }]
110            }
111            DeltaOp::Append(data) => {
112                let mut result = base.to_vec();
113                result.extend_from_slice(data);
114                result
115            }
116        }
117    }
118}
119
120#[derive(Debug, Clone, Default)]
121pub struct StorageDelta {
122    pub deltas: HashMap<[u8; 32], DeltaOp>,
123}
124
125impl StorageDelta {
126    pub fn add_delta(&mut self, key: [u8; 32], delta: i128) {
127        let op = DeltaOp::Add(delta);
128        if let Some(existing) = self.deltas.get(&key) {
129            if let Ok(composed) = existing.compose(&op) {
130                self.deltas.insert(key, composed);
131            }
132        } else {
133            self.deltas.insert(key, op);
134        }
135    }
136
137    pub fn set_max(&mut self, key: [u8; 32], value: u128) {
138        let op = DeltaOp::Max(value);
139        if let Some(existing) = self.deltas.get(&key) {
140            if let Ok(composed) = existing.compose(&op) {
141                self.deltas.insert(key, composed);
142            }
143        } else {
144            self.deltas.insert(key, op);
145        }
146    }
147
148    pub fn set_flag(&mut self, key: [u8; 32], value: bool) {
149        let op = DeltaOp::Or(value);
150        if let Some(existing) = self.deltas.get(&key) {
151            if let Ok(composed) = existing.compose(&op) {
152                self.deltas.insert(key, composed);
153            }
154        } else {
155            self.deltas.insert(key, op);
156        }
157    }
158
159    pub fn append_log(&mut self, key: [u8; 32], data: Vec<u8>) {
160        let op = DeltaOp::Append(data);
161        if let Some(existing) = self.deltas.get(&key) {
162            if let Ok(composed) = existing.compose(&op) {
163                self.deltas.insert(key, composed);
164            }
165        } else {
166            self.deltas.insert(key, op);
167        }
168    }
169
170    pub fn compose(&mut self, other: &StorageDelta) {
171        for (key, delta) in &other.deltas {
172            if let Some(existing) = self.deltas.get(key) {
173                if let Ok(composed) = existing.compose(delta) {
174                    self.deltas.insert(*key, composed);
175                }
176            } else {
177                self.deltas.insert(*key, delta.clone());
178            }
179        }
180    }
181}
182
183#[derive(Debug, Clone)]
184pub enum OracleUpdate {
185    QueueRequest(OracleRequest),
186    AddCommit {
187        request_id: [u8; 32],
188        commit: OracleCommit,
189    },
190    AddReveal {
191        request_id: [u8; 32],
192        reveal: OracleReveal,
193    },
194    FinalizeResult(OracleResult),
195    AddUrlProposal(UrlProposal),
196    VoteUrlProposal {
197        url_pattern: String,
198        voter_pk: Vec<u8>,
199        stake_for_delta: u64,
200        stake_against_delta: u64,
201    },
202    SlashUrlProposer {
203        url_pattern: String,
204    },
205    AddSchemaProposal(SchemaProposal),
206    VoteSchemaProposal {
207        schema_id: [u8; 32],
208        voter_pk: Vec<u8>,
209        stake_for_delta: u64,
210        stake_against_delta: u64,
211    },
212    SetVisibility {
213        cell_id: AccountId,
214        visibility: CellVisibility,
215    },
216}
217
218#[derive(Debug, Clone)]
219pub enum CellUpdate {
220    Deploy {
221        cell_id: AccountId,
222        cell: crate::cells::CellAccount,
223    },
224    StorageChange {
225        cell_id: AccountId,
226        storage_diff: HashMap<[u8; 32], Option<[u8; 32]>>,
227    },
228    BalanceChange {
229        cell_id: AccountId,
230        new_balance: u128,
231    },
232    Remove {
233        cell_id: AccountId,
234    },
235    Upgrade {
236        cell_id: AccountId,
237        new_bytecode: Vec<u8>,
238        new_declared_reads: Vec<[u8; 32]>,
239        new_declared_writes: Vec<[u8; 32]>,
240        new_commutative_keys: Vec<[u8; 32]>,
241        new_storage_key_specs: Vec<truthlinked_core::cells::StorageKeySpec>,
242        new_oracle_schema_ids: Vec<[u8; 32]>,
243        timestamp: u64,
244    },
245    TransferOwnership {
246        cell_id: AccountId,
247        new_owner: AccountId,
248    },
249    AcceptOwnership {
250        cell_id: AccountId,
251        caller: AccountId,
252    },
253    MakeImmutable {
254        cell_id: AccountId,
255        caller: AccountId,
256    },
257}
258
259#[derive(Debug, Clone, Default)]
260pub struct StateDiff {
261    pub account_updates: HashMap<AccountId, AccountRecord>,
262    pub staking_updates: Vec<StakingUpdate>,
263    pub nft_updates: HashMap<[u8; 32], Option<NFTRecord>>,
264    pub cell_updates: Vec<CellUpdate>,
265    pub gas_fee: u128,
266    pub name_fee: u128,
267    pub cu_fee: u128,
268    pub treasury_fee: u128,
269    pub compute_fee_trth: u128,
270    pub tx_hash: [u8; 32],
271    pub tx_hashes: std::collections::HashSet<[u8; 32]>,
272    pub pending_name_proposals: Vec<(String, PendingNameRegistration, AccountId, bool)>,
273    pub name_votes: Vec<(String, Vec<u8>, u64)>,
274    pub pending_token_authority_proposals: Vec<(AccountId, TokenAuthorityProposal)>,
275    pub token_authority_votes: Vec<(AccountId, Vec<u8>, u64)>,
276    pub name_renewals: Vec<(String, u64)>,
277    pub name_transfers: Vec<(String, AccountId)>,
278    pub native_transfers: Vec<(AccountId, u128)>,
279    pub native_debits: Vec<(AccountId, u128)>,
280    pub compute_escrow_credits: Vec<(AccountId, u128)>,
281    pub compute_escrow_debits: Vec<(AccountId, u128)>,
282    pub token_credits: Vec<(AccountId, AccountId, u128)>,
283    pub token_balance_updates: Vec<((AccountId, AccountId), u128)>,
284    pub staking_rewards: Vec<(AccountId, u128)>,
285    pub cell_credits: Vec<(AccountId, u128)>,
286    pub cell_debits: Vec<(AccountId, u128)>,
287    pub storage_deltas: HashMap<AccountId, StorageDelta>,
288    pub airdrop_claims: Vec<(AccountId, u64)>,
289    pub minted_amount: u128,
290    pub frozen_account_updates: Vec<((AccountId, AccountId), bool)>,
291    pub oracle_updates: Vec<OracleUpdate>,
292    pub param_updates: Vec<([u8; 32], [u8; 32])>,
293    pub nonce_updates: Vec<(AccountId, u64)>,
294    pub gas_fee_spent: u128,
295    pub name_fee_spent: u128,
296    pub compute_fee_spent: u128,
297    pub treasury_fee_spent: u128,
298    /// Fee revenue permanently removed during a system distribution.
299    ///
300    /// This is an audit field: balances are not credited here. The amount is
301    /// represented by spent fee accumulators that are intentionally not paid to
302    /// validators or Staked TRTH holders.
303    pub fee_burned: u128,
304    pub is_system: bool,
305    pub gas_breakdown: Vec<(String, u128)>,
306}