clone_cw_multi_test/wasm_emulation/api/
mod.rs

1use crate::wasm_emulation::query::gas::{GAS_COST_CANONICALIZE, GAS_COST_HUMANIZE};
2use bech32::{FromBase32, ToBase32, Variant};
3use cosmwasm_std::Addr;
4use cosmwasm_vm::{BackendApi, BackendError, GasInfo};
5use std::ops::AddAssign;
6
7const SHORT_CANON_LEN: usize = 20;
8const LONG_CANON_LEN: usize = 32;
9
10pub fn bytes_from_bech32(address: &str, prefix: &str) -> Result<Vec<u8>, BackendError> {
11    if address.is_empty() {
12        return Err(BackendError::Unknown {
13            msg: "empty address string is not allowed".to_string(),
14        });
15    }
16
17    let (hrp, data, _variant) = bech32::decode(address).map_err(|e| BackendError::Unknown {
18        msg: format!("Invalid Bech32 address : Err {}", e),
19    })?;
20    if hrp != prefix {
21        return Err(BackendError::Unknown {
22            msg: format!("invalid Bech32 prefix; expected {}, got {}", prefix, hrp),
23        });
24    }
25
26    Ok(Vec::<u8>::from_base32(&data).unwrap())
27}
28
29pub const MAX_PREFIX_CHARS: usize = 10;
30// Prefixes are limited to MAX_PREFIX_CHARS chars
31// This allows one to specify a string prefix and still implement Copy
32#[derive(Clone, Copy)]
33pub struct RealApi {
34    pub prefix: [char; MAX_PREFIX_CHARS],
35}
36
37impl RealApi {
38    pub fn new(prefix: &str) -> RealApi {
39        if prefix.len() > MAX_PREFIX_CHARS {
40            panic!("More chars in the prefix than {}", MAX_PREFIX_CHARS);
41        }
42
43        let mut api_prefix = ['\0'; 10];
44        for (i, c) in prefix.chars().enumerate() {
45            api_prefix[i] = c;
46        }
47        Self { prefix: api_prefix }
48    }
49
50    pub fn get_prefix(&self) -> String {
51        let mut prefix = Vec::new();
52
53        for &c in self.prefix.iter() {
54            if c != '\0' {
55                prefix.push(c);
56            }
57        }
58        prefix.iter().collect()
59    }
60
61    pub fn next_address(&self, count: usize) -> Addr {
62        let mut canon = format!("ADDRESS_{}", count).as_bytes().to_vec();
63        canon.resize(SHORT_CANON_LEN, 0);
64        Addr::unchecked(self.addr_humanize(&canon).0.unwrap())
65    }
66
67    pub fn next_contract_address(&self, count: usize) -> Addr {
68        let mut canon = format!("CONTRACT_{}", count).as_bytes().to_vec();
69        canon.resize(LONG_CANON_LEN, 0);
70        Addr::unchecked(self.addr_humanize(&canon).0.unwrap())
71    }
72}
73
74impl BackendApi for RealApi {
75    fn addr_validate(&self, input: &str) -> cosmwasm_vm::BackendResult<()> {
76        let (canon, mut gas_cost) = self.addr_canonicalize(input);
77
78        if let Err(e) = canon {
79            return (Err(e), gas_cost);
80        }
81        let canon = canon.unwrap();
82
83        let (new_human, human_gas_cost) = self.addr_humanize(&canon);
84
85        if let Err(e) = new_human {
86            gas_cost.add_assign(human_gas_cost);
87            return (Err(e), gas_cost);
88        }
89        let new_human = new_human.unwrap();
90
91        if input == new_human {
92            (Ok(()), gas_cost)
93        } else {
94            (
95                Err(BackendError::user_err(format!(
96                    "Address invalid : {}",
97                    input
98                ))),
99                gas_cost,
100            )
101        }
102    }
103
104    fn addr_canonicalize(&self, human: &str) -> cosmwasm_vm::BackendResult<Vec<u8>> {
105        let gas_cost = GasInfo::with_externally_used(GAS_COST_CANONICALIZE);
106        if human.trim().is_empty() {
107            return (
108                Err(BackendError::Unknown {
109                    msg: "empty address string is not allowed".to_string(),
110                }),
111                gas_cost,
112            );
113        }
114
115        (bytes_from_bech32(human, &self.get_prefix()), gas_cost)
116    }
117
118    fn addr_humanize(&self, canonical: &[u8]) -> cosmwasm_vm::BackendResult<String> {
119        let gas_cost = GasInfo::with_externally_used(GAS_COST_HUMANIZE);
120
121        if canonical.len() != SHORT_CANON_LEN && canonical.len() != LONG_CANON_LEN {
122            return (
123                Err(BackendError::Unknown {
124                    msg: "Canon address doesn't have the right length".to_string(),
125                }),
126                gas_cost,
127            );
128        }
129
130        if canonical.is_empty() {
131            return (Ok("".to_string()), gas_cost);
132        }
133
134        let human = bech32::encode(&self.get_prefix(), canonical.to_base32(), Variant::Bech32)
135            .map_err(|e| BackendError::Unknown { msg: e.to_string() });
136
137        (human, gas_cost)
138    }
139}
140
141#[cfg(test)]
142mod test {
143    use super::RealApi;
144
145    #[test]
146    fn prefix() {
147        let prefix = "migaloo";
148
149        let api = RealApi::new(prefix);
150
151        let final_prefix = api.get_prefix();
152        assert_eq!(prefix, final_prefix);
153    }
154
155    #[test]
156    #[should_panic]
157    fn too_long_prefix() {
158        let prefix = "migaloowithotherchars";
159        RealApi::new(prefix);
160    }
161}