119 lines
4.3 KiB
Rust
119 lines
4.3 KiB
Rust
//! The SoulStream Space contract. Multi-writer: the owner offers
|
|
//! checkpoints, members offer messages; `persona_state::space::merge_space`
|
|
//! is the bouncer (owner signature, roster, dedup, per-author fairness,
|
|
//! the window). Deterministic by construction, so every delivery order
|
|
//! converges; a state that does not merge to itself is refused.
|
|
//!
|
|
//! Build: `cargo build --release --target wasm32-unknown-unknown` in this
|
|
//! directory; copy `target/wasm32-unknown-unknown/release/
|
|
//! soulstream_space_contract.wasm` to `src-tauri/assets/space_contract.wasm`
|
|
//! and re-pin the code hash in `src-tauri/src/spaces.rs`.
|
|
|
|
use freenet_stdlib::prelude::*;
|
|
use persona_state::space as t;
|
|
|
|
pub struct Contract;
|
|
|
|
fn parts(parameters: &Parameters<'static>) -> Result<([u8; 32], [u8; 16]), ContractError> {
|
|
t::params_parts(parameters.as_ref())
|
|
.ok_or_else(|| ContractError::Other("params are not family+owner+space".into()))
|
|
}
|
|
|
|
fn collect<'a>(state: &'a State<'static>, data: &'a [UpdateData<'static>]) -> (Vec<&'a [u8]>, bool) {
|
|
let mut candidates: Vec<&[u8]> = Vec::new();
|
|
if !state.as_ref().is_empty() {
|
|
candidates.push(state.as_ref());
|
|
}
|
|
let mut any_parse = false;
|
|
let mut push = |b: &'a [u8]| {
|
|
if !b.is_empty() {
|
|
any_parse |= bincode::deserialize::<t::SpaceState>(b).is_ok();
|
|
candidates.push(b);
|
|
}
|
|
};
|
|
for ud in data {
|
|
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());
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
(candidates, any_parse)
|
|
}
|
|
|
|
#[contract]
|
|
impl ContractInterface for Contract {
|
|
fn validate_state(
|
|
parameters: Parameters<'static>,
|
|
state: State<'static>,
|
|
_related: RelatedContracts<'static>,
|
|
) -> Result<ValidateResult, ContractError> {
|
|
let (owner, space_id) = parts(¶meters)?;
|
|
let bytes = state.as_ref();
|
|
if bytes.is_empty() {
|
|
return Ok(ValidateResult::Valid);
|
|
}
|
|
if bytes.len() > t::MAX_STATE_BYTES {
|
|
return Err(ContractError::Other(format!(
|
|
"space state {} bytes over cap {}",
|
|
bytes.len(),
|
|
t::MAX_STATE_BYTES
|
|
)));
|
|
}
|
|
let merged = t::merge_space(&owner, &space_id, &[bytes]);
|
|
let canon = bincode::serialize(&merged).map_err(|e| ContractError::Deser(e.to_string()))?;
|
|
if canon != bytes {
|
|
return Err(ContractError::Other(
|
|
"state is not canonical (unsigned, off-roster, or over the window)".into(),
|
|
));
|
|
}
|
|
Ok(ValidateResult::Valid)
|
|
}
|
|
|
|
fn update_state(
|
|
parameters: Parameters<'static>,
|
|
state: State<'static>,
|
|
data: Vec<UpdateData<'static>>,
|
|
) -> Result<UpdateModification<'static>, ContractError> {
|
|
let (owner, space_id) = parts(¶meters)?;
|
|
let (candidates, any_parse) = collect(&state, &data);
|
|
if !any_parse && !data.is_empty() {
|
|
return Err(ContractError::InvalidUpdate);
|
|
}
|
|
let merged = t::merge_space(&owner, &space_id, &candidates);
|
|
let bytes = bincode::serialize(&merged).map_err(|e| ContractError::Deser(e.to_string()))?;
|
|
Ok(UpdateModification::valid(State::from(bytes)))
|
|
}
|
|
|
|
fn summarize_state(
|
|
parameters: Parameters<'static>,
|
|
state: State<'static>,
|
|
) -> Result<StateSummary<'static>, ContractError> {
|
|
let _ = parts(¶meters)?;
|
|
if state.as_ref().is_empty() {
|
|
return Ok(StateSummary::from(Vec::new()));
|
|
}
|
|
Ok(StateSummary::from(blake3::hash(state.as_ref()).as_bytes().to_vec()))
|
|
}
|
|
|
|
fn get_state_delta(
|
|
parameters: Parameters<'static>,
|
|
state: State<'static>,
|
|
summary: StateSummary<'static>,
|
|
) -> Result<StateDelta<'static>, ContractError> {
|
|
let _ = parts(¶meters)?;
|
|
if state.as_ref().is_empty() {
|
|
return Ok(StateDelta::from(Vec::new()));
|
|
}
|
|
if summary.as_ref() == blake3::hash(state.as_ref()).as_bytes() {
|
|
Ok(StateDelta::from(Vec::new()))
|
|
} else {
|
|
Ok(StateDelta::from(state.as_ref().to_vec()))
|
|
}
|
|
}
|
|
}
|