Skip to content

Commit 78b53eb

Browse files
committed
Replace vm MockApi with bech32
1 parent 005d863 commit 78b53eb

File tree

7 files changed

+70
-172
lines changed

7 files changed

+70
-172
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/std/src/testing/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
mod assertions;
77
mod mock;
8-
mod shuffle;
98

109
pub use assertions::assert_approx_eq_impl;
1110
#[cfg(test)]
@@ -26,4 +25,3 @@ pub use mock::{
2625
mock_ibc_channel_connect_ack, mock_ibc_channel_connect_confirm, mock_ibc_channel_open_init,
2726
mock_ibc_channel_open_try, mock_ibc_packet_ack, mock_ibc_packet_recv, mock_ibc_packet_timeout,
2827
};
29-
pub use shuffle::riffle_shuffle;

packages/std/src/testing/shuffle.rs

Lines changed: 0 additions & 69 deletions
This file was deleted.

packages/vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ bench = false
2929
bytes = "1.4.0" # need a higher version than the one required by Wasmer for the Bytes -> Vec<u8> implementation
3030
clru = "0.6.1"
3131
crc32fast = "1.3.2"
32+
bech32 = "0.9.1"
3233
# Uses the path when built locally; uses the given version from crates.io when published
3334
cosmwasm-std = { path = "../std", version = "1.5.0", default-features = false }
3435
cosmwasm-crypto = { path = "../crypto", version = "1.5.0" }

