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;
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 async fn flush(&self) -> Result<()> {
30 Ok(())
31 }
32}
33
34impl TestClient {
35 pub async fn fund_if_needed(&mut self, account_ids: &[AccountId]) -> Result<()> {
40 if !self.chain_charges_fees().await? {
41 return Ok(());
42 }
43
44 let funded = self.funder()?.fund(account_ids).await?;
45 self.stash_funding(funded);
46
47 Ok(())
48 }
49
50 pub async fn flush_funder(&self) -> Result<()> {
52 match self.fee_funder() {
53 Some(funder) => funder.flush().await,
54 None => Ok(()),
55 }
56 }
57
58 fn funder(&self) -> Result<Arc<dyn FeeFunder>> {
60 self.fee_funder().cloned().context(
61 "this chain charges a transaction fee, so every account a test creates has to be \
62 funded before it can transact, but this client has no fee funder. Supply the funder \
63 wallets to draw from (see the integration tests' `--funders` argument)",
64 )
65 }
66
67 pub async fn deploy_account(&mut self, account_id: AccountId) -> Result<()> {
69 self.deploy_accounts(&[account_id]).await
70 }
71
72 pub async fn deploy_accounts(&mut self, account_ids: &[AccountId]) -> Result<()> {
77 let mut undeployed = Vec::with_capacity(account_ids.len());
78 for account_id in account_ids.iter().copied() {
79 let nonce =
82 self.account_reader(account_id).nonce().await.with_context(|| {
83 format!("account {account_id} is not tracked by the client")
84 })?;
85 if nonce == Felt::ZERO {
86 undeployed.push(account_id);
87 }
88 }
89 if undeployed.is_empty() {
90 return Ok(());
91 }
92
93 if self.chain_charges_fees().await? {
94 let mut funded = Vec::with_capacity(undeployed.len());
97 let mut unfunded = Vec::new();
98 for account_id in undeployed.iter().copied() {
99 match self.take_funding(account_id) {
100 Some(note) => funded.push((account_id, note)),
101 None => unfunded.push(account_id),
102 }
103 }
104
105 if !unfunded.is_empty() {
107 funded.extend(self.funder()?.fund(&unfunded).await?);
108 }
109
110 return self.deploy_by_consuming(&funded).await;
111 }
112
113 let mut tx_ids = Vec::with_capacity(undeployed.len());
114 for account_id in undeployed {
115 let request = TransactionRequestBuilder::new()
116 .build()
117 .context("failed to build the deploy transaction request")?;
118 let tx_id =
119 Box::pin(self.submit_new_transaction(account_id, request)).await.with_context(
120 || format!("failed to submit the deploy transaction of {account_id}"),
121 )?;
122 tx_ids.push((account_id, tx_id));
123 }
124
125 self.wait_for_deploys(&tx_ids).await
126 }
127
128 pub async fn deploy_by_consuming(&mut self, funded: &[(AccountId, Note)]) -> Result<()> {
131 let mut tx_ids = Vec::with_capacity(funded.len());
134 for (account_id, note) in funded {
135 let (account_id, note_id) = (*account_id, note.id());
136
137 let request = TransactionRequestBuilder::new()
141 .build_consume_notes(vec![note.clone()])
142 .context("failed to build the funding note consumption request")?;
143 let tx_id =
144 Box::pin(self.submit_new_transaction(account_id, request)).await.with_context(
145 || format!("account {account_id} failed to consume funding note {note_id}"),
146 )?;
147 tx_ids.push((account_id, tx_id));
148 }
149
150 self.wait_for_deploys(&tx_ids).await
151 }
152
153 async fn wait_for_deploys(&mut self, tx_ids: &[(AccountId, TransactionId)]) -> Result<()> {
156 for (account_id, tx_id) in tx_ids.iter().copied() {
157 self.wait_for_tx(tx_id).await.with_context(|| {
158 format!("the deploy transaction of account {account_id} never committed")
159 })?;
160 }
161
162 Ok(())
163 }
164
165 pub async fn chain_charges_fees(&self) -> Result<bool> {
172 let (genesis, _) = self
173 .get_block_header_by_num(BlockNumber::GENESIS)
174 .await?
175 .context("the genesis block header is not in the client's store")?;
176
177 Ok(genesis.fee_parameters().verification_base_fee() != 0)
178 }
179}