Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving address generation #83

Merged
merged 23 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions src/addresses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//! # Implementation of address generators

use crate::error::AnyResult;
use crate::prefixed_storage::prefixed_read;
use crate::wasm::{CONTRACTS, NAMESPACE_WASM};
use cosmwasm_std::{Addr, Api, CanonicalAddr, HexBinary, Order, Storage};

/// Common address generator interface.
///
/// The default implementation of this trait generates fully predictable
/// addresses, no matter if [contract_address](AddressGenerator::contract_address)
/// or [predictable_contract_address](AddressGenerator::predictable_contract_address) is used,
/// but users should not make any assumptions about the value of the generated address.
pub trait AddressGenerator {
#[deprecated(
since = "0.18.0",
note = "use `contract_address` or `predictable_contract_address` instead; will be removed in version 1.0.0"
)]
fn next_address(&self, storage: &mut dyn Storage) -> Addr {
//TODO After removing this function in version 1.0, make `CONTRACTS` and `NAMESPACE_WASM` private in `wasm.rs`.
let count = CONTRACTS
.range_raw(
&prefixed_read(storage, NAMESPACE_WASM),
None,
None,
Order::Ascending,
)
.count();
Addr::unchecked(format!("contract{}", count))
}

/// Generates a _non-predictable_ contract address, just like the real-life chain
/// returns contract address after its instantiation.
/// Address generated by this function is returned as a result of processing
/// `WasmMsg::Instantiate` message.
///
/// The default implementation generates a contract address based
/// on contract's instance identifier only.
///
/// # Example
///
/// ```
/// # use cosmwasm_std::testing::{MockApi, MockStorage};
/// # use cw_multi_test::{AddressGenerator, SimpleAddressGenerator};
/// # let api = MockApi::default();
/// # let mut storage = MockStorage::default();
/// struct MyAddressGenerator;
///
/// impl AddressGenerator for MyAddressGenerator {}
///
/// let my_address_generator = MyAddressGenerator{};
///
/// let addr = my_address_generator.contract_address(&api, &mut storage, 100, 0).unwrap();
/// assert_eq!(addr.to_string(),"contract0");
///
/// let addr = my_address_generator.contract_address(&api, &mut storage, 100, 1).unwrap();
/// assert_eq!(addr.to_string(),"contract1");
///
/// let addr = my_address_generator.contract_address(&api, &mut storage, 200, 5).unwrap();
/// assert_eq!(addr.to_string(),"contract5");
///
/// let addr = my_address_generator.contract_address(&api, &mut storage, 200, 6).unwrap();
/// assert_eq!(addr.to_string(),"contract6");
/// ```
fn contract_address(
&self,
_api: &dyn Api,
_storage: &mut dyn Storage,
_code_id: u64,
instance_id: u64,
) -> AnyResult<Addr> {
Ok(Addr::unchecked(format!("contract{instance_id}")))
}

/// Generates a _predictable_ contract address, just like the real-life chain
/// returns contract address after its instantiation using `MsgInstantiateContract2` message.
/// Address generated by this function is returned as a result of processing
/// `WasmMsg::Instantiate2` message.
///
/// The default implementation generates a contract address based on provided salt only.
///
/// # Example
///
/// ```
/// # use cosmwasm_std::Api;
/// # use cosmwasm_std::testing::{MockApi, MockStorage};
/// # use cw_multi_test::{AddressGenerator, SimpleAddressGenerator};
/// # let api = MockApi::default();
/// # let mut storage = MockStorage::default();
/// # let creator = api.addr_canonicalize("creator").unwrap();
/// struct MyAddressGenerator;
///
/// impl AddressGenerator for MyAddressGenerator {}
///
/// let my_address_generator = MyAddressGenerator{};
///
/// let addr = my_address_generator.predictable_contract_address(&api, &mut storage, 100, 0, &[0], &creator, &[0]).unwrap();
/// assert_eq!(addr.to_string(),"contract00");
///
/// let addr = my_address_generator.predictable_contract_address(&api, &mut storage, 100, 1, &[1], &creator, &[0]).unwrap();
/// assert_eq!(addr.to_string(),"contract00");
///
/// let addr = my_address_generator.predictable_contract_address(&api, &mut storage, 200, 0, &[2], &creator, &[1]).unwrap();
/// assert_eq!(addr.to_string(),"contract01");
///
/// let addr = my_address_generator.predictable_contract_address(&api, &mut storage, 200, 1, &[3], &creator, &[1]).unwrap();
/// assert_eq!(addr.to_string(),"contract01");
/// ```
fn predictable_contract_address(
&self,
_api: &dyn Api,
_storage: &mut dyn Storage,
_code_id: u64,
_instance_id: u64,
_checksum: &[u8],
_creator: &CanonicalAddr,
salt: &[u8],
) -> AnyResult<Addr> {
Ok(Addr::unchecked(format!(
"contract{}",
HexBinary::from(salt).to_hex()
)))
}
}

