1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! Funding support for running the test helpers against a fee-charging chain.
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use anyhow::{Context, Result};
use miden_protocol::Felt;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use super::common::{TestClient, wait_for_tx};
use crate::note::Note;
use crate::transaction::{TransactionId, TransactionRequestBuilder};
/// Makes accounts able to pay their own transaction fees.
#[async_trait::async_trait(?Send)]
pub trait FeeFunder: Send + Sync + fmt::Debug {
/// Pays every account in `account_ids` enough to cover its own fees, returning each paired
/// with the note carrying its funds.
///
/// Taken together so one transaction can pay them all; returned rather than consumed so each
/// account's own next transaction spends its note.
async fn fund(&self, account_ids: &[AccountId]) -> Result<Vec<(AccountId, Note)>>;
}
impl TestClient {
/// Pays `account_ids` what they need to cover their own fees, if the chain charges any.
///
/// Each note is held until the account's next transaction, which consumes it and is thereby
/// also its deploy. Does nothing on a fee-free chain.
pub async fn fund_if_needed(&mut self, account_ids: &[AccountId]) -> Result<()> {
if !self.chain_charges_fees().await? {
return Ok(());
}
let funded = self.funder()?.fund(account_ids).await?;
self.stash_funding(funded);
Ok(())
}
/// Returns the funder, or an error naming what to supply when the chain needs one.
fn funder(&self) -> Result<Arc<dyn FeeFunder>> {
self.fee_funder().cloned().context(
"this chain charges a transaction fee, so every account a test creates has to be \
funded before it can transact, but this client has no fee funder. Supply the funder \
wallets to draw from (see the integration tests' `--funders` argument)",
)
}
/// Deploys `account_id` on-chain, whether or not the chain charges fees.
pub async fn deploy_account(&mut self, account_id: AccountId) -> Result<()> {
self.deploy_accounts(&[account_id]).await
}
/// Deploys `account_ids` on-chain, whether or not the chain charges fees. Already-deployed
/// accounts are left alone.
///
/// Taken together so the funder pays once and the deploys share a single wait.
pub async fn deploy_accounts(&mut self, account_ids: &[AccountId]) -> Result<()> {
let mut undeployed = Vec::with_capacity(account_ids.len());
for account_id in account_ids.iter().copied() {
// A zero nonce is what marks an account as never having transacted, so it reads the
// nonce alone rather than reconstructing the account.
let nonce =
self.account_reader(account_id).nonce().await.with_context(|| {
format!("account {account_id} is not tracked by the client")
})?;
if nonce == Felt::ZERO {
undeployed.push(account_id);
}
}
if undeployed.is_empty() {
return Ok(());
}
if self.chain_charges_fees().await? {
// Deploying on demand means there is no later transaction to fold the funding into,
// so the notes are consumed here.
let mut funded = Vec::with_capacity(undeployed.len());
for account_id in &undeployed {
match self.take_funding(*account_id) {
Some(note) => funded.push((*account_id, note)),
None => funded.extend(self.funder()?.fund(&[*account_id]).await?),
}
}
return self.deploy_by_consuming(&funded).await;
}
let mut tx_ids = Vec::with_capacity(undeployed.len());
for account_id in undeployed {
let request = TransactionRequestBuilder::new()
.build()
.context("failed to build the deploy transaction request")?;
let tx_id =
Box::pin(self.submit_new_transaction(account_id, request)).await.with_context(
|| format!("failed to submit the deploy transaction of {account_id}"),
)?;
tx_ids.push((account_id, tx_id));
}
self.wait_for_deploys(&tx_ids).await
}
/// Deploys each account by consuming the note paired with it, a note carrying enough of the
/// native fee asset for the deploy to settle its own fee.
pub async fn deploy_by_consuming(&mut self, funded: &[(AccountId, Note)]) -> Result<()> {
// Every deploy is submitted before any of them is waited on, so they settle in as few
// blocks as the node packs them into rather than one block apiece.
let mut tx_ids = Vec::with_capacity(funded.len());
for (account_id, note) in funded {
let (account_id, note_id) = (*account_id, note.id());
// Consumed as an unauthenticated input, so the funder's transaction only has to have
// reached the mempool. This doubles as the deploy, paying its fee out of the note it
// just consumed.
let request = TransactionRequestBuilder::new()
.build_consume_notes(vec![note.clone()])
.context("failed to build the funding note consumption request")?;
let tx_id =
Box::pin(self.submit_new_transaction(account_id, request)).await.with_context(
|| format!("account {account_id} failed to consume funding note {note_id}"),
)?;
tx_ids.push((account_id, tx_id));
}
self.wait_for_deploys(&tx_ids).await
}
/// Waits for every deploy transaction to commit, so the test that follows does not see the
/// deploys and funding notes in its own sync.
async fn wait_for_deploys(&mut self, tx_ids: &[(AccountId, TransactionId)]) -> Result<()> {
for (account_id, tx_id) in tx_ids.iter().copied() {
wait_for_tx(self, tx_id).await.with_context(|| {
format!("the deploy transaction of account {account_id} never committed")
})?;
}
Ok(())
}
/// Returns whether the chain charges a non-zero fee per transaction, read from the genesis
/// header.
///
/// Exposed because a few invariants only hold fee-free: paying a fee is itself an account
/// state change, so asserting a transaction left a commitment untouched only holds on a
/// fee-free chain.
pub async fn chain_charges_fees(&self) -> Result<bool> {
let (genesis, _) = self
.get_block_header_by_num(BlockNumber::GENESIS)
.await?
.context("the genesis block header is not in the client's store")?;
Ok(genesis.fee_parameters().verification_base_fee() != 0)
}
}