From 7840fa04fb7b4790e93bf2f85f06e0e2f6c5daf8 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 9 Apr 2021 01:16:51 +0200 Subject: [PATCH] Make Addr::unchecked a const fn --- contracts/hackatom/src/contract.rs | 10 +-- contracts/reflect/src/contract.rs | 4 +- contracts/reflect/src/testing.rs | 2 +- contracts/reflect/tests/integration.rs | 2 +- contracts/staking/src/contract.rs | 2 +- packages/std/src/addresses.rs | 105 +++++++++++++++++++++---- packages/std/src/imports.rs | 2 +- packages/std/src/mock.rs | 12 +-- packages/vm/src/testing/instance.rs | 2 +- packages/vm/src/testing/mock.rs | 10 +-- 10 files changed, 111 insertions(+), 40 deletions(-) diff --git a/contracts/hackatom/src/contract.rs b/contracts/hackatom/src/contract.rs index 49b5a2bc8d..44280e7fd4 100644 --- a/contracts/hackatom/src/contract.rs +++ b/contracts/hackatom/src/contract.rs @@ -92,7 +92,7 @@ fn do_release(deps: DepsMut, env: Env, info: MessageInfo) -> Result StdResult> { - let state = State { - owner: info.sender.clone(), - }; + let state = State { owner: info.sender }; config(deps.storage).save(&state)?; let mut resp = Response::new(); diff --git a/contracts/reflect/src/testing.rs b/contracts/reflect/src/testing.rs index 32ccedaafc..1dd73a8f89 100644 --- a/contracts/reflect/src/testing.rs +++ b/contracts/reflect/src/testing.rs @@ -9,7 +9,7 @@ pub fn mock_dependencies_with_custom_querier( contract_balance: &[Coin], ) -> OwnedDeps> { let custom_querier: MockQuerier = - MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]) + MockQuerier::new(&[(MOCK_CONTRACT_ADDR.as_str(), 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 775ae7f03f..e5ef96c92c 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -46,7 +46,7 @@ pub fn mock_dependencies_with_custom_querier( contract_balance: &[Coin], ) -> Backend> { let custom_querier: MockQuerier = - MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]) + MockQuerier::new(&[(MOCK_CONTRACT_ADDR.as_str(), 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 d087a80ccc..10b88f5924 100644 --- a/contracts/staking/src/contract.rs +++ b/contracts/staking/src/contract.rs @@ -462,7 +462,7 @@ mod tests { let can_redelegate = amount.clone(); FullDelegation { validator: Addr::unchecked(validator_addr), - delegator: Addr::unchecked(MOCK_CONTRACT_ADDR), + delegator: MOCK_CONTRACT_ADDR, amount, can_redelegate, accumulated_rewards: Vec::new(), diff --git a/packages/std/src/addresses.rs b/packages/std/src/addresses.rs index 953866db31..82053dc394 100644 --- a/packages/std/src/addresses.rs +++ b/packages/std/src/addresses.rs @@ -1,9 +1,10 @@ #![allow(deprecated)] use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::{de, ser, Deserialize, Deserializer, Serialize}; use std::fmt; use std::ops::Deref; +use std::str; use crate::binary::Binary; @@ -23,8 +24,18 @@ 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, JsonSchema)] -pub struct Addr(String); +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] +pub struct Addr(#[schemars(with = "String")] AddrInner); + +/// The maximum allowed size for bech32 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) +const ADDR_MAX_LENGTH: usize = 90; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +struct AddrInner { + /// UTF-8 encoded human readable string + data: [u8; ADDR_MAX_LENGTH], + length: usize, +} impl Addr { /// Creates a new `Addr` instance from the given input without checking the validity @@ -42,49 +53,111 @@ impl Addr { /// let address = Addr::unchecked("foobar"); /// assert_eq!(address, "foobar"); /// ``` - pub fn unchecked>(input: T) -> Addr { - Addr(input.into()) + pub const fn unchecked(input: &str) -> Addr { + let input_bytes = input.as_bytes(); + let mut data = [0u8; ADDR_MAX_LENGTH]; + let mut i = 0; + while i < input_bytes.len() { + data[i] = input_bytes[i]; + i += 1; + } + + Addr(AddrInner { + data, + length: input_bytes.len(), + }) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0.data[..self.0.length] + } + + pub fn as_str(&self) -> &str { + unsafe { str::from_utf8_unchecked(self.as_bytes()) } } } impl fmt::Display for Addr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.0) + write!(f, "{}", self.as_str()) + } +} + +impl Serialize for Addr { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&self.as_str()) + } +} + +impl<'de> Deserialize<'de> for Addr { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(AddrVisitor) + } +} + +struct AddrVisitor; + +impl<'de> de::Visitor<'de> for AddrVisitor { + type Value = Addr; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Human readable address string") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + if v.len() > ADDR_MAX_LENGTH { + Err(E::custom(format!( + "Input too long to be stored in an Addr. Max: {}, actual: {}", + ADDR_MAX_LENGTH, + v.len() + ))) + } else { + Ok(Addr::unchecked(v)) + } } } impl AsRef for Addr { #[inline] fn as_ref(&self) -> &str { - &self.0 + self.as_str() } } /// Implement `Addr == &str` impl PartialEq<&str> for Addr { fn eq(&self, rhs: &&str) -> bool { - self.0 == *rhs + self.as_str() == *rhs } } /// Implement `&str == Addr` impl PartialEq for &str { fn eq(&self, rhs: &Addr) -> bool { - *self == rhs.0 + *self == rhs.as_str() } } /// Implement `Addr == String` impl PartialEq for Addr { fn eq(&self, rhs: &String) -> bool { - &self.0 == rhs + self.as_str() == rhs } } /// Implement `String == Addr` impl PartialEq for String { fn eq(&self, rhs: &Addr) -> bool { - self == &rhs.0 + self == rhs.as_str() } } @@ -93,25 +166,25 @@ impl PartialEq for String { impl From for String { fn from(addr: Addr) -> Self { - addr.0 + addr.as_str().to_owned() } } impl From<&Addr> for String { fn from(addr: &Addr) -> Self { - addr.0.clone() + addr.as_str().to_owned() } } impl From for HumanAddr { fn from(addr: Addr) -> Self { - HumanAddr(addr.0) + HumanAddr(addr.into()) } } impl From<&Addr> for HumanAddr { fn from(addr: &Addr) -> Self { - HumanAddr(addr.0.clone()) + HumanAddr(addr.into()) } } @@ -268,7 +341,7 @@ mod tests { #[test] fn addr_unchecked_works() { let a = Addr::unchecked("123"); - let aa = Addr::unchecked(String::from("123")); + let aa = Addr::unchecked("123"); let b = Addr::unchecked("be"); assert_eq!(a, aa); assert_ne!(a, b); diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index 33aab44a6c..6cb17109ef 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -205,7 +205,7 @@ impl Api for ExternalApi { } let address = unsafe { consume_string_region_written_by_vm(human) }; - Ok(Addr::unchecked(address)) + Ok(Addr::unchecked(&address)) } fn secp256k1_verify( diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 836083f3cb..722b0ae0b4 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -21,7 +21,7 @@ use crate::storage::MemoryStorage; use crate::traits::{Api, Querier, QuerierResult}; use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo}; -pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; +pub const MOCK_CONTRACT_ADDR: Addr = Addr::unchecked("cosmos2contract"); /// All external requirements that can be injected for unit tests. /// It sets the given balance for the contract itself, nothing else @@ -31,7 +31,7 @@ pub fn mock_dependencies( OwnedDeps { storage: MockStorage::default(), api: MockApi::default(), - querier: MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)]), + querier: MockQuerier::new(&[(MOCK_CONTRACT_ADDR.as_str(), contract_balance)]), } } @@ -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(Addr::unchecked(human)) + Ok(Addr::unchecked(&human)) } fn secp256k1_verify( @@ -191,16 +191,16 @@ pub fn mock_env() -> Env { chain_id: "cosmos-testnet-14002".to_string(), }, contract: ContractInfo { - address: Addr::unchecked(MOCK_CONTRACT_ADDR), + address: MOCK_CONTRACT_ADDR, }, } } /// Just set sender and funds for the message. /// This is intended for use in test code only. -pub fn mock_info(sender: &str, funds: &[Coin]) -> MessageInfo { +pub fn mock_info>(sender: S, funds: &[Coin]) -> MessageInfo { MessageInfo { - sender: Addr::unchecked(sender), + sender: Addr::unchecked(sender.as_ref()), funds: funds.to_vec(), } } diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index fb522a06ac..af0017c932 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -135,7 +135,7 @@ pub fn mock_instance_with_options( 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.as_str(), 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 6bdbfbd7df..58b7f21642 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -5,7 +5,7 @@ use super::querier::MockQuerier; use super::storage::MockStorage; use crate::{Backend, BackendApi, BackendError, BackendResult, GasInfo}; -pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; +pub const MOCK_CONTRACT_ADDR: Addr = Addr::unchecked("cosmos2contract"); const GAS_COST_HUMANIZE: u64 = 44; const GAS_COST_CANONICALIZE: u64 = 55; @@ -15,7 +15,7 @@ pub fn mock_backend(contract_balance: &[Coin]) -> Backend Env { chain_id: "cosmos-testnet-14002".to_string(), }, contract: ContractInfo { - address: Addr::unchecked(MOCK_CONTRACT_ADDR), + address: MOCK_CONTRACT_ADDR, }, } } /// Just set sender and funds for the message. /// This is intended for use in test code only. -pub fn mock_info(sender: &str, funds: &[Coin]) -> MessageInfo { +pub fn mock_info>(sender: S, funds: &[Coin]) -> MessageInfo { MessageInfo { - sender: Addr::unchecked(sender), + sender: Addr::unchecked(sender.as_ref()), funds: funds.to_vec(), } }