packages/vm/src/backend.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,19 @@ pub trait Querier {
197197
/// must always have gas information attached.
198198
pub type BackendResult<T> = (core::result::Result<T, BackendError>, GasInfo);
199199

200+
/// The equivalent of the `?` operator, but for a [`BackendResult`]
201+
macro_rules! try_br {
202+
($res: expr $(,)?) => {
203+
let (result, gas) = $res;
204+
205+
match result {
206+
Ok(v) => v,
207+
Err(e) => return (Err(e), gas),
208+
}
209+
};
210+
}
211+
pub(crate) use try_br;
212+
200213
#[derive(Error, Debug, PartialEq, Eq)]
201214
#[non_exhaustive]
202215
pub enum BackendError {

packages/vm/src/imports.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,23 +1140,23 @@ mod tests {
11401140
}
11411141
}
11421142

1143+
const CANONICAL_ADDRESS_BUFFER_LENGTH: u32 = 64;
1144+
11431145
#[test]
11441146
fn do_addr_canonicalize_works() {
11451147
let api = MockApi::default();
11461148
let (fe, mut store, instance) = make_instance(api);
11471149
let mut fe_mut = fe.into_mut(&mut store);
1148-
let api = MockApi::default();
11491150

11501151
let source_ptr = write_data(&mut fe_mut, b"foo");
1151-
let dest_ptr = create_empty(&instance, &mut fe_mut, api.canonical_length() as u32);
1152+
let dest_ptr = create_empty(&instance, &mut fe_mut, CANONICAL_ADDRESS_BUFFER_LENGTH);
11521153

11531154
leave_default_data(&mut fe_mut);
11541155

1155-
let api = MockApi::default();
11561156
let res = do_addr_canonicalize(fe_mut.as_mut(), source_ptr, dest_ptr).unwrap();
11571157
assert_eq!(res, 0);
11581158
let data = force_read(&mut fe_mut, dest_ptr);
1159-
assert_eq!(data.len(), api.canonical_length());
1159+
assert_eq!(data.len(), CANONICAL_ADDRESS_BUFFER_LENGTH as usize);
11601160
}
11611161

11621162
#[test]
@@ -1257,7 +1257,7 @@ mod tests {
12571257
..
12581258
} => {
12591259
assert_eq!(size, 7);
1260-
assert_eq!(required, api.canonical_length());
1260+
assert_eq!(required, CANONICAL_ADDRESS_BUFFER_LENGTH as usize);
12611261
}
12621262
err => panic!("Incorrect error returned: {err:?}"),
12631263
}
@@ -1268,9 +1268,8 @@ mod tests {
12681268
let api = MockApi::default();
12691269
let (fe, mut store, instance) = make_instance(api);
12701270
let mut fe_mut = fe.into_mut(&mut store);
1271-
let api = MockApi::default();
12721271

1273-
let source_data = vec![0x22; api.canonical_length()];
1272+
let source_data = vec![0x22; CANONICAL_ADDRESS_BUFFER_LENGTH as usize];
12741273
let source_ptr = write_data(&mut fe_mut, &source_data);
12751274
let dest_ptr = create_empty(&instance, &mut fe_mut, 70);
12761275

@@ -1351,9 +1350,8 @@ mod tests {
13511350
let api = MockApi::default();
13521351
let (fe, mut store, instance) = make_instance(api);
13531352
let mut fe_mut = fe.into_mut(&mut store);
1354-
let api = MockApi::default();
13551353

1356-
let source_data = vec![0x22; api.canonical_length()];
1354+
let source_data = vec![0x22; CANONICAL_ADDRESS_BUFFER_LENGTH as usize];
13571355
let source_ptr = write_data(&mut fe_mut, &source_data);
13581356
let dest_ptr = create_empty(&instance, &mut fe_mut, 2);
13591357

@@ -1366,7 +1364,7 @@ mod tests {
13661364
..
13671365
} => {
13681366
assert_eq!(size, 2);
1369-
assert_eq!(required, api.canonical_length());
1367+
assert_eq!(required, CANONICAL_ADDRESS_BUFFER_LENGTH as usize);
13701368
}
13711369
err => panic!("Incorrect error returned: {err:?}"),
13721370
}

packages/vm/src/testing/mock.rs

Lines changed: 47 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
use cosmwasm_std::testing::{digit_sum, riffle_shuffle};
1+
use bech32::{decode, encode, FromBase32, ToBase32, Variant};
22
use cosmwasm_std::{
33
Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo, Timestamp, TransactionInfo,
44
};
55

66
use super::querier::MockQuerier;
77
use super::storage::MockStorage;
8+
use crate::backend::try_br;
89
use crate::{Backend, BackendApi, BackendError, BackendResult, GasInfo};
910

10-
pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract";
11+
pub const MOCK_CONTRACT_ADDR: &str = "cosmwasmcontract"; // TODO: use correct address
1112
const GAS_COST_HUMANIZE: u64 = 44; // TODO: these seem very low
1213
const GAS_COST_CANONICALIZE: u64 = 55;
1314

15+
/// Default prefix used when creating Bech32 encoded address.
16+
const BECH32_PREFIX: &str = "cosmwasm";
17+
1418
/// All external requirements that can be injected for unit tests.
1519
/// It sets the given balance for the contract itself, nothing else
1620
pub fn mock_backend(contract_balance: &[Coin]) -> Backend<MockApi, MockStorage, MockQuerier> {
@@ -33,130 +37,82 @@ pub fn mock_backend_with_balances(
3337
}
3438
}
3539

36-
/// Length of canonical addresses created with this API. Contracts should not make any assumptions
37-
/// what this value is.
38-
///
39-
/// The value here must be restorable with `SHUFFLES_ENCODE` + `SHUFFLES_DECODE` in-shuffles.
40-
/// See <https://oeis.org/A002326/list> for a table of those values.
41-
const CANONICAL_LENGTH: usize = 64; // n = 32
42-
43-
const SHUFFLES_ENCODE: usize = 10;
44-
const SHUFFLES_DECODE: usize = 2;
45-
4640
/// Zero-pads all human addresses to make them fit the canonical_length and
4741
/// trims off zeros for the reverse operation.
4842
/// This is not really smart, but allows us to see a difference (and consistent length for canonical adddresses).
4943
#[derive(Copy, Clone)]
50-
pub struct MockApi {
51-
/// Length of canonical addresses created with this API. Contracts should not make any assumptions
52-
/// what this value is.
53-
canonical_length: usize,
54-
/// When set, all calls to the API fail with BackendError::Unknown containing this message
55-
backend_error: Option<&'static str>,
44+
pub enum MockApi {
45+
/// With this variant, all calls to the API fail with BackendError::Unknown
46+
/// containing the given message
47+
Error(&'static str),
48+
/// This variant implements Bech32 addresses.
49+
Bech32 {
50+
/// Prefix used for creating addresses in Bech32 encoding.
51+
bech32_prefix: &'static str,
52+
},
5653
}
5754

5855
impl MockApi {
59-
/// Read-only getter for `canonical_length`, which must not be changed by the caller.
60-
pub fn canonical_length(&self) -> usize {
61-
self.canonical_length
62-
}
63-
6456
pub fn new_failing(backend_error: &'static str) -> Self {
65-
MockApi {
66-
backend_error: Some(backend_error),
67-
..MockApi::default()
68-
}
57+
MockApi::Error(backend_error)
6958
}
7059
}
7160

7261
impl Default for MockApi {
7362
fn default() -> Self {
74-
MockApi {
75-
canonical_length: CANONICAL_LENGTH,
76-
backend_error: None,
63+
MockApi::Bech32 {
64+
bech32_prefix: BECH32_PREFIX,
7765
}
7866
}
7967
}
8068

8169
impl BackendApi for MockApi {
8270
fn canonical_address(&self, input: &str) -> BackendResult<Vec<u8>> {
83-
// mimicks formats like hex or bech32 where different casings are valid for one address
84-
let normalized = input.to_lowercase();
85-
8671
let gas_info = GasInfo::with_cost(GAS_COST_CANONICALIZE);
8772

88-
if let Some(backend_error) = self.backend_error {
89-
return (Err(BackendError::unknown(backend_error)), gas_info);
90-
}
91-
92-
// Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated.
93-
let min_length = 3;
94-
let max_length = self.canonical_length;
95-
if normalized.len() < min_length {
96-
return (
97-
Err(BackendError::user_err(
98-
format!("Invalid input: human address too short for this mock implementation (must be >= {min_length})."),
99-
)),
100-
gas_info,
101-
);
102-
}
103-
if normalized.len() > max_length {
104-
return (
105-
Err(BackendError::user_err(
106-
format!("Invalid input: human address too long for this mock implementation (must be <= {max_length})."),
107-
)),
108-
gas_info,
109-
);
110-
}
73+
// handle error case
74+
let bech32_prefix = match self {
75+
MockApi::Error(e) => return (Err(BackendError::unknown(*e)), gas_info),
76+
MockApi::Bech32 { bech32_prefix } => *bech32_prefix,
77+
};
11178

112-
let mut out = Vec::from(normalized);
113-
// pad to canonical length with NULL bytes
114-
out.resize(self.canonical_length, 0x00);
115-
// content-dependent rotate followed by shuffle to destroy
116-
// the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552)
117-
let rotate_by = digit_sum(&out) % self.canonical_length;
118-
out.rotate_left(rotate_by);
119-
for _ in 0..SHUFFLES_ENCODE {
120-
out = riffle_shuffle(&out);
79+
if let Ok((prefix, decoded, Variant::Bech32)) = decode(input) {
80+
if prefix == bech32_prefix {
81+
if let Ok(bytes) = Vec::<u8>::from_base32(&decoded) {
82+
try_br!((validate_length(&bytes), gas_info));
83+
return (Ok(bytes), gas_info);
84+
}
85+
}
12186
}
122-
(Ok(out), gas_info)
87+
(Err(BackendError::user_err("Invalid input")), gas_info)
12388
}
12489

12590
fn human_address(&self, canonical: &[u8]) -> BackendResult<String> {
12691
let gas_info = GasInfo::with_cost(GAS_COST_HUMANIZE);
12792

128-
if let Some(backend_error) = self.backend_error {
129-
return (Err(BackendError::unknown(backend_error)), gas_info);
130-
}
93+
// handle error case
94+
let bech32_prefix = match self {
95+
MockApi::Error(e) => return (Err(BackendError::unknown(*e)), gas_info),
96+
MockApi::Bech32 { bech32_prefix } => *bech32_prefix,
97+
};
13198

132-
if canonical.len() != self.canonical_length {
133-
return (
134-
Err(BackendError::user_err(
135-
"Invalid input: canonical address length not correct",
136-
)),
137-
gas_info,
138-
);
139-
}
99+
try_br!((validate_length(canonical.as_ref()), gas_info));
140100

141-
let mut tmp: Vec<u8> = canonical.into();
142-
// Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds)
143-
for _ in 0..SHUFFLES_DECODE {
144-
tmp = riffle_shuffle(&tmp);
145-
}
146-
// Rotate back
147-
let rotate_by = digit_sum(&tmp) % self.canonical_length;
148-
tmp.rotate_right(rotate_by);
149-
// Remove NULL bytes (i.e. the padding)
150-
let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect();
101+
let result = encode(bech32_prefix, canonical.to_base32(), Variant::Bech32)
102+
.map_err(|_| BackendError::user_err("Invalid canonical address"));
151103

152-
let result = match String::from_utf8(trimmed) {
153-
Ok(human) => Ok(human),
154-
Err(err) => Err(err.into()),
155-
};
156104
(result, gas_info)
157105
}
158106
}
159107

108+
/// Does basic validation of the number of bytes in a canonical address
109+
fn validate_length(bytes: &[u8]) -> Result<(), BackendError> {
110+
if !(1..=255).contains(&bytes.len()) {
111+
return Err(BackendError::user_err("Invalid canonical address length"));
112+
}
113+
Ok(())
114+
}
115+
160116
/// Returns a default enviroment with height, time, chain_id, and contract address
161117
/// You can submit as is to most contracts, or modify height/time if you want to
162118
/// test for expiration.

0 commit comments

Comments
 (0)