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