1use 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#[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#[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}