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::{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 _ => unreachable!(),
118 }
119 }
120}
121
122impl DelegateCode<'static> {
123 fn load_versioned(
124 mut contract_data: Cursor<Vec<u8>>,
125 ) -> Result<(Self, APIVersion), std::io::Error> {
126 let version = contract_data.read_u64::<BigEndian>().map_err(|_| {
128 std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to read version")
129 })?;
130 let version = APIVersion::from_u64(version).map_err(|e| {
131 std::io::Error::new(
132 std::io::ErrorKind::InvalidData,
133 format!("Version error: {}", e),
134 )
135 })?;
136
137 if version == APIVersion::Version0_0_1 {
138 let mut code_hash = [0u8; 32];
139 contract_data.read_exact(&mut code_hash)?;
140 }
141
142 let mut code_data: Vec<u8> = vec![];
144 contract_data
145 .read_to_end(&mut code_data)
146 .map_err(|_| std::io::ErrorKind::InvalidData)?;
147 Ok((DelegateCode::from(code_data), version))
148 }
149
150 pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
152 let contract_data = Cursor::new(Self::load_bytes(path)?);
153 Self::load_versioned(contract_data)
154 }
155
156 pub fn load_versioned_from_bytes(
158 versioned_code: Vec<u8>,
159 ) -> Result<(Self, APIVersion), std::io::Error> {
160 let contract_data = Cursor::new(versioned_code);
161 Self::load_versioned(contract_data)
162 }
163}
164
165impl DelegateCode<'_> {
166 pub fn to_bytes_versioned(
167 &self,
168 version: APIVersion,
169 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
170 match version {
171 APIVersion::Version0_0_1 => {
172 let output_size =
173 std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
174 let mut output: Vec<u8> = Vec::with_capacity(output_size);
175 output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
176 output.extend(self.hash().0.iter());
177 output.extend(self.data());
178 Ok(output)
179 }
180 }
181 }
182}
183
184#[non_exhaustive]
186#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
187
188pub enum ContractWasmAPIVersion {
189 V1(WrappedContract),
190}
191
192impl From<ContractWasmAPIVersion> for ContractContainer {
193 fn from(value: ContractWasmAPIVersion) -> Self {
194 ContractContainer::Wasm(value)
195 }
196}
197
198impl Display for ContractWasmAPIVersion {
199 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
200 match self {
201 ContractWasmAPIVersion::V1(contract_v1) => {
202 write!(f, "[api=0.0.1]({contract_v1})")
203 }
204 }
205 }
206}
207
208#[non_exhaustive]
211#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
212pub enum ContractContainer {
213 Wasm(ContractWasmAPIVersion),
214}
215
216impl From<ContractContainer> for APIVersion {
217 fn from(contract: ContractContainer) -> APIVersion {
218 match contract {
219 ContractContainer::Wasm(ContractWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
220 }
221 }
222}
223
224impl ContractContainer {
225 pub fn key(&self) -> ContractKey {
227 match self {
228 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => *contract_v1.key(),
229 }
230 }
231
232 pub fn id(&self) -> &ContractInstanceId {
234 match self {
235 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.key().id(),
236 }
237 }
238
239 pub fn params(&self) -> Parameters<'static> {
241 match self {
242 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.params().clone(),
243 }
244 }
245
246 pub fn data(&self) -> &[u8] {
248 match self {
249 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.data.data(),
250 }
251 }
252
253 pub fn unwrap_v1(self) -> WrappedContract {
254 match self {
255 Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1,
256 }
257 }
258}
259
260impl Display for ContractContainer {
261 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
262 match self {
263 ContractContainer::Wasm(wasm_version) => {
264 write!(f, "WasmContainer({wasm_version})")
265 }
266 }
267 }
268}
269
270impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for ContractContainer {
271 type Error = std::io::Error;
272
273 fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
274 let (contract_code, version) = ContractCode::load_versioned_from_path(path)?;
275
276 match version {
277 APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
278 WrappedContract::new(Arc::new(contract_code), params),
279 ))),
280 }
281 }
282}
283
284impl<'a, P> TryFrom<(Vec<u8>, P)> for ContractContainer
285where
286 P: std::ops::Deref<Target = Parameters<'a>>,
287{
288 type Error = std::io::Error;
289
290 fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
291 let params = params.deref().clone().into_owned();
292
293 let (contract_code, version) =
294 ContractCode::load_versioned_from_bytes(versioned_contract_bytes)?;
295
296 match version {
297 APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
298 WrappedContract::new(Arc::new(contract_code), params),
299 ))),
300 }
301 }
302}
303
304impl ContractCode<'static> {
305 fn load_versioned(
306 mut contract_data: Cursor<Vec<u8>>,
307 ) -> Result<(Self, APIVersion), std::io::Error> {
308 let version = contract_data.read_u64::<BigEndian>().map_err(|_| {
310 std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to read version")
311 })?;
312 let version = APIVersion::from_u64(version).map_err(|e| {
313 std::io::Error::new(
314 std::io::ErrorKind::InvalidData,
315 format!("Version error: {}", e),
316 )
317 })?;
318
319 if version == APIVersion::Version0_0_1 {
320 let mut code_hash = [0u8; 32];
321 contract_data.read_exact(&mut code_hash)?;
322 }
323
324 let mut code_data: Vec<u8> = vec![];
326 contract_data
327 .read_to_end(&mut code_data)
328 .map_err(|_| std::io::ErrorKind::InvalidData)?;
329 Ok((ContractCode::from(code_data), version))
330 }
331
332 pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
334 let contract_data = Cursor::new(Self::load_bytes(path)?);
335 Self::load_versioned(contract_data)
336 }
337
338 pub fn load_versioned_from_bytes(
340 versioned_code: Vec<u8>,
341 ) -> Result<(Self, APIVersion), std::io::Error> {
342 let contract_data = Cursor::new(versioned_code);
343 Self::load_versioned(contract_data)
344 }
345}
346
347#[derive(Debug, Error)]
348pub enum VersionError {
349 #[error("unsupported incremental API version: {0}")]
350 UnsupportedVersion(u64),
351 #[error("failed to read version: {0}")]
352 IoError(#[from] std::io::Error),
353}
354
355#[derive(PartialEq, Eq, Clone, Copy)]
356pub enum APIVersion {
357 Version0_0_1,
358}
359
360impl APIVersion {
361 fn from_u64(version: u64) -> Result<Self, VersionError> {
362 match version {
363 0 => Ok(Self::Version0_0_1),
364 v => Err(VersionError::UnsupportedVersion(v)),
365 }
366 }
367
368 fn into_u64(self) -> u64 {
369 match self {
370 Self::Version0_0_1 => 0,
371 }
372 }
373}
374
375impl Display for APIVersion {
376 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
377 match self {
378 APIVersion::Version0_0_1 => write!(f, "0.0.1"),
379 }
380 }
381}
382
383impl<'a> TryFrom<&'a semver::Version> for APIVersion {
384 type Error = Box<dyn std::error::Error + Send + Sync>;
385 fn try_from(value: &'a semver::Version) -> Result<Self, Self::Error> {
386 match value {
387 ver if ver == &semver::Version::new(0, 0, 1) => Ok(APIVersion::Version0_0_1),
388 other => Err(format!("{other} version not supported").into()),
389 }
390 }
391}
392
393impl ContractCode<'_> {
394 pub fn to_bytes_versioned(
395 &self,
396 version: APIVersion,
397 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
398 match version {
399 APIVersion::Version0_0_1 => {
400 let output_size =
401 std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
402 let mut output: Vec<u8> = Vec::with_capacity(output_size);
403 output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
404 output.extend(self.hash().0.iter());
405 output.extend(self.data());
406 Ok(output)
407 }
408 }
409 }
410}
411
412impl<'a> TryFromFbs<&FbsContractContainer<'a>> for ContractContainer {
413 fn try_decode_fbs(value: &FbsContractContainer<'a>) -> Result<Self, WsApiError> {
414 match value.contract_type() {
415 ContractType::WasmContractV1 => {
416 let contract = value.contract_as_wasm_contract_v1().unwrap();
417 let data = Arc::new(ContractCode::from(contract.data().data().bytes().to_vec()));
418 let params = Parameters::from(contract.parameters().bytes().to_vec());
419 let key = ContractKey::from_params_and_code(¶ms, &*data);
420 Ok(ContractContainer::from(V1(WrappedContract {
421 data,
422 params,
423 key,
424 })))
425 }
426 _ => unreachable!(),
427 }
428 }
429}