/// Default contract address generator used in [WasmKeeper](crate::WasmKeeper).
pub struct SimpleAddressGenerator;

impl AddressGenerator for SimpleAddressGenerator {}
7 changes: 7 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub enum Error {

#[error("code id {0}: no such code")]
UnregisteredCodeId(u64),

#[error("Contract with this address already exists: {0}")]
DuplicatedContractAddress(String),
}

impl Error {
Expand All @@ -47,4 +50,8 @@ impl Error {
pub fn event_type_too_short(ty: impl Into<String>) -> Self {
Self::EventTypeTooShort(ty.into())
}

pub fn duplicated_contract_address(address: impl Into<String>) -> Self {
Self::DuplicatedContractAddress(address.into())
}
}
34 changes: 34 additions & 0 deletions src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,40 @@ where
Ok(Addr::unchecked(data.contract_address))
}

/// Instantiates a new contract and returns its predictable address.
/// This is a helper function around [execute][Self::execute] function
/// with `WasmMsg::Instantiate2` message.
#[cfg(feature = "cosmwasm_1_2")]
fn instantiate2_contract<M, L, A, S>(
&mut self,
code_id: u64,
sender: Addr,
init_msg: &M,
funds: &[Coin],
label: L,
admin: A,
salt: S,
) -> AnyResult<Addr>
where
M: Serialize,
L: Into<String>,
A: Into<Option<String>>,
S: Into<Binary>,
{
let msg = WasmMsg::Instantiate2 {
admin: admin.into(),
code_id,
msg: to_binary(init_msg)?,
funds: funds.to_vec(),
label: label.into(),
salt: salt.into(),
};
let execute_response = self.execute(sender, msg.into())?;
let instantiate_response =
parse_instantiate_response_data(execute_response.data.unwrap_or_default().as_slice())?;
Ok(Addr::unchecked(instantiate_response.contract_address))
}

/// Execute a contract and process all returned messages.
/// This is just a helper around execute(),
/// but we parse out the data field to that what is returned by the contract (not the protobuf wrapper)
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//!
//! To understand the design of this module, please refer to `../DESIGN.md`

mod addresses;
mod app;
mod app_builder;
mod bank;
Expand All @@ -25,6 +26,7 @@ mod tests;
mod transactions;
mod wasm;

pub use crate::addresses::{AddressGenerator, SimpleAddressGenerator};
pub use crate::app::{custom_app, next_block, App, BasicApp, CosmosRouter, Router, SudoMsg};
pub use crate::app_builder::{AppBuilder, BasicAppBuilder};
pub use crate::bank::{Bank, BankKeeper, BankSudo};
Expand All @@ -37,4 +39,4 @@ pub use crate::module::{FailingModule, Module};
pub use crate::staking::{
Distribution, DistributionKeeper, StakeKeeper, Staking, StakingInfo, StakingSudo,
};
pub use crate::wasm::{AddressGenerator, ContractData, Wasm, WasmKeeper, WasmSudo};
pub use crate::wasm::{ContractData, Wasm, WasmKeeper, WasmSudo};
21 changes: 4 additions & 17 deletions src/tests/test_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1573,30 +1573,17 @@ mod contract_instantiation {
msg: init_msg,
funds: vec![],
label: "label".into(),
salt: [0, 1, 2, 3, 4, 5].as_slice().into(),
salt: [1, 2, 3, 4, 5, 6].as_slice().into(),
};
let res = app.execute(sender, msg.into()).unwrap();

// assert a proper instantiate result
let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap();
assert!(parsed.data.is_none());

// assert contract's address is exactly the predicted one
//
// REMARK:
// Currently implemented address generator is used to generate
// the predictable address of newly instantiated contract.
//
// Conceptually, the address of the contract is fully predictable,
// because it is just the contract0 for the first instance,
// contract1 for the second and so forth.
//
// Comparing this address to real-life blockchain and the implementation
// of cosmwasm_std::instantiate2_address, this approach is totally incompatible.
// This problem will be handled in the next step, please see:
// https://github.com/CosmWasm/cosmwasm/issues/1873
// for details.
assert_eq!("contract0", parsed.contract_address);
// assert contract's address is exactly the predicted one,
// in default address generator, this is like `contract` + salt in hex
assert_eq!(parsed.contract_address, "contract010203040506");
}
}

Expand Down
Loading