Skip to main content

tempo_precompiles/storage_credits/
dispatch.rs

1//! ABI dispatch for the storage credits precompile.
2
3use crate::{
4    Precompile, charge_input_cost, dispatch_call, mutate_void, storage_credits::StorageCredits,
5    view,
6};
7use alloy::{primitives::Address, sol_types::SolInterface};
8use revm::precompile::PrecompileResult;
9use tempo_contracts::precompiles::IStorageCredits::IStorageCreditsCalls;
10
11impl Precompile for StorageCredits {
12    fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
13        if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
14            return err;
15        }
16
17        dispatch_call(
18            calldata,
19            &[],
20            IStorageCreditsCalls::abi_decode,
21            |call| match call {
22                IStorageCreditsCalls::balanceOf(call) => view(call, |c| self.balance_of(c.account)),
23                IStorageCreditsCalls::modeOf(call) => {
24                    view(call, |c| self.mode_of(c.account).map(Into::into))
25                }
26                IStorageCreditsCalls::budgetOf(call) => view(call, |c| self.budget_of(c.account)),
27                IStorageCreditsCalls::setMode(call) => {
28                    mutate_void(call, msg_sender, |sender, c| {
29                        self.set_mode(sender, c.newMode)
30                    })
31                }
32                IStorageCreditsCalls::setBudget(call) => {
33                    mutate_void(call, msg_sender, |sender, c| {
34                        self.set_budget(sender, c.creditBudget)
35                    })
36                }
37            },
38        )
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::{
46        storage::{StorageCtx, hashmap::HashMapStorageProvider},
47        test_util::{assert_full_coverage, check_selector_coverage},
48    };
49    use alloy::sol_types::SolCall;
50    use tempo_contracts::precompiles::{IStorageCredits, StorageCreditsError};
51
52    #[test]
53    fn test_storage_credits_selector_coverage() -> eyre::Result<()> {
54        let mut storage = HashMapStorageProvider::new(1);
55        StorageCtx::enter(&mut storage, || {
56            let mut storage_credits_precompile = StorageCredits::new();
57
58            let unsupported = check_selector_coverage(
59                &mut storage_credits_precompile,
60                IStorageCreditsCalls::SELECTORS,
61                "IStorageCredits",
62                IStorageCreditsCalls::name_by_selector,
63            );
64
65            assert_full_coverage([unsupported]);
66            Ok(())
67        })
68    }
69
70    #[test]
71    fn test_storage_credits_set_budget_zero_stays_direct() -> eyre::Result<()> {
72        let caller = Address::repeat_byte(0x11);
73        let mut storage = HashMapStorageProvider::new(1);
74
75        StorageCtx::enter(&mut storage, || {
76            let mut storage_credits_precompile = StorageCredits::new();
77
78            let set_budget = IStorageCredits::setBudgetCall { creditBudget: 7 };
79            let output = storage_credits_precompile.call(&set_budget.abi_encode(), caller)?;
80            assert!(!output.is_revert());
81
82            let mode = storage_credits_precompile.call(
83                &IStorageCredits::modeOfCall { account: caller }.abi_encode(),
84                caller,
85            )?;
86            assert_eq!(
87                IStorageCredits::modeOfCall::abi_decode_returns(&mode.bytes)?,
88                IStorageCredits::Mode::Direct
89            );
90
91            let budget = storage_credits_precompile.call(
92                &IStorageCredits::budgetOfCall { account: caller }.abi_encode(),
93                caller,
94            )?;
95            assert_eq!(
96                IStorageCredits::budgetOfCall::abi_decode_returns(&budget.bytes)?,
97                7
98            );
99
100            let zero_budget = IStorageCredits::setBudgetCall { creditBudget: 0 };
101            let output = storage_credits_precompile.call(&zero_budget.abi_encode(), caller)?;
102            assert!(!output.is_revert());
103
104            let mode = storage_credits_precompile.call(
105                &IStorageCredits::modeOfCall { account: caller }.abi_encode(),
106                caller,
107            )?;
108            assert_eq!(
109                IStorageCredits::modeOfCall::abi_decode_returns(&mode.bytes)?,
110                IStorageCredits::Mode::Direct,
111                "setBudget(0) keeps Direct selected with a zero spend budget"
112            );
113
114            let budget = storage_credits_precompile.call(
115                &IStorageCredits::budgetOfCall { account: caller }.abi_encode(),
116                caller,
117            )?;
118            assert_eq!(
119                IStorageCredits::budgetOfCall::abi_decode_returns(&budget.bytes)?,
120                0
121            );
122
123            Ok(())
124        })
125    }
126
127    #[test]
128    fn test_storage_credits_set_mode_rejects_reserved_mode() -> eyre::Result<()> {
129        let caller = Address::repeat_byte(0x33);
130        let mut storage = HashMapStorageProvider::new(1);
131
132        StorageCtx::enter(&mut storage, || {
133            let mut storage_credits_precompile = StorageCredits::new();
134            let mut calldata = IStorageCredits::setModeCall {
135                newMode: IStorageCredits::Mode::Refund,
136            }
137            .abi_encode();
138            *calldata
139                .last_mut()
140                .expect("setMode ABI calldata must contain the enum word") = 3;
141
142            let output = storage_credits_precompile.call(&calldata, caller)?;
143            assert!(output.is_revert());
144            assert_eq!(
145                &output.bytes[..4],
146                StorageCreditsError::invalid_mode().selector().as_slice()
147            );
148
149            Ok(())
150        })
151    }
152}