CosmWasm / cosmwasm

Framework for building smart contracts in Wasm for the Cosmos SDK
https://www.cosmwasm.com/
Apache License 2.0
1.07k stars 333 forks source link

New implementation for addr_canonicalize and addr_humanize functions #1873

Closed DariuszDepta closed 10 months ago

DariuszDepta commented 1 year ago

This issue and the corresponding PR is a proposal of using new algorithms in functions:

implemented in cosmwasm_std::testing::MockApi.

The new implementation is based on Bech32 encoding format (as idescribed in BIP-0173 and Bech32m encoding format described in BIP-0350) and uses the implementation provided by crate bech32.

Proposed solution supports all currently implemented functionality, like the possibility of using short custom addresses like foobar that are not bech32 encoded. Additionaly, new functions allow users to generate humanized addresses just like real-life blockchain in bech32 format with user-defined prefix like juno, osmosis etc. Finally, this solution solves the issue #1648 and unlocks implementation of the issue #39.

Proposed solution

This solution is based on Bech32 encoding format with default or user-defined bech32 prefix. User can define Bech32 prefix during MockApi instantiation.

All input addresses represented directly as a string or wrapped with Addr are treated either as Bech32 encoded or as a plain text. The simplest usage scenario is following: a user provides a simple plain-text address which is then canonicalized and humanized, e.g.:

// create mock with default bech32 prefix
let api: MockApi = MockApi::default();
// address is NOT in bech32 encoding
let addr: &str = "creator";
let canonical: CanonicalAddr = api.addr_canonicalize(addr)?;
let humanized: Addr = api.addr_humanize(canonical)?;
assert_eq!("creator", humanized);

When a user provides a long address, similar as in Juno chain, then the previous example would look like this:

// create mock with bech32 prefix equal to "juno"
let api: MockApi = MockApi::new_with_bech32_prefix("juno");
// address is in bech32 encoding format
let addr: &str = "juno1v82su97skv6ucfqvuvswe0t5fph7pfsrtraxf0x33d8ylj5qnrysdvkc95";
let canonical: CanonicalAddr = api.addr_canonicalize(addr)?;
let humanized: Addr = api.addr_humanize(canonical)?;
assert_eq!("juno1v82su97skv6ucfqvuvswe0t5fph7pfsrtraxf0x33d8ylj5qnrysdvkc95", humanized);

When a user wants to generate and humanize a predictable contract address using a function instantiate2_address while testing contracts in Juno chain test cases, then it could be done as follows:

let api: MockApi = MockApi::new_with_bech32_prefix("juno");
let address = api.addr_humanize(&instantiate2_address(&checksum, &creator, &salt)?)?;
// the humanized address will be in bech32 format with prefix
assert_eq!("juno1t6r960j945lfv8mhl4mage2rg97w63xeynwrupum2s2l7em4lprs9ce5hk", address);

Proposed algorithm

It is assumed, that MockApi has configured Bech32 prefix during instantiation for example: juno. The reversed version of this prefix (rev_prefix) is used internally as desribed below.

addr_canonicalize

addr_humanize

DariuszDepta commented 1 year ago

After brainstorming about this change with @webmaster128 and @chipshort we agreed, that the simplest, safest and the closest to real-life blockchain way of handling addresses in tests will be using Bech32 encoding. It means, that all user addresses and contract addresses will be either in canonical form or Bech32 encoded.

Planned changes

Implementation of the trait cosmwasm_std::Api for cosmwasm_std::testing::MockApi will be adjusted in the extent described below.

Function addr_validate

This function will probably stay untouched. The validation process checks if the input address can be canonicalized and then humanized, and the result is compared with the input.

It means, that when the user passes valid Bech32 or Bech32m address with predefined prefix (see Bech32 prefix section below) to this function, it will be properly canonicalized. Canonical address can be always humanized, so the whole validation process will always success.

In case the user passes an invalid address (not Bech32 encoded), the canonicalization will reject it with an error, and then the whole validation fails.

Function addr_canonicalize

Canonicalization will use Bech32 decoder with predefined prefix. If decoding fails, it means that the provided address is invalid and an error will be returned. After change, this function will accept only addresses in Bech32 or Bech32m encoding having predefined prefix (see Bech32 prefix section below).

Function addr_humanize

This function will use Bech32 encoder to generate humanized address with predefined prefix (see Bech32 prefix section below).

Bech32 prefix

cosmwasm_std::testing::MockApi during instantiation will have a predefined prefix set. The default prefix used in Bech32 encoding/decoding will be cosmwasm, but it can be set to any reasonable value. So the user may create addresses like these in real blockchain, e.g. juno1v82su97skv6ucfqvuvswe0t5fph7pfsrtraxf0x33d8ylj5qnrysdvkc95

Addr struct

We will provide utility function named hashed, to be used only for testing purposes (just like unchecked) for creating Bech32 addresses from any provided input. The unchecked function will be deprecated, so the proper ways of creating Addr will be:

DariuszDepta commented 1 year ago

Implementation plan

Adding function Addr::hashed is non-breaking change, under condition, that bech32 crate compiles to wasm32 (can be implemented immediately).

Changing the behaviour of functions addr_canonicalize and addr_humanize is a breaking change. These changes have to be postponed to version 2.0. All test cases im cosmwasm and tests for contracts (cw-plus) have to be reviewed and adjusted.

Adding Bech32 prefix to MockApi seems to be a non-breaking change and can be implemented immediately.

webmaster128 commented 1 year ago

I agree with the above except for one thing: Addr::hashed("cosmwasm", "foobar") requires you to always put the prefix there and means it's easy to use the wrong prefix. Is there something wrong with adding the address maker as MockApi::addr_make(&self, input: &str) -> StdResult<Addr>? Then you can use Addr::unchecked internally.

I think it would be good to keep Addr encoding agnostic.

DariuszDepta commented 1 year ago

I like the idea. Let's keep Addr encoding agnostic and move the address builder to MockApi.

webmaster128 commented 1 year ago

I pulled out https://github.com/CosmWasm/cosmwasm/issues/1887 as a preparation that allows users to use it instead of Addr::unchecked. This I think should go in 1.5. The rest if this ticket we can do later.