Skip to main content

chio_web3/
contracts.rs

1use std::collections::HashSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::Web3ContractError;
6use crate::validation::{ensure_b256_hex, ensure_non_empty, ensure_unique_strings};
7
8pub const CHIO_WEB3_CONTRACT_PACKAGE_SCHEMA: &str = "chio.web3-contract-package.v1";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum Web3ContractKind {
13    RootRegistry,
14    Escrow,
15    BondVault,
16    IdentityRegistry,
17    PriceResolver,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22pub enum Web3BindingLanguage {
23    Rust,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct Web3ContractInterface {
29    pub contract_id: String,
30    pub kind: Web3ContractKind,
31    pub interface_name: String,
32    pub abi_reference: String,
33    pub implementation_reference: String,
34    pub creation_bytecode_hash: String,
35    pub deployed_runtime_codehash: String,
36    pub immutable: bool,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct Web3BindingTarget {
42    pub language: Web3BindingLanguage,
43    pub crate_path: String,
44    pub module_name: String,
45    pub contract_ids: Vec<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(deny_unknown_fields)]
50pub struct Web3ContractPackage {
51    pub schema: String,
52    pub package_id: String,
53    pub version: String,
54    pub chio_contract_version: String,
55    pub contracts: Vec<Web3ContractInterface>,
56    pub bindings: Vec<Web3BindingTarget>,
57    pub deferred_capabilities: Vec<String>,
58}
59
60pub fn validate_web3_contract_package(
61    package: &Web3ContractPackage,
62) -> Result<(), Web3ContractError> {
63    if package.schema != CHIO_WEB3_CONTRACT_PACKAGE_SCHEMA {
64        return Err(Web3ContractError::UnsupportedSchema(package.schema.clone()));
65    }
66    ensure_non_empty(&package.package_id, "web3_contract_package.package_id")?;
67    ensure_non_empty(&package.version, "web3_contract_package.version")?;
68    ensure_non_empty(
69        &package.chio_contract_version,
70        "web3_contract_package.chio_contract_version",
71    )?;
72    if package.contracts.is_empty() {
73        return Err(Web3ContractError::MissingField(
74            "web3_contract_package.contracts",
75        ));
76    }
77    if package.bindings.is_empty() {
78        return Err(Web3ContractError::MissingField(
79            "web3_contract_package.bindings",
80        ));
81    }
82
83    let mut contract_ids = HashSet::new();
84    let mut contract_kinds = HashSet::new();
85    for contract in &package.contracts {
86        ensure_non_empty(
87            &contract.contract_id,
88            "web3_contract_package.contracts.contract_id",
89        )?;
90        ensure_non_empty(
91            &contract.interface_name,
92            "web3_contract_package.contracts.interface_name",
93        )?;
94        ensure_non_empty(
95            &contract.abi_reference,
96            "web3_contract_package.contracts.abi_reference",
97        )?;
98        ensure_non_empty(
99            &contract.implementation_reference,
100            "web3_contract_package.contracts.implementation_reference",
101        )?;
102        ensure_b256_hex(
103            &contract.creation_bytecode_hash,
104            "web3_contract_package.contracts.creation_bytecode_hash",
105        )?;
106        ensure_b256_hex(
107            &contract.deployed_runtime_codehash,
108            "web3_contract_package.contracts.deployed_runtime_codehash",
109        )?;
110        if !contract_ids.insert(contract.contract_id.as_str()) {
111            return Err(Web3ContractError::DuplicateValue(
112                contract.contract_id.clone(),
113            ));
114        }
115        if !contract_kinds.insert(contract.kind) {
116            return Err(Web3ContractError::DuplicateValue(format!(
117                "web3_contract_package.contract_kind:{:?}",
118                contract.kind
119            )));
120        }
121    }
122    for required in [
123        Web3ContractKind::RootRegistry,
124        Web3ContractKind::Escrow,
125        Web3ContractKind::BondVault,
126        Web3ContractKind::IdentityRegistry,
127        Web3ContractKind::PriceResolver,
128    ] {
129        if !contract_kinds.contains(&required) {
130            return Err(Web3ContractError::UnknownReference(format!(
131                "missing required contract kind {:?}",
132                required
133            )));
134        }
135    }
136
137    for binding in &package.bindings {
138        ensure_non_empty(
139            &binding.crate_path,
140            "web3_contract_package.bindings.crate_path",
141        )?;
142        ensure_non_empty(
143            &binding.module_name,
144            "web3_contract_package.bindings.module_name",
145        )?;
146        if binding.contract_ids.is_empty() {
147            return Err(Web3ContractError::MissingField(
148                "web3_contract_package.bindings.contract_ids",
149            ));
150        }
151        ensure_unique_strings(
152            &binding.contract_ids,
153            "web3_contract_package.bindings.contract_ids",
154        )?;
155        for contract_id in &binding.contract_ids {
156            if !contract_ids.contains(contract_id.as_str()) {
157                return Err(Web3ContractError::UnknownReference(contract_id.clone()));
158            }
159        }
160    }
161
162    ensure_unique_strings(
163        &package.deferred_capabilities,
164        "web3_contract_package.deferred_capabilities",
165    )?;
166
167    Ok(())
168}