//! The SoulStream holdings contract. Single writer: one member's latest //! signed album list in one Space, at the address (space id, member key). //! Merge is the pointer contract's total order, (sequence, blake3) — //! `persona_state::holdings::merge` decides, so every delivery order //! converges, and a stale replay cannot bring a withdrawn list back. //! //! The body is opaque here: sealed to the room's epoch key in a private //! Space, and the contract only ever checks the envelope and its signature. //! //! Build: `cargo build --release --target wasm32-unknown-unknown` in this //! directory; copy `target/wasm32-unknown-unknown/release/ //! soulstream_holdings_contract.wasm` to `src-tauri/assets/holdings_contract.wasm` //! and re-pin the code hash in `src-tauri/src/holdings.rs`. use freenet_stdlib::prelude::*; use persona_state::holdings as t; pub struct Contract; fn parts(parameters: &Parameters<'static>) -> Result<([u8; 16], [u8; 32]), ContractError> { t::params_parts(parameters.as_ref()) .ok_or_else(|| ContractError::Other("params are not family+space+member".into())) } fn summary_of(bytes: &[u8], s: &t::HoldingsState) -> Vec { let (seq, hash) = t::rank(bytes, s); let mut out = Vec::with_capacity(40); out.extend_from_slice(&seq.to_le_bytes()); out.extend_from_slice(&hash); out } #[contract] impl ContractInterface for Contract { fn validate_state( parameters: Parameters<'static>, state: State<'static>, _related: RelatedContracts<'static>, ) -> Result { let (space_id, member) = parts(¶meters)?; if state.as_ref().is_empty() { return Ok(ValidateResult::Valid); } match t::validate(&space_id, &member, state.as_ref()) { Ok(_) => Ok(ValidateResult::Valid), Err(e) => Err(ContractError::Other(format!("invalid holdings: {e}"))), } } fn update_state( parameters: Parameters<'static>, state: State<'static>, data: Vec>, ) -> Result, ContractError> { let (space_id, member) = parts(¶meters)?; let mut candidates: Vec> = Vec::new(); if !state.as_ref().is_empty() { candidates.push(state.as_ref().to_vec()); } let mut incoming_valid = 0usize; for ud in &data { let mut push = |bytes: &[u8]| { if bytes.is_empty() { return; } if t::validate(&space_id, &member, bytes).is_ok() { incoming_valid += 1; } candidates.push(bytes.to_vec()); }; match ud { UpdateData::State(s) => push(s.as_ref()), UpdateData::Delta(d) => push(d.as_ref()), UpdateData::StateAndDelta { state, delta } => { push(state.as_ref()); push(delta.as_ref()); } _ => {} } } if incoming_valid == 0 && !data.is_empty() { return Err(ContractError::InvalidUpdate); } match t::merge(&space_id, &member, candidates.iter().map(|v| v.as_slice())) { Some(bytes) => Ok(UpdateModification::valid(State::from(bytes))), None => Ok(UpdateModification::valid(state)), } } fn summarize_state( parameters: Parameters<'static>, state: State<'static>, ) -> Result, ContractError> { let (space_id, member) = parts(¶meters)?; if state.as_ref().is_empty() { return Ok(StateSummary::from(Vec::new())); } let s = t::validate(&space_id, &member, state.as_ref()) .map_err(|e| ContractError::Other(format!("invalid holdings: {e}")))?; Ok(StateSummary::from(summary_of(state.as_ref(), &s))) } fn get_state_delta( parameters: Parameters<'static>, state: State<'static>, summary: StateSummary<'static>, ) -> Result, ContractError> { let (space_id, member) = parts(¶meters)?; if state.as_ref().is_empty() { return Ok(StateDelta::from(Vec::new())); } let s = t::validate(&space_id, &member, state.as_ref()) .map_err(|e| ContractError::Other(format!("invalid holdings: {e}")))?; let ours = t::rank(state.as_ref(), &s); let theirs = { let b = summary.as_ref(); if b.len() == 40 { let seq = u64::from_le_bytes(b[..8].try_into().unwrap()); let mut h = [0u8; 32]; h.copy_from_slice(&b[8..40]); Some((seq, h)) } else { None } }; let newer = match theirs { Some(t) => ours > t, None => true, }; Ok(StateDelta::from(if newer { state.as_ref().to_vec() } else { Vec::new() })) } }