1use crate::chain::coin::{Coin, Denom};
2use crate::chain::error::ChainError;
3use crate::chain::fee::{Fee, GasInfo};
4use crate::chain::msg::Msg;
5use crate::chain::request::TxOptions;
6use crate::chain::tx::RawTx;
7use crate::config::cfg::ChainConfig;
8use crate::modules::auth::error::AccountError;
9use crate::modules::auth::model::{Account, AccountResponse, Address};
10use crate::signing_key::key::SigningKey;
11use async_trait::async_trait;
12use cosmrs::proto::cosmos::auth::v1beta1::{
13 BaseAccount, QueryAccountRequest, QueryAccountResponse,
14};
15use cosmrs::proto::cosmos::tx::v1beta1::{SimulateRequest, SimulateResponse, TxRaw};
16use cosmrs::proto::traits::Message;
17use cosmrs::tendermint::Hash;
18use cosmrs::Any;
19
20use cosmrs::tendermint::abci::{Event, EventAttribute};
21use cosmrs::tx::{Body, SignerInfo};
22#[cfg(feature = "mockall")]
23use mockall::automock;
24
25use serde::Serialize;
26use tendermint_rpc::endpoint::tx;
27
28fn encode_msg<T: Message>(msg: T) -> Result<Vec<u8>, ChainError> {
29 let mut data = Vec::with_capacity(msg.encoded_len());
30 msg.encode(&mut data)
31 .map_err(ChainError::prost_proto_encoding)?;
32 Ok(data)
33}
34
35pub trait GetErr: Sized {
36 fn get_err(self) -> Result<Self, ChainError>;
37}
38
39pub trait GetValue {
40 fn get_value(&self) -> &[u8];
41}
42
43pub trait GetEvents {
44 fn get_events(&self) -> &[Event];
45
46 fn find_event_tags(&self, event_type: String, key_name: String) -> Vec<&EventAttribute> {
47 let mut events = vec![];
48 for event in self.get_events() {
49 if event.kind == event_type {
50 for attr in &event.attributes {
51 if attr.key == key_name {
52 events.push(attr);
53 }
54 }
55 }
56 }
57 events
58 }
59}
60
61#[cfg_attr(feature = "mockall", automock)]
62#[async_trait]
63pub trait HashSearch: ClientAbciQuery {
64 async fn hash_search(&self, hash: &Hash) -> Result<tx::Response, ChainError>;
65}
66
67#[cfg_attr(feature = "mockall", automock)]
68#[async_trait]
69pub trait ClientTxCommit {
70 type Response: GetErr + GetEvents;
71 async fn broadcast_tx_commit(&self, raw_tx: &RawTx) -> Result<Self::Response, ChainError>;
72}
73
74#[cfg_attr(feature = "mockall", automock)]
75#[async_trait]
76pub trait ClientTxSync {
77 type Response: GetErr;
78 async fn broadcast_tx_sync(&self, raw_tx: &RawTx) -> Result<Self::Response, ChainError>;
79}
80
81#[cfg_attr(feature = "mockall", automock)]
82#[async_trait]
83pub trait ClientTxAsync {
84 type Response: GetErr;
85 async fn broadcast_tx_async(&self, raw_tx: &RawTx) -> Result<Self::Response, ChainError>;
86}
87
88#[cfg_attr(feature = "mockall", automock)]
89#[async_trait]
90pub trait ClientAbciQuery: Sized {
91 type Response: GetErr + GetValue;
92 async fn abci_query<V>(
93 &self,
94 path: Option<String>,
95 data: V,
96 height: Option<u32>,
97 prove: bool,
98 ) -> Result<Self::Response, ChainError>
99 where
100 V: Into<Vec<u8>> + Send;
101
102 async fn query<I, O>(&self, msg: I, path: &str) -> Result<O, ChainError>
103 where
104 Self: Sized,
105 I: Message + Default + 'static,
106 O: Message + Default + 'static,
107 {
108 let bytes = encode_msg(msg)?;
109
110 let res = self
111 .abci_query(Some(path.to_string()), bytes, None, false)
112 .await?;
113
114 let proto_res =
115 O::decode(res.get_err()?.get_value()).map_err(ChainError::prost_proto_decoding)?;
116
117 Ok(proto_res)
118 }
119
120 async fn auth_query_account(&self, address: Address) -> Result<AccountResponse, AccountError> {
121 let req = QueryAccountRequest {
122 address: address.into(),
123 };
124
125 let res = self
126 .query::<_, QueryAccountResponse>(req, "/cosmos.auth.v1beta1.Query/Account")
127 .await?;
128
129 let account = res.account.ok_or(AccountError::Address {
130 message: "Invalid account address".to_string(),
131 })?;
132
133 let base_account = BaseAccount::decode(account.value.as_slice())
134 .map_err(ChainError::prost_proto_decoding)?;
135
136 Ok(AccountResponse {
137 account: base_account.try_into()?,
138 })
139 }
140
141 #[allow(deprecated)]
142 async fn query_simulate_tx(&self, tx: &RawTx) -> Result<GasInfo, ChainError> {
143 let req = SimulateRequest {
144 tx: None,
145 tx_bytes: tx.to_bytes()?,
146 };
147
148 let bytes = encode_msg(req)?;
149
150 let res = self
151 .abci_query(
152 Some("/cosmos.tx.v1beta1.Service/Simulate".to_string()),
153 bytes,
154 None,
155 false,
156 )
157 .await?;
158
159 let sim_res = SimulateResponse::decode(res.get_err()?.get_value())
160 .map_err(ChainError::prost_proto_decoding)?;
161
162 let gas_info = sim_res.gas_info.ok_or(ChainError::Simulation {
163 result: sim_res.result.unwrap(),
164 })?;
165
166 Ok(gas_info.into())
167 }
168
169 async fn tx_simulate<I>(
172 &self,
173 denom: &str,
174 gas_price: f64,
175 gas_adjustment: f64,
176 msgs: I,
177 account: &Account,
178 ) -> Result<Fee, ChainError>
179 where
180 I: IntoIterator<Item = Any> + Send,
181 {
182 let tx = Body::new(msgs, "cosm-client memo", 0u16);
183
184 let denom: Denom = denom.parse()?;
185
186 let fee = Fee::new(
187 Coin {
188 denom: denom.clone(),
189 amount: 0u128,
190 },
191 0u64,
192 None,
193 None,
194 );
195
196 let auth_info =
197 SignerInfo::single_direct(None, account.sequence).auth_info(fee.try_into()?);
198
199 let tx_raw = TxRaw {
200 body_bytes: tx.into_bytes().map_err(ChainError::proto_encoding)?,
201 auth_info_bytes: auth_info.into_bytes().map_err(ChainError::proto_encoding)?,
202 signatures: vec![vec![]],
203 };
204
205 let gas_info = self.query_simulate_tx(&tx_raw.into()).await?;
206
207 let gas_limit = (gas_info.gas_used.value() as f64 * gas_adjustment).ceil();
209 let amount = Coin {
210 denom,
211 amount: ((gas_limit * gas_price).ceil() as u64).into(),
212 };
213
214 let fee = Fee::new(amount, gas_limit as u64, None, None);
215
216 Ok(fee)
217 }
218
219 async fn tx_sign<T>(
220 &self,
221 chain_cfg: &ChainConfig,
222 msgs: Vec<T>,
223 key: &SigningKey,
224 tx_options: &TxOptions,
225 ) -> Result<RawTx, AccountError>
226 where
227 T: Msg + Serialize + Send + Sync,
228 <T as Msg>::Err: Send + Sync,
229 {
230 let sender_addr = key
231 .to_addr(&chain_cfg.prefix, &chain_cfg.derivation_path)
232 .await?;
233
234 let timeout_height = tx_options.timeout_height.unwrap_or_default();
235
236 let account = if let Some(ref account) = tx_options.account {
237 account.clone()
238 } else {
239 self.auth_query_account(sender_addr).await?.account
240 };
241
242 let fee = if let Some(fee) = &tx_options.fee {
243 fee.clone()
244 } else {
245 self.tx_simulate(
246 &chain_cfg.denom,
247 chain_cfg.gas_price,
248 chain_cfg.gas_adjustment,
249 msgs.iter()
250 .map(|m| m.to_any())
251 .collect::<Result<Vec<_>, _>>()
252 .map_err(|e| ChainError::ProtoEncoding {
253 message: e.to_string(),
254 })?,
255 &account,
256 )
257 .await?
258 };
259
260 let raw = key
261 .sign(
262 msgs,
263 timeout_height,
264 &tx_options.memo,
265 account,
266 fee,
267 &chain_cfg.chain_id,
268 &chain_cfg.derivation_path,
269 )
270 .await?;
271 Ok(raw)
272 }
273}