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}