use crate::state_model::SemanticDiff;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::{Digest, Sha256};
pub const STATE_DIFF_CONTRACT_ID: &str = "feltdb.state.diff";
pub const STATE_DIFF_CONTRACT_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StateDiffInput {
pub contract: String,
pub state_a: Value,
pub state_b: Value,
pub version: u32,
}
impl StateDiffInput {
pub fn new(state_a: Value, state_b: Value) -> Self {
Self {
contract: STATE_DIFF_CONTRACT_ID.to_string(),
state_a,
state_b,
version: STATE_DIFF_CONTRACT_VERSION,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ContractErrorCode {
ContractInputInvalid,
ContractIdMismatch,
ContractVersionUnsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ContractErrorBody {
pub code: ContractErrorCode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StateDiffOutput {
pub contract: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub diff: Option<SemanticDiff>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<ContractErrorBody>,
pub version: u32,
}
impl StateDiffOutput {
fn computed(diff: SemanticDiff) -> Self {
Self {
contract: STATE_DIFF_CONTRACT_ID.to_string(),
diff: Some(diff),
error: None,
version: STATE_DIFF_CONTRACT_VERSION,
}
}
fn failure(code: ContractErrorCode) -> Self {
Self {
contract: STATE_DIFF_CONTRACT_ID.to_string(),
diff: None,
error: Some(ContractErrorBody { code }),
version: STATE_DIFF_CONTRACT_VERSION,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContractRuntime {
Native,
Wasm,
}
impl ContractRuntime {
pub fn as_str(self) -> &'static str {
match self {
Self::Native => "native",
Self::Wasm => "wasm",
}
}
}
pub fn diff_states(state_a: &Value, state_b: &Value) -> SemanticDiff {
SemanticDiff::compute(state_a, state_b)
}
pub fn canonical_input_bytes(input: &StateDiffInput) -> Vec<u8> {
serde_json::to_vec(input).expect("contract input is always serializable")
}
pub fn canonical_output_bytes(output: &StateDiffOutput) -> Vec<u8> {
serde_json::to_vec(output).expect("contract output is always serializable")
}
pub fn input_digest(input: &StateDiffInput) -> String {
format!("sha256:{:x}", Sha256::digest(canonical_input_bytes(input)))
}
pub fn evaluate_canonical(input: &[u8]) -> Vec<u8> {
canonical_output_bytes(&evaluate_canonical_output(input))
}
pub fn evaluate_canonical_output(input: &[u8]) -> StateDiffOutput {
let Ok(parsed) = serde_json::from_slice::<StateDiffInput>(input) else {
return StateDiffOutput::failure(ContractErrorCode::ContractInputInvalid);
};
if parsed.contract != STATE_DIFF_CONTRACT_ID {
return StateDiffOutput::failure(ContractErrorCode::ContractIdMismatch);
}
if parsed.version != STATE_DIFF_CONTRACT_VERSION {
return StateDiffOutput::failure(ContractErrorCode::ContractVersionUnsupported);
}
StateDiffOutput::computed(diff_states(&parsed.state_a, &parsed.state_b))
}