1use cosmwasm_std::{Addr, Api, BlockInfo, CanonicalAddr, ContractInfo, Empty, Env, OwnedDeps, Querier, RecoverPubkeyError, StdError, StdResult, Timestamp, VerificationError, Order, Storage, Uint128, Response};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use serde_json::to_string;
5use serde_with::serde_as;
6use std::collections::HashMap;
7use std::marker::PhantomData;
8use wasm_bindgen::{JsValue, JsError};
9
10mod memory_storage;
11pub use memory_storage::MemoryStorage;
12
13const CANONICAL_LENGTH: usize = 54;
14
15pub fn create_env() -> Env {
16 create_ownable_env(String::new(), None)
17}
18
19pub fn create_ownable_env(chain_id: impl Into<String>, time: Option<Timestamp>) -> Env {
20 Env {
21 block: BlockInfo {
22 height: 0,
23 time: time.unwrap_or_else(|| Timestamp::from_seconds(0)),
24 chain_id: chain_id.into(),
25 },
26 contract: ContractInfo {
27 address: Addr::unchecked(""),
28 },
29 transaction: None,
30 }
31}
32
33pub fn package_title_from_name(name: &str) -> String {
36 name
37 .trim_start_matches("ownable-")
38 .split(['-', '_'])
39 .filter(|part| !part.is_empty())
40 .map(|part| {
41 let mut chars = part.chars();
42 match chars.next() {
43 Some(first) => format!("{}{}", first.to_ascii_uppercase(), chars.as_str()),
44 None => String::new(),
45 }
46 })
47 .collect::<Vec<_>>()
48 .join(" ")
49}
50
51pub fn load_owned_deps(state_dump: Option<IdbStateDump>) -> OwnedDeps<MemoryStorage, EmptyApi, EmptyQuerier, Empty> {
52 match state_dump {
53 None => OwnedDeps {
54 storage: MemoryStorage::default(),
55 api: EmptyApi::default(),
56 querier: EmptyQuerier::default(),
57 custom_query_type: PhantomData,
58 },
59 Some(dump) => {
60 let idb_storage = IdbStorage::load(dump);
61 OwnedDeps {
62 storage: idb_storage.storage,
63 api: EmptyApi::default(),
64 querier: EmptyQuerier::default(),
65 custom_query_type: PhantomData,
66 }
67 }
68 }
69}
70
71pub fn get_random_color(hash: String) -> String {
73 let (red, green, blue) = derive_rgb_values(hash);
74 rgb_hex(red, green, blue)
75}
76
77
78pub fn derive_rgb_values(hash: String) -> (u8, u8, u8) {
80 let mut s = hash.trim().trim_start_matches("0x").to_string();
82 if s.len() % 2 == 1 {
83 s.insert(0, '0');
84 }
85
86 match hex::decode(&s) {
87 Ok(mut bytes) => {
88 bytes.reverse();
89 let r = *bytes.get(0).unwrap_or(&0);
90 let g = *bytes.get(1).unwrap_or(&0);
91 let b = *bytes.get(2).unwrap_or(&0);
92 (r, g, b)
93 }
94 Err(_) => (0, 0, 0),
95 }
96}
97
98pub fn rgb_hex(r: u8, g: u8, b: u8) -> String {
101 format!("#{:02X}{:02X}{:02X}", r, g, b)
102}
103
104pub fn get_json_response(storage: MemoryStorage, response: Response) -> Result<JsValue, JsError> {
108 let state_dump= IdbStateDump::from(storage);
109 let ownable_state = to_string(&response)?;
110 let response_map = js_sys::Map::new();
111 response_map.set(
112 &JsValue::from_str("mem"),
113 &JsValue::from(to_string(&state_dump)?)
114 );
115 response_map.set(
116 &JsValue::from_str("result"),
117 &JsValue::from(ownable_state)
118 );
119 Ok(JsValue::from(response_map))
120}
121
122pub struct IdbStorage {
123 pub storage: MemoryStorage,
124}
125
126impl IdbStorage {
127 pub fn load(idb: IdbStateDump) -> Self {
128 let mut store = IdbStorage {
129 storage: MemoryStorage::new(),
130 };
131 store.load_to_mem_storage(idb);
132 store
133 }
134
135 pub fn load_to_mem_storage(&mut self, idb_state: IdbStateDump) {
137 for (k, v) in idb_state.state_dump.into_iter() {
138 self.storage.set(&k, &v);
139 }
140 }
141}
142
143#[serde_as]
144#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
145pub struct IdbStateDump {
146 #[serde_as(as = "Vec<(_, _)>")]
148 pub state_dump: HashMap<Vec<u8>, Vec<u8>>,
149}
150
151impl IdbStateDump {
152 pub fn from(store: MemoryStorage) -> IdbStateDump {
154 let mut state: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
155
156 for (key, value) in store.range(None,None, Order::Ascending) {
157 state.insert(key, value);
158 }
159 IdbStateDump {
160 state_dump: state,
161 }
162 }
163}
164
165#[derive(Copy, Clone)]
167pub struct EmptyApi {
168 canonical_length: usize,
171}
172
173impl Default for EmptyApi {
174 fn default() -> Self {
175 EmptyApi {
176 canonical_length: CANONICAL_LENGTH,
177 }
178 }
179}
180
181impl Api for EmptyApi {
182 fn addr_validate(&self, human: &str) -> StdResult<Addr> {
183 self.addr_canonicalize(human).map(|_canonical| ())?;
184 Ok(Addr::unchecked(human))
185 }
186
187 fn addr_canonicalize(&self, human: &str) -> StdResult<CanonicalAddr> {
188 if human.len() < 3 {
190 return Err(StdError::msg(
191 "Invalid input: human address too short",
192 ));
193 }
194 if human.len() > self.canonical_length {
195 return Err(StdError::msg(
196 "Invalid input: human address too long",
197 ));
198 }
199
200 let mut out = Vec::from(human);
201
202 out.resize(self.canonical_length, 0x00);
204 Ok(out.into())
212 }
213
214 fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
215 if canonical.len() != self.canonical_length {
216 return Err(StdError::msg(
217 "Invalid input: canonical address length not correct",
218 ));
219 }
220
221 let tmp: Vec<u8> = canonical.clone().into();
222 let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect();
231 let human = String::from_utf8(trimmed)?;
233 Ok(Addr::unchecked(human))
234 }
235
236 fn secp256k1_verify(
237 &self,
238 _message_hash: &[u8],
239 _signature: &[u8],
240 _public_key: &[u8],
241 ) -> Result<bool, VerificationError> {
242 Err(VerificationError::unknown_err(0))
243 }
244
245 fn secp256k1_recover_pubkey(
246 &self,
247 _message_hash: &[u8],
248 _signature: &[u8],
249 _recovery_param: u8,
250 ) -> Result<Vec<u8>, RecoverPubkeyError> {
251 Err(RecoverPubkeyError::unknown_err(0))
252 }
253
254 fn ed25519_verify(
255 &self,
256 _message: &[u8],
257 _signature: &[u8],
258 _public_key: &[u8],
259 ) -> Result<bool, VerificationError> {
260 Ok(true)
261 }
262
263 fn ed25519_batch_verify(
264 &self,
265 _messages: &[&[u8]],
266 _signatures: &[&[u8]],
267 _public_keys: &[&[u8]],
268 ) -> Result<bool, VerificationError> {
269 Ok(true)
270 }
271
272 fn debug(&self, message: &str) {
273 println!("{}", message);
274 }
275}
276
277#[derive(Default)]
279pub struct EmptyQuerier {}
280
281impl Querier for EmptyQuerier {
282 fn raw_query(&self, _bin_request: &[u8]) -> cosmwasm_std::QuerierResult {
283 todo!()
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
294 fn rgb_hex_formats_correctly() {
295 assert_eq!(rgb_hex(0, 0, 0), "#000000");
296 assert_eq!(rgb_hex(255, 255, 255), "#FFFFFF");
297 assert_eq!(rgb_hex(255, 0, 0), "#FF0000");
298 assert_eq!(rgb_hex(0, 128, 255), "#0080FF");
299 }
300
301 #[test]
304 fn derive_rgb_values_reads_last_three_bytes_reversed() {
305 assert_eq!(derive_rgb_values("010203".to_string()), (3, 2, 1));
307 }
308
309 #[test]
310 fn derive_rgb_values_strips_0x_prefix() {
311 assert_eq!(
312 derive_rgb_values("0x010203".to_string()),
313 derive_rgb_values("010203".to_string())
314 );
315 }
316
317 #[test]
318 fn derive_rgb_values_pads_odd_length_input() {
319 assert_eq!(derive_rgb_values("abc".to_string()), (0xbc, 0x0a, 0));
321 }
322
323 #[test]
324 fn derive_rgb_values_returns_zeros_for_invalid_hex() {
325 assert_eq!(derive_rgb_values("xyz".to_string()), (0, 0, 0));
326 }
327
328 #[test]
329 fn derive_rgb_values_returns_zeros_for_empty_input() {
330 assert_eq!(derive_rgb_values("".to_string()), (0, 0, 0));
331 }
332
333 #[test]
334 fn derive_rgb_values_uses_last_three_bytes_of_long_input() {
335 assert_eq!(
339 derive_rgb_values("aabbccdd11223344".to_string()),
340 (0x44, 0x33, 0x22)
341 );
342 }
343
344 #[test]
347 fn get_random_color_returns_hash_prefixed_hex() {
348 let color = get_random_color("010203".to_string());
349 assert!(color.starts_with('#'));
350 assert_eq!(color.len(), 7);
351 }
352
353 #[test]
354 fn get_random_color_is_deterministic() {
355 let hash = "deadbeef".to_string();
356 assert_eq!(get_random_color(hash.clone()), get_random_color(hash));
357 }
358
359 #[test]
362 fn idb_state_dump_round_trips_through_storage() {
363 let mut storage = MemoryStorage::new();
364 storage.set(b"key1", b"value1");
365 storage.set(b"key2", b"value2");
366
367 let dump = IdbStateDump::from(storage);
368 assert_eq!(dump.state_dump.get(b"key1".as_ref()), Some(&b"value1".to_vec()));
369 assert_eq!(dump.state_dump.get(b"key2".as_ref()), Some(&b"value2".to_vec()));
370 }
371
372 #[test]
373 fn idb_storage_load_restores_all_keys() {
374 let mut storage = MemoryStorage::new();
375 storage.set(b"foo", b"bar");
376 storage.set(b"baz", b"qux");
377
378 let dump = IdbStateDump::from(storage);
379 let loaded = IdbStorage::load(dump);
380
381 assert_eq!(loaded.storage.get(b"foo"), Some(b"bar".to_vec()));
382 assert_eq!(loaded.storage.get(b"baz"), Some(b"qux".to_vec()));
383 }
384
385 #[test]
386 fn idb_state_dump_empty_storage_produces_empty_map() {
387 let storage = MemoryStorage::new();
388 let dump = IdbStateDump::from(storage);
389 assert!(dump.state_dump.is_empty());
390 }
391
392 #[test]
395 fn create_env_produces_default_env() {
396 let env = create_env();
397 assert_eq!(env.block.height, 0);
398 assert_eq!(env.block.chain_id, "");
399 }
400
401 #[test]
402 fn create_ownable_env_sets_chain_id() {
403 let env = create_ownable_env("my-chain", None);
404 assert_eq!(env.block.chain_id, "my-chain");
405 }
406
407 #[test]
408 fn create_ownable_env_sets_timestamp() {
409 use cosmwasm_std::Timestamp;
410 let ts = Timestamp::from_seconds(12345);
411 let env = create_ownable_env("", Some(ts));
412 assert_eq!(env.block.time, ts);
413 }
414}
415
416#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
418pub struct Metadata {
419 pub image: Option<String>,
420 pub image_data: Option<String>,
421 pub external_url: Option<String>,
422 pub description: Option<String>,
423 pub name: Option<String>,
424 pub background_color: Option<String>,
426 pub animation_url: Option<String>,
427 pub youtube_url: Option<String>,
428}
429
430#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
431#[serde(rename_all = "snake_case")]
432pub struct ExternalEventMsg {
433 pub network: Option<String>,
436 pub event_type: String,
437 pub attributes: HashMap<String, String>,
438}
439
440#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
441pub struct OwnableInfo {
442 pub owner: Addr,
443 pub issuer: Addr,
444 pub ownable_type: Option<String>,
445}
446
447#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
448pub struct NFT {
449 pub network: String, pub id: Uint128,
451 pub address: String, pub lock_service: Option<String>,
453}
454
455#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
456pub struct InfoResponse {
457 pub owner: Addr,
458 pub issuer: Addr,
459 pub nft: Option<NFT>,
460 pub ownable_type: Option<String>,
461}