locutus_stdlib/
versioning.rs

1use std::fmt;
2use std::fmt::{Display, Formatter};
3use std::fs::File;
4use std::io::{Cursor, Read};
5use std::path::Path;
6use std::sync::Arc;
7
8use byteorder::{BigEndian, ReadBytesExt};
9use semver::Version;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13use crate::prelude::WrappedContract;
14use crate::{
15    contract_interface::ContractKey,
16    prelude::{ContractCode, Parameters, TryFromTsStd, WsApiError},
17};
18
19/// Wrapper that allows contract versioning. This enum maintains the types of contracts that are
20/// allowed and their corresponding version.
21#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
22pub enum ContractContainer {
23    Wasm(WasmAPIVersion),
24}
25
26impl ContractContainer {
27    /// Return the `ContractContainer` content from the specified path as as `Vec<u8>`.
28    pub(crate) fn get_contract_data_from_fs(path: &Path) -> Result<Vec<u8>, std::io::Error> {
29        let mut contract_file = File::open(path)?;
30        let mut contract_data = if let Ok(md) = contract_file.metadata() {
31            Vec::with_capacity(md.len() as usize)
32        } else {
33            Vec::new()
34        };
35        contract_file.read_to_end(&mut contract_data)?;
36        Ok(contract_data)
37    }
38
39    /// Return the `ContractKey` from the specific contract version.
40    pub fn key(&self) -> ContractKey {
41        match self {
42            Self::Wasm(WasmAPIVersion::V1(contract_v1)) => contract_v1.key().clone(),
43        }
44    }
45
46    /// Return the `Parameters` from the specific contract version.
47    pub fn params(&self) -> Parameters<'static> {
48        match self {
49            Self::Wasm(WasmAPIVersion::V1(contract_v1)) => contract_v1.params().clone(),
50        }
51    }
52
53    /// Return the contract code from the specific contract version as `Vec<u8>`.
54    pub fn data(&self) -> Vec<u8> {
55        match self {
56            Self::Wasm(WasmAPIVersion::V1(contract_v1)) => contract_v1.clone().try_into().unwrap(),
57        }
58    }
59}
60
61impl Display for ContractContainer {
62    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
63        match self {
64            ContractContainer::Wasm(wasm_version) => {
65                write!(f, "Wasm container {wasm_version}")
66            }
67        }
68    }
69}
70
71impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for ContractContainer {
72    type Error = std::io::Error;
73
74    fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
75        let mut contract_data =
76            Cursor::new(ContractContainer::get_contract_data_from_fs(path).unwrap());
77
78        // Get contract version
79        let version_size = contract_data
80            .read_u32::<BigEndian>()
81            .map_err(|_| std::io::ErrorKind::InvalidData)?;
82        let mut version_data = vec![0; version_size as usize];
83        contract_data
84            .read_exact(&mut version_data)
85            .map_err(|_| std::io::ErrorKind::InvalidData)?;
86        let version: Version = serde_json::from_slice(version_data.as_slice())
87            .map_err(|_| std::io::ErrorKind::InvalidData)?;
88
89        // Get Contract code
90        let mut code_data: Vec<u8> = vec![];
91        contract_data
92            .read_to_end(&mut code_data)
93            .map_err(|_| std::io::ErrorKind::InvalidData)?;
94        let contract_code = Arc::new(ContractCode::from(code_data));
95
96        match version.to_string().as_str() {
97            "0.0.1" => Ok(ContractContainer::Wasm(WasmAPIVersion::V1(
98                WrappedContract::new(contract_code, params),
99            ))),
100            _ => Err(std::io::ErrorKind::InvalidData.into()),
101        }
102    }
103}
104
105impl TryFromTsStd<&rmpv::Value> for ContractContainer {
106    fn try_decode(value: &rmpv::Value) -> Result<Self, WsApiError> {
107        let container_map: HashMap<&str, &rmpv::Value> = match value.as_map() {
108            Some(map_value) => HashMap::from_iter(
109                map_value
110                    .iter()
111                    .map(|(key, val)| (key.as_str().unwrap(), val)),
112            ),
113            _ => {
114                return Err(WsApiError::MsgpackDecodeError {
115                    cause: "Failed decoding ContractContainer, input value is not a map"
116                        .to_string(),
117                })
118            }
119        };
120
121        let container_version = match container_map.get("version") {
122            Some(version_value) => (*version_value).as_str().unwrap(),
123            _ => {
124                return Err(WsApiError::MsgpackDecodeError {
125                    cause: "Failed decoding ContractContainer, version not found".to_string(),
126                })
127            }
128        };
129
130        match container_version {
131            "V1" => {
132                let contract = WrappedContract::try_decode(value).map_err(|e| {
133                    WsApiError::MsgpackDecodeError {
134                        cause: format!("{e}"),
135                    }
136                })?;
137                Ok(ContractContainer::Wasm(WasmAPIVersion::V1(contract)))
138            }
139            _ => unreachable!(),
140        }
141    }
142}
143
144/// Contains the different versions available for wasm contracts.
145#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
146pub enum WasmAPIVersion {
147    V1(WrappedContract),
148}
149
150impl Display for WasmAPIVersion {
151    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
152        match self {
153            WasmAPIVersion::V1(contract_v1) => {
154                write!(f, "version 0.0.1 of contract {contract_v1}")
155            }
156        }
157    }
158}
159
160impl From<ContractContainer> for Version {
161    fn from(contract: ContractContainer) -> Version {
162        match contract {
163            ContractContainer::Wasm(WasmAPIVersion::V1(_)) => Version::new(0, 0, 1),
164        }
165    }
166}