From c3a592151e3ea81494b5b543e691e3595d500b7c Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Tue, 28 Oct 2025 04:43:26 +0100 Subject: [PATCH 1/3] refactor: split contract_interface.rs into logical modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the 1992-line contract_interface.rs into 12 focused modules: - error.rs: ContractError type - state.rs: State, StateDelta, StateSummary - key.rs: ContractInstanceId, ContractKey - code.rs: ContractCode - update.rs: Update-related types (UpdateModification, RelatedContracts, etc.) - contract.rs: Contract type - wrapped.rs: WrappedState, WrappedContract - trait_def.rs: ContractInterface trait - wasm_interface.rs: WASM FFI boundary (pub(crate)) - encoding.rs: Typed contract helpers (pub) - tests.rs: Unit tests - mod.rs: Re-exports maintaining public API Benefits: - Improved code navigation and maintainability - Clearer separation of concerns - Easier to understand dependencies between types - No breaking changes - all public APIs preserved via re-exports All tests pass. No functional changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- rust/src/contract_interface.rs | 1992 ----------------- rust/src/contract_interface/code.rs | 157 ++ rust/src/contract_interface/contract.rs | 104 + rust/src/contract_interface/encoding.rs | 384 ++++ rust/src/contract_interface/error.rs | 20 + rust/src/contract_interface/key.rs | 272 +++ rust/src/contract_interface/mod.rs | 33 + rust/src/contract_interface/state.rs | 222 ++ rust/src/contract_interface/tests.rs | 52 + rust/src/contract_interface/trait_def.rs | 100 + rust/src/contract_interface/update.rs | 345 +++ rust/src/contract_interface/wasm_interface.rs | 199 ++ rust/src/contract_interface/wrapped.rs | 201 ++ 13 files changed, 2089 insertions(+), 1992 deletions(-) delete mode 100644 rust/src/contract_interface.rs create mode 100644 rust/src/contract_interface/code.rs create mode 100644 rust/src/contract_interface/contract.rs create mode 100644 rust/src/contract_interface/encoding.rs create mode 100644 rust/src/contract_interface/error.rs create mode 100644 rust/src/contract_interface/key.rs create mode 100644 rust/src/contract_interface/mod.rs create mode 100644 rust/src/contract_interface/state.rs create mode 100644 rust/src/contract_interface/tests.rs create mode 100644 rust/src/contract_interface/trait_def.rs create mode 100644 rust/src/contract_interface/update.rs create mode 100644 rust/src/contract_interface/wasm_interface.rs create mode 100644 rust/src/contract_interface/wrapped.rs diff --git a/rust/src/contract_interface.rs b/rust/src/contract_interface.rs deleted file mode 100644 index c8047d6..0000000 --- a/rust/src/contract_interface.rs +++ /dev/null @@ -1,1992 +0,0 @@ -//! Interface and related utilities for interaction with the compiled WASM contracts. -//! Contracts have an isomorphic interface which partially maps to this interface, -//! allowing interaction between the runtime and the contracts themselves. -//! -//! This abstraction layer shouldn't leak beyond the contract handler. - -use std::{ - borrow::{Borrow, Cow}, - collections::HashMap, - fmt::Display, - fs::File, - hash::{Hash, Hasher}, - io::{Cursor, Read}, - ops::{Deref, DerefMut}, - path::Path, - str::FromStr, - sync::Arc, -}; - -use blake3::{traits::digest::Digest, Hasher as Blake3}; -use byteorder::{LittleEndian, ReadBytesExt}; -use serde::{Deserialize, Deserializer, Serialize}; -use serde_with::serde_as; - -use crate::client_api::TryFromFbs; -use crate::common_generated::common::{ - ContractKey as FbsContractKey, UpdateData as FbsUpdateData, UpdateDataType, -}; -use crate::generated::client_request::RelatedContracts as FbsRelatedContracts; -use crate::{client_api::WsApiError, code_hash::CodeHash, parameters::Parameters}; - -pub(crate) const CONTRACT_KEY_SIZE: usize = 32; - -/// Type of errors during interaction with a contract. -#[derive(Debug, thiserror::Error, Serialize, Deserialize)] -pub enum ContractError { - #[error("de/serialization error: {0}")] - Deser(String), - #[error("invalid contract update")] - InvalidUpdate, - #[error("invalid contract update, reason: {reason}")] - InvalidUpdateWithInfo { reason: String }, - #[error("trying to read an invalid state")] - InvalidState, - #[error("trying to read an invalid delta")] - InvalidDelta, - #[error("{0}")] - Other(String), -} - -/// An update to a contract state or any required related contracts to update that state. -// todo: this should be an enum probably -#[non_exhaustive] -#[derive(Debug, Serialize, Deserialize)] -pub struct UpdateModification<'a> { - #[serde(borrow)] - pub new_state: Option>, - /// Request an other contract so updates can be resolved. - pub related: Vec, -} - -impl<'a> UpdateModification<'a> { - /// Constructor for self when the state is valid. - pub fn valid(new_state: State<'a>) -> Self { - Self { - new_state: Some(new_state), - related: vec![], - } - } - - /// Unwraps self returning a [`State`]. - /// - /// Panics if self does not contain a state. - pub fn unwrap_valid(self) -> State<'a> { - match self.new_state { - Some(s) => s, - _ => panic!("failed unwrapping state in modification"), - } - } -} - -impl UpdateModification<'_> { - /// Constructor for self when this contract still is missing some [`RelatedContract`] - /// to proceed with any verification or updates. - pub fn requires(related: Vec) -> Result { - if related.is_empty() { - return Err(ContractError::InvalidUpdateWithInfo { - reason: "At least one related contract is required".into(), - }); - } - Ok(Self { - new_state: None, - related, - }) - } - - /// Gets the pending related contracts. - pub fn get_related(&self) -> &[RelatedContract] { - &self.related - } - - /// Copies the data if not owned and returns an owned version of self. - pub fn into_owned(self) -> UpdateModification<'static> { - let Self { new_state, related } = self; - UpdateModification { - new_state: new_state.map(State::into_owned), - related, - } - } - - pub fn requires_dependencies(&self) -> bool { - !self.related.is_empty() - } -} - -/// The contracts related to a parent or root contract. Tipically this means -/// contracts which state requires to be verified or integrated in some way with -/// the parent contract. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] -pub struct RelatedContracts<'a> { - #[serde(borrow)] - map: HashMap>>, -} - -impl RelatedContracts<'_> { - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - /// Copies the data if not owned and returns an owned version of self. - pub fn into_owned(self) -> RelatedContracts<'static> { - let mut map = HashMap::with_capacity(self.map.len()); - for (k, v) in self.map { - map.insert(k, v.map(|s| s.into_owned())); - } - RelatedContracts { map } - } - - pub fn deser_related_contracts<'de, D>(deser: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - let value = ::deserialize(deser)?; - Ok(value.into_owned()) - } -} - -impl RelatedContracts<'static> { - pub fn states(&self) -> impl Iterator>)> { - self.map.iter() - } -} - -impl<'a> RelatedContracts<'a> { - pub fn update( - &mut self, - ) -> impl Iterator>)> + '_ { - self.map.iter_mut() - } - - pub fn missing(&mut self, contracts: Vec) { - for key in contracts { - self.map.entry(key).or_default(); - } - } -} - -impl<'a> TryFromFbs<&FbsRelatedContracts<'a>> for RelatedContracts<'a> { - fn try_decode_fbs(related_contracts: &FbsRelatedContracts<'a>) -> Result { - let mut map = HashMap::with_capacity(related_contracts.contracts().len()); - for related in related_contracts.contracts().iter() { - let id = ContractInstanceId::from_bytes(related.instance_id().data().bytes()).unwrap(); - let state = State::from(related.state().bytes()); - map.insert(id, Some(state)); - } - Ok(RelatedContracts::from(map)) - } -} - -impl<'a> From>>> for RelatedContracts<'a> { - fn from(related_contracts: HashMap>>) -> Self { - Self { - map: related_contracts, - } - } -} - -/// A contract related to an other contract and the specification -/// of the kind of update notifications that should be received by this contract. -#[derive(Debug, Serialize, Deserialize)] -pub struct RelatedContract { - pub contract_instance_id: ContractInstanceId, - pub mode: RelatedMode, - // todo: add a timeout so we stop listening/subscribing eventually -} - -/// Specification of the notifications of interest from a related contract. -#[derive(Debug, Serialize, Deserialize)] -pub enum RelatedMode { - /// Retrieve the state once, don't be concerned with subsequent changes. - StateOnce, - /// Retrieve the state once, and then subscribe to updates. - StateThenSubscribe, -} - -/// The result of calling the [`ContractInterface::validate_state`] function. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum ValidateResult { - Valid, - Invalid, - /// The peer will attempt to retrieve the requested contract states - /// and will call validate_state() again when it retrieves them. - RequestRelated(Vec), -} - -/// Update notifications for a contract or a related contract. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub enum UpdateData<'a> { - State(#[serde(borrow)] State<'a>), - Delta(#[serde(borrow)] StateDelta<'a>), - StateAndDelta { - #[serde(borrow)] - state: State<'a>, - #[serde(borrow)] - delta: StateDelta<'a>, - }, - RelatedState { - related_to: ContractInstanceId, - #[serde(borrow)] - state: State<'a>, - }, - RelatedDelta { - related_to: ContractInstanceId, - #[serde(borrow)] - delta: StateDelta<'a>, - }, - RelatedStateAndDelta { - related_to: ContractInstanceId, - #[serde(borrow)] - state: State<'a>, - #[serde(borrow)] - delta: StateDelta<'a>, - }, -} - -impl UpdateData<'_> { - pub fn size(&self) -> usize { - match self { - UpdateData::State(state) => state.size(), - UpdateData::Delta(delta) => delta.size(), - UpdateData::StateAndDelta { state, delta } => state.size() + delta.size(), - UpdateData::RelatedState { state, .. } => state.size() + CONTRACT_KEY_SIZE, - UpdateData::RelatedDelta { delta, .. } => delta.size() + CONTRACT_KEY_SIZE, - UpdateData::RelatedStateAndDelta { state, delta, .. } => { - state.size() + delta.size() + CONTRACT_KEY_SIZE - } - } - } - - pub fn unwrap_delta(&self) -> &StateDelta<'_> { - match self { - UpdateData::Delta(delta) => delta, - _ => panic!(), - } - } - - /// Copies the data if not owned and returns an owned version of self. - pub fn into_owned(self) -> UpdateData<'static> { - match self { - UpdateData::State(s) => UpdateData::State(State::from(s.into_bytes())), - UpdateData::Delta(d) => UpdateData::Delta(StateDelta::from(d.into_bytes())), - UpdateData::StateAndDelta { state, delta } => UpdateData::StateAndDelta { - delta: StateDelta::from(delta.into_bytes()), - state: State::from(state.into_bytes()), - }, - UpdateData::RelatedState { related_to, state } => UpdateData::RelatedState { - related_to, - state: State::from(state.into_bytes()), - }, - UpdateData::RelatedDelta { related_to, delta } => UpdateData::RelatedDelta { - related_to, - delta: StateDelta::from(delta.into_bytes()), - }, - UpdateData::RelatedStateAndDelta { - related_to, - state, - delta, - } => UpdateData::RelatedStateAndDelta { - related_to, - state: State::from(state.into_bytes()), - delta: StateDelta::from(delta.into_bytes()), - }, - } - } - - pub(crate) fn get_self_states<'a>( - updates: &[UpdateData<'a>], - ) -> Vec<(Option>, Option>)> { - let mut own_states = Vec::with_capacity(updates.len()); - for update in updates { - match update { - UpdateData::State(state) => own_states.push((Some(state.clone()), None)), - UpdateData::Delta(delta) => own_states.push((None, Some(delta.clone()))), - UpdateData::StateAndDelta { state, delta } => { - own_states.push((Some(state.clone()), Some(delta.clone()))) - } - _ => {} - } - } - own_states - } - - pub(crate) fn deser_update_data<'de, D>(deser: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - let value = ::deserialize(deser)?; - Ok(value.into_owned()) - } -} - -impl<'a> From> for UpdateData<'a> { - fn from(delta: StateDelta<'a>) -> Self { - UpdateData::Delta(delta) - } -} - -impl<'a> TryFromFbs<&FbsUpdateData<'a>> for UpdateData<'a> { - fn try_decode_fbs(update_data: &FbsUpdateData<'a>) -> Result { - match update_data.update_data_type() { - UpdateDataType::StateUpdate => { - let update = update_data.update_data_as_state_update().unwrap(); - let state = State::from(update.state().bytes()); - Ok(UpdateData::State(state)) - } - UpdateDataType::DeltaUpdate => { - let update = update_data.update_data_as_delta_update().unwrap(); - let delta = StateDelta::from(update.delta().bytes()); - Ok(UpdateData::Delta(delta)) - } - UpdateDataType::StateAndDeltaUpdate => { - let update = update_data.update_data_as_state_and_delta_update().unwrap(); - let state = State::from(update.state().bytes()); - let delta = StateDelta::from(update.delta().bytes()); - Ok(UpdateData::StateAndDelta { state, delta }) - } - UpdateDataType::RelatedStateUpdate => { - let update = update_data.update_data_as_related_state_update().unwrap(); - let state = State::from(update.state().bytes()); - let related_to = - ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap(); - Ok(UpdateData::RelatedState { related_to, state }) - } - UpdateDataType::RelatedDeltaUpdate => { - let update = update_data.update_data_as_related_delta_update().unwrap(); - let delta = StateDelta::from(update.delta().bytes()); - let related_to = - ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap(); - Ok(UpdateData::RelatedDelta { related_to, delta }) - } - UpdateDataType::RelatedStateAndDeltaUpdate => { - let update = update_data - .update_data_as_related_state_and_delta_update() - .unwrap(); - let state = State::from(update.state().bytes()); - let delta = StateDelta::from(update.delta().bytes()); - let related_to = - ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap(); - Ok(UpdateData::RelatedStateAndDelta { - related_to, - state, - delta, - }) - } - _ => unreachable!(), - } - } -} - -/// Trait to implement for the contract building. -/// -/// Contains all necessary methods to interact with the contract. -/// -/// # Examples -/// -/// Implementing `ContractInterface` on a type: -/// -/// ``` -/// # use freenet_stdlib::prelude::*; -/// struct Contract; -/// -/// #[contract] -/// impl ContractInterface for Contract { -/// fn validate_state( -/// _parameters: Parameters<'static>, -/// _state: State<'static>, -/// _related: RelatedContracts -/// ) -> Result { -/// Ok(ValidateResult::Valid) -/// } -/// -/// fn update_state( -/// _parameters: Parameters<'static>, -/// state: State<'static>, -/// _data: Vec, -/// ) -> Result, ContractError> { -/// Ok(UpdateModification::valid(state)) -/// } -/// -/// fn summarize_state( -/// _parameters: Parameters<'static>, -/// _state: State<'static>, -/// ) -> Result, ContractError> { -/// Ok(StateSummary::from(vec![])) -/// } -/// -/// fn get_state_delta( -/// _parameters: Parameters<'static>, -/// _state: State<'static>, -/// _summary: StateSummary<'static>, -/// ) -> Result, ContractError> { -/// Ok(StateDelta::from(vec![])) -/// } -/// } -/// ``` -// ANCHOR: contractifce -/// # ContractInterface -/// -/// This trait defines the core functionality for managing and updating a contract's state. -/// Implementations must ensure that state delta updates are *commutative*. In other words, -/// when applying multiple delta updates to a state, the order in which these updates are -/// applied should not affect the final state. Once all deltas are applied, the resulting -/// state should be the same, regardless of the order in which the deltas were applied. -/// -/// Noncompliant behavior, such as failing to obey the commutativity rule, may result -/// in the contract being deprioritized or removed from the p2p network. -pub trait ContractInterface { - /// Verify that the state is valid, given the parameters. - fn validate_state( - parameters: Parameters<'static>, - state: State<'static>, - related: RelatedContracts<'static>, - ) -> Result; - - /// Update the state to account for the new data - fn update_state( - parameters: Parameters<'static>, - state: State<'static>, - data: Vec>, - ) -> Result, ContractError>; - - /// Generate a concise summary of a state that can be used to create deltas - /// relative to this state. - fn summarize_state( - parameters: Parameters<'static>, - state: State<'static>, - ) -> Result, ContractError>; - - /// Generate a state delta using a summary from the current state. - /// This along with [`Self::summarize_state`] allows flexible and efficient - /// state synchronization between peers. - fn get_state_delta( - parameters: Parameters<'static>, - state: State<'static>, - summary: StateSummary<'static>, - ) -> Result, ContractError>; -} -// ANCHOR_END: contractifce - -/// A complete contract specification requires a `parameters` section -/// and a `contract` section. -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr( - any(feature = "testing", all(test, any(unix, windows))), - derive(arbitrary::Arbitrary) -)] -pub struct Contract<'a> { - #[serde(borrow)] - pub parameters: Parameters<'a>, - #[serde(borrow)] - pub data: ContractCode<'a>, - // todo: skip serializing and instead compute it - key: ContractKey, -} - -impl<'a> Contract<'a> { - /// Returns a contract from [contract code](ContractCode) and given [parameters](Parameters). - pub fn new(contract: ContractCode<'a>, parameters: Parameters<'a>) -> Contract<'a> { - let key = ContractKey::from_params_and_code(¶meters, &contract); - Contract { - parameters, - data: contract, - key, - } - } - - /// Key portion of the specification. - pub fn key(&self) -> &ContractKey { - &self.key - } - - /// Code portion of the specification. - pub fn into_code(self) -> ContractCode<'a> { - self.data - } -} - -impl TryFrom> for Contract<'static> { - type Error = std::io::Error; - - fn try_from(data: Vec) -> Result { - let mut reader = Cursor::new(data); - - let params_len = reader.read_u64::()?; - let mut params_buf = vec![0; params_len as usize]; - reader.read_exact(&mut params_buf)?; - let parameters = Parameters::from(params_buf); - - let contract_len = reader.read_u64::()?; - let mut contract_buf = vec![0; contract_len as usize]; - reader.read_exact(&mut contract_buf)?; - let contract = ContractCode::from(contract_buf); - - let key = ContractKey::from_params_and_code(¶meters, &contract); - - Ok(Contract { - parameters, - data: contract, - key, - }) - } -} - -impl PartialEq for Contract<'_> { - fn eq(&self, other: &Self) -> bool { - self.key == other.key - } -} - -impl Eq for Contract<'_> {} - -impl std::fmt::Display for Contract<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ContractSpec( key: ")?; - internal_fmt_key(&self.key.instance.0, f)?; - let data: String = if self.data.data.len() > 8 { - self.data.data[..4] - .iter() - .map(|b| char::from(*b)) - .chain("...".chars()) - .chain(self.data.data[4..].iter().map(|b| char::from(*b))) - .collect() - } else { - self.data.data.iter().copied().map(char::from).collect() - }; - write!(f, ", data: [{data}])") - } -} - -/// Data associated with a contract that can be retrieved by Applications and Delegates. -/// -/// For efficiency and flexibility, contract state is represented as a simple [u8] byte array. -#[serde_as] -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] -pub struct State<'a>( - // TODO: conver this to Arc<[u8]> instead - #[serde_as(as = "serde_with::Bytes")] - #[serde(borrow)] - Cow<'a, [u8]>, -); - -impl State<'_> { - /// Gets the number of bytes of data stored in the `State`. - pub fn size(&self) -> usize { - self.0.len() - } - - pub fn into_owned(self) -> State<'static> { - State(self.0.into_owned().into()) - } - - /// Extracts the owned data as a `Vec`. - pub fn into_bytes(self) -> Vec { - self.0.into_owned() - } - - /// Acquires a mutable reference to the owned form of the `State` data. - pub fn to_mut(&mut self) -> &mut Vec { - self.0.to_mut() - } -} - -impl From> for State<'_> { - fn from(state: Vec) -> Self { - State(Cow::from(state)) - } -} - -impl<'a> From<&'a [u8]> for State<'a> { - fn from(state: &'a [u8]) -> Self { - State(Cow::from(state)) - } -} - -impl AsRef<[u8]> for State<'_> { - fn as_ref(&self) -> &[u8] { - match &self.0 { - Cow::Borrowed(arr) => arr, - Cow::Owned(arr) => arr.as_ref(), - } - } -} - -impl<'a> Deref for State<'a> { - type Target = Cow<'a, [u8]>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for State<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl std::io::Read for State<'_> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - self.as_ref().read(buf) - } -} - -/// Represents a modification to some state - similar to a diff in source code. -/// -/// The exact format of a delta is determined by the contract. A [contract](Contract) implementation will determine whether -/// a delta is valid - perhaps by verifying it is signed by someone authorized to modify the -/// contract state. A delta may be created in response to a [State Summary](StateSummary) as part of the State -/// Synchronization mechanism. -#[serde_as] -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] -pub struct StateDelta<'a>( - // TODO: conver this to Arc<[u8]> instead - #[serde_as(as = "serde_with::Bytes")] - #[serde(borrow)] - Cow<'a, [u8]>, -); - -impl StateDelta<'_> { - /// Gets the number of bytes of data stored in the `StateDelta`. - pub fn size(&self) -> usize { - self.0.len() - } - - /// Extracts the owned data as a `Vec`. - pub fn into_bytes(self) -> Vec { - self.0.into_owned() - } - - pub fn into_owned(self) -> StateDelta<'static> { - StateDelta(self.0.into_owned().into()) - } -} - -impl From> for StateDelta<'_> { - fn from(delta: Vec) -> Self { - StateDelta(Cow::from(delta)) - } -} - -impl<'a> From<&'a [u8]> for StateDelta<'a> { - fn from(delta: &'a [u8]) -> Self { - StateDelta(Cow::from(delta)) - } -} - -impl AsRef<[u8]> for StateDelta<'_> { - fn as_ref(&self) -> &[u8] { - match &self.0 { - Cow::Borrowed(arr) => arr, - Cow::Owned(arr) => arr.as_ref(), - } - } -} - -impl<'a> Deref for StateDelta<'a> { - type Target = Cow<'a, [u8]>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for StateDelta<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Summary of `State` changes. -/// -/// Given a contract state, this is a small piece of data that can be used to determine a delta -/// between two contracts as part of the state synchronization mechanism. The format of a state -/// summary is determined by the state's contract. -#[serde_as] -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] -pub struct StateSummary<'a>( - // TODO: conver this to Arc<[u8]> instead - #[serde_as(as = "serde_with::Bytes")] - #[serde(borrow)] - Cow<'a, [u8]>, -); - -impl StateSummary<'_> { - /// Extracts the owned data as a `Vec`. - pub fn into_bytes(self) -> Vec { - self.0.into_owned() - } - - /// Gets the number of bytes of data stored in the `StateSummary`. - pub fn size(&self) -> usize { - self.0.len() - } - - pub fn into_owned(self) -> StateSummary<'static> { - StateSummary(self.0.into_owned().into()) - } - - pub fn deser_state_summary<'de, D>(deser: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - let value = ::deserialize(deser)?; - Ok(value.into_owned()) - } -} - -impl From> for StateSummary<'_> { - fn from(state: Vec) -> Self { - StateSummary(Cow::from(state)) - } -} - -impl<'a> From<&'a [u8]> for StateSummary<'a> { - fn from(state: &'a [u8]) -> Self { - StateSummary(Cow::from(state)) - } -} - -impl AsRef<[u8]> for StateSummary<'_> { - fn as_ref(&self) -> &[u8] { - match &self.0 { - Cow::Borrowed(arr) => arr, - Cow::Owned(arr) => arr.as_ref(), - } - } -} - -impl<'a> Deref for StateSummary<'a> { - type Target = Cow<'a, [u8]>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for StateSummary<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// The executable contract. -/// -/// It is the part of the executable belonging to the full specification -/// and does not include any other metadata (like the parameters). -#[serde_as] -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr( - any(feature = "testing", all(test, any(unix, windows))), - derive(arbitrary::Arbitrary) -)] -pub struct ContractCode<'a> { - // TODO: conver this to Arc<[u8]> instead - #[serde_as(as = "serde_with::Bytes")] - #[serde(borrow)] - pub(crate) data: Cow<'a, [u8]>, - // todo: skip serializing and instead compute it - pub(crate) code_hash: CodeHash, -} - -impl ContractCode<'static> { - /// Loads the contract raw wasm module, without any version. - pub fn load_raw(path: &Path) -> Result { - let contract_data = Self::load_bytes(path)?; - Ok(ContractCode::from(contract_data)) - } - - pub(crate) fn load_bytes(path: &Path) -> Result, std::io::Error> { - let mut contract_file = File::open(path)?; - let mut contract_data = if let Ok(md) = contract_file.metadata() { - Vec::with_capacity(md.len() as usize) - } else { - Vec::new() - }; - contract_file.read_to_end(&mut contract_data)?; - Ok(contract_data) - } -} - -impl ContractCode<'_> { - /// Contract code hash. - pub fn hash(&self) -> &CodeHash { - &self.code_hash - } - - /// Returns the `Base58` string representation of the contract key. - pub fn hash_str(&self) -> String { - Self::encode_hash(&self.code_hash.0) - } - - /// Reference to contract code. - pub fn data(&self) -> &[u8] { - &self.data - } - - /// Extracts the owned contract code data as a `Vec`. - pub fn into_bytes(self) -> Vec { - self.data.to_vec() - } - - /// Returns the `Base58` string representation of a hash. - pub fn encode_hash(hash: &[u8; CONTRACT_KEY_SIZE]) -> String { - bs58::encode(hash) - .with_alphabet(bs58::Alphabet::BITCOIN) - .into_string() - } - - /// Copies the data if not owned and returns an owned version of self. - pub fn into_owned(self) -> ContractCode<'static> { - ContractCode { - data: self.data.into_owned().into(), - code_hash: self.code_hash, - } - } - - fn gen_hash(data: &[u8]) -> CodeHash { - let mut hasher = Blake3::new(); - hasher.update(data); - let key_arr = hasher.finalize(); - debug_assert_eq!(key_arr[..].len(), CONTRACT_KEY_SIZE); - let mut key = [0; CONTRACT_KEY_SIZE]; - key.copy_from_slice(&key_arr); - CodeHash(key) - } -} - -impl From> for ContractCode<'static> { - fn from(data: Vec) -> Self { - let key = ContractCode::gen_hash(&data); - ContractCode { - data: Cow::from(data), - code_hash: key, - } - } -} - -impl<'a> From<&'a [u8]> for ContractCode<'a> { - fn from(data: &'a [u8]) -> ContractCode<'a> { - let hash = ContractCode::gen_hash(data); - ContractCode { - data: Cow::from(data), - code_hash: hash, - } - } -} - -impl PartialEq for ContractCode<'_> { - fn eq(&self, other: &Self) -> bool { - self.code_hash == other.code_hash - } -} - -impl Eq for ContractCode<'_> {} - -impl std::fmt::Display for ContractCode<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Contract( key: ")?; - internal_fmt_key(&self.code_hash.0, f)?; - let data: String = if self.data.len() > 8 { - self.data[..4] - .iter() - .map(|b| char::from(*b)) - .chain("...".chars()) - .chain(self.data[4..].iter().map(|b| char::from(*b))) - .collect() - } else { - self.data.iter().copied().map(char::from).collect() - }; - write!(f, ", data: [{data}])") - } -} - -impl std::fmt::Debug for ContractCode<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ContractCode") - .field("hash", &self.code_hash); - Ok(()) - } -} - -/// The key representing the hash of the contract executable code hash and a set of `parameters`. -#[serde_as] -#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] -#[cfg_attr( - any(feature = "testing", all(test, any(unix, windows))), - derive(arbitrary::Arbitrary) -)] -#[repr(transparent)] -pub struct ContractInstanceId(#[serde_as(as = "[_; CONTRACT_KEY_SIZE]")] [u8; CONTRACT_KEY_SIZE]); - -impl ContractInstanceId { - pub fn from_params_and_code<'a>( - params: impl Borrow>, - code: impl Borrow>, - ) -> Self { - generate_id(params.borrow(), code.borrow()) - } - - pub const fn new(key: [u8; CONTRACT_KEY_SIZE]) -> Self { - Self(key) - } - - /// `Base58` string representation of the `contract id`. - pub fn encode(&self) -> String { - bs58::encode(self.0) - .with_alphabet(bs58::Alphabet::BITCOIN) - .into_string() - } - - pub fn as_bytes(&self) -> &[u8] { - self.0.as_slice() - } - - /// Build `ContractId` from the binary representation. - pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { - let mut spec = [0; CONTRACT_KEY_SIZE]; - bs58::decode(bytes) - .with_alphabet(bs58::Alphabet::BITCOIN) - .onto(&mut spec)?; - Ok(Self(spec)) - } -} - -impl Deref for ContractInstanceId { - type Target = [u8; CONTRACT_KEY_SIZE]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl FromStr for ContractInstanceId { - type Err = bs58::decode::Error; - - fn from_str(s: &str) -> Result { - ContractInstanceId::from_bytes(s) - } -} - -impl TryFrom for ContractInstanceId { - type Error = bs58::decode::Error; - - fn try_from(s: String) -> Result { - ContractInstanceId::from_bytes(s) - } -} - -impl Display for ContractInstanceId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.encode()) - } -} - -impl std::fmt::Debug for ContractInstanceId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("ContractInstanceId") - .field(&self.encode()) - .finish() - } -} - -/// A complete key specification, that represents a cryptographic hash that identifies the contract. -#[serde_as] -#[derive(Debug, Eq, Copy, Clone, Serialize, Deserialize)] -#[cfg_attr( - any(feature = "testing", all(test, any(unix, windows))), - derive(arbitrary::Arbitrary) -)] -pub struct ContractKey { - instance: ContractInstanceId, - code: Option, -} - -impl ContractKey { - pub fn from_params_and_code<'a>( - params: impl Borrow>, - wasm_code: impl Borrow>, - ) -> Self { - let code = wasm_code.borrow(); - let id = generate_id(params.borrow(), code); - let code_hash = code.hash(); - Self { - instance: id, - code: Some(*code_hash), - } - } - - /// Builds a partial [`ContractKey`](ContractKey), the contract code part is unspecified. - pub fn from_id(instance: impl Into) -> Result { - let instance = ContractInstanceId::try_from(instance.into())?; - Ok(Self { - instance, - code: None, - }) - } - - /// Gets the whole spec key hash. - pub fn as_bytes(&self) -> &[u8] { - self.instance.0.as_ref() - } - - /// Returns the hash of the contract code only, if the key is fully specified. - pub fn code_hash(&self) -> Option<&CodeHash> { - self.code.as_ref() - } - - /// Returns the encoded hash of the contract code, if the key is fully specified. - pub fn encoded_code_hash(&self) -> Option { - self.code.as_ref().map(|c| { - bs58::encode(c.0) - .with_alphabet(bs58::Alphabet::BITCOIN) - .into_string() - }) - } - - /// Returns the contract key from the encoded hash of the contract code and the given - /// parameters. - pub fn from_params( - code_hash: impl Into, - parameters: Parameters, - ) -> Result { - let mut code_key = [0; CONTRACT_KEY_SIZE]; - bs58::decode(code_hash.into()) - .with_alphabet(bs58::Alphabet::BITCOIN) - .onto(&mut code_key)?; - - let mut hasher = Blake3::new(); - hasher.update(code_key.as_slice()); - hasher.update(parameters.as_ref()); - let full_key_arr = hasher.finalize(); - - let mut spec = [0; CONTRACT_KEY_SIZE]; - spec.copy_from_slice(&full_key_arr); - Ok(Self { - instance: ContractInstanceId(spec), - code: Some(CodeHash(code_key)), - }) - } - - /// Returns the `Base58` encoded string of the [`ContractInstanceId`](ContractInstanceId). - pub fn encoded_contract_id(&self) -> String { - self.instance.encode() - } - - pub fn id(&self) -> &ContractInstanceId { - &self.instance - } -} - -impl PartialEq for ContractKey { - fn eq(&self, other: &Self) -> bool { - self.instance == other.instance - } -} - -impl std::hash::Hash for ContractKey { - fn hash(&self, state: &mut H) { - self.instance.0.hash(state); - } -} - -impl From for ContractKey { - fn from(instance: ContractInstanceId) -> Self { - Self { - instance, - code: None, - } - } -} - -impl From for ContractInstanceId { - fn from(key: ContractKey) -> Self { - key.instance - } -} - -impl Deref for ContractKey { - type Target = [u8; CONTRACT_KEY_SIZE]; - - fn deref(&self) -> &Self::Target { - &self.instance.0 - } -} - -impl std::fmt::Display for ContractKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.instance.fmt(f) - } -} - -impl<'a> TryFromFbs<&FbsContractKey<'a>> for ContractKey { - fn try_decode_fbs(key: &FbsContractKey<'a>) -> Result { - let key_bytes: [u8; CONTRACT_KEY_SIZE] = key.instance().data().bytes().try_into().unwrap(); - let instance = ContractInstanceId::new(key_bytes); - let code = key - .code() - .map(|code_hash| CodeHash::from_code(code_hash.bytes())); - Ok(ContractKey { instance, code }) - } -} - -fn generate_id<'a>( - parameters: &Parameters<'a>, - code_data: &ContractCode<'a>, -) -> ContractInstanceId { - let contract_hash = code_data.hash(); - - let mut hasher = Blake3::new(); - hasher.update(contract_hash.0.as_slice()); - hasher.update(parameters.as_ref()); - let full_key_arr = hasher.finalize(); - - debug_assert_eq!(full_key_arr[..].len(), CONTRACT_KEY_SIZE); - let mut spec = [0; CONTRACT_KEY_SIZE]; - spec.copy_from_slice(&full_key_arr); - ContractInstanceId(spec) -} - -#[inline] -fn internal_fmt_key( - key: &[u8; CONTRACT_KEY_SIZE], - f: &mut std::fmt::Formatter<'_>, -) -> std::fmt::Result { - let r = bs58::encode(key) - .with_alphabet(bs58::Alphabet::BITCOIN) - .into_string(); - write!(f, "{}", &r[..8]) -} - -// TODO: get rid of this when State is internally an Arc<[u8]> -/// The state for a contract. -#[derive(PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] -#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] -pub struct WrappedState( - #[serde( - serialize_with = "WrappedState::ser_state", - deserialize_with = "WrappedState::deser_state" - )] - Arc>, -); - -impl WrappedState { - pub fn new(bytes: Vec) -> Self { - WrappedState(Arc::new(bytes)) - } - - pub fn size(&self) -> usize { - self.0.len() - } - - fn ser_state(data: &Arc>, ser: S) -> Result - where - S: serde::Serializer, - { - serde_bytes::serialize(&**data, ser) - } - - fn deser_state<'de, D>(deser: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let data: Vec = serde_bytes::deserialize(deser)?; - Ok(Arc::new(data)) - } -} - -impl From> for WrappedState { - fn from(bytes: Vec) -> Self { - Self::new(bytes) - } -} - -impl From<&'_ [u8]> for WrappedState { - fn from(bytes: &[u8]) -> Self { - Self::new(bytes.to_owned()) - } -} - -impl AsRef<[u8]> for WrappedState { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Deref for WrappedState { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Borrow<[u8]> for WrappedState { - fn borrow(&self) -> &[u8] { - &self.0 - } -} - -impl From for State<'static> { - fn from(value: WrappedState) -> Self { - match Arc::try_unwrap(value.0) { - Ok(v) => State::from(v), - Err(v) => State::from(v.as_ref().to_vec()), - } - } -} - -impl std::fmt::Display for WrappedState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ContractState(data: [0x")?; - for b in self.0.iter().take(8) { - write!(f, "{:02x}", b)?; - } - write!(f, "...])") - } -} - -impl std::fmt::Debug for WrappedState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self, f) - } -} - -/// Just as `freenet_stdlib::Contract` but with some convenience impl. -#[non_exhaustive] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WrappedContract { - #[serde( - serialize_with = "WrappedContract::ser_contract_data", - deserialize_with = "WrappedContract::deser_contract_data" - )] - pub data: Arc>, - #[serde(deserialize_with = "Parameters::deser_params")] - pub params: Parameters<'static>, - pub key: ContractKey, -} - -impl PartialEq for WrappedContract { - fn eq(&self, other: &Self) -> bool { - self.key == other.key - } -} - -impl Eq for WrappedContract {} - -impl WrappedContract { - pub fn new(data: Arc>, params: Parameters<'static>) -> WrappedContract { - let key = ContractKey::from_params_and_code(¶ms, &*data); - WrappedContract { data, params, key } - } - - #[inline] - pub fn key(&self) -> &ContractKey { - &self.key - } - - #[inline] - pub fn code(&self) -> &Arc> { - &self.data - } - - #[inline] - pub fn params(&self) -> &Parameters<'static> { - &self.params - } - - fn ser_contract_data(data: &Arc>, ser: S) -> Result - where - S: serde::Serializer, - { - data.serialize(ser) - } - - fn deser_contract_data<'de, D>(deser: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let data: ContractCode<'de> = Deserialize::deserialize(deser)?; - Ok(Arc::new(data.into_owned())) - } -} - -impl TryInto> for WrappedContract { - type Error = (); - fn try_into(self) -> Result, Self::Error> { - Arc::try_unwrap(self.data) - .map(|r| r.into_bytes()) - .map_err(|_| ()) - } -} - -impl Display for WrappedContract { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.key.fmt(f) - } -} - -#[cfg(feature = "testing")] -impl<'a> arbitrary::Arbitrary<'a> for WrappedContract { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - use arbitrary::Arbitrary; - let data = ::arbitrary(u)?.into_owned(); - let param_bytes: Vec = Arbitrary::arbitrary(u)?; - let params = Parameters::from(param_bytes); - let key = ContractKey::from_params_and_code(¶ms, &data); - Ok(Self { - data: Arc::new(data), - params, - key, - }) - } -} - -#[doc(hidden)] -pub(crate) mod wasm_interface { - //! Contains all the types to interface between the host environment and - //! the wasm module execution. - use super::*; - use crate::memory::WasmLinearMem; - - #[repr(i32)] - enum ResultKind { - ValidateState = 0, - ValidateDelta = 1, - UpdateState = 2, - SummarizeState = 3, - StateDelta = 4, - } - - impl From for ResultKind { - fn from(v: i32) -> Self { - match v { - 0 => ResultKind::ValidateState, - 1 => ResultKind::ValidateDelta, - 2 => ResultKind::UpdateState, - 3 => ResultKind::SummarizeState, - 4 => ResultKind::StateDelta, - _ => panic!(), - } - } - } - - #[doc(hidden)] - #[repr(C)] - #[derive(Debug, Clone, Copy)] - pub struct ContractInterfaceResult { - ptr: i64, - kind: i32, - size: u32, - } - - impl ContractInterfaceResult { - pub unsafe fn unwrap_validate_state_res( - self, - mem: WasmLinearMem, - ) -> Result { - #![allow(clippy::let_and_return)] - let kind = ResultKind::from(self.kind); - match kind { - ResultKind::ValidateState => { - let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); - let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); - let value = bincode::deserialize(serialized) - .map_err(|e| ContractError::Other(format!("{e}")))?; - #[cfg(feature = "trace")] - self.log_input(serialized, &value, ptr); - value - } - _ => unreachable!(), - } - } - - pub unsafe fn unwrap_update_state( - self, - mem: WasmLinearMem, - ) -> Result, ContractError> { - let kind = ResultKind::from(self.kind); - match kind { - ResultKind::UpdateState => { - let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); - let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); - let value: Result, ContractError> = - bincode::deserialize(serialized) - .map_err(|e| ContractError::Other(format!("{e}")))?; - #[cfg(feature = "trace")] - self.log_input(serialized, &value, ptr); - // TODO: it may be possible to not own this value while deserializing - // under certain circumstances (e.g. when the cotnract mem is kept alive) - value.map(|r| r.into_owned()) - } - _ => unreachable!(), - } - } - - pub unsafe fn unwrap_summarize_state( - self, - mem: WasmLinearMem, - ) -> Result, ContractError> { - let kind = ResultKind::from(self.kind); - match kind { - ResultKind::SummarizeState => { - let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); - let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); - let value: Result, ContractError> = - bincode::deserialize(serialized) - .map_err(|e| ContractError::Other(format!("{e}")))?; - #[cfg(feature = "trace")] - self.log_input(serialized, &value, ptr); - // TODO: it may be possible to not own this value while deserializing - // under certain circumstances (e.g. when the contract mem is kept alive) - value.map(|s| StateSummary::from(s.into_bytes())) - } - _ => unreachable!(), - } - } - - pub unsafe fn unwrap_get_state_delta( - self, - mem: WasmLinearMem, - ) -> Result, ContractError> { - let kind = ResultKind::from(self.kind); - match kind { - ResultKind::StateDelta => { - let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); - let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); - let value: Result, ContractError> = - bincode::deserialize(serialized) - .map_err(|e| ContractError::Other(format!("{e}")))?; - #[cfg(feature = "trace")] - self.log_input(serialized, &value, ptr); - // TODO: it may be possible to not own this value while deserializing - // under certain circumstances (e.g. when the contract mem is kept alive) - value.map(|d| StateDelta::from(d.into_bytes())) - } - _ => unreachable!(), - } - } - - #[cfg(feature = "contract")] - pub fn into_raw(self) -> i64 { - #[cfg(feature = "trace")] - { - tracing::trace!("returning FFI -> {self:?}"); - } - let ptr = Box::into_raw(Box::new(self)); - #[cfg(feature = "trace")] - { - tracing::trace!("FFI result ptr: {ptr:p} ({}i64)", ptr as i64); - } - ptr as _ - } - - pub unsafe fn from_raw(ptr: i64, mem: &WasmLinearMem) -> Self { - let result = Box::leak(Box::from_raw(crate::memory::buf::compute_ptr( - ptr as *mut Self, - mem, - ))); - #[cfg(feature = "trace")] - { - tracing::trace!( - "got FFI result @ {ptr} ({:p}) -> {result:?}", - ptr as *mut Self - ); - } - *result - } - - #[cfg(feature = "trace")] - fn log_input(&self, serialized: &[u8], value: &T, ptr: *mut u8) { - tracing::trace!( - "got result through FFI; addr: {:p} ({}i64, mapped: {ptr:p}) - serialized: {serialized:?} - value: {value:?}", - self.ptr as *mut u8, - self.ptr - ); - } - } - - #[cfg(feature = "contract")] - macro_rules! conversion { - ($value:ty: $kind:expr) => { - impl From<$value> for ContractInterfaceResult { - fn from(value: $value) -> Self { - let kind = $kind as i32; - // TODO: research if there is a safe way to just transmute the pointer in memory - // independently of the architecture when stored in WASM and accessed from - // the host, maybe even if is just for some architectures - let serialized = bincode::serialize(&value).unwrap(); - let size = serialized.len() as _; - let ptr = serialized.as_ptr(); - #[cfg(feature = "trace")] { - tracing::trace!( - "sending result through FFI; addr: {ptr:p} ({}),\n serialized: {serialized:?}\n value: {value:?}", - ptr as i64 - ); - } - std::mem::forget(serialized); - Self { kind, ptr: ptr as i64, size } - } - } - }; - } - - #[cfg(feature = "contract")] - conversion!(Result: ResultKind::ValidateState); - #[cfg(feature = "contract")] - conversion!(Result: ResultKind::ValidateDelta); - #[cfg(feature = "contract")] - conversion!(Result, ContractError>: ResultKind::UpdateState); - #[cfg(feature = "contract")] - conversion!(Result, ContractError>: ResultKind::SummarizeState); - #[cfg(feature = "contract")] - conversion!(Result, ContractError>: ResultKind::StateDelta); -} - -#[cfg(all(test, any(unix, windows)))] -mod test { - use super::*; - use once_cell::sync::Lazy; - use rand::{rng, rngs::SmallRng, Rng, SeedableRng}; - - static RND_BYTES: Lazy<[u8; 1024]> = Lazy::new(|| { - let mut bytes = [0; 1024]; - let mut rng = SmallRng::from_rng(&mut rng()); - rng.fill(&mut bytes); - bytes - }); - - #[test] - fn key_encoding() -> Result<(), Box> { - let code = ContractCode::from(vec![1, 2, 3]); - let expected = ContractKey::from_params_and_code(Parameters::from(vec![]), &code); - // let encoded_key = expected.encode(); - // println!("encoded key: {encoded_key}"); - // let encoded_code = expected.contract_part_as_str(); - // println!("encoded key: {encoded_code}"); - - let decoded = ContractKey::from_params(code.hash_str(), [].as_ref().into())?; - assert_eq!(expected, decoded); - assert_eq!(expected.code_hash(), decoded.code_hash()); - Ok(()) - } - - #[test] - fn key_ser() -> Result<(), Box> { - let mut gen = arbitrary::Unstructured::new(&*RND_BYTES); - let expected: ContractKey = gen.arbitrary()?; - let encoded = bs58::encode(expected.as_bytes()).into_string(); - // println!("encoded key: {encoded}"); - - let serialized = bincode::serialize(&expected)?; - let deserialized: ContractKey = bincode::deserialize(&serialized)?; - let decoded = bs58::encode(deserialized.as_bytes()).into_string(); - assert_eq!(encoded, decoded); - assert_eq!(deserialized, expected); - Ok(()) - } - - #[test] - fn contract_ser() -> Result<(), Box> { - let mut gen = arbitrary::Unstructured::new(&*RND_BYTES); - let expected: Contract = gen.arbitrary()?; - - let serialized = bincode::serialize(&expected)?; - let deserialized: Contract = bincode::deserialize(&serialized)?; - assert_eq!(deserialized, expected); - Ok(()) - } -} - -pub mod encoding { - //! Helper types for interaction between wasm and host boundaries. - use std::{collections::HashSet, marker::PhantomData}; - - use serde::de::DeserializeOwned; - - use super::*; - - pub enum MergeResult { - Success, - RequestRelated(RelatedContractsContainer), - Error(ContractError), - } - - #[derive(Default)] - pub struct RelatedContractsContainer { - contracts: HashMap>, - pending: HashSet, - not_found: HashSet, - } - - impl From> for RelatedContractsContainer { - fn from(found: RelatedContracts<'static>) -> Self { - let mut not_found = HashSet::new(); - let mut contracts = HashMap::with_capacity(found.map.len()); - for (id, state) in found.map.into_iter() { - match state { - Some(state) => { - contracts.insert(id, state); - } - None => { - not_found.insert(id); - } - } - } - RelatedContractsContainer { - contracts, - pending: HashSet::new(), - not_found, - } - } - } - - impl From for Vec { - fn from(related: RelatedContractsContainer) -> Self { - related - .pending - .into_iter() - .map(|id| RelatedContract { - contract_instance_id: id, - mode: RelatedMode::StateOnce, - }) - .collect() - } - } - - impl From>> for RelatedContractsContainer { - fn from(updates: Vec>) -> Self { - let mut this = RelatedContractsContainer::default(); - for update in updates { - match update { - UpdateData::RelatedState { related_to, state } => { - this.contracts.insert(related_to, state); - } - UpdateData::RelatedStateAndDelta { - related_to, state, .. - } => { - this.contracts.insert(related_to, state); - } - _ => {} - } - } - this - } - } - - impl RelatedContractsContainer { - pub fn get( - &self, - params: &C::Parameters, - ) -> Result, <::SelfEncoder as Encoder>::Error> - { - let id = ::instance_id(params); - if let Some(res) = self.contracts.get(&id) { - match <::SelfEncoder>::deserialize(res.as_ref()) { - Ok(state) => return Ok(Related::Found { state }), - Err(err) => return Err(err), - } - } - if self.pending.contains(&id) { - return Ok(Related::RequestPending); - } - if self.not_found.contains(&id) { - return Ok(Related::NotFound); - } - Ok(Related::NotRequested) - } - - pub fn request(&mut self, id: ContractInstanceId) { - self.pending.insert(id); - } - - pub fn merge(&mut self, other: Self) { - let Self { - contracts, - pending, - not_found, - } = other; - self.pending.extend(pending); - self.not_found.extend(not_found); - self.contracts.extend(contracts); - } - } - - pub enum Related { - /// The state was previously requested and found - Found { state: C }, - /// The state was previously requested but not found - NotFound, - /// The state was previously requested but request is still in flight - RequestPending, - /// The state was not previously requested, this enum can be included - /// in the MergeResult return value which will request it - NotRequested, - } - - /// A contract state and it's associated types which can be encoded and decoded - /// via an specific encoder. - pub trait EncodingAdapter - where - Self: Sized, - { - type Parameters; - type Delta; - type Summary; - - type SelfEncoder: Encoder; - type ParametersEncoder: Encoder; - type DeltaEncoder: Encoder; - type SummaryEncoder: Encoder; - } - - pub enum TypedUpdateData { - RelatedState { state: T }, - RelatedDelta { delta: T::Delta }, - RelatedStateAndDelta { state: T, delta: T::Delta }, - } - - impl TypedUpdateData { - pub fn from_other(value: &TypedUpdateData) -> Self - where - Parent: EncodingAdapter, - T: for<'x> From<&'x Parent>, - T::Delta: for<'x> From<&'x Parent::Delta>, - { - match value { - TypedUpdateData::RelatedState { state } => { - let state = T::from(state); - TypedUpdateData::RelatedState { state } - } - TypedUpdateData::RelatedDelta { delta } => { - let delta: T::Delta = ::Delta::from(delta); - TypedUpdateData::RelatedDelta { delta } - } - TypedUpdateData::RelatedStateAndDelta { state, delta } => { - let state = T::from(state); - let delta: T::Delta = ::Delta::from(delta); - TypedUpdateData::RelatedStateAndDelta { state, delta } - } - } - } - } - - impl TryFrom<(Option, Option)> for TypedUpdateData { - type Error = ContractError; - fn try_from((state, delta): (Option, Option)) -> Result { - match (state, delta) { - (None, None) => Err(ContractError::InvalidState), - (None, Some(delta)) => Ok(Self::RelatedDelta { delta }), - (Some(state), None) => Ok(Self::RelatedState { state }), - (Some(state), Some(delta)) => Ok(Self::RelatedStateAndDelta { state, delta }), - } - } - } - - pub trait TypedContract: EncodingAdapter { - fn instance_id(params: &Self::Parameters) -> ContractInstanceId; - - fn verify( - &self, - parameters: Self::Parameters, - related: RelatedContractsContainer, - ) -> Result; - - fn merge( - &mut self, - parameters: &Self::Parameters, - update: TypedUpdateData, - related: &RelatedContractsContainer, - ) -> MergeResult; - - fn summarize(&self, parameters: Self::Parameters) -> Result; - - fn delta( - &self, - parameters: Self::Parameters, - summary: Self::Summary, - ) -> Result; - } - - pub trait Encoder { - type Error: Into; - fn deserialize(bytes: &[u8]) -> Result; - fn serialize(value: &T) -> Result, Self::Error>; - } - - pub struct JsonEncoder(PhantomData); - - impl Encoder for JsonEncoder - where - T: DeserializeOwned + Serialize, - { - type Error = serde_json::Error; - - fn deserialize(bytes: &[u8]) -> Result { - serde_json::from_slice(bytes) - } - - fn serialize(value: &T) -> Result, Self::Error> { - serde_json::to_vec(value) - } - } - - impl From for ContractError { - fn from(value: serde_json::Error) -> Self { - ContractError::Deser(format!("{value}")) - } - } - - pub struct BincodeEncoder(PhantomData); - - impl Encoder for BincodeEncoder - where - T: DeserializeOwned + Serialize, - { - type Error = bincode::Error; - - fn deserialize(bytes: &[u8]) -> Result { - bincode::deserialize(bytes) - } - - fn serialize(value: &T) -> Result, Self::Error> { - bincode::serialize(value) - } - } - - impl From for ContractError { - fn from(value: bincode::Error) -> Self { - ContractError::Deser(format!("{value}")) - } - } - - pub fn inner_validate_state( - parameters: Parameters<'static>, - state: State<'static>, - related: RelatedContracts<'static>, - ) -> Result - where - T: EncodingAdapter + TypedContract, - ContractError: From< - <::ParametersEncoder as Encoder< - ::Parameters, - >>::Error, - >, - ContractError: From<<::SelfEncoder as Encoder>::Error>, - { - let typed_params = - <::ParametersEncoder>::deserialize(parameters.as_ref())?; - let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; - let related_container = RelatedContractsContainer::from(related); - typed_state.verify(typed_params, related_container) - } - - pub fn inner_update_state( - parameters: Parameters<'static>, - state: State<'static>, - data: Vec>, - ) -> Result, ContractError> - where - T: EncodingAdapter + TypedContract, - ContractError: From<<::SelfEncoder as Encoder>::Error>, - ContractError: From< - <::ParametersEncoder as Encoder< - ::Parameters, - >>::Error, - >, - ContractError: From< - <::DeltaEncoder as Encoder<::Delta>>::Error, - >, - { - let typed_params = - <::ParametersEncoder>::deserialize(parameters.as_ref())?; - let mut typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; - let self_updates = UpdateData::get_self_states(&data); - let related_container = RelatedContractsContainer::from(data); - for (state, delta) in self_updates { - let state = state - .map(|s| <::SelfEncoder>::deserialize(s.as_ref())) - .transpose()?; - let delta = delta - .map(|d| <::DeltaEncoder>::deserialize(d.as_ref())) - .transpose()?; - let typed_update = TypedUpdateData::try_from((state, delta))?; - match typed_state.merge(&typed_params, typed_update, &related_container) { - MergeResult::Success => {} - MergeResult::RequestRelated(req) => { - return UpdateModification::requires(req.into()); - } - MergeResult::Error(err) => return Err(err), - } - } - let encoded = <::SelfEncoder>::serialize(&typed_state)?; - Ok(UpdateModification::valid(encoded.into())) - } - - pub fn inner_summarize_state( - parameters: Parameters<'static>, - state: State<'static>, - ) -> Result, ContractError> - where - T: EncodingAdapter + TypedContract, - ContractError: From<<::SelfEncoder as Encoder>::Error>, - ContractError: From< - <::ParametersEncoder as Encoder< - ::Parameters, - >>::Error, - >, - ContractError: - From< - <::SummaryEncoder as Encoder< - ::Summary, - >>::Error, - >, - { - let typed_params = - <::ParametersEncoder>::deserialize(parameters.as_ref())?; - let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; - let summary = typed_state.summarize(typed_params)?; - let encoded = <::SummaryEncoder>::serialize(&summary)?; - Ok(encoded.into()) - } - - pub fn inner_state_delta( - parameters: Parameters<'static>, - state: State<'static>, - summary: StateSummary<'static>, - ) -> Result, ContractError> - where - T: EncodingAdapter + TypedContract, - ContractError: From<<::SelfEncoder as Encoder>::Error>, - ContractError: From< - <::ParametersEncoder as Encoder< - ::Parameters, - >>::Error, - >, - ContractError: - From< - <::SummaryEncoder as Encoder< - ::Summary, - >>::Error, - >, - ContractError: From< - <::DeltaEncoder as Encoder<::Delta>>::Error, - >, - { - let typed_params = - <::ParametersEncoder>::deserialize(parameters.as_ref())?; - let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; - let typed_summary = - <::SummaryEncoder>::deserialize(summary.as_ref())?; - let summary = typed_state.delta(typed_params, typed_summary)?; - let encoded = <::DeltaEncoder>::serialize(&summary)?; - Ok(encoded.into()) - } -} diff --git a/rust/src/contract_interface/code.rs b/rust/src/contract_interface/code.rs new file mode 100644 index 0000000..7fe175d --- /dev/null +++ b/rust/src/contract_interface/code.rs @@ -0,0 +1,157 @@ +//! Contract executable code representation. +//! +//! This module provides the `ContractCode` type which represents the executable +//! WASM bytecode for a contract, along with its hash. + +use std::borrow::Cow; +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use blake3::{traits::digest::Digest, Hasher as Blake3}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::code_hash::CodeHash; + +use super::key::internal_fmt_key; +use super::CONTRACT_KEY_SIZE; + +/// The executable contract. +/// +/// It is the part of the executable belonging to the full specification +/// and does not include any other metadata (like the parameters). +#[serde_as] +#[derive(Serialize, Deserialize, Clone)] +#[cfg_attr( + any(feature = "testing", all(test, any(unix, windows))), + derive(arbitrary::Arbitrary) +)] +pub struct ContractCode<'a> { + // TODO: conver this to Arc<[u8]> instead + #[serde_as(as = "serde_with::Bytes")] + #[serde(borrow)] + pub(crate) data: Cow<'a, [u8]>, + // todo: skip serializing and instead compute it + pub(crate) code_hash: CodeHash, +} + +impl ContractCode<'static> { + /// Loads the contract raw wasm module, without any version. + pub fn load_raw(path: &Path) -> Result { + let contract_data = Self::load_bytes(path)?; + Ok(ContractCode::from(contract_data)) + } + + pub(crate) fn load_bytes(path: &Path) -> Result, std::io::Error> { + let mut contract_file = File::open(path)?; + let mut contract_data = if let Ok(md) = contract_file.metadata() { + Vec::with_capacity(md.len() as usize) + } else { + Vec::new() + }; + contract_file.read_to_end(&mut contract_data)?; + Ok(contract_data) + } +} + +impl ContractCode<'_> { + /// Contract code hash. + pub fn hash(&self) -> &CodeHash { + &self.code_hash + } + + /// Returns the `Base58` string representation of the contract key. + pub fn hash_str(&self) -> String { + Self::encode_hash(&self.code_hash.0) + } + + /// Reference to contract code. + pub fn data(&self) -> &[u8] { + &self.data + } + + /// Extracts the owned contract code data as a `Vec`. + pub fn into_bytes(self) -> Vec { + self.data.to_vec() + } + + /// Returns the `Base58` string representation of a hash. + pub fn encode_hash(hash: &[u8; CONTRACT_KEY_SIZE]) -> String { + bs58::encode(hash) + .with_alphabet(bs58::Alphabet::BITCOIN) + .into_string() + } + + /// Copies the data if not owned and returns an owned version of self. + pub fn into_owned(self) -> ContractCode<'static> { + ContractCode { + data: self.data.into_owned().into(), + code_hash: self.code_hash, + } + } + + fn gen_hash(data: &[u8]) -> CodeHash { + let mut hasher = Blake3::new(); + hasher.update(data); + let key_arr = hasher.finalize(); + debug_assert_eq!(key_arr[..].len(), CONTRACT_KEY_SIZE); + let mut key = [0; CONTRACT_KEY_SIZE]; + key.copy_from_slice(&key_arr); + CodeHash(key) + } +} + +impl From> for ContractCode<'static> { + fn from(data: Vec) -> Self { + let key = ContractCode::gen_hash(&data); + ContractCode { + data: Cow::from(data), + code_hash: key, + } + } +} + +impl<'a> From<&'a [u8]> for ContractCode<'a> { + fn from(data: &'a [u8]) -> ContractCode<'a> { + let hash = ContractCode::gen_hash(data); + ContractCode { + data: Cow::from(data), + code_hash: hash, + } + } +} + +impl PartialEq for ContractCode<'_> { + fn eq(&self, other: &Self) -> bool { + self.code_hash == other.code_hash + } +} + +impl Eq for ContractCode<'_> {} + +impl std::fmt::Display for ContractCode<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Contract( key: ")?; + internal_fmt_key(&self.code_hash.0, f)?; + let data: String = if self.data.len() > 8 { + self.data[..4] + .iter() + .map(|b| char::from(*b)) + .chain("...".chars()) + .chain(self.data[4..].iter().map(|b| char::from(*b))) + .collect() + } else { + self.data.iter().copied().map(char::from).collect() + }; + write!(f, ", data: [{data}])") + } +} + +impl std::fmt::Debug for ContractCode<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ContractCode") + .field("hash", &self.code_hash); + Ok(()) + } +} diff --git a/rust/src/contract_interface/contract.rs b/rust/src/contract_interface/contract.rs new file mode 100644 index 0000000..be22da4 --- /dev/null +++ b/rust/src/contract_interface/contract.rs @@ -0,0 +1,104 @@ +//! Complete contract specification combining code and parameters. +//! +//! This module provides the `Contract` type which represents a complete +//! contract with both executable code and runtime parameters. + +use std::io::{Cursor, Read}; + +use byteorder::{LittleEndian, ReadBytesExt}; +use serde::{Deserialize, Serialize}; + +use crate::parameters::Parameters; + +use super::code::ContractCode; +use super::key::{internal_fmt_key, ContractKey}; + +/// A complete contract specification requires a `parameters` section +/// and a `contract` section. +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr( + any(feature = "testing", all(test, any(unix, windows))), + derive(arbitrary::Arbitrary) +)] +pub struct Contract<'a> { + #[serde(borrow)] + pub parameters: Parameters<'a>, + #[serde(borrow)] + pub data: ContractCode<'a>, + // todo: skip serializing and instead compute it + key: ContractKey, +} + +impl<'a> Contract<'a> { + /// Returns a contract from [contract code](ContractCode) and given [parameters](Parameters). + pub fn new(contract: ContractCode<'a>, parameters: Parameters<'a>) -> Contract<'a> { + let key = ContractKey::from_params_and_code(¶meters, &contract); + Contract { + parameters, + data: contract, + key, + } + } + + /// Key portion of the specification. + pub fn key(&self) -> &ContractKey { + &self.key + } + + /// Code portion of the specification. + pub fn into_code(self) -> ContractCode<'a> { + self.data + } +} + +impl TryFrom> for Contract<'static> { + type Error = std::io::Error; + + fn try_from(data: Vec) -> Result { + let mut reader = Cursor::new(data); + + let params_len = reader.read_u64::()?; + let mut params_buf = vec![0; params_len as usize]; + reader.read_exact(&mut params_buf)?; + let parameters = Parameters::from(params_buf); + + let contract_len = reader.read_u64::()?; + let mut contract_buf = vec![0; contract_len as usize]; + reader.read_exact(&mut contract_buf)?; + let contract = ContractCode::from(contract_buf); + + let key = ContractKey::from_params_and_code(¶meters, &contract); + + Ok(Contract { + parameters, + data: contract, + key, + }) + } +} + +impl PartialEq for Contract<'_> { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for Contract<'_> {} + +impl std::fmt::Display for Contract<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ContractSpec( key: ")?; + internal_fmt_key(&self.key, f)?; + let data: String = if self.data.data.len() > 8 { + self.data.data[..4] + .iter() + .map(|b| char::from(*b)) + .chain("...".chars()) + .chain(self.data.data[4..].iter().map(|b| char::from(*b))) + .collect() + } else { + self.data.data.iter().copied().map(char::from).collect() + }; + write!(f, ", data: [{data}])") + } +} diff --git a/rust/src/contract_interface/encoding.rs b/rust/src/contract_interface/encoding.rs new file mode 100644 index 0000000..b9f2440 --- /dev/null +++ b/rust/src/contract_interface/encoding.rs @@ -0,0 +1,384 @@ +//! Helper types for interaction between wasm and host boundaries. +use std::{collections::{HashMap, HashSet}, marker::PhantomData}; + +use serde::{de::DeserializeOwned, Serialize}; + +use crate::parameters::Parameters; +use super::*; + +pub enum MergeResult { + Success, + RequestRelated(RelatedContractsContainer), + Error(ContractError), +} + +#[derive(Default)] +pub struct RelatedContractsContainer { + contracts: HashMap>, + pending: HashSet, + not_found: HashSet, +} + +impl From> for RelatedContractsContainer { + fn from(found: RelatedContracts<'static>) -> Self { + let mut not_found = HashSet::new(); + let mut contracts = HashMap::new(); + for (id, state) in found.states() { + match state { + Some(state) => { + contracts.insert(*id, state.clone()); + } + None => { + not_found.insert(*id); + } + } + } + RelatedContractsContainer { + contracts, + pending: HashSet::new(), + not_found, + } + } +} + +impl From for Vec { + fn from(related: RelatedContractsContainer) -> Self { + related + .pending + .into_iter() + .map(|id| RelatedContract { + contract_instance_id: id, + mode: RelatedMode::StateOnce, + }) + .collect() + } +} + +impl From>> for RelatedContractsContainer { + fn from(updates: Vec>) -> Self { + let mut this = RelatedContractsContainer::default(); + for update in updates { + match update { + UpdateData::RelatedState { related_to, state } => { + this.contracts.insert(related_to, state); + } + UpdateData::RelatedStateAndDelta { + related_to, state, .. + } => { + this.contracts.insert(related_to, state); + } + _ => {} + } + } + this + } +} + +impl RelatedContractsContainer { + pub fn get( + &self, + params: &C::Parameters, + ) -> Result, <::SelfEncoder as Encoder>::Error> + { + let id = ::instance_id(params); + if let Some(res) = self.contracts.get(&id) { + match <::SelfEncoder>::deserialize(res.as_ref()) { + Ok(state) => return Ok(Related::Found { state }), + Err(err) => return Err(err), + } + } + if self.pending.contains(&id) { + return Ok(Related::RequestPending); + } + if self.not_found.contains(&id) { + return Ok(Related::NotFound); + } + Ok(Related::NotRequested) + } + + pub fn request(&mut self, id: ContractInstanceId) { + self.pending.insert(id); + } + + pub fn merge(&mut self, other: Self) { + let Self { + contracts, + pending, + not_found, + } = other; + self.pending.extend(pending); + self.not_found.extend(not_found); + self.contracts.extend(contracts); + } +} + +pub enum Related { + /// The state was previously requested and found + Found { state: C }, + /// The state was previously requested but not found + NotFound, + /// The state was previously requested but request is still in flight + RequestPending, + /// The state was not previously requested, this enum can be included + /// in the MergeResult return value which will request it + NotRequested, +} + +/// A contract state and it's associated types which can be encoded and decoded +/// via an specific encoder. +pub trait EncodingAdapter +where + Self: Sized, +{ + type Parameters; + type Delta; + type Summary; + + type SelfEncoder: Encoder; + type ParametersEncoder: Encoder; + type DeltaEncoder: Encoder; + type SummaryEncoder: Encoder; +} + +pub enum TypedUpdateData { + RelatedState { state: T }, + RelatedDelta { delta: T::Delta }, + RelatedStateAndDelta { state: T, delta: T::Delta }, +} + +impl TypedUpdateData { + pub fn from_other(value: &TypedUpdateData) -> Self + where + Parent: EncodingAdapter, + T: for<'x> From<&'x Parent>, + T::Delta: for<'x> From<&'x Parent::Delta>, + { + match value { + TypedUpdateData::RelatedState { state } => { + let state = T::from(state); + TypedUpdateData::RelatedState { state } + } + TypedUpdateData::RelatedDelta { delta } => { + let delta: T::Delta = ::Delta::from(delta); + TypedUpdateData::RelatedDelta { delta } + } + TypedUpdateData::RelatedStateAndDelta { state, delta } => { + let state = T::from(state); + let delta: T::Delta = ::Delta::from(delta); + TypedUpdateData::RelatedStateAndDelta { state, delta } + } + } + } +} + +impl TryFrom<(Option, Option)> for TypedUpdateData { + type Error = ContractError; + fn try_from((state, delta): (Option, Option)) -> Result { + match (state, delta) { + (None, None) => Err(ContractError::InvalidState), + (None, Some(delta)) => Ok(Self::RelatedDelta { delta }), + (Some(state), None) => Ok(Self::RelatedState { state }), + (Some(state), Some(delta)) => Ok(Self::RelatedStateAndDelta { state, delta }), + } + } +} + +pub trait TypedContract: EncodingAdapter { + fn instance_id(params: &Self::Parameters) -> ContractInstanceId; + + fn verify( + &self, + parameters: Self::Parameters, + related: RelatedContractsContainer, + ) -> Result; + + fn merge( + &mut self, + parameters: &Self::Parameters, + update: TypedUpdateData, + related: &RelatedContractsContainer, + ) -> MergeResult; + + fn summarize(&self, parameters: Self::Parameters) -> Result; + + fn delta( + &self, + parameters: Self::Parameters, + summary: Self::Summary, + ) -> Result; +} + +pub trait Encoder { + type Error: Into; + fn deserialize(bytes: &[u8]) -> Result; + fn serialize(value: &T) -> Result, Self::Error>; +} + +pub struct JsonEncoder(PhantomData); + +impl Encoder for JsonEncoder +where + T: DeserializeOwned + Serialize, +{ + type Error = serde_json::Error; + + fn deserialize(bytes: &[u8]) -> Result { + serde_json::from_slice(bytes) + } + + fn serialize(value: &T) -> Result, Self::Error> { + serde_json::to_vec(value) + } +} + +impl From for ContractError { + fn from(value: serde_json::Error) -> Self { + ContractError::Deser(format!("{value}")) + } +} + +pub struct BincodeEncoder(PhantomData); + +impl Encoder for BincodeEncoder +where + T: DeserializeOwned + Serialize, +{ + type Error = bincode::Error; + + fn deserialize(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } + + fn serialize(value: &T) -> Result, Self::Error> { + bincode::serialize(value) + } +} + +impl From for ContractError { + fn from(value: bincode::Error) -> Self { + ContractError::Deser(format!("{value}")) + } +} + +pub fn inner_validate_state( + parameters: Parameters<'static>, + state: State<'static>, + related: RelatedContracts<'static>, +) -> Result +where + T: EncodingAdapter + TypedContract, + ContractError: From< + <::ParametersEncoder as Encoder< + ::Parameters, + >>::Error, + >, + ContractError: From<<::SelfEncoder as Encoder>::Error>, +{ + let typed_params = + <::ParametersEncoder>::deserialize(parameters.as_ref())?; + let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; + let related_container = RelatedContractsContainer::from(related); + typed_state.verify(typed_params, related_container) +} + +pub fn inner_update_state( + parameters: Parameters<'static>, + state: State<'static>, + data: Vec>, +) -> Result, ContractError> +where + T: EncodingAdapter + TypedContract, + ContractError: From<<::SelfEncoder as Encoder>::Error>, + ContractError: From< + <::ParametersEncoder as Encoder< + ::Parameters, + >>::Error, + >, + ContractError: From< + <::DeltaEncoder as Encoder<::Delta>>::Error, + >, +{ + let typed_params = + <::ParametersEncoder>::deserialize(parameters.as_ref())?; + let mut typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; + let self_updates = UpdateData::get_self_states(&data); + let related_container = RelatedContractsContainer::from(data); + for (state, delta) in self_updates { + let state = state + .map(|s| <::SelfEncoder>::deserialize(s.as_ref())) + .transpose()?; + let delta = delta + .map(|d| <::DeltaEncoder>::deserialize(d.as_ref())) + .transpose()?; + let typed_update = TypedUpdateData::try_from((state, delta))?; + match typed_state.merge(&typed_params, typed_update, &related_container) { + MergeResult::Success => {} + MergeResult::RequestRelated(req) => { + return UpdateModification::requires(req.into()); + } + MergeResult::Error(err) => return Err(err), + } + } + let encoded = <::SelfEncoder>::serialize(&typed_state)?; + Ok(UpdateModification::valid(encoded.into())) +} + +pub fn inner_summarize_state( + parameters: Parameters<'static>, + state: State<'static>, +) -> Result, ContractError> +where + T: EncodingAdapter + TypedContract, + ContractError: From<<::SelfEncoder as Encoder>::Error>, + ContractError: From< + <::ParametersEncoder as Encoder< + ::Parameters, + >>::Error, + >, + ContractError: + From< + <::SummaryEncoder as Encoder< + ::Summary, + >>::Error, + >, +{ + let typed_params = + <::ParametersEncoder>::deserialize(parameters.as_ref())?; + let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; + let summary = typed_state.summarize(typed_params)?; + let encoded = <::SummaryEncoder>::serialize(&summary)?; + Ok(encoded.into()) +} + +pub fn inner_state_delta( + parameters: Parameters<'static>, + state: State<'static>, + summary: StateSummary<'static>, +) -> Result, ContractError> +where + T: EncodingAdapter + TypedContract, + ContractError: From<<::SelfEncoder as Encoder>::Error>, + ContractError: From< + <::ParametersEncoder as Encoder< + ::Parameters, + >>::Error, + >, + ContractError: + From< + <::SummaryEncoder as Encoder< + ::Summary, + >>::Error, + >, + ContractError: From< + <::DeltaEncoder as Encoder<::Delta>>::Error, + >, +{ + let typed_params = + <::ParametersEncoder>::deserialize(parameters.as_ref())?; + let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; + let typed_summary = + <::SummaryEncoder>::deserialize(summary.as_ref())?; + let summary = typed_state.delta(typed_params, typed_summary)?; + let encoded = <::DeltaEncoder>::serialize(&summary)?; + Ok(encoded.into()) +} diff --git a/rust/src/contract_interface/error.rs b/rust/src/contract_interface/error.rs new file mode 100644 index 0000000..f3d2614 --- /dev/null +++ b/rust/src/contract_interface/error.rs @@ -0,0 +1,20 @@ +//! Error types for contract interface operations. + +use serde::{Deserialize, Serialize}; + +/// Type of errors during interaction with a contract. +#[derive(Debug, thiserror::Error, Serialize, Deserialize)] +pub enum ContractError { + #[error("de/serialization error: {0}")] + Deser(String), + #[error("invalid contract update")] + InvalidUpdate, + #[error("invalid contract update, reason: {reason}")] + InvalidUpdateWithInfo { reason: String }, + #[error("trying to read an invalid state")] + InvalidState, + #[error("trying to read an invalid delta")] + InvalidDelta, + #[error("{0}")] + Other(String), +} diff --git a/rust/src/contract_interface/key.rs b/rust/src/contract_interface/key.rs new file mode 100644 index 0000000..60c065a --- /dev/null +++ b/rust/src/contract_interface/key.rs @@ -0,0 +1,272 @@ +//! Contract key types and identifiers. +//! +//! This module provides the core types for identifying contracts: +//! - `ContractInstanceId`: The hash of contract code and parameters +//! - `ContractKey`: A complete key specification with optional code hash + +use std::borrow::Borrow; +use std::fmt::Display; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; +use std::str::FromStr; + +use blake3::{traits::digest::Digest, Hasher as Blake3}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::client_api::{TryFromFbs, WsApiError}; +use crate::code_hash::CodeHash; +use crate::common_generated::common::ContractKey as FbsContractKey; +use crate::parameters::Parameters; + +use super::code::ContractCode; +use super::CONTRACT_KEY_SIZE; + +/// The key representing the hash of the contract executable code hash and a set of `parameters`. +#[serde_as] +#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] +#[cfg_attr( + any(feature = "testing", all(test, any(unix, windows))), + derive(arbitrary::Arbitrary) +)] +#[repr(transparent)] +pub struct ContractInstanceId(#[serde_as(as = "[_; CONTRACT_KEY_SIZE]")] [u8; CONTRACT_KEY_SIZE]); + +impl ContractInstanceId { + pub fn from_params_and_code<'a>( + params: impl Borrow>, + code: impl Borrow>, + ) -> Self { + generate_id(params.borrow(), code.borrow()) + } + + pub const fn new(key: [u8; CONTRACT_KEY_SIZE]) -> Self { + Self(key) + } + + /// `Base58` string representation of the `contract id`. + pub fn encode(&self) -> String { + bs58::encode(self.0) + .with_alphabet(bs58::Alphabet::BITCOIN) + .into_string() + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_slice() + } + + /// Build `ContractId` from the binary representation. + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { + let mut spec = [0; CONTRACT_KEY_SIZE]; + bs58::decode(bytes) + .with_alphabet(bs58::Alphabet::BITCOIN) + .onto(&mut spec)?; + Ok(Self(spec)) + } +} + +impl Deref for ContractInstanceId { + type Target = [u8; CONTRACT_KEY_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for ContractInstanceId { + type Err = bs58::decode::Error; + + fn from_str(s: &str) -> Result { + ContractInstanceId::from_bytes(s) + } +} + +impl TryFrom for ContractInstanceId { + type Error = bs58::decode::Error; + + fn try_from(s: String) -> Result { + ContractInstanceId::from_bytes(s) + } +} + +impl Display for ContractInstanceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.encode()) + } +} + +impl std::fmt::Debug for ContractInstanceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ContractInstanceId") + .field(&self.encode()) + .finish() + } +} + +/// A complete key specification, that represents a cryptographic hash that identifies the contract. +#[serde_as] +#[derive(Debug, Eq, Copy, Clone, Serialize, Deserialize)] +#[cfg_attr( + any(feature = "testing", all(test, any(unix, windows))), + derive(arbitrary::Arbitrary) +)] +pub struct ContractKey { + instance: ContractInstanceId, + code: Option, +} + +impl ContractKey { + pub fn from_params_and_code<'a>( + params: impl Borrow>, + wasm_code: impl Borrow>, + ) -> Self { + let code = wasm_code.borrow(); + let id = generate_id(params.borrow(), code); + let code_hash = code.hash(); + Self { + instance: id, + code: Some(*code_hash), + } + } + + /// Builds a partial [`ContractKey`](ContractKey), the contract code part is unspecified. + pub fn from_id(instance: impl Into) -> Result { + let instance = ContractInstanceId::try_from(instance.into())?; + Ok(Self { + instance, + code: None, + }) + } + + /// Gets the whole spec key hash. + pub fn as_bytes(&self) -> &[u8] { + self.instance.0.as_ref() + } + + /// Returns the hash of the contract code only, if the key is fully specified. + pub fn code_hash(&self) -> Option<&CodeHash> { + self.code.as_ref() + } + + /// Returns the encoded hash of the contract code, if the key is fully specified. + pub fn encoded_code_hash(&self) -> Option { + self.code.as_ref().map(|c| { + bs58::encode(c.0) + .with_alphabet(bs58::Alphabet::BITCOIN) + .into_string() + }) + } + + /// Returns the contract key from the encoded hash of the contract code and the given + /// parameters. + pub fn from_params( + code_hash: impl Into, + parameters: Parameters, + ) -> Result { + let mut code_key = [0; CONTRACT_KEY_SIZE]; + bs58::decode(code_hash.into()) + .with_alphabet(bs58::Alphabet::BITCOIN) + .onto(&mut code_key)?; + + let mut hasher = Blake3::new(); + hasher.update(code_key.as_slice()); + hasher.update(parameters.as_ref()); + let full_key_arr = hasher.finalize(); + + let mut spec = [0; CONTRACT_KEY_SIZE]; + spec.copy_from_slice(&full_key_arr); + Ok(Self { + instance: ContractInstanceId(spec), + code: Some(CodeHash(code_key)), + }) + } + + /// Returns the `Base58` encoded string of the [`ContractInstanceId`](ContractInstanceId). + pub fn encoded_contract_id(&self) -> String { + self.instance.encode() + } + + pub fn id(&self) -> &ContractInstanceId { + &self.instance + } +} + +impl PartialEq for ContractKey { + fn eq(&self, other: &Self) -> bool { + self.instance == other.instance + } +} + +impl std::hash::Hash for ContractKey { + fn hash(&self, state: &mut H) { + self.instance.0.hash(state); + } +} + +impl From for ContractKey { + fn from(instance: ContractInstanceId) -> Self { + Self { + instance, + code: None, + } + } +} + +impl From for ContractInstanceId { + fn from(key: ContractKey) -> Self { + key.instance + } +} + +impl Deref for ContractKey { + type Target = [u8; CONTRACT_KEY_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.instance.0 + } +} + +impl std::fmt::Display for ContractKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.instance.fmt(f) + } +} + +impl<'a> TryFromFbs<&FbsContractKey<'a>> for ContractKey { + fn try_decode_fbs(key: &FbsContractKey<'a>) -> Result { + let key_bytes: [u8; CONTRACT_KEY_SIZE] = key.instance().data().bytes().try_into().unwrap(); + let instance = ContractInstanceId::new(key_bytes); + let code = key + .code() + .map(|code_hash| CodeHash::from_code(code_hash.bytes())); + Ok(ContractKey { instance, code }) + } +} + +fn generate_id<'a>( + parameters: &Parameters<'a>, + code_data: &ContractCode<'a>, +) -> ContractInstanceId { + let contract_hash = code_data.hash(); + + let mut hasher = Blake3::new(); + hasher.update(contract_hash.0.as_slice()); + hasher.update(parameters.as_ref()); + let full_key_arr = hasher.finalize(); + + debug_assert_eq!(full_key_arr[..].len(), CONTRACT_KEY_SIZE); + let mut spec = [0; CONTRACT_KEY_SIZE]; + spec.copy_from_slice(&full_key_arr); + ContractInstanceId(spec) +} + +#[inline] +pub(super) fn internal_fmt_key( + key: &[u8; CONTRACT_KEY_SIZE], + f: &mut std::fmt::Formatter<'_>, +) -> std::fmt::Result { + let r = bs58::encode(key) + .with_alphabet(bs58::Alphabet::BITCOIN) + .into_string(); + write!(f, "{}", &r[..8]) +} diff --git a/rust/src/contract_interface/mod.rs b/rust/src/contract_interface/mod.rs new file mode 100644 index 0000000..3fee666 --- /dev/null +++ b/rust/src/contract_interface/mod.rs @@ -0,0 +1,33 @@ +//! Interface and related utilities for interaction with the compiled WASM contracts. +//! Contracts have an isomorphic interface which partially maps to this interface, +//! allowing interaction between the runtime and the contracts themselves. +//! +//! This abstraction layer shouldn't leak beyond the contract handler. + +pub(crate) const CONTRACT_KEY_SIZE: usize = 32; + +mod error; +mod state; +mod key; +mod code; +mod update; +mod contract; +mod wrapped; +mod trait_def; +pub(crate) mod wasm_interface; +pub mod encoding; + +#[cfg(all(test, any(unix, windows)))] +mod tests; + +// Re-export all public types +pub use error::ContractError; +pub use state::{State, StateDelta, StateSummary}; +pub use key::{ContractInstanceId, ContractKey}; +pub use code::ContractCode; +pub use update::{ + UpdateData, UpdateModification, RelatedContracts, RelatedContract, RelatedMode, ValidateResult, +}; +pub use contract::Contract; +pub use wrapped::{WrappedState, WrappedContract}; +pub use trait_def::ContractInterface; diff --git a/rust/src/contract_interface/state.rs b/rust/src/contract_interface/state.rs new file mode 100644 index 0000000..536337d --- /dev/null +++ b/rust/src/contract_interface/state.rs @@ -0,0 +1,222 @@ +//! Contract state types: State, StateDelta, and StateSummary. + +use std::{ + borrow::Cow, + ops::{Deref, DerefMut}, +}; + +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +#[serde_as] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] +pub struct State<'a>( + // TODO: conver this to Arc<[u8]> instead + #[serde_as(as = "serde_with::Bytes")] + #[serde(borrow)] + Cow<'a, [u8]>, +); + +impl State<'_> { + /// Gets the number of bytes of data stored in the `State`. + pub fn size(&self) -> usize { + self.0.len() + } + + pub fn into_owned(self) -> State<'static> { + State(self.0.into_owned().into()) + } + + /// Extracts the owned data as a `Vec`. + pub fn into_bytes(self) -> Vec { + self.0.into_owned() + } + + /// Acquires a mutable reference to the owned form of the `State` data. + pub fn to_mut(&mut self) -> &mut Vec { + self.0.to_mut() + } +} + +impl From> for State<'_> { + fn from(state: Vec) -> Self { + State(Cow::from(state)) + } +} + +impl<'a> From<&'a [u8]> for State<'a> { + fn from(state: &'a [u8]) -> Self { + State(Cow::from(state)) + } +} + +impl AsRef<[u8]> for State<'_> { + fn as_ref(&self) -> &[u8] { + match &self.0 { + Cow::Borrowed(arr) => arr, + Cow::Owned(arr) => arr.as_ref(), + } + } +} + +impl<'a> Deref for State<'a> { + type Target = Cow<'a, [u8]>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for State<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::io::Read for State<'_> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.as_ref().read(buf) + } +} + +/// Represents a modification to some state - similar to a diff in source code. +/// +/// The exact format of a delta is determined by the contract. A [contract](Contract) implementation will determine whether +/// a delta is valid - perhaps by verifying it is signed by someone authorized to modify the +/// contract state. A delta may be created in response to a [State Summary](StateSummary) as part of the State +/// Synchronization mechanism. +#[serde_as] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] +pub struct StateDelta<'a>( + // TODO: conver this to Arc<[u8]> instead + #[serde_as(as = "serde_with::Bytes")] + #[serde(borrow)] + Cow<'a, [u8]>, +); + +impl StateDelta<'_> { + /// Gets the number of bytes of data stored in the `StateDelta`. + pub fn size(&self) -> usize { + self.0.len() + } + + /// Extracts the owned data as a `Vec`. + pub fn into_bytes(self) -> Vec { + self.0.into_owned() + } + + pub fn into_owned(self) -> StateDelta<'static> { + StateDelta(self.0.into_owned().into()) + } +} + +impl From> for StateDelta<'_> { + fn from(delta: Vec) -> Self { + StateDelta(Cow::from(delta)) + } +} + +impl<'a> From<&'a [u8]> for StateDelta<'a> { + fn from(delta: &'a [u8]) -> Self { + StateDelta(Cow::from(delta)) + } +} + +impl AsRef<[u8]> for StateDelta<'_> { + fn as_ref(&self) -> &[u8] { + match &self.0 { + Cow::Borrowed(arr) => arr, + Cow::Owned(arr) => arr.as_ref(), + } + } +} + +impl<'a> Deref for StateDelta<'a> { + type Target = Cow<'a, [u8]>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StateDelta<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Summary of `State` changes. +/// +/// Given a contract state, this is a small piece of data that can be used to determine a delta +/// between two contracts as part of the state synchronization mechanism. The format of a state +/// summary is determined by the state's contract. +#[serde_as] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] +pub struct StateSummary<'a>( + // TODO: conver this to Arc<[u8]> instead + #[serde_as(as = "serde_with::Bytes")] + #[serde(borrow)] + Cow<'a, [u8]>, +); + +impl StateSummary<'_> { + /// Extracts the owned data as a `Vec`. + pub fn into_bytes(self) -> Vec { + self.0.into_owned() + } + + /// Gets the number of bytes of data stored in the `StateSummary`. + pub fn size(&self) -> usize { + self.0.len() + } + + pub fn into_owned(self) -> StateSummary<'static> { + StateSummary(self.0.into_owned().into()) + } + + pub fn deser_state_summary<'de, D>(deser: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deser)?; + Ok(value.into_owned()) + } +} + +impl From> for StateSummary<'_> { + fn from(state: Vec) -> Self { + StateSummary(Cow::from(state)) + } +} + +impl<'a> From<&'a [u8]> for StateSummary<'a> { + fn from(state: &'a [u8]) -> Self { + StateSummary(Cow::from(state)) + } +} + +impl AsRef<[u8]> for StateSummary<'_> { + fn as_ref(&self) -> &[u8] { + match &self.0 { + Cow::Borrowed(arr) => arr, + Cow::Owned(arr) => arr.as_ref(), + } + } +} + +impl<'a> Deref for StateSummary<'a> { + type Target = Cow<'a, [u8]>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StateSummary<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/rust/src/contract_interface/tests.rs b/rust/src/contract_interface/tests.rs new file mode 100644 index 0000000..3b17c68 --- /dev/null +++ b/rust/src/contract_interface/tests.rs @@ -0,0 +1,52 @@ +use super::*; +use crate::parameters::Parameters; +use once_cell::sync::Lazy; +use rand::{rng, rngs::SmallRng, Rng, SeedableRng}; + +static RND_BYTES: Lazy<[u8; 1024]> = Lazy::new(|| { + let mut bytes = [0; 1024]; + let mut rng = SmallRng::from_rng(&mut rng()); + rng.fill(&mut bytes); + bytes +}); + +#[test] +fn key_encoding() -> Result<(), Box> { + let code = ContractCode::from(vec![1, 2, 3]); + let expected = ContractKey::from_params_and_code(Parameters::from(vec![]), &code); + // let encoded_key = expected.encode(); + // println!("encoded key: {encoded_key}"); + // let encoded_code = expected.contract_part_as_str(); + // println!("encoded key: {encoded_code}"); + + let decoded = ContractKey::from_params(code.hash_str(), [].as_ref().into())?; + assert_eq!(expected, decoded); + assert_eq!(expected.code_hash(), decoded.code_hash()); + Ok(()) +} + +#[test] +fn key_ser() -> Result<(), Box> { + let mut gen = arbitrary::Unstructured::new(&*RND_BYTES); + let expected: ContractKey = gen.arbitrary()?; + let encoded = bs58::encode(expected.as_bytes()).into_string(); + // println!("encoded key: {encoded}"); + + let serialized = bincode::serialize(&expected)?; + let deserialized: ContractKey = bincode::deserialize(&serialized)?; + let decoded = bs58::encode(deserialized.as_bytes()).into_string(); + assert_eq!(encoded, decoded); + assert_eq!(deserialized, expected); + Ok(()) +} + +#[test] +fn contract_ser() -> Result<(), Box> { + let mut gen = arbitrary::Unstructured::new(&*RND_BYTES); + let expected: Contract = gen.arbitrary()?; + + let serialized = bincode::serialize(&expected)?; + let deserialized: Contract = bincode::deserialize(&serialized)?; + assert_eq!(deserialized, expected); + Ok(()) +} diff --git a/rust/src/contract_interface/trait_def.rs b/rust/src/contract_interface/trait_def.rs new file mode 100644 index 0000000..8970773 --- /dev/null +++ b/rust/src/contract_interface/trait_def.rs @@ -0,0 +1,100 @@ +//! Contract interface trait definition. +//! +//! This module defines the `ContractInterface` trait which all contracts must implement. + +use crate::parameters::Parameters; + +use super::{ + ContractError, RelatedContracts, State, StateDelta, StateSummary, UpdateData, + UpdateModification, ValidateResult, +}; + +/// Trait to implement for the contract building. +/// +/// Contains all necessary methods to interact with the contract. +/// +/// # Examples +/// +/// Implementing `ContractInterface` on a type: +/// +/// ``` +/// # use freenet_stdlib::prelude::*; +/// struct Contract; +/// +/// #[contract] +/// impl ContractInterface for Contract { +/// fn validate_state( +/// _parameters: Parameters<'static>, +/// _state: State<'static>, +/// _related: RelatedContracts +/// ) -> Result { +/// Ok(ValidateResult::Valid) +/// } +/// +/// fn update_state( +/// _parameters: Parameters<'static>, +/// state: State<'static>, +/// _data: Vec, +/// ) -> Result, ContractError> { +/// Ok(UpdateModification::valid(state)) +/// } +/// +/// fn summarize_state( +/// _parameters: Parameters<'static>, +/// _state: State<'static>, +/// ) -> Result, ContractError> { +/// Ok(StateSummary::from(vec![])) +/// } +/// +/// fn get_state_delta( +/// _parameters: Parameters<'static>, +/// _state: State<'static>, +/// _summary: StateSummary<'static>, +/// ) -> Result, ContractError> { +/// Ok(StateDelta::from(vec![])) +/// } +/// } +/// ``` +// ANCHOR: contractifce +/// # ContractInterface +/// +/// This trait defines the core functionality for managing and updating a contract's state. +/// Implementations must ensure that state delta updates are *commutative*. In other words, +/// when applying multiple delta updates to a state, the order in which these updates are +/// applied should not affect the final state. Once all deltas are applied, the resulting +/// state should be the same, regardless of the order in which the deltas were applied. +/// +/// Noncompliant behavior, such as failing to obey the commutativity rule, may result +/// in the contract being deprioritized or removed from the p2p network. +pub trait ContractInterface { + /// Verify that the state is valid, given the parameters. + fn validate_state( + parameters: Parameters<'static>, + state: State<'static>, + related: RelatedContracts<'static>, + ) -> Result; + + /// Update the state to account for the new data + fn update_state( + parameters: Parameters<'static>, + state: State<'static>, + data: Vec>, + ) -> Result, ContractError>; + + /// Generate a concise summary of a state that can be used to create deltas + /// relative to this state. + fn summarize_state( + parameters: Parameters<'static>, + state: State<'static>, + ) -> Result, ContractError>; + + /// Generate a state delta using a summary from the current state. + /// This along with [`Self::summarize_state`] allows flexible and efficient + /// state synchronization between peers. + fn get_state_delta( + parameters: Parameters<'static>, + state: State<'static>, + summary: StateSummary<'static>, + ) -> Result, ContractError>; +} +// ANCHOR_END: contractifce diff --git a/rust/src/contract_interface/update.rs b/rust/src/contract_interface/update.rs new file mode 100644 index 0000000..0b094c5 --- /dev/null +++ b/rust/src/contract_interface/update.rs @@ -0,0 +1,345 @@ +//! Contract update mechanisms and related contract management. +//! +//! This module provides types for updating contract state, managing related contracts, +//! and validation results. + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::client_api::{TryFromFbs, WsApiError}; +use crate::common_generated::common::{UpdateData as FbsUpdateData, UpdateDataType}; +use crate::generated::client_request::RelatedContracts as FbsRelatedContracts; + +use super::{ContractError, ContractInstanceId, State, StateDelta, CONTRACT_KEY_SIZE}; + +/// An update to a contract state or any required related contracts to update that state. +// todo: this should be an enum probably +#[non_exhaustive] +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateModification<'a> { + #[serde(borrow)] + pub new_state: Option>, + /// Request an other contract so updates can be resolved. + pub related: Vec, +} + +impl<'a> UpdateModification<'a> { + /// Constructor for self when the state is valid. + pub fn valid(new_state: State<'a>) -> Self { + Self { + new_state: Some(new_state), + related: vec![], + } + } + + /// Unwraps self returning a [`State`]. + /// + /// Panics if self does not contain a state. + pub fn unwrap_valid(self) -> State<'a> { + match self.new_state { + Some(s) => s, + _ => panic!("failed unwrapping state in modification"), + } + } +} + +impl UpdateModification<'_> { + /// Constructor for self when this contract still is missing some [`RelatedContract`] + /// to proceed with any verification or updates. + pub fn requires(related: Vec) -> Result { + if related.is_empty() { + return Err(ContractError::InvalidUpdateWithInfo { + reason: "At least one related contract is required".into(), + }); + } + Ok(Self { + new_state: None, + related, + }) + } + + /// Gets the pending related contracts. + pub fn get_related(&self) -> &[RelatedContract] { + &self.related + } + + /// Copies the data if not owned and returns an owned version of self. + pub fn into_owned(self) -> UpdateModification<'static> { + let Self { new_state, related } = self; + UpdateModification { + new_state: new_state.map(State::into_owned), + related, + } + } + + pub fn requires_dependencies(&self) -> bool { + !self.related.is_empty() + } +} + +/// The contracts related to a parent or root contract. Tipically this means +/// contracts which state requires to be verified or integrated in some way with +/// the parent contract. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] +pub struct RelatedContracts<'a> { + #[serde(borrow)] + map: HashMap>>, +} + +impl RelatedContracts<'_> { + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + /// Copies the data if not owned and returns an owned version of self. + pub fn into_owned(self) -> RelatedContracts<'static> { + let mut map = HashMap::with_capacity(self.map.len()); + for (k, v) in self.map { + map.insert(k, v.map(|s| s.into_owned())); + } + RelatedContracts { map } + } + + pub fn deser_related_contracts<'de, D>(deser: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deser)?; + Ok(value.into_owned()) + } +} + +impl RelatedContracts<'static> { + pub fn states(&self) -> impl Iterator>)> { + self.map.iter() + } +} + +impl<'a> RelatedContracts<'a> { + pub fn update( + &mut self, + ) -> impl Iterator>)> + '_ { + self.map.iter_mut() + } + + pub fn missing(&mut self, contracts: Vec) { + for key in contracts { + self.map.entry(key).or_default(); + } + } +} + +impl<'a> TryFromFbs<&FbsRelatedContracts<'a>> for RelatedContracts<'a> { + fn try_decode_fbs(related_contracts: &FbsRelatedContracts<'a>) -> Result { + let mut map = HashMap::with_capacity(related_contracts.contracts().len()); + for related in related_contracts.contracts().iter() { + let id = ContractInstanceId::from_bytes(related.instance_id().data().bytes()).unwrap(); + let state = State::from(related.state().bytes()); + map.insert(id, Some(state)); + } + Ok(RelatedContracts::from(map)) + } +} + +impl<'a> From>>> for RelatedContracts<'a> { + fn from(related_contracts: HashMap>>) -> Self { + Self { + map: related_contracts, + } + } +} + +/// A contract related to an other contract and the specification +/// of the kind of update notifications that should be received by this contract. +#[derive(Debug, Serialize, Deserialize)] +pub struct RelatedContract { + pub contract_instance_id: ContractInstanceId, + pub mode: RelatedMode, + // todo: add a timeout so we stop listening/subscribing eventually +} + +/// Specification of the notifications of interest from a related contract. +#[derive(Debug, Serialize, Deserialize)] +pub enum RelatedMode { + /// Retrieve the state once, don't be concerned with subsequent changes. + StateOnce, + /// Retrieve the state once, and then subscribe to updates. + StateThenSubscribe, +} + +/// The result of calling the [`ContractInterface::validate_state`] function. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum ValidateResult { + Valid, + Invalid, + /// The peer will attempt to retrieve the requested contract states + /// and will call validate_state() again when it retrieves them. + RequestRelated(Vec), +} + +/// Update notifications for a contract or a related contract. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub enum UpdateData<'a> { + State(#[serde(borrow)] State<'a>), + Delta(#[serde(borrow)] StateDelta<'a>), + StateAndDelta { + #[serde(borrow)] + state: State<'a>, + #[serde(borrow)] + delta: StateDelta<'a>, + }, + RelatedState { + related_to: ContractInstanceId, + #[serde(borrow)] + state: State<'a>, + }, + RelatedDelta { + related_to: ContractInstanceId, + #[serde(borrow)] + delta: StateDelta<'a>, + }, + RelatedStateAndDelta { + related_to: ContractInstanceId, + #[serde(borrow)] + state: State<'a>, + #[serde(borrow)] + delta: StateDelta<'a>, + }, +} + +impl UpdateData<'_> { + pub fn size(&self) -> usize { + match self { + UpdateData::State(state) => state.size(), + UpdateData::Delta(delta) => delta.size(), + UpdateData::StateAndDelta { state, delta } => state.size() + delta.size(), + UpdateData::RelatedState { state, .. } => state.size() + CONTRACT_KEY_SIZE, + UpdateData::RelatedDelta { delta, .. } => delta.size() + CONTRACT_KEY_SIZE, + UpdateData::RelatedStateAndDelta { state, delta, .. } => { + state.size() + delta.size() + CONTRACT_KEY_SIZE + } + } + } + + pub fn unwrap_delta(&self) -> &StateDelta<'_> { + match self { + UpdateData::Delta(delta) => delta, + _ => panic!(), + } + } + + /// Copies the data if not owned and returns an owned version of self. + pub fn into_owned(self) -> UpdateData<'static> { + match self { + UpdateData::State(s) => UpdateData::State(State::from(s.into_bytes())), + UpdateData::Delta(d) => UpdateData::Delta(StateDelta::from(d.into_bytes())), + UpdateData::StateAndDelta { state, delta } => UpdateData::StateAndDelta { + delta: StateDelta::from(delta.into_bytes()), + state: State::from(state.into_bytes()), + }, + UpdateData::RelatedState { related_to, state } => UpdateData::RelatedState { + related_to, + state: State::from(state.into_bytes()), + }, + UpdateData::RelatedDelta { related_to, delta } => UpdateData::RelatedDelta { + related_to, + delta: StateDelta::from(delta.into_bytes()), + }, + UpdateData::RelatedStateAndDelta { + related_to, + state, + delta, + } => UpdateData::RelatedStateAndDelta { + related_to, + state: State::from(state.into_bytes()), + delta: StateDelta::from(delta.into_bytes()), + }, + } + } + + pub(crate) fn get_self_states<'a>( + updates: &[UpdateData<'a>], + ) -> Vec<(Option>, Option>)> { + let mut own_states = Vec::with_capacity(updates.len()); + for update in updates { + match update { + UpdateData::State(state) => own_states.push((Some(state.clone()), None)), + UpdateData::Delta(delta) => own_states.push((None, Some(delta.clone()))), + UpdateData::StateAndDelta { state, delta } => { + own_states.push((Some(state.clone()), Some(delta.clone()))) + } + _ => {} + } + } + own_states + } + + pub(crate) fn deser_update_data<'de, D>(deser: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deser)?; + Ok(value.into_owned()) + } +} + +impl<'a> From> for UpdateData<'a> { + fn from(delta: StateDelta<'a>) -> Self { + UpdateData::Delta(delta) + } +} + +impl<'a> TryFromFbs<&FbsUpdateData<'a>> for UpdateData<'a> { + fn try_decode_fbs(update_data: &FbsUpdateData<'a>) -> Result { + match update_data.update_data_type() { + UpdateDataType::StateUpdate => { + let update = update_data.update_data_as_state_update().unwrap(); + let state = State::from(update.state().bytes()); + Ok(UpdateData::State(state)) + } + UpdateDataType::DeltaUpdate => { + let update = update_data.update_data_as_delta_update().unwrap(); + let delta = StateDelta::from(update.delta().bytes()); + Ok(UpdateData::Delta(delta)) + } + UpdateDataType::StateAndDeltaUpdate => { + let update = update_data.update_data_as_state_and_delta_update().unwrap(); + let state = State::from(update.state().bytes()); + let delta = StateDelta::from(update.delta().bytes()); + Ok(UpdateData::StateAndDelta { state, delta }) + } + UpdateDataType::RelatedStateUpdate => { + let update = update_data.update_data_as_related_state_update().unwrap(); + let state = State::from(update.state().bytes()); + let related_to = + ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap(); + Ok(UpdateData::RelatedState { related_to, state }) + } + UpdateDataType::RelatedDeltaUpdate => { + let update = update_data.update_data_as_related_delta_update().unwrap(); + let delta = StateDelta::from(update.delta().bytes()); + let related_to = + ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap(); + Ok(UpdateData::RelatedDelta { related_to, delta }) + } + UpdateDataType::RelatedStateAndDeltaUpdate => { + let update = update_data + .update_data_as_related_state_and_delta_update() + .unwrap(); + let state = State::from(update.state().bytes()); + let delta = StateDelta::from(update.delta().bytes()); + let related_to = + ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap(); + Ok(UpdateData::RelatedStateAndDelta { + related_to, + state, + delta, + }) + } + _ => unreachable!(), + } + } +} diff --git a/rust/src/contract_interface/wasm_interface.rs b/rust/src/contract_interface/wasm_interface.rs new file mode 100644 index 0000000..3810dd0 --- /dev/null +++ b/rust/src/contract_interface/wasm_interface.rs @@ -0,0 +1,199 @@ +//! Contains all the types to interface between the host environment and +//! the wasm module execution. +use super::*; +use crate::memory::WasmLinearMem; + +#[repr(i32)] +enum ResultKind { + ValidateState = 0, + ValidateDelta = 1, + UpdateState = 2, + SummarizeState = 3, + StateDelta = 4, +} + +impl From for ResultKind { + fn from(v: i32) -> Self { + match v { + 0 => ResultKind::ValidateState, + 1 => ResultKind::ValidateDelta, + 2 => ResultKind::UpdateState, + 3 => ResultKind::SummarizeState, + 4 => ResultKind::StateDelta, + _ => panic!(), + } + } +} + +#[doc(hidden)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ContractInterfaceResult { + ptr: i64, + kind: i32, + size: u32, +} + +impl ContractInterfaceResult { + pub unsafe fn unwrap_validate_state_res( + self, + mem: WasmLinearMem, + ) -> Result { + #![allow(clippy::let_and_return)] + let kind = ResultKind::from(self.kind); + match kind { + ResultKind::ValidateState => { + let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); + let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); + let value = bincode::deserialize(serialized) + .map_err(|e| ContractError::Other(format!("{e}")))?; + #[cfg(feature = "trace")] + self.log_input(serialized, &value, ptr); + value + } + _ => unreachable!(), + } + } + + pub unsafe fn unwrap_update_state( + self, + mem: WasmLinearMem, + ) -> Result, ContractError> { + let kind = ResultKind::from(self.kind); + match kind { + ResultKind::UpdateState => { + let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); + let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); + let value: Result, ContractError> = + bincode::deserialize(serialized) + .map_err(|e| ContractError::Other(format!("{e}")))?; + #[cfg(feature = "trace")] + self.log_input(serialized, &value, ptr); + // TODO: it may be possible to not own this value while deserializing + // under certain circumstances (e.g. when the cotnract mem is kept alive) + value.map(|r| r.into_owned()) + } + _ => unreachable!(), + } + } + + pub unsafe fn unwrap_summarize_state( + self, + mem: WasmLinearMem, + ) -> Result, ContractError> { + let kind = ResultKind::from(self.kind); + match kind { + ResultKind::SummarizeState => { + let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); + let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); + let value: Result, ContractError> = + bincode::deserialize(serialized) + .map_err(|e| ContractError::Other(format!("{e}")))?; + #[cfg(feature = "trace")] + self.log_input(serialized, &value, ptr); + // TODO: it may be possible to not own this value while deserializing + // under certain circumstances (e.g. when the contract mem is kept alive) + value.map(|s| StateSummary::from(s.into_bytes())) + } + _ => unreachable!(), + } + } + + pub unsafe fn unwrap_get_state_delta( + self, + mem: WasmLinearMem, + ) -> Result, ContractError> { + let kind = ResultKind::from(self.kind); + match kind { + ResultKind::StateDelta => { + let ptr = crate::memory::buf::compute_ptr(self.ptr as *mut u8, &mem); + let serialized = std::slice::from_raw_parts(ptr as *const u8, self.size as _); + let value: Result, ContractError> = + bincode::deserialize(serialized) + .map_err(|e| ContractError::Other(format!("{e}")))?; + #[cfg(feature = "trace")] + self.log_input(serialized, &value, ptr); + // TODO: it may be possible to not own this value while deserializing + // under certain circumstances (e.g. when the contract mem is kept alive) + value.map(|d| StateDelta::from(d.into_bytes())) + } + _ => unreachable!(), + } + } + + #[cfg(feature = "contract")] + pub fn into_raw(self) -> i64 { + #[cfg(feature = "trace")] + { + tracing::trace!("returning FFI -> {self:?}"); + } + let ptr = Box::into_raw(Box::new(self)); + #[cfg(feature = "trace")] + { + tracing::trace!("FFI result ptr: {ptr:p} ({}i64)", ptr as i64); + } + ptr as _ + } + + pub unsafe fn from_raw(ptr: i64, mem: &WasmLinearMem) -> Self { + let result = Box::leak(Box::from_raw(crate::memory::buf::compute_ptr( + ptr as *mut Self, + mem, + ))); + #[cfg(feature = "trace")] + { + tracing::trace!( + "got FFI result @ {ptr} ({:p}) -> {result:?}", + ptr as *mut Self + ); + } + *result + } + + #[cfg(feature = "trace")] + fn log_input(&self, serialized: &[u8], value: &T, ptr: *mut u8) { + tracing::trace!( + "got result through FFI; addr: {:p} ({}i64, mapped: {ptr:p}) + serialized: {serialized:?} + value: {value:?}", + self.ptr as *mut u8, + self.ptr + ); + } +} + +#[cfg(feature = "contract")] +macro_rules! conversion { + ($value:ty: $kind:expr) => { + impl From<$value> for ContractInterfaceResult { + fn from(value: $value) -> Self { + let kind = $kind as i32; + // TODO: research if there is a safe way to just transmute the pointer in memory + // independently of the architecture when stored in WASM and accessed from + // the host, maybe even if is just for some architectures + let serialized = bincode::serialize(&value).unwrap(); + let size = serialized.len() as _; + let ptr = serialized.as_ptr(); + #[cfg(feature = "trace")] { + tracing::trace!( + "sending result through FFI; addr: {ptr:p} ({}),\n serialized: {serialized:?}\n value: {value:?}", + ptr as i64 + ); + } + std::mem::forget(serialized); + Self { kind, ptr: ptr as i64, size } + } + } + }; +} + +#[cfg(feature = "contract")] +conversion!(Result: ResultKind::ValidateState); +#[cfg(feature = "contract")] +conversion!(Result: ResultKind::ValidateDelta); +#[cfg(feature = "contract")] +conversion!(Result, ContractError>: ResultKind::UpdateState); +#[cfg(feature = "contract")] +conversion!(Result, ContractError>: ResultKind::SummarizeState); +#[cfg(feature = "contract")] +conversion!(Result, ContractError>: ResultKind::StateDelta); diff --git a/rust/src/contract_interface/wrapped.rs b/rust/src/contract_interface/wrapped.rs new file mode 100644 index 0000000..340d4ca --- /dev/null +++ b/rust/src/contract_interface/wrapped.rs @@ -0,0 +1,201 @@ +//! Arc-wrapped versions of contract types for efficient sharing. +//! +//! This module provides `WrappedState` and `WrappedContract` which use `Arc` +//! for efficient sharing of state and contract data across threads. + +use std::borrow::Borrow; +use std::fmt::{Debug, Display}; +use std::ops::Deref; +use std::sync::Arc; + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::parameters::Parameters; + +use super::code::ContractCode; +use super::key::ContractKey; +use super::state::State; + +// TODO: get rid of this when State is internally an Arc<[u8]> +/// The state for a contract. +#[derive(PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "testing", derive(arbitrary::Arbitrary))] +pub struct WrappedState( + #[serde( + serialize_with = "WrappedState::ser_state", + deserialize_with = "WrappedState::deser_state" + )] + Arc>, +); + +impl WrappedState { + pub fn new(bytes: Vec) -> Self { + WrappedState(Arc::new(bytes)) + } + + pub fn size(&self) -> usize { + self.0.len() + } + + fn ser_state(data: &Arc>, ser: S) -> Result + where + S: serde::Serializer, + { + serde_bytes::serialize(&**data, ser) + } + + fn deser_state<'de, D>(deser: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let data: Vec = serde_bytes::deserialize(deser)?; + Ok(Arc::new(data)) + } +} + +impl From> for WrappedState { + fn from(bytes: Vec) -> Self { + Self::new(bytes) + } +} + +impl From<&'_ [u8]> for WrappedState { + fn from(bytes: &[u8]) -> Self { + Self::new(bytes.to_owned()) + } +} + +impl AsRef<[u8]> for WrappedState { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Deref for WrappedState { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Borrow<[u8]> for WrappedState { + fn borrow(&self) -> &[u8] { + &self.0 + } +} + +impl From for State<'static> { + fn from(value: WrappedState) -> Self { + match Arc::try_unwrap(value.0) { + Ok(v) => State::from(v), + Err(v) => State::from(v.as_ref().to_vec()), + } + } +} + +impl std::fmt::Display for WrappedState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ContractState(data: [0x")?; + for b in self.0.iter().take(8) { + write!(f, "{:02x}", b)?; + } + write!(f, "...])") + } +} + +impl std::fmt::Debug for WrappedState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, f) + } +} + +/// Just as `freenet_stdlib::Contract` but with some convenience impl. +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WrappedContract { + #[serde( + serialize_with = "WrappedContract::ser_contract_data", + deserialize_with = "WrappedContract::deser_contract_data" + )] + pub data: Arc>, + #[serde(deserialize_with = "Parameters::deser_params")] + pub params: Parameters<'static>, + pub key: ContractKey, +} + +impl PartialEq for WrappedContract { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl Eq for WrappedContract {} + +impl WrappedContract { + pub fn new(data: Arc>, params: Parameters<'static>) -> WrappedContract { + let key = ContractKey::from_params_and_code(¶ms, &*data); + WrappedContract { data, params, key } + } + + #[inline] + pub fn key(&self) -> &ContractKey { + &self.key + } + + #[inline] + pub fn code(&self) -> &Arc> { + &self.data + } + + #[inline] + pub fn params(&self) -> &Parameters<'static> { + &self.params + } + + fn ser_contract_data(data: &Arc>, ser: S) -> Result + where + S: serde::Serializer, + { + data.serialize(ser) + } + + fn deser_contract_data<'de, D>(deser: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let data: ContractCode<'de> = Deserialize::deserialize(deser)?; + Ok(Arc::new(data.into_owned())) + } +} + +impl TryInto> for WrappedContract { + type Error = (); + fn try_into(self) -> Result, Self::Error> { + Arc::try_unwrap(self.data) + .map(|r| r.into_bytes()) + .map_err(|_| ()) + } +} + +impl Display for WrappedContract { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.key, f) + } +} + +#[cfg(feature = "testing")] +impl<'a> arbitrary::Arbitrary<'a> for WrappedContract { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + use arbitrary::Arbitrary; + let data = ::arbitrary(u)?.into_owned(); + let param_bytes: Vec = Arbitrary::arbitrary(u)?; + let params = Parameters::from(param_bytes); + let key = ContractKey::from_params_and_code(¶ms, &data); + Ok(Self { + data: Arc::new(data), + params, + key, + }) + } +} From 5f509a076a6dd500befee70eed2e82ba815cc48d Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sun, 2 Nov 2025 22:45:13 +0100 Subject: [PATCH 2/3] fix: guard websocket callbacks against drop --- rust/src/contract_interface/encoding.rs | 31 +++++++++++-------------- rust/src/contract_interface/mod.rs | 24 +++++++++---------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/rust/src/contract_interface/encoding.rs b/rust/src/contract_interface/encoding.rs index b9f2440..401f354 100644 --- a/rust/src/contract_interface/encoding.rs +++ b/rust/src/contract_interface/encoding.rs @@ -1,10 +1,13 @@ //! Helper types for interaction between wasm and host boundaries. -use std::{collections::{HashMap, HashSet}, marker::PhantomData}; +use std::{ + collections::{HashMap, HashSet}, + marker::PhantomData, +}; use serde::{de::DeserializeOwned, Serialize}; -use crate::parameters::Parameters; use super::*; +use crate::parameters::Parameters; pub enum MergeResult { Success, @@ -78,8 +81,7 @@ impl RelatedContractsContainer { pub fn get( &self, params: &C::Parameters, - ) -> Result, <::SelfEncoder as Encoder>::Error> - { + ) -> Result, <::SelfEncoder as Encoder>::Error> { let id = ::instance_id(params); if let Some(res) = self.contracts.get(&id) { match <::SelfEncoder>::deserialize(res.as_ref()) { @@ -335,12 +337,9 @@ where ::Parameters, >>::Error, >, - ContractError: - From< - <::SummaryEncoder as Encoder< - ::Summary, - >>::Error, - >, + ContractError: From< + <::SummaryEncoder as Encoder<::Summary>>::Error, + >, { let typed_params = <::ParametersEncoder>::deserialize(parameters.as_ref())?; @@ -363,12 +362,9 @@ where ::Parameters, >>::Error, >, - ContractError: - From< - <::SummaryEncoder as Encoder< - ::Summary, - >>::Error, - >, + ContractError: From< + <::SummaryEncoder as Encoder<::Summary>>::Error, + >, ContractError: From< <::DeltaEncoder as Encoder<::Delta>>::Error, >, @@ -376,8 +372,7 @@ where let typed_params = <::ParametersEncoder>::deserialize(parameters.as_ref())?; let typed_state = <::SelfEncoder>::deserialize(state.as_ref())?; - let typed_summary = - <::SummaryEncoder>::deserialize(summary.as_ref())?; + let typed_summary = <::SummaryEncoder>::deserialize(summary.as_ref())?; let summary = typed_state.delta(typed_params, typed_summary)?; let encoded = <::DeltaEncoder>::serialize(&summary)?; Ok(encoded.into()) diff --git a/rust/src/contract_interface/mod.rs b/rust/src/contract_interface/mod.rs index 3fee666..7de525c 100644 --- a/rust/src/contract_interface/mod.rs +++ b/rust/src/contract_interface/mod.rs @@ -6,28 +6,28 @@ pub(crate) const CONTRACT_KEY_SIZE: usize = 32; -mod error; -mod state; -mod key; mod code; -mod update; mod contract; -mod wrapped; +pub mod encoding; +mod error; +mod key; +mod state; mod trait_def; +mod update; pub(crate) mod wasm_interface; -pub mod encoding; +mod wrapped; #[cfg(all(test, any(unix, windows)))] mod tests; // Re-export all public types +pub use code::ContractCode; +pub use contract::Contract; pub use error::ContractError; -pub use state::{State, StateDelta, StateSummary}; pub use key::{ContractInstanceId, ContractKey}; -pub use code::ContractCode; +pub use state::{State, StateDelta, StateSummary}; +pub use trait_def::ContractInterface; pub use update::{ - UpdateData, UpdateModification, RelatedContracts, RelatedContract, RelatedMode, ValidateResult, + RelatedContract, RelatedContracts, RelatedMode, UpdateData, UpdateModification, ValidateResult, }; -pub use contract::Contract; -pub use wrapped::{WrappedState, WrappedContract}; -pub use trait_def::ContractInterface; +pub use wrapped::{WrappedContract, WrappedState}; From 48e3d230c558f596aaccbc316f92935cc09470b6 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sun, 2 Nov 2025 22:47:04 +0100 Subject: [PATCH 3/3] chore: bump version to 0.1.24 --- rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 489c2f2..0bba229 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "freenet-stdlib" -version = "0.1.23" +version = "0.1.24" edition = "2021" rust-version = "1.71.1" publish = true