use std::str::FromStr;
use cosmrs::AccountId;
use cosmrs::{
tendermint::chain::Id,
tx::{self, Body, Fee, Raw, SequenceNumber, SignDoc},
Any, Coin,
};
use cosmwasm_std::Addr;
use cw_orch_core::log::transaction_target;
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;
#[derive(Clone, Debug)]
pub struct TxBuilder {
pub(crate) body: Body,
pub(crate) fee_amount: Option<u128>,
pub(crate) gas_limit: Option<u64>,
pub(crate) sequence: Option<SequenceNumber>,
}
impl TxBuilder {
pub fn new(body: Body) -> Self {
Self {
body,
fee_amount: None,
gas_limit: None,
sequence: None,
}
}
pub fn fee_amount(&mut self, fee_amount: u128) -> &mut Self {
self.fee_amount = Some(fee_amount);
self
}
pub fn gas_limit(&mut self, gas_limit: u64) -> &mut Self {
self.gas_limit = Some(gas_limit);
self
}
pub fn sequence(&mut self, sequence: u64) -> &mut Self {
self.sequence = Some(sequence);
self
}
pub fn build_body(msgs: Vec<Any>, memo: Option<&str>, timeout: u64) -> tx::Body {
tx::Body::new(
msgs,
memo.unwrap_or("Tx committed using cw-orchestrator! ⚙️"),
timeout as u32,
)
}
pub fn build_fee(
amount: impl Into<u128>,
denom: &str,
gas_limit: u64,
fee_granter: Option<Addr>,
) -> Result<Fee, DaemonError> {
let fee = Coin::new(amount.into(), denom).unwrap();
let mut fee = Fee::from_amount_and_gas(fee, gas_limit);
fee.granter = fee_granter
.map(|g| AccountId::from_str(g.as_str()))
.transpose()?;
Ok(fee)
}
pub async fn simulate(&self, wallet: &impl Signer) -> Result<u64, DaemonError> {
let SigningAccount {
account_number,
sequence,
} = wallet.signing_account().await?;
let sequence = self.sequence.unwrap_or(sequence);
wallet
.calculate_gas(&self.body, sequence, account_number)
.await
}
pub async fn build(&mut self, wallet: &impl Signer) -> Result<Raw, DaemonError> {
let SigningAccount {
account_number,
sequence,
} = wallet.signing_account().await?;
let sequence = self.sequence.unwrap_or(sequence);
let (tx_fee, gas_limit) = if let (Some(fee), Some(gas_limit)) =
(self.fee_amount, self.gas_limit)
{
log::debug!(
target: &transaction_target(),
"Using pre-defined fee and gas limits: {}, {}",
fee,
gas_limit
);
(fee, gas_limit)
} else {
let sim_gas_used = wallet
.calculate_gas(&self.body, sequence, account_number)
.await?;
log::debug!(target: &transaction_target(), "Simulated gas needed {:?}", 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);
self.gas_limit = Some(gas_expected);
(fee_amount, gas_expected)
};
let fee = wallet.build_fee(tx_fee, gas_limit)?;
log::debug!(
target: &transaction_target(),
"submitting TX: \n fee: {:?}\naccount_nr: {:?}\nsequence: {:?}",
fee,
account_number,
sequence
);
let auth_info = wallet.signer_info(sequence).auth_info(fee);
let sign_doc = SignDoc::new(
&self.body,
&auth_info,
&Id::try_from(wallet.chain_id())?,
account_number,
)?;
wallet.sign(sign_doc).map_err(Into::into)
}
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))
}
}