Skip to content

Commit b395f3d

Browse files
authored
fix: GrpcClient not taking gas amount into account (#319)
1 parent 5c8b0a3 commit b395f3d

File tree

1 file changed

+83
-7
lines changed

1 file changed

+83
-7
lines changed

crates/utils/cw-client/src/grpc.rs

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::error::Error;
1+
use std::{error::Error, str::FromStr};
22

33
use anyhow::anyhow;
44
use cosmos_sdk_proto::{
@@ -25,7 +25,7 @@ use cosmrs::{
2525
tendermint::chain::Id as TmChainId,
2626
tx,
2727
tx::{Fee, Msg, SignDoc, SignerInfo},
28-
AccountId, Coin,
28+
AccountId, Amount, Coin, Denom,
2929
};
3030
use reqwest::Url;
3131
use serde::de::DeserializeOwned;
@@ -100,7 +100,7 @@ impl CwClient for GrpcClient {
100100
gas: u64,
101101
_sender: &str,
102102
msg: M,
103-
_pay_amount: &str,
103+
pay_amount: &str,
104104
) -> Result<String, Self::Error> {
105105
let tm_pubkey = self.sk.public_key();
106106
let sender = tm_pubkey
@@ -119,10 +119,7 @@ impl CwClient for GrpcClient {
119119
let account = account_info(self.url.to_string(), sender.to_string())
120120
.await
121121
.map_err(|e| anyhow!("error querying account info: {}", e))?;
122-
let amount = Coin {
123-
amount: 11000u128,
124-
denom: "untrn".parse().expect("hardcoded denom"),
125-
};
122+
let amount = parse_coin(pay_amount)?;
126123
let tx_bytes = tx_bytes(
127124
&self.sk,
128125
amount,
@@ -215,3 +212,82 @@ pub async fn send_tx(
215212
let tx_response = client.broadcast_tx(request).await?;
216213
Ok(tx_response.into_inner())
217214
}
215+
216+
pub fn parse_coin(input: &str) -> anyhow::Result<Coin> {
217+
let split_at = input
218+
.find(|c: char| !c.is_ascii_digit())
219+
.ok_or(anyhow!("invalid coin format: missing denomination"))?;
220+
let (amt_str, denom_str) = input.split_at(split_at);
221+
222+
let amount: Amount = amt_str.parse()?;
223+
let denom: Denom = Denom::from_str(denom_str).map_err(|e| anyhow!("invalid denom: {e}"))?;
224+
225+
Ok(Coin { denom, amount })
226+
}
227+
228+
#[cfg(test)]
229+
mod tests {
230+
use std::str::FromStr;
231+
232+
use super::*;
233+
234+
#[test]
235+
fn parse_valid_basic() {
236+
let coin = parse_coin("11000untrn").unwrap();
237+
assert_eq!(coin.amount, 11_000);
238+
assert_eq!(coin.denom, Denom::from_str("untrn").unwrap());
239+
}
240+
241+
#[test]
242+
fn parse_leading_zeros() {
243+
let coin = parse_coin("000123abc").unwrap();
244+
assert_eq!(coin.amount, 123);
245+
assert_eq!(coin.denom, Denom::from_str("abc").unwrap());
246+
}
247+
248+
#[test]
249+
fn parse_zero_amount() {
250+
let coin = parse_coin("0xyz").unwrap();
251+
assert_eq!(coin.amount, 0);
252+
assert_eq!(coin.denom, Denom::from_str("xyz").unwrap());
253+
}
254+
255+
#[test]
256+
fn parse_denom_with_digits() {
257+
let coin = parse_coin("10token123").unwrap();
258+
assert_eq!(coin.amount, 10);
259+
assert_eq!(coin.denom, Denom::from_str("token123").unwrap());
260+
}
261+
262+
#[test]
263+
fn parse_max_u128_amount() {
264+
// u128::MAX = 340282366920938463463374607431768211455
265+
let s = "340282366920938463463374607431768211455max";
266+
let coin = parse_coin(s).unwrap();
267+
assert_eq!(coin.amount, u128::MAX);
268+
assert_eq!(coin.denom, Denom::from_str("max").unwrap());
269+
}
270+
271+
#[test]
272+
fn error_missing_denom() {
273+
assert!(parse_coin("123").is_err());
274+
}
275+
276+
#[test]
277+
fn error_missing_amount() {
278+
assert!(parse_coin("abc").is_err());
279+
}
280+
281+
#[test]
282+
fn error_overflow_amount() {
283+
// one more than u128::MAX
284+
let s = "340282366920938463463374607431768211456overflow";
285+
assert!(parse_coin(s).is_err());
286+
}
287+
288+
#[test]
289+
fn error_negative_amount() {
290+
// '-' is non-digit at pos 0 → empty amount → parse error
291+
assert!(parse_coin("-100untrn").is_err());
292+
}
293+
}

0 commit comments

Comments
 (0)