Skip to content

Commit 8066204

Browse files
DariuszDeptachipshort
authored andcommitted
New implementation of addr_canonicalize and addr_humanize functions.
1 parent 04a0e52 commit 8066204

File tree

2 files changed

+59
-122
lines changed

2 files changed

+59
-122
lines changed

packages/std/src/testing/mock.rs

Lines changed: 57 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use alloc::collections::BTreeMap;
2-
use bech32::{encode, ToBase32, Variant};
2+
use bech32::{decode, encode, FromBase32, ToBase32, Variant};
33
use core::iter::IntoIterator;
44
use core::marker::PhantomData;
55
#[cfg(feature = "cosmwasm_1_3")]
@@ -53,8 +53,6 @@ use crate::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdResponse};
5353
#[cfg(feature = "cosmwasm_1_4")]
5454
use crate::{Decimal256, DelegationRewardsResponse, DelegatorValidatorsResponse};
5555

56-
use super::riffle_shuffle;
57-
5856
pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract";
5957

6058
/// Creates all external requirements that can be injected for unit tests.
@@ -96,19 +94,6 @@ pub fn mock_dependencies_with_balances(
9694
// We can later make simplifications here if needed
9795
pub type MockStorage = MemoryStorage;
9896

99-
/// Length of canonical addresses created with this API. Contracts should not make any assumptions
100-
/// what this value is.
101-
///
102-
/// The mock API can only canonicalize and humanize addresses up to this length. So it must be
103-
/// long enough to store common bech32 addresses.
104-
///
105-
/// The value here must be restorable with `SHUFFLES_ENCODE` + `SHUFFLES_DECODE` in-shuffles.
106-
/// See <https://oeis.org/A002326/list> for a table of those values.
107-
const CANONICAL_LENGTH: usize = 90; // n = 45
108-
109-
const SHUFFLES_ENCODE: usize = 10;
110-
const SHUFFLES_DECODE: usize = 2;
111-
11297
/// Default prefix used when creating Bech32 encoded address.
11398
const BECH32_PREFIX: &str = "cosmwasm";
11499

@@ -117,17 +102,13 @@ const BECH32_PREFIX: &str = "cosmwasm";
117102
// not really smart, but allows us to see a difference (and consistent length for canonical addresses)
118103
#[derive(Copy, Clone)]
119104
pub struct MockApi {
120-
/// Length of canonical addresses created with this API. Contracts should not make any assumptions
121-
/// what this value is.
122-
canonical_length: usize,
123105
/// Prefix used for creating addresses in Bech32 encoding.
124106
bech32_prefix: &'static str,
125107
}
126108

127109
impl Default for MockApi {
128110
fn default() -> Self {
129111
MockApi {
130-
canonical_length: CANONICAL_LENGTH,
131112
bech32_prefix: BECH32_PREFIX,
132113
}
133114
}
@@ -142,62 +123,29 @@ impl Api for MockApi {
142123
"Invalid input: address not normalized",
143124
));
144125
}
145-
146126
Ok(Addr::unchecked(input))
147127
}
148128

