aztec_fee/
fee_juice_with_claim.rs

1use async_trait::async_trait;
2use aztec_core::abi::{AbiValue, FunctionSelector, FunctionType};
3use aztec_core::constants::protocol_contract_address;
4use aztec_core::tx::{ExecutionPayload, FunctionCall};
5use aztec_core::types::{AztecAddress, Fr};
6use aztec_core::Error;
7
8use crate::fee_payment_method::FeePaymentMethod;
9use crate::types::L2AmountClaim;
10
11/// Pays transaction fees by claiming Fee Juice from an L1-to-L2 bridge deposit.
12///
13/// This constructs a call to the FeeJuice protocol contract's
14/// `claim_and_end_setup` function, which:
15/// 1. Consumes the L1-to-L2 message (proving the L1 deposit)
16/// 2. Credits the sender's Fee Juice balance
17/// 3. Ends the transaction setup phase (making the balance available for fees)
18///
19/// The claim is placed in the non-revertible phase so the sequencer is
20/// guaranteed to collect fees even if the revertible portion fails.
21pub struct FeeJuicePaymentMethodWithClaim {
22    /// Address of the account claiming and paying fees.
23    sender: AztecAddress,
24    /// Claim data from the L1 bridge deposit.
25    claim: L2AmountClaim,
26}
27
28impl FeeJuicePaymentMethodWithClaim {
29    /// Create a new fee payment method that claims bridged Fee Juice.
30    ///
31    /// `sender` is the account that will pay fees after claiming.
32    /// `claim` contains the L1 bridge deposit data needed for the claim.
33    pub fn new(sender: AztecAddress, claim: L2AmountClaim) -> Self {
34        Self { sender, claim }
35    }
36}
37
38#[async_trait]
39impl FeePaymentMethod for FeeJuicePaymentMethodWithClaim {
40    async fn get_asset(&self) -> Result<AztecAddress, Error> {
41        Ok(protocol_contract_address::fee_juice())
42    }
43
44    async fn get_fee_payer(&self) -> Result<AztecAddress, Error> {
45        Ok(self.sender)
46    }
47
48    async fn get_fee_execution_payload(&self) -> Result<ExecutionPayload, Error> {
49        let call = FunctionCall {
50            to: protocol_contract_address::fee_juice(),
51            selector: FunctionSelector::from_signature(
52                "claim_and_end_setup((Field),u128,Field,Field)",
53            ),
54            args: vec![
55                AbiValue::Field(self.sender.0),
56                AbiValue::Integer(self.claim.claim_amount as i128),
57                AbiValue::Field(self.claim.claim_secret),
58                AbiValue::Field(Fr::from(self.claim.message_leaf_index)),
59            ],
60            function_type: FunctionType::Private,
61            is_static: false,
62            hide_msg_sender: false,
63        };
64
65        Ok(ExecutionPayload {
66            calls: vec![call],
67            auth_witnesses: vec![],
68            capsules: vec![],
69            extra_hashed_args: vec![],
70            fee_payer: Some(self.sender),
71        })
72    }
73}
74
75#[cfg(test)]
76#[allow(clippy::expect_used)]
77mod tests {
78    use super::*;
79
80    fn test_claim() -> L2AmountClaim {
81        L2AmountClaim {
82            claim_amount: 1000,
83            claim_secret: Fr::from(99u64),
84            message_leaf_index: 7,
85        }
86    }
87
88    #[tokio::test]
89    async fn payload_targets_fee_juice_contract() {
90        let sender = AztecAddress(Fr::from(1u64));
91        let method = FeeJuicePaymentMethodWithClaim::new(sender, test_claim());
92        let payload = method.get_fee_execution_payload().await.expect("payload");
93
94        assert_eq!(payload.calls.len(), 1);
95        let call = &payload.calls[0];
96        assert_eq!(call.to, protocol_contract_address::fee_juice());
97        assert_eq!(
98            call.selector,
99            FunctionSelector::from_signature("claim_and_end_setup((Field),u128,Field,Field)")
100        );
101        assert_eq!(call.function_type, FunctionType::Private);
102        assert!(!call.is_static);
103    }
104
105    #[tokio::test]
106    async fn arguments_are_correctly_ordered() {
107        let sender = AztecAddress(Fr::from(1u64));
108        let claim = test_claim();
109        let method = FeeJuicePaymentMethodWithClaim::new(sender, claim.clone());
110        let payload = method.get_fee_execution_payload().await.expect("payload");
111
112        let args = &payload.calls[0].args;
113        assert_eq!(args.len(), 4);
114
115        // arg 0: sender as struct { inner: Field }
116        assert_eq!(args[0], AbiValue::Field(sender.0));
117        // arg 1: claim_amount
118        assert_eq!(args[1], AbiValue::Integer(claim.claim_amount as i128));
119        // arg 2: claim_secret
120        assert_eq!(args[2], AbiValue::Field(claim.claim_secret));
121        // arg 3: message_leaf_index as Field
122        assert_eq!(args[3], AbiValue::Field(Fr::from(claim.message_leaf_index)));
123    }
124
125    #[tokio::test]
126    async fn fee_payer_is_sender() {
127        let sender = AztecAddress(Fr::from(1u64));
128        let method = FeeJuicePaymentMethodWithClaim::new(sender, test_claim());
129
130        assert_eq!(method.get_fee_payer().await.expect("fee payer"), sender);
131
132        let payload = method.get_fee_execution_payload().await.expect("payload");
133        assert_eq!(payload.fee_payer, Some(sender));
134    }
135
136    #[tokio::test]
137    async fn asset_is_fee_juice() {
138        let sender = AztecAddress(Fr::from(1u64));
139        let method = FeeJuicePaymentMethodWithClaim::new(sender, test_claim());
140        assert_eq!(
141            method.get_asset().await.expect("asset"),
142            protocol_contract_address::fee_juice()
143        );
144    }
145}