1use std::fmt;
2use std::fmt::{Display, Formatter};
3use std::io::{Cursor, Read};
4use std::path::Path;
5use std::sync::Arc;
6
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use crate::{
12 client_api::{unknown_union_discriminant, TryFromFbs, WsApiError},
13 common_generated::common::{ContractContainer as FbsContractContainer, ContractType},
14 contract_interface::{ContractInstanceId, ContractKey},
15 generated::client_request::{DelegateContainer as FbsDelegateContainer, DelegateType},
16 parameters::Parameters,
17 prelude::{
18 CodeHash, ContractCode, ContractWasmAPIVersion::V1, Delegate, DelegateCode, DelegateKey,
19 WrappedContract,
20 },
21};
22
23#[non_exhaustive]
25#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
26pub enum DelegateWasmAPIVersion {
27 V1(#[serde(deserialize_with = "Delegate::deserialize_delegate")] Delegate<'static>),
28}
29
30impl DelegateWasmAPIVersion {}
31
32#[non_exhaustive]
33#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
34pub enum DelegateContainer {
35 Wasm(DelegateWasmAPIVersion),
36}
37
38impl From<DelegateContainer> for APIVersion {
39 fn from(delegate: DelegateContainer) -> APIVersion {
40 match delegate {
41 DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
42 }
43 }
44}
45
46impl DelegateContainer {
47 pub fn key(&self) -> &DelegateKey {
48 match self {
49 Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.key(),
50 }
51 }
52
53 pub fn code(&self) -> &DelegateCode<'_> {
54 match self {
55 Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.code(),
56 }
57 }
58
59 pub fn code_hash(&self) -> &CodeHash {
60 match self {
61 Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.code_hash(),
62 }
63 }
64}
65
66impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for DelegateContainer {
67 type Error = std::io::Error;
68
69 fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
70 let (contract_code, version) = DelegateCode::load_versioned_from_path(path)?;
71
72 match version {
73 APIVersion::Version0_0_1 => {
74 let delegate = Delegate::from((&contract_code, ¶ms));
75 Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
76 delegate,
77 )))
78 }
79 }
80 }
81}
82
83impl<'a, P> TryFrom<(Vec<u8>, P)> for DelegateContainer
84where
85 P: std::ops::Deref<Target = Parameters<'a>>,
86{
87 type Error = std::io::Error;
88
89 fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
90 let params = params.deref().clone().into_owned();
91
92 let (contract_code, version) =
93 DelegateCode::load_versioned_from_bytes(versioned_contract_bytes)?;
94
95 match version {
96 APIVersion::Version0_0_1 => {
97 let delegate = Delegate::from((&contract_code, ¶ms));
98 Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
99 delegate,
100 )))
101 }
102 }
103 }
104}
105
106impl<'a> TryFromFbs<&FbsDelegateContainer<'a>> for DelegateContainer {
107 fn try_decode_fbs(container: &FbsDelegateContainer<'a>) -> Result<Self, WsApiError> {
108 match container.delegate_type() {
109 DelegateType::WasmDelegateV1 => {
110 let delegate = container.delegate_as_wasm_delegate_v1().unwrap();
111 let data = DelegateCode::from(delegate.data().data().bytes().to_vec());
112 let params = Parameters::from(delegate.parameters().bytes().to_vec());
113 Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
114 Delegate::from((&data, ¶ms)),
115 )))
116 }
117 other => Err(unknown_union_discriminant("DelegateType", other.0)),
123 }
124 }
125}
126
127impl DelegateCode<'static> {
128 fn load_versioned(
129 mut contract_data: Cursor<Vec<u8>>,
130 ) -> Result<(Self, APIVersion), std::io::Error> {
131 let version = contract_data.read_u64::<BigEndian>().map_err(|_| {
133 std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to read version")
134 })?;
135 let version = APIVersion::from_u64(version).map_err(|e| {
136 std::io::Error::new(
137 std::io::ErrorKind::InvalidData,
138 format!("Version error: {}", e),
139 )
140 })?;
141
142 if version == APIVersion::Version0_0_1 {
143 let mut code_hash = [0u8; 32];
144 contract_data.read_exact(&mut code_hash)?;
145 }
146
147 let mut code_data: Vec<u8> = vec![];
149 contract_data
150 .read_to_end(&mut code_data)
151 .map_err(|_| std::io::ErrorKind::InvalidData)?;
152 Ok((DelegateCode::from(code_data), version))
153 }
154
155 pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
157 let contract_data = Cursor::new(Self::load_bytes(path)?);
158 Self::load_versioned(contract_data)
159 }
160
161 pub fn load_versioned_from_bytes(
163 versioned_code: Vec<u8>,
164 ) -> Result<(Self, APIVersion), std::io::Error> {
165 let contract_data = Cursor::new(versioned_code);
166 Self::load_versioned(contract_data)
167 }
168}
169
170impl DelegateCode<'_> {
171 pub fn to_bytes_versioned(
172 &self,
173 version: APIVersion,
174 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
175 match version {
176 APIVersion::Version0_0_1 => {
177 let output_size =
178 std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
179 let mut output: Vec<u8> = Vec::with_capacity(output_size);
180 output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
181 output.extend(self.hash().0.iter());
182 output.extend(self.data());
183 Ok(output)
184 }
185 }
186 }
187}
188
189#[non_exhaustive]
191#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
192
193pub enum ContractWasmAPIVersion {
194 V1(WrappedContract),
195}
196
197impl From<ContractWasmAPIVersion> for ContractContainer {
198 fn from(value: ContractWasmAPIVersion) -> Self {
199 ContractContainer::Wasm(value)
200 }
201}
202
203impl Display for ContractWasmAPIVersion {
204 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
205 match self {
206 ContractWasmAPIVersion::V1(contract_v1) => {
207 write!(f, "[api=0.0.1]({contract_v1})")
208 }
209 }
210 }
211}
212
213#[non_exhaustive]
216#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
217pub enum ContractContainer {
218 Wasm(ContractWasmAPIVersion),
219}
220
221impl From<ContractContainer> for APIVersion {
222 fn from(contract: ContractContainer) -> APIVersion {
223 match contract {
224 ContractContainer::Wasm(ContractWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
225 }
226 }
227}
228
229impl ContractContainer {
230 pub fn key(&self) -> ContractKey {
232 match self {
233 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => *contract_v1.key(),
234 }
235 }
236
237 pub fn id(&self) -> &ContractInstanceId {
239 match self {
240 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.key().id(),
241 }
242 }
243
244 pub fn params(&self) -> Parameters<'static> {
246 match self {
247 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.params().clone(),
248 }
249 }
250
251 pub fn data(&self) -> &[u8] {
253 match self {
254 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.data.data(),
255 }
256 }
257
258 pub fn unwrap_v1(self) -> WrappedContract {
259 match self {
260 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1,
261 }
262 }
263}
264
265impl Display for ContractContainer {
266 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
267 match self {
268 ContractContainer::Wasm(wasm_version) => {
269 write!(f, "WasmContainer({wasm_version})")
270 }
271 }
272 }
273}
274
275impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for ContractContainer {
276 type Error = std::io::Error;
277
278 fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
279 let (contract_code, version) = ContractCode::load_versioned_from_path(path)?;
280
281 match version {
282 APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
283 WrappedContract::new(Arc::new(contract_code), params),
284 ))),
285 }
286 }
287}
288
289impl<'a, P> TryFrom<(Vec<u8>, P)> for ContractContainer
290where
291 P: std::ops::Deref<Target = Parameters<'a>>,
292{
293 type Error = std::io::Error;
294
295 fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
296 let params = params.deref().clone().into_owned();
297
298 let (contract_code, version) =
299 ContractCode::load_versioned_from_bytes(versioned_contract_bytes)?;
300
301 match version {
302 APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
303 WrappedContract::new(Arc::new(contract_code), params),
304 ))),
305 }
306 }
307}
308
309impl ContractCode<'static> {
310 fn load_versioned(
311 mut contract_data: Cursor<Vec<u8>>,
312 ) -> Result<(Self, APIVersion), std::io::Error> {
313 let version = contract_data.read_u64::<BigEndian>().map_err(|_| {
315 std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to read version")
316 })?;
317 let version = APIVersion::from_u64(version).map_err(|e| {
318 std::io::Error::new(
319 std::io::ErrorKind::InvalidData,
320 format!("Version error: {}", e),
321 )
322 })?;
323
324 if version == APIVersion::Version0_0_1 {
325 let mut code_hash = [0u8; 32];
326 contract_data.read_exact(&mut code_hash)?;
327 }
328
329 let mut code_data: Vec<u8> = vec![];
331 contract_data
332 .read_to_end(&mut code_data)
333 .map_err(|_| std::io::ErrorKind::InvalidData)?;
334 Ok((ContractCode::from(code_data), version))
335 }
336
337 pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
339 let contract_data = Cursor::new(Self::load_bytes(path)?);
340 Self::load_versioned(contract_data)
341 }
342
343 pub fn load_versioned_from_bytes(
345 versioned_code: Vec<u8>,
346 ) -> Result<(Self, APIVersion), std::io::Error> {
347 let contract_data = Cursor::new(versioned_code);
348 Self::load_versioned(contract_data)
349 }
350}
351
352#[derive(Debug, Error)]
353pub enum VersionError {
354 #[error("unsupported incremental API version: {0}")]
355 UnsupportedVersion(u64),
356 #[error("failed to read version: {0}")]
357 IoError(#[from] std::io::Error),
358}
359
360#[non_exhaustive]
366#[derive(PartialEq, Eq, Clone, Copy)]
367pub enum APIVersion {
368 Version0_0_1,
369}
370
371impl APIVersion {
372 fn from_u64(version: u64) -> Result<Self, VersionError> {
373 match version {
374 0 => Ok(Self::Version0_0_1),
375 v => Err(VersionError::UnsupportedVersion(v)),
376 }
377 }
378
379 fn into_u64(self) -> u64 {
380 match self {
381 Self::Version0_0_1 => 0,
382 }
383 }
384}
385
386impl Display for APIVersion {
387 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
388 match self {
389 APIVersion::Version0_0_1 => write!(f, "0.0.1"),
390 }
391 }
392}
393
394impl<'a> TryFrom<&'a semver::Version> for APIVersion {
395 type Error = Box<dyn std::error::Error + Send + Sync>;
396 fn try_from(value: &'a semver::Version) -> Result<Self, Self::Error> {
397 match value {
398 ver if ver == &semver::Version::new(0, 0, 1) => Ok(APIVersion::Version0_0_1),
399 other => Err(format!("{other} version not supported").into()),
400 }
401 }
402}
403
404impl ContractCode<'_> {
405 pub fn to_bytes_versioned(
406 &self,
407 version: APIVersion,
408 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
409 match version {
410 APIVersion::Version0_0_1 => {
411 let output_size =
412 std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
413 let mut output: Vec<u8> = Vec::with_capacity(output_size);
414 output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
415 output.extend(self.hash().0.iter());
416 output.extend(self.data());
417 Ok(output)
418 }
419 }
420 }
421}
422
423impl<'a> TryFromFbs<&FbsContractContainer<'a>> for ContractContainer {
424 fn try_decode_fbs(value: &FbsContractContainer<'a>) -> Result<Self, WsApiError> {
425 match value.contract_type() {
426 ContractType::WasmContractV1 => {
427 let contract = value.contract_as_wasm_contract_v1().unwrap();
428 let data = Arc::new(ContractCode::from(contract.data().data().bytes().to_vec()));
429 let params = Parameters::from(contract.parameters().bytes().to_vec());
430 let key = ContractKey::from_params_and_code(¶ms, &*data);
431 Ok(ContractContainer::from(V1(WrappedContract {
432 data,
433 params,
434 key,
435 })))
436 }
437 other => Err(unknown_union_discriminant("ContractType", other.0)),
443 }
444 }
445}