Skip to main content

truthlinked_runtime/
cells.rs

1//! Axiom cell state, addressing, and execution-facing helpers.
2//!
3//! Cell records describe deterministic bytecode state, storage declarations, rent
4//! metadata, token configuration, and governance-managed lifecycle changes.
5
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use truthlinked_governance::params as gp;
9
10use truthlinked_core::cells::{ManifestAnalysis, StorageKeySpec};
11pub type AccountId = truthlinked_core::pq_execution::AccountId;
12
13/// Cell account - stores bytecode and state
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct CellAccount {
16    pub cell_id: AccountId,
17    pub owner: AccountId,
18    pub bytecode: Vec<u8>,
19    pub storage: HashMap<[u8; 32], [u8; 32]>,
20    pub balance: u128,
21    pub rent_deposit: u128,
22    pub is_token: bool,
23    pub token_config: Option<TokenConfig>,
24    pub created_at: u64,
25    pub upgraded_at: Option<u64>,
26    pub last_rent_paid_height: u64,
27    pub rent_grace_blocks: u64,
28    pub pending_owner: Option<AccountId>,
29    pub is_immutable: bool,
30    pub declared_reads: Vec<[u8; 32]>,
31    pub declared_writes: Vec<[u8; 32]>,
32    pub commutative_keys: Vec<[u8; 32]>,
33    pub storage_key_specs: Vec<StorageKeySpec>,
34    pub oracle_schema_ids: Vec<[u8; 32]>,
35    pub governance_proposal: Option<GovernanceProposal>,
36    pub manifest_version: u64,
37    pub manifest_hash: [u8; 32],
38}
39
40/// Governance proposal for cell upgrades
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct GovernanceProposal {
43    pub proposal_type: ProposalType,
44    pub proposer: AccountId,
45    pub created_at_height: u64,
46    pub timelock_blocks: u64,
47    pub require_vote: bool,
48    pub votes_for: u64,
49    pub votes_against: u64,
50    pub voters: HashSet<AccountId>,
51    pub executed: bool,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub enum ProposalType {
56    OwnershipTransfer {
57        new_owner: AccountId,
58    },
59    Upgrade {
60        new_bytecode: Vec<u8>,
61        declared_reads: Vec<[u8; 32]>,
62        declared_writes: Vec<[u8; 32]>,
63        commutative_keys: Vec<[u8; 32]>,
64        storage_key_specs: Vec<StorageKeySpec>,
65        oracle_schema_ids: Vec<[u8; 32]>,
66    },
67    MakeImmutable,
68}
69
70impl crate::ConflictAwareCell for CellAccount {
71    fn rw_set(
72        &self,
73        intent: &truthlinked_core::pq_execution::TransactionIntent,
74    ) -> (Vec<AccountId>, Vec<AccountId>) {
75        use truthlinked_core::pq_execution::TransactionIntent;
76
77        if let TransactionIntent::CallCell { calldata, .. } = intent {
78            if !self.storage_key_specs.is_empty() {
79                let mut writes = Vec::new();
80                for spec in &self.storage_key_specs {
81                    let end = match spec.offset.checked_add(spec.len) {
82                        Some(e) => e,
83                        None => continue,
84                    };
85                    if calldata.len() >= end {
86                        let slot_bytes = &calldata[spec.offset..end];
87                        let key = blake3::hash(&[self.cell_id.as_ref(), slot_bytes].concat());
88                        let mut conflict_key = [0u8; 32];
89                        conflict_key.copy_from_slice(key.as_bytes());
90                        writes.push(conflict_key);
91                    }
92                }
93                if !writes.is_empty() {
94                    return (vec![], writes);
95                }
96            }
97        }
98
99        if !self.declared_reads.is_empty() || !self.declared_writes.is_empty() {
100            return (self.declared_reads.clone(), self.declared_writes.clone());
101        }
102
103        if !self.bytecode.is_empty() {
104            if let Ok(analysis) =
105                truthlinked_core::cells::CellAccount::analyze_bytecode(&self.bytecode)
106            {
107                let reads = if analysis.static_read_slots.is_empty() {
108                    vec![self.cell_id]
109                } else {
110                    analysis.static_read_slots
111                };
112                let writes = if analysis.static_write_slots.is_empty() {
113                    vec![self.cell_id]
114                } else {
115                    analysis.static_write_slots
116                };
117                return (reads, writes);
118            }
119        }
120
121        (vec![self.cell_id], vec![self.cell_id])
122    }
123}
124
125impl CellAccount {
126    pub fn compute_manifest_hash(
127        bytecode: &[u8],
128        declared_reads: &[[u8; 32]],
129        declared_writes: &[[u8; 32]],
130        commutative_keys: &[[u8; 32]],
131        oracle_schema_ids: &[[u8; 32]],
132    ) -> [u8; 32] {
133        truthlinked_core::cells::CellAccount::compute_manifest_hash(
134            bytecode,
135            declared_reads,
136            declared_writes,
137            commutative_keys,
138            oracle_schema_ids,
139        )
140    }
141
142    pub fn analyze_bytecode(bytecode: &[u8]) -> Result<ManifestAnalysis, String> {
143        truthlinked_core::cells::CellAccount::analyze_bytecode(bytecode)
144    }
145
146    pub fn require_inferable(
147        bytecode: &[u8],
148        storage_key_specs: &[StorageKeySpec],
149    ) -> Result<(), String> {
150        let analysis = Self::analyze_bytecode(bytecode)?;
151        if analysis.fully_resolved {
152            return Ok(());
153        }
154        if !storage_key_specs.is_empty() {
155            return Ok(());
156        }
157        Err(
158            "Storage slots are not inferable. Provide storage_key_specs via the SDK manifest."
159                .to_string(),
160        )
161    }
162
163    pub fn verify_manifest_against_bytecode(
164        bytecode: &[u8],
165        declared_reads: &[[u8; 32]],
166        declared_writes: &[[u8; 32]],
167        storage_key_specs: &[StorageKeySpec],
168    ) -> Result<(), String> {
169        truthlinked_core::cells::CellAccount::verify_manifest_against_bytecode(
170            bytecode,
171            declared_reads,
172            declared_writes,
173            storage_key_specs,
174        )
175    }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
179pub struct TokenConfig {
180    pub name: String,
181    pub symbol: String,
182    pub decimals: u8,
183    pub total_supply: u128,
184    pub mint_authority: Option<AccountId>,
185    pub freeze_authority: Option<AccountId>,
186    pub transfer_fee_bps: u16,
187    pub transfer_fee_recipient: Option<AccountId>,
188    pub transfer_hook: Option<AccountId>,
189    pub transfer_hook_gas: u64,
190    pub max_supply: Option<u128>,
191    pub non_transferable: bool,
192    pub metadata_uri: Option<String>,
193    pub permanent_delegate: Option<AccountId>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct CellState {
198    pub cells: HashMap<AccountId, CellAccount>,
199    pub token_balances: HashMap<(AccountId, AccountId), u128>,
200    pub frozen_accounts: HashMap<(AccountId, AccountId), bool>,
201}
202
203impl CellState {
204    pub fn new() -> Self {
205        Self {
206            cells: HashMap::new(),
207            token_balances: HashMap::new(),
208            frozen_accounts: HashMap::new(),
209        }
210    }
211
212    pub fn deploy_cell(
213        &mut self,
214        cell_id: AccountId,
215        owner: AccountId,
216        bytecode: Vec<u8>,
217        initial_storage: HashMap<[u8; 32], [u8; 32]>,
218        initial_balance: u128,
219        timestamp: u64,
220        declared_reads: Vec<[u8; 32]>,
221        declared_writes: Vec<[u8; 32]>,
222        commutative_keys: Vec<[u8; 32]>,
223        storage_key_specs: Vec<StorageKeySpec>,
224        oracle_schema_ids: Vec<[u8; 32]>,
225    ) -> Result<(), String> {
226        if self.cells.contains_key(&cell_id) {
227            return Err("Cell already exists".to_string());
228        }
229
230        if bytecode.len() > gp::get_usize(gp::PARAM_MAX_CELL_BYTECODE_SIZE) {
231            return Err(format!(
232                "Bytecode too large: {} bytes (max: {})",
233                bytecode.len(),
234                gp::get_usize(gp::PARAM_MAX_CELL_BYTECODE_SIZE)
235            ));
236        }
237
238        let storage_size: u64 = initial_storage
239            .iter()
240            .map(|(k, v)| k.len() as u64 + v.len() as u64)
241            .sum();
242
243        if storage_size > gp::get_u64(gp::PARAM_MAX_CELL_STORAGE_BYTES) {
244            return Err(format!("Initial storage too large: {} bytes", storage_size));
245        }
246
247        CellAccount::verify_manifest_against_bytecode(
248            &bytecode,
249            &declared_reads,
250            &declared_writes,
251            &storage_key_specs,
252        )?;
253        CellAccount::require_inferable(&bytecode, &storage_key_specs)?;
254
255        let manifest_hash = CellAccount::compute_manifest_hash(
256            &bytecode,
257            &declared_reads,
258            &declared_writes,
259            &commutative_keys,
260            &oracle_schema_ids,
261        );
262
263        self.cells.insert(
264            cell_id,
265            CellAccount {
266                cell_id,
267                owner,
268                bytecode,
269                storage: initial_storage,
270                balance: initial_balance,
271                rent_deposit: gp::get_u128(gp::PARAM_STORAGE_RENT_LIFETIME_FEE),
272                is_token: false,
273                token_config: None,
274                created_at: timestamp,
275                upgraded_at: None,
276                last_rent_paid_height: 0,
277                rent_grace_blocks: gp::get_u64(gp::PARAM_STORAGE_RENT_GRACE_PERIOD_BLOCKS),
278                pending_owner: None,
279                is_immutable: false,
280                declared_reads,
281                declared_writes,
282                commutative_keys,
283                storage_key_specs,
284                oracle_schema_ids,
285                governance_proposal: None,
286                manifest_version: 1,
287                manifest_hash,
288            },
289        );
290
291        Ok(())
292    }
293
294    pub fn deploy_token(
295        &mut self,
296        cell_id: AccountId,
297        owner: AccountId,
298        config: TokenConfig,
299        initial_balance: u128,
300        timestamp: u64,
301    ) -> Result<(), String> {
302        if self.cells.contains_key(&cell_id) {
303            return Err("Cell already exists".to_string());
304        }
305
306        self.token_balances
307            .insert((cell_id, owner), config.total_supply);
308
309        let manifest_hash = CellAccount::compute_manifest_hash(&[], &[], &[], &[], &[]);
310
311        self.cells.insert(
312            cell_id,
313            CellAccount {
314                cell_id,
315                owner,
316                bytecode: vec![],
317                storage: HashMap::new(),
318                balance: initial_balance,
319                rent_deposit: gp::get_u128(gp::PARAM_STORAGE_RENT_LIFETIME_FEE),
320                is_token: true,
321                token_config: Some(config),
322                created_at: timestamp,
323                upgraded_at: None,
324                last_rent_paid_height: 0,
325                rent_grace_blocks: gp::get_u64(gp::PARAM_STORAGE_RENT_GRACE_PERIOD_BLOCKS),
326                pending_owner: None,
327                is_immutable: false,
328                declared_reads: Vec::new(),
329                declared_writes: Vec::new(),
330                commutative_keys: Vec::new(),
331                storage_key_specs: Vec::new(),
332                oracle_schema_ids: Vec::new(),
333                governance_proposal: None,
334                manifest_version: 1,
335                manifest_hash,
336            },
337        );
338
339        Ok(())
340    }
341
342    pub fn accept_ownership(
343        &mut self,
344        cell_id: AccountId,
345        caller: AccountId,
346    ) -> Result<(), String> {
347        let cell = self.cells.get_mut(&cell_id).ok_or("Cell not found")?;
348        if cell.pending_owner != Some(caller) {
349            return Err("Not pending owner".to_string());
350        }
351        cell.owner = caller;
352        cell.pending_owner = None;
353        Ok(())
354    }
355
356    pub fn make_immutable(&mut self, cell_id: AccountId, caller: AccountId) -> Result<(), String> {
357        let cell = self.cells.get_mut(&cell_id).ok_or("Cell not found")?;
358        if cell.owner != caller {
359            return Err("Not cell owner".to_string());
360        }
361        cell.is_immutable = true;
362        cell.pending_owner = None;
363        Ok(())
364    }
365
366    pub fn upgrade_cell(
367        &mut self,
368        cell_id: AccountId,
369        caller: AccountId,
370        new_bytecode: Vec<u8>,
371        timestamp: u64,
372        new_declared_reads: Vec<[u8; 32]>,
373        new_declared_writes: Vec<[u8; 32]>,
374        new_commutative_keys: Vec<[u8; 32]>,
375        new_storage_key_specs: Vec<StorageKeySpec>,
376        new_oracle_schema_ids: Vec<[u8; 32]>,
377    ) -> Result<(), String> {
378        let cell = self.cells.get_mut(&cell_id).ok_or("Cell not found")?;
379
380        if cell.owner != caller {
381            return Err("Not cell owner".to_string());
382        }
383        if cell.is_immutable {
384            return Err("Cell is immutable, cannot upgrade".to_string());
385        }
386        if new_bytecode.len() > gp::get_usize(gp::PARAM_MAX_CELL_BYTECODE_SIZE) {
387            return Err(format!(
388                "Bytecode too large: {} bytes (max: {})",
389                new_bytecode.len(),
390                gp::get_usize(gp::PARAM_MAX_CELL_BYTECODE_SIZE)
391            ));
392        }
393
394        CellAccount::verify_manifest_against_bytecode(
395            &new_bytecode,
396            &new_declared_reads,
397            &new_declared_writes,
398            &new_storage_key_specs,
399        )?;
400        CellAccount::require_inferable(&new_bytecode, &new_storage_key_specs)?;
401
402        let new_manifest_hash = CellAccount::compute_manifest_hash(
403            &new_bytecode,
404            &new_declared_reads,
405            &new_declared_writes,
406            &new_commutative_keys,
407            &new_oracle_schema_ids,
408        );
409
410        cell.manifest_version = cell
411            .manifest_version
412            .checked_add(1)
413            .ok_or("Manifest version overflow")?;
414        cell.bytecode = new_bytecode;
415        cell.declared_reads = new_declared_reads;
416        cell.declared_writes = new_declared_writes;
417        cell.commutative_keys = new_commutative_keys;
418        cell.storage_key_specs = new_storage_key_specs;
419        cell.oracle_schema_ids = new_oracle_schema_ids;
420        cell.manifest_hash = new_manifest_hash;
421        cell.upgraded_at = Some(timestamp);
422
423        Ok(())
424    }
425}
426
427impl Default for CellState {
428    fn default() -> Self {
429        Self::new()
430    }
431}