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 wasm_bindgen::{JsValue, JsError};
7use std::collections::HashMap;
8use std::marker::PhantomData;
9use blake2::Blake2bVar;
10use blake2::digest::{Update, VariableOutput};
11use sha2::Digest as Sha2Digest;
12use sha3::{Digest};
13
14const CANONICAL_LENGTH: usize = 54;
15
16pub fn create_lto_env() -> Env {
17    Env {
18        block: BlockInfo {
19            height: 0,
20            time: Timestamp::from_seconds(0),
21            chain_id: "lto".to_string(),
22        },
23        contract: ContractInfo {
24            address: Addr::unchecked(""),
25        },
26        transaction: None,
27    }
28}
29
30pub fn load_lto_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
51/// takes a b58 of compressed secp256k1 public key and returns an address.
52/// performs validation on the pk, including length checks, bs58 decoding,
53/// compression check. also does eip55 checksum validation.
54pub fn address_eip155(public_key: String) -> Result<Addr, StdError> {
55    if public_key.is_empty() {
56        return Err(StdError::not_found("empty input"));
57    }
58
59    // decode b58 pk
60    let pk = bs58::decode(public_key.as_bytes()).into_vec();
61    let decoded_pk = match pk {
62        Ok(pk) => pk,
63        Err(e) => return Err(StdError::generic_err(e.to_string())),
64    };
65
66    // instantiate secp256k1 public key from input
67    let public_key = secp256k1::PublicKey::from_slice(decoded_pk.as_slice()).unwrap();
68    let mut uncompressed_hex_pk = hex::encode(public_key.serialize_uncompressed());
69    if uncompressed_hex_pk.starts_with("04") {
70        uncompressed_hex_pk = uncompressed_hex_pk.split_off(2);
71    }
72
73    // pass the raw bytes to keccak256
74    let uncompressed_raw_pk = hex::decode(uncompressed_hex_pk).unwrap();
75
76    let mut hasher = sha3::Keccak256::new();
77    hasher.input(uncompressed_raw_pk.as_slice());
78    let hashed_addr = hex::encode(hasher.result().as_slice()).to_string();
79
80    let result = &hashed_addr[hashed_addr.len() - 40..];
81    let checksum_addr = "0x".to_owned() + eip_55_checksum(result).as_str();
82
83    Ok(Addr::unchecked(checksum_addr))
84}
85
86fn eip_55_checksum(addr: &str) -> String {
87    let mut checksum_hasher = sha3::Keccak256::new();
88    checksum_hasher.input(&addr[addr.len() - 40..].as_bytes());
89    let hashed_addr = hex::encode(checksum_hasher.result()).to_string();
90
91    let mut checksum_buff = "".to_owned();
92    let result_chars: Vec<char> = addr.chars()
93        .into_iter()
94        .collect();
95    let keccak_chars: Vec<char> = hashed_addr.chars()
96        .into_iter()
97        .collect();
98    for i in 0..addr.len() {
99        let mut char = result_chars[i];
100        if char.is_alphabetic() {
101            let keccak_digit = keccak_chars[i]
102                .to_digit(16)
103                .unwrap();
104            // if the corresponding hex digit >= 8, convert to uppercase
105            if keccak_digit >= 8 {
106                char = char.to_ascii_uppercase();
107            }
108        }
109        checksum_buff += char.to_string().as_str();
110    }
111
112    checksum_buff
113}
114
115/// takes a network_id (T/L) and a b58 encoded public key and
116/// returns a base58 encoded address.
117/// performs validations on the network_id, public key encoding,
118/// and expects sha256(Blake2b(data)) to process correctly.
119pub fn address_lto(network_id: char, public_key: String) -> Result<Addr, StdError> {
120    if network_id != 'L' && network_id != 'T' {
121        return Err(StdError::generic_err("unrecognized network_id"));
122    }
123    if bs58::decode(public_key.clone()).into_vec().is_err() {
124        return Err(StdError::generic_err("invalid public key"));
125    }
126
127    // decode b58 of pubkey into byte array
128    let public_key = bs58::decode(public_key).into_vec().unwrap();
129    // get the ascii value from network char
130    let network_id = network_id as u8;
131    let pub_key_secure_hash = secure_hash(public_key.as_slice());
132    // get the first 20 bytes of the securehash
133    let address_bytes = &pub_key_secure_hash[0..20];
134    let version = &1_u8.to_be_bytes();
135    let checksum_input:Vec<u8> = [version, &[network_id], address_bytes].concat();
136
137    // checksum is the first 4 bytes of secureHash of version, chain_id, and hash
138    let checksum = &secure_hash(checksum_input.as_slice())
139        .to_vec()[0..4];
140
141    let addr_fields = [
142        version,
143        &[network_id],
144        address_bytes,
145        checksum
146    ];
147
148    let address: Vec<u8> = addr_fields.concat();
149    Ok(Addr::unchecked(base58(address.as_slice())))
150}
151
152fn base58(input: &[u8]) -> String {
153    bs58::encode(input).into_string()
154}
155
156fn secure_hash(m: &[u8]) -> Vec<u8> {
157    let mut hasher = Blake2bVar::new(32).unwrap();
158    hasher.update(m);
159    let mut buf = [0u8; 32];
160    hasher.finalize_variable(&mut buf).unwrap();
161
162    // get the sha256 of blake
163    let mut sha256_hasher = sha2::Sha256::new();
164    Update::update(&mut sha256_hasher, buf.as_slice());
165    let res = sha256_hasher.finalize();
166    // let mut hasher = sha2::Sha256::new();
167    // hasher.update(&buf);
168    // let mut buf = hasher.finalize();
169    res.to_vec()
170}
171
172/// returns a hex color in string format from a hash
173pub fn get_random_color(hash: String) -> String {
174    let (red, green, blue) = derive_rgb_values(hash);
175    rgb_hex(red, green, blue)
176}
177
178/// takes a b58 hash and derives a seemingly-random rgb tuple
179pub fn derive_rgb_values(hash: String) -> (u8, u8, u8) {
180    let mut decoded_hash = bs58::decode(&hash).into_vec().unwrap();
181    decoded_hash.reverse();
182    (decoded_hash[0], decoded_hash[1], decoded_hash[2])
183}
184
185/// takes three u8 values representing rgb values (0-255)
186/// and returns a hex string
187pub fn rgb_hex(r: u8, g: u8, b: u8) -> String {
188    format!("#{:02X}{:02X}{:02X}", r, g, b)
189}
190
191/// takes a cw MemoryStorage and Response and returns a JsValue
192/// response that contains the memory state dump and response
193/// result
194pub fn get_json_response(storage: MemoryStorage, response: Response) -> Result<JsValue, JsError> {
195    let state_dump= IdbStateDump::from(storage);
196    let ownable_state = to_string(&response)?;
197    let response_map = js_sys::Map::new();
198    response_map.set(
199        &JsValue::from_str("mem"),
200        &JsValue::from(to_string(&state_dump)?)
201    );
202    response_map.set(
203        &JsValue::from_str("result"),
204        &JsValue::from(ownable_state)
205    );
206    Ok(JsValue::from(response_map))
207}
208
209pub struct IdbStorage {
210    pub storage: MemoryStorage,
211}
212
213impl IdbStorage {
214    pub fn load(idb: IdbStateDump) -> Self {
215        let mut store = IdbStorage {
216            storage: MemoryStorage::new(),
217        };
218        store.load_to_mem_storage(idb);
219        store
220    }
221
222    /// takes a IdbStateDump and loads the values into MemoryStorage
223    pub fn load_to_mem_storage(&mut self, idb_state: IdbStateDump) {
224        for (k, v) in idb_state.state_dump.into_iter() {
225            self.storage.set(&k, &v);
226        }
227    }
228}
229
230#[serde_as]
231#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
232pub struct IdbStateDump {
233    // map of the indexed db key value pairs of the state object store
234    #[serde_as(as = "Vec<(_, _)>")]
235    pub state_dump: HashMap<Vec<u8>, Vec<u8>>,
236}
237
238impl IdbStateDump {
239    /// generates a state dump from all key-value pairs in MemoryStorage
240    pub fn from(store: MemoryStorage) -> IdbStateDump {
241        let mut state: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
242
243        for (key, value) in store.range(None,None, Order::Ascending) {
244            state.insert(key, value);
245        }
246        IdbStateDump {
247            state_dump: state,
248        }
249    }
250}
251
252// 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.
253#[derive(Copy, Clone)]
254pub struct EmptyApi {
255    /// Length of canonical addresses created with this API. Contracts should not make any assumtions
256    /// what this value is.
257    canonical_length: usize,
258}
259
260impl Default for EmptyApi {
261    fn default() -> Self {
262        EmptyApi {
263            canonical_length: CANONICAL_LENGTH,
264        }
265    }
266}
267
268impl Api for EmptyApi {
269    fn addr_validate(&self, human: &str) -> StdResult<Addr> {
270        self.addr_canonicalize(human).map(|_canonical| ())?;
271        Ok(Addr::unchecked(human))
272    }
273
274    fn addr_canonicalize(&self, human: &str) -> StdResult<CanonicalAddr> {
275        // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated.
276        if human.len() < 3 {
277            return Err(StdError::generic_err(
278                "Invalid input: human address too short",
279            ));
280        }
281        if human.len() > self.canonical_length {
282            return Err(StdError::generic_err(
283                "Invalid input: human address too long",
284            ));
285        }
286
287        let mut out = Vec::from(human);
288
289        // pad to canonical length with NULL bytes
290        out.resize(self.canonical_length, 0x00);
291        // // content-dependent rotate followed by shuffle to destroy
292        // // the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552)
293        // let rotate_by = digit_sum(&out) % self.canonical_length;
294        // out.rotate_left(rotate_by);
295        // for _ in 0..SHUFFLES_ENCODE {
296        //     out = riffle_shuffle(&out);
297        // }
298        Ok(out.into())
299    }
300
301    fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
302        if canonical.len() != self.canonical_length {
303            return Err(StdError::generic_err(
304                "Invalid input: canonical address length not correct",
305            ));
306        }
307
308        let tmp: Vec<u8> = canonical.clone().into();
309        // // Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds)
310        // for _ in 0..SHUFFLES_DECODE {
311        //     tmp = riffle_shuffle(&tmp);
312        // }
313        // // Rotate back
314        // let rotate_by = digit_sum(&tmp) % self.canonical_length;
315        // tmp.rotate_right(rotate_by);
316        // Remove NULL bytes (i.e. the padding)
317        let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect();
318        // decode UTF-8 bytes into string
319        let human = String::from_utf8(trimmed)?;
320        Ok(Addr::unchecked(human))
321    }
322
323    fn secp256k1_verify(
324        &self,
325        _message_hash: &[u8],
326        _signature: &[u8],
327        _public_key: &[u8],
328    ) -> Result<bool, VerificationError> {
329        Err(VerificationError::unknown_err(0))
330    }
331
332    fn secp256k1_recover_pubkey(
333        &self,
334        _message_hash: &[u8],
335        _signature: &[u8],
336        _recovery_param: u8,
337    ) -> Result<Vec<u8>, RecoverPubkeyError> {
338        Err(RecoverPubkeyError::unknown_err(0))
339    }
340
341    fn ed25519_verify(
342        &self,
343        _message: &[u8],
344        _signature: &[u8],
345        _public_key: &[u8],
346    ) -> Result<bool, VerificationError> {
347        Ok(true)
348    }
349
350    fn ed25519_batch_verify(
351        &self,
352        _messages: &[&[u8]],
353        _signatures: &[&[u8]],
354        _public_keys: &[&[u8]],
355    ) -> Result<bool, VerificationError> {
356        Ok(true)
357    }
358
359    fn debug(&self, message: &str) {
360        println!("{}", message);
361    }
362}
363
364/// Empty Querier that is meant to conform the traits expected by the cosmwasm standard contract syntax. It should not be used whatsoever
365#[derive(Default)]
366pub struct EmptyQuerier {}
367
368impl Querier for EmptyQuerier {
369    fn raw_query(&self, _bin_request: &[u8]) -> cosmwasm_std::QuerierResult {
370        todo!()
371    }
372}
373
374// from github.com/CosmWasm/cw-nfts/blob/main/contracts/cw721-metadata-onchain
375#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
376pub struct Metadata {
377    pub image: Option<String>,
378    pub image_data: Option<String>,
379    pub external_url: Option<String>,
380    pub description: Option<String>,
381    pub name: Option<String>,
382    // pub attributes: Option<Vec<Trait>>,
383    pub background_color: Option<String>,
384    pub animation_url: Option<String>,
385    pub youtube_url: Option<String>,
386}
387
388#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
389#[serde(rename_all = "snake_case")]
390pub struct ExternalEventMsg {
391    // CAIP-2 format: <namespace + ":" + reference>
392    // e.g. ethereum: eip155:1
393    pub network: Option<String>,
394    pub event_type: String,
395    pub attributes: HashMap<String, String>,
396}
397
398#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
399pub struct OwnableInfo {
400    pub owner: Addr,
401    pub issuer: Addr,
402    pub ownable_type: Option<String>,
403}
404
405#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
406pub struct NFT {
407    pub network: String,    // eip155:1
408    pub id: Uint128,
409    pub address: String, // 0x341...
410    pub lock_service: Option<String>,
411}
412
413#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
414pub struct InfoResponse {
415    pub owner: Addr,
416    pub issuer: Addr,
417    pub nft: Option<NFT>,
418    pub ownable_type: Option<String>,
419}