Skip to main content

tempo_precompiles/address_registry/
dispatch.rs

1use crate::{
2    Precompile, SelectorSchedule, address_registry::AddressRegistry, charge_input_cost,
3    dispatch_call, mutate, view,
4};
5use alloy::{
6    primitives::Address,
7    sol_types::{SolCall, SolInterface},
8};
9use revm::precompile::PrecompileResult;
10use tempo_chainspec::hardfork::TempoHardfork;
11use tempo_contracts::precompiles::IAddressRegistry::{self, IAddressRegistryCalls};
12use tempo_primitives::{MasterId, TempoAddressExt, UserTag};
13
14/// Selectors introduced at T5 (TIP-1035).
15const T5_ADDED: &[[u8; 4]] = &[IAddressRegistry::isImplicitlyApprovedCall::SELECTOR];
16
17impl Precompile for AddressRegistry {
18    fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
19        if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
20            return err;
21        }
22
23        dispatch_call(
24            calldata,
25            &[SelectorSchedule::new(TempoHardfork::T5).with_added(T5_ADDED)],
26            IAddressRegistryCalls::abi_decode,
27            |call| match call {
28                // Registration
29                IAddressRegistryCalls::registerVirtualMaster(call) => {
30                    mutate(call, msg_sender, |s, c| self.register_virtual_master(s, c))
31                }
32                // View functions
33                IAddressRegistryCalls::getMaster(call) => view(call, |c| {
34                    Ok(self.get_master(c.masterId)?.unwrap_or(Address::ZERO))
35                }),
36                IAddressRegistryCalls::resolveRecipient(call) => {
37                    view(call, |c| self.resolve_recipient(c.to))
38                }
39                IAddressRegistryCalls::resolveVirtualAddress(call) => {
40                    view(call, |c| self.resolve_virtual_address(c.virtualAddr))
41                }
42                // Pure functions
43                IAddressRegistryCalls::isVirtualAddress(call) => {
44                    view(call, |c| Ok(c.addr.is_virtual()))
45                }
46                IAddressRegistryCalls::decodeVirtualAddress(call) => view(call, |c| {
47                    let (is_virtual, master_id, user_tag) = match c.addr.decode_virtual() {
48                        Some((mid, tag)) => (true, mid, tag),
49                        None => (false, MasterId::ZERO, UserTag::ZERO),
50                    };
51                    Ok((is_virtual, master_id, user_tag).into())
52                }),
53                IAddressRegistryCalls::isImplicitlyApproved(call) => {
54                    view(call, |c| Ok(self.is_implicitly_approved(c.addr)))
55                }
56            },
57        )
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::{
65        address_registry::IAddressRegistry,
66        storage::{StorageCtx, hashmap::HashMapStorageProvider},
67        test_util::{assert_full_coverage, check_selector_coverage},
68    };
69    use alloy::sol_types::{SolCall, SolError, SolValue};
70    use tempo_chainspec::hardfork::TempoHardfork;
71
72    #[test]
73    fn test_selector_coverage() -> eyre::Result<()> {
74        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T5);
75        StorageCtx::enter(&mut storage, || {
76            let mut registry = AddressRegistry::new();
77
78            let unsupported = check_selector_coverage(
79                &mut registry,
80                IAddressRegistryCalls::SELECTORS,
81                "IAddressRegistry",
82                IAddressRegistryCalls::name_by_selector,
83            );
84
85            assert_full_coverage([unsupported]);
86
87            Ok(())
88        })
89    }
90
91    #[test]
92    fn test_is_implicitly_approved_selector_gated_pre_t5() -> eyre::Result<()> {
93        // Pre-T5: the isImplicitlyApproved selector must be treated as unknown.
94        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T4);
95        StorageCtx::enter(&mut storage, || {
96            let mut registry = AddressRegistry::new();
97            let call = IAddressRegistry::isImplicitlyApprovedCall {
98                addr: Address::ZERO,
99            };
100            let result = registry.call(&call.abi_encode(), Address::ZERO)?;
101            assert!(result.is_revert());
102            assert!(
103                tempo_contracts::precompiles::UnknownFunctionSelector::abi_decode(&result.bytes)
104                    .is_ok()
105            );
106            Ok(())
107        })
108    }
109
110    #[test]
111    fn test_is_implicitly_approved_precompile_t5() -> eyre::Result<()> {
112        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T5);
113        StorageCtx::enter(&mut storage, || {
114            let mut registry = AddressRegistry::new();
115
116            // Listed precompile returns true.
117            let call = IAddressRegistry::isImplicitlyApprovedCall {
118                addr: tempo_contracts::precompiles::TIP_FEE_MANAGER_ADDRESS,
119            };
120            let result = registry.call(&call.abi_encode(), Address::ZERO)?;
121            assert!(!result.is_revert());
122            assert!(bool::abi_decode(&result.bytes).unwrap());
123
124            // Unlisted address returns false.
125            let call = IAddressRegistry::isImplicitlyApprovedCall {
126                addr: Address::random(),
127            };
128            let result = registry.call(&call.abi_encode(), Address::ZERO)?;
129            assert!(!result.is_revert());
130            assert!(!bool::abi_decode(&result.bytes).unwrap());
131
132            Ok(())
133        })
134    }
135
136    #[test]
137    fn test_get_master_precompile() -> eyre::Result<()> {
138        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
139        StorageCtx::enter(&mut storage, || {
140            let mut registry = AddressRegistry::new();
141
142            // Unregistered masterId returns address(0)
143            let call = IAddressRegistry::getMasterCall {
144                masterId: Default::default(),
145            };
146            let result = registry.call(&call.abi_encode(), Address::ZERO)?;
147            assert!(!result.is_revert());
148            let addr = Address::abi_decode(&result.bytes).unwrap();
149            assert_eq!(addr, Address::ZERO);
150
151            Ok(())
152        })
153    }
154
155    #[test]
156    fn test_is_virtual_address_precompile() -> eyre::Result<()> {
157        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
158        StorageCtx::enter(&mut storage, || {
159            let mut registry = AddressRegistry::new();
160
161            // Non-virtual
162            let call = IAddressRegistry::isVirtualAddressCall {
163                addr: Address::random(),
164            };
165            let result = registry.call(&call.abi_encode(), Address::ZERO)?;
166            assert!(!bool::abi_decode(&result.bytes).unwrap());
167
168            // Virtual
169            let mut bytes = [0u8; 20];
170            bytes[4..14].fill(0xFD);
171            let call = IAddressRegistry::isVirtualAddressCall {
172                addr: Address::from(bytes),
173            };
174            let result = registry.call(&call.abi_encode(), Address::ZERO)?;
175            assert!(bool::abi_decode(&result.bytes).unwrap());
176
177            Ok(())
178        })
179    }
180}