From 957cfb73b3b745c1179c26855569b2b07e1da5a2 Mon Sep 17 00:00:00 2001 From: Kayanski <44806566+Kayanski@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:06:19 +0200 Subject: [PATCH 1/8] Added public key accessor on wallet (#499) --- CHANGELOG.md | 1 + cw-orch-daemon/src/senders/cosmos.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de4c5b438..bb1cc98c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Adds an `upload_wasm` function to CosmosSender to upload wasm code associated to no Contract structure - Update syn to 2.0 - Added cw-plus orchestrator interface to the repo. Pacing the way for more integrations inside this repository in the future +- Add easier way to get PublicKey for `cw_orch_daemon::Wallet` ### Breaking diff --git a/cw-orch-daemon/src/senders/cosmos.rs b/cw-orch-daemon/src/senders/cosmos.rs index 5a762e1ff..957edc449 100644 --- a/cw-orch-daemon/src/senders/cosmos.rs +++ b/cw-orch-daemon/src/senders/cosmos.rs @@ -22,7 +22,7 @@ use cosmrs::{ crypto::secp256k1::SigningKey, proto::{cosmos::authz::v1beta1::MsgExec, traits::Message}, tendermint::chain::Id, - tx::{self, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo}, + tx::{self, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo, SignerPublicKey}, AccountId, Any, }; use cosmwasm_std::{coin, Addr, Coin}; @@ -124,6 +124,10 @@ impl Wallet { self.options.clone() } + pub fn public_key(&self) -> Option { + self.private_key.get_signer_public_key(&self.secp) + } + /// Replaces the private key that the [CosmosSender] is using with key derived from the provided 24-word mnemonic. /// If you want more control over the derived private key, use [Self::set_private_key] pub fn set_mnemonic(&mut self, mnemonic: impl Into) -> Result<(), DaemonError> { From e590ce825b7ee516637e0dd347ca06d0acbdc215 Mon Sep 17 00:00:00 2001 From: Kayanski <44806566+Kayanski@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:58:28 +0200 Subject: [PATCH 2/8] Changed version for LGPL (#498) * Changed version for LGPL * Typo * Doc nit --- packages/cw-orch-core/Cargo.toml | 2 +- packages/cw-orch-mock/Cargo.toml | 2 +- packages/cw-orch-networks/Cargo.toml | 2 +- packages/cw-orch-traits/Cargo.toml | 2 +- packages/interchain/interchain-core/src/env.rs | 2 +- packages/interchain/interchain-daemon/src/interchain_env.rs | 2 +- packages/macros/cw-orch-contract-derive/Cargo.toml | 2 +- packages/macros/cw-orch-fns-derive/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cw-orch-core/Cargo.toml b/packages/cw-orch-core/Cargo.toml index 9a94b4d80..124e9f7da 100644 --- a/packages/cw-orch-core/Cargo.toml +++ b/packages/cw-orch-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-core" -version = "2.1.0" +version = "2.1.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/packages/cw-orch-mock/Cargo.toml b/packages/cw-orch-mock/Cargo.toml index cee2fec30..6eac3774d 100644 --- a/packages/cw-orch-mock/Cargo.toml +++ b/packages/cw-orch-mock/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-mock" -version = "0.24.1" +version = "0.24.2" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/packages/cw-orch-networks/Cargo.toml b/packages/cw-orch-networks/Cargo.toml index 19e1001e1..50fb6f2df 100644 --- a/packages/cw-orch-networks/Cargo.toml +++ b/packages/cw-orch-networks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-networks" -version = "0.24.1" +version = "0.24.2" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/packages/cw-orch-traits/Cargo.toml b/packages/cw-orch-traits/Cargo.toml index a2d70f0fd..04f176bcd 100644 --- a/packages/cw-orch-traits/Cargo.toml +++ b/packages/cw-orch-traits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-traits" -version = "0.24.0" +version = "0.24.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/packages/interchain/interchain-core/src/env.rs b/packages/interchain/interchain-core/src/env.rs index 0798f90ee..74cfe7f3d 100644 --- a/packages/interchain/interchain-core/src/env.rs +++ b/packages/interchain/interchain-core/src/env.rs @@ -146,7 +146,7 @@ pub trait InterchainEnv: Clone { /// use counter_contract::CounterContract; /// let interchain = MockBech32InterchainEnv::new(vec![("osmosis-1","osmo"),("archway-1","arch")]); /// - /// let all_chains: Vec<&MockBeck32> = interchain.chains().collect(); + /// let all_chains: Vec<&MockBech32> = interchain.chains().collect(); /// /// ``` fn chains<'a>(&'a self) -> impl Iterator diff --git a/packages/interchain/interchain-daemon/src/interchain_env.rs b/packages/interchain/interchain-daemon/src/interchain_env.rs index f4d35171b..5123e33a5 100644 --- a/packages/interchain/interchain-daemon/src/interchain_env.rs +++ b/packages/interchain/interchain-daemon/src/interchain_env.rs @@ -308,7 +308,7 @@ impl DaemonInterchain { /// let src_chain = OSMOSIS_1; /// /// let interchain = DaemonInterchain::new( - /// vec![(src_chain.clone(), None), (dst_chain, None)], + /// vec![src_chain.clone(), dst_chain], /// &ChannelCreationValidator, /// ).unwrap(); /// diff --git a/packages/macros/cw-orch-contract-derive/Cargo.toml b/packages/macros/cw-orch-contract-derive/Cargo.toml index d2661c706..1a6fbcd28 100644 --- a/packages/macros/cw-orch-contract-derive/Cargo.toml +++ b/packages/macros/cw-orch-contract-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-contract-derive" -version = "0.21.0" +version = "0.21.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/packages/macros/cw-orch-fns-derive/Cargo.toml b/packages/macros/cw-orch-fns-derive/Cargo.toml index d625fcebf..116660edb 100644 --- a/packages/macros/cw-orch-fns-derive/Cargo.toml +++ b/packages/macros/cw-orch-fns-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-fns-derive" -version = "0.23.0" +version = "0.23.1" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } From a58d5af52cd541c415e7642cf965e3cb5bc08ad9 Mon Sep 17 00:00:00 2001 From: Mykhailo Donchenko <91957742+Buckram123@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:59:26 +0300 Subject: [PATCH 3/8] Remove polytone dep (#501) * remove polytone dep * format --- .../interchain/interchain-core/Cargo.toml | 2 +- .../interchain-core/src/ack_parser.rs | 58 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/interchain/interchain-core/Cargo.toml b/packages/interchain/interchain-core/Cargo.toml index 1cf68804c..b1b7ae150 100644 --- a/packages/interchain/interchain-core/Cargo.toml +++ b/packages/interchain/interchain-core/Cargo.toml @@ -23,7 +23,7 @@ cw-orch-daemon = { workspace = true, optional = true } futures = "0.3.30" ibc-relayer-types = { workspace = true } log = { workspace = true } -polytone = "1.0.0" +# TODO: polytone = "2.0.0" prost = "0.13.1" serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/packages/interchain/interchain-core/src/ack_parser.rs b/packages/interchain/interchain-core/src/ack_parser.rs index 5466a58ac..f6676738b 100644 --- a/packages/interchain/interchain-core/src/ack_parser.rs +++ b/packages/interchain/interchain-core/src/ack_parser.rs @@ -1,8 +1,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, Binary}; use cw_orch_core::environment::CwEnv; -use polytone::ack::Callback; use prost::Message; +// TODO: when polytone updates to cosmwasm v2 use polytone::ack::Callback; +use polytone_callback::Callback; use crate::{ env::decode_ack_error, @@ -147,3 +148,58 @@ pub mod acknowledgement { } } } + +mod polytone_callback { + use super::*; + + use cosmwasm_std::{SubMsgResponse, Uint64}; + + #[cw_serde] + pub struct ExecutionResponse { + /// The address on the remote chain that executed the messages. + pub executed_by: String, + /// Index `i` corresponds to the result of executing the `i`th + /// message. + pub result: Vec, + } + + #[cw_serde] + pub struct ErrorResponse { + /// The index of the first message who's execution failed. + pub message_index: Uint64, + /// The error that occured executing the message. + pub error: String, + } + + /// Copy of the [polytone::ack::Callback](https://docs.rs/polytone/1.0.0/polytone/ack/index.html#reexport.Callback) + /// But without cosmwasm v1 dependencies + #[cw_serde] + pub enum Callback { + /// Result of executing the requested query, or an error. + /// + /// result[i] corresponds to the i'th query and contains the + /// base64 encoded query response. + Query(Result, ErrorResponse>), + + /// Result of executing the requested messages, or an error. + /// + /// 14/04/23: if a submessage errors the reply handler can see + /// `codespace: wasm, code: 5`, but not the actual error. as a + /// result, we can't return good errors for Execution and this + /// error string will only tell you the error's codespace. for + /// example, an out-of-gas error is code 11 and looks like + /// `codespace: sdk, code: 11`. + Execute(Result), + + /// An error occured that could not be recovered from. The only + /// known way that this can occur is message handling running out + /// of gas, in which case the error will be `codespace: sdk, code: + /// 11`. + /// + /// This error is not named becuase it could also occur due to a + /// panic or unhandled error during message processing. We don't + /// expect this to happen and have carefully written the code to + /// avoid it. + FatalError(String), + } +} From aad7559ea7b890328856190d0f31bba06ece90ad Mon Sep 17 00:00:00 2001 From: Kayanski <44806566+Kayanski@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:01:02 +0200 Subject: [PATCH 4/8] Correct authz capabilities (#495) --- cw-orch-daemon/src/core.rs | 8 ++++---- cw-orch-daemon/src/senders/cosmos.rs | 16 +++++++++++----- cw-orch-daemon/src/senders/tx.rs | 7 +++++++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cw-orch-daemon/src/core.rs b/cw-orch-daemon/src/core.rs index 05273cd6a..f3b7f5fd9 100644 --- a/cw-orch-daemon/src/core.rs +++ b/cw-orch-daemon/src/core.rs @@ -234,7 +234,7 @@ impl DaemonAsyncBase { contract_address: &Addr, ) -> Result { let exec_msg: MsgExecuteContract = MsgExecuteContract { - sender: self.sender().account_id(), + sender: self.sender().msg_sender().map_err(Into::into)?, contract: AccountId::from_str(contract_address.as_str())?, msg: serde_json::to_vec(&exec_msg)?, funds: parse_cw_coins(coins)?, @@ -262,7 +262,7 @@ impl DaemonAsyncBase { code_id, label: Some(label.unwrap_or("instantiate_contract").to_string()), admin: admin.map(|a| FromStr::from_str(a.as_str()).unwrap()), - sender: self.sender().account_id(), + sender: self.sender().msg_sender().map_err(Into::into)?, msg: serde_json::to_vec(&init_msg)?, funds: parse_cw_coins(coins)?, }; @@ -324,7 +324,7 @@ impl DaemonAsyncBase { contract_address: &Addr, ) -> Result { let exec_msg: MsgMigrateContract = MsgMigrateContract { - sender: self.sender().account_id(), + sender: self.sender().msg_sender().map_err(Into::into)?, contract: AccountId::from_str(contract_address.as_str())?, msg: serde_json::to_vec(&migrate_msg)?, code_id: new_code_id, @@ -380,7 +380,7 @@ pub async fn upload_wasm( e.write_all(&file_contents)?; let wasm_byte_code = e.finish()?; let store_msg = cosmrs::cosmwasm::MsgStoreCode { - sender: sender.account_id(), + sender: sender.msg_sender().map_err(Into::into)?, wasm_byte_code, instantiate_permission: access.map(access_config_to_cosmrs).transpose()?, }; diff --git a/cw-orch-daemon/src/senders/cosmos.rs b/cw-orch-daemon/src/senders/cosmos.rs index 957edc449..195957a24 100644 --- a/cw-orch-daemon/src/senders/cosmos.rs +++ b/cw-orch-daemon/src/senders/cosmos.rs @@ -183,11 +183,7 @@ impl Wallet { recipient: &Addr, coins: Vec, ) -> Result { - let acc_id = if let Some(granter) = self.options.authz_granter.as_ref() { - AccountId::from_str(granter.as_str()).unwrap() - } else { - self.account_id() - }; + let acc_id = self.msg_sender()?; let msg_send = MsgSend { from_address: acc_id, @@ -488,6 +484,16 @@ impl TxSender for Wallet { // unwrap as address is validated on construction .unwrap() } + + /// Actual sender of the messages. + /// This is different when using authz capabilites + fn msg_sender(&self) -> Result { + if let Some(sender) = &self.options.authz_granter { + Ok(sender.as_str().parse()?) + } else { + Ok(self.account_id()) + } + } } fn get_mnemonic_env(chain_kind: &ChainKind) -> Result { diff --git a/cw-orch-daemon/src/senders/tx.rs b/cw-orch-daemon/src/senders/tx.rs index 619d78ff9..4309001d0 100644 --- a/cw-orch-daemon/src/senders/tx.rs +++ b/cw-orch-daemon/src/senders/tx.rs @@ -21,6 +21,13 @@ pub trait TxSender: QuerySender { Addr::unchecked(self.account_id().to_string()) } + /// Actual sender of the messages. + /// + /// For example, this can be different when using authz capabilites + fn msg_sender(&self) -> Result { + Ok(self.account_id()) + } + /// Commit a transaction to the chain using this sender. fn commit_tx( &self, From 348dfa0334acc5cee7258daefee40272636c1b99 Mon Sep 17 00:00:00 2001 From: Kayanski <44806566+Kayanski@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:25:30 +0200 Subject: [PATCH 5/8] removed abstract cw20 (#500) * removed abstract cw20 * Removed comment --- Cargo.toml | 4 ++-- contracts-ws/Cargo.toml | 4 ++-- packages/cw-orch-mock/Cargo.toml | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 635e47be8..61bacf1eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,8 +37,8 @@ cosmos-sdk-proto = { version = "0.24.0", default-features = false } cw-multi-test = { package = "abstract-cw-multi-test", version = "2.0.2", features = [ "cosmwasm_1_2", ] } -cw20 = { package = "abstract-cw20", version = "3.0.0" } -cw20-base = { package = "abstract-cw20-base", version = "3.0.0" } +cw20 = { version = "2.0.0" } +cw20-base = { version = "2.0.0" } osmosis-test-tube = { version = "25.0.0" } neutron-test-tube = { version = "4.2.0" } diff --git a/contracts-ws/Cargo.toml b/contracts-ws/Cargo.toml index 04ee55fdc..9793dc23d 100644 --- a/contracts-ws/Cargo.toml +++ b/contracts-ws/Cargo.toml @@ -9,8 +9,8 @@ edition = "2021" [workspace.dependencies] cosmwasm-std = "2.0.0" -cw20 = { package = "abstract-cw20", version = "2.0.0" } -cw20-base = { package = "abstract-cw20-base", version = "2.0.0" } +cw20 = { version = "2.0.0" } +cw20-base = { version = "2.0.0" } cw-storage-plus = { version = "2.0.0" } serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/cw-orch-mock/Cargo.toml b/packages/cw-orch-mock/Cargo.toml index 6eac3774d..0c4f06fd4 100644 --- a/packages/cw-orch-mock/Cargo.toml +++ b/packages/cw-orch-mock/Cargo.toml @@ -20,7 +20,6 @@ log = { workspace = true } [dev-dependencies] speculoos = { workspace = true } -# Should be able to replace with workspace cw20 when abstract-cw-plus fork updates to CosmWasm 2 cw20 = { version = "2.0.0" } cw20-base = { version = "2.0.0" } From d5afc96ec4acf126fb0f5e6a675996712d1fed44 Mon Sep 17 00:00:00 2001 From: Mykhailo Donchenko <91957742+Buckram123@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:28:02 +0300 Subject: [PATCH 6/8] More flexible upload_with_access_config unimplemented (#502) * more flexible upload_with_access_config unimplemented * Versino --------- Co-authored-by: Kayanski --- packages/cw-orch-core/Cargo.toml | 2 +- packages/cw-orch-core/src/environment/tx_handler.rs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/cw-orch-core/Cargo.toml b/packages/cw-orch-core/Cargo.toml index 124e9f7da..eb7f976f6 100644 --- a/packages/cw-orch-core/Cargo.toml +++ b/packages/cw-orch-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-orch-core" -version = "2.1.1" +version = "2.1.2" authors = { workspace = true } edition = { workspace = true } license = { workspace = true } diff --git a/packages/cw-orch-core/src/environment/tx_handler.rs b/packages/cw-orch-core/src/environment/tx_handler.rs index acf98089b..fed8761a9 100644 --- a/packages/cw-orch-core/src/environment/tx_handler.rs +++ b/packages/cw-orch-core/src/environment/tx_handler.rs @@ -38,10 +38,16 @@ pub trait TxHandler: ChainState + Clone { /// Uploads a contract to the chain and specify the permissions for instantiating fn upload_with_access_config( &self, - _contract_source: &T, - _access_config: Option, + contract_source: &T, + access_config: Option, ) -> Result { - unimplemented!(); + // If access config provided make sure it's handled + // or we can just use default upload otherwise + if access_config.is_some() { + unimplemented!(); + } else { + self.upload(contract_source) + } } /// Send a InstantiateMsg to a contract. From f2cd8b69d3cb17f0cff796b91b6bbb8b38904dd5 Mon Sep 17 00:00:00 2001 From: Kayanski <44806566+Kayanski@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:29:09 +0200 Subject: [PATCH 7/8] Added CosmosSigner default implementation (#494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added CosmosSigner default implementation * Moved broadcast to sender * Renamed CosmosSigneré * Nit * Nits * Renamed to account_id * Update cw-orch-daemon/src/senders/sign.rs Co-authored-by: CyberHoward <88450409+CyberHoward@users.noreply.github.com> --------- Co-authored-by: CyberHoward <88450409+CyberHoward@users.noreply.github.com> --- cw-orch-daemon/src/senders/cosmos.rs | 177 ++++++++++++--------------- cw-orch-daemon/src/senders/mod.rs | 1 + cw-orch-daemon/src/senders/sign.rs | 135 ++++++++++++++++++++ cw-orch-daemon/src/senders/tx.rs | 30 ++++- cw-orch-daemon/src/tx_broadcaster.rs | 20 +-- cw-orch-daemon/src/tx_builder.rs | 63 ++++++---- 6 files changed, 286 insertions(+), 140 deletions(-) create mode 100644 cw-orch-daemon/src/senders/sign.rs diff --git a/cw-orch-daemon/src/senders/cosmos.rs b/cw-orch-daemon/src/senders/cosmos.rs index 195957a24..1fba13405 100644 --- a/cw-orch-daemon/src/senders/cosmos.rs +++ b/cw-orch-daemon/src/senders/cosmos.rs @@ -1,4 +1,9 @@ -use super::{cosmos_options::CosmosWalletKey, query::QuerySender, tx::TxSender}; +use super::{ + cosmos_options::CosmosWalletKey, + query::QuerySender, + sign::{Signer, SigningAccount}, + tx::TxSender, +}; use crate::{ core::parse_cw_coins, cosmos_modules::{self, auth::BaseAccount}, @@ -7,10 +12,6 @@ use crate::{ keys::private::PrivateKey, proto::injective::{InjectiveEthAccount, ETHEREUM_COIN_TYPE}, queriers::{Bank, Node}, - tx_broadcaster::{ - account_sequence_strategy, assert_broadcast_code_cosm_response, insufficient_fee_strategy, - TxBroadcaster, - }, tx_builder::TxBuilder, tx_resp::CosmTxResponse, upload_wasm, CosmosOptions, GrpcChannel, @@ -20,9 +21,9 @@ use cosmos_modules::vesting::PeriodicVestingAccount; use cosmrs::{ bank::MsgSend, crypto::secp256k1::SigningKey, - proto::{cosmos::authz::v1beta1::MsgExec, traits::Message}, + proto::traits::Message, tendermint::chain::Id, - tx::{self, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo, SignerPublicKey}, + tx::{self, Fee, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo, SignerPublicKey}, AccountId, Any, }; use cosmwasm_std::{coin, Addr, Coin}; @@ -159,23 +160,7 @@ impl Wallet { } pub fn pub_addr_str(&self) -> String { - self.account_id().to_string() - } - - pub async fn broadcast_tx( - &self, - tx: Raw, - ) -> Result { - let mut client = cosmos_modules::tx::service_client::ServiceClient::new(self.channel()); - let commit = client - .broadcast_tx(cosmos_modules::tx::BroadcastTxRequest { - tx_bytes: tx.to_bytes()?, - mode: cosmos_modules::tx::BroadcastMode::Sync.into(), - }) - .await?; - - let commit = commit.into_inner().tx_response.unwrap(); - Ok(commit) + Signer::account_id(self).to_string() } pub async fn bank_send( @@ -269,21 +254,6 @@ impl Wallet { self.commit_tx_any(msgs, memo).await } - pub fn sign(&self, sign_doc: SignDoc) -> Result { - let tx_raw = if self.private_key.coin_type == ETHEREUM_COIN_TYPE { - #[cfg(not(feature = "eth"))] - panic!( - "Coin Type {} not supported without eth feature", - ETHEREUM_COIN_TYPE - ); - #[cfg(feature = "eth")] - self.private_key.sign_injective(sign_doc)? - } else { - sign_doc.sign(&self.cosmos_private_key())? - }; - Ok(tx_raw) - } - pub async fn base_account(&self) -> Result { let addr = self.address().to_string(); @@ -433,47 +403,79 @@ impl QuerySender for Wallet { } } -impl TxSender for Wallet { - async fn commit_tx_any( - &self, - msgs: Vec, - memo: Option<&str>, - ) -> Result { - let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64; +fn get_mnemonic_env(chain_kind: &ChainKind) -> Result { + match chain_kind { + ChainKind::Local => DaemonEnvVars::local_mnemonic(), + ChainKind::Testnet => DaemonEnvVars::test_mnemonic(), + ChainKind::Mainnet => DaemonEnvVars::main_mnemonic(), + _ => None, + } + .ok_or(CwEnvError::EnvVarNotPresentNamed( + get_mnemonic_env_name(chain_kind).to_string(), + )) +} + +fn get_mnemonic_env_name(chain_kind: &ChainKind) -> &str { + match chain_kind { + ChainKind::Local => LOCAL_MNEMONIC_ENV_NAME, + ChainKind::Testnet => TEST_MNEMONIC_ENV_NAME, + ChainKind::Mainnet => MAIN_MNEMONIC_ENV_NAME, + _ => panic!("Can't set mnemonic for unspecified chainkind"), + } +} - let msgs = if self.options.authz_granter.is_some() { - // We wrap authz messages - vec![Any { - type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(), - value: MsgExec { - grantee: self.pub_addr_str(), - msgs, - } - .encode_to_vec(), - }] +impl Signer for Wallet { + fn sign(&self, sign_doc: SignDoc) -> Result { + let tx_raw = if self.private_key.coin_type == ETHEREUM_COIN_TYPE { + #[cfg(not(feature = "eth"))] + panic!( + "Coin Type {} not supported without eth feature", + ETHEREUM_COIN_TYPE + ); + #[cfg(feature = "eth")] + self.private_key.sign_injective(sign_doc)? } else { - msgs + sign_doc.sign(&self.cosmos_private_key())? }; + Ok(tx_raw) + } - let tx_body = TxBuilder::build_body(msgs, memo, timeout_height); + fn chain_id(&self) -> String { + self.chain_info.chain_id.clone() + } - let tx_builder = TxBuilder::new(tx_body); + fn signer_info(&self, sequence: u64) -> SignerInfo { + SignerInfo { + public_key: self.private_key.get_signer_public_key(&self.secp), + mode_info: ModeInfo::single(SignMode::Direct), + sequence, + } + } + + fn build_fee(&self, amount: impl Into, gas_limit: u64) -> Result { + TxBuilder::build_fee( + amount, + &self.get_fee_token(), + gas_limit, + self.options.fee_granter.clone(), + ) + } - // We retry broadcasting the tx, with the following strategies - // 1. In case there is an `incorrect account sequence` error, we can retry as much as possible (doesn't cost anything to the user) - // 2. In case there is an insufficient_fee error, we retry once (costs fee to the user everytime we submit this kind of tx) - // 3. In case there is an other error, we fail - let tx_response = TxBroadcaster::default() - .add_strategy(insufficient_fee_strategy()) - .add_strategy(account_sequence_strategy()) - .broadcast(tx_builder, self) - .await?; + async fn signing_account(&self) -> Result { + let BaseAccount { + account_number, + sequence, + .. + } = self.base_account().await?; - let resp = Node::new_async(self.channel()) - ._find_tx(tx_response.txhash) - .await?; + Ok(SigningAccount { + account_number, + sequence, + }) + } - assert_broadcast_code_cosm_response(resp) + fn gas_price(&self) -> Result { + Ok(self.chain_info.gas_price) } fn account_id(&self) -> AccountId { @@ -485,34 +487,7 @@ impl TxSender for Wallet { .unwrap() } - /// Actual sender of the messages. - /// This is different when using authz capabilites - fn msg_sender(&self) -> Result { - if let Some(sender) = &self.options.authz_granter { - Ok(sender.as_str().parse()?) - } else { - Ok(self.account_id()) - } - } -} - -fn get_mnemonic_env(chain_kind: &ChainKind) -> Result { - match chain_kind { - ChainKind::Local => DaemonEnvVars::local_mnemonic(), - ChainKind::Testnet => DaemonEnvVars::test_mnemonic(), - ChainKind::Mainnet => DaemonEnvVars::main_mnemonic(), - _ => None, - } - .ok_or(CwEnvError::EnvVarNotPresentNamed( - get_mnemonic_env_name(chain_kind).to_string(), - )) -} - -fn get_mnemonic_env_name(chain_kind: &ChainKind) -> &str { - match chain_kind { - ChainKind::Local => LOCAL_MNEMONIC_ENV_NAME, - ChainKind::Testnet => TEST_MNEMONIC_ENV_NAME, - ChainKind::Mainnet => MAIN_MNEMONIC_ENV_NAME, - _ => panic!("Can't set mnemonic for unspecified chainkind"), + fn authz_granter(&self) -> Option<&Addr> { + self.options.authz_granter.as_ref() } } diff --git a/cw-orch-daemon/src/senders/mod.rs b/cw-orch-daemon/src/senders/mod.rs index e637e4470..80257e602 100644 --- a/cw-orch-daemon/src/senders/mod.rs +++ b/cw-orch-daemon/src/senders/mod.rs @@ -1,6 +1,7 @@ // Core Sender traits pub mod builder; pub mod query; +pub mod sign; pub mod tx; // Senders diff --git a/cw-orch-daemon/src/senders/sign.rs b/cw-orch-daemon/src/senders/sign.rs new file mode 100644 index 000000000..2662e7f91 --- /dev/null +++ b/cw-orch-daemon/src/senders/sign.rs @@ -0,0 +1,135 @@ +use crate::{ + queriers::Node, + tx_broadcaster::{ + account_sequence_strategy, assert_broadcast_code_cosm_response, insufficient_fee_strategy, + TxBroadcaster, + }, + CosmTxResponse, DaemonError, QuerySender, TxBuilder, TxSender, +}; +use cosmrs::{ + proto::cosmos::authz::v1beta1::MsgExec, + tendermint::chain::Id, + tx::{Body, Fee, Raw, SignDoc, SignerInfo}, + AccountId, Any, +}; +use cosmwasm_std::Addr; +use prost::Message; + +pub struct SigningAccount { + pub account_number: u64, + pub sequence: u64, +} + +pub trait Signer: QuerySender + Sync { + // --- General information about the signer --- // + /// The chain id of the connected chain + fn chain_id(&self) -> String; + + /// The account id of the signer. + fn account_id(&self) -> AccountId; + + fn signing_account( + &self, + ) -> impl std::future::Future> + Send; + + /// Signals wether this signer is using authz + /// If set to true, the signed messages will be wrapped inside authz messages + fn authz_granter(&self) -> Option<&Addr> { + None + } + + // --- Related to transaction signing --- // + /// Transaction signing + fn sign(&self, sign_doc: SignDoc) -> Result; + + fn signer_info(&self, sequence: u64) -> SignerInfo; + + fn build_fee(&self, amount: impl Into, gas_limit: u64) -> Result; + + fn gas_price(&self) -> Result; + + /// Computes the gas needed for submitting a transaction + fn calculate_gas( + &self, + tx_body: &Body, + sequence: u64, + account_number: u64, + ) -> impl std::future::Future> + Send { + async move { + let fee = self.build_fee(0u8, 0)?; + + let auth_info = self.signer_info(sequence).auth_info(fee); + + let sign_doc = SignDoc::new( + tx_body, + &auth_info, + &Id::try_from(self.chain_id())?, + account_number, + )?; + + let tx_raw = self.sign(sign_doc)?; + + Node::new_async(self.channel()) + ._simulate_tx(tx_raw.to_bytes()?) + .await + } + } +} + +impl TxSender for T { + fn account_id(&self) -> cosmrs::AccountId { + self.account_id() + } + + async fn commit_tx_any( + &self, + msgs: Vec, + memo: Option<&str>, + ) -> Result { + let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64; + + let msgs = if self.authz_granter().is_some() { + // We wrap authz messages + vec![Any { + type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(), + value: MsgExec { + grantee: self.account_id().to_string(), + msgs, + } + .encode_to_vec(), + }] + } else { + msgs + }; + + let tx_body = TxBuilder::build_body(msgs, memo, timeout_height); + + let tx_builder = TxBuilder::new(tx_body); + + // We retry broadcasting the tx, with the following strategies + // 1. In case there is an `incorrect account sequence` error, we can retry as much as possible (doesn't cost anything to the user) + // 2. In case there is an insufficient_fee error, we retry once (costs fee to the user everytime we submit this kind of tx) + // 3. In case there is an other error, we fail + + let tx_response = TxBroadcaster::default() + .add_strategy(insufficient_fee_strategy()) + .add_strategy(account_sequence_strategy()) + .broadcast(tx_builder, self) + .await?; + + let resp = Node::new_async(self.channel()) + ._find_tx(tx_response.txhash) + .await?; + + assert_broadcast_code_cosm_response(resp) + } + /// Actual sender of the messages. + /// This is different when using authz capabilites + fn msg_sender(&self) -> Result { + if let Some(sender) = self.authz_granter() { + Ok(sender.as_str().parse()?) + } else { + Ok(self.account_id()) + } + } +} diff --git a/cw-orch-daemon/src/senders/tx.rs b/cw-orch-daemon/src/senders/tx.rs index 4309001d0..0426f22e4 100644 --- a/cw-orch-daemon/src/senders/tx.rs +++ b/cw-orch-daemon/src/senders/tx.rs @@ -1,11 +1,14 @@ -use cosmrs::{tx::Msg, AccountId, Any}; +use cosmrs::{ + tx::{Msg, Raw}, + AccountId, Any, +}; use cosmwasm_std::Addr; -use crate::CosmTxResponse; +use crate::{cosmos_modules, CosmTxResponse, DaemonError}; use super::query::QuerySender; -pub trait TxSender: QuerySender { +pub trait TxSender: QuerySender + Sync { /// Returns the `AccountId` of the sender that commits the transaction. fn account_id(&self) -> AccountId; @@ -42,4 +45,25 @@ pub trait TxSender: QuerySender { self.commit_tx_any(msgs, memo) } + + /// Transaction broadcasting for Tendermint Transactions + fn broadcast_tx( + &self, + tx: Raw, + ) -> impl std::future::Future< + Output = Result, + > + Send { + async move { + let mut client = cosmos_modules::tx::service_client::ServiceClient::new(self.channel()); + let commit = client + .broadcast_tx(cosmos_modules::tx::BroadcastTxRequest { + tx_bytes: tx.to_bytes()?, + mode: cosmos_modules::tx::BroadcastMode::Sync.into(), + }) + .await?; + + let commit = commit.into_inner().tx_response.unwrap(); + Ok(commit) + } + } } diff --git a/cw-orch-daemon/src/tx_broadcaster.rs b/cw-orch-daemon/src/tx_broadcaster.rs index 9632862f9..e4c295290 100644 --- a/cw-orch-daemon/src/tx_broadcaster.rs +++ b/cw-orch-daemon/src/tx_broadcaster.rs @@ -1,7 +1,8 @@ use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use cw_orch_core::log::transaction_target; -use crate::{queriers::Node, CosmTxResponse, DaemonError, TxBuilder, Wallet}; +use crate::senders::tx::TxSender; +use crate::{queriers::Node, senders::sign::Signer, CosmTxResponse, DaemonError, TxBuilder}; pub type StrategyAction = fn(&mut TxBuilder, &Result) -> Result<(), DaemonError>; @@ -62,17 +63,16 @@ impl TxBroadcaster { self } - // We can't make async recursions easily because wallet is not `Sync` - // Thus we use a `while` loop structure here + /// Broadcasts a transaction with the given signer pub async fn broadcast( mut self, mut tx_builder: TxBuilder, - wallet: &Wallet, + signer: &impl Signer, ) -> Result { let mut tx_retry = true; // We try and broadcast once - let mut tx_response = broadcast_helper(&mut tx_builder, wallet).await; + let mut tx_response = broadcast_helper(&mut tx_builder, signer).await; log::info!( target: &transaction_target(), "Awaiting TX inclusion in block..." @@ -90,7 +90,7 @@ impl TxBroadcaster { tx_retry = true; // We still await for the next block, to avoid spamming retry when an error occurs - let block_speed = Node::new_async(wallet.channel()) + let block_speed = Node::new_async(signer.channel()) ._average_block_speed(None) .await?; log::warn!( @@ -101,7 +101,7 @@ impl TxBroadcaster { ); tokio::time::sleep(block_speed).await; - tx_response = broadcast_helper(&mut tx_builder, wallet).await; + tx_response = broadcast_helper(&mut tx_builder, signer).await; continue; } } @@ -122,10 +122,10 @@ fn strategy_condition_met( async fn broadcast_helper( tx_builder: &mut TxBuilder, - wallet: &Wallet, + signer: &impl Signer, ) -> Result { - let tx = tx_builder.build(wallet).await?; - let tx_response = wallet.broadcast_tx(tx).await?; + let tx = tx_builder.build(signer).await?; + let tx_response = signer.broadcast_tx(tx).await?; log::debug!(target: &transaction_target(), "TX broadcast response: {:?}", tx_response); assert_broadcast_code_response(tx_response) diff --git a/cw-orch-daemon/src/tx_builder.rs b/cw-orch-daemon/src/tx_builder.rs index cf6df35b9..bb4242ad0 100644 --- a/cw-orch-daemon/src/tx_builder.rs +++ b/cw-orch-daemon/src/tx_builder.rs @@ -1,20 +1,23 @@ use std::str::FromStr; -use cosmrs::tx::{ModeInfo, SignMode}; use cosmrs::AccountId; use cosmrs::{ - proto::cosmos::auth::v1beta1::BaseAccount, tendermint::chain::Id, - tx::{self, Body, Fee, Raw, SequenceNumber, SignDoc, SignerInfo}, + tx::{self, Body, Fee, Raw, SequenceNumber, SignDoc}, Any, Coin, }; use cosmwasm_std::Addr; use cw_orch_core::log::transaction_target; -use crate::Wallet; +use crate::env::DaemonEnvVars; +use crate::senders::sign::{Signer, SigningAccount}; use super::DaemonError; +const GAS_BUFFER: f64 = 1.3; +const BUFFER_THRESHOLD: u64 = 200_000; +const SMALL_GAS_BUFFER: f64 = 1.4; + /// Struct used to build a raw transaction and broadcast it with a sender. #[derive(Clone, Debug)] pub struct TxBuilder { @@ -77,13 +80,12 @@ impl TxBuilder { } /// Simulates the transaction and returns the necessary gas fee returned by the simulation on a node - pub async fn simulate(&self, wallet: &Wallet) -> Result { + pub async fn simulate(&self, wallet: &impl Signer) -> Result { // get the account number of the wallet - let BaseAccount { + let SigningAccount { account_number, sequence, - .. - } = wallet.base_account().await?; + } = wallet.signing_account().await?; // overwrite sequence if set (can be used for concurrent txs) let sequence = self.sequence.unwrap_or(sequence); @@ -95,13 +97,12 @@ impl TxBuilder { /// Builds the raw tx with a given body and fee and signs it. /// Sets the TxBuilder's gas limit to its simulated amount for later use. - pub async fn build(&mut self, wallet: &Wallet) -> Result { + pub async fn build(&mut self, wallet: &impl Signer) -> Result { // get the account number of the wallet - let BaseAccount { + let SigningAccount { account_number, sequence, - .. - } = wallet.base_account().await?; + } = wallet.signing_account().await?; // overwrite sequence if set (can be used for concurrent txs) let sequence = self.sequence.unwrap_or(sequence); @@ -123,7 +124,8 @@ impl TxBuilder { .await?; log::debug!(target: &transaction_target(), "Simulated gas needed {:?}", sim_gas_used); - let (gas_expected, fee_amount) = wallet.get_fee_from_gas(sim_gas_used)?; + let (gas_expected, fee_amount) = + TxBuilder::get_fee_from_gas(sim_gas_used, wallet.gas_price()?)?; log::debug!(target: &transaction_target(), "Calculated fee needed: {:?}", fee_amount); // set the gas limit of self for future txs @@ -133,12 +135,7 @@ impl TxBuilder { (fee_amount, gas_expected) }; - let fee = Self::build_fee( - tx_fee, - &wallet.get_fee_token(), - gas_limit, - wallet.options.fee_granter.clone(), - )?; + let fee = wallet.build_fee(tx_fee, gas_limit)?; log::debug!( target: &transaction_target(), @@ -148,19 +145,33 @@ impl TxBuilder { sequence ); - let auth_info = SignerInfo { - public_key: wallet.private_key.get_signer_public_key(&wallet.secp), - mode_info: ModeInfo::single(SignMode::Direct), - sequence, - } - .auth_info(fee); + let auth_info = wallet.signer_info(sequence).auth_info(fee); let sign_doc = SignDoc::new( &self.body, &auth_info, - &Id::try_from(wallet.chain_info.chain_id.to_string())?, + &Id::try_from(wallet.chain_id())?, account_number, )?; wallet.sign(sign_doc).map_err(Into::into) } + + /// Compute the gas fee from the expected gas in the transaction + /// Applies a Gas Buffer for including signature verification + pub(crate) fn get_fee_from_gas(gas: u64, gas_price: f64) -> Result<(u64, u128), DaemonError> { + let mut gas_expected = if let Some(gas_buffer) = DaemonEnvVars::gas_buffer() { + gas as f64 * gas_buffer + } else if gas < BUFFER_THRESHOLD { + gas as f64 * SMALL_GAS_BUFFER + } else { + gas as f64 * GAS_BUFFER + }; + + let min_gas = DaemonEnvVars::min_gas(); + gas_expected = (min_gas as f64).max(gas_expected); + + let fee_amount = gas_expected * (gas_price + 0.00001); + + Ok((gas_expected as u64, fee_amount as u128)) + } } From ea1be9af83135c1706eaa02c323b7b1d7726ad87 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Tue, 1 Oct 2024 08:47:17 +0000 Subject: [PATCH 8/8] Changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb1cc98c8..09e5abe36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ ### Breaking -- Added Support for more mnemonic lengths (at least 24 and 12). This is breaking because of how the mnemonic words are stored and retrieved (`words` method on `PrivateKey`) +- Added Support for more mnemonic lengths (at least 24 and 12). This is breaking because of how the mnemonic words are stored and retrieved (`words` method on `PrivateKey`) (published in cw-orch-daemon 0.26.0) +- Added `Signer` trait for being able to re-use the signing/broadcast flow (Unpublished) ## 0.25.0