149129
fn addr_canonicalize(&self, input: &str) -> StdResult<CanonicalAddr> {
150-
// Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated.
151-
let min_length = 3;
152-
let max_length = self.canonical_length;
153-
if input.len() < min_length {
154-
return Err(StdError::generic_err(
155-
format!("Invalid input: human address too short for this mock implementation (must be >= {min_length})."),
156-
));
157-
}
158-
if input.len() > max_length {
159-
return Err(StdError::generic_err(
160-
format!("Invalid input: human address too long for this mock implementation (must be <= {max_length})."),
161-
));
162-
}
163-
164-
// mimics formats like hex or bech32 where different casings are valid for one address
165-
let normalized = input.to_lowercase();
166-
167-
let mut out = Vec::from(normalized);
168-
169-
// pad to canonical length with NULL bytes
170-
out.resize(self.canonical_length, 0x00);
171-
// content-dependent rotate followed by shuffle to destroy
172-
// the most obvious structure (https://github.com/CosmWasm/cosmwasm/issues/552)
173-
let rotate_by = digit_sum(&out) % self.canonical_length;
174-
out.rotate_left(rotate_by);
175-
for _ in 0..SHUFFLES_ENCODE {
176-
out = riffle_shuffle(&out);
130+
if let Ok((prefix, decoded, Variant::Bech32)) = decode(input) {
131+
if prefix == self.bech32_prefix {
132+
if let Ok(bytes) = Vec::<u8>::from_base32(&decoded) {
133+
return Ok(bytes.into());
134+
}
135+
}
177136
}
178-
Ok(out.into())
137+
Err(StdError::generic_err("Invalid input"))
179138
}
180139

181140
fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
182-
if canonical.len() != self.canonical_length {
183-
return Err(StdError::generic_err(
184-
"Invalid input: canonical address length not correct",
185-
));
186-
}
187-
188-
let mut tmp: Vec<u8> = canonical.clone().into();
189-
// Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds)
190-
for _ in 0..SHUFFLES_DECODE {
191-
tmp = riffle_shuffle(&tmp);
192-
}
193-
// Rotate back
194-
let rotate_by = digit_sum(&tmp) % self.canonical_length;
195-
tmp.rotate_right(rotate_by);
196-
// Remove NULL bytes (i.e. the padding)
197-
let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect();
198-
// decode UTF-8 bytes into string
199-
let human = String::from_utf8(trimmed)?;
200-
Ok(Addr::unchecked(human))
141+
let Ok(encoded) = encode(
142+
self.bech32_prefix,
143+
canonical.as_slice().to_base32(),
144+
Variant::Bech32,
145+
) else {
146+
return Err(StdError::generic_err("Invalid canonical address"));
147+
};
148+
Ok(Addr::unchecked(encoded))
201149
}
202150

