Skip to main content

tempo_precompiles/address_registry/
mod.rs

1//! [TIP-1022] virtual address registry precompile. Enabled on `TempoHardfork::T3`.
2//!
3//! Provides on-chain registration of virtual-address masters and resolution of
4//! [TIP-1022] virtual addresses back to their registered master EOA/contract.
5//!
6//! [TIP-1022]: <https://docs.tempo.xyz/protocol/tip1022>
7
8pub mod dispatch;
9
10use crate::{
11    ADDRESS_REGISTRY_ADDRESS,
12    error::Result,
13    storage::{Handler, Mapping},
14};
15use alloy::{
16    primitives::{Address, FixedBytes, keccak256},
17    sol_types::SolValue,
18};
19pub use tempo_contracts::precompiles::{AddrRegistryError, AddrRegistryEvent, IAddressRegistry};
20use tempo_precompiles_macros::{Storable, contract};
21pub use tempo_primitives::{MasterId, TempoAddressExt, UserTag};
22
23/// [TIP-1022] virtual address registry contract.
24///
25/// Maps a 4-byte [`MasterId`] to its registered master address and metadata.
26/// Registration requires a 32-bit proof-of-work to prevent squatting.
27///
28/// The struct fields define the on-chain storage layout; the `#[contract]` macro generates the
29/// storage handlers which provide an ergonomic way to interact with the EVM state.
30///
31/// [TIP-1022]: <https://docs.tempo.xyz/protocol/tip1022>
32#[contract(addr = ADDRESS_REGISTRY_ADDRESS)]
33pub struct AddressRegistry {
34    /// Maps `masterId → RegistryData` (master address + metadata).
35    data: Mapping<MasterId, RegistryData>,
36}
37
38/// Storage record for a registered master. Packed into a single 32-byte slot.
39#[derive(Debug, Clone, Default, Storable)]
40struct RegistryData {
41    /// The EOA or contract that owns this `masterId`.
42    master_address: Address,
43    /// Reserved bytes for future use.
44    reserved: FixedBytes<11>,
45    /// Master type discriminator (currently unused, always `0`).
46    ty: u8,
47}
48
49impl RegistryData {
50    /// Returns the master address, or `None` if the slot is empty (`address(0)`).
51    fn master_address(&self) -> Option<Address> {
52        match self.master_address {
53            Address::ZERO => None,
54            master => Some(master),
55        }
56    }
57}
58
59impl AddressRegistry {
60    /// Initializes the registry contract by setting its bytecode marker.
61    pub fn initialize(&mut self) -> Result<()> {
62        self.__initialize()
63    }
64
65    // ────────────────── Registration ──────────────────
66
67    /// Registers `msg_sender` as a virtual-address master.
68    ///
69    /// The registration hash is `keccak256(abi.encodePacked(msg.sender, salt))`.
70    /// The first 4 bytes MUST be zero (32-bit proof-of-work). `masterId` is bytes `[4:8]`.
71    ///
72    /// # Errors
73    /// - `InvalidMasterAddress` — `msg_sender` is zero, a virtual address, or a TIP-20 token
74    /// - `ProofOfWorkFailed` — the first 4 bytes of the registration hash are not zero
75    /// - `MasterIdCollision` — the derived `masterId` is already registered
76    pub fn register_virtual_master(
77        &mut self,
78        msg_sender: Address,
79        call: IAddressRegistry::registerVirtualMasterCall,
80    ) -> Result<MasterId> {
81        // Validate master address
82        if !msg_sender.is_valid_master() {
83            return Err(AddrRegistryError::invalid_master_address().into());
84        }
85
86        // Compute registration hash: keccak256(abi.encodePacked(msg.sender, salt))
87        let registration_hash = keccak256((msg_sender, call.salt).abi_encode_packed());
88
89        // 32-bit PoW: first 4 bytes must be zero
90        if registration_hash[0..4] != [0u8; 4] {
91            return Err(AddrRegistryError::proof_of_work_failed().into());
92        }
93
94        // masterId = bytes [4:8]
95        let master_id = MasterId::from_slice(&registration_hash[4..8]);
96
97        // Ensure no collisions
98        if let Some(master) = self.data[master_id].read()?.master_address() {
99            return Err(AddrRegistryError::master_id_collision(master).into());
100        }
101
102        // Store the registration
103        self.data[master_id].write(RegistryData {
104            master_address: msg_sender,
105            reserved: FixedBytes::ZERO,
106            ty: 0,
107        })?;
108
109        // Emit event
110        self.emit_event(AddrRegistryEvent::MasterRegistered(
111            IAddressRegistry::MasterRegistered {
112                masterId: master_id,
113                masterAddress: msg_sender,
114            },
115        ))?;
116
117        Ok(master_id)
118    }
119
120    // ────────────────── View Functions ──────────────────
121
122    /// Returns the registered master address for `master_id`, or `None` if unregistered.
123    pub fn get_master(&self, master_id: MasterId) -> Result<Option<Address>> {
124        Ok(self.data[master_id].read()?.master_address())
125    }
126
127    /// Resolves a transfer recipient using virtual address semantics.
128    ///
129    /// Non-virtual addresses are returned unchanged.
130    /// Virtual addresses are resolved to their registered master.
131    ///
132    /// # Errors
133    /// - `VirtualAddressUnregistered` — `to` is a virtual address whose `masterId` is not registered
134    pub fn resolve_recipient(&self, to: Address) -> Result<Address> {
135        // Explicit check because it isn't exclusively a view function.
136        // It is also used by `tip20::Recipient`.
137        if !self.storage.spec().is_t3() {
138            return Ok(to);
139        }
140
141        match to.decode_virtual() {
142            None => Ok(to),
143            Some((master_id, _)) => self
144                .get_master(master_id)?
145                .ok_or(AddrRegistryError::virtual_address_unregistered().into()),
146        }
147    }
148
149    /// Resolves a virtual address to its registered master.
150    ///
151    /// Returns `address(0)` if the address is not virtual or the [`MasterId`] is unregistered.
152    pub fn resolve_virtual_address(&self, addr: Address) -> Result<Address> {
153        match addr.decode_virtual() {
154            None => Ok(Address::ZERO),
155            Some((master_id, _)) => Ok(self.get_master(master_id)?.unwrap_or(Address::ZERO)),
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::{
164        error::TempoPrecompileError,
165        storage::{StorageCtx, hashmap::HashMapStorageProvider},
166        test_util::{VIRTUAL_MASTER, VIRTUAL_SALT},
167    };
168    use alloy_primitives::hex_literal::hex;
169    use tempo_chainspec::hardfork::TempoHardfork;
170
171    #[test]
172    fn test_register_virtual_master() -> eyre::Result<()> {
173        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
174        let (master, salt) = (VIRTUAL_MASTER, VIRTUAL_SALT.into());
175
176        StorageCtx::enter(&mut storage, || {
177            let mut registry = AddressRegistry::new();
178
179            let master_id = registry.register_virtual_master(
180                master,
181                IAddressRegistry::registerVirtualMasterCall { salt },
182            )?;
183
184            assert_eq!(registry.get_master(master_id)?, Some(master));
185
186            Ok(())
187        })
188    }
189
190    #[test]
191    fn test_register_rejects_bad_pow() -> eyre::Result<()> {
192        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
193        let master = Address::random();
194        let bad_salt = FixedBytes::<32>::ZERO;
195
196        StorageCtx::enter(&mut storage, || {
197            let mut registry = AddressRegistry::new();
198
199            let result = registry.register_virtual_master(
200                master,
201                IAddressRegistry::registerVirtualMasterCall { salt: bad_salt },
202            );
203            assert!(matches!(
204                result.unwrap_err(),
205                TempoPrecompileError::AddrRegistryError(AddrRegistryError::ProofOfWorkFailed(_))
206            ));
207
208            Ok(())
209        })
210    }
211
212    #[test]
213    fn test_register_rejects_zero_address() -> eyre::Result<()> {
214        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
215
216        StorageCtx::enter(&mut storage, || {
217            let mut registry = AddressRegistry::new();
218
219            let result = registry.register_virtual_master(
220                Address::ZERO,
221                IAddressRegistry::registerVirtualMasterCall {
222                    salt: FixedBytes::ZERO,
223                },
224            );
225            assert!(matches!(
226                result.unwrap_err(),
227                TempoPrecompileError::AddrRegistryError(AddrRegistryError::InvalidMasterAddress(_))
228            ));
229
230            Ok(())
231        })
232    }
233
234    #[test]
235    fn test_register_rejects_virtual_address_as_master() -> eyre::Result<()> {
236        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
237
238        StorageCtx::enter(&mut storage, || {
239            let mut registry = AddressRegistry::new();
240
241            let result = registry.register_virtual_master(
242                Address::new_virtual(MasterId::ZERO, UserTag::ZERO),
243                IAddressRegistry::registerVirtualMasterCall {
244                    salt: FixedBytes::ZERO,
245                },
246            );
247            assert!(matches!(
248                result.unwrap_err(),
249                TempoPrecompileError::AddrRegistryError(AddrRegistryError::InvalidMasterAddress(_))
250            ));
251
252            Ok(())
253        })
254    }
255
256    #[test]
257    fn test_register_rejects_tip20_address_as_master() -> eyre::Result<()> {
258        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
259        let tip20_addr = crate::PATH_USD_ADDRESS;
260
261        StorageCtx::enter(&mut storage, || {
262            let mut registry = AddressRegistry::new();
263
264            let result = registry.register_virtual_master(
265                tip20_addr,
266                IAddressRegistry::registerVirtualMasterCall {
267                    salt: FixedBytes::ZERO,
268                },
269            );
270            assert!(matches!(
271                result.unwrap_err(),
272                TempoPrecompileError::AddrRegistryError(AddrRegistryError::InvalidMasterAddress(_))
273            ));
274
275            Ok(())
276        })
277    }
278
279    #[test]
280    fn test_register_duplicate_reverts_with_collision() -> eyre::Result<()> {
281        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
282        let (master, salt) = (VIRTUAL_MASTER, VIRTUAL_SALT.into());
283
284        StorageCtx::enter(&mut storage, || {
285            let mut registry = AddressRegistry::new();
286
287            // First registration succeeds
288            registry.register_virtual_master(
289                master,
290                IAddressRegistry::registerVirtualMasterCall { salt },
291            )?;
292
293            // Second registration with same (address, salt) reverts
294            let result = registry.register_virtual_master(
295                master,
296                IAddressRegistry::registerVirtualMasterCall { salt },
297            );
298            assert!(matches!(
299                result.unwrap_err(),
300                TempoPrecompileError::AddrRegistryError(AddrRegistryError::MasterIdCollision(_))
301            ));
302
303            Ok(())
304        })
305    }
306
307    #[test]
308    fn test_is_virtual_address() {
309        assert!(!Address::random().is_virtual());
310        assert!(Address::new_virtual(MasterId::random(), UserTag::random()).is_virtual());
311    }
312
313    #[test]
314    fn test_decode_virtual_address() {
315        let mid = MasterId::random();
316        let tag = UserTag::random();
317        let addr = Address::new_virtual(mid, tag);
318
319        let (master_id, user_tag) = addr.decode_virtual().unwrap();
320        assert_eq!(master_id, mid);
321        assert_eq!(user_tag, tag);
322
323        assert!(Address::random().decode_virtual().is_none());
324    }
325
326    #[test]
327    fn test_resolve_recipient_non_virtual() -> eyre::Result<()> {
328        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
329        let normal_addr = Address::random();
330
331        StorageCtx::enter(&mut storage, || {
332            let registry = AddressRegistry::new();
333
334            let resolved = registry.resolve_recipient(normal_addr)?;
335            assert_eq!(resolved, normal_addr);
336
337            Ok(())
338        })
339    }
340
341    #[test]
342    fn test_resolve_recipient_virtual_unregistered_reverts() -> eyre::Result<()> {
343        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
344        let virtual_addr = Address::new_virtual(MasterId::ZERO, UserTag::ZERO);
345
346        StorageCtx::enter(&mut storage, || {
347            let registry = AddressRegistry::new();
348
349            let result = registry.resolve_recipient(virtual_addr);
350            assert!(matches!(
351                result.unwrap_err(),
352                TempoPrecompileError::AddrRegistryError(
353                    AddrRegistryError::VirtualAddressUnregistered(_)
354                )
355            ));
356
357            Ok(())
358        })
359    }
360
361    #[test]
362    fn test_resolve_recipient_virtual_registered() -> eyre::Result<()> {
363        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
364        let (master, salt) = (VIRTUAL_MASTER, VIRTUAL_SALT.into());
365
366        StorageCtx::enter(&mut storage, || {
367            let mut registry = AddressRegistry::new();
368
369            let master_id = registry.register_virtual_master(
370                master,
371                IAddressRegistry::registerVirtualMasterCall { salt },
372            )?;
373
374            let virtual_addr = Address::new_virtual(master_id, UserTag::new(hex!("010203040506")));
375
376            let resolved = registry.resolve_recipient(virtual_addr)?;
377            assert_eq!(resolved, master);
378
379            Ok(())
380        })
381    }
382
383    #[test]
384    fn test_resolve_virtual_address_view() -> eyre::Result<()> {
385        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
386        let (master, salt) = (VIRTUAL_MASTER, VIRTUAL_SALT.into());
387
388        StorageCtx::enter(&mut storage, || {
389            let mut registry = AddressRegistry::new();
390
391            // Non-virtual → zero
392            assert_eq!(
393                registry.resolve_virtual_address(Address::random())?,
394                Address::ZERO
395            );
396
397            // Unregistered virtual → zero
398            let unregistered = Address::new_virtual(MasterId::ZERO, UserTag::ZERO);
399            assert_eq!(
400                registry.resolve_virtual_address(unregistered)?,
401                Address::ZERO
402            );
403
404            // Registered virtual → master
405            let master_id = registry.register_virtual_master(
406                master,
407                IAddressRegistry::registerVirtualMasterCall { salt },
408            )?;
409            let virtual_addr = Address::new_virtual(master_id, UserTag::new(hex!("aabbccddeeff")));
410            assert_eq!(registry.resolve_virtual_address(virtual_addr)?, master);
411
412            Ok(())
413        })
414    }
415
416    #[test]
417    fn test_resolve_recipient_pre_t3_returns_literal() -> eyre::Result<()> {
418        let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
419        let virtual_addr = Address::new_virtual(MasterId::ZERO, UserTag::ZERO);
420
421        StorageCtx::enter(&mut storage, || {
422            let registry = AddressRegistry::new();
423            assert_eq!(registry.resolve_recipient(virtual_addr)?, virtual_addr);
424            Ok(())
425        })
426    }
427
428    #[test]
429    fn test_is_valid_master_address() {
430        assert!(!Address::ZERO.is_valid_master());
431        assert!(!Address::new_virtual(MasterId::ZERO, UserTag::ZERO).is_valid_master());
432        assert!(!crate::PATH_USD_ADDRESS.is_valid_master());
433        assert!(Address::repeat_byte(0x42).is_valid_master());
434    }
435}