ownable_std/
lib.rs

1use cosmwasm_std::{Addr, Api, BlockInfo, CanonicalAddr, ContractInfo, Empty, Env, MemoryStorage, 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
10const CANONICAL_LENGTH: usize = 54;
11
12pub fn create_env() -> Env {
13    create_ownable_env(String::new(), None)
14}
15
16pub fn create_ownable_env(chain_id: impl Into<String>, time: Option<Timestamp>) -> Env {
17    Env {
18        block: BlockInfo {
19            height: 0,
20            time: time.unwrap_or_else(|| Timestamp::from_seconds(0)),
21            chain_id: chain_id.into(),
22        },
23        contract: ContractInfo {
24            address: Addr::unchecked(""),
25        },
26        transaction: None,
27    }
28}
29
30pub fn load_owned_deps(state_dump: Option<IdbStateDump>) -> OwnedDeps<MemoryStorage, EmptyApi, EmptyQuerier, Empty> {
31    match state_dump {
32        None => OwnedDeps {
33            storage: MemoryStorage::default(),
34            api: EmptyApi::default(),
35            querier: EmptyQuerier::default(),
36            custom_query_type: PhantomData,
37        },
38        Some(dump) => {
39            let idb_storage = IdbStorage::load(dump);
40            OwnedDeps {
41                storage: idb_storage.storage,
42                api: EmptyApi::default(),
43                querier: EmptyQuerier::default(),
44                custom_query_type: PhantomData,
45            }
46        }
47    }
48}
49
50/// returns a hex color in string format from a hash
51pub fn get_random_color(hash: String) -> String {
52    let (red, green, blue) = derive_rgb_values(hash);
53    rgb_hex(red, green, blue)
54}
55
56
57/// takes a hex-encoded hash and derives a seemingly-random rgb tuple
58pub fn derive_rgb_values(hash: String) -> (u8, u8, u8) {
59    // allow optional 0x and odd length
60    let mut s = hash.trim().trim_start_matches("0x").to_string();
61    if s.len() % 2 == 1 {
62        s.insert(0, '0');
63    }
64
65    match hex::decode(&s) {
66        Ok(mut bytes) => {
67            bytes.reverse();
68            let r = *bytes.get(0).unwrap_or(&0);
69            let g = *bytes.get(1).unwrap_or(&0);
70            let b = *bytes.get(2).unwrap_or(&0);
71            (r, g, b)
72        }
73        Err(_) => (0, 0, 0),
74    }
75}
76
77/// takes three u8 values representing rgb values (0-255)f
78/// and returns a hex string
79pub fn rgb_hex(r: u8, g: u8, b: u8) -> String {
80    format!("#{:02X}{:02X}{:02X}", r, g, b)
81}
82
83/// takes a cw MemoryStorage and Response and returns a JsValue
84/// response that contains the memory state dump and response
85/// result
86pub fn get_json_response(storage: MemoryStorage, response: Response) -> Result<JsValue, JsError> {
87    let state_dump= IdbStateDump::from(storage);
88    let ownable_state = to_string(&response)?;
89    let response_map = js_sys::Map::new();
90    response_map.set(
91        &JsValue::from_str("mem"),
92        &JsValue::from(to_string(&state_dump)?)
93    );
94    response_map.set(
95        &JsValue::from_str("result"),
96        &JsValue::from(ownable_state)
97    );
98    Ok(JsValue::from(response_map))
99}
100
101pub struct IdbStorage {
102    pub storage: MemoryStorage,
103}
104
105impl IdbStorage {
106    pub fn load(idb: IdbStateDump) -> Self {
107        let mut store = IdbStorage {
108            storage: MemoryStorage::new(),
109        };
110        store.load_to_mem_storage(idb);
111        store
112    }
113
114    /// takes a IdbStateDump and loads the values into MemoryStorage
115    pub fn load_to_mem_storage(&mut self, idb_state: IdbStateDump) {
116        for (k, v) in idb_state.state_dump.into_iter() {
117            self.storage.set(&k, &v);
118        }
119    }
120}
121
122#[serde_as]
123#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
124pub struct IdbStateDump {
125    // map of the indexed db key value pairs of the state object store
126    #[serde_as(as = "Vec<(_, _)>")]
127    pub state_dump: HashMap<Vec<u8>, Vec<u8>>,
128}
129
130impl IdbStateDump {
131    /// generates a state dump from all key-value pairs in MemoryStorage
132    pub fn from(store: MemoryStorage) -> IdbStateDump {
133        let mut state: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
134
135        for (key, value) in store.range(None,None, Order::Ascending) {
136            state.insert(key, value);
137        }
138        IdbStateDump {
139            state_dump: state,
140        }
141    }
142}
143
144// EmptyApi that is meant to conform the traits by the cosmwasm standard contract syntax. The functions of this implementation are not meant to be used or produce any sensible results.
145#[derive(Copy, Clone)]
146pub struct EmptyApi {
147    /// Length of canonical addresses created with this API. Contracts should not make any assumtions
148    /// what this value is.
149    canonical_length: usize,
150}
151
152impl Default for EmptyApi {
153    fn default() -> Self {
154        EmptyApi {
155            canonical_length: CANONICAL_LENGTH,
156        }
157    }
158}
159
160impl Api for EmptyApi {
161    fn addr_validate(&self, human: &str) -> StdResult<Addr> {
162        self.addr_canonicalize(human).map(|_canonical| ())?;
163        Ok(Addr::unchecked(human))
164    }
165
166    fn addr_canonicalize(&self, human: &str) -> StdResult<CanonicalAddr> {
167        // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated.
168        if human.len() < 3 {
169            return Err(StdError::generic_err(
170                "Invalid input: human address too short",
171            ));
172        }
173        if human.len() > self.canonical_length {
174            return Err(StdError::generic_err(
175                "Invalid input: human address too long",
176            ));
177        }
178
179        let mut out = Vec::from(human);
180
181        // pad to canonical length with NULL bytes
182        out.resize(self.canonical_length, 0x00);
183        // // content-dependent rotate followed by shuffle to destroy
184        // // the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552)
185        // let rotate_by = digit_sum(&out) % self.canonical_length;
186        // out.rotate_left(rotate_by);
187        // for _ in 0..SHUFFLES_ENCODE {
188        //     out = riffle_shuffle(&out);
189        // }
190        Ok(out.into())
191    }
192
193    fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
194        if canonical.len() != self.canonical_length {
195            return Err(StdError::generic_err(
196                "Invalid input: canonical address length not correct",
197            ));
198        }
199
200        let tmp: Vec<u8> = canonical.clone().into();
201        // // Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds)
202        // for _ in 0..SHUFFLES_DECODE {
203        //     tmp = riffle_shuffle(&tmp);
204        // }
205        // // Rotate back
206        // let rotate_by = digit_sum(&tmp) % self.canonical_length;
207        // tmp.rotate_right(rotate_by);
208        // Remove NULL bytes (i.e. the padding)
209        let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect();
210        // decode UTF-8 bytes into string
211        let human = String::from_utf8(trimmed)?;
212        Ok(Addr::unchecked(human))
213    }
214
215    fn secp256k1_verify(
216        &self,
217        _message_hash: &[u8],
218        _signature: &[u8],
219        _public_key: &[u8],
220    ) -> Result<bool, VerificationError> {
221        Err(VerificationError::unknown_err(0))
222    }
223
224    fn secp256k1_recover_pubkey(
225        &self,
226        _message_hash: &[u8],
227        _signature: &[u8],
228        _recovery_param: u8,
229    ) -> Result<Vec<u8>, RecoverPubkeyError> {
230        Err(RecoverPubkeyError::unknown_err(0))
231    }
232
233    fn ed25519_verify(
234        &self,
235        _message: &[u8],
236        _signature: &[u8],
237        _public_key: &[u8],
238    ) -> Result<bool, VerificationError> {
239        Ok(true)
240    }
241
242    fn ed25519_batch_verify(
243        &self,
244        _messages: &[&[u8]],
245        _signatures: &[&[u8]],
246        _public_keys: &[&[u8]],
247    ) -> Result<bool, VerificationError> {
248        Ok(true)
249    }
250
251    fn debug(&self, message: &str) {
252        println!("{}", message);
253    }
254}
255
256/// Empty Querier that is meant to conform the traits expected by the cosmwasm standard contract syntax. It should not be used whatsoever
257#[derive(Default)]
258pub struct EmptyQuerier {}
259
260impl Querier for EmptyQuerier {
261    fn raw_query(&self, _bin_request: &[u8]) -> cosmwasm_std::QuerierResult {
262        todo!()
263    }
264}
265
266// from github.com/CosmWasm/cw-nfts/blob/main/contracts/cw721-metadata-onchain
267#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
268pub struct Metadata {
269    pub image: Option<String>,
270    pub image_data: Option<String>,
271    pub external_url: Option<String>,
272    pub description: Option<String>,
273    pub name: Option<String>,
274    // pub attributes: Option<Vec<Trait>>,
275    pub background_color: Option<String>,
276    pub animation_url: Option<String>,
277    pub youtube_url: Option<String>,
278}
279
280#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
281#[serde(rename_all = "snake_case")]
282pub struct ExternalEventMsg {
283    // CAIP-2 format: <namespace + ":" + reference>
284    // e.g. ethereum: eip155:1
285    pub network: Option<String>,
286    pub event_type: String,
287    pub attributes: HashMap<String, String>,
288}
289
290#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
291pub struct OwnableInfo {
292    pub owner: Addr,
293    pub issuer: Addr,
294    pub ownable_type: Option<String>,
295}
296
297#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
298pub struct NFT {
299    pub network: String,    // eip155:1
300    pub id: Uint128,
301    pub address: String, // 0x341...
302    pub lock_service: Option<String>,
303}
304
305#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
306pub struct InfoResponse {
307    pub owner: Addr,
308    pub issuer: Addr,
309    pub nft: Option<NFT>,
310    pub ownable_type: Option<String>,
311}