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