Skip to main content

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        };
63
64        Ok(ExecutionPayload {
65            calls: vec![call],
66            auth_witnesses: vec![],
67            capsules: vec![],
68            extra_hashed_args: vec![],
69            fee_payer: Some(self.sender),
70        })
71    }
72}
73
74#[cfg(test)]
75#[allow(clippy::expect_used)]
76mod tests {
77    use super::*;
78
79    fn test_claim() -> L2AmountClaim {
80        L2AmountClaim {
81            claim_amount: 1000,
82            claim_secret: Fr::from(99u64),
83            message_leaf_index: 7,
84        }
85    }
86
87    #[tokio::test]
88    async fn payload_targets_fee_juice_contract() {
89        let sender = AztecAddress(Fr::from(1u64));
90        let method = FeeJuicePaymentMethodWithClaim::new(sender, test_claim());
91        let payload = method.get_fee_execution_payload().await.expect("payload");
92
93        assert_eq!(payload.calls.len(), 1);
94        let call = &payload.calls[0];
95        assert_eq!(call.to, protocol_contract_address::fee_juice());
96        assert_eq!(
97            call.selector,
98            FunctionSelector::from_signature("claim_and_end_setup((Field),u128,Field,Field)")
99        );
100        assert_eq!(call.function_type, FunctionType::Private);
101        assert!(!call.is_static);
102    }
103
104    #[tokio::test]
105    async fn arguments_are_correctly_ordered() {
106        let sender = AztecAddress(Fr::from(1u64));
107        let claim = test_claim();
108        let method = FeeJuicePaymentMethodWithClaim::new(sender, claim.clone());
109        let payload = method.get_fee_execution_payload().await.expect("payload");
110
111        let args = &payload.calls[0].args;
112        assert_eq!(args.len(), 4);
113
114        // arg 0: sender as struct { inner: Field }
115        assert_eq!(args[0], AbiValue::Field(sender.0));
116        // arg 1: claim_amount
117        assert_eq!(args[1], AbiValue::Integer(claim.claim_amount as i128));
118        // arg 2: claim_secret
119        assert_eq!(args[2], AbiValue::Field(claim.claim_secret));
120        // arg 3: message_leaf_index as Field
121        assert_eq!(args[3], AbiValue::Field(Fr::from(claim.message_leaf_index)));
122    }
123
124    #[tokio::test]
125    async fn fee_payer_is_sender() {
126        let sender = AztecAddress(Fr::from(1u64));
127        let method = FeeJuicePaymentMethodWithClaim::new(sender, test_claim());
128
129        assert_eq!(method.get_fee_payer().await.expect("fee payer"), sender);
130
131        let payload = method.get_fee_execution_payload().await.expect("payload");
132        assert_eq!(payload.fee_payer, Some(sender));
133    }
134
135    #[tokio::test]
136    async fn asset_is_fee_juice() {
137        let sender = AztecAddress(Fr::from(1u64));
138        let method = FeeJuicePaymentMethodWithClaim::new(sender, test_claim());
139        assert_eq!(
140            method.get_asset().await.expect("asset"),
141            protocol_contract_address::fee_juice()
142        );
143    }
144}