@@ -20,7 +20,8 @@ to create composite keys.
2020
2121<Callout >
2222 Unlike values, keys do ** not** need to implement anything like ` serde::Serialize ` or
23- ` serde::Deserialize ` . Key encoding is handled by the ` PrimaryKey ` trait.
23+ ` serde::Deserialize ` . Key encoding is handled by the ` PrimaryKey ` trait. Please see the
24+ [ Advanced] ( #advanced ) section below for details.
2425</Callout >
2526
2627## Values
@@ -161,3 +162,137 @@ we lock the first component to `"alice"`, entries are ordered lexicographically
161162component.
162163
163164</Callout >
165+
166+ ## Advanced
167+
168+ ### Using custom types as a Key in a Map storage
169+
170+ The current section provides an example of an implementation for
171+ [ ` PrimaryKey ` ] ( https://docs.rs/cw-storage-plus/latest/cw_storage_plus/trait.PrimaryKey.html ) (and
172+ ` KeyDeserialize ` ) traits.
173+
174+ Let's imagine that we would like to use ` Denom ` as a key in a map like ` Map<Denom, Uint64> ` . Denom
175+ is a widely used enum that represents either a CW20 token or a Native token.
176+
177+ Here is a complete implementation.
178+
179+ ``` rust
180+ use cosmwasm_std :: {Addr , StdError , StdResult };
181+ use cw_storage_plus :: {split_first_key, Key , KeyDeserialize , Prefixer , PrimaryKey };
182+ use std :: u8 ;
183+
184+ #[derive(Clone , Debug , PartialEq , Eq )]
185+ enum Denom {
186+ Native (String ),
187+ Cw20 (Addr ),
188+ }
189+
190+ const NATIVE_PREFIX : u8 = 1 ;
191+ const CW20_PREFIX : u8 = 2 ;
192+
193+ impl PrimaryKey <'_ > for Denom {
194+ type Prefix = u8 ;
195+ type SubPrefix = ();
196+ type Suffix = String ;
197+ type SuperSuffix = Self ;
198+
199+ fn key (& self ) -> Vec <Key > {
200+ let (prefix , value ) = match self {
201+ Denom :: Native (name ) => (NATIVE_PREFIX , name . as_bytes ()),
202+ Denom :: Cw20 (addr ) => (CW20_PREFIX , addr . as_bytes ()),
203+ };
204+ vec! [Key :: Val8 ([prefix ]), Key :: Ref (value )]
205+ }
206+ }
207+
208+ impl Prefixer <'_ > for Denom {
209+ fn prefix (& self ) -> Vec <Key > {
210+ let (prefix , value ) = match self {
211+ Denom :: Native (name ) => (NATIVE_PREFIX . prefix (), name . prefix ()),
212+ Denom :: Cw20 (addr ) => (CW20_PREFIX . prefix (), addr . prefix ()),
213+ };
214+
215+ let mut result : Vec <Key > = vec! [];
216+ result . extend (prefix );
217+ result . extend (value );
218+ result
219+ }
220+ }
221+
222+ impl KeyDeserialize for Denom {
223+ type Output = Self ;
224+
225+ const KEY_ELEMS : u16 = 2 ;
226+
227+ #[inline(always)]
228+ fn from_vec (value : Vec <u8 >) -> StdResult <Self :: Output > {
229+ let (prefix , value ) = split_first_key (Self :: KEY_ELEMS , value . as_ref ())? ;
230+ let value = value . to_vec ();
231+
232+ match u8 :: from_vec (prefix )? {
233+ NATIVE_PREFIX => Ok (Denom :: Native (String :: from_vec (value )? )),
234+ CW20_PREFIX => Ok (Denom :: Cw20 (Addr :: from_vec (value )? )),
235+ _ => Err (StdError :: generic_err (" Invalid prefix" )),
236+ }
237+ }
238+ }
239+
240+ #[cfg(test)]
241+ mod test {
242+ use crate :: Denom ;
243+ use cosmwasm_std :: testing :: MockStorage ;
244+ use cosmwasm_std :: {Addr , Uint64 };
245+ use cw_storage_plus :: Map ;
246+
247+ #[test]
248+ fn round_trip_tests () {
249+ let test_data = vec! [
250+ Denom :: Native (" cosmos" . to_string ()),
251+ Denom :: Native (" some_long_native_value_with_high_precise" . to_string ()),
252+ Denom :: Cw20 (Addr :: unchecked (" contract1" )),
253+ Denom :: Cw20 (Addr :: unchecked (
254+ " cosmos1p7d8mnjttcszv34pk2a5yyug3474mhffasa7tg" ,
255+ )),
256+ ];
257+
258+ for denom in test_data {
259+ verify_map_serde (denom );
260+ }
261+ }
262+
263+ fn verify_map_serde (denom : Denom ) {
264+ let mut storage = MockStorage :: new ();
265+ let map : Map <Denom , Uint64 > = Map :: new (" denom_map" );
266+ let mock_value = Uint64 :: from (123u64 );
267+
268+ map . save (& mut storage , denom . clone (), & mock_value ). unwrap ();
269+
270+ assert! (map . has (& storage , denom . clone ()), " key should exist" );
271+
272+ let value = map . load (& storage , denom ). unwrap ();
273+ assert_eq! (value , mock_value , " value should match" );
274+ }
275+ }
276+ ```
277+
278+ The idea is to store the Denom in the storage as a composite key with 2 elements:
279+
280+ 1 . The prefix which is either ` NATIVE_PREFIX ` or ` CW20_PREFIX ` to differentiate between the two
281+ types on a raw bytes level.
282+ 2 . The value which is either the native token name or the cw20 token address
283+
284+ <Callout >
285+ The example implementation is based on a so-called composite key i.e. with KEY_ELEMS=2.
286+ </Callout >
287+
288+ #### Choosing between single element key and composite key
289+
290+ The composite key approach in our example was chosen for demonstration purpose rather than a common
291+ logic. Because such a prefix with only 2 unique values doesn't give much profit in terms of index
292+ lookup performance vs full-collection lookup. Also, Denom's type and its value are logically coupled
293+ with each other so there is not much need to split them into prefixes and suffixes.
294+
295+ Another example is a User entity with ` first_name ` and ` last_name ` fields where such a separation
296+ for prefixes and suffixes would be natural. In this case, if you define ` first_name ` as a prefix and
297+ ` last_name ` as a suffix you will be able to have indexed search to query all John's; and have to
298+ fetch all keys to find all Doe's.
0 commit comments