freenet_stdlib/contract_interface/
key.rs

1//! Contract key types and identifiers.
2//!
3//! This module provides the core types for identifying contracts:
4//! - `ContractInstanceId`: The hash of contract code and parameters
5//! - `ContractKey`: A complete key specification with optional code hash
6
7use std::borrow::Borrow;
8use std::fmt::Display;
9use std::hash::{Hash, Hasher};
10use std::ops::Deref;
11use std::str::FromStr;
12
13use blake3::{traits::digest::Digest, Hasher as Blake3};
14use serde::{Deserialize, Serialize};
15use serde_with::serde_as;
16
17use crate::client_api::{TryFromFbs, WsApiError};
18use crate::code_hash::CodeHash;
19use crate::common_generated::common::ContractKey as FbsContractKey;
20use crate::parameters::Parameters;
21
22use super::code::ContractCode;
23use super::CONTRACT_KEY_SIZE;
24
25/// The key representing the hash of the contract executable code hash and a set of `parameters`.
26#[serde_as]
27#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
28#[cfg_attr(
29    any(feature = "testing", all(test, any(unix, windows))),
30    derive(arbitrary::Arbitrary)
31)]
32#[repr(transparent)]
33pub struct ContractInstanceId(#[serde_as(as = "[_; CONTRACT_KEY_SIZE]")] [u8; CONTRACT_KEY_SIZE]);
34
35impl ContractInstanceId {
36    pub fn from_params_and_code<'a>(
37        params: impl Borrow<Parameters<'a>>,
38        code: impl Borrow<ContractCode<'a>>,
39    ) -> Self {
40        generate_id(params.borrow(), code.borrow())
41    }
42
43    pub const fn new(key: [u8; CONTRACT_KEY_SIZE]) -> Self {
44        Self(key)
45    }
46
47    /// `Base58` string representation of the `contract id`.
48    pub fn encode(&self) -> String {
49        bs58::encode(self.0)
50            .with_alphabet(bs58::Alphabet::BITCOIN)
51            .into_string()
52    }
53
54    pub fn as_bytes(&self) -> &[u8] {
55        self.0.as_slice()
56    }
57
58    /// Build `ContractId` from the binary representation.
59    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, bs58::decode::Error> {
60        let mut spec = [0; CONTRACT_KEY_SIZE];
61        bs58::decode(bytes)
62            .with_alphabet(bs58::Alphabet::BITCOIN)
63            .onto(&mut spec)?;
64        Ok(Self(spec))
65    }
66}
67
68impl Deref for ContractInstanceId {
69    type Target = [u8; CONTRACT_KEY_SIZE];
70
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl FromStr for ContractInstanceId {
77    type Err = bs58::decode::Error;
78
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        ContractInstanceId::from_bytes(s)
81    }
82}
83
84impl TryFrom<String> for ContractInstanceId {
85    type Error = bs58::decode::Error;
86
87    fn try_from(s: String) -> Result<Self, Self::Error> {
88        ContractInstanceId::from_bytes(s)
89    }
90}
91
92impl Display for ContractInstanceId {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(f, "{}", self.encode())
95    }
96}
97
98impl std::fmt::Debug for ContractInstanceId {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        f.debug_tuple("ContractInstanceId")
101            .field(&self.encode())
102            .finish()
103    }
104}
105
106/// A complete key specification, that represents a cryptographic hash that identifies the contract.
107#[serde_as]
108#[derive(Debug, Eq, Copy, Clone, Serialize, Deserialize)]
109#[cfg_attr(
110    any(feature = "testing", all(test, any(unix, windows))),
111    derive(arbitrary::Arbitrary)
112)]
113pub struct ContractKey {
114    instance: ContractInstanceId,
115    code: Option<CodeHash>,
116}
117
118impl ContractKey {
119    pub fn from_params_and_code<'a>(
120        params: impl Borrow<Parameters<'a>>,
121        wasm_code: impl Borrow<ContractCode<'a>>,
122    ) -> Self {
123        let code = wasm_code.borrow();
124        let id = generate_id(params.borrow(), code);
125        let code_hash = code.hash();
126        Self {
127            instance: id,
128            code: Some(*code_hash),
129        }
130    }
131
132    /// Builds a partial [`ContractKey`](ContractKey), the contract code part is unspecified.
133    pub fn from_id(instance: impl Into<String>) -> Result<Self, bs58::decode::Error> {
134        let instance = ContractInstanceId::try_from(instance.into())?;
135        Ok(Self {
136            instance,
137            code: None,
138        })
139    }
140
141    /// Gets the whole spec key hash.
142    pub fn as_bytes(&self) -> &[u8] {
143        self.instance.0.as_ref()
144    }
145
146    /// Returns the hash of the contract code only, if the key is fully specified.
147    pub fn code_hash(&self) -> Option<&CodeHash> {
148        self.code.as_ref()
149    }
150
151    /// Returns the encoded hash of the contract code, if the key is fully specified.
152    pub fn encoded_code_hash(&self) -> Option<String> {
153        self.code.as_ref().map(|c| {
154            bs58::encode(c.0)
155                .with_alphabet(bs58::Alphabet::BITCOIN)
156                .into_string()
157        })
158    }
159
160    /// Returns the contract key from the encoded hash of the contract code and the given
161    /// parameters.
162    pub fn from_params(
163        code_hash: impl Into<String>,
164        parameters: Parameters,
165    ) -> Result<Self, bs58::decode::Error> {
166        let mut code_key = [0; CONTRACT_KEY_SIZE];
167        bs58::decode(code_hash.into())
168            .with_alphabet(bs58::Alphabet::BITCOIN)
169            .onto(&mut code_key)?;
170
171        let mut hasher = Blake3::new();
172        hasher.update(code_key.as_slice());
173        hasher.update(parameters.as_ref());
174        let full_key_arr = hasher.finalize();
175
176        let mut spec = [0; CONTRACT_KEY_SIZE];
177        spec.copy_from_slice(&full_key_arr);
178        Ok(Self {
179            instance: ContractInstanceId(spec),
180            code: Some(CodeHash(code_key)),
181        })
182    }
183
184    /// Returns the `Base58` encoded string of the [`ContractInstanceId`](ContractInstanceId).
185    pub fn encoded_contract_id(&self) -> String {
186        self.instance.encode()
187    }
188
189    pub fn id(&self) -> &ContractInstanceId {
190        &self.instance
191    }
192}
193
194impl PartialEq for ContractKey {
195    fn eq(&self, other: &Self) -> bool {
196        self.instance == other.instance
197    }
198}
199
200impl std::hash::Hash for ContractKey {
201    fn hash<H: Hasher>(&self, state: &mut H) {
202        self.instance.0.hash(state);
203    }
204}
205
206impl From<ContractInstanceId> for ContractKey {
207    fn from(instance: ContractInstanceId) -> Self {
208        Self {
209            instance,
210            code: None,
211        }
212    }
213}
214
215impl From<ContractKey> for ContractInstanceId {
216    fn from(key: ContractKey) -> Self {
217        key.instance
218    }
219}
220
221impl Deref for ContractKey {
222    type Target = [u8; CONTRACT_KEY_SIZE];
223
224    fn deref(&self) -> &Self::Target {
225        &self.instance.0
226    }
227}
228
229impl std::fmt::Display for ContractKey {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        self.instance.fmt(f)
232    }
233}
234
235impl<'a> TryFromFbs<&FbsContractKey<'a>> for ContractKey {
236    fn try_decode_fbs(key: &FbsContractKey<'a>) -> Result<Self, WsApiError> {
237        let key_bytes: [u8; CONTRACT_KEY_SIZE] = key.instance().data().bytes().try_into().unwrap();
238        let instance = ContractInstanceId::new(key_bytes);
239        let code = key
240            .code()
241            .map(|code_hash| CodeHash::from_code(code_hash.bytes()));
242        Ok(ContractKey { instance, code })
243    }
244}
245
246fn generate_id<'a>(
247    parameters: &Parameters<'a>,
248    code_data: &ContractCode<'a>,
249) -> ContractInstanceId {
250    let contract_hash = code_data.hash();
251
252    let mut hasher = Blake3::new();
253    hasher.update(contract_hash.0.as_slice());
254    hasher.update(parameters.as_ref());
255    let full_key_arr = hasher.finalize();
256
257    debug_assert_eq!(full_key_arr[..].len(), CONTRACT_KEY_SIZE);
258    let mut spec = [0; CONTRACT_KEY_SIZE];
259    spec.copy_from_slice(&full_key_arr);
260    ContractInstanceId(spec)
261}
262
263#[inline]
264pub(super) fn internal_fmt_key(
265    key: &[u8; CONTRACT_KEY_SIZE],
266    f: &mut std::fmt::Formatter<'_>,
267) -> std::fmt::Result {
268    let r = bs58::encode(key)
269        .with_alphabet(bs58::Alphabet::BITCOIN)
270        .into_string();
271    write!(f, "{}", &r[..8])
272}