203151
fn secp256k1_verify(
@@ -1197,11 +1145,13 @@ mod tests {
11971145

11981146
#[test]
11991147
fn addr_validate_works() {
1148+
// default prefix is 'cosmwasm'
12001149
let api = MockApi::default();
12011150

12021151
// valid
1203-
let addr = api.addr_validate("foobar123").unwrap();
1204-
assert_eq!(addr.as_str(), "foobar123");
1152+
let humanized = "cosmwasm1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqs8s7vcp";
1153+
let addr = api.addr_validate(humanized).unwrap();
1154+
assert_eq!(addr, humanized);
12051155

12061156
// invalid: too short
12071157
api.addr_validate("").unwrap_err();
@@ -1214,31 +1164,44 @@ mod tests {
12141164
fn addr_canonicalize_works() {
12151165
let api = MockApi::default();
12161166

1217-
api.addr_canonicalize("foobar123").unwrap();
1167+
api.addr_canonicalize(
1168+
"cosmwasm1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqs8s7vcp",
1169+
)
1170+
.unwrap();
12181171

12191172
// is case insensitive
1220-
let data1 = api.addr_canonicalize("foo123").unwrap();
1221-
let data2 = api.addr_canonicalize("FOO123").unwrap();
1173+
let data1 = api
1174+
.addr_canonicalize(
1175+
"cosmwasm1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqs8s7vcp",
1176+
)
1177+
.unwrap();
1178+
let data2 = api
1179+
.addr_canonicalize(
1180+
"COSMWASM1H34LMPYWH4UPNJDG90CJF4J70AEE6Z8QQFSPUGAMJP42E4Q28KQS8S7VCP",
1181+
)
1182+
.unwrap();
12221183
assert_eq!(data1, data2);
12231184
}
12241185

12251186
#[test]
12261187
fn canonicalize_and_humanize_restores_original() {
1188+
// create api with 'cosmwasm' prefix
12271189
let api = MockApi::default();
12281190

1229-
// simple
1230-
let original = String::from("shorty");
1231-
let canonical = api.addr_canonicalize(&original).unwrap();
1232-
let recovered = api.addr_humanize(&canonical).unwrap();
1233-
assert_eq!(recovered.as_str(), original);
1234-
12351191
// normalizes input
1236-
let original = String::from("CosmWasmChef");
1192+
let original =
1193+
String::from("COSMWASM1H34LMPYWH4UPNJDG90CJF4J70AEE6Z8QQFSPUGAMJP42E4Q28KQS8S7VCP");
12371194
let canonical = api.addr_canonicalize(&original).unwrap();
12381195
let recovered = api.addr_humanize(&canonical).unwrap();
1239-
assert_eq!(recovered.as_str(), "cosmwasmchef");
1196+
assert_eq!(
1197+
recovered,
1198+
"cosmwasm1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqs8s7vcp"
1199+
);
12401200

1241-
// Long input (Juno contract address)
1201+
// create api with 'juno' prefix
1202+
let api = MockApi::default().with_prefix("juno");
1203+
1204+
// long input (Juno contract address)
12421205
let original =
12431206
String::from("juno1v82su97skv6ucfqvuvswe0t5fph7pfsrtraxf0x33d8ylj5qnrysdvkc95");
12441207
let canonical = api.addr_canonicalize(&original).unwrap();
@@ -1247,32 +1210,29 @@ mod tests {
12471210
}
12481211

12491212
#[test]
1250-
fn addr_canonicalize_min_input_length() {
1213+
fn addr_canonicalize_short_input() {
12511214
let api = MockApi::default();
1252-
let human = String::from("1");
1253-
let err = api.addr_canonicalize(&human).unwrap_err();
1254-
assert!(err
1255-
.to_string()
1256-
.contains("human address too short for this mock implementation (must be >= 3)"));
1215+
let human = String::from("cosmwasm1pj90vm");
1216+
assert_eq!(api.addr_canonicalize(&human).unwrap().to_string(), "");
12571217
}
12581218

12591219
#[test]
1260-
fn addr_canonicalize_max_input_length() {
1220+
fn addr_canonicalize_long_input() {
12611221
let api = MockApi::default();
12621222
let human =
1263-
String::from("some-extremely-long-address-not-supported-by-this-api-longer-than-supported------------------------");
1264-
let err = api.addr_canonicalize(&human).unwrap_err();
1265-
assert!(err
1266-
.to_string()
1267-
.contains("human address too long for this mock implementation (must be <= 90)"));
1223+
"some-extremely-long-address-some-extremely-long-address-some-extremely-long-address-some-extremely-long-address";
1224+
let err = api.addr_canonicalize(human).unwrap_err();
1225+
assert!(err.to_string().contains("Invalid input"));
12681226
}
12691227

12701228
#[test]
1271-
#[should_panic(expected = "length not correct")]
12721229
fn addr_humanize_input_length() {
12731230
let api = MockApi::default();
1274-
let input = CanonicalAddr::from(vec![61; 11]);
1275-
api.addr_humanize(&input).unwrap();
1231+
let input = CanonicalAddr::from(vec![]);
1232+
assert_eq!(
1233+
api.addr_humanize(&input).unwrap(),
1234+
Addr::unchecked("cosmwasm1pj90vm")
1235+
);
12761236
}
12771237

12781238
// Basic "works" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
@@ -2363,27 +2323,4 @@ mod tests {
23632323
fn making_an_address_with_empty_prefix_should_panic() {
23642324
MockApi::default().with_prefix("").addr_make("creator");
23652325
}
2366-
2367-
#[test]
2368-
#[cfg(feature = "cosmwasm_1_3")]
2369-
fn distribution_querier_new_works() {
2370-
let addresses = [
2371-
("addr0000".to_string(), "addr0001".to_string()),
2372-
("addr0002".to_string(), "addr0001".to_string()),
2373-
];
2374-
let btree_map = BTreeMap::from(addresses.clone());
2375-
2376-
// should still work with HashMap
2377-
let hashmap = HashMap::from(addresses.clone());
2378-
let querier = DistributionQuerier::new(hashmap);
2379-
assert_eq!(querier.withdraw_addresses, btree_map);
2380-
2381-
// should work with BTreeMap
2382-
let querier = DistributionQuerier::new(btree_map.clone());
2383-
assert_eq!(querier.withdraw_addresses, btree_map);
2384-
2385-
// should work with array
2386-
let querier = DistributionQuerier::new(addresses);
2387-
assert_eq!(querier.withdraw_addresses, btree_map);
2388-
}
23892326
}

packages/std/src/traits.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ pub trait Api {
129129
/// ```
130130
/// # use cosmwasm_std::{Api, Addr};
131131
/// # use cosmwasm_std::testing::MockApi;
132-
/// # let api = MockApi::default();
133-
/// let input = "what-users-provide";
132+
/// let api = MockApi::default().with_prefix("juno");
133+
/// let input = "juno1v82su97skv6ucfqvuvswe0t5fph7pfsrtraxf0x33d8ylj5qnrysdvkc95";
134134
/// let validated: Addr = api.addr_validate(input).unwrap();
135135
/// assert_eq!(validated.as_str(), input);
136136
/// ```

0 commit comments

Comments
 (0)