Skip to main content

tempo_precompiles/tip20_factory/
dispatch.rs

1//! ABI dispatch for the [`TIP20Factory`] precompile.
2
3use crate::{
4    Precompile, SelectorSchedule, charge_input_cost, dispatch_call, mutate,
5    tip20_factory::TIP20Factory, view,
6};
7use alloy::{
8    primitives::Address,
9    sol_types::{SolCall, SolInterface},
10};
11use revm::precompile::PrecompileResult;
12use tempo_chainspec::hardfork::TempoHardfork;
13use tempo_contracts::precompiles::{ITIP20Factory::ITIP20FactoryCalls, createTokenWithLogoCall};
14
15/// Selectors added at T5: TIP-1026 Token Logo URI factory overload.
16const T5_ADDED: &[[u8; 4]] = &[createTokenWithLogoCall::SELECTOR];
17
18impl Precompile for TIP20Factory {
19    fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
20        if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
21            return err;
22        }
23
24        dispatch_call(
25            calldata,
26            &[SelectorSchedule::new(TempoHardfork::T5).with_added(T5_ADDED)],
27            ITIP20FactoryCalls::abi_decode,
28            |call| match call {
29                ITIP20FactoryCalls::createToken_0(call) => {
30                    mutate(call, msg_sender, |s, c| self.create_token(s, c))
31                }
32                ITIP20FactoryCalls::createToken_1(call) => {
33                    mutate(call, msg_sender, |s, c| self.create_token_with_logo(s, c))
34                }
35                ITIP20FactoryCalls::isTIP20(call) => view(call, |c| self.is_tip20(c.token)),
36                ITIP20FactoryCalls::getTokenAddress(call) => {
37                    view(call, |c| self.get_token_address(c))
38                }
39            },
40        )
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::{
48        storage::{StorageCtx, hashmap::HashMapStorageProvider},
49        test_util::{assert_full_coverage, check_selector_coverage},
50    };
51    use alloy::{
52        primitives::B256,
53        sol_types::{SolCall, SolError},
54    };
55    use tempo_chainspec::hardfork::TempoHardfork;
56    use tempo_contracts::precompiles::{
57        ITIP20Factory::ITIP20FactoryCalls, UnknownFunctionSelector,
58    };
59
60    #[test]
61    fn tip20_factory_test_selector_coverage() {
62        // Use T5 hardfork so T5-gated `createTokenWithLogo` selector is active.
63        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T5);
64
65        StorageCtx::enter(&mut storage, || {
66            let mut factory = TIP20Factory::new();
67
68            let unsupported = check_selector_coverage(
69                &mut factory,
70                ITIP20FactoryCalls::SELECTORS,
71                "ITIP20Factory",
72                ITIP20FactoryCalls::name_by_selector,
73            );
74
75            assert_full_coverage([unsupported]);
76        })
77    }
78
79    #[test]
80    fn test_create_token_with_logo_gated_behind_t5() -> eyre::Result<()> {
81        // Pre-T5: createTokenWithLogo should return unknown selector.
82        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T4);
83        let sender = Address::random();
84
85        StorageCtx::enter(&mut storage, || {
86            let mut factory = TIP20Factory::new();
87
88            let calldata = createTokenWithLogoCall {
89                name: "Logo".to_string(),
90                symbol: "LOGO".to_string(),
91                currency: "USD".to_string(),
92                quoteToken: Address::ZERO,
93                admin: sender,
94                salt: B256::ZERO,
95                logoURI: String::new(),
96            }
97            .abi_encode();
98
99            let result = factory.call(&calldata, sender)?;
100            assert!(result.is_revert());
101            assert!(UnknownFunctionSelector::abi_decode(&result.bytes).is_ok());
102
103            Ok(())
104        })
105    }
106}