1
- use cosmwasm_std :: testing :: { digit_sum , riffle_shuffle } ;
1
+ use bech32 :: { decode , encode , FromBase32 , ToBase32 , Variant } ;
2
2
use cosmwasm_std:: {
3
3
Addr , BlockInfo , Coin , ContractInfo , Env , MessageInfo , Timestamp , TransactionInfo ,
4
4
} ;
5
5
6
6
use super :: querier:: MockQuerier ;
7
7
use super :: storage:: MockStorage ;
8
+ use crate :: backend:: try_br;
8
9
use crate :: { Backend , BackendApi , BackendError , BackendResult , GasInfo } ;
9
10
10
- pub const MOCK_CONTRACT_ADDR : & str = "cosmos2contract" ;
11
+ pub const MOCK_CONTRACT_ADDR : & str = "cosmwasmcontract" ; // TODO: use correct address
11
12
const GAS_COST_HUMANIZE : u64 = 44 ; // TODO: these seem very low
12
13
const GAS_COST_CANONICALIZE : u64 = 55 ;
13
14
15
+ /// Default prefix used when creating Bech32 encoded address.
16
+ const BECH32_PREFIX : & str = "cosmwasm" ;
17
+
14
18
/// All external requirements that can be injected for unit tests.
15
19
/// It sets the given balance for the contract itself, nothing else
16
20
pub fn mock_backend ( contract_balance : & [ Coin ] ) -> Backend < MockApi , MockStorage , MockQuerier > {
@@ -33,130 +37,82 @@ pub fn mock_backend_with_balances(
33
37
}
34
38
}
35
39
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
-
46
40
/// Zero-pads all human addresses to make them fit the canonical_length and
47
41
/// trims off zeros for the reverse operation.
48
42
/// This is not really smart, but allows us to see a difference (and consistent length for canonical adddresses).
49
43
#[ 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
+ } ,
56
53
}
57
54
58
55
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
-
64
56
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)
69
58
}
70
59
}
71
60
72
61
impl Default for MockApi {
73
62
fn default ( ) -> Self {
74
- MockApi {
75
- canonical_length : CANONICAL_LENGTH ,
76
- backend_error : None ,
63
+ MockApi :: Bech32 {
64
+ bech32_prefix : BECH32_PREFIX ,
77
65
}
78
66
}
79
67
}
80
68
81
69
impl BackendApi for MockApi {
82
70
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
-
86
71
let gas_info = GasInfo :: with_cost ( GAS_COST_CANONICALIZE ) ;
87
72
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
+ } ;
111
78
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
+ }
121
86
}
122
- ( Ok ( out ) , gas_info)
87
+ ( Err ( BackendError :: user_err ( "Invalid input" ) ) , gas_info)
123
88
}
124
89
125
90
fn human_address ( & self , canonical : & [ u8 ] ) -> BackendResult < String > {
126
91
let gas_info = GasInfo :: with_cost ( GAS_COST_HUMANIZE ) ;
127
92
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
+ } ;
131
98
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) ) ;
140
100
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" ) ) ;
151
103
152
- let result = match String :: from_utf8 ( trimmed) {
153
- Ok ( human) => Ok ( human) ,
154
- Err ( err) => Err ( err. into ( ) ) ,
155
- } ;
156
104
( result, gas_info)
157
105
}
158
106
}
159
107
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
+
160
116
/// Returns a default enviroment with height, time, chain_id, and contract address
161
117
/// You can submit as is to most contracts, or modify height/time if you want to
162
118
/// test for expiration.
0 commit comments