From 0cee7087e77817116f205c8ba5768c4e84866b8d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 13:22:00 +0200 Subject: [PATCH 01/38] Split scripts in check/test workspace --- devtools/check_workspace.sh | 10 +++++----- devtools/test_workspace.sh | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100755 devtools/test_workspace.sh diff --git a/devtools/check_workspace.sh b/devtools/check_workspace.sh index 4a1666d55b..47cc56a23f 100755 --- a/devtools/check_workspace.sh +++ b/devtools/check_workspace.sh @@ -3,8 +3,8 @@ set -o errexit -o nounset -o pipefail command -v shellcheck >/dev/null && shellcheck "$0" cargo fmt -(cd packages/crypto && cargo build && cargo test && cargo clippy --tests -- -D warnings) -(cd packages/std && cargo wasm-debug --features iterator && cargo test --features iterator && cargo clippy --tests --features iterator -- -D warnings && cargo schema) -(cd packages/storage && cargo build && cargo test --features iterator && cargo clippy --tests --features iterator -- -D warnings) -(cd packages/schema && cargo build && cargo test && cargo clippy --tests -- -D warnings) -(cd packages/vm && cargo build --features iterator,stargate && cargo test --features iterator,stargate && cargo clippy --tests --features iterator,stargate -- -D warnings) +(cd packages/crypto && cargo build && cargo clippy --tests -- -D warnings) +(cd packages/std && cargo wasm-debug --features iterator && cargo clippy --tests --features iterator -- -D warnings && cargo schema) +(cd packages/storage && cargo build && cargo clippy --tests --features iterator -- -D warnings) +(cd packages/schema && cargo build && cargo clippy --tests -- -D warnings) +(cd packages/vm && cargo build --features iterator,stargate && cargo clippy --tests --features iterator,stargate -- -D warnings) diff --git a/devtools/test_workspace.sh b/devtools/test_workspace.sh new file mode 100755 index 0000000000..b2d3a1771f --- /dev/null +++ b/devtools/test_workspace.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +cargo fmt +(cd packages/crypto && cargo test) +(cd packages/std && cargo test --features iterator) +(cd packages/storage && cargo test --features iterator) +(cd packages/schema && cargo test) +(cd packages/vm && cargo test --features iterator,stargate) From 5e377fd8a499593f84afc375afabe1cb5f44a1eb Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Mar 2021 07:26:52 +0100 Subject: [PATCH 02/38] Sort tests --- packages/std/src/mock.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 23038073f1..19184e257b 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -547,14 +547,6 @@ mod tests { assert_eq!(recovered, original); } - #[test] - #[should_panic(expected = "length not correct")] - fn human_address_input_length() { - let api = MockApi::default(); - let input = CanonicalAddr(Binary(vec![61; 11])); - api.human_address(&input).unwrap(); - } - #[test] #[should_panic(expected = "address too short")] fn canonical_address_min_input_length() { @@ -571,6 +563,14 @@ mod tests { let _ = api.canonical_address(&human).unwrap(); } + #[test] + #[should_panic(expected = "length not correct")] + fn human_address_input_length() { + let api = MockApi::default(); + let input = CanonicalAddr(Binary(vec![61; 11])); + api.human_address(&input).unwrap(); + } + // Basic "works" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs) #[test] fn secp256k1_verify_works() { From 1d5ca28b3fd9c54891d58f69f6fc668ce8d1638c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 30 Mar 2021 22:16:01 +0200 Subject: [PATCH 03/38] Add addr_validate and addr_ prefixed humanize/canonicalize --- contracts/hackatom/src/contract.rs | 36 +++++++++++++++--------------- contracts/reflect/src/contract.rs | 16 ++++++------- contracts/staking/src/contract.rs | 18 +++++++-------- packages/std/src/imports.rs | 4 ++-- packages/std/src/mock.rs | 20 ++++++++--------- packages/std/src/traits.rs | 17 ++++++++++++-- 6 files changed, 62 insertions(+), 49 deletions(-) diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index ba7f9dbdaf..9ffd258550 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -108,9 +108,9 @@ pub fn instantiate( deps.storage.set( CONFIG_KEY, &to_vec(&State { - verifier: deps.api.canonical_address(&msg.verifier)?, - beneficiary: deps.api.canonical_address(&msg.beneficiary)?, - funder: deps.api.canonical_address(&info.sender)?, + verifier: deps.api.addr_canonicalize(&msg.verifier)?, + beneficiary: deps.api.addr_canonicalize(&msg.beneficiary)?, + funder: deps.api.addr_canonicalize(&info.sender)?, })?, ); @@ -126,7 +126,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result Result Result { // Canonicalize let empty = HumanAddr::from(""); - match api.canonical_address(&empty).unwrap_err() { + match api.addr_canonicalize(&empty).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -259,7 +259,7 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { } let invalid_bech32 = HumanAddr::from("bn93hg934hg08q340g8u4jcau3"); - match api.canonical_address(&invalid_bech32).unwrap_err() { + match api.addr_canonicalize(&invalid_bech32).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -273,7 +273,7 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { // Humanize let empty: CanonicalAddr = vec![].into(); - match api.human_address(&empty).unwrap_err() { + match api.addr_humanize(&empty).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -285,7 +285,7 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { } let too_short: CanonicalAddr = vec![0xAA, 0xBB, 0xCC].into(); - match api.human_address(&too_short).unwrap_err() { + match api.addr_humanize(&too_short).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -297,7 +297,7 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { } let wrong_length: CanonicalAddr = vec![0xA6; 17].into(); - match api.human_address(&wrong_length).unwrap_err() { + match api.addr_humanize(&wrong_length).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -327,7 +327,7 @@ fn query_verifier(deps: Deps) -> StdResult { .get(CONFIG_KEY) .ok_or_else(|| StdError::not_found("State"))?; let state: State = from_slice(&data)?; - let addr = deps.api.human_address(&state.verifier)?; + let addr = deps.api.addr_humanize(&state.verifier)?; Ok(VerifierResponse { verifier: addr }) } @@ -384,9 +384,9 @@ mod tests { let beneficiary = HumanAddr(String::from("benefits")); let creator = HumanAddr(String::from("creator")); let expected_state = State { - verifier: deps.api.canonical_address(&verifier).unwrap(), - beneficiary: deps.api.canonical_address(&beneficiary).unwrap(), - funder: deps.api.canonical_address(&creator).unwrap(), + verifier: deps.api.addr_canonicalize(&verifier).unwrap(), + beneficiary: deps.api.addr_canonicalize(&beneficiary).unwrap(), + funder: deps.api.addr_canonicalize(&creator).unwrap(), }; let msg = InstantiateMsg { @@ -585,9 +585,9 @@ mod tests { assert_eq!( state, State { - verifier: deps.api.canonical_address(&verifier).unwrap(), - beneficiary: deps.api.canonical_address(&beneficiary).unwrap(), - funder: deps.api.canonical_address(&creator).unwrap(), + verifier: deps.api.addr_canonicalize(&verifier).unwrap(), + beneficiary: deps.api.addr_canonicalize(&beneficiary).unwrap(), + funder: deps.api.addr_canonicalize(&creator).unwrap(), } ); } diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index bff304510f..964a2d9941 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -18,7 +18,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult> { let state = State { - owner: deps.api.canonical_address(&info.sender)?, + owner: deps.api.addr_canonicalize(&info.sender)?, }; config(deps.storage).save(&state)?; @@ -59,7 +59,7 @@ pub fn try_reflect( ) -> Result, ReflectError> { let state = config(deps.storage).load()?; - let sender = deps.api.canonical_address(&info.sender)?; + let sender = deps.api.addr_canonicalize(&info.sender)?; if sender != state.owner { return Err(ReflectError::NotCurrentOwner { expected: state.owner, @@ -86,7 +86,7 @@ pub fn try_reflect_subcall( msgs: Vec>, ) -> Result, ReflectError> { let state = config(deps.storage).load()?; - let sender = deps.api.canonical_address(&info.sender)?; + let sender = deps.api.addr_canonicalize(&info.sender)?; if sender != state.owner { return Err(ReflectError::NotCurrentOwner { expected: state.owner, @@ -114,14 +114,14 @@ pub fn try_change_owner( ) -> Result, ReflectError> { let api = deps.api; config(deps.storage).update(|mut state| { - let sender = api.canonical_address(&info.sender)?; + let sender = api.addr_canonicalize(&info.sender)?; if sender != state.owner { return Err(ReflectError::NotCurrentOwner { expected: state.owner, actual: sender, }); } - state.owner = api.canonical_address(&owner)?; + state.owner = api.addr_canonicalize(&owner)?; Ok(state) })?; Ok(Response { @@ -151,7 +151,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { fn query_owner(deps: Deps) -> StdResult { let state = config_read(deps.storage).load()?; let resp = OwnerResponse { - owner: deps.api.human_address(&state.owner)?, + owner: deps.api.addr_humanize(&state.owner)?, }; Ok(resp) } @@ -384,8 +384,8 @@ mod tests { let msg = ExecuteMsg::ChangeOwner { owner: new_owner }; let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - let expected = deps.api.canonical_address(&creator).unwrap(); - let actual = deps.api.canonical_address(&random).unwrap(); + let expected = deps.api.addr_canonicalize(&creator).unwrap(); + let actual = deps.api.addr_canonicalize(&random).unwrap(); assert_eq!(err, ReflectError::NotCurrentOwner { expected, actual }); } diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index e389cef94a..defccb1428 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -41,7 +41,7 @@ pub fn instantiate( let denom = deps.querier.query_bonded_denom()?; let invest = InvestmentInfo { - owner: deps.api.canonical_address(&info.sender)?, + owner: deps.api.addr_canonicalize(&info.sender)?, exit_tax: msg.exit_tax, bond_denom: denom, validator: msg.validator, @@ -82,8 +82,8 @@ pub fn transfer( recipient: HumanAddr, send: Uint128, ) -> StdResult { - let rcpt_raw = deps.api.canonical_address(&recipient)?; - let sender_raw = deps.api.canonical_address(&info.sender)?; + let rcpt_raw = deps.api.addr_canonicalize(&recipient)?; + let sender_raw = deps.api.addr_canonicalize(&info.sender)?; let mut accounts = balances(deps.storage); accounts.update(&sender_raw, |balance: Option| -> StdResult<_> { @@ -140,7 +140,7 @@ fn assert_bonds(supply: &Supply, bonded: Uint128) -> StdResult<()> { } pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { - let sender_raw = deps.api.canonical_address(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(&info.sender)?; // ensure we have the proper denom let invest = invest_info_read(deps.storage).load()?; @@ -193,7 +193,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { } pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> StdResult { - let sender_raw = deps.api.canonical_address(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(&info.sender)?; let invest = invest_info_read(deps.storage).load()?; // ensure it is big enough to care @@ -271,7 +271,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult } // check how much to send - min(balance, claims[sender]), and reduce the claim - let sender_raw = deps.api.canonical_address(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(&info.sender)?; let mut to_send = balance.amount; claims(deps.storage).update(sender_raw.as_slice(), |claim| -> StdResult<_> { let claim = claim.ok_or_else(|| StdError::generic_err("no claim for this address"))?; @@ -394,7 +394,7 @@ pub fn query_token_info(deps: Deps) -> StdResult { } pub fn query_balance(deps: Deps, address: HumanAddr) -> StdResult { - let address_raw = deps.api.canonical_address(&address)?; + let address_raw = deps.api.addr_canonicalize(&address)?; let balance = balances_read(deps.storage) .may_load(address_raw.as_slice())? .unwrap_or_default(); @@ -402,7 +402,7 @@ pub fn query_balance(deps: Deps, address: HumanAddr) -> StdResult StdResult { - let address_raw = deps.api.canonical_address(&address)?; + let address_raw = deps.api.addr_canonicalize(&address)?; let claims = claims_read(deps.storage) .may_load(address_raw.as_slice())? .unwrap_or_default(); @@ -414,7 +414,7 @@ pub fn query_investment(deps: Deps) -> StdResult { let supply = total_supply_read(deps.storage).load()?; let res = InvestmentResponse { - owner: deps.api.human_address(&invest.owner)?, + owner: deps.api.addr_humanize(&invest.owner)?, exit_tax: invest.exit_tax, validator: invest.validator, min_withdrawal: invest.min_withdrawal, diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 8baf2e7d8d..d9e4440b18 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -155,7 +155,7 @@ impl ExternalApi { } impl Api for ExternalApi { - fn canonical_address(&self, human: &HumanAddr) -> StdResult { + fn addr_canonicalize(&self, human: &HumanAddr) -> StdResult { let send = build_region(human.as_str().as_bytes()); let send_ptr = &*send as *const Region as u32; let canon = alloc(CANONICAL_ADDRESS_BUFFER_LENGTH); @@ -173,7 +173,7 @@ impl Api for ExternalApi { Ok(CanonicalAddr(Binary(out))) } - fn human_address(&self, canonical: &CanonicalAddr) -> StdResult { + fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { let send = build_region(&canonical); let send_ptr = &*send as *const Region as u32; let human = alloc(HUMAN_ADDRESS_BUFFER_LENGTH); diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 19184e257b..873ca3a913 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -71,7 +71,7 @@ impl Default for MockApi { } impl Api for MockApi { - fn canonical_address(&self, human: &HumanAddr) -> StdResult { + fn addr_canonicalize(&self, human: &HumanAddr) -> StdResult { // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated. if human.len() < 3 { return Err(StdError::generic_err( @@ -98,7 +98,7 @@ impl Api for MockApi { Ok(out.into()) } - fn human_address(&self, canonical: &CanonicalAddr) -> StdResult { + fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { if canonical.len() != self.canonical_length { return Err(StdError::generic_err( "Invalid input: canonical address length not correct", @@ -542,33 +542,33 @@ mod tests { let api = MockApi::default(); let original = HumanAddr::from("shorty"); - let canonical = api.canonical_address(&original).unwrap(); - let recovered = api.human_address(&canonical).unwrap(); + let canonical = api.addr_canonicalize(&original).unwrap(); + let recovered = api.addr_humanize(&canonical).unwrap(); assert_eq!(recovered, original); } #[test] #[should_panic(expected = "address too short")] - fn canonical_address_min_input_length() { + fn addr_canonicalize_min_input_length() { let api = MockApi::default(); let human = HumanAddr("1".to_string()); - let _ = api.canonical_address(&human).unwrap(); + let _ = api.addr_canonicalize(&human).unwrap(); } #[test] #[should_panic(expected = "address too long")] - fn canonical_address_max_input_length() { + fn addr_canonicalize_max_input_length() { let api = MockApi::default(); let human = HumanAddr::from("some-extremely-long-address-not-supported-by-this-api"); - let _ = api.canonical_address(&human).unwrap(); + let _ = api.addr_canonicalize(&human).unwrap(); } #[test] #[should_panic(expected = "length not correct")] - fn human_address_input_length() { + fn addr_humanize_input_length() { let api = MockApi::default(); let input = CanonicalAddr(Binary(vec![61; 11])); - api.human_address(&input).unwrap(); + api.addr_humanize(&input).unwrap(); } // Basic "works" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs) diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 5d0c1f5496..0b408075d5 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -64,8 +64,21 @@ pub trait Storage { /// We can use feature flags to opt-in to non-essential methods /// for backwards compatibility in systems that don't have them all. pub trait Api { - fn canonical_address(&self, human: &HumanAddr) -> StdResult; - fn human_address(&self, canonical: &CanonicalAddr) -> StdResult; + /// Takes a human readable address and validates if it's correctly formatted. + fn addr_validate(&self, human: &HumanAddr) -> bool { + match self.addr_canonicalize(human) { + Ok(_) => true, + Err(_) => false, + } + } + + /// Takes a human readable address and returns a canonical binary representation of it. + /// This can be used when a compact fixed length representation is needed. + fn addr_canonicalize(&self, human: &HumanAddr) -> StdResult; + + /// Takes a canonical address and returns a human readble address. + /// This is the inverse of [addr_canonicalize]. + fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult; fn secp256k1_verify( &self, From 7f93bcd277b2155f994ea4e2d45e1a90ecc79316 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 25 Feb 2021 23:47:20 +0100 Subject: [PATCH 04/38] Let addr_validate return StdResult<()> --- packages/std/src/traits.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 0b408075d5..588939ae12 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -65,11 +65,8 @@ pub trait Storage { /// for backwards compatibility in systems that don't have them all. pub trait Api { /// Takes a human readable address and validates if it's correctly formatted. - fn addr_validate(&self, human: &HumanAddr) -> bool { - match self.addr_canonicalize(human) { - Ok(_) => true, - Err(_) => false, - } + fn addr_validate(&self, human: &HumanAddr) -> StdResult<()> { + self.addr_canonicalize(human).map(|_canonical| ()) } /// Takes a human readable address and returns a canonical binary representation of it. From d81d404a0d18f9c283aa8bdc4e9e33a0a55929c3 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Mar 2021 08:20:03 +0100 Subject: [PATCH 05/38] Let addr_canonicalize accept &str inputs --- packages/std/src/imports.rs | 4 ++-- packages/std/src/mock.rs | 15 +++++++++++++-- packages/std/src/traits.rs | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index d9e4440b18..23c67e8b16 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -155,8 +155,8 @@ impl ExternalApi { } impl Api for ExternalApi { - fn addr_canonicalize(&self, human: &HumanAddr) -> StdResult { - let send = build_region(human.as_str().as_bytes()); + fn addr_canonicalize(&self, human: &str) -> StdResult { + let send = build_region(human.as_bytes()); let send_ptr = &*send as *const Region as u32; let canon = alloc(CANONICAL_ADDRESS_BUFFER_LENGTH); diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 873ca3a913..6f8152b0cd 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -71,7 +71,7 @@ impl Default for MockApi { } impl Api for MockApi { - fn addr_canonicalize(&self, human: &HumanAddr) -> StdResult { + fn addr_canonicalize(&self, human: &str) -> StdResult { // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated. if human.len() < 3 { return Err(StdError::generic_err( @@ -84,7 +84,7 @@ impl Api for MockApi { )); } - let mut out = Vec::from(human.as_str()); + let mut out = Vec::from(human); // pad to canonical length with NULL bytes out.resize(self.canonical_length, 0x00); @@ -563,6 +563,17 @@ mod tests { let _ = api.addr_canonicalize(&human).unwrap(); } + #[test] + fn addr_canonicalize_works_with_string_inputs() { + let api = MockApi::default(); + + let input = String::from("foobar123"); + api.addr_canonicalize(&input).unwrap(); + + let input = "foobar456"; + api.addr_canonicalize(&input).unwrap(); + } + #[test] #[should_panic(expected = "length not correct")] fn addr_humanize_input_length() { diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 588939ae12..f8637da8bf 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -65,13 +65,13 @@ pub trait Storage { /// for backwards compatibility in systems that don't have them all. pub trait Api { /// Takes a human readable address and validates if it's correctly formatted. - fn addr_validate(&self, human: &HumanAddr) -> StdResult<()> { + fn addr_validate(&self, human: &str) -> StdResult<()> { self.addr_canonicalize(human).map(|_canonical| ()) } /// Takes a human readable address and returns a canonical binary representation of it. /// This can be used when a compact fixed length representation is needed. - fn addr_canonicalize(&self, human: &HumanAddr) -> StdResult; + fn addr_canonicalize(&self, human: &str) -> StdResult; /// Takes a canonical address and returns a human readble address. /// This is the inverse of [addr_canonicalize]. From 3aeaafe7f78085365a3014749a028347d3492a95 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Mar 2021 08:29:45 +0100 Subject: [PATCH 06/38] Let addr_validate return HumanAddr --- packages/std/src/traits.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index f8637da8bf..20288a7db8 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -65,8 +65,21 @@ pub trait Storage { /// for backwards compatibility in systems that don't have them all. pub trait Api { /// Takes a human readable address and validates if it's correctly formatted. - fn addr_validate(&self, human: &str) -> StdResult<()> { - self.addr_canonicalize(human).map(|_canonical| ()) + /// If it succeeds, a HumanAddr is returned. + /// + /// ## Examples + /// + /// ``` + /// # use cosmwasm_std::{Api, HumanAddr}; + /// # use cosmwasm_std::testing::MockApi; + /// # let api = MockApi::default(); + /// let input = "what-users-provide"; + /// let validated: HumanAddr = api.addr_validate(input).unwrap(); + /// assert_eq!(validated, input); + /// ``` + fn addr_validate(&self, human: &str) -> StdResult { + self.addr_canonicalize(human).map(|_canonical| ())?; + Ok(human.into()) } /// Takes a human readable address and returns a canonical binary representation of it. From 0224fa5a8931f8571524aa442630eec71404a98e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 20:51:30 +0200 Subject: [PATCH 07/38] Rename imports to addr_canonicalize/addr_humanize --- CHANGELOG.md | 3 ++ README.md | 4 +-- packages/std/src/imports.rs | 12 +++---- packages/vm/src/compatibility.rs | 14 ++++---- packages/vm/src/environment.rs | 4 +-- packages/vm/src/imports.rs | 60 ++++++++++++++++---------------- packages/vm/src/instance.rs | 12 +++---- 7 files changed, 56 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b8d125698..499b718306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,8 @@ and this project adheres to `interface_version_5`. - cosmwasm-vm: Rename trait `Api` to `BackendApi` to better express this is the API provided by the VM's backend (i.e. the blockchain). +- cosmwasm-vm: Rename imports to `addr_canonicalize` and `addr_humanize` + ([#802]). - contracts: `reflect` contract requires `stargate` feature and supports redispatching `Stargate` and `IbcMsg::Transfer` messages ([#692]) - cosmwasm-std: The arithmetic methods of `Uint128` got a huge overhaul, making @@ -140,6 +142,7 @@ and this project adheres to [#853]: https://github.com/CosmWasm/cosmwasm/pull/853 [#858]: https://github.com/CosmWasm/cosmwasm/issues/858 [u128]: https://doc.rust-lang.org/std/primitive.u128.html +[#802]: https://github.com/CosmWasm/cosmwasm/pull/802 ### Deprecated diff --git a/README.md b/README.md index 76f8106ba1..f2475ddb92 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,8 @@ extern "C" { #[cfg(feature = "iterator")] fn db_next(iterator_id: u32) -> u32; - fn canonicalize_address(source: u32, destination: u32) -> u32; - fn humanize_address(source: u32, destination: u32) -> u32; + fn addr_canonicalize(source: u32, destination: u32) -> u32; + fn addr_humanize(source: u32, destination: u32) -> u32; /// Verifies message hashes against a signature with a public key, using the /// secp256k1 ECDSA parametrization. diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 23c67e8b16..7243e81762 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -36,8 +36,8 @@ extern "C" { #[cfg(feature = "iterator")] fn db_next(iterator_id: u32) -> u32; - fn canonicalize_address(source_ptr: u32, destination_ptr: u32) -> u32; - fn humanize_address(source_ptr: u32, destination_ptr: u32) -> u32; + fn addr_canonicalize(source_ptr: u32, destination_ptr: u32) -> u32; + fn addr_humanize(source_ptr: u32, destination_ptr: u32) -> u32; fn secp256k1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32; fn secp256k1_recover_pubkey( @@ -160,11 +160,11 @@ impl Api for ExternalApi { let send_ptr = &*send as *const Region as u32; let canon = alloc(CANONICAL_ADDRESS_BUFFER_LENGTH); - let result = unsafe { canonicalize_address(send_ptr, canon as u32) }; + let result = unsafe { addr_canonicalize(send_ptr, canon as u32) }; if result != 0 { let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; return Err(StdError::generic_err(format!( - "canonicalize_address errored: {}", + "addr_canonicalize errored: {}", error ))); } @@ -178,11 +178,11 @@ impl Api for ExternalApi { let send_ptr = &*send as *const Region as u32; let human = alloc(HUMAN_ADDRESS_BUFFER_LENGTH); - let result = unsafe { humanize_address(send_ptr, human as u32) }; + let result = unsafe { addr_humanize(send_ptr, human as u32) }; if result != 0 { let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; return Err(StdError::generic_err(format!( - "humanize_address errored: {}", + "addr_humanize errored: {}", error ))); } diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index b593af0033..4ab7328cf3 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -13,8 +13,8 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.db_read", "env.db_write", "env.db_remove", - "env.canonicalize_address", - "env.humanize_address", + "env.addr_canonicalize", + "env.addr_humanize", "env.secp256k1_verify", "env.secp256k1_recover_pubkey", "env.ed25519_verify", @@ -319,8 +319,8 @@ mod tests { (import "env" "db_read" (func (param i32 i32) (result i32))) (import "env" "db_write" (func (param i32 i32) (result i32))) (import "env" "db_remove" (func (param i32) (result i32))) - (import "env" "canonicalize_address" (func (param i32 i32) (result i32))) - (import "env" "humanize_address" (func (param i32 i32) (result i32))) + (import "env" "addr_canonicalize" (func (param i32 i32) (result i32))) + (import "env" "addr_humanize" (func (param i32 i32) (result i32))) (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32))) (import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64))) (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32))) @@ -354,8 +354,8 @@ mod tests { "env.db_read", "env.db_write", "env.db_remove", - "env.canonicalize_address", - "env.humanize_address", + "env.addr_canonicalize", + "env.addr_humanize", "env.debug", "env.query_chain", ]; @@ -365,7 +365,7 @@ mod tests { println!("{}", msg); assert_eq!( msg, - r#"Wasm contract requires unsupported import: "env.foo". Required imports: {"env.bar", "env.foo", "env.spammyspam01", "env.spammyspam02", "env.spammyspam03", "env.spammyspam04", "env.spammyspam05", "env.spammyspam06", "env.spammyspam07", "env.spammyspam08", ... 2 more}. Available imports: ["env.db_read", "env.db_write", "env.db_remove", "env.canonicalize_address", "env.humanize_address", "env.debug", "env.query_chain"]."# + r#"Wasm contract requires unsupported import: "env.foo". Required imports: {"env.bar", "env.foo", "env.spammyspam01", "env.spammyspam02", "env.spammyspam03", "env.spammyspam04", "env.spammyspam05", "env.spammyspam06", "env.spammyspam07", "env.spammyspam08", ... 2 more}. Available imports: ["env.db_read", "env.db_write", "env.db_remove", "env.addr_canonicalize", "env.addr_humanize", "env.debug", "env.query_chain"]."# ); } err => panic!("Unexpected error: {:?}", err), diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 25ffd5afa4..8ab34bfdcd 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -429,8 +429,8 @@ mod tests { "db_scan" => Function::new_native(&store, |_a: u32, _b: u32, _c: i32| -> u32 { 0 }), "db_next" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "query_chain" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), - "canonicalize_address" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), - "humanize_address" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), + "addr_canonicalize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), + "addr_humanize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "secp256k1_recover_pubkey" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }), "ed25519_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index d40b8fc1ab..600cd6c488 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -74,20 +74,20 @@ pub fn native_db_remove( do_remove(env, key_ptr) } -pub fn native_canonicalize_address( +pub fn native_addr_canonicalize( env: &Environment, source_ptr: u32, destination_ptr: u32, ) -> VmResult { - do_canonicalize_address(&env, source_ptr, destination_ptr) + do_addr_canonicalize(&env, source_ptr, destination_ptr) } -pub fn native_humanize_address( +pub fn native_addr_humanize( env: &Environment, source_ptr: u32, destination_ptr: u32, ) -> VmResult { - do_humanize_address(&env, source_ptr, destination_ptr) + do_addr_humanize(&env, source_ptr, destination_ptr) } pub fn native_secp256k1_verify( @@ -226,7 +226,7 @@ fn do_remove( Ok(()) } -fn do_canonicalize_address( +fn do_addr_canonicalize( env: &Environment, source_ptr: u32, destination_ptr: u32, @@ -256,7 +256,7 @@ fn do_canonicalize_address( } } -fn do_humanize_address( +fn do_addr_humanize( env: &Environment, source_ptr: u32, destination_ptr: u32, @@ -564,8 +564,8 @@ mod tests { "db_scan" => Function::new_native(&store, |_a: u32, _b: u32, _c: i32| -> u32 { 0 }), "db_next" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "query_chain" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), - "canonicalize_address" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), - "humanize_address" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), + "addr_canonicalize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), + "addr_humanize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), "secp256k1_recover_pubkey" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }), "ed25519_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), @@ -894,7 +894,7 @@ mod tests { } #[test] - fn do_canonicalize_address_works() { + fn do_addr_canonicalize_works() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); let api = MockApi::default(); @@ -905,14 +905,14 @@ mod tests { leave_default_data(&env); let api = MockApi::default(); - let res = do_canonicalize_address(&env, source_ptr, dest_ptr).unwrap(); + let res = do_addr_canonicalize(&env, source_ptr, dest_ptr).unwrap(); assert_eq!(res, 0); let data = force_read(&env, dest_ptr); assert_eq!(data.len(), api.canonical_length); } #[test] - fn do_canonicalize_address_reports_invalid_input_back_to_contract() { + fn do_addr_canonicalize_reports_invalid_input_back_to_contract() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); @@ -923,24 +923,24 @@ mod tests { leave_default_data(&env); - let res = do_canonicalize_address(&env, source_ptr1, dest_ptr).unwrap(); + let res = do_addr_canonicalize(&env, source_ptr1, dest_ptr).unwrap(); assert_ne!(res, 0); let err = String::from_utf8(force_read(&env, res)).unwrap(); assert_eq!(err, "Input is not valid UTF-8"); - let res = do_canonicalize_address(&env, source_ptr2, dest_ptr).unwrap(); + let res = do_addr_canonicalize(&env, source_ptr2, dest_ptr).unwrap(); assert_ne!(res, 0); let err = String::from_utf8(force_read(&env, res)).unwrap(); assert_eq!(err, "Input is empty"); - let res = do_canonicalize_address(&env, source_ptr3, dest_ptr).unwrap(); + let res = do_addr_canonicalize(&env, source_ptr3, dest_ptr).unwrap(); assert_ne!(res, 0); let err = String::from_utf8(force_read(&env, res)).unwrap(); assert_eq!(err, "Invalid input: human address too long"); } #[test] - fn do_canonicalize_address_fails_for_broken_backend() { + fn do_addr_canonicalize_fails_for_broken_backend() { let api = MockApi::new_failing("Temporarily unavailable"); let (env, mut instance) = make_instance(api); @@ -949,7 +949,7 @@ mod tests { leave_default_data(&env); - let result = do_canonicalize_address(&env, source_ptr, dest_ptr); + let result = do_addr_canonicalize(&env, source_ptr, dest_ptr); match result.unwrap_err() { VmError::BackendErr { source: BackendError::Unknown { msg, .. }, @@ -962,7 +962,7 @@ mod tests { } #[test] - fn do_canonicalize_address_fails_for_large_inputs() { + fn do_addr_canonicalize_fails_for_large_inputs() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); @@ -971,7 +971,7 @@ mod tests { leave_default_data(&env); - let result = do_canonicalize_address(&env, source_ptr, dest_ptr); + let result = do_addr_canonicalize(&env, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { source: @@ -988,7 +988,7 @@ mod tests { } #[test] - fn do_canonicalize_address_fails_for_small_destination_region() { + fn do_addr_canonicalize_fails_for_small_destination_region() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); @@ -997,7 +997,7 @@ mod tests { leave_default_data(&env); - let result = do_canonicalize_address(&env, source_ptr, dest_ptr); + let result = do_addr_canonicalize(&env, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { source: CommunicationError::RegionTooSmall { size, required, .. }, @@ -1011,7 +1011,7 @@ mod tests { } #[test] - fn do_humanize_address_works() { + fn do_addr_humanize_works() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); let api = MockApi::default(); @@ -1022,13 +1022,13 @@ mod tests { leave_default_data(&env); - let error_ptr = do_humanize_address(&env, source_ptr, dest_ptr).unwrap(); + let error_ptr = do_addr_humanize(&env, source_ptr, dest_ptr).unwrap(); assert_eq!(error_ptr, 0); assert_eq!(force_read(&env, dest_ptr), source_data); } #[test] - fn do_humanize_address_reports_invalid_input_back_to_contract() { + fn do_addr_humanize_reports_invalid_input_back_to_contract() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); @@ -1037,14 +1037,14 @@ mod tests { leave_default_data(&env); - let res = do_humanize_address(&env, source_ptr, dest_ptr).unwrap(); + let res = do_addr_humanize(&env, source_ptr, dest_ptr).unwrap(); assert_ne!(res, 0); let err = String::from_utf8(force_read(&env, res)).unwrap(); assert_eq!(err, "Invalid input: canonical address length not correct"); } #[test] - fn do_humanize_address_fails_for_broken_backend() { + fn do_addr_humanize_fails_for_broken_backend() { let api = MockApi::new_failing("Temporarily unavailable"); let (env, mut instance) = make_instance(api); @@ -1053,7 +1053,7 @@ mod tests { leave_default_data(&env); - let result = do_humanize_address(&env, source_ptr, dest_ptr); + let result = do_addr_humanize(&env, source_ptr, dest_ptr); match result.unwrap_err() { VmError::BackendErr { source: BackendError::Unknown { msg, .. }, @@ -1064,7 +1064,7 @@ mod tests { } #[test] - fn do_humanize_address_fails_for_input_too_long() { + fn do_addr_humanize_fails_for_input_too_long() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); @@ -1073,7 +1073,7 @@ mod tests { leave_default_data(&env); - let result = do_humanize_address(&env, source_ptr, dest_ptr); + let result = do_addr_humanize(&env, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { source: @@ -1090,7 +1090,7 @@ mod tests { } #[test] - fn do_humanize_address_fails_for_destination_region_too_small() { + fn do_addr_humanize_fails_for_destination_region_too_small() { let api = MockApi::default(); let (env, mut instance) = make_instance(api); let api = MockApi::default(); @@ -1101,7 +1101,7 @@ mod tests { leave_default_data(&env); - let result = do_humanize_address(&env, source_ptr, dest_ptr); + let result = do_addr_humanize(&env, source_ptr, dest_ptr); match result.unwrap_err() { VmError::CommunicationErr { source: CommunicationError::RegionTooSmall { size, required, .. }, diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index 1e9076842c..5d7bfcbf65 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -9,8 +9,8 @@ use crate::environment::Environment; use crate::errors::{CommunicationError, VmError, VmResult}; use crate::features::required_features_from_wasmer_instance; use crate::imports::{ - native_canonicalize_address, native_db_read, native_db_remove, native_db_write, native_debug, - native_ed25519_batch_verify, native_ed25519_verify, native_humanize_address, + native_addr_canonicalize, native_addr_humanize, native_db_read, native_db_remove, + native_db_write, native_debug, native_ed25519_batch_verify, native_ed25519_verify, native_query_chain, native_secp256k1_recover_pubkey, native_secp256k1_verify, }; #[cfg(feature = "iterator")] @@ -110,8 +110,8 @@ where // Returns 0 on success. Returns a non-zero memory location to a Region containing an UTF-8 encoded error string for invalid inputs. // Ownership of both input and output pointer is not transferred to the host. env_imports.insert( - "canonicalize_address", - Function::new_native_with_env(store, env.clone(), native_canonicalize_address), + "addr_canonicalize", + Function::new_native_with_env(store, env.clone(), native_addr_canonicalize), ); // Reads canonical address from source_ptr and writes humanized representation to destination_ptr. @@ -119,8 +119,8 @@ where // Returns 0 on success. Returns a non-zero memory location to a Region containing an UTF-8 encoded error string for invalid inputs. // Ownership of both input and output pointer is not transferred to the host. env_imports.insert( - "humanize_address", - Function::new_native_with_env(store, env.clone(), native_humanize_address), + "addr_humanize", + Function::new_native_with_env(store, env.clone(), native_addr_humanize), ); // Verifies message hashes against a signature with a public key, using the secp256k1 ECDSA parametrization. From 09a3fe0fab578049c4fe556f9471bc83fc18a6c7 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 30 Mar 2021 22:33:15 +0200 Subject: [PATCH 08/38] Update testing contracts --- packages/vm/src/instance.rs | 8 ++++---- packages/vm/testdata/hackatom_0.14.wasm | Bin 207200 -> 205967 bytes packages/vm/testdata/ibc_reflect_0.14.wasm | Bin 269324 -> 267615 bytes 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index 5d7bfcbf65..84090f5272 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -597,7 +597,7 @@ mod tests { let report2 = instance.create_gas_report(); assert_eq!(report2.used_externally, 146); - assert_eq!(report2.used_internally, 52531); + assert_eq!(report2.used_internally, 52702); assert_eq!(report2.limit, LIMIT); assert_eq!( report2.remaining, @@ -796,7 +796,7 @@ mod singlepass_tests { .unwrap(); let init_used = orig_gas - instance.get_gas_left(); - assert_eq!(init_used, 52677); + assert_eq!(init_used, 52848); } #[test] @@ -819,7 +819,7 @@ mod singlepass_tests { .unwrap(); let execute_used = gas_before_execute - instance.get_gas_left(); - assert_eq!(execute_used, 168564); + assert_eq!(execute_used, 168158); } #[test] @@ -853,6 +853,6 @@ mod singlepass_tests { assert_eq!(answer.as_slice(), b"{\"verifier\":\"verifies\"}"); let query_used = gas_before_query - instance.get_gas_left(); - assert_eq!(query_used, 38502); + assert_eq!(query_used, 38349); } } diff --git a/packages/vm/testdata/hackatom_0.14.wasm b/packages/vm/testdata/hackatom_0.14.wasm index 875d77f12f80013f31f0c0c4b87b96f8d59ca318..6fdf0187d1dd3a7d3bfffc480fb3514800a69ce9 100644 GIT binary patch delta 70330 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 delta 70984 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 Qv{%z&JT7fImniRl18F|FPXGV_ diff --git a/packages/vm/testdata/ibc_reflect_0.14.wasm b/packages/vm/testdata/ibc_reflect_0.14.wasm index 5f5b185370b5c34c24184434f23b3f4ca184dcfd..39e9ba08e49818357d6e12273dc98a764cde99ee 100644 GIT binary patch literal 267615 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 literal 269324 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 From d42a2ce01a09a9e9140cfe4cd0743cb000858acc Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Mar 2021 09:21:26 +0100 Subject: [PATCH 09/38] Add addr_validate import --- CHANGELOG.md | 2 + packages/vm/src/compatibility.rs | 2 + packages/vm/src/environment.rs | 1 + packages/vm/src/imports.rs | 120 +++++++++++++++++++++++++++++++ packages/vm/src/instance.rs | 15 +++- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 499b718306..e933fed4a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ and this project adheres to returns "submessages" (above). ([#796]) - cosmwasm-std: Implement `From for String`, `From for u128` as well as `From for Uint128`. +- cosmwasm-vm: Add import `addr_validate` ([#802]). [#692]: https://github.com/CosmWasm/cosmwasm/issues/692 [#706]: https://github.com/CosmWasm/cosmwasm/pull/706 @@ -62,6 +63,7 @@ and this project adheres to [#768]: https://github.com/CosmWasm/cosmwasm/pull/768 [#793]: https://github.com/CosmWasm/cosmwasm/pull/793 [#796]: https://github.com/CosmWasm/cosmwasm/pull/796 +[#802]: https://github.com/CosmWasm/cosmwasm/pull/802 ### Changed diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index 4ab7328cf3..7c744ba393 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -13,6 +13,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[ "env.db_read", "env.db_write", "env.db_remove", + "env.addr_validate", "env.addr_canonicalize", "env.addr_humanize", "env.secp256k1_verify", @@ -319,6 +320,7 @@ mod tests { (import "env" "db_read" (func (param i32 i32) (result i32))) (import "env" "db_write" (func (param i32 i32) (result i32))) (import "env" "db_remove" (func (param i32) (result i32))) + (import "env" "addr_validate" (func (param i32) (result i32))) (import "env" "addr_canonicalize" (func (param i32 i32) (result i32))) (import "env" "addr_humanize" (func (param i32 i32) (result i32))) (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32))) diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 8ab34bfdcd..eda6014381 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -429,6 +429,7 @@ mod tests { "db_scan" => Function::new_native(&store, |_a: u32, _b: u32, _c: i32| -> u32 { 0 }), "db_next" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "query_chain" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), + "addr_validate" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "addr_canonicalize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "addr_humanize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 600cd6c488..82ed0ffc9a 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -74,6 +74,13 @@ pub fn native_db_remove( do_remove(env, key_ptr) } +pub fn native_addr_validate( + env: &Environment, + source_ptr: u32, +) -> VmResult { + do_addr_validate(&env, source_ptr) +} + pub fn native_addr_canonicalize( env: &Environment, source_ptr: u32, @@ -226,6 +233,32 @@ fn do_remove( Ok(()) } +fn do_addr_validate( + env: &Environment, + source_ptr: u32, +) -> VmResult { + let source_data = read_region(&env.memory(), source_ptr, MAX_LENGTH_HUMAN_ADDRESS)?; + if source_data.is_empty() { + return write_to_contract::(env, b"Input is empty"); + } + + let source_string = match String::from_utf8(source_data) { + Ok(s) => s, + Err(_) => return write_to_contract::(env, b"Input is not valid UTF-8"), + }; + let human: HumanAddr = source_string.into(); + + let (result, gas_info) = env.api.canonical_address(&human); + process_gas_info::(env, gas_info)?; + match result { + Ok(_canonical) => Ok(0), + Err(BackendError::UserErr { msg, .. }) => { + Ok(write_to_contract::(env, msg.as_bytes())?) + } + Err(err) => Err(VmError::from(err)), + } +} + fn do_addr_canonicalize( env: &Environment, source_ptr: u32, @@ -564,6 +597,7 @@ mod tests { "db_scan" => Function::new_native(&store, |_a: u32, _b: u32, _c: i32| -> u32 { 0 }), "db_next" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "query_chain" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), + "addr_validate" => Function::new_native(&store, |_a: u32| -> u32 { 0 }), "addr_canonicalize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "addr_humanize" => Function::new_native(&store, |_a: u32, _b: u32| -> u32 { 0 }), "secp256k1_verify" => Function::new_native(&store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }), @@ -893,6 +927,92 @@ mod tests { } } + #[test] + fn do_addr_validate_works() { + let api = MockApi::default(); + let (env, _instance) = make_instance(api); + + let source_ptr = write_data(&env, b"foo"); + + leave_default_data(&env); + + let res = do_addr_validate(&env, source_ptr).unwrap(); + assert_eq!(res, 0); + } + + #[test] + fn do_addr_validate_reports_invalid_input_back_to_contract() { + let api = MockApi::default(); + let (env, _instance) = make_instance(api); + + let source_ptr1 = write_data(&env, b"fo\x80o"); // invalid UTF-8 (fo�o) + let source_ptr2 = write_data(&env, b""); // empty + let source_ptr3 = write_data(&env, b"addressexceedingaddressspace"); // too long + + leave_default_data(&env); + + let res = do_addr_validate(&env, source_ptr1).unwrap(); + assert_ne!(res, 0); + let err = String::from_utf8(force_read(&env, res)).unwrap(); + assert_eq!(err, "Input is not valid UTF-8"); + + let res = do_addr_validate(&env, source_ptr2).unwrap(); + assert_ne!(res, 0); + let err = String::from_utf8(force_read(&env, res)).unwrap(); + assert_eq!(err, "Input is empty"); + + let res = do_addr_validate(&env, source_ptr3).unwrap(); + assert_ne!(res, 0); + let err = String::from_utf8(force_read(&env, res)).unwrap(); + assert_eq!(err, "Invalid input: human address too long"); + } + + #[test] + fn do_addr_validate_fails_for_broken_backend() { + let api = MockApi::new_failing("Temporarily unavailable"); + let (env, _instance) = make_instance(api); + + let source_ptr = write_data(&env, b"foo"); + + leave_default_data(&env); + + let result = do_addr_validate(&env, source_ptr); + match result.unwrap_err() { + VmError::BackendErr { + source: BackendError::Unknown { msg, .. }, + .. + } => { + assert_eq!(msg.unwrap(), "Temporarily unavailable"); + } + err => panic!("Incorrect error returned: {:?}", err), + } + } + + #[test] + fn do_addr_validate_fails_for_large_inputs() { + let api = MockApi::default(); + let (env, _instance) = make_instance(api); + + let source_ptr = write_data(&env, &[61; 100]); + + leave_default_data(&env); + + let result = do_addr_validate(&env, source_ptr); + match result.unwrap_err() { + VmError::CommunicationErr { + source: + CommunicationError::RegionLengthTooBig { + length, max_length, .. + }, + .. + } => { + assert_eq!(length, 100); + assert_eq!(max_length, 90); + } + err => panic!("Incorrect error returned: {:?}", err), + } + } + #[test] fn do_addr_canonicalize_works() { let api = MockApi::default(); diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index 84090f5272..bb9856cd38 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -9,9 +9,10 @@ use crate::environment::Environment; use crate::errors::{CommunicationError, VmError, VmResult}; use crate::features::required_features_from_wasmer_instance; use crate::imports::{ - native_addr_canonicalize, native_addr_humanize, native_db_read, native_db_remove, - native_db_write, native_debug, native_ed25519_batch_verify, native_ed25519_verify, - native_query_chain, native_secp256k1_recover_pubkey, native_secp256k1_verify, + native_addr_canonicalize, native_addr_humanize, native_addr_validate, native_db_read, + native_db_remove, native_db_write, native_debug, native_ed25519_batch_verify, + native_ed25519_verify, native_query_chain, native_secp256k1_recover_pubkey, + native_secp256k1_verify, }; #[cfg(feature = "iterator")] use crate::imports::{native_db_next, native_db_scan}; @@ -105,6 +106,14 @@ where Function::new_native_with_env(store, env.clone(), native_db_remove), ); + // Reads human address from source_ptr and checks if it is valid. + // Returns 0 on if the input is valid. Returns a non-zero memory location to a Region containing an UTF-8 encoded error string for invalid inputs. + // Ownership of the input pointer is not transferred to the host. + env_imports.insert( + "addr_validate", + Function::new_native_with_env(store, env.clone(), native_addr_validate), + ); + // Reads human address from source_ptr and writes canonicalized representation to destination_ptr. // A prepared and sufficiently large memory Region is expected at destination_ptr that points to pre-allocated memory. // Returns 0 on success. Returns a non-zero memory location to a Region containing an UTF-8 encoded error string for invalid inputs. From 98cd3a35031190ee79a760ebbbbba8e809de78aa Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 2 Mar 2021 09:33:09 +0100 Subject: [PATCH 10/38] Use addr_validate import --- README.md | 1 + packages/std/src/imports.rs | 17 +++++++++++++++++ packages/std/src/mock.rs | 5 +++++ packages/std/src/traits.rs | 5 +---- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f2475ddb92..58d606f98c 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ extern "C" { #[cfg(feature = "iterator")] fn db_next(iterator_id: u32) -> u32; + fn addr_validate(source_ptr: u32) -> u32; fn addr_canonicalize(source: u32, destination: u32) -> u32; fn addr_humanize(source: u32, destination: u32) -> u32; diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 7243e81762..99cf2a163b 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -36,6 +36,7 @@ extern "C" { #[cfg(feature = "iterator")] fn db_next(iterator_id: u32) -> u32; + fn addr_validate(source_ptr: u32) -> u32; fn addr_canonicalize(source_ptr: u32, destination_ptr: u32) -> u32; fn addr_humanize(source_ptr: u32, destination_ptr: u32) -> u32; @@ -155,6 +156,22 @@ impl ExternalApi { } impl Api for ExternalApi { + fn addr_validate(&self, human: &str) -> StdResult { + let source = build_region(human.as_bytes()); + let source_ptr = &*source as *const Region as u32; + + let result = unsafe { addr_validate(source_ptr) }; + if result != 0 { + let error = unsafe { consume_string_region_written_by_vm(result as *mut Region) }; + return Err(StdError::generic_err(format!( + "addr_validate errored: {}", + error + ))); + } + + Ok(human.into()) + } + fn addr_canonicalize(&self, human: &str) -> StdResult { let send = build_region(human.as_bytes()); let send_ptr = &*send as *const Region as u32; diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 6f8152b0cd..a05cfb18eb 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -71,6 +71,11 @@ impl Default for MockApi { } impl Api for MockApi { + fn addr_validate(&self, human: &str) -> StdResult { + self.addr_canonicalize(human).map(|_canonical| ())?; + Ok(human.into()) + } + fn addr_canonicalize(&self, human: &str) -> StdResult { // Dummy input validation. This is more sophisticated for formats like bech32, where format and checksum are validated. if human.len() < 3 { diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 20288a7db8..41722399a4 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -77,10 +77,7 @@ pub trait Api { /// let validated: HumanAddr = api.addr_validate(input).unwrap(); /// assert_eq!(validated, input); /// ``` - fn addr_validate(&self, human: &str) -> StdResult { - self.addr_canonicalize(human).map(|_canonical| ())?; - Ok(human.into()) - } + fn addr_validate(&self, human: &str) -> StdResult; /// Takes a human readable address and returns a canonical binary representation of it. /// This can be used when a compact fixed length representation is needed. From 227da0aa9fae4b2054d09beb68391caee1ece4ea Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 11:56:24 +0200 Subject: [PATCH 11/38] Let addr_validate return the new Addr type --- CHANGELOG.md | 2 + packages/std/src/addresses.rs | 76 +++++++++++++++++++++++++++++++++++ packages/std/src/imports.rs | 6 +-- packages/std/src/lib.rs | 2 +- packages/std/src/mock.rs | 6 +-- packages/std/src/traits.rs | 10 ++--- 6 files changed, 90 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e933fed4a7..19e1d2df84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to returns "submessages" (above). ([#796]) - cosmwasm-std: Implement `From for String`, `From for u128` as well as `From for Uint128`. +- cosmwasm-std: Create new address type `Addr`. This is human readable (like + `HumanAddr`) but is immutable and always contains a valid address ([#802]). - cosmwasm-vm: Add import `addr_validate` ([#802]). [#692]: https://github.com/CosmWasm/cosmwasm/issues/692 diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 05a107d7b0..edc24cbd14 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -5,6 +5,51 @@ use std::ops::Deref; use crate::binary::Binary; +/// A human readable address. +/// +/// In Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no +/// assumptions should be made other than being UTF-8 encoded and of reasonable length. +/// +/// This type represents a validated address. It can be created in the following ways +/// 1. Use `Addr::unchecked(input)` +/// 2. Use `let checked: Addr = deps.api.addr_validate(input)?` +/// 3. Deserialize from JSON. This must only be done from JSON that was validated before +/// such as a contract's state. `Addr` must not be used in messages sent by the user +/// because this would result in unvalidated instances. +/// +/// This type is immutable. If you really need to mutate it (Really? Are you sure?), create +/// a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` +/// instance. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Addr(String); + +impl Addr { + pub fn unchecked>(input: T) -> Addr { + Addr(input.into()) + } +} + +impl fmt::Display for Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +// Addr->String and Addr->HumanAddr are safe conversions. +// However, the opposite direction is unsafe and must not be implemented. + +impl From for String { + fn from(addr: Addr) -> Self { + addr.0 + } +} + +impl From for HumanAddr { + fn from(addr: Addr) -> Self { + HumanAddr(addr.0) + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] pub struct HumanAddr(pub String); @@ -151,6 +196,37 @@ mod tests { use std::hash::{Hash, Hasher}; use std::iter::FromIterator; + #[test] + fn addr_unchecked_works() { + let a = Addr::unchecked("123"); + let aa = Addr::unchecked(String::from("123")); + let b = Addr::unchecked("be"); + assert_eq!(a, aa); + assert_ne!(a, b); + } + + #[test] + fn addr_implements_display() { + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + let embedded = format!("Address: {}", addr); + assert_eq!(embedded, "Address: cos934gh9034hg04g0h134"); + assert_eq!(addr.to_string(), "cos934gh9034hg04g0h134"); + } + + #[test] + fn addr_implements_into_string() { + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + let string: String = addr.into(); + assert_eq!(string, "cos934gh9034hg04g0h134"); + } + + #[test] + fn addr_implements_into_human_address() { + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + let human: HumanAddr = addr.into(); + assert_eq!(human, "cos934gh9034hg04g0h134"); + } + // Test HumanAddr as_str() for each HumanAddr::from input type #[test] fn human_addr_as_str() { diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 99cf2a163b..c9c410c708 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -1,6 +1,6 @@ use std::vec::Vec; -use crate::addresses::{CanonicalAddr, HumanAddr}; +use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; use crate::binary::Binary; use crate::errors::{RecoverPubkeyError, StdError, StdResult, SystemError, VerificationError}; use crate::import_helpers::{from_high_half, from_low_half}; @@ -156,7 +156,7 @@ impl ExternalApi { } impl Api for ExternalApi { - fn addr_validate(&self, human: &str) -> StdResult { + fn addr_validate(&self, human: &str) -> StdResult { let source = build_region(human.as_bytes()); let source_ptr = &*source as *const Region as u32; @@ -169,7 +169,7 @@ impl Api for ExternalApi { ))); } - Ok(human.into()) + Ok(Addr::unchecked(human)) } fn addr_canonicalize(&self, human: &str) -> StdResult { diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 069e348733..604b43a2aa 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -22,7 +22,7 @@ mod storage; mod traits; mod types; -pub use crate::addresses::{CanonicalAddr, HumanAddr}; +pub use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; pub use crate::binary::Binary; pub use crate::coins::{coin, coins, has_coins, Coin}; pub use crate::deps::{Deps, DepsMut, OwnedDeps}; diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index a05cfb18eb..d2b685ad84 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use std::collections::HashMap; -use crate::addresses::{CanonicalAddr, HumanAddr}; +use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; use crate::binary::Binary; use crate::coins::Coin; use crate::deps::OwnedDeps; @@ -71,9 +71,9 @@ impl Default for MockApi { } impl Api for MockApi { - fn addr_validate(&self, human: &str) -> StdResult { + fn addr_validate(&self, human: &str) -> StdResult { self.addr_canonicalize(human).map(|_canonical| ())?; - Ok(human.into()) + Ok(Addr::unchecked(human)) } fn addr_canonicalize(&self, human: &str) -> StdResult { diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 41722399a4..db541f84f4 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -1,7 +1,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::ops::Deref; -use crate::addresses::{CanonicalAddr, HumanAddr}; +use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; use crate::binary::Binary; use crate::coins::Coin; use crate::errors::{RecoverPubkeyError, StdError, StdResult, VerificationError}; @@ -65,19 +65,19 @@ pub trait Storage { /// for backwards compatibility in systems that don't have them all. pub trait Api { /// Takes a human readable address and validates if it's correctly formatted. - /// If it succeeds, a HumanAddr is returned. + /// If it succeeds, a Addr is returned. /// /// ## Examples /// /// ``` - /// # use cosmwasm_std::{Api, HumanAddr}; + /// # use cosmwasm_std::{Api, Addr}; /// # use cosmwasm_std::testing::MockApi; /// # let api = MockApi::default(); /// let input = "what-users-provide"; - /// let validated: HumanAddr = api.addr_validate(input).unwrap(); + /// let validated: Addr = api.addr_validate(input).unwrap(); /// assert_eq!(validated, input); /// ``` - fn addr_validate(&self, human: &str) -> StdResult; + fn addr_validate(&self, human: &str) -> StdResult; /// Takes a human readable address and returns a canonical binary representation of it. /// This can be used when a compact fixed length representation is needed. From e6689741b55531aa1cb72aa3a1386fde20694ea2 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 13:22:27 +0200 Subject: [PATCH 12/38] Implement PartialEq between Addr and &str --- packages/std/src/addresses.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index edc24cbd14..9cae7dd73d 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -35,6 +35,20 @@ impl fmt::Display for Addr { } } +/// Implement `Addr == &str` +impl PartialEq<&str> for Addr { + fn eq(&self, rhs: &&str) -> bool { + self.0 == *rhs + } +} + +/// Implement `&str == Addr` +impl PartialEq for &str { + fn eq(&self, rhs: &Addr) -> bool { + *self == rhs.0 + } +} + // Addr->String and Addr->HumanAddr are safe conversions. // However, the opposite direction is unsafe and must not be implemented. @@ -213,6 +227,16 @@ mod tests { assert_eq!(addr.to_string(), "cos934gh9034hg04g0h134"); } + #[test] + fn addr_implements_partial_eq_with_str() { + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + + // `Addr == &str` + assert_eq!(addr, "cos934gh9034hg04g0h134"); + // `&str == Addr` + assert_eq!("cos934gh9034hg04g0h134", addr); + } + #[test] fn addr_implements_into_string() { let addr = Addr::unchecked("cos934gh9034hg04g0h134"); From 77b008a5ff9d59baedca47488688ea54c45e7c53 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 14:51:37 +0200 Subject: [PATCH 13/38] Implement AsRef for Addr --- packages/std/src/addresses.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 9cae7dd73d..249db358ef 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -35,6 +35,13 @@ impl fmt::Display for Addr { } } +impl AsRef for Addr { + #[inline] + fn as_ref(&self) -> &str { + &self.0 + } +} + /// Implement `Addr == &str` impl PartialEq<&str> for Addr { fn eq(&self, rhs: &&str) -> bool { @@ -227,6 +234,12 @@ mod tests { assert_eq!(addr.to_string(), "cos934gh9034hg04g0h134"); } + #[test] + fn addr_implements_as_ref_for_str() { + let addr = Addr::unchecked("literal-string"); + assert_eq!(addr.as_ref(), "literal-string"); + } + #[test] fn addr_implements_partial_eq_with_str() { let addr = Addr::unchecked("cos934gh9034hg04g0h134"); From fd7bcaaa9b84712a6909fa3381e4e1c2a56e1948 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 13:45:40 +0200 Subject: [PATCH 14/38] Change type of ContractInfo::address to Addr --- CHANGELOG.md | 1 + contracts/burner/src/contract.rs | 4 +- contracts/hackatom/src/contract.rs | 11 +++-- contracts/ibc-reflect/src/contract.rs | 2 +- contracts/reflect/src/contract.rs | 2 +- contracts/staking/src/contract.rs | 16 +++---- packages/std/examples/schema.rs | 3 +- packages/std/schema/env.json | 64 --------------------------- packages/std/src/mock.rs | 2 +- packages/std/src/types.rs | 18 ++++---- packages/vm/src/testing/mock.rs | 6 ++- 11 files changed, 36 insertions(+), 93 deletions(-) delete mode 100644 packages/std/schema/env.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e1d2df84..1f13bc4d05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,6 +110,7 @@ and this project adheres to - cosmwasm-std: Rename type `KV` to `Pair` in order to comply to naming convention as enforced by clippy rule `upper_case_acronyms` from Rust 1.51.0 on. +- cosmwasm-std: `ContractInfo::address` is now of type `Addr`. - cosmwasm-vm: Bump required marker export `cosmwasm_vm_version_4` to `interface_version_5`. - cosmwasm-vm: Rename trait `Api` to `BackendApi` to better express this is the diff --git a/contracts/burner/src/contract.rs b/contracts/burner/src/contract.rs index 24334cf8ee..d012386875 100644 --- a/contracts/burner/src/contract.rs +++ b/contracts/burner/src/contract.rs @@ -30,7 +30,9 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult } // get balance and send all to recipient - let balance = deps.querier.query_all_balances(&env.contract.address)?; + let balance = deps + .querier + .query_all_balances(env.contract.address.clone())?; let send = BankMsg::Send { to_address: msg.payout.clone(), amount: balance, diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 9ffd258550..ba63922901 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -173,7 +173,7 @@ fn do_release(deps: DepsMut, env: Env, info: MessageInfo) -> Result StdResult { match msg { QueryMsg::Verifier {} => to_binary(&query_verifier(deps)?), QueryMsg::OtherBalance { address } => to_binary(&query_other_balance(deps, address)?), - QueryMsg::Recurse { depth, work } => { - to_binary(&query_recurse(deps, depth, work, env.contract.address)?) - } + QueryMsg::Recurse { depth, work } => to_binary(&query_recurse( + deps, + depth, + work, + env.contract.address.into(), + )?), } } diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index 70b770b555..485900a828 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -175,7 +175,7 @@ pub fn ibc_channel_close( let amount = deps.querier.query_all_balances(&reflect_addr)?; let messages: Vec> = if !amount.is_empty() { let bank_msg = BankMsg::Send { - to_address: env.contract.address, + to_address: env.contract.address.into(), amount, }; let reflect_msg = ReflectExecuteMsg::ReflectMsg { diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index 964a2d9941..4af3c5253b 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -26,7 +26,7 @@ pub fn instantiate( if let Some(id) = msg.callback_id { let data = CallbackMsg::InitCallback { id, - contract_addr: env.contract.address, + contract_addr: env.contract.address.into(), }; let msg = WasmMsg::Execute { contract_addr: info.sender, diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index defccb1428..7b96663507 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -109,7 +109,7 @@ pub fn transfer( // get_bonded returns the total amount of delegations from contract // it ensures they are all the same denom -fn get_bonded(querier: &QuerierWrapper, contract: &HumanAddr) -> StdResult { +fn get_bonded(querier: &QuerierWrapper, contract: HumanAddr) -> StdResult { let bonds = querier.query_all_delegations(contract)?; if bonds.is_empty() { return Ok(Uint128(0)); @@ -152,7 +152,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { .ok_or_else(|| StdError::generic_err(format!("No {} tokens sent", &invest.bond_denom)))?; // bonded is the total number of tokens we have delegated from this address - let bonded = get_bonded(&deps.querier, &env.contract.address)?; + let bonded = get_bonded(&deps.querier, env.contract.address.into())?; // calculate to_mint and update total supply let mut totals = total_supply(deps.storage); @@ -220,7 +220,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St // re-calculate bonded to ensure we have real values // bonded is the total number of tokens we have delegated from this address - let bonded = get_bonded(&deps.querier, &env.contract.address)?; + let bonded = get_bonded(&deps.querier, env.contract.address.into())?; // calculate how many native tokens this is worth and update supply let remainder = amount.checked_sub(tax)?; @@ -263,7 +263,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult let invest = invest_info_read(deps.storage).load()?; let mut balance = deps .querier - .query_balance(&env.contract.address, &invest.bond_denom)?; + .query_balance(env.contract.address, &invest.bond_denom)?; if balance.amount < invest.min_withdrawal { return Err(StdError::generic_err( "Insufficient balance in contract to process claim", @@ -318,11 +318,11 @@ pub fn reinvest(deps: DepsMut, env: Env, _info: MessageInfo) -> StdResult Result { // this is just meant as a call-back to ourself - if info.sender != env.contract.address { + if info.sender.as_str() != env.contract.address { return Err(Unauthorized {}.build()); } @@ -348,7 +348,7 @@ pub fn _bond_all_tokens( let invest = invest_info_read(deps.storage).load()?; let mut balance = deps .querier - .query_balance(&env.contract.address, &invest.bond_denom)?; + .query_balance(env.contract.address, &invest.bond_denom)?; // we deduct pending claims from our account balance before reinvesting. // if there is not enough funds, we just return a no-op diff --git a/packages/std/examples/schema.rs b/packages/std/examples/schema.rs index 0c1eb1ca96..96c5f50aff 100644 --- a/packages/std/examples/schema.rs +++ b/packages/std/examples/schema.rs @@ -2,7 +2,7 @@ use std::env::current_dir; use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; -use cosmwasm_std::{CosmosMsg, Env, MessageInfo}; +use cosmwasm_std::{CosmosMsg, MessageInfo}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -10,7 +10,6 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); - export_schema(&schema_for!(Env), &out_dir); export_schema(&schema_for!(MessageInfo), &out_dir); export_schema_with_title(&mut schema_for!(CosmosMsg), &out_dir, "CosmosMsg"); } diff --git a/packages/std/schema/env.json b/packages/std/schema/env.json deleted file mode 100644 index 5a44676efe..0000000000 --- a/packages/std/schema/env.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Env", - "type": "object", - "required": [ - "block", - "contract" - ], - "properties": { - "block": { - "$ref": "#/definitions/BlockInfo" - }, - "contract": { - "$ref": "#/definitions/ContractInfo" - } - }, - "definitions": { - "BlockInfo": { - "type": "object", - "required": [ - "chain_id", - "height", - "time", - "time_nanos" - ], - "properties": { - "chain_id": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "time": { - "description": "Absolute time of the block creation in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC).\n\nThe source of this is the [BFT Time in Tendermint](https://docs.tendermint.com/master/spec/consensus/bft-time.html), converted from nanoseconds to second precision by truncating the fractioal part.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "time_nanos": { - "description": "The fractional part of the block time in nanoseconds since `time` (0 to 999999999). Add this to `time` if you need a high precision block time.\n\n# Examples\n\nUsing chrono:\n\n``` # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; # let env = Env { # block: BlockInfo { # height: 12_345, # time: 1_571_797_419, # time_nanos: 879305533, # chain_id: \"cosmos-testnet-14002\".to_string(), # }, # contract: ContractInfo { # address: HumanAddr::from(\"contract\"), # }, # }; # extern crate chrono; use chrono::NaiveDateTime; let dt = NaiveDateTime::from_timestamp(env.block.time as i64, env.block.time_nanos as u32); ```\n\nCreating a simple millisecond-precision timestamp (as used in JavaScript):\n\n``` # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; # let env = Env { # block: BlockInfo { # height: 12_345, # time: 1_571_797_419, # time_nanos: 879305533, # chain_id: \"cosmos-testnet-14002\".to_string(), # }, # contract: ContractInfo { # address: HumanAddr::from(\"contract\"), # }, # }; let millis = (env.block.time * 1_000) + (env.block.time_nanos / 1_000_000); ```", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "ContractInfo": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "$ref": "#/definitions/HumanAddr" - } - } - }, - "HumanAddr": { - "type": "string" - } - } -} diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index d2b685ad84..03ca9839f2 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -192,7 +192,7 @@ pub fn mock_env() -> Env { chain_id: "cosmos-testnet-14002".to_string(), }, contract: ContractInfo { - address: HumanAddr::from(MOCK_CONTRACT_ADDR), + address: Addr::unchecked(MOCK_CONTRACT_ADDR), }, } } diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs index dd6d0911ad..ce84d8a85c 100644 --- a/packages/std/src/types.rs +++ b/packages/std/src/types.rs @@ -1,16 +1,16 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::addresses::HumanAddr; +use crate::addresses::{Addr, HumanAddr}; use crate::coins::Coin; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct Env { pub block: BlockInfo, pub contract: ContractInfo, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct BlockInfo { pub height: u64, /// Absolute time of the block creation in seconds since the UNIX epoch (00:00:00 on 1970-01-01 UTC). @@ -26,7 +26,7 @@ pub struct BlockInfo { /// Using chrono: /// /// ``` - /// # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; + /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo}; /// # let env = Env { /// # block: BlockInfo { /// # height: 12_345, @@ -35,7 +35,7 @@ pub struct BlockInfo { /// # chain_id: "cosmos-testnet-14002".to_string(), /// # }, /// # contract: ContractInfo { - /// # address: HumanAddr::from("contract"), + /// # address: Addr::unchecked("contract"), /// # }, /// # }; /// # extern crate chrono; @@ -46,7 +46,7 @@ pub struct BlockInfo { /// Creating a simple millisecond-precision timestamp (as used in JavaScript): /// /// ``` - /// # use cosmwasm_std::{BlockInfo, ContractInfo, Env, HumanAddr, MessageInfo}; + /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo}; /// # let env = Env { /// # block: BlockInfo { /// # height: 12_345, @@ -55,7 +55,7 @@ pub struct BlockInfo { /// # chain_id: "cosmos-testnet-14002".to_string(), /// # }, /// # contract: ContractInfo { - /// # address: HumanAddr::from("contract"), + /// # address: Addr::unchecked("contract"), /// # }, /// # }; /// let millis = (env.block.time * 1_000) + (env.block.time_nanos / 1_000_000); @@ -89,7 +89,7 @@ pub struct MessageInfo { pub funds: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct ContractInfo { - pub address: HumanAddr, + pub address: Addr, } diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index b51fc5c971..6e8a9affca 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -1,5 +1,7 @@ use cosmwasm_std::testing::{digit_sum, riffle_shuffle}; -use cosmwasm_std::{BlockInfo, CanonicalAddr, Coin, ContractInfo, Env, HumanAddr, MessageInfo}; +use cosmwasm_std::{ + Addr, BlockInfo, CanonicalAddr, Coin, ContractInfo, Env, HumanAddr, MessageInfo, +}; use super::querier::MockQuerier; use super::storage::MockStorage; @@ -150,7 +152,7 @@ pub fn mock_env() -> Env { chain_id: "cosmos-testnet-14002".to_string(), }, contract: ContractInfo { - address: HumanAddr::from(MOCK_CONTRACT_ADDR), + address: Addr::unchecked(MOCK_CONTRACT_ADDR), }, } } From fdb41aec6fe8188d888a92dd6aca388f20a841a4 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 14:13:19 +0200 Subject: [PATCH 15/38] Remove HumanAddr/CanonicalAddr from BackendApi --- CHANGELOG.md | 4 ++++ contracts/hackatom/tests/integration.rs | 12 +++++----- packages/vm/src/backend.rs | 6 ++--- packages/vm/src/imports.rs | 12 ++++------ packages/vm/src/testing/mock.rs | 32 ++++++++++++------------- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f13bc4d05..792b746432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,10 @@ and this project adheres to API provided by the VM's backend (i.e. the blockchain). - cosmwasm-vm: Rename imports to `addr_canonicalize` and `addr_humanize` ([#802]). +- cosmwasm-vm: Replace types `HumanAddr`/`CanonicalAddr` with + `&str`/`String`/`&[u8]`/`Vec` in the methods of `BackendApi`. The address + types belong in the contract development and the backend operates on raw + strings and binary anyways. - contracts: `reflect` contract requires `stargate` feature and supports redispatching `Stargate` and `IbcMsg::Transfer` messages ([#692]) - cosmwasm-std: The arithmetic methods of `Uint128` got a huge overhaul, making diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index 62d42408b2..cc2316a66d 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -58,9 +58,9 @@ fn proper_initialization() { let beneficiary = HumanAddr(String::from("benefits")); let creator = HumanAddr(String::from("creator")); let expected_state = State { - verifier: deps.api().canonical_address(&verifier).0.unwrap(), - beneficiary: deps.api().canonical_address(&beneficiary).0.unwrap(), - funder: deps.api().canonical_address(&creator).0.unwrap(), + verifier: deps.api().canonical_address(&verifier).0.unwrap().into(), + beneficiary: deps.api().canonical_address(&beneficiary).0.unwrap().into(), + funder: deps.api().canonical_address(&creator).0.unwrap().into(), }; let msg = InstantiateMsg { @@ -301,9 +301,9 @@ fn execute_release_fails_for_wrong_sender() { assert_eq!( state, State { - verifier: deps.api().canonical_address(&verifier).0.unwrap(), - beneficiary: deps.api().canonical_address(&beneficiary).0.unwrap(), - funder: deps.api().canonical_address(&creator).0.unwrap(), + verifier: deps.api().canonical_address(&verifier).0.unwrap().into(), + beneficiary: deps.api().canonical_address(&beneficiary).0.unwrap().into(), + funder: deps.api().canonical_address(&creator).0.unwrap().into(), } ); } diff --git a/packages/vm/src/backend.rs b/packages/vm/src/backend.rs index 3fb238310a..11771bc361 100644 --- a/packages/vm/src/backend.rs +++ b/packages/vm/src/backend.rs @@ -3,7 +3,7 @@ use std::ops::AddAssign; use std::string::FromUtf8Error; use thiserror::Error; -use cosmwasm_std::{Binary, CanonicalAddr, ContractResult, HumanAddr, SystemResult}; +use cosmwasm_std::{Binary, ContractResult, SystemResult}; #[cfg(feature = "iterator")] use cosmwasm_std::{Order, Pair}; @@ -125,8 +125,8 @@ pub trait Storage { /// We can use feature flags to opt-in to non-essential methods /// for backwards compatibility in systems that don't have them all. pub trait BackendApi: Copy + Clone + Send { - fn canonical_address(&self, human: &HumanAddr) -> BackendResult; - fn human_address(&self, canonical: &CanonicalAddr) -> BackendResult; + fn canonical_address(&self, human: &str) -> BackendResult>; + fn human_address(&self, canonical: &[u8]) -> BackendResult; } pub trait Querier { diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 82ed0ffc9a..1007642e4f 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -13,7 +13,6 @@ use cosmwasm_crypto::{ #[cfg(feature = "iterator")] use cosmwasm_std::Order; -use cosmwasm_std::{CanonicalAddr, HumanAddr}; use crate::backend::{BackendApi, BackendError, Querier, Storage}; use crate::conversion::{ref_to_u32, to_u32}; @@ -246,9 +245,8 @@ fn do_addr_validate( Ok(s) => s, Err(_) => return write_to_contract::(env, b"Input is not valid UTF-8"), }; - let human: HumanAddr = source_string.into(); - let (result, gas_info) = env.api.canonical_address(&human); + let (result, gas_info) = env.api.canonical_address(&source_string); process_gas_info::(env, gas_info)?; match result { Ok(_canonical) => Ok(0), @@ -273,9 +271,8 @@ fn do_addr_canonicalize( Ok(s) => s, Err(_) => return write_to_contract::(env, b"Input is not valid UTF-8"), }; - let human: HumanAddr = source_string.into(); - let (result, gas_info) = env.api.canonical_address(&human); + let (result, gas_info) = env.api.canonical_address(&source_string); process_gas_info::(env, gas_info)?; match result { Ok(canonical) => { @@ -294,14 +291,13 @@ fn do_addr_humanize( source_ptr: u32, destination_ptr: u32, ) -> VmResult { - let canonical: CanonicalAddr = - read_region(&env.memory(), source_ptr, MAX_LENGTH_CANONICAL_ADDRESS)?.into(); + let canonical = read_region(&env.memory(), source_ptr, MAX_LENGTH_CANONICAL_ADDRESS)?; let (result, gas_info) = env.api.human_address(&canonical); process_gas_info::(env, gas_info)?; match result { Ok(human) => { - write_region(&env.memory(), destination_ptr, human.as_str().as_bytes())?; + write_region(&env.memory(), destination_ptr, human.as_bytes())?; Ok(0) } Err(BackendError::UserErr { msg, .. }) => { diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index 6e8a9affca..09b9e50bce 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -1,7 +1,5 @@ use cosmwasm_std::testing::{digit_sum, riffle_shuffle}; -use cosmwasm_std::{ - Addr, BlockInfo, CanonicalAddr, Coin, ContractInfo, Env, HumanAddr, MessageInfo, -}; +use cosmwasm_std::{Addr, BlockInfo, Coin, ContractInfo, Env, HumanAddr, MessageInfo}; use super::querier::MockQuerier; use super::storage::MockStorage; @@ -65,7 +63,7 @@ impl Default for MockApi { } impl BackendApi for MockApi { - fn canonical_address(&self, human: &HumanAddr) -> BackendResult { + fn canonical_address(&self, human: &str) -> BackendResult> { let gas_info = GasInfo::with_cost(GAS_COST_CANONICALIZE); if let Some(backend_error) = self.backend_error { @@ -90,7 +88,7 @@ impl BackendApi for MockApi { ); } - let mut out = Vec::from(human.as_str()); + let mut out = Vec::from(human); // pad to canonical length with NULL bytes out.resize(self.canonical_length, 0x00); // content-dependent rotate followed by shuffle to destroy @@ -100,10 +98,10 @@ impl BackendApi for MockApi { for _ in 0..18 { out = riffle_shuffle(&out); } - (Ok(out.into()), gas_info) + (Ok(out), gas_info) } - fn human_address(&self, canonical: &CanonicalAddr) -> BackendResult { + fn human_address(&self, canonical: &[u8]) -> BackendResult { let gas_info = GasInfo::with_cost(GAS_COST_HUMANIZE); if let Some(backend_error) = self.backend_error { @@ -119,7 +117,7 @@ impl BackendApi for MockApi { ); } - let mut tmp: Vec = canonical.clone().into(); + let mut tmp: Vec = canonical.into(); // Shuffle two more times which restored the original value (24 elements are back to original after 20 rounds) for _ in 0..2 { tmp = riffle_shuffle(&tmp); @@ -131,7 +129,7 @@ impl BackendApi for MockApi { let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect(); let result = match String::from_utf8(trimmed) { - Ok(human) => Ok(HumanAddr(human)), + Ok(human) => Ok(human), Err(err) => Err(err.into()), }; (result, gas_info) @@ -170,7 +168,7 @@ pub fn mock_info>(sender: U, funds: &[Coin]) -> MessageInfo { mod tests { use super::*; use crate::BackendError; - use cosmwasm_std::{coins, Binary}; + use cosmwasm_std::coins; #[test] fn mock_info_arguments() { @@ -190,8 +188,8 @@ mod tests { fn canonicalize_and_humanize_restores_original() { let api = MockApi::default(); - let original = HumanAddr::from("shorty"); - let canonical = api.canonical_address(&original).0.unwrap(); + let original = "shorty"; + let canonical = api.canonical_address(original).0.unwrap(); let (recovered, _gas_cost) = api.human_address(&canonical); assert_eq!(recovered.unwrap(), original); } @@ -199,7 +197,7 @@ mod tests { #[test] fn human_address_input_length() { let api = MockApi::default(); - let input = CanonicalAddr(Binary(vec![61; 11])); + let input = vec![61; 11]; let (result, _gas_info) = api.human_address(&input); match result.unwrap_err() { BackendError::UserErr { .. } => {} @@ -210,8 +208,8 @@ mod tests { #[test] fn canonical_address_min_input_length() { let api = MockApi::default(); - let human = HumanAddr::from("1"); - match api.canonical_address(&human).0.unwrap_err() { + let human = "1"; + match api.canonical_address(human).0.unwrap_err() { BackendError::UserErr { .. } => {} err => panic!("Unexpected error: {:?}", err), } @@ -220,8 +218,8 @@ mod tests { #[test] fn canonical_address_max_input_length() { let api = MockApi::default(); - let human = HumanAddr::from("longer-than-the-address-length-supported-by-this-api"); - match api.canonical_address(&human).0.unwrap_err() { + let human = "longer-than-the-address-length-supported-by-this-api"; + match api.canonical_address(human).0.unwrap_err() { BackendError::UserErr { .. } => {} err => panic!("Unexpected error: {:?}", err), } From bfe77697bc71a6ce0ac9aba3907cd18136fc956f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:55:06 +0200 Subject: [PATCH 16/38] Change type of MessageInfo::sender to Addr --- CHANGELOG.md | 3 +- contracts/hackatom/src/contract.rs | 4 +- contracts/ibc-reflect-send/src/contract.rs | 10 +++-- contracts/ibc-reflect/src/contract.rs | 6 +-- contracts/ibc-reflect/tests/integration.rs | 4 +- contracts/reflect/src/contract.rs | 10 ++--- contracts/staking/src/contract.rs | 14 +++--- packages/std/examples/schema.rs | 5 +-- packages/std/schema/message_info.json | 50 ---------------------- packages/std/src/coins.rs | 4 +- packages/std/src/mock.rs | 8 ++-- packages/std/src/types.rs | 7 ++- packages/vm/src/testing/mock.rs | 8 ++-- 13 files changed, 40 insertions(+), 93 deletions(-) delete mode 100644 packages/std/schema/message_info.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 792b746432..93cd2148ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,7 +110,8 @@ and this project adheres to - cosmwasm-std: Rename type `KV` to `Pair` in order to comply to naming convention as enforced by clippy rule `upper_case_acronyms` from Rust 1.51.0 on. -- cosmwasm-std: `ContractInfo::address` is now of type `Addr`. +- cosmwasm-std: `ContractInfo::address` and `MessageInfo::sender` are now of + type `Addr`. The value of those fields is created by the host and thus valid. - cosmwasm-vm: Bump required marker export `cosmwasm_vm_version_4` to `interface_version_5`. - cosmwasm-vm: Rename trait `Api` to `BackendApi` to better express this is the diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index ba63922901..f09595779a 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -110,7 +110,7 @@ pub fn instantiate( &to_vec(&State { verifier: deps.api.addr_canonicalize(&msg.verifier)?, beneficiary: deps.api.addr_canonicalize(&msg.beneficiary)?, - funder: deps.api.addr_canonicalize(&info.sender)?, + funder: deps.api.addr_canonicalize(info.sender.as_ref())?, })?, ); @@ -171,7 +171,7 @@ fn do_release(deps: DepsMut, env: Env, info: MessageInfo) -> Result StdResult { // we store the reflect_id for creating accounts later - let cfg = Config { admin: info.sender }; + let cfg = Config { + admin: info.sender.into(), + }; config(deps.storage).save(&cfg)?; Ok(Response { @@ -54,7 +56,7 @@ pub fn handle_update_admin( ) -> StdResult { // auth check let mut cfg = config(deps.storage).load()?; - if info.sender != cfg.admin { + if info.sender.as_ref() != cfg.admin { return Err(StdError::generic_err("Only admin may set new admin")); } cfg.admin = new_admin; @@ -80,7 +82,7 @@ pub fn handle_send_msgs( ) -> StdResult { // auth check let cfg = config(deps.storage).load()?; - if info.sender != cfg.admin { + if info.sender.as_ref() != cfg.admin { return Err(StdError::generic_err("Only admin may send messages")); } // ensure the channel exists (not found if not registered) @@ -111,7 +113,7 @@ pub fn handle_check_remote_balance( ) -> StdResult { // auth check let cfg = config(deps.storage).load()?; - if info.sender != cfg.admin { + if info.sender.as_ref() != cfg.admin { return Err(StdError::generic_err("Only admin may send messages")); } // ensure the channel exists (not found if not registered) diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index 485900a828..5755626200 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -56,7 +56,7 @@ pub fn execute_init_callback( contract_addr: HumanAddr, ) -> StdResult { // sanity check - the caller is registering itself - if info.sender != contract_addr { + if info.sender.as_ref() != contract_addr { return Err(StdError::generic_err("Must register self on callback")); } @@ -349,7 +349,7 @@ mod tests { // connect will run through the entire handshake to set up a proper connect and // save the account (tested in detail in `proper_handshake_flow`) fn connect>(mut deps: DepsMut, channel_id: &str, account: T) { - let account = account.into(); + let account: HumanAddr = account.into(); // open packet has no counterparty versin, connect does // TODO: validate this with alpe @@ -367,7 +367,7 @@ mod tests { id: channel_id.into(), contract_addr: account.clone(), }; - let info = mock_info(account, &[]); + let info = mock_info(&account, &[]); execute(deps.branch(), mock_env(), info, execute_msg).unwrap(); } diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs index 0d59d86205..ab456cc0a3 100644 --- a/contracts/ibc-reflect/tests/integration.rs +++ b/contracts/ibc-reflect/tests/integration.rs @@ -61,7 +61,7 @@ fn connect>( channel_id: &str, account: T, ) { - let account = account.into(); + let account: HumanAddr = account.into(); // first we try to open with a valid handshake let mut handshake_open = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION); handshake_open.counterparty_version = None; @@ -76,7 +76,7 @@ fn connect>( id: channel_id.into(), contract_addr: account.clone(), }; - let info = mock_info(account, &[]); + let info = mock_info(&account, &[]); let _: Response = execute(deps, mock_env(), info, execute_msg).unwrap(); } diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index 4af3c5253b..85b4107cdf 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -18,7 +18,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult> { let state = State { - owner: deps.api.addr_canonicalize(&info.sender)?, + owner: deps.api.addr_canonicalize(info.sender.as_ref())?, }; config(deps.storage).save(&state)?; @@ -29,7 +29,7 @@ pub fn instantiate( contract_addr: env.contract.address.into(), }; let msg = WasmMsg::Execute { - contract_addr: info.sender, + contract_addr: info.sender.into(), msg: to_binary(&data)?, send: vec![], }; @@ -59,7 +59,7 @@ pub fn try_reflect( ) -> Result, ReflectError> { let state = config(deps.storage).load()?; - let sender = deps.api.addr_canonicalize(&info.sender)?; + let sender = deps.api.addr_canonicalize(info.sender.as_ref())?; if sender != state.owner { return Err(ReflectError::NotCurrentOwner { expected: state.owner, @@ -86,7 +86,7 @@ pub fn try_reflect_subcall( msgs: Vec>, ) -> Result, ReflectError> { let state = config(deps.storage).load()?; - let sender = deps.api.addr_canonicalize(&info.sender)?; + let sender = deps.api.addr_canonicalize(info.sender.as_ref())?; if sender != state.owner { return Err(ReflectError::NotCurrentOwner { expected: state.owner, @@ -114,7 +114,7 @@ pub fn try_change_owner( ) -> Result, ReflectError> { let api = deps.api; config(deps.storage).update(|mut state| { - let sender = api.addr_canonicalize(&info.sender)?; + let sender = api.addr_canonicalize(info.sender.as_ref())?; if sender != state.owner { return Err(ReflectError::NotCurrentOwner { expected: state.owner, diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 7b96663507..bf97adda36 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -41,7 +41,7 @@ pub fn instantiate( let denom = deps.querier.query_bonded_denom()?; let invest = InvestmentInfo { - owner: deps.api.addr_canonicalize(&info.sender)?, + owner: deps.api.addr_canonicalize(info.sender.as_ref())?, exit_tax: msg.exit_tax, bond_denom: denom, validator: msg.validator, @@ -83,7 +83,7 @@ pub fn transfer( send: Uint128, ) -> StdResult { let rcpt_raw = deps.api.addr_canonicalize(&recipient)?; - let sender_raw = deps.api.addr_canonicalize(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(info.sender.as_ref())?; let mut accounts = balances(deps.storage); accounts.update(&sender_raw, |balance: Option| -> StdResult<_> { @@ -140,7 +140,7 @@ fn assert_bonds(supply: &Supply, bonded: Uint128) -> StdResult<()> { } pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { - let sender_raw = deps.api.addr_canonicalize(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(info.sender.as_ref())?; // ensure we have the proper denom let invest = invest_info_read(deps.storage).load()?; @@ -193,7 +193,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { } pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> StdResult { - let sender_raw = deps.api.addr_canonicalize(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(info.sender.as_ref())?; let invest = invest_info_read(deps.storage).load()?; // ensure it is big enough to care @@ -271,7 +271,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult } // check how much to send - min(balance, claims[sender]), and reduce the claim - let sender_raw = deps.api.addr_canonicalize(&info.sender)?; + let sender_raw = deps.api.addr_canonicalize(info.sender.as_ref())?; let mut to_send = balance.amount; claims(deps.storage).update(sender_raw.as_slice(), |claim| -> StdResult<_> { let claim = claim.ok_or_else(|| StdError::generic_err("no claim for this address"))?; @@ -290,7 +290,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult let res = Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: info.sender.clone(), + to_address: info.sender.clone().into(), amount: vec![balance], } .into()], @@ -340,7 +340,7 @@ pub fn _bond_all_tokens( info: MessageInfo, ) -> Result { // this is just meant as a call-back to ourself - if info.sender.as_str() != env.contract.address { + if info.sender != env.contract.address { return Err(Unauthorized {}.build()); } diff --git a/packages/std/examples/schema.rs b/packages/std/examples/schema.rs index 96c5f50aff..ff23a86fe7 100644 --- a/packages/std/examples/schema.rs +++ b/packages/std/examples/schema.rs @@ -1,8 +1,8 @@ use std::env::current_dir; use std::fs::create_dir_all; -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; -use cosmwasm_std::{CosmosMsg, MessageInfo}; +use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::CosmosMsg; fn main() { let mut out_dir = current_dir().unwrap(); @@ -10,6 +10,5 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); - export_schema(&schema_for!(MessageInfo), &out_dir); export_schema_with_title(&mut schema_for!(CosmosMsg), &out_dir, "CosmosMsg"); } diff --git a/packages/std/schema/message_info.json b/packages/std/schema/message_info.json deleted file mode 100644 index ee1732e1b6..0000000000 --- a/packages/std/schema/message_info.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MessageInfo", - "description": "Additional information from [MsgInstantiateContract] and [MsgExecuteContract], which is passed along with the contract execution message into the `instantiate` and `execute` entry points.\n\nIt contains the essential info for authorization - identity of the call, and payment.\n\n[MsgInstantiateContract]: https://github.com/CosmWasm/wasmd/blob/v0.15.0/x/wasm/internal/types/tx.proto#L47-L61 [MsgExecuteContract]: https://github.com/CosmWasm/wasmd/blob/v0.15.0/x/wasm/internal/types/tx.proto#L68-L78", - "type": "object", - "required": [ - "funds", - "sender" - ], - "properties": { - "funds": { - "description": "The funds that are sent to the contract as part of `MsgInstantiateContract` or `MsgExecuteContract`. The transfer is processed in bank before the contract is executed such that the new balance is visible during contract execution.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "sender": { - "description": "The `sender` field from `MsgInstantiateContract` and `MsgExecuteContract`. You can think of this as the address that initiated the action (i.e. the message). What that means exactly heavily depends on the application.\n\nThe x/wasm module ensures that the sender address signed the transaction or is otherwise authorized to send the message.\n\nAdditional signers of the transaction that are either needed for other messages or contain unnecessary signatures are not propagated into the contract.", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] - } - }, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/packages/std/src/coins.rs b/packages/std/src/coins.rs index f2e77ad592..cc03665501 100644 --- a/packages/std/src/coins.rs +++ b/packages/std/src/coins.rs @@ -31,7 +31,7 @@ impl Coin { /// /// let mut response: Response = Default::default(); /// response.messages = vec![CosmosMsg::Bank(BankMsg::Send { -/// to_address: info.sender, +/// to_address: info.sender.into(), /// amount: tip, /// })]; /// ``` @@ -55,7 +55,7 @@ pub fn coins>(amount: u128, denom: S) -> Vec { /// /// let mut response: Response = Default::default(); /// response.messages = vec![CosmosMsg::Bank(BankMsg::Send { -/// to_address: info.sender, +/// to_address: info.sender.into(), /// amount: tip, /// })]; /// ``` diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 03ca9839f2..dcacefd760 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -199,9 +199,9 @@ pub fn mock_env() -> Env { /// Just set sender and funds for the message. /// This is intended for use in test code only. -pub fn mock_info>(sender: U, funds: &[Coin]) -> MessageInfo { +pub fn mock_info(sender: &str, funds: &[Coin]) -> MessageInfo { MessageInfo { - sender: sender.into(), + sender: Addr::unchecked(sender), funds: funds.to_vec(), } } @@ -532,14 +532,12 @@ mod tests { fn mock_info_arguments() { let name = HumanAddr("my name".to_string()); - // make sure we can generate with &str, &HumanAddr, and HumanAddr + // make sure we can generate with &str and &HumanAddr let a = mock_info("my name", &coins(100, "atom")); let b = mock_info(&name, &coins(100, "atom")); - let c = mock_info(name, &coins(100, "atom")); // and the results are the same assert_eq!(a, b); - assert_eq!(a, c); } #[test] diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs index ce84d8a85c..b2e163c04b 100644 --- a/packages/std/src/types.rs +++ b/packages/std/src/types.rs @@ -1,7 +1,6 @@ -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::addresses::{Addr, HumanAddr}; +use crate::addresses::Addr; use crate::coins::Coin; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -71,7 +70,7 @@ pub struct BlockInfo { /// /// [MsgInstantiateContract]: https://github.com/CosmWasm/wasmd/blob/v0.15.0/x/wasm/internal/types/tx.proto#L47-L61 /// [MsgExecuteContract]: https://github.com/CosmWasm/wasmd/blob/v0.15.0/x/wasm/internal/types/tx.proto#L68-L78 -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct MessageInfo { /// The `sender` field from `MsgInstantiateContract` and `MsgExecuteContract`. /// You can think of this as the address that initiated the action (i.e. the message). What that @@ -82,7 +81,7 @@ pub struct MessageInfo { /// /// Additional signers of the transaction that are either needed for other messages or contain unnecessary /// signatures are not propagated into the contract. - pub sender: HumanAddr, + pub sender: Addr, /// The funds that are sent to the contract as part of `MsgInstantiateContract` /// or `MsgExecuteContract`. The transfer is processed in bank before the contract /// is executed such that the new balance is visible during contract execution. diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index 09b9e50bce..978409af4c 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -157,9 +157,9 @@ pub fn mock_env() -> Env { /// Just set sender and funds for the message. /// This is intended for use in test code only. -pub fn mock_info>(sender: U, funds: &[Coin]) -> MessageInfo { +pub fn mock_info(sender: &str, funds: &[Coin]) -> MessageInfo { MessageInfo { - sender: sender.into(), + sender: Addr::unchecked(sender), funds: funds.to_vec(), } } @@ -174,14 +174,12 @@ mod tests { fn mock_info_arguments() { let name = HumanAddr("my name".to_string()); - // make sure we can generate with &str, &HumanAddr, and HumanAddr + // make sure we can generate with &str and &HumanAddr let a = mock_info("my name", &coins(100, "atom")); let b = mock_info(&name, &coins(100, "atom")); - let c = mock_info(name, &coins(100, "atom")); // and the results are the same assert_eq!(a, b); - assert_eq!(a, c); } #[test] From 87b6af83be06015a93ca0b9b5f80d9eafab82e13 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 15:17:57 +0200 Subject: [PATCH 17/38] Pull out msg and state module in hackatom --- contracts/hackatom/examples/schema.rs | 5 +- contracts/hackatom/src/contract.rs | 101 ++---------------------- contracts/hackatom/src/lib.rs | 2 + contracts/hackatom/src/msg.rs | 82 +++++++++++++++++++ contracts/hackatom/src/state.rs | 13 +++ contracts/hackatom/tests/integration.rs | 5 +- 6 files changed, 109 insertions(+), 99 deletions(-) create mode 100644 contracts/hackatom/src/msg.rs create mode 100644 contracts/hackatom/src/state.rs diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index 3db2694adf..a9cd2bbee1 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -4,9 +4,8 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; use cosmwasm_std::BalanceResponse; -use hackatom::contract::{ - ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, State, SudoMsg, VerifierResponse, -}; +use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg, VerifierResponse}; +use hackatom::state::State; fn main() { let mut out_dir = current_dir().unwrap(); diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index f09595779a..2755b49042 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -1,101 +1,16 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use cosmwasm_std::{ - entry_point, from_slice, to_binary, to_vec, AllBalanceResponse, Api, BankMsg, Binary, - CanonicalAddr, Coin, Deps, DepsMut, Env, HumanAddr, MessageInfo, QueryRequest, QueryResponse, - Response, StdError, StdResult, WasmQuery, + entry_point, from_slice, to_binary, to_vec, AllBalanceResponse, Api, BankMsg, CanonicalAddr, + Deps, DepsMut, Env, HumanAddr, MessageInfo, QueryRequest, QueryResponse, Response, StdError, + StdResult, WasmQuery, }; use crate::errors::HackError; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - pub verifier: HumanAddr, - pub beneficiary: HumanAddr, -} - -/// MigrateMsg allows a privileged contract administrator to run -/// a migration on the contract. In this (demo) case it is just migrating -/// from one hackatom code to the same code, but taking advantage of the -/// migration step to set a new validator. -/// -/// Note that the contract doesn't enforce permissions here, this is done -/// by blockchain logic (in the future by blockchain governance) -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MigrateMsg { - pub verifier: HumanAddr, -} - -/// SudoMsg is only exposed for internal Cosmos SDK modules to call. -/// This is showing how we can expose "admin" functionality than can not be called by -/// external users or contracts, but only trusted (native/Go) code in the blockchain -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SudoMsg { - StealFunds { - recipient: HumanAddr, - amount: Vec, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct State { - pub verifier: CanonicalAddr, - pub beneficiary: CanonicalAddr, - pub funder: CanonicalAddr, -} - -// failure modes to help test wasmd, based on this comment -// https://github.com/cosmwasm/wasmd/issues/8#issuecomment-576146751 -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// Releasing all funds in the contract to the beneficiary. This is the only "proper" action of this demo contract. - Release {}, - /// Infinite loop to burn cpu cycles (only run when metering is enabled) - CpuLoop {}, - /// Infinite loop making storage calls (to test when their limit hits) - StorageLoop {}, - /// Infinite loop reading and writing memory - MemoryLoop {}, - /// Allocate large amounts of memory without consuming much gas - AllocateLargeMemory { pages: u32 }, - /// Trigger a panic to ensure framework handles gracefully - Panic {}, - /// Starting with CosmWasm 0.10, some API calls return user errors back to the contract. - /// This triggers such user errors, ensuring the transaction does not fail in the backend. - UserErrorsInApiCalls {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - /// returns a human-readable representation of the verifier - /// use to ensure query path works in integration tests - Verifier {}, - /// This returns cosmwasm_std::AllBalanceResponse to demo use of the querier - OtherBalance { address: HumanAddr }, - /// Recurse will execute a query into itself up to depth-times and return - /// Each step of the recursion may perform some extra work to test gas metering - /// (`work` rounds of sha256 on contract). - /// Now that we have Env, we can auto-calculate the address to recurse into - Recurse { depth: u32, work: u32 }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct VerifierResponse { - pub verifier: HumanAddr, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct RecurseResponse { - /// hashed is the result of running sha256 "work+1" times on the contract's human address - pub hashed: Binary, -} - -pub const CONFIG_KEY: &[u8] = b"config"; +use crate::msg::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, RecurseResponse, SudoMsg, VerifierResponse, +}; +use crate::state::{State, CONFIG_KEY}; pub fn instantiate( deps: DepsMut, @@ -377,7 +292,7 @@ mod tests { mock_dependencies, mock_dependencies_with_balances, mock_env, mock_info, MOCK_CONTRACT_ADDR, }; // import trait Storage to get access to read - use cosmwasm_std::{attr, coins, Storage}; + use cosmwasm_std::{attr, coins, Binary, Storage}; #[test] fn proper_initialization() { diff --git a/contracts/hackatom/src/lib.rs b/contracts/hackatom/src/lib.rs index 7acafa4e11..1e46dd6144 100644 --- a/contracts/hackatom/src/lib.rs +++ b/contracts/hackatom/src/lib.rs @@ -1,5 +1,7 @@ pub mod contract; mod errors; +pub mod msg; +pub mod state; #[cfg(target_arch = "wasm32")] cosmwasm_std::create_entry_points_with_migration!(contract); diff --git a/contracts/hackatom/src/msg.rs b/contracts/hackatom/src/msg.rs new file mode 100644 index 0000000000..6368ba8dbb --- /dev/null +++ b/contracts/hackatom/src/msg.rs @@ -0,0 +1,82 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Binary, Coin, HumanAddr}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + pub verifier: HumanAddr, + pub beneficiary: HumanAddr, +} + +/// MigrateMsg allows a privileged contract administrator to run +/// a migration on the contract. In this (demo) case it is just migrating +/// from one hackatom code to the same code, but taking advantage of the +/// migration step to set a new validator. +/// +/// Note that the contract doesn't enforce permissions here, this is done +/// by blockchain logic (in the future by blockchain governance) +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MigrateMsg { + pub verifier: HumanAddr, +} + +/// SudoMsg is only exposed for internal Cosmos SDK modules to call. +/// This is showing how we can expose "admin" functionality than can not be called by +/// external users or contracts, but only trusted (native/Go) code in the blockchain +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SudoMsg { + StealFunds { + recipient: HumanAddr, + amount: Vec, + }, +} + +// failure modes to help test wasmd, based on this comment +// https://github.com/cosmwasm/wasmd/issues/8#issuecomment-576146751 +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Releasing all funds in the contract to the beneficiary. This is the only "proper" action of this demo contract. + Release {}, + /// Infinite loop to burn cpu cycles (only run when metering is enabled) + CpuLoop {}, + /// Infinite loop making storage calls (to test when their limit hits) + StorageLoop {}, + /// Infinite loop reading and writing memory + MemoryLoop {}, + /// Allocate large amounts of memory without consuming much gas + AllocateLargeMemory { pages: u32 }, + /// Trigger a panic to ensure framework handles gracefully + Panic {}, + /// Starting with CosmWasm 0.10, some API calls return user errors back to the contract. + /// This triggers such user errors, ensuring the transaction does not fail in the backend. + UserErrorsInApiCalls {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// returns a human-readable representation of the verifier + /// use to ensure query path works in integration tests + Verifier {}, + /// This returns cosmwasm_std::AllBalanceResponse to demo use of the querier + OtherBalance { address: HumanAddr }, + /// Recurse will execute a query into itself up to depth-times and return + /// Each step of the recursion may perform some extra work to test gas metering + /// (`work` rounds of sha256 on contract). + /// Now that we have Env, we can auto-calculate the address to recurse into + Recurse { depth: u32, work: u32 }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct VerifierResponse { + pub verifier: HumanAddr, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct RecurseResponse { + /// hashed is the result of running sha256 "work+1" times on the contract's human address + pub hashed: Binary, +} diff --git a/contracts/hackatom/src/state.rs b/contracts/hackatom/src/state.rs new file mode 100644 index 0000000000..2e7f3513a9 --- /dev/null +++ b/contracts/hackatom/src/state.rs @@ -0,0 +1,13 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::CanonicalAddr; + +pub const CONFIG_KEY: &[u8] = b"config"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct State { + pub verifier: CanonicalAddr, + pub beneficiary: CanonicalAddr, + pub funder: CanonicalAddr, +} diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index cc2316a66d..ddc5429bb4 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -30,9 +30,8 @@ use cosmwasm_vm::{ BackendApi, Storage, VmError, }; -use hackatom::contract::{ - ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, State, SudoMsg, CONFIG_KEY, -}; +use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; +use hackatom::state::{State, CONFIG_KEY}; static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/hackatom.wasm"); From 8cdec39e626be6960cee4ba21c457c19ff29c976 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:56:11 +0200 Subject: [PATCH 18/38] Use Addr in State and String in messages --- contracts/burner/schema/migrate_msg.json | 5 - contracts/burner/src/contract.rs | 12 +- contracts/burner/src/msg.rs | 4 +- contracts/burner/tests/integration.rs | 6 +- contracts/hackatom/examples/schema.rs | 2 - .../hackatom/schema/instantiate_msg.json | 7 +- contracts/hackatom/schema/migrate_msg.json | 5 - contracts/hackatom/schema/query_msg.json | 9 +- contracts/hackatom/schema/state.json | 30 ---- contracts/hackatom/schema/sudo_msg.json | 5 +- .../hackatom/schema/verifier_response.json | 5 - contracts/hackatom/src/contract.rs | 152 +++++++++--------- contracts/hackatom/src/msg.rs | 14 +- contracts/hackatom/src/state.rs | 11 +- contracts/hackatom/tests/integration.rs | 99 ++++++------ .../schema/account_response.json | 13 +- .../schema/admin_response.json | 5 - .../ibc-reflect-send/schema/execute_msg.json | 2 +- .../schema/list_accounts_response.json | 13 +- contracts/ibc-reflect-send/src/contract.rs | 22 +-- contracts/ibc-reflect-send/src/ibc.rs | 24 +-- contracts/ibc-reflect-send/src/ibc_msg.rs | 6 +- contracts/ibc-reflect-send/src/msg.rs | 14 +- contracts/ibc-reflect-send/src/state.rs | 11 +- .../ibc-reflect-send/tests/integration.rs | 8 +- .../schema/acknowledgement_msg_balances.json | 5 +- .../schema/acknowledgement_msg_who_am_i.json | 5 +- contracts/ibc-reflect/schema/execute_msg.json | 13 +- contracts/ibc-reflect/src/contract.rs | 41 +++-- contracts/ibc-reflect/src/msg.rs | 12 +- contracts/ibc-reflect/src/state.rs | 6 +- contracts/ibc-reflect/tests/integration.rs | 12 +- contracts/queue/tests/integration.rs | 6 +- contracts/reflect/examples/schema.rs | 2 - contracts/reflect/schema/execute_msg.json | 2 +- contracts/reflect/schema/owner_response.json | 5 - contracts/reflect/schema/query_msg.json | 2 +- contracts/reflect/schema/state.json | 22 --- contracts/reflect/src/contract.rs | 69 ++++---- contracts/reflect/src/errors.rs | 7 +- contracts/reflect/src/msg.rs | 10 +- contracts/reflect/src/state.rs | 7 +- contracts/reflect/tests/integration.rs | 4 +- contracts/staking/examples/schema.rs | 3 - contracts/staking/schema/execute_msg.json | 5 +- contracts/staking/schema/instantiate_msg.json | 9 +- contracts/staking/schema/investment_info.json | 70 -------- .../staking/schema/investment_response.json | 15 +- contracts/staking/schema/query_msg.json | 11 +- contracts/staking/schema/supply.json | 42 ----- contracts/staking/src/contract.rs | 70 ++++---- contracts/staking/src/msg.rs | 17 +- contracts/staking/src/state.rs | 11 +- contracts/staking/tests/integration.rs | 8 +- 54 files changed, 358 insertions(+), 617 deletions(-) delete mode 100644 contracts/hackatom/schema/state.json delete mode 100644 contracts/reflect/schema/state.json delete mode 100644 contracts/staking/schema/investment_info.json delete mode 100644 contracts/staking/schema/supply.json diff --git a/contracts/burner/schema/migrate_msg.json b/contracts/burner/schema/migrate_msg.json index b0ef43c536..b2fd46b4fa 100644 --- a/contracts/burner/schema/migrate_msg.json +++ b/contracts/burner/schema/migrate_msg.json @@ -7,11 +7,6 @@ ], "properties": { "payout": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { "type": "string" } } diff --git a/contracts/burner/src/contract.rs b/contracts/burner/src/contract.rs index d012386875..bf498d690b 100644 --- a/contracts/burner/src/contract.rs +++ b/contracts/burner/src/contract.rs @@ -30,11 +30,9 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult } // get balance and send all to recipient - let balance = deps - .querier - .query_all_balances(env.contract.address.clone())?; + let balance = deps.querier.query_all_balances(env.contract.address)?; let send = BankMsg::Send { - to_address: msg.payout.clone(), + to_address: msg.payout.clone().into(), amount: balance, }; @@ -52,7 +50,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult mod tests { use super::*; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{coins, HumanAddr, StdError, Storage}; + use cosmwasm_std::{coins, StdError, Storage}; #[test] fn instantiate_fails() { @@ -82,7 +80,7 @@ mod tests { assert_eq!(3, cnt); // change the verifier via migrate - let payout = HumanAddr::from("someone else"); + let payout = String::from("someone else"); let msg = MigrateMsg { payout: payout.clone(), }; @@ -93,7 +91,7 @@ mod tests { assert_eq!( msg, &BankMsg::Send { - to_address: payout, + to_address: payout.into(), amount: coins(123456, "gold"), } .into(), diff --git a/contracts/burner/src/msg.rs b/contracts/burner/src/msg.rs index 952fb8f1c3..3a705c430c 100644 --- a/contracts/burner/src/msg.rs +++ b/contracts/burner/src/msg.rs @@ -1,11 +1,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::HumanAddr; - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct MigrateMsg { - pub payout: HumanAddr, + pub payout: String, } /// A placeholder where we don't take any input diff --git a/contracts/burner/tests/integration.rs b/contracts/burner/tests/integration.rs index 1e24e1961e..b7df6183c1 100644 --- a/contracts/burner/tests/integration.rs +++ b/contracts/burner/tests/integration.rs @@ -17,7 +17,7 @@ //! }); //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{coins, BankMsg, ContractResult, HumanAddr, Order, Response}; +use cosmwasm_std::{coins, BankMsg, ContractResult, Order, Response}; use cosmwasm_vm::testing::{instantiate, migrate, mock_env, mock_info, mock_instance}; use burner::msg::{InstantiateMsg, MigrateMsg}; @@ -60,7 +60,7 @@ fn migrate_cleans_up_data() { .unwrap(); // change the verifier via migrate - let payout = HumanAddr::from("someone else"); + let payout = String::from("someone else"); let msg = MigrateMsg { payout: payout.clone(), }; @@ -71,7 +71,7 @@ fn migrate_cleans_up_data() { assert_eq!( msg, &BankMsg::Send { - to_address: payout, + to_address: payout.into(), amount: coins(123456, "gold"), } .into(), diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index a9cd2bbee1..375100b2fd 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -5,7 +5,6 @@ use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; use cosmwasm_std::BalanceResponse; use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg, VerifierResponse}; -use hackatom::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -18,7 +17,6 @@ fn main() { export_schema(&schema_for!(MigrateMsg), &out_dir); export_schema(&schema_for!(SudoMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(State), &out_dir); export_schema(&schema_for!(VerifierResponse), &out_dir); export_schema(&schema_for!(BalanceResponse), &out_dir); } diff --git a/contracts/hackatom/schema/instantiate_msg.json b/contracts/hackatom/schema/instantiate_msg.json index f40476d5d3..7f844a5062 100644 --- a/contracts/hackatom/schema/instantiate_msg.json +++ b/contracts/hackatom/schema/instantiate_msg.json @@ -8,14 +8,9 @@ ], "properties": { "beneficiary": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "verifier": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { "type": "string" } } diff --git a/contracts/hackatom/schema/migrate_msg.json b/contracts/hackatom/schema/migrate_msg.json index a9878c23e3..dbdad8fec9 100644 --- a/contracts/hackatom/schema/migrate_msg.json +++ b/contracts/hackatom/schema/migrate_msg.json @@ -8,11 +8,6 @@ ], "properties": { "verifier": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { "type": "string" } } diff --git a/contracts/hackatom/schema/query_msg.json b/contracts/hackatom/schema/query_msg.json index 6c3df170c2..a0b5791acf 100644 --- a/contracts/hackatom/schema/query_msg.json +++ b/contracts/hackatom/schema/query_msg.json @@ -29,7 +29,7 @@ ], "properties": { "address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -65,10 +65,5 @@ }, "additionalProperties": false } - ], - "definitions": { - "HumanAddr": { - "type": "string" - } - } + ] } diff --git a/contracts/hackatom/schema/state.json b/contracts/hackatom/schema/state.json deleted file mode 100644 index 0b578b8e40..0000000000 --- a/contracts/hackatom/schema/state.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "State", - "type": "object", - "required": [ - "beneficiary", - "funder", - "verifier" - ], - "properties": { - "beneficiary": { - "$ref": "#/definitions/CanonicalAddr" - }, - "funder": { - "$ref": "#/definitions/CanonicalAddr" - }, - "verifier": { - "$ref": "#/definitions/CanonicalAddr" - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "CanonicalAddr": { - "$ref": "#/definitions/Binary" - } - } -} diff --git a/contracts/hackatom/schema/sudo_msg.json b/contracts/hackatom/schema/sudo_msg.json index 64fe612845..0b78b63422 100644 --- a/contracts/hackatom/schema/sudo_msg.json +++ b/contracts/hackatom/schema/sudo_msg.json @@ -23,7 +23,7 @@ } }, "recipient": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -47,9 +47,6 @@ } } }, - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/hackatom/schema/verifier_response.json b/contracts/hackatom/schema/verifier_response.json index 530c0b02f0..8af8ef3684 100644 --- a/contracts/hackatom/schema/verifier_response.json +++ b/contracts/hackatom/schema/verifier_response.json @@ -7,11 +7,6 @@ ], "properties": { "verifier": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { "type": "string" } } diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 2755b49042..28777dde49 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -1,9 +1,9 @@ use sha2::{Digest, Sha256}; use cosmwasm_std::{ - entry_point, from_slice, to_binary, to_vec, AllBalanceResponse, Api, BankMsg, CanonicalAddr, - Deps, DepsMut, Env, HumanAddr, MessageInfo, QueryRequest, QueryResponse, Response, StdError, - StdResult, WasmQuery, + entry_point, from_slice, to_binary, to_vec, Addr, AllBalanceResponse, Api, BankMsg, + CanonicalAddr, Deps, DepsMut, Env, MessageInfo, QueryRequest, QueryResponse, Response, + StdError, StdResult, WasmQuery, }; use crate::errors::HackError; @@ -23,9 +23,9 @@ pub fn instantiate( deps.storage.set( CONFIG_KEY, &to_vec(&State { - verifier: deps.api.addr_canonicalize(&msg.verifier)?, - beneficiary: deps.api.addr_canonicalize(&msg.beneficiary)?, - funder: deps.api.addr_canonicalize(info.sender.as_ref())?, + verifier: deps.api.addr_validate(&msg.verifier)?, + beneficiary: deps.api.addr_validate(&msg.beneficiary)?, + funder: info.sender, })?, ); @@ -41,7 +41,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result Result { let msg = BankMsg::Send { - to_address: recipient, + to_address: recipient.into(), amount, }; let mut response = Response::default(); @@ -86,15 +86,15 @@ fn do_release(deps: DepsMut, env: Env, info: MessageInfo) -> Result Result { fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { // Canonicalize - let empty = HumanAddr::from(""); - match api.addr_canonicalize(&empty).unwrap_err() { + let empty = ""; + match api.addr_canonicalize(empty).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -173,8 +173,8 @@ fn do_user_errors_in_api_calls(api: &dyn Api) -> Result { } } - let invalid_bech32 = HumanAddr::from("bn93hg934hg08q340g8u4jcau3"); - match api.addr_canonicalize(&invalid_bech32).unwrap_err() { + let invalid_bech32 = "bn93hg934hg08q340g8u4jcau3"; + match api.addr_canonicalize(invalid_bech32).unwrap_err() { StdError::GenericErr { .. } => {} err => { return Err(StdError::generic_err(format!( @@ -230,12 +230,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Verifier {} => to_binary(&query_verifier(deps)?), QueryMsg::OtherBalance { address } => to_binary(&query_other_balance(deps, address)?), - QueryMsg::Recurse { depth, work } => to_binary(&query_recurse( - deps, - depth, - work, - env.contract.address.into(), - )?), + QueryMsg::Recurse { depth, work } => { + to_binary(&query_recurse(deps, depth, work, env.contract.address)?) + } } } @@ -245,23 +242,19 @@ fn query_verifier(deps: Deps) -> StdResult { .get(CONFIG_KEY) .ok_or_else(|| StdError::not_found("State"))?; let state: State = from_slice(&data)?; - let addr = deps.api.addr_humanize(&state.verifier)?; - Ok(VerifierResponse { verifier: addr }) + Ok(VerifierResponse { + verifier: state.verifier.into(), + }) } -fn query_other_balance(deps: Deps, address: HumanAddr) -> StdResult { +fn query_other_balance(deps: Deps, address: String) -> StdResult { let amount = deps.querier.query_all_balances(address)?; Ok(AllBalanceResponse { amount }) } -fn query_recurse( - deps: Deps, - depth: u32, - work: u32, - contract: HumanAddr, -) -> StdResult { +fn query_recurse(deps: Deps, depth: u32, work: u32, contract: Addr) -> StdResult { // perform all hashes as requested - let mut hashed: Vec = contract.as_str().as_bytes().to_vec(); + let mut hashed: Vec = contract.as_ref().into(); for _ in 0..work { hashed = Sha256::digest(&hashed).to_vec() } @@ -278,7 +271,7 @@ fn query_recurse( work, }; let query = QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: contract, + contract_addr: contract.into(), msg: to_binary(&req)?, }); deps.querier.query(&query) @@ -298,13 +291,13 @@ mod tests { fn proper_initialization() { let mut deps = mock_dependencies(&[]); - let verifier = HumanAddr(String::from("verifies")); - let beneficiary = HumanAddr(String::from("benefits")); - let creator = HumanAddr(String::from("creator")); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let expected_state = State { - verifier: deps.api.addr_canonicalize(&verifier).unwrap(), - beneficiary: deps.api.addr_canonicalize(&beneficiary).unwrap(), - funder: deps.api.addr_canonicalize(&creator).unwrap(), + verifier: deps.api.addr_validate(&verifier).unwrap(), + beneficiary: deps.api.addr_validate(&beneficiary).unwrap(), + funder: deps.api.addr_validate(&creator).unwrap(), }; let msg = InstantiateMsg { @@ -328,14 +321,14 @@ mod tests { fn instantiate_and_query() { let mut deps = mock_dependencies(&[]); - let verifier = HumanAddr(String::from("verifies")); - let beneficiary = HumanAddr(String::from("benefits")); - let creator = HumanAddr(String::from("creator")); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let msg = InstantiateMsg { verifier: verifier.clone(), beneficiary, }; - let info = mock_info(creator.as_str(), &[]); + let info = mock_info(&creator, &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -348,14 +341,14 @@ mod tests { fn migrate_verifier() { let mut deps = mock_dependencies(&[]); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); - let creator = HumanAddr::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let msg = InstantiateMsg { verifier, beneficiary, }; - let info = mock_info(creator.as_str(), &[]); + let info = mock_info(&creator, &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -364,7 +357,7 @@ mod tests { assert_eq!(query_response.as_slice(), b"{\"verifier\":\"verifies\"}"); // change the verifier via migrate - let new_verifier = HumanAddr::from("someone else"); + let new_verifier = String::from("someone else"); let msg = MigrateMsg { verifier: new_verifier.clone(), }; @@ -380,19 +373,19 @@ mod tests { fn sudo_can_steal_tokens() { let mut deps = mock_dependencies(&[]); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); - let creator = HumanAddr::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let msg = InstantiateMsg { verifier, beneficiary, }; - let info = mock_info(creator.as_str(), &[]); + let info = mock_info(&creator, &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // sudo takes any tax it wants - let to_address = HumanAddr::from("community-pool"); + let to_address = String::from("community-pool"); let amount = coins(700, "gold"); let sys_msg = SudoMsg::StealFunds { recipient: to_address.clone(), @@ -401,21 +394,28 @@ mod tests { let res = sudo(deps.as_mut(), mock_env(), sys_msg).unwrap(); assert_eq!(1, res.messages.len()); let msg = res.messages.get(0).expect("no message"); - assert_eq!(msg, &BankMsg::Send { to_address, amount }.into(),); + assert_eq!( + msg, + &BankMsg::Send { + to_address: to_address.into(), + amount + } + .into(), + ); } #[test] fn querier_callbacks_work() { - let rich_addr = HumanAddr::from("foobar"); + let rich_addr = String::from("foobar"); let rich_balance = coins(10000, "gold"); - let deps = mock_dependencies_with_balances(&[(&rich_addr, &rich_balance)]); + let deps = mock_dependencies_with_balances(&[(&rich_addr.clone().into(), &rich_balance)]); // querying with balance gets the balance let bal = query_other_balance(deps.as_ref(), rich_addr).unwrap(); assert_eq!(bal.amount, rich_balance); // querying other accounts gets none - let bal = query_other_balance(deps.as_ref(), HumanAddr::from("someone else")).unwrap(); + let bal = query_other_balance(deps.as_ref(), String::from("someone else")).unwrap(); assert_eq!(bal.amount, vec![]); } @@ -424,16 +424,16 @@ mod tests { let mut deps = mock_dependencies(&[]); // initialize the store - let creator = HumanAddr::from("creator"); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); + let creator = String::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); let instantiate_msg = InstantiateMsg { verifier: verifier.clone(), beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_info = mock_info(creator.as_str(), &init_amount); + let init_info = mock_info(&creator, &init_amount); let init_res = instantiate(deps.as_mut(), mock_env(), init_info, instantiate_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); @@ -454,7 +454,7 @@ mod tests { assert_eq!( msg, &BankMsg::Send { - to_address: beneficiary, + to_address: beneficiary.into(), amount: coins(1000, "earth"), } .into(), @@ -471,16 +471,16 @@ mod tests { let mut deps = mock_dependencies(&[]); // initialize the store - let creator = HumanAddr::from("creator"); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); + let creator = String::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); let instantiate_msg = InstantiateMsg { verifier: verifier.clone(), beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_info = mock_info(creator.as_str(), &init_amount); + let init_info = mock_info(&creator, &init_amount); let init_res = instantiate(deps.as_mut(), mock_env(), init_info, instantiate_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); @@ -503,9 +503,9 @@ mod tests { assert_eq!( state, State { - verifier: deps.api.addr_canonicalize(&verifier).unwrap(), - beneficiary: deps.api.addr_canonicalize(&beneficiary).unwrap(), - funder: deps.api.addr_canonicalize(&creator).unwrap(), + verifier: Addr::unchecked(verifier), + beneficiary: Addr::unchecked(beneficiary), + funder: Addr::unchecked(creator), } ); } @@ -516,19 +516,19 @@ mod tests { let mut deps = mock_dependencies(&[]); // initialize the store - let verifier = HumanAddr(String::from("verifies")); - let beneficiary = HumanAddr(String::from("benefits")); - let creator = HumanAddr(String::from("creator")); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let instantiate_msg = InstantiateMsg { verifier, beneficiary: beneficiary.clone(), }; - let init_info = mock_info(creator.as_str(), &coins(1000, "earth")); + let init_info = mock_info(&creator, &coins(1000, "earth")); let init_res = instantiate(deps.as_mut(), mock_env(), init_info, instantiate_msg).unwrap(); assert_eq!(0, init_res.messages.len()); - let execute_info = mock_info(beneficiary.as_str(), &[]); + let execute_info = mock_info(&beneficiary, &[]); // this should panic let _ = execute( deps.as_mut(), @@ -543,8 +543,8 @@ mod tests { let mut deps = mock_dependencies(&[]); let instantiate_msg = InstantiateMsg { - verifier: HumanAddr::from("verifies"), - beneficiary: HumanAddr::from("benefits"), + verifier: String::from("verifies"), + beneficiary: String::from("benefits"), }; let init_info = mock_info("creator", &coins(1000, "earth")); let init_res = instantiate(deps.as_mut(), mock_env(), init_info, instantiate_msg).unwrap(); @@ -566,7 +566,7 @@ mod tests { // let's just make sure the last step looks right let deps = mock_dependencies(&[]); - let contract = HumanAddr::from("my-contract"); + let contract = Addr::unchecked("my-contract"); let bin_contract: &[u8] = b"my-contract"; // return the unhashed value here diff --git a/contracts/hackatom/src/msg.rs b/contracts/hackatom/src/msg.rs index 6368ba8dbb..6b51d1c853 100644 --- a/contracts/hackatom/src/msg.rs +++ b/contracts/hackatom/src/msg.rs @@ -1,12 +1,12 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, Coin, HumanAddr}; +use cosmwasm_std::{Binary, Coin}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { - pub verifier: HumanAddr, - pub beneficiary: HumanAddr, + pub verifier: String, + pub beneficiary: String, } /// MigrateMsg allows a privileged contract administrator to run @@ -18,7 +18,7 @@ pub struct InstantiateMsg { /// by blockchain logic (in the future by blockchain governance) #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct MigrateMsg { - pub verifier: HumanAddr, + pub verifier: String, } /// SudoMsg is only exposed for internal Cosmos SDK modules to call. @@ -28,7 +28,7 @@ pub struct MigrateMsg { #[serde(rename_all = "snake_case")] pub enum SudoMsg { StealFunds { - recipient: HumanAddr, + recipient: String, amount: Vec, }, } @@ -62,7 +62,7 @@ pub enum QueryMsg { /// use to ensure query path works in integration tests Verifier {}, /// This returns cosmwasm_std::AllBalanceResponse to demo use of the querier - OtherBalance { address: HumanAddr }, + OtherBalance { address: String }, /// Recurse will execute a query into itself up to depth-times and return /// Each step of the recursion may perform some extra work to test gas metering /// (`work` rounds of sha256 on contract). @@ -72,7 +72,7 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct VerifierResponse { - pub verifier: HumanAddr, + pub verifier: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/contracts/hackatom/src/state.rs b/contracts/hackatom/src/state.rs index 2e7f3513a9..e306cf8370 100644 --- a/contracts/hackatom/src/state.rs +++ b/contracts/hackatom/src/state.rs @@ -1,13 +1,12 @@ -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::CanonicalAddr; +use cosmwasm_std::Addr; pub const CONFIG_KEY: &[u8] = b"config"; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct State { - pub verifier: CanonicalAddr, - pub beneficiary: CanonicalAddr, - pub funder: CanonicalAddr, + pub verifier: Addr, + pub beneficiary: Addr, + pub funder: Addr, } diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index ddc5429bb4..61f3867879 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -18,8 +18,8 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - attr, coins, from_binary, to_vec, AllBalanceResponse, BankMsg, Binary, ContractResult, Empty, - HumanAddr, Response, + attr, coins, from_binary, to_vec, Addr, AllBalanceResponse, BankMsg, Binary, ContractResult, + Empty, Response, }; use cosmwasm_vm::{ call_execute, from_slice, @@ -27,7 +27,7 @@ use cosmwasm_vm::{ execute, instantiate, migrate, mock_env, mock_info, mock_instance, mock_instance_with_balances, query, sudo, test_io, MOCK_CONTRACT_ADDR, }, - BackendApi, Storage, VmError, + Storage, VmError, }; use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; @@ -35,10 +35,10 @@ use hackatom::state::{State, CONFIG_KEY}; static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/hackatom.wasm"); -fn make_init_msg() -> (InstantiateMsg, HumanAddr) { - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); - let creator = HumanAddr::from("creator"); +fn make_init_msg() -> (InstantiateMsg, String) { + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); ( InstantiateMsg { verifier, @@ -53,20 +53,20 @@ fn proper_initialization() { let mut deps = mock_instance(WASM, &[]); assert_eq!(deps.required_features.len(), 0); - let verifier = HumanAddr(String::from("verifies")); - let beneficiary = HumanAddr(String::from("benefits")); - let creator = HumanAddr(String::from("creator")); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let expected_state = State { - verifier: deps.api().canonical_address(&verifier).0.unwrap().into(), - beneficiary: deps.api().canonical_address(&beneficiary).0.unwrap().into(), - funder: deps.api().canonical_address(&creator).0.unwrap().into(), + verifier: Addr::unchecked(&verifier), + beneficiary: Addr::unchecked(&beneficiary), + funder: Addr::unchecked(&creator), }; let msg = InstantiateMsg { verifier, beneficiary, }; - let info = mock_info("creator", &coins(1000, "earth")); + let info = mock_info(&creator, &coins(1000, "earth")); let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); assert_eq!(res.attributes.len(), 1); @@ -91,14 +91,14 @@ fn proper_initialization() { fn instantiate_and_query() { let mut deps = mock_instance(WASM, &[]); - let verifier = HumanAddr(String::from("verifies")); - let beneficiary = HumanAddr(String::from("benefits")); - let creator = HumanAddr(String::from("creator")); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let msg = InstantiateMsg { verifier, beneficiary, }; - let info = mock_info(creator.as_str(), &coins(1000, "earth")); + let info = mock_info(&creator, &coins(1000, "earth")); let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -116,14 +116,14 @@ fn instantiate_and_query() { fn migrate_verifier() { let mut deps = mock_instance(WASM, &[]); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); - let creator = HumanAddr::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let msg = InstantiateMsg { verifier, beneficiary, }; - let info = mock_info(creator.as_str(), &[]); + let info = mock_info(&creator, &[]); let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -133,7 +133,7 @@ fn migrate_verifier() { // change the verifier via migrate let msg = MigrateMsg { - verifier: HumanAddr::from("someone else"), + verifier: String::from("someone else"), }; let res: Response = migrate(&mut deps, mock_env(), msg).unwrap(); assert_eq!(0, res.messages.len()); @@ -150,19 +150,19 @@ fn migrate_verifier() { fn sudo_can_steal_tokens() { let mut deps = mock_instance(WASM, &[]); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); - let creator = HumanAddr::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); + let creator = String::from("creator"); let msg = InstantiateMsg { verifier, beneficiary, }; - let info = mock_info(creator.as_str(), &[]); + let info = mock_info(&creator, &[]); let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); // sudo takes any tax it wants - let to_address = HumanAddr::from("community-pool"); + let to_address = String::from("community-pool"); let amount = coins(700, "gold"); let sys_msg = SudoMsg::StealFunds { recipient: to_address.clone(), @@ -171,14 +171,21 @@ fn sudo_can_steal_tokens() { let res: Response = sudo(&mut deps, mock_env(), sys_msg).unwrap(); assert_eq!(1, res.messages.len()); let msg = res.messages.get(0).expect("no message"); - assert_eq!(msg, &BankMsg::Send { to_address, amount }.into(),); + assert_eq!( + msg, + &BankMsg::Send { + to_address: to_address.into(), + amount + } + .into(), + ); } #[test] fn querier_callbacks_work() { - let rich_addr = HumanAddr::from("foobar"); + let rich_addr = String::from("foobar"); let rich_balance = coins(10000, "gold"); - let mut deps = mock_instance_with_balances(WASM, &[(&rich_addr, &rich_balance)]); + let mut deps = mock_instance_with_balances(WASM, &[(&rich_addr.clone().into(), &rich_balance)]); // querying with balance gets the balance let query_msg = QueryMsg::OtherBalance { address: rich_addr }; @@ -188,7 +195,7 @@ fn querier_callbacks_work() { // querying other accounts gets none let query_msg = QueryMsg::OtherBalance { - address: HumanAddr::from("someone else"), + address: String::from("someone else"), }; let query_response = query(&mut deps, mock_env(), query_msg).unwrap(); let bal: AllBalanceResponse = from_binary(&query_response).unwrap(); @@ -211,16 +218,16 @@ fn execute_release_works() { let mut deps = mock_instance(WASM, &[]); // initialize the store - let creator = HumanAddr::from("creator"); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); + let creator = String::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); let instantiate_msg = InstantiateMsg { verifier: verifier.clone(), beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_info = mock_info(creator.as_str(), &init_amount); + let init_info = mock_info(&creator, &init_amount); let init_res: Response = instantiate(&mut deps, mock_env(), init_info, instantiate_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); @@ -233,7 +240,7 @@ fn execute_release_works() { .unwrap(); // beneficiary can release it - let execute_info = mock_info(verifier.as_str(), &[]); + let execute_info = mock_info(&verifier, &[]); let execute_res: Response = execute(&mut deps, mock_env(), execute_info, ExecuteMsg::Release {}).unwrap(); assert_eq!(execute_res.messages.len(), 1); @@ -241,7 +248,7 @@ fn execute_release_works() { assert_eq!( msg, &BankMsg::Send { - to_address: beneficiary, + to_address: beneficiary.into(), amount: coins(1000, "earth"), } .into(), @@ -258,16 +265,16 @@ fn execute_release_fails_for_wrong_sender() { let mut deps = mock_instance(WASM, &[]); // initialize the store - let creator = HumanAddr::from("creator"); - let verifier = HumanAddr::from("verifies"); - let beneficiary = HumanAddr::from("benefits"); + let creator = String::from("creator"); + let verifier = String::from("verifies"); + let beneficiary = String::from("benefits"); let instantiate_msg = InstantiateMsg { verifier: verifier.clone(), beneficiary: beneficiary.clone(), }; let init_amount = coins(1000, "earth"); - let init_info = mock_info(creator.as_str(), &init_amount); + let init_info = mock_info(&creator, &init_amount); let init_res: Response = instantiate(&mut deps, mock_env(), init_info, instantiate_msg).unwrap(); assert_eq!(init_res.messages.len(), 0); @@ -280,7 +287,7 @@ fn execute_release_fails_for_wrong_sender() { .unwrap(); // beneficiary cannot release it - let execute_info = mock_info(beneficiary.as_str(), &[]); + let execute_info = mock_info(&beneficiary, &[]); let execute_res: ContractResult = execute(&mut deps, mock_env(), execute_info, ExecuteMsg::Release {}); let msg = execute_res.unwrap_err(); @@ -300,9 +307,9 @@ fn execute_release_fails_for_wrong_sender() { assert_eq!( state, State { - verifier: deps.api().canonical_address(&verifier).0.unwrap().into(), - beneficiary: deps.api().canonical_address(&beneficiary).0.unwrap().into(), - funder: deps.api().canonical_address(&creator).0.unwrap().into(), + verifier: Addr::unchecked(&verifier), + beneficiary: Addr::unchecked(&beneficiary), + funder: Addr::unchecked(&creator), } ); } diff --git a/contracts/ibc-reflect-send/schema/account_response.json b/contracts/ibc-reflect-send/schema/account_response.json index a30a7b2787..90faddfa1a 100644 --- a/contracts/ibc-reflect-send/schema/account_response.json +++ b/contracts/ibc-reflect-send/schema/account_response.json @@ -15,13 +15,9 @@ }, "remote_addr": { "description": "in normal cases, it should be set, but there is a delay between binding the channel and making a query and in that time it is empty", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "remote_balance": { @@ -47,9 +43,6 @@ } } }, - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/ibc-reflect-send/schema/admin_response.json b/contracts/ibc-reflect-send/schema/admin_response.json index af7a896be9..b0d3272d06 100644 --- a/contracts/ibc-reflect-send/schema/admin_response.json +++ b/contracts/ibc-reflect-send/schema/admin_response.json @@ -7,11 +7,6 @@ ], "properties": { "admin": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { "type": "string" } } diff --git a/contracts/ibc-reflect-send/schema/execute_msg.json b/contracts/ibc-reflect-send/schema/execute_msg.json index bb22dd5b23..8728e476ba 100644 --- a/contracts/ibc-reflect-send/schema/execute_msg.json +++ b/contracts/ibc-reflect-send/schema/execute_msg.json @@ -16,7 +16,7 @@ ], "properties": { "admin": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } diff --git a/contracts/ibc-reflect-send/schema/list_accounts_response.json b/contracts/ibc-reflect-send/schema/list_accounts_response.json index 3d99c0f11d..a229e91f38 100644 --- a/contracts/ibc-reflect-send/schema/list_accounts_response.json +++ b/contracts/ibc-reflect-send/schema/list_accounts_response.json @@ -33,13 +33,9 @@ }, "remote_addr": { "description": "in normal cases, it should be set, but there is a delay between binding the channel and making a query and in that time it is empty", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "remote_balance": { @@ -65,9 +61,6 @@ } } }, - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index 6e3e6f0b05..f8063c58bc 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - attr, entry_point, to_binary, CosmosMsg, Deps, DepsMut, Env, HumanAddr, IbcMsg, MessageInfo, - Order, QueryResponse, Response, StdError, StdResult, + attr, entry_point, to_binary, Addr, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, Order, + QueryResponse, Response, StdError, StdResult, }; use crate::ibc::build_timeout_timestamp; @@ -19,9 +19,7 @@ pub fn instantiate( _msg: InstantiateMsg, ) -> StdResult { // we store the reflect_id for creating accounts later - let cfg = Config { - admin: info.sender.into(), - }; + let cfg = Config { admin: info.sender }; config(deps.storage).save(&cfg)?; Ok(Response { @@ -52,14 +50,14 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S pub fn handle_update_admin( deps: DepsMut, info: MessageInfo, - new_admin: HumanAddr, + new_admin: String, ) -> StdResult { // auth check let mut cfg = config(deps.storage).load()?; - if info.sender.as_ref() != cfg.admin { + if info.sender != cfg.admin { return Err(StdError::generic_err("Only admin may set new admin")); } - cfg.admin = new_admin; + cfg.admin = deps.api.addr_validate(&new_admin)?; config(deps.storage).save(&cfg)?; Ok(Response { @@ -161,7 +159,7 @@ pub fn handle_send_funds( // load remote account let data = accounts(deps.storage).load(reflect_channel_id.as_bytes())?; - let remote_addr = match data.remote_addr { + let remote_addr: Addr = match data.remote_addr { Some(addr) => addr, None => { return Err(StdError::generic_err( @@ -173,7 +171,7 @@ pub fn handle_send_funds( // construct a packet to send let msg = IbcMsg::Transfer { channel_id: transfer_channel_id, - to_address: remote_addr, + to_address: remote_addr.into(), amount, timeout_block: None, timeout_timestamp: Some(build_timeout_timestamp(&env.block)), @@ -217,7 +215,9 @@ fn query_list_accounts(deps: Deps) -> StdResult { fn query_admin(deps: Deps) -> StdResult { let Config { admin } = config_read(deps.storage).load()?; - Ok(AdminResponse { admin }) + Ok(AdminResponse { + admin: admin.into(), + }) } #[cfg(test)] diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index c9acecf27f..2c50686e4f 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -160,7 +160,7 @@ fn acknowledge_who_am_i( ack: AcknowledgementMsg, ) -> StdResult { // ignore errors (but mention in log) - let res: WhoAmIResponse = match ack { + let WhoAmIResponse { account } = match ack { AcknowledgementMsg::Ok(res) => res, AcknowledgementMsg::Err(e) => { return Ok(IbcBasicResponse { @@ -170,13 +170,14 @@ fn acknowledge_who_am_i( }) } }; + let checked_account = deps.api.addr_validate(&account)?; accounts(deps.storage).update(caller.as_bytes(), |acct| -> StdResult<_> { match acct { Some(mut acct) => { // set the account the first time if acct.remote_addr.is_none() { - acct.remote_addr = Some(res.account); + acct.remote_addr = Some(checked_account); } Ok(acct) } @@ -199,7 +200,7 @@ fn acknowledge_balances( ack: AcknowledgementMsg, ) -> StdResult { // ignore errors (but mention in log) - let res: BalancesResponse = match ack { + let BalancesResponse { account, balances } = match ack { AcknowledgementMsg::Ok(res) => res, AcknowledgementMsg::Err(e) => { return Ok(IbcBasicResponse { @@ -209,22 +210,23 @@ fn acknowledge_balances( }) } }; + let checked_account = deps.api.addr_validate(&account)?; accounts(deps.storage).update(caller.as_bytes(), |acct| -> StdResult<_> { match acct { Some(acct) => { - if let Some(old_addr) = &acct.remote_addr { - if old_addr != &res.account { + if let Some(old_addr) = acct.remote_addr { + if old_addr != checked_account { return Err(StdError::generic_err(format!( "remote account changed from {} to {}", - old_addr, &res.account + old_addr, checked_account ))); } } Ok(AccountData { last_update_time: env.block.time, - remote_addr: Some(res.account), - remote_balance: res.balances, + remote_addr: Some(checked_account), + remote_balance: balances, }) } None => Err(StdError::generic_err("no account to update")), @@ -262,7 +264,7 @@ mod tests { mock_dependencies, mock_env, mock_ibc_channel, mock_ibc_packet_ack, mock_info, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{coin, coins, BankMsg, CosmosMsg, HumanAddr, OwnedDeps}; + use cosmwasm_std::{coin, coins, BankMsg, CosmosMsg, OwnedDeps}; const CREATOR: &str = "creator"; @@ -299,7 +301,7 @@ mod tests { }; } - fn who_am_i_response>(deps: DepsMut, channel_id: &str, account: T) { + fn who_am_i_response>(deps: DepsMut, channel_id: &str, account: T) { let packet = PacketMsg::WhoAmI {}; let response = AcknowledgementMsg::Ok(WhoAmIResponse { account: account.into(), @@ -353,7 +355,7 @@ mod tests { }; let r = query(deps.as_ref(), mock_env(), q).unwrap(); let acct: AccountResponse = from_slice(&r).unwrap(); - assert_eq!(acct.remote_addr.unwrap(), HumanAddr::from(remote_addr)); + assert_eq!(acct.remote_addr.unwrap(), remote_addr); assert!(acct.remote_balance.is_empty()); assert_eq!(0, acct.last_update_time); } diff --git a/contracts/ibc-reflect-send/src/ibc_msg.rs b/contracts/ibc-reflect-send/src/ibc_msg.rs index 6a436be752..94c55fdacc 100644 --- a/contracts/ibc-reflect-send/src/ibc_msg.rs +++ b/contracts/ibc-reflect-send/src/ibc_msg.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, ContractResult, CosmosMsg, HumanAddr}; +use cosmwasm_std::{Coin, ContractResult, CosmosMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -23,13 +23,13 @@ pub type DispatchResponse = (); /// Return the caller's account address on the remote chain #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct WhoAmIResponse { - pub account: HumanAddr, + pub account: String, } /// This is the success response we send on ack for PacketMsg::Balance. /// Just acknowledge success or error #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BalancesResponse { - pub account: HumanAddr, + pub account: String, pub balances: Vec, } diff --git a/contracts/ibc-reflect-send/src/msg.rs b/contracts/ibc-reflect-send/src/msg.rs index e61491bb65..e439f73225 100644 --- a/contracts/ibc-reflect-send/src/msg.rs +++ b/contracts/ibc-reflect-send/src/msg.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, CosmosMsg, Empty, HumanAddr}; +use cosmwasm_std::{Coin, CosmosMsg, Empty}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -14,7 +14,7 @@ pub struct InstantiateMsg {} pub enum ExecuteMsg { /// Changes the admin UpdateAdmin { - admin: HumanAddr, + admin: String, }, SendMsgs { channel_id: String, @@ -50,7 +50,7 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct AdminResponse { - pub admin: HumanAddr, + pub admin: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -65,7 +65,7 @@ pub struct AccountInfo { pub last_update_time: u64, /// in normal cases, it should be set, but there is a delay between binding /// the channel and making a query and in that time it is empty - pub remote_addr: Option, + pub remote_addr: Option, pub remote_balance: Vec, } @@ -74,7 +74,7 @@ impl AccountInfo { AccountInfo { channel_id, last_update_time: input.last_update_time, - remote_addr: input.remote_addr, + remote_addr: input.remote_addr.map(|addr| addr.into()), remote_balance: input.remote_balance, } } @@ -86,7 +86,7 @@ pub struct AccountResponse { pub last_update_time: u64, /// in normal cases, it should be set, but there is a delay between binding /// the channel and making a query and in that time it is empty - pub remote_addr: Option, + pub remote_addr: Option, pub remote_balance: Vec, } @@ -94,7 +94,7 @@ impl From for AccountResponse { fn from(input: AccountData) -> Self { AccountResponse { last_update_time: input.last_update_time, - remote_addr: input.remote_addr, + remote_addr: input.remote_addr.map(|addr| addr.into()), remote_balance: input.remote_balance, } } diff --git a/contracts/ibc-reflect-send/src/state.rs b/contracts/ibc-reflect-send/src/state.rs index da687edef4..decc004be4 100644 --- a/contracts/ibc-reflect-send/src/state.rs +++ b/contracts/ibc-reflect-send/src/state.rs @@ -1,7 +1,6 @@ -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Coin, HumanAddr, Storage}; +use cosmwasm_std::{Addr, Coin, Storage}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, @@ -10,18 +9,18 @@ use cosmwasm_storage::{ pub const KEY_CONFIG: &[u8] = b"config"; pub const PREFIX_ACCOUNTS: &[u8] = b"accounts"; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct Config { - pub admin: HumanAddr, + pub admin: Addr, } -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)] pub struct AccountData { /// last block balance was updated (0 is never) pub last_update_time: u64, /// in normal cases, it should be set, but there is a delay between binding /// the channel and making a query and in that time it is empty - pub remote_addr: Option, + pub remote_addr: Option, pub remote_balance: Vec, } diff --git a/contracts/ibc-reflect-send/tests/integration.rs b/contracts/ibc-reflect-send/tests/integration.rs index 0193a82d2e..a0a375c008 100644 --- a/contracts/ibc-reflect-send/tests/integration.rs +++ b/contracts/ibc-reflect-send/tests/integration.rs @@ -19,8 +19,8 @@ use cosmwasm_std::testing::{mock_ibc_channel, mock_ibc_packet_ack}; use cosmwasm_std::{ - attr, coin, coins, to_binary, BankMsg, CosmosMsg, Empty, HumanAddr, IbcAcknowledgement, - IbcBasicResponse, IbcMsg, IbcOrder, Response, + attr, coin, coins, to_binary, BankMsg, CosmosMsg, Empty, IbcAcknowledgement, IbcBasicResponse, + IbcMsg, IbcOrder, Response, }; use cosmwasm_vm::testing::{ execute, ibc_channel_connect, ibc_channel_open, ibc_packet_ack, instantiate, mock_env, @@ -71,7 +71,7 @@ fn connect(deps: &mut Instance, channel_id: & }; } -fn who_am_i_response>( +fn who_am_i_response>( deps: &mut Instance, channel_id: &str, account: T, @@ -140,7 +140,7 @@ fn proper_handshake_flow() { // account should be set up let acct = get_account(&mut deps, channel_id); - assert_eq!(acct.remote_addr.unwrap(), HumanAddr::from(remote_addr)); + assert_eq!(acct.remote_addr.unwrap(), remote_addr); assert!(acct.remote_balance.is_empty()); assert_eq!(0, acct.last_update_time); } diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json index d7e06ce0f9..198e96e575 100644 --- a/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json +++ b/contracts/ibc-reflect/schema/acknowledgement_msg_balances.json @@ -39,7 +39,7 @@ ], "properties": { "account": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "balances": { "type": "array", @@ -64,9 +64,6 @@ } } }, - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json b/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json index 5718ad1197..ded83faf45 100644 --- a/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json +++ b/contracts/ibc-reflect/schema/acknowledgement_msg_who_am_i.json @@ -30,9 +30,6 @@ } ], "definitions": { - "HumanAddr": { - "type": "string" - }, "WhoAmIResponse": { "description": "This is the success response we send on ack for PacketMsg::WhoAmI. Return the caller's account address on the remote chain", "type": "object", @@ -41,7 +38,7 @@ ], "properties": { "account": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } diff --git a/contracts/ibc-reflect/schema/execute_msg.json b/contracts/ibc-reflect/schema/execute_msg.json index f97134ed82..8fb4672447 100644 --- a/contracts/ibc-reflect/schema/execute_msg.json +++ b/contracts/ibc-reflect/schema/execute_msg.json @@ -18,11 +18,7 @@ "properties": { "contract_addr": { "description": "contract_addr is the address of this contract", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" }, "id": { "description": "id was provided in the InitMsg", @@ -33,10 +29,5 @@ }, "additionalProperties": false } - ], - "definitions": { - "HumanAddr": { - "type": "string" - } - } + ] } diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index 5755626200..14cbbb0c11 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ attr, entry_point, from_slice, to_binary, wasm_execute, wasm_instantiate, BankMsg, CosmosMsg, - Deps, DepsMut, Empty, Env, HumanAddr, IbcAcknowledgement, IbcBasicResponse, IbcChannel, - IbcOrder, IbcPacket, IbcReceiveResponse, MessageInfo, Order, QueryResponse, Response, StdError, + Deps, DepsMut, Empty, Env, IbcAcknowledgement, IbcBasicResponse, IbcChannel, IbcOrder, + IbcPacket, IbcReceiveResponse, MessageInfo, Order, QueryResponse, Response, StdError, StdResult, }; @@ -53,10 +53,12 @@ pub fn execute_init_callback( deps: DepsMut, info: MessageInfo, id: String, - contract_addr: HumanAddr, + contract_addr: String, ) -> StdResult { + let contract_addr = deps.api.addr_validate(&contract_addr)?; + // sanity check - the caller is registering itself - if info.sender.as_ref() != contract_addr { + if info.sender != contract_addr { return Err(StdError::generic_err("Must register self on callback")); } @@ -90,18 +92,18 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { pub fn query_account(deps: Deps, channel_id: String) -> StdResult { let account = accounts_read(deps.storage).load(channel_id.as_bytes())?; Ok(AccountResponse { - account: Some(account), + account: Some(account.into()), }) } pub fn query_list_accounts(deps: Deps) -> StdResult { let accounts: StdResult> = accounts_read(deps.storage) .range(None, None, Order::Ascending) - .map(|r| { - let (k, account) = r?; + .map(|item| { + let (key, account) = item?; Ok(AccountInfo { - account, - channel_id: String::from_utf8(k)?, + account: account.into(), + channel_id: String::from_utf8(key)?, }) }) .collect(); @@ -172,7 +174,7 @@ pub fn ibc_channel_close( accounts(deps.storage).remove(channel_id.as_bytes()); // transfer current balance if any (steal the money) - let amount = deps.querier.query_all_balances(&reflect_addr)?; + let amount = deps.querier.query_all_balances(reflect_addr.clone())?; let messages: Vec> = if !amount.is_empty() { let bank_msg = BankMsg::Send { to_address: env.contract.address.into(), @@ -266,7 +268,9 @@ fn receive_dispatch( // processes PacketMsg::WhoAmI variant fn receive_who_am_i(deps: DepsMut, caller: String) -> StdResult { let account = accounts(deps.storage).load(caller.as_bytes())?; - let response = WhoAmIResponse { account }; + let response = WhoAmIResponse { + account: account.into(), + }; let acknowledgement = to_binary(&AcknowledgementMsg::Ok(response))?; // and we are golden Ok(IbcReceiveResponse { @@ -280,8 +284,11 @@ fn receive_who_am_i(deps: DepsMut, caller: String) -> StdResult StdResult { let account = accounts(deps.storage).load(caller.as_bytes())?; - let balances = deps.querier.query_all_balances(&account)?; - let response = BalancesResponse { account, balances }; + let balances = deps.querier.query_all_balances(account.clone())?; + let response = BalancesResponse { + account: account.into(), + balances, + }; let acknowledgement = to_binary(&AcknowledgementMsg::Ok(response))?; // and we are golden Ok(IbcReceiveResponse { @@ -348,8 +355,8 @@ mod tests { // connect will run through the entire handshake to set up a proper connect and // save the account (tested in detail in `proper_handshake_flow`) - fn connect>(mut deps: DepsMut, channel_id: &str, account: T) { - let account: HumanAddr = account.into(); + fn connect>(mut deps: DepsMut, channel_id: &str, account: T) { + let account: String = account.into(); // open packet has no counterparty versin, connect does // TODO: validate this with alpe @@ -465,7 +472,7 @@ mod tests { ) .unwrap(); let res: AccountResponse = from_slice(&raw).unwrap(); - assert_eq!(res.account.unwrap(), HumanAddr::from(REFLECT_ADDR)); + assert_eq!(res.account.unwrap(), REFLECT_ADDR); } #[test] @@ -492,7 +499,7 @@ mod tests { let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap(); assert_eq!( ack.unwrap_err(), - "invalid packet: cosmwasm_std::addresses::HumanAddr not found" + "invalid packet: cosmwasm_std::addresses::Addr not found" ); // register the channel diff --git a/contracts/ibc-reflect/src/msg.rs b/contracts/ibc-reflect/src/msg.rs index f11a807dd5..41ef3b7460 100644 --- a/contracts/ibc-reflect/src/msg.rs +++ b/contracts/ibc-reflect/src/msg.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, ContractResult, CosmosMsg, HumanAddr}; +use cosmwasm_std::{Coin, ContractResult, CosmosMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ pub enum ExecuteMsg { /// id was provided in the InitMsg id: String, /// contract_addr is the address of this contract - contract_addr: HumanAddr, + contract_addr: String, }, } @@ -35,7 +35,7 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct AccountResponse { - pub account: Option, + pub account: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -45,7 +45,7 @@ pub struct ListAccountsResponse { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct AccountInfo { - pub account: HumanAddr, + pub account: String, pub channel_id: String, } @@ -81,13 +81,13 @@ pub type DispatchResponse = (); /// Return the caller's account address on the remote chain #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct WhoAmIResponse { - pub account: HumanAddr, + pub account: String, } /// This is the success response we send on ack for PacketMsg::Balance. /// Just acknowledge success or error #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BalancesResponse { - pub account: HumanAddr, + pub account: String, pub balances: Vec, } diff --git a/contracts/ibc-reflect/src/state.rs b/contracts/ibc-reflect/src/state.rs index 355123a6cc..21bc37835e 100644 --- a/contracts/ibc-reflect/src/state.rs +++ b/contracts/ibc-reflect/src/state.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{HumanAddr, Storage}; +use cosmwasm_std::{Addr, Storage}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, @@ -16,11 +16,11 @@ pub struct Config { } /// accounts is lookup of channel_id to reflect contract -pub fn accounts(storage: &mut dyn Storage) -> Bucket { +pub fn accounts(storage: &mut dyn Storage) -> Bucket { bucket(storage, PREFIX_ACCOUNTS) } -pub fn accounts_read(storage: &dyn Storage) -> ReadonlyBucket { +pub fn accounts_read(storage: &dyn Storage) -> ReadonlyBucket { bucket_read(storage, PREFIX_ACCOUNTS) } diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs index ab456cc0a3..b6028b2766 100644 --- a/contracts/ibc-reflect/tests/integration.rs +++ b/contracts/ibc-reflect/tests/integration.rs @@ -19,8 +19,8 @@ use cosmwasm_std::testing::{mock_ibc_channel, mock_ibc_packet_recv}; use cosmwasm_std::{ - coins, BankMsg, ContractResult, CosmosMsg, HumanAddr, IbcBasicResponse, IbcOrder, - IbcReceiveResponse, Response, WasmMsg, + coins, BankMsg, ContractResult, CosmosMsg, IbcBasicResponse, IbcOrder, IbcReceiveResponse, + Response, WasmMsg, }; use cosmwasm_vm::testing::{ execute, ibc_channel_connect, ibc_channel_open, ibc_packet_receive, instantiate, mock_env, @@ -56,12 +56,12 @@ fn setup() -> Instance { // connect will run through the entire handshake to set up a proper connect and // save the account (tested in detail in `proper_handshake_flow`) -fn connect>( +fn connect>( deps: &mut Instance, channel_id: &str, account: T, ) { - let account: HumanAddr = account.into(); + let account: String = account.into(); // first we try to open with a valid handshake let mut handshake_open = mock_ibc_channel(channel_id, IbcOrder::Ordered, IBC_VERSION); handshake_open.counterparty_version = None; @@ -176,7 +176,7 @@ fn proper_handshake_flow() { ) .unwrap(); let res: AccountResponse = from_slice(&raw).unwrap(); - assert_eq!(res.account.unwrap(), HumanAddr::from(REFLECT_ADDR)); + assert_eq!(res.account.unwrap(), REFLECT_ADDR); } #[test] @@ -203,7 +203,7 @@ fn handle_dispatch_packet() { let ack: AcknowledgementMsg = from_slice(&res.acknowledgement).unwrap(); assert_eq!( ack.unwrap_err(), - "invalid packet: cosmwasm_std::addresses::HumanAddr not found" + "invalid packet: cosmwasm_std::addresses::Addr not found" ); // register the channel diff --git a/contracts/queue/tests/integration.rs b/contracts/queue/tests/integration.rs index d33650c13e..d096ad9380 100644 --- a/contracts/queue/tests/integration.rs +++ b/contracts/queue/tests/integration.rs @@ -17,7 +17,7 @@ //! }); //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) -use cosmwasm_std::{from_binary, from_slice, HumanAddr, MessageInfo, Response}; +use cosmwasm_std::{from_binary, from_slice, MessageInfo, Response}; use cosmwasm_vm::{ testing::{ execute, instantiate, migrate, mock_env, mock_info, mock_instance_with_gas_limit, query, @@ -36,8 +36,8 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/qu fn create_contract() -> (Instance, MessageInfo) { let gas_limit = 500_000_000; // enough for many executions within one instance let mut deps = mock_instance_with_gas_limit(WASM, gas_limit); - let creator = HumanAddr(String::from("creator")); - let info = mock_info(creator.as_str(), &[]); + let creator = String::from("creator"); + let info = mock_info(&creator, &[]); let res: Response = instantiate(&mut deps, mock_env(), info.clone(), InstantiateMsg {}).unwrap(); assert_eq!(0, res.messages.len()); diff --git a/contracts/reflect/examples/schema.rs b/contracts/reflect/examples/schema.rs index edbb6e083b..027928a2c7 100644 --- a/contracts/reflect/examples/schema.rs +++ b/contracts/reflect/examples/schema.rs @@ -8,7 +8,6 @@ use reflect::msg::{ CapitalizedResponse, ChainResponse, CustomMsg, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg, RawResponse, }; -use reflect::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -21,7 +20,6 @@ fn main() { export_schema(&schema_for!(ExecuteMsg), &out_dir); export_schema(&schema_for!(Response), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(State), &out_dir); // The possible return types for QueryMsg cases export_schema(&schema_for!(OwnerResponse), &out_dir); diff --git a/contracts/reflect/schema/execute_msg.json b/contracts/reflect/schema/execute_msg.json index 5fb601b79d..23ce07ec61 100644 --- a/contracts/reflect/schema/execute_msg.json +++ b/contracts/reflect/schema/execute_msg.json @@ -61,7 +61,7 @@ ], "properties": { "owner": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } diff --git a/contracts/reflect/schema/owner_response.json b/contracts/reflect/schema/owner_response.json index e2a3fc2b8e..0ec6409812 100644 --- a/contracts/reflect/schema/owner_response.json +++ b/contracts/reflect/schema/owner_response.json @@ -7,11 +7,6 @@ ], "properties": { "owner": { - "$ref": "#/definitions/HumanAddr" - } - }, - "definitions": { - "HumanAddr": { "type": "string" } } diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json index c746c5d090..4f30a8e606 100644 --- a/contracts/reflect/schema/query_msg.json +++ b/contracts/reflect/schema/query_msg.json @@ -71,7 +71,7 @@ ], "properties": { "contract": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "key": { "$ref": "#/definitions/Binary" diff --git a/contracts/reflect/schema/state.json b/contracts/reflect/schema/state.json deleted file mode 100644 index d4b4a3340f..0000000000 --- a/contracts/reflect/schema/state.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "State", - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "$ref": "#/definitions/CanonicalAddr" - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "CanonicalAddr": { - "$ref": "#/definitions/Binary" - } - } -} diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index 85b4107cdf..982b71b538 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ attr, entry_point, to_binary, to_vec, Binary, ContractResult, CosmosMsg, Deps, DepsMut, Env, - HumanAddr, MessageInfo, QueryRequest, QueryResponse, Reply, Response, StdError, StdResult, - SubMsg, SystemResult, WasmMsg, + MessageInfo, QueryRequest, QueryResponse, Reply, Response, StdError, StdResult, SubMsg, + SystemResult, WasmMsg, }; use crate::errors::ReflectError; @@ -18,7 +18,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult> { let state = State { - owner: deps.api.addr_canonicalize(info.sender.as_ref())?, + owner: info.sender.clone(), }; config(deps.storage).save(&state)?; @@ -59,11 +59,10 @@ pub fn try_reflect( ) -> Result, ReflectError> { let state = config(deps.storage).load()?; - let sender = deps.api.addr_canonicalize(info.sender.as_ref())?; - if sender != state.owner { + if info.sender != state.owner { return Err(ReflectError::NotCurrentOwner { - expected: state.owner, - actual: sender, + expected: state.owner.into(), + actual: info.sender.into(), }); } @@ -86,11 +85,10 @@ pub fn try_reflect_subcall( msgs: Vec>, ) -> Result, ReflectError> { let state = config(deps.storage).load()?; - let sender = deps.api.addr_canonicalize(info.sender.as_ref())?; - if sender != state.owner { + if info.sender != state.owner { return Err(ReflectError::NotCurrentOwner { - expected: state.owner, - actual: sender, + expected: state.owner.into(), + actual: info.sender.into(), }); } @@ -110,22 +108,21 @@ pub fn try_change_owner( deps: DepsMut, _env: Env, info: MessageInfo, - owner: HumanAddr, + new_owner: String, ) -> Result, ReflectError> { let api = deps.api; config(deps.storage).update(|mut state| { - let sender = api.addr_canonicalize(info.sender.as_ref())?; - if sender != state.owner { + if info.sender != state.owner { return Err(ReflectError::NotCurrentOwner { - expected: state.owner, - actual: sender, + expected: state.owner.into(), + actual: info.sender.into(), }); } - state.owner = api.addr_canonicalize(&owner)?; + state.owner = api.addr_validate(&new_owner)?; Ok(state) })?; Ok(Response { - attributes: vec![attr("action", "change_owner"), attr("owner", owner)], + attributes: vec![attr("action", "change_owner"), attr("owner", new_owner)], ..Response::default() }) } @@ -151,7 +148,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { fn query_owner(deps: Deps) -> StdResult { let state = config_read(deps.storage).load()?; let resp = OwnerResponse { - owner: deps.api.addr_humanize(&state.owner)?, + owner: state.owner.into(), }; Ok(resp) } @@ -184,7 +181,7 @@ fn query_chain(deps: Deps, request: &QueryRequest) -> StdResult StdResult { +fn query_raw(deps: Deps, contract: String, key: Binary) -> StdResult { let response: Option> = deps.querier.query_wasm_raw(contract, key)?; Ok(RawResponse { data: response.unwrap_or_default().into(), @@ -197,8 +194,8 @@ mod tests { use crate::testing::mock_dependencies_with_custom_querier; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - coin, coins, from_binary, AllBalanceResponse, Api, BankMsg, BankQuery, Binary, - ContractResult, Event, ReplyOn, StakingMsg, StdError, SubcallResponse, + coin, coins, from_binary, AllBalanceResponse, BankMsg, BankQuery, Binary, ContractResult, + Event, HumanAddr, ReplyOn, StakingMsg, StdError, SubcallResponse, }; #[test] @@ -220,7 +217,7 @@ mod tests { #[test] fn instantiate_with_callback() { let mut deps = mock_dependencies_with_custom_querier(&[]); - let caller = HumanAddr::from("calling-contract"); + let caller = String::from("calling-contract"); let msg = InstantiateMsg { callback_id: Some("foobar".to_string()), @@ -237,7 +234,7 @@ mod tests { msg, send, }) => { - assert_eq!(contract_addr, &caller); + assert_eq!(contract_addr.as_str(), &caller); let parsed: CallbackMsg = from_binary(&msg).unwrap(); assert_eq!( parsed, @@ -253,7 +250,7 @@ mod tests { // it worked, let's query the state let value = query_owner(deps.as_ref()).unwrap(); - assert_eq!(caller, value.owner); + assert_eq!(value.owner, caller); } #[test] @@ -359,7 +356,7 @@ mod tests { let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); let info = mock_info("creator", &[]); - let new_owner = HumanAddr::from("friend"); + let new_owner = String::from("friend"); let msg = ExecuteMsg::ChangeOwner { owner: new_owner }; let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); @@ -374,25 +371,29 @@ mod tests { let mut deps = mock_dependencies_with_custom_querier(&[]); let msg = InstantiateMsg { callback_id: None }; - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let info = mock_info(&creator, &coins(2, "token")); let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - let random = HumanAddr::from("random"); + let random = String::from("random"); let info = mock_info(&random, &[]); - let new_owner = HumanAddr::from("friend"); + let new_owner = String::from("friend"); let msg = ExecuteMsg::ChangeOwner { owner: new_owner }; let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - let expected = deps.api.addr_canonicalize(&creator).unwrap(); - let actual = deps.api.addr_canonicalize(&random).unwrap(); - assert_eq!(err, ReflectError::NotCurrentOwner { expected, actual }); + assert_eq!( + err, + ReflectError::NotCurrentOwner { + expected: creator, + actual: random + } + ); } #[test] fn change_owner_errors_for_invalid_new_address() { let mut deps = mock_dependencies_with_custom_querier(&[]); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let msg = InstantiateMsg { callback_id: None }; let info = mock_info(&creator, &coins(2, "token")); @@ -400,7 +401,7 @@ mod tests { let info = mock_info(&creator, &[]); let msg = ExecuteMsg::ChangeOwner { - owner: HumanAddr::from("x"), + owner: String::from("x"), }; let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); match err { diff --git a/contracts/reflect/src/errors.rs b/contracts/reflect/src/errors.rs index 58e360d9ed..98db940523 100644 --- a/contracts/reflect/src/errors.rs +++ b/contracts/reflect/src/errors.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{CanonicalAddr, StdError}; +use cosmwasm_std::StdError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -8,10 +8,7 @@ pub enum ReflectError { Std(#[from] StdError), // this is whatever we want #[error("Permission denied: the sender is not the current owner")] - NotCurrentOwner { - expected: CanonicalAddr, - actual: CanonicalAddr, - }, + NotCurrentOwner { expected: String, actual: String }, #[error("Messages empty. Must reflect at least one message")] MessagesEmpty, } diff --git a/contracts/reflect/src/msg.rs b/contracts/reflect/src/msg.rs index b4cd5e0aae..14f4b364c8 100644 --- a/contracts/reflect/src/msg.rs +++ b/contracts/reflect/src/msg.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, CosmosMsg, CustomQuery, HumanAddr, QueryRequest, SubMsg}; +use cosmwasm_std::{Binary, CosmosMsg, CustomQuery, QueryRequest, SubMsg}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { @@ -19,7 +19,7 @@ pub enum CallbackMsg { /// Callback ID provided in the InstantiateMsg id: String, /// contract_addr is the address of this contract - contract_addr: HumanAddr, + contract_addr: String, }, } @@ -28,7 +28,7 @@ pub enum CallbackMsg { pub enum ExecuteMsg { ReflectMsg { msgs: Vec> }, ReflectSubCall { msgs: Vec> }, - ChangeOwner { owner: HumanAddr }, + ChangeOwner { owner: String }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -45,7 +45,7 @@ pub enum QueryMsg { }, /// Queries another contract and returns the data Raw { - contract: HumanAddr, + contract: String, key: Binary, }, /// If there was a previous ReflectSubCall with this ID, returns cosmwasm_std::Reply @@ -58,7 +58,7 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct OwnerResponse { - pub owner: HumanAddr, + pub owner: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/contracts/reflect/src/state.rs b/contracts/reflect/src/state.rs index 00b9031401..ef2706b9dc 100644 --- a/contracts/reflect/src/state.rs +++ b/contracts/reflect/src/state.rs @@ -1,7 +1,6 @@ -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{CanonicalAddr, Reply, Storage}; +use cosmwasm_std::{Addr, Reply, Storage}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, @@ -10,9 +9,9 @@ use cosmwasm_storage::{ const CONFIG_KEY: &[u8] = b"config"; const RESULT_PREFIX: &[u8] = b"result"; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct State { - pub owner: CanonicalAddr, + pub owner: Addr, } pub fn config(storage: &mut dyn Storage) -> Singleton { diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index ee744cadd5..e6a1901840 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -138,7 +138,7 @@ fn transfer() { let _res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); let info = mock_info("creator", &[]); - let new_owner = HumanAddr::from("friend"); + let new_owner = String::from("friend"); let msg = ExecuteMsg::ChangeOwner { owner: new_owner }; let res: Response = execute(&mut deps, mock_env(), info, msg).unwrap(); @@ -158,7 +158,7 @@ fn transfer_requires_owner() { let _res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); let info = mock_info("random", &[]); - let new_owner = HumanAddr::from("friend"); + let new_owner = String::from("friend"); let msg = ExecuteMsg::ChangeOwner { owner: new_owner }; let res: ContractResult = execute(&mut deps, mock_env(), info, msg); diff --git a/contracts/staking/examples/schema.rs b/contracts/staking/examples/schema.rs index cb706f1c21..4d57c6de89 100644 --- a/contracts/staking/examples/schema.rs +++ b/contracts/staking/examples/schema.rs @@ -7,7 +7,6 @@ use staking::msg::{ BalanceResponse, ClaimsResponse, ExecuteMsg, InstantiateMsg, InvestmentResponse, QueryMsg, TokenInfoResponse, }; -use staking::state::{InvestmentInfo, Supply}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -22,6 +21,4 @@ fn main() { export_schema(&schema_for!(ClaimsResponse), &out_dir); export_schema(&schema_for!(InvestmentResponse), &out_dir); export_schema(&schema_for!(TokenInfoResponse), &out_dir); - export_schema(&schema_for!(InvestmentInfo), &out_dir); - export_schema(&schema_for!(Supply), &out_dir); } diff --git a/contracts/staking/schema/execute_msg.json b/contracts/staking/schema/execute_msg.json index c324cab0e2..2aad3c31ec 100644 --- a/contracts/staking/schema/execute_msg.json +++ b/contracts/staking/schema/execute_msg.json @@ -20,7 +20,7 @@ "$ref": "#/definitions/Uint128" }, "recipient": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -102,9 +102,6 @@ } ], "definitions": { - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/staking/schema/instantiate_msg.json b/contracts/staking/schema/instantiate_msg.json index e4f865f970..361879b512 100644 --- a/contracts/staking/schema/instantiate_msg.json +++ b/contracts/staking/schema/instantiate_msg.json @@ -43,11 +43,7 @@ }, "validator": { "description": "This is the validator that all tokens will be bonded to", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } }, "definitions": { @@ -55,9 +51,6 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/staking/schema/investment_info.json b/contracts/staking/schema/investment_info.json deleted file mode 100644 index be49695bb3..0000000000 --- a/contracts/staking/schema/investment_info.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InvestmentInfo", - "description": "Investment info is fixed at initialization, and is used to control the function of the contract", - "type": "object", - "required": [ - "bond_denom", - "exit_tax", - "min_withdrawal", - "owner", - "validator" - ], - "properties": { - "bond_denom": { - "description": "this is the denomination we can stake (and only one we accept for payments)", - "type": "string" - }, - "exit_tax": { - "description": "this is how much the owner takes as a cut when someone unbonds", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "min_withdrawal": { - "description": "This is the minimum amount we will pull out to reinvest, as well as a minumum that can be unbonded (to avoid needless staking tx)", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "owner": { - "description": "owner created the contract and takes a cut", - "allOf": [ - { - "$ref": "#/definitions/CanonicalAddr" - } - ] - }, - "validator": { - "description": "All tokens are bonded to this validator FIXME: humanize/canonicalize address doesn't work for validator addrresses", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "CanonicalAddr": { - "$ref": "#/definitions/Binary" - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "HumanAddr": { - "type": "string" - }, - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/staking/schema/investment_response.json b/contracts/staking/schema/investment_response.json index cbaaafef5c..ce1a140b6a 100644 --- a/contracts/staking/schema/investment_response.json +++ b/contracts/staking/schema/investment_response.json @@ -33,11 +33,7 @@ }, "owner": { "description": "owner created the contract and takes a cut", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" }, "staked_tokens": { "$ref": "#/definitions/Coin" @@ -47,11 +43,7 @@ }, "validator": { "description": "All tokens are bonded to this validator", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } }, "definitions": { @@ -74,9 +66,6 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, - "HumanAddr": { - "type": "string" - }, "Uint128": { "type": "string" } diff --git a/contracts/staking/schema/query_msg.json b/contracts/staking/schema/query_msg.json index d7cde031f2..4eb5798f66 100644 --- a/contracts/staking/schema/query_msg.json +++ b/contracts/staking/schema/query_msg.json @@ -16,7 +16,7 @@ ], "properties": { "address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -37,7 +37,7 @@ ], "properties": { "address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -70,10 +70,5 @@ }, "additionalProperties": false } - ], - "definitions": { - "HumanAddr": { - "type": "string" - } - } + ] } diff --git a/contracts/staking/schema/supply.json b/contracts/staking/schema/supply.json deleted file mode 100644 index 0c5af2696b..0000000000 --- a/contracts/staking/schema/supply.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Supply", - "description": "Supply is dynamic and tracks the current supply of staked and ERC20 tokens.", - "type": "object", - "required": [ - "bonded", - "claims", - "issued" - ], - "properties": { - "bonded": { - "description": "bonded is how many native tokens exist bonded to the validator", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "claims": { - "description": "claims is how many tokens need to be reserved paying back those who unbonded", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "issued": { - "description": "issued is how many derivative tokens this contract has issued", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "definitions": { - "Uint128": { - "type": "string" - } - } -} diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index bf97adda36..dbc7a1941b 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -25,7 +25,7 @@ pub fn instantiate( ) -> StdResult { // ensure the validator is registered let vals = deps.querier.query_validators()?; - if !vals.iter().any(|v| v.address == msg.validator) { + if !vals.iter().any(|v| v.address.as_str() == msg.validator) { return Err(StdError::generic_err(format!( "{} is not in the current validator set", msg.validator @@ -41,10 +41,10 @@ pub fn instantiate( let denom = deps.querier.query_bonded_denom()?; let invest = InvestmentInfo { - owner: deps.api.addr_canonicalize(info.sender.as_ref())?, + owner: info.sender, exit_tax: msg.exit_tax, bond_denom: denom, - validator: msg.validator, + validator: deps.api.addr_validate(&msg.validator)?.into(), min_withdrawal: msg.min_withdrawal, }; invest_info(deps.storage).save(&invest)?; @@ -79,7 +79,7 @@ pub fn transfer( deps: DepsMut, _env: Env, info: MessageInfo, - recipient: HumanAddr, + recipient: String, send: Uint128, ) -> StdResult { let rcpt_raw = deps.api.addr_canonicalize(&recipient)?; @@ -177,7 +177,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { let res = Response { submessages: vec![], messages: vec![StakingMsg::Delegate { - validator: invest.validator, + validator: invest.validator.into(), amount: payment.clone(), } .into()], @@ -193,8 +193,6 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { } pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> StdResult { - let sender_raw = deps.api.addr_canonicalize(info.sender.as_ref())?; - let invest = invest_info_read(deps.storage).load()?; // ensure it is big enough to care if amount < invest.min_withdrawal { @@ -203,6 +201,10 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St invest.min_withdrawal, invest.bond_denom ))); } + + let sender_raw = deps.api.addr_canonicalize(info.sender.as_ref())?; + let owner_raw = deps.api.addr_canonicalize(invest.owner.as_ref())?; + // calculate tax and remainer to unbond let tax = amount * invest.exit_tax; @@ -213,7 +215,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St })?; if tax > Uint128(0) { // add tax to the owner - accounts.update(&invest.owner, |balance: Option| -> StdResult<_> { + accounts.update(&owner_raw, |balance: Option| -> StdResult<_> { Ok(balance.unwrap_or_default() + tax) })?; } @@ -243,7 +245,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St let res = Response { submessages: vec![], messages: vec![StakingMsg::Undelegate { - validator: invest.validator, + validator: invest.validator.into(), amount: coin(unbond.u128(), &invest.bond_denom), } .into()], @@ -317,7 +319,7 @@ pub fn reinvest(deps: DepsMut, env: Env, _info: MessageInfo) -> StdResult StdResult { match msg { QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?), QueryMsg::Investment {} => to_binary(&query_investment(deps)?), - QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?), - QueryMsg::Claims { address } => to_binary(&query_claims(deps, address)?), + QueryMsg::Balance { address } => to_binary(&query_balance(deps, &address)?), + QueryMsg::Claims { address } => to_binary(&query_claims(deps, &address)?), } } @@ -393,16 +395,16 @@ pub fn query_token_info(deps: Deps) -> StdResult { token_info_read(deps.storage).load() } -pub fn query_balance(deps: Deps, address: HumanAddr) -> StdResult { - let address_raw = deps.api.addr_canonicalize(&address)?; +pub fn query_balance(deps: Deps, address: &str) -> StdResult { + let address_raw = deps.api.addr_canonicalize(address)?; let balance = balances_read(deps.storage) .may_load(address_raw.as_slice())? .unwrap_or_default(); Ok(BalanceResponse { balance }) } -pub fn query_claims(deps: Deps, address: HumanAddr) -> StdResult { - let address_raw = deps.api.addr_canonicalize(&address)?; +pub fn query_claims(deps: Deps, address: &str) -> StdResult { + let address_raw = deps.api.addr_canonicalize(address)?; let claims = claims_read(deps.storage) .may_load(address_raw.as_slice())? .unwrap_or_default(); @@ -414,7 +416,7 @@ pub fn query_investment(deps: Deps) -> StdResult { let supply = total_supply_read(deps.storage).load()?; let res = InvestmentResponse { - owner: deps.api.addr_humanize(&invest.owner)?, + owner: invest.owner.into(), exit_tax: invest.exit_tax, validator: invest.validator, min_withdrawal: invest.min_withdrawal, @@ -477,18 +479,18 @@ mod tests { name: "Cool Derivative".to_string(), symbol: "DRV".to_string(), decimals: 9, - validator: HumanAddr::from(DEFAULT_VALIDATOR), + validator: String::from(DEFAULT_VALIDATOR), exit_tax: Decimal::percent(tax_percent), min_withdrawal: Uint128(min_withdrawal), } } - fn get_balance>(deps: Deps, addr: U) -> Uint128 { - query_balance(deps, addr.into()).unwrap().balance + fn get_balance(deps: Deps, addr: &str) -> Uint128 { + query_balance(deps, addr).unwrap().balance } - fn get_claims>(deps: Deps, addr: U) -> Uint128 { - query_claims(deps, addr.into()).unwrap().claims + fn get_claims(deps: Deps, addr: &str) -> Uint128 { + query_claims(deps, addr).unwrap().claims } #[test] @@ -497,12 +499,12 @@ mod tests { deps.querier .update_staking("ustake", &[sample_validator("john")], &[]); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let msg = InstantiateMsg { name: "Cool Derivative".to_string(), symbol: "DRV".to_string(), decimals: 9, - validator: HumanAddr::from("my-validator"), + validator: String::from("my-validator"), exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; @@ -531,12 +533,12 @@ mod tests { &[], ); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let msg = InstantiateMsg { name: "Cool Derivative".to_string(), symbol: "DRV".to_string(), decimals: 0, - validator: HumanAddr::from("my-validator"), + validator: String::from("my-validator"), exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; @@ -574,7 +576,7 @@ mod tests { let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let instantiate_msg = default_init(2, 50); let info = mock_info(&creator, &[]); @@ -583,7 +585,7 @@ mod tests { assert_eq!(0, res.messages.len()); // let's bond some tokens now - let bob = HumanAddr::from("bob"); + let bob = String::from("bob"); let bond_msg = ExecuteMsg::Bond {}; let info = mock_info(&bob, &[coin(10, "random"), coin(1000, "ustake")]); @@ -614,7 +616,7 @@ mod tests { let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let instantiate_msg = default_init(2, 50); let info = mock_info(&creator, &[]); @@ -623,7 +625,7 @@ mod tests { assert_eq!(0, res.messages.len()); // let's bond some tokens now - let bob = HumanAddr::from("bob"); + let bob = String::from("bob"); let bond_msg = ExecuteMsg::Bond {}; let info = mock_info(&bob, &[coin(10, "random"), coin(1000, "ustake")]); let res = execute(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); @@ -650,7 +652,7 @@ mod tests { assert_eq!(invest.nominal_value, ratio); // we bond some other tokens and get a different issuance price (maintaining the ratio) - let alice = HumanAddr::from("alice"); + let alice = String::from("alice"); let bond_msg = ExecuteMsg::Bond {}; let info = mock_info(&alice, &[coin(3000, "ustake")]); let res = execute(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); @@ -701,7 +703,7 @@ mod tests { let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let instantiate_msg = default_init(10, 50); let info = mock_info(&creator, &[]); @@ -710,7 +712,7 @@ mod tests { assert_eq!(0, res.messages.len()); // let's bond some tokens now - let bob = HumanAddr::from("bob"); + let bob = String::from("bob"); let bond_msg = ExecuteMsg::Bond {}; let info = mock_info(&bob, &[coin(10, "random"), coin(1000, "ustake")]); let res = execute(deps.as_mut(), mock_env(), info, bond_msg).unwrap(); diff --git a/contracts/staking/src/msg.rs b/contracts/staking/src/msg.rs index 5742fc0caa..0603597186 100644 --- a/contracts/staking/src/msg.rs +++ b/contracts/staking/src/msg.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Coin, Decimal, HumanAddr, Uint128}; +use cosmwasm_std::{Coin, Decimal, Uint128}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { @@ -15,7 +15,7 @@ pub struct InstantiateMsg { pub decimals: u8, /// This is the validator that all tokens will be bonded to - pub validator: HumanAddr, + pub validator: String, /// this is how much the owner takes as a cut when someone unbonds /// TODO @@ -29,10 +29,7 @@ pub struct InstantiateMsg { #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { /// Transfer moves the derivative token - Transfer { - recipient: HumanAddr, - amount: Uint128, - }, + Transfer { recipient: String, amount: Uint128 }, /// Bond will bond all staking tokens sent with the message and release derivative tokens Bond {}, /// Unbond will "burn" the given amount of derivative tokens and send the unbonded @@ -55,9 +52,9 @@ pub enum ExecuteMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { /// Balance shows the number of staking derivatives - Balance { address: HumanAddr }, + Balance { address: String }, /// Claims shows the number of tokens this address can access when they are done unbonding - Claims { address: HumanAddr }, + Claims { address: String }, /// TokenInfo shows the metadata of the token for UIs TokenInfo {}, /// Investment shows info on total staking tokens under custody, @@ -95,11 +92,11 @@ pub struct InvestmentResponse { pub nominal_value: Decimal, /// owner created the contract and takes a cut - pub owner: HumanAddr, + pub owner: String, /// this is how much the owner takes as a cut when someone unbonds pub exit_tax: Decimal, /// All tokens are bonded to this validator - pub validator: HumanAddr, + pub validator: String, /// This is the minimum amount we will pull out to reinvest, as well as a minumum /// that can be unbonded (to avoid needless staking tx) pub min_withdrawal: Uint128, diff --git a/contracts/staking/src/state.rs b/contracts/staking/src/state.rs index d868af45ac..7c39974c40 100644 --- a/contracts/staking/src/state.rs +++ b/contracts/staking/src/state.rs @@ -1,7 +1,6 @@ -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{CanonicalAddr, Decimal, HumanAddr, Storage, Uint128}; +use cosmwasm_std::{Addr, Decimal, Storage, Uint128}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, Singleton, @@ -35,24 +34,24 @@ pub fn claims_read(storage: &dyn Storage) -> ReadonlyBucket { } /// Investment info is fixed at initialization, and is used to control the function of the contract -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct InvestmentInfo { /// owner created the contract and takes a cut - pub owner: CanonicalAddr, + pub owner: Addr, /// this is the denomination we can stake (and only one we accept for payments) pub bond_denom: String, /// this is how much the owner takes as a cut when someone unbonds pub exit_tax: Decimal, /// All tokens are bonded to this validator /// FIXME: humanize/canonicalize address doesn't work for validator addrresses - pub validator: HumanAddr, + pub validator: String, /// This is the minimum amount we will pull out to reinvest, as well as a minumum /// that can be unbonded (to avoid needless staking tx) pub min_withdrawal: Uint128, } /// Supply is dynamic and tracks the current supply of staked and ERC20 tokens. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub struct Supply { /// issued is how many derivative tokens this contract has issued pub issued: Uint128, diff --git a/contracts/staking/tests/integration.rs b/contracts/staking/tests/integration.rs index 7a0203ca39..cbe88563ad 100644 --- a/contracts/staking/tests/integration.rs +++ b/contracts/staking/tests/integration.rs @@ -53,12 +53,12 @@ fn initialization_with_missing_validator() { let (instance_options, memory_limit) = mock_instance_options(); let mut deps = Instance::from_code(WASM, backend, instance_options, memory_limit).unwrap(); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let msg = InstantiateMsg { name: "Cool Derivative".to_string(), symbol: "DRV".to_string(), decimals: 9, - validator: HumanAddr::from("my-validator"), + validator: String::from("my-validator"), exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; @@ -91,12 +91,12 @@ fn proper_initialization() { assert_eq!(deps.required_features.len(), 1); assert!(deps.required_features.contains("staking")); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let msg = InstantiateMsg { name: "Cool Derivative".to_string(), symbol: "DRV".to_string(), decimals: 9, - validator: HumanAddr::from("my-validator"), + validator: String::from("my-validator"), exit_tax: Decimal::percent(2), min_withdrawal: Uint128(50), }; From 9fdbbab5590b4688b659b34e8f52ab877fdf7561 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 22:05:21 +0200 Subject: [PATCH 19/38] Remove unnecessary HumanAddr in test code --- contracts/staking/src/contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index dbc7a1941b..7954db3744 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -675,7 +675,7 @@ mod tests { let mut deps = mock_dependencies(&[]); set_validator(&mut deps.querier); - let creator = HumanAddr::from("creator"); + let creator = String::from("creator"); let instantiate_msg = default_init(2, 50); let info = mock_info(&creator, &[]); @@ -684,7 +684,7 @@ mod tests { assert_eq!(0, res.messages.len()); // let's bond some tokens now - let bob = HumanAddr::from("bob"); + let bob = String::from("bob"); let bond_msg = ExecuteMsg::Bond {}; let info = mock_info(&bob, &[coin(500, "photon")]); From 9d3fdd9d7e901be9814487395e2711981bb7ed9d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 21:37:55 +0200 Subject: [PATCH 20/38] Replace HumanAddr with String in BankQuery --- CHANGELOG.md | 1 + contracts/hackatom/src/contract.rs | 2 +- contracts/hackatom/tests/integration.rs | 2 +- contracts/reflect/schema/query_msg.json | 4 ++-- contracts/reflect/src/contract.rs | 2 +- contracts/reflect/src/testing.rs | 5 ++--- contracts/reflect/tests/integration.rs | 3 +-- contracts/staking/src/contract.rs | 2 +- packages/std/src/mock.rs | 26 ++++++++++++------------- packages/std/src/query.rs | 4 ++-- packages/std/src/traits.rs | 12 ++++++++---- packages/vm/src/environment.rs | 6 +++--- packages/vm/src/imports.rs | 4 ++-- packages/vm/src/instance.rs | 6 +++--- packages/vm/src/testing/instance.rs | 12 ++++++------ packages/vm/src/testing/mock.rs | 9 ++++----- packages/vm/src/testing/querier.rs | 18 ++++++++--------- 17 files changed, 59 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93cd2148ab..fa1399a46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ and this project adheres to as well as `From for Uint128`. - cosmwasm-std: Create new address type `Addr`. This is human readable (like `HumanAddr`) but is immutable and always contains a valid address ([#802]). +- cosmwasm-std: Replace `HumanAddr` with `String` in `BankQuery` query requests. - cosmwasm-vm: Add import `addr_validate` ([#802]). [#692]: https://github.com/CosmWasm/cosmwasm/issues/692 diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 28777dde49..ae5afec597 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -408,7 +408,7 @@ mod tests { fn querier_callbacks_work() { let rich_addr = String::from("foobar"); let rich_balance = coins(10000, "gold"); - let deps = mock_dependencies_with_balances(&[(&rich_addr.clone().into(), &rich_balance)]); + let deps = mock_dependencies_with_balances(&[(&rich_addr, &rich_balance)]); // querying with balance gets the balance let bal = query_other_balance(deps.as_ref(), rich_addr).unwrap(); diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index 61f3867879..e030633a78 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -185,7 +185,7 @@ fn sudo_can_steal_tokens() { fn querier_callbacks_work() { let rich_addr = String::from("foobar"); let rich_balance = coins(10000, "gold"); - let mut deps = mock_instance_with_balances(WASM, &[(&rich_addr.clone().into(), &rich_balance)]); + let mut deps = mock_instance_with_balances(WASM, &[(&rich_addr, &rich_balance)]); // querying with balance gets the balance let query_msg = QueryMsg::OtherBalance { address: rich_addr }; diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json index 4f30a8e606..71449c96fc 100644 --- a/contracts/reflect/schema/query_msg.json +++ b/contracts/reflect/schema/query_msg.json @@ -123,7 +123,7 @@ ], "properties": { "address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "denom": { "type": "string" @@ -147,7 +147,7 @@ ], "properties": { "address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index 982b71b538..d0d9771785 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -431,7 +431,7 @@ mod tests { // with bank query let msg = QueryMsg::Chain { request: BankQuery::AllBalances { - address: HumanAddr::from(MOCK_CONTRACT_ADDR), + address: MOCK_CONTRACT_ADDR.to_string(), } .into(), }; diff --git a/contracts/reflect/src/testing.rs b/contracts/reflect/src/testing.rs index e20c9c58b3..32ccedaafc 100644 --- a/contracts/reflect/src/testing.rs +++ b/contracts/reflect/src/testing.rs @@ -1,16 +1,15 @@ use crate::msg::{SpecialQuery, SpecialResponse}; use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{to_binary, Binary, Coin, ContractResult, HumanAddr, OwnedDeps, SystemResult}; +use cosmwasm_std::{to_binary, Binary, Coin, ContractResult, OwnedDeps, SystemResult}; /// A drop-in replacement for cosmwasm_std::testing::mock_dependencies /// this uses our CustomQuerier. pub fn mock_dependencies_with_custom_querier( contract_balance: &[Coin], ) -> OwnedDeps> { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); let custom_querier: MockQuerier = - MockQuerier::new(&[(&contract_addr, contract_balance)]) + MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]) .with_custom_handler(|query| SystemResult::Ok(custom_query_execute(&query))); OwnedDeps { storage: MockStorage::default(), diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index e6a1901840..812b922975 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -45,9 +45,8 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/re pub fn mock_dependencies_with_custom_querier( contract_balance: &[Coin], ) -> Backend> { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); let custom_querier: MockQuerier = - MockQuerier::new(&[(&contract_addr, contract_balance)]) + MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]) .with_custom_handler(|query| SystemResult::Ok(custom_query_execute(query))); Backend { diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 7954db3744..8b348c4fbd 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -265,7 +265,7 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult let invest = invest_info_read(deps.storage).load()?; let mut balance = deps .querier - .query_balance(env.contract.address, &invest.bond_denom)?; + .query_balance(env.contract.address, invest.bond_denom)?; if balance.amount < invest.min_withdrawal { return Err(StdError::generic_err( "Insufficient balance in contract to process claim", diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index dcacefd760..c3a3e79a78 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -28,18 +28,17 @@ pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; pub fn mock_dependencies( contract_balance: &[Coin], ) -> OwnedDeps { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); OwnedDeps { storage: MockStorage::default(), api: MockApi::default(), - querier: MockQuerier::new(&[(&contract_addr, contract_balance)]), + querier: MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]), } } /// Initializes the querier along with the mock_dependencies. /// Sets all balances provided (yoy must explicitly set contract balance if desired) pub fn mock_dependencies_with_balances( - balances: &[(&HumanAddr, &[Coin])], + balances: &[(&str, &[Coin])], ) -> OwnedDeps { OwnedDeps { storage: MockStorage::default(), @@ -292,7 +291,7 @@ pub struct MockQuerier { } impl MockQuerier { - pub fn new(balances: &[(&HumanAddr, &[Coin])]) -> Self { + pub fn new(balances: &[(&str, &[Coin])]) -> Self { MockQuerier { bank: BankQuerier::new(balances), staking: StakingQuerier::default(), @@ -307,7 +306,7 @@ impl MockQuerier { } // set a new balance for the given address and return the old balance - pub fn update_balance>( + pub fn update_balance>( &mut self, addr: U, balance: Vec, @@ -386,14 +385,14 @@ impl NoWasmQuerier { #[derive(Clone, Default)] pub struct BankQuerier { - balances: HashMap>, + balances: HashMap>, } impl BankQuerier { - pub fn new(balances: &[(&HumanAddr, &[Coin])]) -> Self { + pub fn new(balances: &[(&str, &[Coin])]) -> Self { let mut map = HashMap::new(); for (addr, coins) in balances.iter() { - map.insert(HumanAddr::from(addr), coins.to_vec()); + map.insert(addr.to_string(), coins.to_vec()); } BankQuerier { balances: map } } @@ -792,11 +791,10 @@ mod tests { #[test] fn bank_querier_all_balances() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let bank = BankQuerier::new(&[(&addr, &balance)]); - // all let all = bank .query(&BankQuery::AllBalances { address: addr }) .unwrap() @@ -807,7 +805,7 @@ mod tests { #[test] fn bank_querier_one_balance() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let bank = BankQuerier::new(&[(&addr, &balance)]); @@ -836,14 +834,14 @@ mod tests { #[test] fn bank_querier_missing_account() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let bank = BankQuerier::new(&[(&addr, &balance)]); // all balances on empty account is empty vec let all = bank .query(&BankQuery::AllBalances { - address: HumanAddr::from("elsewhere"), + address: String::from("elsewhere"), }) .unwrap() .unwrap(); @@ -853,7 +851,7 @@ mod tests { // any denom on balances on empty account is empty coin let miss = bank .query(&BankQuery::Balance { - address: HumanAddr::from("elsewhere"), + address: String::from("elsewhere"), denom: "ELF".to_string(), }) .unwrap() diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs index d120a67cd2..8329fc92e2 100644 --- a/packages/std/src/query.rs +++ b/packages/std/src/query.rs @@ -38,11 +38,11 @@ pub enum QueryRequest { pub enum BankQuery { /// This calls into the native bank module for one denomination /// Return value is BalanceResponse - Balance { address: HumanAddr, denom: String }, + Balance { address: String, denom: String }, /// This calls into the native bank module for all denominations. /// Note that this may be much more expensive than Balance and should be avoided if possible. /// Return value is AllBalanceResponse. - AllBalances { address: HumanAddr }, + AllBalances { address: String }, } /// A trait that is required to avoid conflicts with other query types like BankQuery and WasmQuery diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index db541f84f4..7f1072fc7e 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -182,17 +182,21 @@ impl<'a> QuerierWrapper<'a> { } } - pub fn query_balance>(&self, address: U, denom: &str) -> StdResult { + pub fn query_balance, V: Into>( + &self, + address: U, + denom: V, + ) -> StdResult { let request = BankQuery::Balance { address: address.into(), - denom: denom.to_string(), + denom: denom.into(), } .into(); let res: BalanceResponse = self.query(&request)?; Ok(res.amount) } - pub fn query_all_balances>(&self, address: U) -> StdResult> { + pub fn query_all_balances>(&self, address: U) -> StdResult> { let request = BankQuery::AllBalances { address: address.into(), } @@ -327,7 +331,7 @@ mod tests { #[test] fn auto_deref_raw_query() { - let acct = HumanAddr::from("foobar"); + let acct = String::from("foobar"); let querier: MockQuerier = MockQuerier::new(&[(&acct, &coins(5, "BTC"))]); let wrapper = QuerierWrapper::new(&querier); let query = QueryRequest::::Bank(BankQuery::Balance { diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index eda6014381..d0e30ad5ad 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -392,7 +392,7 @@ mod tests { use crate::testing::{MockApi, MockQuerier, MockStorage}; use crate::wasm_backend::compile; use cosmwasm_std::{ - coins, from_binary, to_vec, AllBalanceResponse, BankQuery, Empty, HumanAddr, QueryRequest, + coins, from_binary, to_vec, AllBalanceResponse, BankQuery, Empty, QueryRequest, }; use wasmer::{imports, Function, Instance as WasmerInstance}; @@ -456,7 +456,7 @@ mod tests { .0 .expect("error setting value"); let querier: MockQuerier = - MockQuerier::new(&[(&HumanAddr::from(INIT_ADDR), &coins(INIT_AMOUNT, INIT_DENOM))]); + MockQuerier::new(&[(INIT_ADDR, &coins(INIT_AMOUNT, INIT_DENOM))]); env.move_in(storage, querier); } @@ -813,7 +813,7 @@ mod tests { let res = env .with_querier_from_context::<_, _>(|querier| { let req: QueryRequest = QueryRequest::Bank(BankQuery::AllBalances { - address: HumanAddr::from(INIT_ADDR), + address: INIT_ADDR.to_string(), }); let (result, _gas_info) = querier.query_raw(&to_vec(&req).unwrap(), DEFAULT_QUERY_GAS_LIMIT); diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 1007642e4f..7c8f37e264 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -619,7 +619,7 @@ mod tests { storage.set(KEY1, VALUE1).0.expect("error setting"); storage.set(KEY2, VALUE2).0.expect("error setting"); let querier: MockQuerier = - MockQuerier::new(&[(&HumanAddr::from(INIT_ADDR), &coins(INIT_AMOUNT, INIT_DENOM))]); + MockQuerier::new(&[(INIT_ADDR, &coins(INIT_AMOUNT, INIT_DENOM))]); env.move_in(storage, querier); } @@ -1748,7 +1748,7 @@ mod tests { let (env, _instance) = make_instance(api); let request: QueryRequest = QueryRequest::Bank(BankQuery::AllBalances { - address: HumanAddr::from(INIT_ADDR), + address: INIT_ADDR.to_string(), }); let request_data = cosmwasm_std::to_vec(&request).unwrap(); let request_ptr = write_data(&env, &request_data); diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index bb9856cd38..99eacc9246 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -343,7 +343,7 @@ mod tests { mock_instance_with_options, MockInstanceOptions, }; use cosmwasm_std::{ - coin, coins, from_binary, AllBalanceResponse, BalanceResponse, BankQuery, Empty, HumanAddr, + coin, coins, from_binary, AllBalanceResponse, BalanceResponse, BankQuery, Empty, QueryRequest, }; @@ -671,7 +671,7 @@ mod tests { #[test] fn with_querier_works_readonly() { - let rich_addr = HumanAddr::from("foobar"); + let rich_addr = String::from("foobar"); let rich_balance = vec![coin(10000, "gold"), coin(8000, "silver")]; let mut instance = mock_instance_with_balances(&CONTRACT, &[(&rich_addr, &rich_balance)]); @@ -726,7 +726,7 @@ mod tests { /// This is needed for writing intagration tests in which the balance of a contract changes over time #[test] fn with_querier_allows_updating_balances() { - let rich_addr = HumanAddr::from("foobar"); + let rich_addr = String::from("foobar"); let rich_balance1 = vec![coin(10000, "gold"), coin(500, "silver")]; let rich_balance2 = vec![coin(10000, "gold"), coin(8000, "silver")]; let mut instance = mock_instance_with_balances(&CONTRACT, &[(&rich_addr, &rich_balance1)]); diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index eb0b045df0..fb522a06ac 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -1,7 +1,7 @@ //! This file has some helpers for integration tests. //! They should be imported via full path to ensure there is no confusion //! use cosmwasm_vm::testing::X -use cosmwasm_std::{Coin, HumanAddr}; +use cosmwasm_std::Coin; use std::collections::HashSet; use crate::compatibility::check_wasm; @@ -51,7 +51,7 @@ pub fn mock_instance_with_failing_api( pub fn mock_instance_with_balances( wasm: &[u8], - balances: &[(&HumanAddr, &[Coin])], + balances: &[(&str, &[Coin])], ) -> Instance { mock_instance_with_options( wasm, @@ -78,7 +78,7 @@ pub fn mock_instance_with_gas_limit( #[derive(Debug)] pub struct MockInstanceOptions<'a> { // dependencies - pub balances: &'a [(&'a HumanAddr, &'a [Coin])], + pub balances: &'a [(&'a str, &'a [Coin])], /// This option is merged into balances and might override an existing value pub contract_balance: Option<&'a [Coin]>, /// When set, all calls to the API fail with BackendError::Unknown containing this message @@ -126,16 +126,16 @@ pub fn mock_instance_with_options( options: MockInstanceOptions, ) -> Instance { check_wasm(wasm, &options.supported_features).unwrap(); - let contract_address = HumanAddr::from(MOCK_CONTRACT_ADDR); + let contract_address = MOCK_CONTRACT_ADDR; // merge balances let mut balances = options.balances.to_vec(); if let Some(contract_balance) = options.contract_balance { // Remove old entry if exists - if let Some(pos) = balances.iter().position(|item| *item.0 == contract_address) { + if let Some(pos) = balances.iter().position(|item| item.0 == contract_address) { balances.remove(pos); } - balances.push((&contract_address, contract_balance)); + balances.push((contract_address, contract_balance)); } let api = if let Some(backend_error) = options.backend_error { diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index 978409af4c..1f22032053 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -1,5 +1,5 @@ use cosmwasm_std::testing::{digit_sum, riffle_shuffle}; -use cosmwasm_std::{Addr, BlockInfo, Coin, ContractInfo, Env, HumanAddr, MessageInfo}; +use cosmwasm_std::{Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo}; use super::querier::MockQuerier; use super::storage::MockStorage; @@ -12,18 +12,17 @@ const GAS_COST_CANONICALIZE: u64 = 55; /// All external requirements that can be injected for unit tests. /// It sets the given balance for the contract itself, nothing else pub fn mock_backend(contract_balance: &[Coin]) -> Backend { - let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR); Backend { api: MockApi::default(), storage: MockStorage::default(), - querier: MockQuerier::new(&[(&contract_addr, contract_balance)]), + querier: MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]), } } /// Initializes the querier along with the mock_dependencies. /// Sets all balances provided (yoy must explicitly set contract balance if desired) pub fn mock_backend_with_balances( - balances: &[(&HumanAddr, &[Coin])], + balances: &[(&str, &[Coin])], ) -> Backend { Backend { api: MockApi::default(), @@ -168,7 +167,7 @@ pub fn mock_info(sender: &str, funds: &[Coin]) -> MessageInfo { mod tests { use super::*; use crate::BackendError; - use cosmwasm_std::coins; + use cosmwasm_std::{coins, HumanAddr}; #[test] fn mock_info_arguments() { diff --git a/packages/vm/src/testing/querier.rs b/packages/vm/src/testing/querier.rs index 0e6b3853ef..a6d6c36bb2 100644 --- a/packages/vm/src/testing/querier.rs +++ b/packages/vm/src/testing/querier.rs @@ -2,7 +2,7 @@ use serde::de::DeserializeOwned; use cosmwasm_std::testing::{MockQuerier as StdMockQuerier, MockQuerierCustomHandlerResult}; use cosmwasm_std::{ - to_binary, to_vec, Binary, Coin, ContractResult, CustomQuery, Empty, HumanAddr, Querier as _, + to_binary, to_vec, Binary, Coin, ContractResult, CustomQuery, Empty, Querier as _, QueryRequest, SystemError, SystemResult, }; @@ -21,14 +21,14 @@ pub struct MockQuerier { } impl MockQuerier { - pub fn new(balances: &[(&HumanAddr, &[Coin])]) -> Self { + pub fn new(balances: &[(&str, &[Coin])]) -> Self { MockQuerier { querier: StdMockQuerier::new(balances), } } // set a new balance for the given address and return the old balance - pub fn update_balance>( + pub fn update_balance>( &mut self, addr: U, balance: Vec, @@ -113,7 +113,7 @@ mod tests { #[test] fn query_raw_fails_when_out_of_gas() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let querier: MockQuerier = MockQuerier::new(&[(&addr, &balance)]); @@ -127,7 +127,7 @@ mod tests { #[test] fn bank_querier_all_balances() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let querier = MockQuerier::new(&[(&addr, &balance)]); @@ -147,7 +147,7 @@ mod tests { #[test] fn bank_querier_one_balance() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let querier = MockQuerier::new(&[(&addr, &balance)]); @@ -188,7 +188,7 @@ mod tests { #[test] fn bank_querier_missing_account() { - let addr = HumanAddr::from("foobar"); + let addr = String::from("foobar"); let balance = vec![coin(123, "ELF"), coin(777, "FLY")]; let querier = MockQuerier::new(&[(&addr, &balance)]); @@ -196,7 +196,7 @@ mod tests { let all = querier .query::( &BankQuery::AllBalances { - address: HumanAddr::from("elsewhere"), + address: String::from("elsewhere"), } .into(), DEFAULT_QUERY_GAS_LIMIT, @@ -212,7 +212,7 @@ mod tests { let miss = querier .query::( &BankQuery::Balance { - address: HumanAddr::from("elsewhere"), + address: String::from("elsewhere"), denom: "ELF".to_string(), } .into(), From d45dcd5d52b3f064becf6c652857ca302db51c3a Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 21:50:38 +0200 Subject: [PATCH 21/38] Replace HumanAddr with String in WasmQuery --- CHANGELOG.md | 3 ++- contracts/reflect/schema/query_msg.json | 4 ++-- packages/std/src/errors/system_error.rs | 21 ++++++++++++++++----- packages/std/src/query.rs | 4 ++-- packages/std/src/traits.rs | 12 ++++++------ packages/vm/src/imports.rs | 6 +++--- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1399a46d..6af520e067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,7 +54,8 @@ and this project adheres to as well as `From for Uint128`. - cosmwasm-std: Create new address type `Addr`. This is human readable (like `HumanAddr`) but is immutable and always contains a valid address ([#802]). -- cosmwasm-std: Replace `HumanAddr` with `String` in `BankQuery` query requests. +- cosmwasm-std: Replace `HumanAddr` with `String` in `BankQuery` and `WasmQuery` + query requests. - cosmwasm-vm: Add import `addr_validate` ([#802]). [#692]: https://github.com/CosmWasm/cosmwasm/issues/692 diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json index 71449c96fc..1c7a727685 100644 --- a/contracts/reflect/schema/query_msg.json +++ b/contracts/reflect/schema/query_msg.json @@ -454,7 +454,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded QueryMsg struct", @@ -484,7 +484,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "key": { "description": "Key is the raw key used in the contracts Storage", diff --git a/packages/std/src/errors/system_error.rs b/packages/std/src/errors/system_error.rs index 7d4a095f2c..eefdfc922f 100644 --- a/packages/std/src/errors/system_error.rs +++ b/packages/std/src/errors/system_error.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{Binary, HumanAddr}; +use crate::Binary; /// SystemError is used for errors inside the VM and is API friendly (i.e. serializable). /// @@ -16,11 +16,22 @@ use crate::{Binary, HumanAddr}; #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum SystemError { - InvalidRequest { error: String, request: Binary }, - InvalidResponse { error: String, response: Binary }, - NoSuchContract { addr: HumanAddr }, + InvalidRequest { + error: String, + request: Binary, + }, + InvalidResponse { + error: String, + response: Binary, + }, + NoSuchContract { + /// The address that was attemted to query + addr: String, + }, Unknown {}, - UnsupportedRequest { kind: String }, + UnsupportedRequest { + kind: String, + }, } impl std::error::Error for SystemError {} diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs index 8329fc92e2..83c6fb6542 100644 --- a/packages/std/src/query.rs +++ b/packages/std/src/query.rs @@ -75,14 +75,14 @@ pub enum WasmQuery { /// this queries the public API of another contract at a known address (with known ABI) /// return value is whatever the contract returns (caller should know) Smart { - contract_addr: HumanAddr, + contract_addr: String, /// msg is the json-encoded QueryMsg struct msg: Binary, }, /// this queries the raw kv-store of the contract. /// returns the raw, unparsed data stored at that key, which may be an empty vector if not present Raw { - contract_addr: HumanAddr, + contract_addr: String, /// Key is the raw key used in the contracts Storage key: Binary, }, diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 7f1072fc7e..0371452890 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -207,13 +207,13 @@ impl<'a> QuerierWrapper<'a> { // this queries another wasm contract. You should know a priori the proper types for T and U // (response and request) based on the contract API - pub fn query_wasm_smart>( + pub fn query_wasm_smart>( &self, - contract: V, + contract_addr: V, msg: &U, ) -> StdResult { let request = WasmQuery::Smart { - contract_addr: contract.into(), + contract_addr: contract_addr.into(), msg: to_binary(msg)?, } .into(); @@ -227,13 +227,13 @@ impl<'a> QuerierWrapper<'a> { // // Similar return value to Storage.get(). Returns Some(val) or None if the data is there. // It only returns error on some runtime issue, not on any data cases. - pub fn query_wasm_raw, U: Into>( + pub fn query_wasm_raw, U: Into>( &self, - contract: T, + contract_addr: T, key: U, ) -> StdResult>> { let request: QueryRequest = WasmQuery::Raw { - contract_addr: contract.into(), + contract_addr: contract_addr.into(), key: key.into(), } .into(); diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 7c8f37e264..31212133a3 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -536,7 +536,7 @@ fn to_low_half(data: u32) -> u64 { mod tests { use super::*; use cosmwasm_std::{ - coins, from_binary, AllBalanceResponse, BankQuery, Binary, Empty, HumanAddr, QueryRequest, + coins, from_binary, AllBalanceResponse, BankQuery, Binary, Empty, QueryRequest, SystemError, SystemResult, WasmQuery, }; use hex_literal::hex; @@ -1796,7 +1796,7 @@ mod tests { let (env, _instance) = make_instance(api); let request: QueryRequest = QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: HumanAddr::from("non-existent"), + contract_addr: String::from("non-existent"), msg: Binary::from(b"{}" as &[u8]), }); let request_data = cosmwasm_std::to_vec(&request).unwrap(); @@ -1812,7 +1812,7 @@ mod tests { match query_result { SystemResult::Ok(_) => panic!("This must not succeed"), SystemResult::Err(SystemError::NoSuchContract { addr }) => { - assert_eq!(addr, HumanAddr::from("non-existent")) + assert_eq!(addr, "non-existent") } SystemResult::Err(err) => panic!("Unexpected error: {:?}", err), } From e6a7943e7f29d322467f506c8b07cbf6ae2c7c8a Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 31 Mar 2021 22:00:59 +0200 Subject: [PATCH 22/38] Replace HumanAddr with String in StakingQuery --- CHANGELOG.md | 4 +-- contracts/reflect/schema/query_msg.json | 9 ++---- contracts/staking/src/contract.rs | 15 ++++----- packages/std/src/mock.rs | 43 ++++++++++++------------- packages/std/src/query.rs | 6 ++-- packages/std/src/traits.rs | 6 ++-- 6 files changed, 39 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af520e067..50008411b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,8 +54,8 @@ and this project adheres to as well as `From for Uint128`. - cosmwasm-std: Create new address type `Addr`. This is human readable (like `HumanAddr`) but is immutable and always contains a valid address ([#802]). -- cosmwasm-std: Replace `HumanAddr` with `String` in `BankQuery` and `WasmQuery` - query requests. +- cosmwasm-std: Replace `HumanAddr` with `String` in `BankQuery`, `StakingQuery` + and `WasmQuery` query requests. - cosmwasm-vm: Add import `addr_validate` ([#802]). [#692]: https://github.com/CosmWasm/cosmwasm/issues/692 diff --git a/contracts/reflect/schema/query_msg.json b/contracts/reflect/schema/query_msg.json index 1c7a727685..8b24046d67 100644 --- a/contracts/reflect/schema/query_msg.json +++ b/contracts/reflect/schema/query_msg.json @@ -160,9 +160,6 @@ "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", "type": "string" }, - "HumanAddr": { - "type": "string" - }, "IbcQuery": { "description": "These are queries to the various IBC modules to see the state of the contract's IBC connection. These will return errors if the contract is not \"ibc enabled\"", "anyOf": [ @@ -390,7 +387,7 @@ ], "properties": { "delegator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -412,10 +409,10 @@ ], "properties": { "delegator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 8b348c4fbd..dc6a04ac7b 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -1,7 +1,6 @@ use cosmwasm_std::{ - attr, coin, entry_point, to_binary, BankMsg, Decimal, Deps, DepsMut, Env, HumanAddr, - MessageInfo, QuerierWrapper, QueryResponse, Response, StakingMsg, StdError, StdResult, Uint128, - WasmMsg, + attr, coin, entry_point, to_binary, BankMsg, Decimal, Deps, DepsMut, Env, MessageInfo, + QuerierWrapper, QueryResponse, Response, StakingMsg, StdError, StdResult, Uint128, WasmMsg, }; use crate::errors::{StakingError, Unauthorized}; @@ -109,8 +108,8 @@ pub fn transfer( // get_bonded returns the total amount of delegations from contract // it ensures they are all the same denom -fn get_bonded(querier: &QuerierWrapper, contract: HumanAddr) -> StdResult { - let bonds = querier.query_all_delegations(contract)?; +fn get_bonded>(querier: &QuerierWrapper, contract_addr: U) -> StdResult { + let bonds = querier.query_all_delegations(contract_addr)?; if bonds.is_empty() { return Ok(Uint128(0)); } @@ -152,7 +151,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { .ok_or_else(|| StdError::generic_err(format!("No {} tokens sent", &invest.bond_denom)))?; // bonded is the total number of tokens we have delegated from this address - let bonded = get_bonded(&deps.querier, env.contract.address.into())?; + let bonded = get_bonded(&deps.querier, env.contract.address)?; // calculate to_mint and update total supply let mut totals = total_supply(deps.storage); @@ -222,7 +221,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St // re-calculate bonded to ensure we have real values // bonded is the total number of tokens we have delegated from this address - let bonded = get_bonded(&deps.querier, env.contract.address.into())?; + let bonded = get_bonded(&deps.querier, env.contract.address)?; // calculate how many native tokens this is worth and update supply let remainder = amount.checked_sub(tax)?; @@ -437,7 +436,7 @@ mod tests { use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockQuerier, MOCK_CONTRACT_ADDR, }; - use cosmwasm_std::{coins, Coin, CosmosMsg, Decimal, FullDelegation, Validator}; + use cosmwasm_std::{coins, Coin, CosmosMsg, Decimal, FullDelegation, HumanAddr, Validator}; use std::str::FromStr; fn sample_validator>(addr: U) -> Validator { diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index c3a3e79a78..31a4e73d4b 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -461,7 +461,7 @@ impl StakingQuerier { let delegations: Vec<_> = self .delegations .iter() - .filter(|d| &d.delegator == delegator) + .filter(|d| d.delegator.as_str() == delegator) .cloned() .map(|d| d.into()) .collect(); @@ -472,10 +472,9 @@ impl StakingQuerier { delegator, validator, } => { - let delegation = self - .delegations - .iter() - .find(|d| &d.delegator == delegator && &d.validator == validator); + let delegation = self.delegations.iter().find(|d| { + d.delegator.as_str() == delegator && d.validator.as_str() == validator + }); let res = DelegationResponse { delegation: delegation.cloned(), }; @@ -887,7 +886,7 @@ mod tests { } // gets delegators from query or panic - fn get_all_delegators(staking: &StakingQuerier, delegator: HumanAddr) -> Vec { + fn get_all_delegators(staking: &StakingQuerier, delegator: String) -> Vec { let raw = staking .query(&StakingQuery::AllDelegations { delegator }) .unwrap() @@ -899,8 +898,8 @@ mod tests { // gets full delegators from query or panic fn get_delegator( staking: &StakingQuerier, - delegator: HumanAddr, - validator: HumanAddr, + delegator: String, + validator: String, ) -> Option { let raw = staking .query(&StakingQuery::Delegation { @@ -915,24 +914,24 @@ mod tests { #[test] fn staking_querier_delegations() { - let val1 = HumanAddr::from("validator-one"); - let val2 = HumanAddr::from("validator-two"); + let val1 = String::from("validator-one"); + let val2 = String::from("validator-two"); - let user_a = HumanAddr::from("investor"); - let user_b = HumanAddr::from("speculator"); - let user_c = HumanAddr::from("hodler"); + let user_a = String::from("investor"); + let user_b = String::from("speculator"); + let user_c = String::from("hodler"); // we need multiple validators per delegator, so the queries provide different results let del1a = FullDelegation { - delegator: user_a.clone(), - validator: val1.clone(), + delegator: user_a.clone().into(), + validator: val1.clone().into(), amount: coin(100, "ustake"), can_redelegate: coin(100, "ustake"), accumulated_rewards: coins(5, "ustake"), }; let del2a = FullDelegation { - delegator: user_a.clone(), - validator: val2.clone(), + delegator: user_a.clone().into(), + validator: val2.clone().into(), amount: coin(500, "ustake"), can_redelegate: coin(500, "ustake"), accumulated_rewards: coins(20, "ustake"), @@ -940,8 +939,8 @@ mod tests { // note we cannot have multiple delegations on one validator, they are collapsed into one let del1b = FullDelegation { - delegator: user_b.clone(), - validator: val1.clone(), + delegator: user_b.clone().into(), + validator: val1.clone().into(), amount: coin(500, "ustake"), can_redelegate: coin(0, "ustake"), accumulated_rewards: coins(0, "ustake"), @@ -949,8 +948,8 @@ mod tests { // and another one on val2 let del2c = FullDelegation { - delegator: user_c.clone(), - validator: val2.clone(), + delegator: user_c.clone().into(), + validator: val2.clone().into(), amount: coin(8888, "ustake"), can_redelegate: coin(4567, "ustake"), accumulated_rewards: coins(900, "ustake"), @@ -975,7 +974,7 @@ mod tests { assert_eq!(dels, vec![del2c.clone().into()]); // for user with no delegations... - let dels = get_all_delegators(&staking, HumanAddr::from("no one")); + let dels = get_all_delegators(&staking, String::from("no one")); assert_eq!(dels, vec![]); // filter a by validator (1 and 1) diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs index 83c6fb6542..328449bdff 100644 --- a/packages/std/src/query.rs +++ b/packages/std/src/query.rs @@ -150,12 +150,12 @@ pub enum StakingQuery { /// Returns the denomination that can be bonded (if there are multiple native tokens on the chain) BondedDenom {}, /// AllDelegations will return all delegations by the delegator - AllDelegations { delegator: HumanAddr }, + AllDelegations { delegator: String }, /// Delegation will return more detailed info on a particular /// delegation, defined by delegator/validator pair Delegation { - delegator: HumanAddr, - validator: HumanAddr, + delegator: String, + validator: String, }, /// Returns all registered Validators on the system Validators {}, diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 0371452890..08d7212cac 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -275,7 +275,7 @@ impl<'a> QuerierWrapper<'a> { } #[cfg(feature = "staking")] - pub fn query_all_delegations>( + pub fn query_all_delegations>( &self, delegator: U, ) -> StdResult> { @@ -288,10 +288,10 @@ impl<'a> QuerierWrapper<'a> { } #[cfg(feature = "staking")] - pub fn query_delegation>( + pub fn query_delegation, V: Into>( &self, delegator: U, - validator: U, + validator: V, ) -> StdResult> { let request = StakingQuery::Delegation { delegator: delegator.into(), From b134ccd973c01624ff4fd6e21b1c0385fd627161 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 10:01:05 +0200 Subject: [PATCH 23/38] Make adresses in FullDelegation/Delegation/Validator Addr --- contracts/staking/src/contract.rs | 14 +++---- contracts/staking/tests/integration.rs | 6 +-- packages/std/src/addresses.rs | 2 +- packages/std/src/mock.rs | 53 ++++++++++++++------------ packages/std/src/query.rs | 21 ++++++---- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index dc6a04ac7b..5f7f7fe5ec 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -24,7 +24,7 @@ pub fn instantiate( ) -> StdResult { // ensure the validator is registered let vals = deps.querier.query_validators()?; - if !vals.iter().any(|v| v.address.as_str() == msg.validator) { + if !vals.iter().any(|v| v.address.as_ref() == msg.validator) { return Err(StdError::generic_err(format!( "{} is not in the current validator set", msg.validator @@ -436,23 +436,23 @@ mod tests { use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockQuerier, MOCK_CONTRACT_ADDR, }; - use cosmwasm_std::{coins, Coin, CosmosMsg, Decimal, FullDelegation, HumanAddr, Validator}; + use cosmwasm_std::{coins, Addr, Coin, CosmosMsg, Decimal, FullDelegation, Validator}; use std::str::FromStr; - fn sample_validator>(addr: U) -> Validator { + fn sample_validator(addr: &str) -> Validator { Validator { - address: addr.into(), + address: Addr::unchecked(addr), commission: Decimal::percent(3), max_commission: Decimal::percent(10), max_change_rate: Decimal::percent(1), } } - fn sample_delegation>(addr: U, amount: Coin) -> FullDelegation { + fn sample_delegation(validator_addr: &str, amount: Coin) -> FullDelegation { let can_redelegate = amount.clone(); FullDelegation { - validator: addr.into(), - delegator: HumanAddr::from(MOCK_CONTRACT_ADDR), + validator: Addr::unchecked(validator_addr), + delegator: Addr::unchecked(MOCK_CONTRACT_ADDR), amount, can_redelegate, accumulated_rewards: Vec::new(), diff --git a/contracts/staking/tests/integration.rs b/contracts/staking/tests/integration.rs index cbe88563ad..0d5fc5c33e 100644 --- a/contracts/staking/tests/integration.rs +++ b/contracts/staking/tests/integration.rs @@ -18,7 +18,7 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coin, from_binary, ContractResult, Decimal, HumanAddr, Response, Uint128, Validator, + coin, from_binary, Addr, ContractResult, Decimal, Response, Uint128, Validator, }; use cosmwasm_vm::testing::{ instantiate, mock_backend, mock_env, mock_info, mock_instance_options, query, @@ -35,9 +35,9 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/st // You can uncomment this line instead to test productionified build from cosmwasm-opt // static WASM: &[u8] = include_bytes!("../contract.wasm"); -fn sample_validator>(addr: U) -> Validator { +fn sample_validator(addr: &str) -> Validator { Validator { - address: addr.into(), + address: Addr::unchecked(addr), commission: Decimal::percent(3), max_commission: Decimal::percent(10), max_change_rate: Decimal::percent(1), diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 249db358ef..30b8ffed2a 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -20,7 +20,7 @@ use crate::binary::Binary; /// This type is immutable. If you really need to mutate it (Really? Are you sure?), create /// a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` /// instance. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] pub struct Addr(String); impl Addr { diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 31a4e73d4b..75c9dd5871 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -461,7 +461,7 @@ impl StakingQuerier { let delegations: Vec<_> = self .delegations .iter() - .filter(|d| d.delegator.as_str() == delegator) + .filter(|d| d.delegator.as_ref() == delegator) .cloned() .map(|d| d.into()) .collect(); @@ -473,7 +473,7 @@ impl StakingQuerier { validator, } => { let delegation = self.delegations.iter().find(|d| { - d.delegator.as_str() == delegator && d.validator.as_str() == validator + d.delegator.as_ref() == delegator && d.validator.as_ref() == validator }); let res = DelegationResponse { delegation: delegation.cloned(), @@ -862,13 +862,13 @@ mod tests { #[test] fn staking_querier_validators() { let val1 = Validator { - address: HumanAddr::from("validator-one"), + address: Addr::unchecked("validator-one"), commission: Decimal::percent(1), max_commission: Decimal::percent(3), max_change_rate: Decimal::percent(1), }; let val2 = Validator { - address: HumanAddr::from("validator-two"), + address: Addr::unchecked("validator-two"), commission: Decimal::permille(15), max_commission: Decimal::permille(40), max_change_rate: Decimal::permille(5), @@ -886,9 +886,14 @@ mod tests { } // gets delegators from query or panic - fn get_all_delegators(staking: &StakingQuerier, delegator: String) -> Vec { + fn get_all_delegators>( + staking: &StakingQuerier, + delegator: U, + ) -> Vec { let raw = staking - .query(&StakingQuery::AllDelegations { delegator }) + .query(&StakingQuery::AllDelegations { + delegator: delegator.into(), + }) .unwrap() .unwrap(); let dels: AllDelegationsResponse = from_binary(&raw).unwrap(); @@ -896,15 +901,15 @@ mod tests { } // gets full delegators from query or panic - fn get_delegator( + fn get_delegator, V: Into>( staking: &StakingQuerier, - delegator: String, - validator: String, + delegator: U, + validator: V, ) -> Option { let raw = staking .query(&StakingQuery::Delegation { - delegator, - validator, + delegator: delegator.into(), + validator: validator.into(), }) .unwrap() .unwrap(); @@ -914,24 +919,24 @@ mod tests { #[test] fn staking_querier_delegations() { - let val1 = String::from("validator-one"); - let val2 = String::from("validator-two"); + let val1 = Addr::unchecked("validator-one"); + let val2 = Addr::unchecked("validator-two"); - let user_a = String::from("investor"); - let user_b = String::from("speculator"); - let user_c = String::from("hodler"); + let user_a = Addr::unchecked("investor"); + let user_b = Addr::unchecked("speculator"); + let user_c = Addr::unchecked("hodler"); // we need multiple validators per delegator, so the queries provide different results let del1a = FullDelegation { - delegator: user_a.clone().into(), - validator: val1.clone().into(), + delegator: user_a.clone(), + validator: val1.clone(), amount: coin(100, "ustake"), can_redelegate: coin(100, "ustake"), accumulated_rewards: coins(5, "ustake"), }; let del2a = FullDelegation { - delegator: user_a.clone().into(), - validator: val2.clone().into(), + delegator: user_a.clone(), + validator: val2.clone(), amount: coin(500, "ustake"), can_redelegate: coin(500, "ustake"), accumulated_rewards: coins(20, "ustake"), @@ -939,8 +944,8 @@ mod tests { // note we cannot have multiple delegations on one validator, they are collapsed into one let del1b = FullDelegation { - delegator: user_b.clone().into(), - validator: val1.clone().into(), + delegator: user_b.clone(), + validator: val1.clone(), amount: coin(500, "ustake"), can_redelegate: coin(0, "ustake"), accumulated_rewards: coins(0, "ustake"), @@ -948,8 +953,8 @@ mod tests { // and another one on val2 let del2c = FullDelegation { - delegator: user_c.clone().into(), - validator: val2.clone().into(), + delegator: user_c.clone(), + validator: val2.clone(), amount: coin(8888, "ustake"), can_redelegate: coin(4567, "ustake"), accumulated_rewards: coins(900, "ustake"), diff --git a/packages/std/src/query.rs b/packages/std/src/query.rs index 328449bdff..7491f82880 100644 --- a/packages/std/src/query.rs +++ b/packages/std/src/query.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::addresses::HumanAddr; +use crate::addresses::Addr; use crate::binary::Binary; use crate::coins::Coin; #[cfg(feature = "stargate")] @@ -175,11 +175,13 @@ pub struct AllDelegationsResponse { pub delegations: Vec, } -/// Delegation is basic (cheap to query) data about a delegation +/// Delegation is basic (cheap to query) data about a delegation. +/// +/// Instances are created in the querier. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Delegation { - pub delegator: HumanAddr, - pub validator: HumanAddr, + pub delegator: Addr, + pub validator: Addr, /// How much we have locked in the delegation pub amount: Coin, } @@ -202,11 +204,13 @@ pub struct DelegationResponse { } /// FullDelegation is all the info on the delegation, some (like accumulated_reward and can_redelegate) -/// is expensive to query +/// is expensive to query. +/// +/// Instances are created in the querier. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct FullDelegation { - pub delegator: HumanAddr, - pub validator: HumanAddr, + pub delegator: Addr, + pub validator: Addr, /// How much we have locked in the delegation pub amount: Coin, /// can_redelegate captures how much can be immediately redelegated. @@ -223,9 +227,10 @@ pub struct ValidatorsResponse { pub validators: Vec, } +/// Instances are created in the querier. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Validator { - pub address: HumanAddr, + pub address: Addr, pub commission: Decimal, pub max_commission: Decimal, /// TODO: what units are these (in terms of time)? From 44aa457b001ec11142954e02ff8df152f9dc487c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:04:21 +0200 Subject: [PATCH 24/38] Change address types in CosmosMsg --- CHANGELOG.md | 2 + contracts/burner/src/contract.rs | 4 +- contracts/burner/tests/integration.rs | 2 +- contracts/hackatom/src/contract.rs | 13 ++----- contracts/hackatom/tests/integration.rs | 11 +----- .../ibc-reflect-send/schema/execute_msg.json | 35 ++++++------------ .../ibc-reflect-send/schema/packet_msg.json | 35 ++++++------------ contracts/ibc-reflect/schema/packet_msg.json | 35 ++++++------------ contracts/reflect/schema/execute_msg.json | 35 ++++++------------ .../schema/response_for__custom_msg.json | 37 +++++++------------ contracts/reflect/src/contract.rs | 12 +++--- contracts/reflect/tests/integration.rs | 12 +++--- contracts/staking/src/contract.rs | 8 ++-- packages/std/schema/cosmos_msg.json | 29 ++++++--------- packages/std/src/ibc.rs | 3 +- packages/std/src/results/context.rs | 8 ++-- packages/std/src/results/cosmos_msg.rs | 23 ++++++------ packages/std/src/results/response.rs | 9 ++--- 18 files changed, 118 insertions(+), 195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50008411b4..f5abd19394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,8 @@ and this project adheres to library. Please use the explicit `*_sub` methods introduced above. In a couple of releases from now, we want to introduce the operator again with panicking overflow behaviour ([#858]). +- cosmwasm-std: Change address types in `BankMsg`, `IbcMsg` and `WasmMsg` from + `HumanAddr` to `String` ([#802]). [#696]: https://github.com/CosmWasm/cosmwasm/issues/696 [#697]: https://github.com/CosmWasm/cosmwasm/issues/697 diff --git a/contracts/burner/src/contract.rs b/contracts/burner/src/contract.rs index bf498d690b..93a15620b6 100644 --- a/contracts/burner/src/contract.rs +++ b/contracts/burner/src/contract.rs @@ -32,7 +32,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult // get balance and send all to recipient let balance = deps.querier.query_all_balances(env.contract.address)?; let send = BankMsg::Send { - to_address: msg.payout.clone().into(), + to_address: msg.payout.clone(), amount: balance, }; @@ -91,7 +91,7 @@ mod tests { assert_eq!( msg, &BankMsg::Send { - to_address: payout.into(), + to_address: payout, amount: coins(123456, "gold"), } .into(), diff --git a/contracts/burner/tests/integration.rs b/contracts/burner/tests/integration.rs index b7df6183c1..fdd437e07b 100644 --- a/contracts/burner/tests/integration.rs +++ b/contracts/burner/tests/integration.rs @@ -71,7 +71,7 @@ fn migrate_cleans_up_data() { assert_eq!( msg, &BankMsg::Send { - to_address: payout.into(), + to_address: payout, amount: coins(123456, "gold"), } .into(), diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index ae5afec597..49b5a2bc8d 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -52,7 +52,7 @@ pub fn sudo(_deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { let msg = BankMsg::Send { - to_address: recipient.into(), + to_address: recipient, amount, }; let mut response = Response::default(); @@ -394,14 +394,7 @@ mod tests { let res = sudo(deps.as_mut(), mock_env(), sys_msg).unwrap(); assert_eq!(1, res.messages.len()); let msg = res.messages.get(0).expect("no message"); - assert_eq!( - msg, - &BankMsg::Send { - to_address: to_address.into(), - amount - } - .into(), - ); + assert_eq!(msg, &BankMsg::Send { to_address, amount }.into(),); } #[test] @@ -454,7 +447,7 @@ mod tests { assert_eq!( msg, &BankMsg::Send { - to_address: beneficiary.into(), + to_address: beneficiary, amount: coins(1000, "earth"), } .into(), diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index e030633a78..64a353a421 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -171,14 +171,7 @@ fn sudo_can_steal_tokens() { let res: Response = sudo(&mut deps, mock_env(), sys_msg).unwrap(); assert_eq!(1, res.messages.len()); let msg = res.messages.get(0).expect("no message"); - assert_eq!( - msg, - &BankMsg::Send { - to_address: to_address.into(), - amount - } - .into(), - ); + assert_eq!(msg, &BankMsg::Send { to_address, amount }.into(),); } #[test] @@ -248,7 +241,7 @@ fn execute_release_works() { assert_eq!( msg, &BankMsg::Send { - to_address: beneficiary.into(), + to_address: beneficiary, amount: coins(1000, "earth"), } .into(), diff --git a/contracts/ibc-reflect-send/schema/execute_msg.json b/contracts/ibc-reflect-send/schema/execute_msg.json index 8728e476ba..dfc8c5988d 100644 --- a/contracts/ibc-reflect-send/schema/execute_msg.json +++ b/contracts/ibc-reflect-send/schema/execute_msg.json @@ -123,7 +123,7 @@ } }, "to_address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -244,9 +244,6 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, - "HumanAddr": { - "type": "string" - }, "IbcMsg": { "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", "anyOf": [ @@ -299,11 +296,7 @@ }, "to_address": { "description": "address on the remote chain to receive these tokens", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } } } @@ -421,7 +414,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -446,7 +439,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -468,17 +461,13 @@ "properties": { "recipient": { "description": "this is the \"withdraw address\", the one that should receive the rewards if None, then use delegator address", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -504,10 +493,10 @@ "$ref": "#/definitions/Coin" }, "dst_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "src_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -538,7 +527,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", @@ -619,7 +608,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", diff --git a/contracts/ibc-reflect-send/schema/packet_msg.json b/contracts/ibc-reflect-send/schema/packet_msg.json index f83b977942..167f9f556d 100644 --- a/contracts/ibc-reflect-send/schema/packet_msg.json +++ b/contracts/ibc-reflect-send/schema/packet_msg.json @@ -76,7 +76,7 @@ } }, "to_address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -197,9 +197,6 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, - "HumanAddr": { - "type": "string" - }, "IbcMsg": { "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", "anyOf": [ @@ -252,11 +249,7 @@ }, "to_address": { "description": "address on the remote chain to receive these tokens", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } } } @@ -374,7 +367,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -399,7 +392,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -421,17 +414,13 @@ "properties": { "recipient": { "description": "this is the \"withdraw address\", the one that should receive the rewards if None, then use delegator address", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -457,10 +446,10 @@ "$ref": "#/definitions/Coin" }, "dst_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "src_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -491,7 +480,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", @@ -572,7 +561,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", diff --git a/contracts/ibc-reflect/schema/packet_msg.json b/contracts/ibc-reflect/schema/packet_msg.json index d7e478df9c..9fc4c1890c 100644 --- a/contracts/ibc-reflect/schema/packet_msg.json +++ b/contracts/ibc-reflect/schema/packet_msg.json @@ -75,7 +75,7 @@ } }, "to_address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -196,9 +196,6 @@ "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" }, - "HumanAddr": { - "type": "string" - }, "IbcMsg": { "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", "anyOf": [ @@ -251,11 +248,7 @@ }, "to_address": { "description": "address on the remote chain to receive these tokens", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } } } @@ -373,7 +366,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -398,7 +391,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -420,17 +413,13 @@ "properties": { "recipient": { "description": "this is the \"withdraw address\", the one that should receive the rewards if None, then use delegator address", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -456,10 +445,10 @@ "$ref": "#/definitions/Coin" }, "dst_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "src_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -490,7 +479,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", @@ -571,7 +560,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", diff --git a/contracts/reflect/schema/execute_msg.json b/contracts/reflect/schema/execute_msg.json index 23ce07ec61..1be850d27e 100644 --- a/contracts/reflect/schema/execute_msg.json +++ b/contracts/reflect/schema/execute_msg.json @@ -94,7 +94,7 @@ } }, "to_address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -240,9 +240,6 @@ } ] }, - "HumanAddr": { - "type": "string" - }, "IbcMsg": { "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", "anyOf": [ @@ -295,11 +292,7 @@ }, "to_address": { "description": "address on the remote chain to receive these tokens", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } } } @@ -426,7 +419,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -451,7 +444,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -473,17 +466,13 @@ "properties": { "recipient": { "description": "this is the \"withdraw address\", the one that should receive the rewards if None, then use delegator address", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -509,10 +498,10 @@ "$ref": "#/definitions/Coin" }, "dst_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "src_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -573,7 +562,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", @@ -654,7 +643,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", diff --git a/contracts/reflect/schema/response_for__custom_msg.json b/contracts/reflect/schema/response_for__custom_msg.json index 50c25700d7..24bb6b74fc 100644 --- a/contracts/reflect/schema/response_for__custom_msg.json +++ b/contracts/reflect/schema/response_for__custom_msg.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Response_for_CustomMsg", - "description": "A response of a contract entry point, such as `instantiate`, `execute` or `migrate`.\n\nThis type can be constructed directly at the end of the call. Alternatively a mutable response instance can be created early in the contract's logic and incrementally be updated.\n\n## Examples\n\nDirect:\n\n``` # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo}; # type InstantiateMsg = (); # use cosmwasm_std::{attr, Response, StdResult};\n\npub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { // ...\n\nOk(Response { submessages: vec![], messages: vec![], attributes: vec![attr(\"action\", \"instantiate\")], data: None, }) } ```\n\nMutating:\n\n``` # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, HumanAddr, MessageInfo}; # type InstantiateMsg = (); # type MyError = (); # use cosmwasm_std::Response;\n\npub fn instantiate( deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg, ) -> Result { let mut response = Response::new(); // ... response.add_attribute(\"Let the\", \"hacking begin\"); // ... response.add_message(BankMsg::Send { to_address: HumanAddr::from(\"recipient\"), amount: coins(128, \"uint\"), }); response.add_attribute(\"foo\", \"bar\"); // ... response.set_data(Binary::from(b\"the result data\")); Ok(response) } ```", + "description": "A response of a contract entry point, such as `instantiate`, `execute` or `migrate`.\n\nThis type can be constructed directly at the end of the call. Alternatively a mutable response instance can be created early in the contract's logic and incrementally be updated.\n\n## Examples\n\nDirect:\n\n``` # use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo}; # type InstantiateMsg = (); # use cosmwasm_std::{attr, Response, StdResult};\n\npub fn instantiate( deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { // ...\n\nOk(Response { submessages: vec![], messages: vec![], attributes: vec![attr(\"action\", \"instantiate\")], data: None, }) } ```\n\nMutating:\n\n``` # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, MessageInfo}; # type InstantiateMsg = (); # type MyError = (); # use cosmwasm_std::Response;\n\npub fn instantiate( deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg, ) -> Result { let mut response = Response::new(); // ... response.add_attribute(\"Let the\", \"hacking begin\"); // ... response.add_message(BankMsg::Send { to_address: String::from(\"recipient\"), amount: coins(128, \"uint\"), }); response.add_attribute(\"foo\", \"bar\"); // ... response.set_data(Binary::from(b\"the result data\")); Ok(response) } ```", "type": "object", "required": [ "attributes", @@ -82,7 +82,7 @@ } }, "to_address": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -228,9 +228,6 @@ } ] }, - "HumanAddr": { - "type": "string" - }, "IbcMsg": { "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", "anyOf": [ @@ -283,11 +280,7 @@ }, "to_address": { "description": "address on the remote chain to receive these tokens", - "allOf": [ - { - "$ref": "#/definitions/HumanAddr" - } - ] + "type": "string" } } } @@ -414,7 +407,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -439,7 +432,7 @@ "$ref": "#/definitions/Coin" }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -461,17 +454,13 @@ "properties": { "recipient": { "description": "this is the \"withdraw address\", the one that should receive the rewards if None, then use delegator address", - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] }, "validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -497,10 +486,10 @@ "$ref": "#/definitions/Coin" }, "dst_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "src_validator": { - "$ref": "#/definitions/HumanAddr" + "type": "string" } } } @@ -561,7 +550,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", @@ -642,7 +631,7 @@ ], "properties": { "contract_addr": { - "$ref": "#/definitions/HumanAddr" + "type": "string" }, "msg": { "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index d0d9771785..dcd31f538a 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -195,7 +195,7 @@ mod tests { use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ coin, coins, from_binary, AllBalanceResponse, BankMsg, BankQuery, Binary, ContractResult, - Event, HumanAddr, ReplyOn, StakingMsg, StdError, SubcallResponse, + Event, ReplyOn, StakingMsg, StdError, SubcallResponse, }; #[test] @@ -262,7 +262,7 @@ mod tests { let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); let payload = vec![BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into()]; @@ -285,7 +285,7 @@ mod tests { // signer is not owner let payload = vec![BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into()]; @@ -325,7 +325,7 @@ mod tests { let payload = vec![ BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into(), @@ -333,7 +333,7 @@ mod tests { CustomMsg::Raw(Binary(b"{\"foo\":123}".to_vec())).into(), CustomMsg::Debug("Hi, Dad!".to_string()).into(), StakingMsg::Delegate { - validator: HumanAddr::from("validator"), + validator: String::from("validator"), amount: coin(100, "ustake"), } .into(), @@ -463,7 +463,7 @@ mod tests { id, gas_limit: None, msg: BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into(), diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index 812b922975..775ae7f03f 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -18,8 +18,8 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - attr, coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, Event, HumanAddr, Reply, - Response, StakingMsg, SubMsg, SubcallResponse, SystemResult, + attr, coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, Event, Reply, Response, + StakingMsg, SubMsg, SubcallResponse, SystemResult, }; use cosmwasm_vm::{ testing::{ @@ -83,7 +83,7 @@ fn reflect() { let payload = vec![ BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into(), @@ -91,7 +91,7 @@ fn reflect() { CustomMsg::Raw(Binary(b"{\"foo\":123}".to_vec())).into(), CustomMsg::Debug("Hi, Dad!".to_string()).into(), StakingMsg::Delegate { - validator: HumanAddr::from("validator"), + validator: String::from("validator"), amount: coin(100, "ustake"), } .into(), @@ -116,7 +116,7 @@ fn reflect_requires_owner() { // signer is not owner let payload = vec![BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into()]; @@ -199,7 +199,7 @@ fn reflect_subcall() { id, gas_limit: None, msg: BankMsg::Send { - to_address: HumanAddr::from("friend"), + to_address: String::from("friend"), amount: coins(1, "token"), } .into(), diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 5f7f7fe5ec..55b3b96a11 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -176,7 +176,7 @@ pub fn bond(deps: DepsMut, env: Env, info: MessageInfo) -> StdResult { let res = Response { submessages: vec![], messages: vec![StakingMsg::Delegate { - validator: invest.validator.into(), + validator: invest.validator, amount: payment.clone(), } .into()], @@ -244,7 +244,7 @@ pub fn unbond(deps: DepsMut, env: Env, info: MessageInfo, amount: Uint128) -> St let res = Response { submessages: vec![], messages: vec![StakingMsg::Undelegate { - validator: invest.validator.into(), + validator: invest.validator, amount: coin(unbond.u128(), &invest.bond_denom), } .into()], @@ -318,7 +318,7 @@ pub fn reinvest(deps: DepsMut, env: Env, _info: MessageInfo) -> StdResult, }, } @@ -63,24 +62,24 @@ pub enum BankMsg { pub enum StakingMsg { /// This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). /// `delegator_address` is automatically filled with the current contract's address. - Delegate { validator: HumanAddr, amount: Coin }, + Delegate { validator: String, amount: Coin }, /// This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). /// `delegator_address` is automatically filled with the current contract's address. - Undelegate { validator: HumanAddr, amount: Coin }, + Undelegate { validator: String, amount: Coin }, /// This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37) /// followed by a [MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). /// `delegator_address` is automatically filled with the current contract's address. Withdraw { - validator: HumanAddr, + validator: String, /// this is the "withdraw address", the one that should receive the rewards /// if None, then use delegator address - recipient: Option, + recipient: Option, }, /// This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). /// `delegator_address` is automatically filled with the current contract's address. Redelegate { - src_validator: HumanAddr, - dst_validator: HumanAddr, + src_validator: String, + dst_validator: String, amount: Coin, }, } @@ -97,7 +96,7 @@ pub enum WasmMsg { /// This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). /// `sender` is automatically filled with the current contract's address. Execute { - contract_addr: HumanAddr, + contract_addr: String, /// msg is the json-encoded ExecuteMsg struct (as raw Binary) msg: Binary, send: Vec, @@ -122,7 +121,7 @@ pub enum WasmMsg { /// This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). /// `sender` is automatically filled with the current contract's address. Migrate { - contract_addr: HumanAddr, + contract_addr: String, /// the code_id of the new logic to place in the given contract new_code_id: u64, /// msg is the json-encoded MigrateMsg struct that will be passed to the new code @@ -152,7 +151,7 @@ where /// Shortcut helper as the construction of WasmMsg::Instantiate can be quite verbose in contract code pub fn wasm_execute(contract_addr: T, msg: &U, send: Vec) -> StdResult where - T: Into, + T: Into, U: Serialize, { let payload = to_binary(msg)?; @@ -196,7 +195,7 @@ mod tests { #[test] fn from_bank_msg_works() { - let to_address = HumanAddr::from("you"); + let to_address = String::from("you"); let amount = coins(1015, "earth"); let bank = BankMsg::Send { to_address, amount }; let msg: CosmosMsg = bank.clone().into(); diff --git a/packages/std/src/results/response.rs b/packages/std/src/results/response.rs index f50d5a1334..85adfa4187 100644 --- a/packages/std/src/results/response.rs +++ b/packages/std/src/results/response.rs @@ -43,7 +43,7 @@ use crate::results::SubMsg; /// Mutating: /// /// ``` -/// # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, HumanAddr, MessageInfo}; +/// # use cosmwasm_std::{coins, BankMsg, Binary, DepsMut, Env, MessageInfo}; /// # type InstantiateMsg = (); /// # type MyError = (); /// # @@ -60,7 +60,7 @@ use crate::results::SubMsg; /// response.add_attribute("Let the", "hacking begin"); /// // ... /// response.add_message(BankMsg::Send { -/// to_address: HumanAddr::from("recipient"), +/// to_address: String::from("recipient"), /// amount: coins(128, "uint"), /// }); /// response.add_attribute("foo", "bar"); @@ -145,7 +145,6 @@ where mod tests { use super::super::BankMsg; use super::*; - use crate::addresses::HumanAddr; use crate::{coins, from_slice, to_vec}; #[test] @@ -154,7 +153,7 @@ mod tests { submessages: vec![SubMsg { id: 12, msg: BankMsg::Send { - to_address: HumanAddr::from("checker"), + to_address: String::from("checker"), amount: coins(888, "moon"), } .into(), @@ -162,7 +161,7 @@ mod tests { reply_on: ReplyOn::Always, }], messages: vec![BankMsg::Send { - to_address: HumanAddr::from("you"), + to_address: String::from("you"), amount: coins(1015, "earth"), } .into()], From ad6d0bd3080c13603e888dfe25900860a9d1906f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:12:20 +0200 Subject: [PATCH 25/38] Simplify mock_info tests --- packages/std/src/mock.rs | 21 ++++++++++++--------- packages/vm/src/testing/mock.rs | 23 +++++++++++++---------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 75c9dd5871..e055f01de3 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -527,15 +527,18 @@ mod tests { "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"; #[test] - fn mock_info_arguments() { - let name = HumanAddr("my name".to_string()); - - // make sure we can generate with &str and &HumanAddr - let a = mock_info("my name", &coins(100, "atom")); - let b = mock_info(&name, &coins(100, "atom")); - - // and the results are the same - assert_eq!(a, b); + fn mock_info_works() { + let info = mock_info("my name", &coins(100, "atom")); + assert_eq!( + info, + MessageInfo { + sender: Addr::unchecked("my name"), + funds: vec![Coin { + amount: 100u128.into(), + denom: "atom".into(), + }] + } + ); } #[test] diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index 1f22032053..6bdbfbd7df 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -167,18 +167,21 @@ pub fn mock_info(sender: &str, funds: &[Coin]) -> MessageInfo { mod tests { use super::*; use crate::BackendError; - use cosmwasm_std::{coins, HumanAddr}; + use cosmwasm_std::coins; #[test] - fn mock_info_arguments() { - let name = HumanAddr("my name".to_string()); - - // make sure we can generate with &str and &HumanAddr - let a = mock_info("my name", &coins(100, "atom")); - let b = mock_info(&name, &coins(100, "atom")); - - // and the results are the same - assert_eq!(a, b); + fn mock_info_works() { + let info = mock_info("my name", &coins(100, "atom")); + assert_eq!( + info, + MessageInfo { + sender: Addr::unchecked("my name"), + funds: vec![Coin { + amount: 100u128.into(), + denom: "atom".into(), + }] + } + ); } #[test] From 89c1b99fb0e443b4ec2d2dd5c29d6096989721c5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:18:12 +0200 Subject: [PATCH 26/38] Implement PartialEq for Addr and String --- packages/std/src/addresses.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 30b8ffed2a..67baaf2bbe 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -56,6 +56,20 @@ impl PartialEq for &str { } } +/// Implement `Addr == String` +impl PartialEq for Addr { + fn eq(&self, rhs: &String) -> bool { + &self.0 == rhs + } +} + +/// Implement `String == Addr` +impl PartialEq for String { + fn eq(&self, rhs: &Addr) -> bool { + self == &rhs.0 + } +} + // Addr->String and Addr->HumanAddr are safe conversions. // However, the opposite direction is unsafe and must not be implemented. @@ -250,6 +264,16 @@ mod tests { assert_eq!("cos934gh9034hg04g0h134", addr); } + #[test] + fn addr_implements_partial_eq_with_string() { + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + + // `Addr == String` + assert_eq!(addr, String::from("cos934gh9034hg04g0h134")); + // `String == Addr` + assert_eq!(String::from("cos934gh9034hg04g0h134"), addr); + } + #[test] fn addr_implements_into_string() { let addr = Addr::unchecked("cos934gh9034hg04g0h134"); From 7aa03abf656cdc376ebaeb5fa5fb7c139395f70d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:29:01 +0200 Subject: [PATCH 27/38] Let Api::addr_humanize return Addr --- CHANGELOG.md | 2 ++ packages/std/src/addresses.rs | 3 ++- packages/std/src/imports.rs | 6 +++--- packages/std/src/mock.rs | 14 +++++++------- packages/std/src/results/attribute.rs | 2 -- packages/std/src/traits.rs | 4 ++-- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5abd19394..0fd77a7b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,8 @@ and this project adheres to panicking overflow behaviour ([#858]). - cosmwasm-std: Change address types in `BankMsg`, `IbcMsg` and `WasmMsg` from `HumanAddr` to `String` ([#802]). +- cosmwasm-std: `Api::addr_humanize` now returns `Addr` instead of `HumanAddr` + ([#802]). [#696]: https://github.com/CosmWasm/cosmwasm/issues/696 [#697]: https://github.com/CosmWasm/cosmwasm/issues/697 diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 67baaf2bbe..9de7224458 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -13,7 +13,8 @@ use crate::binary::Binary; /// This type represents a validated address. It can be created in the following ways /// 1. Use `Addr::unchecked(input)` /// 2. Use `let checked: Addr = deps.api.addr_validate(input)?` -/// 3. Deserialize from JSON. This must only be done from JSON that was validated before +/// 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` +/// 4. Deserialize from JSON. This must only be done from JSON that was validated before /// such as a contract's state. `Addr` must not be used in messages sent by the user /// because this would result in unvalidated instances. /// diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index c9c410c708..33aab44a6c 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -1,6 +1,6 @@ use std::vec::Vec; -use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; +use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; use crate::errors::{RecoverPubkeyError, StdError, StdResult, SystemError, VerificationError}; use crate::import_helpers::{from_high_half, from_low_half}; @@ -190,7 +190,7 @@ impl Api for ExternalApi { Ok(CanonicalAddr(Binary(out))) } - fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { + fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { let send = build_region(&canonical); let send_ptr = &*send as *const Region as u32; let human = alloc(HUMAN_ADDRESS_BUFFER_LENGTH); @@ -205,7 +205,7 @@ impl Api for ExternalApi { } let address = unsafe { consume_string_region_written_by_vm(human) }; - Ok(address.into()) + Ok(Addr::unchecked(address)) } fn secp256k1_verify( diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index e055f01de3..836083f3cb 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use std::collections::HashMap; -use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; +use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; use crate::coins::Coin; use crate::deps::OwnedDeps; @@ -102,7 +102,7 @@ impl Api for MockApi { Ok(out.into()) } - fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { + fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { if canonical.len() != self.canonical_length { return Err(StdError::generic_err( "Invalid input: canonical address length not correct", @@ -121,7 +121,7 @@ impl Api for MockApi { let trimmed = tmp.into_iter().filter(|&x| x != 0x00).collect(); // decode UTF-8 bytes into string let human = String::from_utf8(trimmed)?; - Ok(human.into()) + Ok(Addr::unchecked(human)) } fn secp256k1_verify( @@ -513,7 +513,7 @@ pub fn digit_sum(input: &[u8]) -> usize { mod tests { use super::*; use crate::query::Delegation; - use crate::{coin, coins, from_binary, Decimal, HumanAddr}; + use crate::{coin, coins, from_binary, Decimal}; use hex_literal::hex; const SECP256K1_MSG_HASH_HEX: &str = @@ -545,7 +545,7 @@ mod tests { fn canonicalize_and_humanize_restores_original() { let api = MockApi::default(); - let original = HumanAddr::from("shorty"); + let original = String::from("shorty"); let canonical = api.addr_canonicalize(&original).unwrap(); let recovered = api.addr_humanize(&canonical).unwrap(); assert_eq!(recovered, original); @@ -555,7 +555,7 @@ mod tests { #[should_panic(expected = "address too short")] fn addr_canonicalize_min_input_length() { let api = MockApi::default(); - let human = HumanAddr("1".to_string()); + let human = String::from("1"); let _ = api.addr_canonicalize(&human).unwrap(); } @@ -563,7 +563,7 @@ mod tests { #[should_panic(expected = "address too long")] fn addr_canonicalize_max_input_length() { let api = MockApi::default(); - let human = HumanAddr::from("some-extremely-long-address-not-supported-by-this-api"); + let human = String::from("some-extremely-long-address-not-supported-by-this-api"); let _ = api.addr_canonicalize(&human).unwrap(); } diff --git a/packages/std/src/results/attribute.rs b/packages/std/src/results/attribute.rs index e068dfc98f..619e12bbad 100644 --- a/packages/std/src/results/attribute.rs +++ b/packages/std/src/results/attribute.rs @@ -19,7 +19,6 @@ pub fn attr(key: K, value: V) -> Attribute { #[cfg(test)] mod tests { use super::*; - use crate::addresses::HumanAddr; use crate::Uint128; #[test] @@ -32,7 +31,6 @@ mod tests { assert_eq!(attr("foo", "42"), expeceted); assert_eq!(attr("foo".to_string(), "42"), expeceted); assert_eq!(attr("foo", "42".to_string()), expeceted); - assert_eq!(attr("foo", HumanAddr::from("42")), expeceted); assert_eq!(attr("foo", Uint128(42)), expeceted); assert_eq!(attr("foo", 42), expeceted); } diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 08d7212cac..52718b4a27 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -1,7 +1,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::ops::Deref; -use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; +use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; use crate::coins::Coin; use crate::errors::{RecoverPubkeyError, StdError, StdResult, VerificationError}; @@ -85,7 +85,7 @@ pub trait Api { /// Takes a canonical address and returns a human readble address. /// This is the inverse of [addr_canonicalize]. - fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult; + fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult; fn secp256k1_verify( &self, From 58be1305747711dee1c6b19ab9e37d362ac06f67 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 1 Apr 2021 22:40:28 +0200 Subject: [PATCH 28/38] Deprecarte HumanAddr --- CHANGELOG.md | 6 ++++++ packages/std/src/addresses.rs | 6 ++++++ packages/std/src/lib.rs | 1 + 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd77a7b02..bf6aeedd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,12 @@ and this project adheres to deprecated in favour of the new `Response`. - cosmwasm-std: `Context` is deprecated in favour of the new mutable helpers in `Response`. +- cosmwasm-std: `HumanAddr` is not much more than an alias to `String` and it + does not provide significant safety advantages. With CosmWasm 0.14, we now use + `String` when there was `HumanAddr` before. There is also the new `Addr`, + which holds a validated immutable human readable address. ([#802]) + +[#802]: https://github.com/CosmWasm/cosmwasm/pull/802 ## [0.13.2] - 2021-01-14 diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 9de7224458..7260424e03 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt; @@ -86,6 +88,10 @@ impl From for HumanAddr { } } +#[deprecated( + since = "0.14.0", + note = "HumanAddr is not much more than an alias to String and it does not provide significant safety advantages. With CosmWasm 0.14, we now use String when there was HumanAddr before. There is also the new Addr, which holds a validated immutable human readable address." +)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] pub struct HumanAddr(pub String); diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 604b43a2aa..1274357451 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -22,6 +22,7 @@ mod storage; mod traits; mod types; +#[allow(deprecated)] pub use crate::addresses::{Addr, CanonicalAddr, HumanAddr}; pub use crate::binary::Binary; pub use crate::coins::{coin, coins, has_coins, Coin}; From 238a976d6eb811e47b6f7cf7a1ca6b946ceb4791 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 6 Apr 2021 14:11:06 +0200 Subject: [PATCH 29/38] Add MIGRATING entry --- MIGRATING.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/MIGRATING.md b/MIGRATING.md index d97b6c90a1..576f5ff0cc 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -225,6 +225,80 @@ major releases of `cosmwasm`. Note that you can also view the - If necessary, add a wildcard arm to the `match` of now non-exhaustive message types `BankMsg`, `BankQuery`, `WasmMsg` and `WasmQuery`. +- `HumanAddr` has been deprecated in favour of simply `String`. It never added + any significant safety bonus over `String` and was just a marker type. The new + type `Addr` was created to hold validated addresses. Those can be created via + `Addr::unchecked`, `Api::addr_validate`, `Api::addr_humanize` and JSON + deserialization. In order to maintain type safety, deserialization into `Addr` + must only be done from trusted sources like a contract's state or a query + response. User inputs must be deserialized into `String`. This new `Addr` type + allows makes it easy to use human readable addresses in state: + + With pre-validated `Addr` from `MessageInfo`: + + ```rust + // before + pub struct State { + pub owner: CanonicalAddr, + } + + let state = State { + owner: deps.api.canonical_address(&info.sender)?, + }; + + + // after + pub struct State { + pub owner: Addr, + } + let state = State { + owner: info.sender.clone(), + }; + ``` + + With user input in `msg`: + + ```rust + // before + pub struct State { + pub verifier: CanonicalAddr, + pub beneficiary: CanonicalAddr, + pub funder: CanonicalAddr, + } + + deps.storage.set( + CONFIG_KEY, + &to_vec(&State { + verifier: deps.api.canonical_address(&msg.verifier)?, + beneficiary: deps.api.canonical_address(&msg.beneficiary)?, + funder: deps.api.canonical_address(&info.sender)?, + })?, + ); + + // after + pub struct State { + pub verifier: Addr, + pub beneficiary: Addr, + pub funder: Addr, + } + + deps.storage.set( + CONFIG_KEY, + &to_vec(&State { + verifier: deps.api.addr_validate(&msg.verifier)?, + verifier: deps.api.addr_validate(&msg.verifier)?, + funder: info.sender, + })?, + ); + ``` + + The existing `CanonicalAddr` remains unchanged and can be used in which a + compact binary representation is desired. For JSON state this does not save + much data (e.g. the bech32 address + cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz takes 45 bytes as direct ASCII + and 28 bytes when its canonical representation is base64 encoded). For fixed + length database keys `CanonicalAddr` remains handy though. + ## 0.12 -> 0.13 - The minimum Rust supported version for 0.13 is 1.47.0. Verify your Rust From 0d246834f616c3425bede08245a16ae26905d351 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 6 Apr 2021 14:17:21 +0200 Subject: [PATCH 30/38] Bring back state schemas for hackatom --- contracts/hackatom/examples/schema.rs | 5 +++++ contracts/hackatom/schema/state.json | 27 +++++++++++++++++++++++++++ contracts/hackatom/src/state.rs | 3 ++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 contracts/hackatom/schema/state.json diff --git a/contracts/hackatom/examples/schema.rs b/contracts/hackatom/examples/schema.rs index 375100b2fd..8adeee986f 100644 --- a/contracts/hackatom/examples/schema.rs +++ b/contracts/hackatom/examples/schema.rs @@ -5,6 +5,7 @@ use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; use cosmwasm_std::BalanceResponse; use hackatom::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg, VerifierResponse}; +use hackatom::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -12,6 +13,7 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); + // messages export_schema(&schema_for!(InstantiateMsg), &out_dir); export_schema(&schema_for!(ExecuteMsg), &out_dir); export_schema(&schema_for!(MigrateMsg), &out_dir); @@ -19,4 +21,7 @@ fn main() { export_schema(&schema_for!(QueryMsg), &out_dir); export_schema(&schema_for!(VerifierResponse), &out_dir); export_schema(&schema_for!(BalanceResponse), &out_dir); + + // state + export_schema(&schema_for!(State), &out_dir); } diff --git a/contracts/hackatom/schema/state.json b/contracts/hackatom/schema/state.json new file mode 100644 index 0000000000..8462e9fb34 --- /dev/null +++ b/contracts/hackatom/schema/state.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "type": "object", + "required": [ + "beneficiary", + "funder", + "verifier" + ], + "properties": { + "beneficiary": { + "$ref": "#/definitions/Addr" + }, + "funder": { + "$ref": "#/definitions/Addr" + }, + "verifier": { + "$ref": "#/definitions/Addr" + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/hackatom/src/state.rs b/contracts/hackatom/src/state.rs index e306cf8370..b1e8196bee 100644 --- a/contracts/hackatom/src/state.rs +++ b/contracts/hackatom/src/state.rs @@ -1,10 +1,11 @@ +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::Addr; pub const CONFIG_KEY: &[u8] = b"config"; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct State { pub verifier: Addr, pub beneficiary: Addr, From 91d1f90eac41ae5a679f71be36146e4759f9f3b8 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 6 Apr 2021 14:19:00 +0200 Subject: [PATCH 31/38] Bring back state schema for reflect --- contracts/reflect/examples/schema.rs | 5 +++++ contracts/reflect/schema/state.json | 19 +++++++++++++++++++ contracts/reflect/src/state.rs | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 contracts/reflect/schema/state.json diff --git a/contracts/reflect/examples/schema.rs b/contracts/reflect/examples/schema.rs index 027928a2c7..cc86ed17d9 100644 --- a/contracts/reflect/examples/schema.rs +++ b/contracts/reflect/examples/schema.rs @@ -8,6 +8,7 @@ use reflect::msg::{ CapitalizedResponse, ChainResponse, CustomMsg, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg, RawResponse, }; +use reflect::state::State; fn main() { let mut out_dir = current_dir().unwrap(); @@ -15,6 +16,7 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); + // messages export_schema(&schema_for!(CustomMsg), &out_dir); export_schema(&schema_for!(InstantiateMsg), &out_dir); export_schema(&schema_for!(ExecuteMsg), &out_dir); @@ -26,4 +28,7 @@ fn main() { export_schema(&schema_for!(CapitalizedResponse), &out_dir); export_schema(&schema_for!(ChainResponse), &out_dir); export_schema(&schema_for!(RawResponse), &out_dir); + + // state + export_schema(&schema_for!(State), &out_dir); } diff --git a/contracts/reflect/schema/state.json b/contracts/reflect/schema/state.json new file mode 100644 index 0000000000..1c10ba441e --- /dev/null +++ b/contracts/reflect/schema/state.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "$ref": "#/definitions/Addr" + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/reflect/src/state.rs b/contracts/reflect/src/state.rs index ef2706b9dc..57b475dac7 100644 --- a/contracts/reflect/src/state.rs +++ b/contracts/reflect/src/state.rs @@ -1,3 +1,4 @@ +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, Reply, Storage}; @@ -9,7 +10,7 @@ use cosmwasm_storage::{ const CONFIG_KEY: &[u8] = b"config"; const RESULT_PREFIX: &[u8] = b"result"; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct State { pub owner: Addr, } From b84f5a6ffab8cf397329925dbf0f6e7c2979587e Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 6 Apr 2021 14:25:12 +0200 Subject: [PATCH 32/38] Restore state schema for staking contract --- contracts/staking/examples/schema.rs | 7 +++ contracts/staking/schema/investment_info.json | 60 +++++++++++++++++++ contracts/staking/schema/supply.json | 42 +++++++++++++ contracts/staking/schema/token_info.json | 27 +++++++++ contracts/staking/src/contract.rs | 16 ++++- contracts/staking/src/state.rs | 22 +++++-- 6 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 contracts/staking/schema/investment_info.json create mode 100644 contracts/staking/schema/supply.json create mode 100644 contracts/staking/schema/token_info.json diff --git a/contracts/staking/examples/schema.rs b/contracts/staking/examples/schema.rs index 4d57c6de89..780fc0c0ed 100644 --- a/contracts/staking/examples/schema.rs +++ b/contracts/staking/examples/schema.rs @@ -7,6 +7,7 @@ use staking::msg::{ BalanceResponse, ClaimsResponse, ExecuteMsg, InstantiateMsg, InvestmentResponse, QueryMsg, TokenInfoResponse, }; +use staking::state::{InvestmentInfo, Supply, TokenInfo}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -14,6 +15,7 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); + // messages export_schema(&schema_for!(InstantiateMsg), &out_dir); export_schema(&schema_for!(ExecuteMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); @@ -21,4 +23,9 @@ fn main() { export_schema(&schema_for!(ClaimsResponse), &out_dir); export_schema(&schema_for!(InvestmentResponse), &out_dir); export_schema(&schema_for!(TokenInfoResponse), &out_dir); + + // state + export_schema(&schema_for!(InvestmentInfo), &out_dir); + export_schema(&schema_for!(TokenInfo), &out_dir); + export_schema(&schema_for!(Supply), &out_dir); } diff --git a/contracts/staking/schema/investment_info.json b/contracts/staking/schema/investment_info.json new file mode 100644 index 0000000000..9ea300a35d --- /dev/null +++ b/contracts/staking/schema/investment_info.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InvestmentInfo", + "description": "Investment info is fixed at initialization, and is used to control the function of the contract", + "type": "object", + "required": [ + "bond_denom", + "exit_tax", + "min_withdrawal", + "owner", + "validator" + ], + "properties": { + "bond_denom": { + "description": "this is the denomination we can stake (and only one we accept for payments)", + "type": "string" + }, + "exit_tax": { + "description": "this is how much the owner takes as a cut when someone unbonds", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "min_withdrawal": { + "description": "This is the minimum amount we will pull out to reinvest, as well as a minumum that can be unbonded (to avoid needless staking tx)", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "owner": { + "description": "owner created the contract and takes a cut", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "validator": { + "description": "All tokens are bonded to this validator FIXME: humanize/canonicalize address doesn't work for validator addrresses", + "type": "string" + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/staking/schema/supply.json b/contracts/staking/schema/supply.json new file mode 100644 index 0000000000..0c5af2696b --- /dev/null +++ b/contracts/staking/schema/supply.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Supply", + "description": "Supply is dynamic and tracks the current supply of staked and ERC20 tokens.", + "type": "object", + "required": [ + "bonded", + "claims", + "issued" + ], + "properties": { + "bonded": { + "description": "bonded is how many native tokens exist bonded to the validator", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "claims": { + "description": "claims is how many tokens need to be reserved paying back those who unbonded", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "issued": { + "description": "issued is how many derivative tokens this contract has issued", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "Uint128": { + "type": "string" + } + } +} diff --git a/contracts/staking/schema/token_info.json b/contracts/staking/schema/token_info.json new file mode 100644 index 0000000000..5329082429 --- /dev/null +++ b/contracts/staking/schema/token_info.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfo", + "description": "Info to display the derivative token in a UI", + "type": "object", + "required": [ + "decimals", + "name", + "symbol" + ], + "properties": { + "decimals": { + "description": "decimal places of the derivative token (for UI)", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "name": { + "description": "name of the derivative token", + "type": "string" + }, + "symbol": { + "description": "symbol / ticker of the derivative token", + "type": "string" + } + } +} diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 55b3b96a11..0d91bab7fc 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -10,7 +10,7 @@ use crate::msg::{ }; use crate::state::{ balances, balances_read, claims, claims_read, invest_info, invest_info_read, token_info, - token_info_read, total_supply, total_supply_read, InvestmentInfo, Supply, + token_info_read, total_supply, total_supply_read, InvestmentInfo, Supply, TokenInfo, }; const FALLBACK_RATIO: Decimal = Decimal::one(); @@ -31,7 +31,7 @@ pub fn instantiate( ))); } - let token = TokenInfoResponse { + let token = TokenInfo { name: msg.name, symbol: msg.symbol, decimals: msg.decimals, @@ -391,7 +391,17 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } pub fn query_token_info(deps: Deps) -> StdResult { - token_info_read(deps.storage).load() + let TokenInfo { + name, + symbol, + decimals, + } = token_info_read(deps.storage).load()?; + + Ok(TokenInfoResponse { + name, + symbol, + decimals, + }) } pub fn query_balance(deps: Deps, address: &str) -> StdResult { diff --git a/contracts/staking/src/state.rs b/contracts/staking/src/state.rs index 7c39974c40..f59c2ccea1 100644 --- a/contracts/staking/src/state.rs +++ b/contracts/staking/src/state.rs @@ -1,3 +1,4 @@ +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, Decimal, Storage, Uint128}; @@ -6,8 +7,6 @@ use cosmwasm_storage::{ Singleton, }; -use crate::msg::TokenInfoResponse; - pub const KEY_INVESTMENT: &[u8] = b"invest"; pub const KEY_TOKEN_INFO: &[u8] = b"token"; pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; @@ -34,7 +33,7 @@ pub fn claims_read(storage: &dyn Storage) -> ReadonlyBucket { } /// Investment info is fixed at initialization, and is used to control the function of the contract -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InvestmentInfo { /// owner created the contract and takes a cut pub owner: Addr, @@ -50,8 +49,19 @@ pub struct InvestmentInfo { pub min_withdrawal: Uint128, } +/// Info to display the derivative token in a UI +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct TokenInfo { + /// name of the derivative token + pub name: String, + /// symbol / ticker of the derivative token + pub symbol: String, + /// decimal places of the derivative token (for UI) + pub decimals: u8, +} + /// Supply is dynamic and tracks the current supply of staked and ERC20 tokens. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)] pub struct Supply { /// issued is how many derivative tokens this contract has issued pub issued: Uint128, @@ -69,11 +79,11 @@ pub fn invest_info_read(storage: &dyn Storage) -> ReadonlySingleton Singleton { +pub fn token_info(storage: &mut dyn Storage) -> Singleton { singleton(storage, KEY_TOKEN_INFO) } -pub fn token_info_read(storage: &dyn Storage) -> ReadonlySingleton { +pub fn token_info_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, KEY_TOKEN_INFO) } From c9e137762a2c6033d76ee2de575886af5069f5da Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Apr 2021 14:52:18 +0200 Subject: [PATCH 33/38] Avoid unnecessary as_ref() for Addr == Addr and Addr == String --- contracts/ibc-reflect-send/src/contract.rs | 4 ++-- contracts/staking/src/contract.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index f8063c58bc..060d412ea5 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -80,7 +80,7 @@ pub fn handle_send_msgs( ) -> StdResult { // auth check let cfg = config(deps.storage).load()?; - if info.sender.as_ref() != cfg.admin { + if info.sender != cfg.admin { return Err(StdError::generic_err("Only admin may send messages")); } // ensure the channel exists (not found if not registered) @@ -111,7 +111,7 @@ pub fn handle_check_remote_balance( ) -> StdResult { // auth check let cfg = config(deps.storage).load()?; - if info.sender.as_ref() != cfg.admin { + if info.sender != cfg.admin { return Err(StdError::generic_err("Only admin may send messages")); } // ensure the channel exists (not found if not registered) diff --git a/contracts/staking/src/contract.rs b/contracts/staking/src/contract.rs index 0d91bab7fc..d087a80ccc 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -24,7 +24,7 @@ pub fn instantiate( ) -> StdResult { // ensure the validator is registered let vals = deps.querier.query_validators()?; - if !vals.iter().any(|v| v.address.as_ref() == msg.validator) { + if !vals.iter().any(|v| v.address == msg.validator) { return Err(StdError::generic_err(format!( "{} is not in the current validator set", msg.validator From ec6a16fdcc472949934b06dab959f4d84cb77ecd Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Apr 2021 15:43:00 +0200 Subject: [PATCH 34/38] Fix and improve MIGRATING text --- MIGRATING.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/MIGRATING.md b/MIGRATING.md index 576f5ff0cc..5ab7c92579 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -232,7 +232,7 @@ major releases of `cosmwasm`. Note that you can also view the deserialization. In order to maintain type safety, deserialization into `Addr` must only be done from trusted sources like a contract's state or a query response. User inputs must be deserialized into `String`. This new `Addr` type - allows makes it easy to use human readable addresses in state: + makes it easy to use human readable addresses in state: With pre-validated `Addr` from `MessageInfo`: @@ -243,7 +243,7 @@ major releases of `cosmwasm`. Note that you can also view the } let state = State { - owner: deps.api.canonical_address(&info.sender)?, + owner: deps.api.canonical_address(&info.sender /* of type HumanAddr */)?, }; @@ -252,7 +252,7 @@ major releases of `cosmwasm`. Note that you can also view the pub owner: Addr, } let state = State { - owner: info.sender.clone(), + owner: info.sender.clone() /* of type Addr */, }; ``` @@ -269,9 +269,9 @@ major releases of `cosmwasm`. Note that you can also view the deps.storage.set( CONFIG_KEY, &to_vec(&State { - verifier: deps.api.canonical_address(&msg.verifier)?, - beneficiary: deps.api.canonical_address(&msg.beneficiary)?, - funder: deps.api.canonical_address(&info.sender)?, + verifier: deps.api.canonical_address(&msg.verifier /* of type HumanAddr */)?, + beneficiary: deps.api.canonical_address(&msg.beneficiary /* of type HumanAddr */)?, + funder: deps.api.canonical_address(&info.sender /* of type HumanAddr */)?, })?, ); @@ -285,16 +285,16 @@ major releases of `cosmwasm`. Note that you can also view the deps.storage.set( CONFIG_KEY, &to_vec(&State { - verifier: deps.api.addr_validate(&msg.verifier)?, - verifier: deps.api.addr_validate(&msg.verifier)?, - funder: info.sender, + verifier: deps.api.addr_validate(&msg.verifier /* of type String */)?, + beneficiary: deps.api.addr_validate(&msg.beneficiary /* of type String */)?, + funder: info.sender /* of type Addr */, })?, ); ``` - The existing `CanonicalAddr` remains unchanged and can be used in which a - compact binary representation is desired. For JSON state this does not save - much data (e.g. the bech32 address + The existing `CanonicalAddr` remains unchanged and can be used in cases in + which a compact binary representation is desired. For JSON state this does not + save much data (e.g. the bech32 address cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz takes 45 bytes as direct ASCII and 28 bytes when its canonical representation is base64 encoded). For fixed length database keys `CanonicalAddr` remains handy though. From 8eb6d4ded9005501e188210a17706ba03c8381ba Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Apr 2021 15:43:16 +0200 Subject: [PATCH 35/38] Fix typo in NoSuchContract::addr comment --- packages/std/src/errors/system_error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/std/src/errors/system_error.rs b/packages/std/src/errors/system_error.rs index eefdfc922f..e04a6bbcd0 100644 --- a/packages/std/src/errors/system_error.rs +++ b/packages/std/src/errors/system_error.rs @@ -25,7 +25,7 @@ pub enum SystemError { response: Binary, }, NoSuchContract { - /// The address that was attemted to query + /// The address that was attempted to query addr: String, }, Unknown {}, From d9b428327b49b9b400dba44c99c076ccc43d844f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Apr 2021 15:42:17 +0200 Subject: [PATCH 36/38] Make remote_addr a String in ibc-reflect-send --- contracts/ibc-reflect-send/src/contract.rs | 6 +++--- contracts/ibc-reflect-send/src/ibc.rs | 10 ++++------ contracts/ibc-reflect-send/src/msg.rs | 4 ++-- contracts/ibc-reflect-send/src/state.rs | 9 ++++++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/contracts/ibc-reflect-send/src/contract.rs b/contracts/ibc-reflect-send/src/contract.rs index 060d412ea5..b85e08fb27 100644 --- a/contracts/ibc-reflect-send/src/contract.rs +++ b/contracts/ibc-reflect-send/src/contract.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - attr, entry_point, to_binary, Addr, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, Order, + attr, entry_point, to_binary, CosmosMsg, Deps, DepsMut, Env, IbcMsg, MessageInfo, Order, QueryResponse, Response, StdError, StdResult, }; @@ -159,7 +159,7 @@ pub fn handle_send_funds( // load remote account let data = accounts(deps.storage).load(reflect_channel_id.as_bytes())?; - let remote_addr: Addr = match data.remote_addr { + let remote_addr = match data.remote_addr { Some(addr) => addr, None => { return Err(StdError::generic_err( @@ -171,7 +171,7 @@ pub fn handle_send_funds( // construct a packet to send let msg = IbcMsg::Transfer { channel_id: transfer_channel_id, - to_address: remote_addr.into(), + to_address: remote_addr, amount, timeout_block: None, timeout_timestamp: Some(build_timeout_timestamp(&env.block)), diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index 2c50686e4f..b15b96387d 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -170,14 +170,13 @@ fn acknowledge_who_am_i( }) } }; - let checked_account = deps.api.addr_validate(&account)?; accounts(deps.storage).update(caller.as_bytes(), |acct| -> StdResult<_> { match acct { Some(mut acct) => { // set the account the first time if acct.remote_addr.is_none() { - acct.remote_addr = Some(checked_account); + acct.remote_addr = Some(account); } Ok(acct) } @@ -210,22 +209,21 @@ fn acknowledge_balances( }) } }; - let checked_account = deps.api.addr_validate(&account)?; accounts(deps.storage).update(caller.as_bytes(), |acct| -> StdResult<_> { match acct { Some(acct) => { if let Some(old_addr) = acct.remote_addr { - if old_addr != checked_account { + if old_addr != account { return Err(StdError::generic_err(format!( "remote account changed from {} to {}", - old_addr, checked_account + old_addr, account ))); } } Ok(AccountData { last_update_time: env.block.time, - remote_addr: Some(checked_account), + remote_addr: Some(account), remote_balance: balances, }) } diff --git a/contracts/ibc-reflect-send/src/msg.rs b/contracts/ibc-reflect-send/src/msg.rs index e439f73225..2bc47c4309 100644 --- a/contracts/ibc-reflect-send/src/msg.rs +++ b/contracts/ibc-reflect-send/src/msg.rs @@ -74,7 +74,7 @@ impl AccountInfo { AccountInfo { channel_id, last_update_time: input.last_update_time, - remote_addr: input.remote_addr.map(|addr| addr.into()), + remote_addr: input.remote_addr, remote_balance: input.remote_balance, } } @@ -94,7 +94,7 @@ impl From for AccountResponse { fn from(input: AccountData) -> Self { AccountResponse { last_update_time: input.last_update_time, - remote_addr: input.remote_addr.map(|addr| addr.into()), + remote_addr: input.remote_addr, remote_balance: input.remote_balance, } } diff --git a/contracts/ibc-reflect-send/src/state.rs b/contracts/ibc-reflect-send/src/state.rs index decc004be4..7e8e5e165e 100644 --- a/contracts/ibc-reflect-send/src/state.rs +++ b/contracts/ibc-reflect-send/src/state.rs @@ -18,9 +18,12 @@ pub struct Config { pub struct AccountData { /// last block balance was updated (0 is never) pub last_update_time: u64, - /// in normal cases, it should be set, but there is a delay between binding - /// the channel and making a query and in that time it is empty - pub remote_addr: Option, + /// In normal cases, it should be set, but there is a delay between binding + /// the channel and making a query and in that time it is empty. + /// + /// Since we do not have a way to validate the remote address format, this + /// must not be of type `Addr`. + pub remote_addr: Option, pub remote_balance: Vec, } From d0298f22de1a8460735d658a3e3c4d1adb75630c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Apr 2021 15:53:31 +0200 Subject: [PATCH 37/38] Implement From<&Addr> for String and From<&Addr> for HumanAddr --- contracts/ibc-reflect/src/contract.rs | 4 ++-- packages/std/src/addresses.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index 14cbbb0c11..33c9727e03 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -174,7 +174,7 @@ pub fn ibc_channel_close( accounts(deps.storage).remove(channel_id.as_bytes()); // transfer current balance if any (steal the money) - let amount = deps.querier.query_all_balances(reflect_addr.clone())?; + let amount = deps.querier.query_all_balances(&reflect_addr)?; let messages: Vec> = if !amount.is_empty() { let bank_msg = BankMsg::Send { to_address: env.contract.address.into(), @@ -284,7 +284,7 @@ fn receive_who_am_i(deps: DepsMut, caller: String) -> StdResult StdResult { let account = accounts(deps.storage).load(caller.as_bytes())?; - let balances = deps.querier.query_all_balances(account.clone())?; + let balances = deps.querier.query_all_balances(&account)?; let response = BalancesResponse { account: account.into(), balances, diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 7260424e03..474ac3f380 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -82,12 +82,24 @@ impl From for String { } } +impl From<&Addr> for String { + fn from(addr: &Addr) -> Self { + addr.0.clone() + } +} + impl From for HumanAddr { fn from(addr: Addr) -> Self { HumanAddr(addr.0) } } +impl From<&Addr> for HumanAddr { + fn from(addr: &Addr) -> Self { + HumanAddr(addr.0.clone()) + } +} + #[deprecated( since = "0.14.0", note = "HumanAddr is not much more than an alias to String and it does not provide significant safety advantages. With CosmWasm 0.14, we now use String when there was HumanAddr before. There is also the new Addr, which holds a validated immutable human readable address." @@ -283,16 +295,30 @@ mod tests { #[test] fn addr_implements_into_string() { + // owned Addr let addr = Addr::unchecked("cos934gh9034hg04g0h134"); let string: String = addr.into(); assert_eq!(string, "cos934gh9034hg04g0h134"); + + // &Addr + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + let addr_ref = &addr; + let string: String = addr_ref.into(); + assert_eq!(string, "cos934gh9034hg04g0h134"); } #[test] fn addr_implements_into_human_address() { + // owned Addr let addr = Addr::unchecked("cos934gh9034hg04g0h134"); let human: HumanAddr = addr.into(); assert_eq!(human, "cos934gh9034hg04g0h134"); + + // &Addr + let addr = Addr::unchecked("cos934gh9034hg04g0h134"); + let addr_ref = &addr; + let human: HumanAddr = addr_ref.into(); + assert_eq!(human, "cos934gh9034hg04g0h134"); } // Test HumanAddr as_str() for each HumanAddr::from input type From e5271a930b51c1bd7e0938013f922a0f2eaf31ef Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 7 Apr 2021 16:33:58 +0200 Subject: [PATCH 38/38] Add doc comment to Addr::unchecked --- packages/std/src/addresses.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 474ac3f380..953866db31 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -27,6 +27,21 @@ use crate::binary::Binary; pub struct Addr(String); impl Addr { + /// Creates a new `Addr` instance from the given input without checking the validity + /// of the input. Since `Addr` must always contain valid addresses, the caller is + /// responsible for ensuring the input is valid. + /// + /// Use this in cases where the address was validated before or in test code. + /// If you see this in contract code, it should most likely be replaced with + /// `let checked: Addr = deps.api.addr_humanize(canonical_addr)?`. + /// + /// ## Examples + /// + /// ``` + /// # use cosmwasm_std::{Addr}; + /// let address = Addr::unchecked("foobar"); + /// assert_eq!(address, "foobar"); + /// ``` pub fn unchecked>(input: T) -> Addr { Addr(input.into()) }