freenet_stdlib/contract_interface/
code.rs

1//! Contract executable code representation.
2//!
3//! This module provides the `ContractCode` type which represents the executable
4//! WASM bytecode for a contract, along with its hash.
5
6use std::borrow::Cow;
7use std::fs::File;
8use std::io::Read;
9use std::path::Path;
10
11use blake3::{traits::digest::Digest, Hasher as Blake3};
12use serde::{Deserialize, Serialize};
13use serde_with::serde_as;
14
15use crate::code_hash::CodeHash;
16
17use super::key::internal_fmt_key;
18use super::CONTRACT_KEY_SIZE;
19
20/// The executable contract.
21///
22/// It is the part of the executable belonging to the full specification
23/// and does not include any other metadata (like the parameters).
24#[serde_as]
25#[derive(Serialize, Deserialize, Clone)]
26#[cfg_attr(
27    any(feature = "testing", all(test, any(unix, windows))),
28    derive(arbitrary::Arbitrary)
29)]
30pub struct ContractCode<'a> {
31    // TODO: conver this to Arc<[u8]> instead
32    #[serde_as(as = "serde_with::Bytes")]
33    #[serde(borrow)]
34    pub(crate) data: Cow<'a, [u8]>,
35    // todo: skip serializing and instead compute it
36    pub(crate) code_hash: CodeHash,
37}
38
39impl ContractCode<'static> {
40    /// Loads the contract raw wasm module, without any version.
41    pub fn load_raw(path: &Path) -> Result<Self, std::io::Error> {
42        let contract_data = Self::load_bytes(path)?;
43        Ok(ContractCode::from(contract_data))
44    }
45
46    pub(crate) fn load_bytes(path: &Path) -> Result<Vec<u8>, std::io::Error> {
47        let mut contract_file = File::open(path)?;
48        let mut contract_data = if let Ok(md) = contract_file.metadata() {
49            Vec::with_capacity(md.len() as usize)
50        } else {
51            Vec::new()
52        };
53        contract_file.read_to_end(&mut contract_data)?;
54        Ok(contract_data)
55    }
56}
57
58impl ContractCode<'_> {
59    /// Contract code hash.
60    pub fn hash(&self) -> &CodeHash {
61        &self.code_hash
62    }
63
64    /// Returns the `Base58` string representation of the contract key.
65    pub fn hash_str(&self) -> String {
66        Self::encode_hash(&self.code_hash.0)
67    }
68
69    /// Reference to contract code.
70    pub fn data(&self) -> &[u8] {
71        &self.data
72    }
73
74    /// Extracts the owned contract code data as a `Vec<u8>`.
75    pub fn into_bytes(self) -> Vec<u8> {
76        self.data.to_vec()
77    }
78
79    /// Returns the `Base58` string representation of a hash.
80    pub fn encode_hash(hash: &[u8; CONTRACT_KEY_SIZE]) -> String {
81        bs58::encode(hash)
82            .with_alphabet(bs58::Alphabet::BITCOIN)
83            .into_string()
84    }
85
86    /// Copies the data if not owned and returns an owned version of self.
87    pub fn into_owned(self) -> ContractCode<'static> {
88        ContractCode {
89            data: self.data.into_owned().into(),
90            code_hash: self.code_hash,
91        }
92    }
93
94    fn gen_hash(data: &[u8]) -> CodeHash {
95        let mut hasher = Blake3::new();
96        hasher.update(data);
97        let key_arr = hasher.finalize();
98        debug_assert_eq!(key_arr[..].len(), CONTRACT_KEY_SIZE);
99        let mut key = [0; CONTRACT_KEY_SIZE];
100        key.copy_from_slice(&key_arr);
101        CodeHash(key)
102    }
103}
104
105impl From<Vec<u8>> for ContractCode<'static> {
106    fn from(data: Vec<u8>) -> Self {
107        let key = ContractCode::gen_hash(&data);
108        ContractCode {
109            data: Cow::from(data),
110            code_hash: key,
111        }
112    }
113}
114
115impl<'a> From<&'a [u8]> for ContractCode<'a> {
116    fn from(data: &'a [u8]) -> ContractCode<'a> {
117        let hash = ContractCode::gen_hash(data);
118        ContractCode {
119            data: Cow::from(data),
120            code_hash: hash,
121        }
122    }
123}
124
125impl PartialEq for ContractCode<'_> {
126    fn eq(&self, other: &Self) -> bool {
127        self.code_hash == other.code_hash
128    }
129}
130
131impl Eq for ContractCode<'_> {}
132
133impl std::fmt::Display for ContractCode<'_> {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(f, "Contract( key: ")?;
136        internal_fmt_key(&self.code_hash.0, f)?;
137        let data: String = if self.data.len() > 8 {
138            self.data[..4]
139                .iter()
140                .map(|b| char::from(*b))
141                .chain("...".chars())
142                .chain(self.data[4..].iter().map(|b| char::from(*b)))
143                .collect()
144        } else {
145            self.data.iter().copied().map(char::from).collect()
146        };
147        write!(f, ", data: [{data}])")
148    }
149}
150
151impl std::fmt::Debug for ContractCode<'_> {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        f.debug_struct("ContractCode")
154            .field("hash", &self.code_hash);
155        Ok(())
156    }
157}