diff --git a/cw-orch-daemon/src/core.rs b/cw-orch-daemon/src/core.rs index c0f3f4814..f6b518311 100644 --- a/cw-orch-daemon/src/core.rs +++ b/cw-orch-daemon/src/core.rs @@ -11,16 +11,18 @@ use super::{ use cosmrs::{ cosmwasm::{MsgExecuteContract, MsgInstantiateContract, MsgMigrateContract}, + proto::cosmwasm::wasm::v1::MsgInstantiateContract2, tendermint::Time, - AccountId, Denom, + AccountId, Any, Denom, }; -use cosmwasm_std::{Addr, Coin}; +use cosmwasm_std::{Addr, Binary, Coin}; use cw_orch_core::{ contract::interface_traits::Uploadable, environment::{ChainState, IndexResponse}, log::transaction_target, }; use flate2::{write, Compression}; +use prost::Message; use serde::{de::DeserializeOwned, Serialize}; use serde_json::from_str; use std::{ @@ -162,6 +164,44 @@ impl DaemonAsync { Ok(result) } + /// Instantiate a contract. + pub async fn instantiate2( + &self, + code_id: u64, + init_msg: &I, + label: Option<&str>, + admin: Option<&Addr>, + coins: &[Coin], + salt: Binary, + ) -> Result { + let sender = &self.sender; + + let init_msg = MsgInstantiateContract2 { + code_id, + label: label.unwrap_or("instantiate_contract").to_string(), + admin: admin.map(Into::into).unwrap_or_default(), + sender: sender.address()?.to_string(), + msg: serde_json::to_vec(&init_msg)?, + funds: proto_parse_cw_coins(coins)?, + salt: salt.to_vec(), + fix_msg: false, + }; + + let result = sender + .commit_tx_any( + vec![Any { + type_url: "/cosmwasm.wasm.v1.MsgInstantiateContract2".to_string(), + value: init_msg.encode_to_vec(), + }], + None, + ) + .await?; + + log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash); + + Ok(result) + } + /// Query a contract. pub async fn query( &self, @@ -299,3 +339,17 @@ pub(crate) fn parse_cw_coins( }) .collect::, DaemonError>>() } + +pub(crate) fn proto_parse_cw_coins( + coins: &[cosmwasm_std::Coin], +) -> Result, DaemonError> { + coins + .iter() + .map(|cosmwasm_std::Coin { amount, denom }| { + Ok(cosmrs::proto::cosmos::base::v1beta1::Coin { + amount: amount.to_string(), + denom: denom.clone(), + }) + }) + .collect::, DaemonError>>() +} diff --git a/cw-orch-daemon/src/error.rs b/cw-orch-daemon/src/error.rs index fc1e163cd..8319a3cfc 100644 --- a/cw-orch-daemon/src/error.rs +++ b/cw-orch-daemon/src/error.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] -use cosmwasm_std::Coin; +use cosmwasm_std::{Coin, Instantiate2AddressError}; use cw_orch_core::CwEnvError; use thiserror::Error; @@ -118,6 +118,8 @@ pub enum DaemonError { NotEnoughBalance { expected: Coin, current: Coin }, #[error("Can't set the daemon state, it's read-only")] StateReadOnly, + #[error(transparent)] + Instantiate2Error(#[from] Instantiate2AddressError), } impl DaemonError { diff --git a/cw-orch-daemon/src/queriers/cosmwasm.rs b/cw-orch-daemon/src/queriers/cosmwasm.rs index 4fe014791..24ac2e2a5 100644 --- a/cw-orch-daemon/src/queriers/cosmwasm.rs +++ b/cw-orch-daemon/src/queriers/cosmwasm.rs @@ -1,12 +1,17 @@ +use std::str::FromStr; + +use super::DaemonQuerier; use crate::{cosmos_modules, error::DaemonError, Daemon}; use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; -use cosmwasm_std::{from_json, to_json_binary, CodeInfoResponse, ContractInfoResponse}; +use cosmrs::AccountId; +use cosmwasm_std::{ + from_json, instantiate2_address, to_json_binary, CanonicalAddr, CodeInfoResponse, + ContractInfoResponse, +}; use cw_orch_core::environment::{Querier, QuerierGetter, WasmQuerier}; use tokio::runtime::Handle; use tonic::transport::Channel; -use super::DaemonQuerier; - /// Querier for the CosmWasm SDK module pub struct CosmWasm { channel: Channel, @@ -265,4 +270,20 @@ impl WasmQuerier for DaemonWasmQuerier { Ok(c) } + + fn instantiate2_addr( + &self, + code_id: u64, + creator: impl Into, + salt: cosmwasm_std::Binary, + ) -> Result { + let creator_str = creator.into(); + let account_id = AccountId::from_str(&creator_str)?; + let prefix = account_id.prefix(); + let canon = account_id.to_bytes(); + let checksum = self.code_id_hash(code_id)?; + let addr = instantiate2_address(checksum.as_bytes(), &CanonicalAddr(canon.into()), &salt)?; + + Ok(AccountId::new(prefix, &addr.0)?.to_string()) + } } diff --git a/cw-orch-daemon/src/sync/core.rs b/cw-orch-daemon/src/sync/core.rs index c12800e7d..9fe25920d 100644 --- a/cw-orch-daemon/src/sync/core.rs +++ b/cw-orch-daemon/src/sync/core.rs @@ -146,6 +146,21 @@ impl TxHandler for Daemon { .migrate(migrate_msg, new_code_id, contract_address), ) } + + fn instantiate2( + &self, + code_id: u64, + init_msg: &I, + label: Option<&str>, + admin: Option<&Addr>, + coins: &[cosmwasm_std::Coin], + salt: cosmwasm_std::Binary, + ) -> Result { + self.rt_handle.block_on( + self.daemon + .instantiate2(code_id, init_msg, label, admin, coins, salt), + ) + } } impl Stargate for Daemon { diff --git a/cw-orch-daemon/tests/instantiate2.rs b/cw-orch-daemon/tests/instantiate2.rs new file mode 100644 index 000000000..9e7b6dd38 --- /dev/null +++ b/cw-orch-daemon/tests/instantiate2.rs @@ -0,0 +1,36 @@ +mod common; + +#[cfg(feature = "node-tests")] +pub mod test { + + use cosmwasm_std::Binary; + use cw_orch_core::contract::interface_traits::ContractInstance; + use cw_orch_core::contract::interface_traits::CwOrchInstantiate; + use cw_orch_core::contract::interface_traits::CwOrchUpload; + use cw_orch_daemon::Daemon; + use cw_orch_networks::networks; + use mock_contract::InstantiateMsg; + use mock_contract::MockContract; + + #[test] + fn instantiate2() -> anyhow::Result<()> { + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let app = Daemon::builder() + .chain(networks::LOCAL_JUNO) + .handle(runtime.handle()) + .build() + .unwrap(); + + let salt = Binary(vec![12, 89, 156, 63]); + let mock_contract = MockContract::new("mock-contract", app.clone()); + + mock_contract.upload()?; + + mock_contract.instantiate2(&InstantiateMsg {}, None, None, salt.clone())?; + + mock_contract.address()?; + + Ok(()) + } +} diff --git a/cw-orch/src/osmosis_test_tube/core.rs b/cw-orch/src/osmosis_test_tube/core.rs index 6546e372e..8fb5fa320 100644 --- a/cw-orch/src/osmosis_test_tube/core.rs +++ b/cw-orch/src/osmosis_test_tube/core.rs @@ -267,6 +267,18 @@ impl TxHandler for OsmosisTestTube { ) -> Result { panic!("Migrate not implemented on osmosis test_tube") } + + fn instantiate2( + &self, + _code_id: u64, + _init_msg: &I, + _label: Option<&str>, + _admin: Option<&Addr>, + _coins: &[cosmwasm_std::Coin], + _salt: Binary, + ) -> Result { + unimplemented!("Osmosis Test Tube doesn't support Instantiate 2 directly"); + } } impl BankSetter for OsmosisTestTube { diff --git a/cw-orch/src/osmosis_test_tube/queriers/wasm.rs b/cw-orch/src/osmosis_test_tube/queriers/wasm.rs index d1c416e0e..731e992de 100644 --- a/cw-orch/src/osmosis_test_tube/queriers/wasm.rs +++ b/cw-orch/src/osmosis_test_tube/queriers/wasm.rs @@ -1,6 +1,10 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc, str::FromStr}; -use cosmwasm_std::{from_json, to_json_vec, CodeInfoResponse, ContractInfoResponse}; +use cosmrs::AccountId; +use cosmwasm_std::{ + from_json, instantiate2_address, to_json_vec, CanonicalAddr, CodeInfoResponse, + ContractInfoResponse, +}; use cw_orch_core::{ environment::{Querier, QuerierGetter, StateInterface, WasmQuerier}, CwEnvError, @@ -152,4 +156,22 @@ impl WasmQuerier for OsmosisTestTubeWasmQuerier { Ok(c) } + + fn instantiate2_addr( + &self, + code_id: u64, + creator: impl Into, + salt: cosmwasm_std::Binary, + ) -> Result { + let checksum = self.code_id_hash(code_id)?; + + let creator_str = creator.into(); + let account_id = AccountId::from_str(&creator_str).unwrap(); + let prefix = account_id.prefix(); + let canon = account_id.to_bytes(); + let addr = + instantiate2_address(checksum.as_bytes(), &CanonicalAddr(canon.into()), &salt).unwrap(); + + Ok(AccountId::new(prefix, &addr.0).unwrap().to_string()) + } } diff --git a/packages/cw-orch-core/src/contract/contract_instance.rs b/packages/cw-orch-core/src/contract/contract_instance.rs index 44f54292f..8e2fdee9d 100644 --- a/packages/cw-orch-core/src/contract/contract_instance.rs +++ b/packages/cw-orch-core/src/contract/contract_instance.rs @@ -8,7 +8,7 @@ use crate::{ }; use crate::environment::QueryHandler; -use cosmwasm_std::{Addr, Coin}; +use cosmwasm_std::{Addr, Binary, Coin}; use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; @@ -171,6 +171,58 @@ impl Contract { Ok(resp) } + /// Initializes the contract + pub fn instantiate2( + &self, + msg: &I, + admin: Option<&Addr>, + coins: Option<&[Coin]>, + salt: Binary, + ) -> Result, CwEnvError> { + log::info!( + target: &contract_target(), + "[{}][Instantiate]", + self.id, + ); + + log::debug!( + target: &contract_target(), + "[{}][Instantiate] {}", + self.id, + log_serialize_message(msg)? + ); + + let resp = self + .chain + .instantiate2( + self.code_id()?, + msg, + Some(&self.id), + admin, + coins.unwrap_or(&[]), + salt, + ) + .map_err(Into::into)?; + let contract_address = resp.instantiated_contract_address()?; + + self.set_address(&contract_address); + + log::info!( + target: &&contract_target(), + "[{}][Instantiated] {}", + self.id, + contract_address + ); + log::debug!( + target: &&transaction_target(), + "[{}][Instantiated] response: {:?}", + self.id, + resp + ); + + Ok(resp) + } + /// Query the contract pub fn query( &self, diff --git a/packages/cw-orch-core/src/contract/interface_traits.rs b/packages/cw-orch-core/src/contract/interface_traits.rs index 43954be50..292604401 100644 --- a/packages/cw-orch-core/src/contract/interface_traits.rs +++ b/packages/cw-orch-core/src/contract/interface_traits.rs @@ -4,7 +4,7 @@ use crate::{ error::CwEnvError, log::contract_target, }; -use cosmwasm_std::{Addr, Coin, Empty}; +use cosmwasm_std::{Addr, Binary, Coin, Empty}; use cw_multi_test::Contract as MockContract; use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; @@ -118,6 +118,18 @@ pub trait CwOrchInstantiate: InstantiableContract + ContractInstan self.as_instance() .instantiate(instantiate_msg, admin, coins) } + + /// Instantiates the contract using instantiate2 + fn instantiate2( + &self, + instantiate_msg: &Self::InstantiateMsg, + admin: Option<&Addr>, + coins: Option<&[Coin]>, + salt: Binary, + ) -> Result { + self.as_instance() + .instantiate2(instantiate_msg, admin, coins, salt) + } } impl, Chain: CwEnv> CwOrchInstantiate diff --git a/packages/cw-orch-core/src/environment/cosmwasm_environment.rs b/packages/cw-orch-core/src/environment/cosmwasm_environment.rs index 912723fc6..3d554bdff 100644 --- a/packages/cw-orch-core/src/environment/cosmwasm_environment.rs +++ b/packages/cw-orch-core/src/environment/cosmwasm_environment.rs @@ -2,7 +2,7 @@ use super::{queriers::QueryHandler, ChainState, IndexResponse}; use crate::{contract::interface_traits::Uploadable, error::CwEnvError}; -use cosmwasm_std::{Addr, Coin}; +use cosmwasm_std::{Addr, Binary, Coin}; use serde::Serialize; use std::fmt::Debug; @@ -46,6 +46,17 @@ pub trait TxHandler: ChainState + Clone { coins: &[cosmwasm_std::Coin], ) -> Result; + /// Send a Instantiate2Msg to a contract. + fn instantiate2( + &self, + code_id: u64, + init_msg: &I, + label: Option<&str>, + admin: Option<&Addr>, + coins: &[cosmwasm_std::Coin], + salt: Binary, + ) -> Result; + /// Send a ExecMsg to a contract. fn execute( &self, @@ -169,6 +180,18 @@ mod tests { ) -> Result { unimplemented!() } + + fn instantiate2( + &self, + _code_id: u64, + _init_msg: &I, + _label: Option<&str>, + _admin: Option<&Addr>, + _coins: &[cosmwasm_std::Coin], + _salt: Binary, + ) -> Result { + unimplemented!() + } } fn associated_error(t: T) -> anyhow::Result<()> { diff --git a/packages/cw-orch-core/src/environment/queriers/wasm.rs b/packages/cw-orch-core/src/environment/queriers/wasm.rs index c765a2c84..d86d5dee6 100644 --- a/packages/cw-orch-core/src/environment/queriers/wasm.rs +++ b/packages/cw-orch-core/src/environment/queriers/wasm.rs @@ -45,4 +45,11 @@ pub trait WasmQuerier: Querier { ) -> Result { contract.wasm().checksum() } + + fn instantiate2_addr( + &self, + code_id: u64, + creator: impl Into, + salt: cosmwasm_std::Binary, + ) -> Result; } diff --git a/packages/cw-orch-mock/src/core.rs b/packages/cw-orch-mock/src/core.rs index 068e534c1..d92dd623f 100644 --- a/packages/cw-orch-mock/src/core.rs +++ b/packages/cw-orch-mock/src/core.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, fmt::Debug, rc::Rc}; use cosmwasm_std::{ testing::{MockApi, MockStorage}, - Addr, Coin, Empty, Event, Uint128, + to_json_binary, Addr, Binary, Coin, CosmosMsg, Empty, Event, Uint128, WasmMsg, }; use cw_multi_test::{ ibc::IbcSimpleModule, App, AppBuilder, AppResponse, BankKeeper, Contract, DistributionKeeper, @@ -281,20 +281,51 @@ impl TxHandler for Mock { admin: Option<&Addr>, coins: &[cosmwasm_std::Coin], ) -> Result { - let addr = self.app.borrow_mut().instantiate_contract( + let msg = WasmMsg::Instantiate { + admin: admin.map(|a| a.to_string()), code_id, - self.sender.clone(), - init_msg, - coins, - label.unwrap_or("contract_init"), - admin.map(|a| a.to_string()), - )?; - // add contract address to events manually - let mut event = Event::new("instantiate"); - event = event.add_attribute("_contract_address", addr); + label: label.unwrap_or("contract_init").to_string(), + msg: to_json_binary(init_msg)?, + funds: coins.to_vec(), + }; + let app = self + .app + .borrow_mut() + .execute(self.sender.clone(), CosmosMsg::Wasm(msg))?; + let resp = AppResponse { - events: vec![event], - ..Default::default() + events: app.events, + data: app.data, + }; + Ok(resp) + } + + fn instantiate2( + &self, + code_id: u64, + init_msg: &I, + label: Option<&str>, + admin: Option<&Addr>, + coins: &[cosmwasm_std::Coin], + salt: Binary, + ) -> Result { + let msg = WasmMsg::Instantiate2 { + admin: admin.map(|a| a.to_string()), + code_id, + label: label.unwrap_or("contract_init").to_string(), + msg: to_json_binary(init_msg)?, + funds: coins.to_vec(), + salt, + }; + + let app = self + .app + .borrow_mut() + .execute(self.sender.clone(), CosmosMsg::Wasm(msg))?; + + let resp = AppResponse { + events: app.events, + data: app.data, }; Ok(resp) } diff --git a/packages/cw-orch-mock/src/queriers/wasm.rs b/packages/cw-orch-mock/src/queriers/wasm.rs index 9db735b45..49718137c 100644 --- a/packages/cw-orch-mock/src/queriers/wasm.rs +++ b/packages/cw-orch-mock/src/queriers/wasm.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, rc::Rc}; -use cosmwasm_std::{to_json_binary, ContractInfoResponse, Empty}; -use cw_multi_test::BasicApp; +use cosmwasm_std::{to_json_binary, ContractInfoResponse, Empty, HexBinary}; +use cw_multi_test::{AddressGenerator, BasicApp}; use cw_orch_core::{ contract::interface_traits::{ContractInstance, Uploadable}, environment::{Querier, QuerierGetter, QueryHandler, StateInterface, TxHandler, WasmQuerier}, @@ -104,4 +104,15 @@ impl WasmQuerier for MockWasmQuerier { cosmwasm_std::WasmQuery::CodeInfo { code_id }, ))?) } + + fn instantiate2_addr( + &self, + _code_id: u64, + _creator: impl Into, + salt: cosmwasm_std::Binary, + ) -> Result { + Ok(format!("contract{}", HexBinary::from(salt).to_hex())) + } } + +impl AddressGenerator for MockWasmQuerier {} diff --git a/packages/cw-orch-mock/tests/instantiate2.rs b/packages/cw-orch-mock/tests/instantiate2.rs index de764409d..fc210bd38 100644 --- a/packages/cw-orch-mock/tests/instantiate2.rs +++ b/packages/cw-orch-mock/tests/instantiate2.rs @@ -1,12 +1,9 @@ -use cosmwasm_std::to_json_binary; use cosmwasm_std::Binary; use cosmwasm_std::HexBinary; -use cw_multi_test::Executor; use cw_orch_core::contract::interface_traits::ContractInstance; +use cw_orch_core::contract::interface_traits::CwOrchInstantiate; use cw_orch_core::contract::interface_traits::CwOrchUpload; -use cw_orch_core::environment::TxHandler; use cw_orch_mock::Mock; -use cw_utils::parse_instantiate_response_data; use mock_contract::InstantiateMsg; use mock_contract::MockContract; @@ -14,28 +11,17 @@ use mock_contract::MockContract; fn instantiate2() -> anyhow::Result<()> { let app = Mock::new("sender"); + let salt = Binary(vec![12, 89, 156, 63]); let mock_contract = MockContract::new("mock-contract", app.clone()); mock_contract.upload()?; - let salt = Binary(vec![12, 89, 156, 63]); - - let execution_response = app.app.borrow_mut().execute( - app.sender(), - cosmwasm_std::CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Instantiate2 { - admin: None, - code_id: mock_contract.code_id()?, - label: "Weird label".to_string(), - msg: to_json_binary(&InstantiateMsg {})?, - funds: vec![], - salt: salt.clone(), - }), - )?; + mock_contract.instantiate2(&InstantiateMsg {}, None, None, salt.clone())?; - let addr = parse_instantiate_response_data(&execution_response.data.unwrap())?; + let addr = mock_contract.address()?; assert_eq!( - addr.contract_address, + addr.to_string(), format!("contract/sender/{}", HexBinary::from(salt).to_hex()) );