freenet_stdlib/contract_interface/
key.rs1use 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#[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 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 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#[serde_as]
113#[derive(Debug, Eq, Copy, Clone, Serialize, Deserialize)]
114#[cfg_attr(
115 any(feature = "testing", all(test, any(unix, windows))),
116 derive(arbitrary::Arbitrary)
117)]
118pub struct ContractKey {
119 instance: ContractInstanceId,
120 code: CodeHash,
121}
122
123impl ContractKey {
124 pub fn from_params_and_code<'a>(
125 params: impl Borrow<Parameters<'a>>,
126 wasm_code: impl Borrow<ContractCode<'a>>,
127 ) -> Self {
128 let code = wasm_code.borrow();
129 let id = generate_id(params.borrow(), code);
130 let code_hash = *code.hash();
131 Self {
132 instance: id,
133 code: code_hash,
134 }
135 }
136
137 pub fn as_bytes(&self) -> &[u8] {
139 self.instance.0.as_ref()
140 }
141
142 pub fn code_hash(&self) -> &CodeHash {
144 &self.code
145 }
146
147 pub fn encoded_code_hash(&self) -> String {
149 bs58::encode(self.code.0)
150 .with_alphabet(bs58::Alphabet::BITCOIN)
151 .into_string()
152 }
153
154 pub fn from_params(
157 code_hash: impl Into<String>,
158 parameters: Parameters,
159 ) -> Result<Self, bs58::decode::Error> {
160 let mut code_key = [0; CONTRACT_KEY_SIZE];
161 bs58::decode(code_hash.into())
162 .with_alphabet(bs58::Alphabet::BITCOIN)
163 .onto(&mut code_key)?;
164
165 let mut hasher = Blake3::new();
166 hasher.update(code_key.as_slice());
167 hasher.update(parameters.as_ref());
168 let full_key_arr = hasher.finalize();
169
170 let mut spec = [0; CONTRACT_KEY_SIZE];
171 spec.copy_from_slice(&full_key_arr);
172 Ok(Self {
173 instance: ContractInstanceId(spec),
174 code: CodeHash(code_key),
175 })
176 }
177
178 pub fn encoded_contract_id(&self) -> String {
180 self.instance.encode()
181 }
182
183 pub fn id(&self) -> &ContractInstanceId {
184 &self.instance
185 }
186}
187
188impl PartialEq for ContractKey {
189 fn eq(&self, other: &Self) -> bool {
190 self.instance == other.instance
191 }
192}
193
194impl std::hash::Hash for ContractKey {
195 fn hash<H: Hasher>(&self, state: &mut H) {
196 self.instance.0.hash(state);
197 }
198}
199
200
201impl From<ContractKey> for ContractInstanceId {
202 fn from(key: ContractKey) -> Self {
203 key.instance
204 }
205}
206
207impl Deref for ContractKey {
208 type Target = [u8; CONTRACT_KEY_SIZE];
209
210 fn deref(&self) -> &Self::Target {
211 &self.instance.0
212 }
213}
214
215impl std::fmt::Display for ContractKey {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 self.instance.fmt(f)
218 }
219}
220
221impl<'a> TryFromFbs<&FbsContractKey<'a>> for ContractKey {
222 fn try_decode_fbs(key: &FbsContractKey<'a>) -> Result<Self, WsApiError> {
223 let key_bytes: [u8; CONTRACT_KEY_SIZE] = key.instance().data().bytes().try_into().unwrap();
224 let instance = ContractInstanceId::new(key_bytes);
225 let code = key
226 .code()
227 .map(|code_hash| CodeHash::from_code(code_hash.bytes()))
228 .ok_or_else(|| WsApiError::deserialization("ContractKey missing code hash".into()))?;
229 Ok(ContractKey { instance, code })
230 }
231}
232
233fn generate_id<'a>(
234 parameters: &Parameters<'a>,
235 code_data: &ContractCode<'a>,
236) -> ContractInstanceId {
237 let contract_hash = code_data.hash();
238
239 let mut hasher = Blake3::new();
240 hasher.update(contract_hash.0.as_slice());
241 hasher.update(parameters.as_ref());
242 let full_key_arr = hasher.finalize();
243
244 debug_assert_eq!(full_key_arr[..].len(), CONTRACT_KEY_SIZE);
245 let mut spec = [0; CONTRACT_KEY_SIZE];
246 spec.copy_from_slice(&full_key_arr);
247 ContractInstanceId(spec)
248}
249
250#[inline]
251pub(super) fn internal_fmt_key(
252 key: &[u8; CONTRACT_KEY_SIZE],
253 f: &mut std::fmt::Formatter<'_>,
254) -> std::fmt::Result {
255 let r = bs58::encode(key)
256 .with_alphabet(bs58::Alphabet::BITCOIN)
257 .into_string();
258 write!(f, "{}", &r[..8])
259}