miden_client/test_utils/
fee.rs1use alloc::boxed::Box;
4use alloc::sync::Arc;
5use alloc::vec;
6use alloc::vec::Vec;
7use core::fmt;
8
9use anyhow::{Context, Result};
10use miden_protocol::Felt;
11use miden_protocol::account::AccountId;
12use miden_protocol::block::BlockNumber;
13
14use super::common::{TestClient, wait_for_tx};
15use crate::note::Note;
16use crate::transaction::{TransactionId, TransactionRequestBuilder};
17
18#[async_trait::async_trait(?Send)]
20pub trait FeeFunder: Send + Sync + fmt::Debug {
21 async fn fund(&self, account_ids: &[AccountId]) -> Result<Vec<(AccountId, Note)>>;
27}
28
29impl TestClient {
30 pub async fn fund_if_needed(&mut self, account_ids: &[AccountId]) -> Result<()> {
35 if !self.chain_charges_fees().await? {
36 return Ok(());
37 }
38
39 let funded = self.funder()?.fund(account_ids).await?;
40 self.stash_funding(funded);
41
42 Ok(())
43 }
44
45 fn funder(&self) -> Result<Arc<dyn FeeFunder>> {
47 self.fee_funder().cloned().context(
48 "this chain charges a transaction fee, so every account a test creates has to be \
49 funded before it can transact, but this client has no fee funder. Supply the funder \
50 wallets to draw from (see the integration tests' `--funders` argument)",
51 )
52 }
53
54 pub async fn deploy_account(&mut self, account_id: AccountId) -> Result<()> {
56 self.deploy_accounts(&[account_id]).await
57 }
58
59 pub async fn deploy_accounts(&mut self, account_ids: &[AccountId]) -> Result<()> {
64 let mut undeployed = Vec::with_capacity(account_ids.len());
65 for account_id in account_ids.iter().copied() {
66 let nonce =
69 self.account_reader(account_id).nonce().await.with_context(|| {
70 format!("account {account_id} is not tracked by the client")
71 })?;
72 if nonce == Felt::ZERO {
73 undeployed.push(account_id);
74 }
75 }
76 if undeployed.is_empty() {
77 return Ok(());
78 }
79
80 if self.chain_charges_fees().await? {
81 let mut funded = Vec::with_capacity(undeployed.len());
84 for account_id in &undeployed {
85 match self.take_funding(*account_id) {
86 Some(note) => funded.push((*account_id, note)),
87 None => funded.extend(self.funder()?.fund(&[*account_id]).await?),
88 }
89 }
90
91 return self.deploy_by_consuming(&funded).await;
92 }
93
94 let mut tx_ids = Vec::with_capacity(undeployed.len());
95 for account_id in undeployed {
96 let request = TransactionRequestBuilder::new()
97 .build()
98 .context("failed to build the deploy transaction request")?;
99 let tx_id =
100 Box::pin(self.submit_new_transaction(account_id, request)).await.with_context(
101 || format!("failed to submit the deploy transaction of {account_id}"),
102 )?;
103 tx_ids.push((account_id, tx_id));
104 }
105
106 self.wait_for_deploys(&tx_ids).await
107 }
108
109 pub async fn deploy_by_consuming(&mut self, funded: &[(AccountId, Note)]) -> Result<()> {
112 let mut tx_ids = Vec::with_capacity(funded.len());
115 for (account_id, note) in funded {
116 let (account_id, note_id) = (*account_id, note.id());
117
118 let request = TransactionRequestBuilder::new()
122 .build_consume_notes(vec![note.clone()])
123 .context("failed to build the funding note consumption request")?;
124 let tx_id =
125 Box::pin(self.submit_new_transaction(account_id, request)).await.with_context(
126 || format!("account {account_id} failed to consume funding note {note_id}"),
127 )?;
128 tx_ids.push((account_id, tx_id));
129 }
130
131 self.wait_for_deploys(&tx_ids).await
132 }
133
134 async fn wait_for_deploys(&mut self, tx_ids: &[(AccountId, TransactionId)]) -> Result<()> {
137 for (account_id, tx_id) in tx_ids.iter().copied() {
138 wait_for_tx(self, tx_id).await.with_context(|| {
139 format!("the deploy transaction of account {account_id} never committed")
140 })?;
141 }
142
143 Ok(())
144 }
145
146 pub async fn chain_charges_fees(&self) -> Result<bool> {
153 let (genesis, _) = self
154 .get_block_header_by_num(BlockNumber::GENESIS)
155 .await?
156 .context("the genesis block header is not in the client's store")?;
157
158 Ok(genesis.fee_parameters().verification_base_fee() != 0)
159 }
160}