chio-web3 0.1.2

Chio web3 settlement, anchoring, and contract-surface artifacts
Documentation
use std::collections::HashSet;

use serde::{Deserialize, Serialize};

use crate::error::Web3ContractError;
use crate::validation::{ensure_b256_hex, ensure_non_empty, ensure_unique_strings};

pub const CHIO_WEB3_CONTRACT_PACKAGE_SCHEMA: &str = "chio.web3-contract-package.v1";

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Web3ContractKind {
    RootRegistry,
    Escrow,
    BondVault,
    IdentityRegistry,
    PriceResolver,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Web3BindingLanguage {
    Rust,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Web3ContractInterface {
    pub contract_id: String,
    pub kind: Web3ContractKind,
    pub interface_name: String,
    pub abi_reference: String,
    pub implementation_reference: String,
    pub creation_bytecode_hash: String,
    pub deployed_runtime_codehash: String,
    pub immutable: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Web3BindingTarget {
    pub language: Web3BindingLanguage,
    pub crate_path: String,
    pub module_name: String,
    pub contract_ids: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Web3ContractPackage {
    pub schema: String,
    pub package_id: String,
    pub version: String,
    pub chio_contract_version: String,
    pub contracts: Vec<Web3ContractInterface>,
    pub bindings: Vec<Web3BindingTarget>,
    pub deferred_capabilities: Vec<String>,
}

pub fn validate_web3_contract_package(
    package: &Web3ContractPackage,
) -> Result<(), Web3ContractError> {
    if package.schema != CHIO_WEB3_CONTRACT_PACKAGE_SCHEMA {
        return Err(Web3ContractError::UnsupportedSchema(package.schema.clone()));
    }
    ensure_non_empty(&package.package_id, "web3_contract_package.package_id")?;
    ensure_non_empty(&package.version, "web3_contract_package.version")?;
    ensure_non_empty(
        &package.chio_contract_version,
        "web3_contract_package.chio_contract_version",
    )?;
    if package.contracts.is_empty() {
        return Err(Web3ContractError::MissingField(
            "web3_contract_package.contracts",
        ));
    }
    if package.bindings.is_empty() {
        return Err(Web3ContractError::MissingField(
            "web3_contract_package.bindings",
        ));
    }

    let mut contract_ids = HashSet::new();
    let mut contract_kinds = HashSet::new();
    for contract in &package.contracts {
        ensure_non_empty(
            &contract.contract_id,
            "web3_contract_package.contracts.contract_id",
        )?;
        ensure_non_empty(
            &contract.interface_name,
            "web3_contract_package.contracts.interface_name",
        )?;
        ensure_non_empty(
            &contract.abi_reference,
            "web3_contract_package.contracts.abi_reference",
        )?;
        ensure_non_empty(
            &contract.implementation_reference,
            "web3_contract_package.contracts.implementation_reference",
        )?;
        ensure_b256_hex(
            &contract.creation_bytecode_hash,
            "web3_contract_package.contracts.creation_bytecode_hash",
        )?;
        ensure_b256_hex(
            &contract.deployed_runtime_codehash,
            "web3_contract_package.contracts.deployed_runtime_codehash",
        )?;
        if !contract_ids.insert(contract.contract_id.as_str()) {
            return Err(Web3ContractError::DuplicateValue(
                contract.contract_id.clone(),
            ));
        }
        if !contract_kinds.insert(contract.kind) {
            return Err(Web3ContractError::DuplicateValue(format!(
                "web3_contract_package.contract_kind:{:?}",
                contract.kind
            )));
        }
    }
    for required in [
        Web3ContractKind::RootRegistry,
        Web3ContractKind::Escrow,
        Web3ContractKind::BondVault,
        Web3ContractKind::IdentityRegistry,
        Web3ContractKind::PriceResolver,
    ] {
        if !contract_kinds.contains(&required) {
            return Err(Web3ContractError::UnknownReference(format!(
                "missing required contract kind {:?}",
                required
            )));
        }
    }

    for binding in &package.bindings {
        ensure_non_empty(
            &binding.crate_path,
            "web3_contract_package.bindings.crate_path",
        )?;
        ensure_non_empty(
            &binding.module_name,
            "web3_contract_package.bindings.module_name",
        )?;
        if binding.contract_ids.is_empty() {
            return Err(Web3ContractError::MissingField(
                "web3_contract_package.bindings.contract_ids",
            ));
        }
        ensure_unique_strings(
            &binding.contract_ids,
            "web3_contract_package.bindings.contract_ids",
        )?;
        for contract_id in &binding.contract_ids {
            if !contract_ids.contains(contract_id.as_str()) {
                return Err(Web3ContractError::UnknownReference(contract_id.clone()));
            }
        }
    }

    ensure_unique_strings(
        &package.deferred_capabilities,
        "web3_contract_package.deferred_capabilities",
    )?;

    Ok(())
}