tempo_precompiles/tip403_registry/
dispatch.rs

1use crate::{
2    Precompile, fill_precompile_output, input_cost, mutate, mutate_void, unknown_selector, view,
3};
4use alloy::{primitives::Address, sol_types::SolCall};
5use revm::precompile::{PrecompileError, PrecompileResult};
6
7use crate::tip403_registry::{ITIP403Registry, TIP403Registry};
8
9impl Precompile for TIP403Registry {
10    fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
11        self.storage
12            .deduct_gas(input_cost(calldata.len()))
13            .map_err(|_| PrecompileError::OutOfGas)?;
14
15        let selector: [u8; 4] = calldata
16            .get(..4)
17            .ok_or_else(|| {
18                PrecompileError::Other("Invalid input: missing function selector".into())
19            })?
20            .try_into()
21            .unwrap();
22
23        let result = match selector {
24            ITIP403Registry::policyIdCounterCall::SELECTOR => {
25                view::<ITIP403Registry::policyIdCounterCall>(calldata, |_call| {
26                    self.policy_id_counter()
27                })
28            }
29            ITIP403Registry::policyDataCall::SELECTOR => {
30                view::<ITIP403Registry::policyDataCall>(calldata, |call| self.policy_data(call))
31            }
32            ITIP403Registry::isAuthorizedCall::SELECTOR => {
33                view::<ITIP403Registry::isAuthorizedCall>(calldata, |call| self.is_authorized(call))
34            }
35            ITIP403Registry::createPolicyCall::SELECTOR => {
36                mutate::<ITIP403Registry::createPolicyCall>(calldata, msg_sender, |s, call| {
37                    self.create_policy(s, call)
38                })
39            }
40            ITIP403Registry::createPolicyWithAccountsCall::SELECTOR => {
41                mutate::<ITIP403Registry::createPolicyWithAccountsCall>(
42                    calldata,
43                    msg_sender,
44                    |s, call| self.create_policy_with_accounts(s, call),
45                )
46            }
47            ITIP403Registry::setPolicyAdminCall::SELECTOR => {
48                mutate_void::<ITIP403Registry::setPolicyAdminCall>(
49                    calldata,
50                    msg_sender,
51                    |s, call| self.set_policy_admin(s, call),
52                )
53            }
54            ITIP403Registry::modifyPolicyWhitelistCall::SELECTOR => {
55                mutate_void::<ITIP403Registry::modifyPolicyWhitelistCall>(
56                    calldata,
57                    msg_sender,
58                    |s, call| self.modify_policy_whitelist(s, call),
59                )
60            }
61            ITIP403Registry::modifyPolicyBlacklistCall::SELECTOR => {
62                mutate_void::<ITIP403Registry::modifyPolicyBlacklistCall>(
63                    calldata,
64                    msg_sender,
65                    |s, call| self.modify_policy_blacklist(s, call),
66                )
67            }
68            _ => unknown_selector(selector, self.storage.gas_used(), self.storage.spec()),
69        };
70
71        result.map(|res| fill_precompile_output(res, &mut self.storage))
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::{
79        storage::{StorageCtx, hashmap::HashMapStorageProvider},
80        test_util::{assert_full_coverage, check_selector_coverage},
81    };
82    use alloy::sol_types::SolValue;
83    use tempo_chainspec::hardfork::TempoHardfork;
84    use tempo_contracts::precompiles::ITIP403Registry::ITIP403RegistryCalls;
85
86    #[test]
87    fn test_is_authorized_precompile() -> eyre::Result<()> {
88        let mut storage = HashMapStorageProvider::new(1);
89        let user = Address::random();
90        StorageCtx::enter(&mut storage, || {
91            let mut registry = TIP403Registry::new();
92
93            // Test policy 1 (always allow)
94            let call = ITIP403Registry::isAuthorizedCall { policyId: 1, user };
95            let calldata = call.abi_encode();
96            let result = registry.call(&calldata, Address::ZERO);
97
98            assert!(result.is_ok());
99            let output = result.unwrap();
100            let decoded: bool =
101                ITIP403Registry::isAuthorizedCall::abi_decode_returns(&output.bytes).unwrap();
102            assert!(decoded);
103
104            Ok(())
105        })
106    }
107
108    #[test]
109    fn test_create_policy_precompile() -> eyre::Result<()> {
110        let mut storage = HashMapStorageProvider::new(1);
111        let admin = Address::random();
112        StorageCtx::enter(&mut storage, || {
113            let mut registry = TIP403Registry::new();
114
115            let call = ITIP403Registry::createPolicyCall {
116                admin,
117                policyType: ITIP403Registry::PolicyType::WHITELIST,
118            };
119            let calldata = call.abi_encode();
120            let result = registry.call(&calldata, admin);
121
122            assert!(result.is_ok());
123            let output = result.unwrap();
124            let decoded: u64 =
125                ITIP403Registry::createPolicyCall::abi_decode_returns(&output.bytes).unwrap();
126            assert_eq!(decoded, 2); // First created policy ID
127
128            Ok(())
129        })
130    }
131
132    #[test]
133    fn test_policy_id_counter_initialization() -> eyre::Result<()> {
134        let mut storage = HashMapStorageProvider::new(1);
135        let sender = Address::random();
136        StorageCtx::enter(&mut storage, || {
137            let mut registry = TIP403Registry::new();
138
139            // Get initial counter
140            let counter_call = ITIP403Registry::policyIdCounterCall {};
141            let calldata = counter_call.abi_encode();
142            let result = registry.call(&calldata, sender).unwrap();
143            let counter = u64::abi_decode(&result.bytes).unwrap();
144            assert_eq!(counter, 2); // Counter starts at 2 (policies 0 and 1 are reserved)
145
146            Ok(())
147        })
148    }
149
150    #[test]
151    fn test_create_policy_with_accounts() -> eyre::Result<()> {
152        let mut storage = HashMapStorageProvider::new(1);
153        let admin = Address::random();
154        let account1 = Address::random();
155        let account2 = Address::random();
156        let other_account = Address::random();
157        StorageCtx::enter(&mut storage, || {
158            let mut registry = TIP403Registry::new();
159
160            let accounts = vec![account1, account2];
161            let call = ITIP403Registry::createPolicyWithAccountsCall {
162                admin,
163                policyType: ITIP403Registry::PolicyType::WHITELIST,
164                accounts,
165            };
166            let calldata = call.abi_encode();
167            let result = registry.call(&calldata, admin).unwrap();
168
169            let policy_id: u64 =
170                ITIP403Registry::createPolicyWithAccountsCall::abi_decode_returns(&result.bytes)
171                    .unwrap();
172            assert_eq!(policy_id, 2);
173
174            // Check that accounts are authorized
175            let is_auth_call = ITIP403Registry::isAuthorizedCall {
176                policyId: policy_id,
177                user: account1,
178            };
179            let calldata = is_auth_call.abi_encode();
180            let result = registry.call(&calldata, admin).unwrap();
181            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
182            assert!(is_authorized);
183
184            let is_auth_call = ITIP403Registry::isAuthorizedCall {
185                policyId: policy_id,
186                user: account2,
187            };
188            let calldata = is_auth_call.abi_encode();
189            let result = registry.call(&calldata, admin).unwrap();
190            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
191            assert!(is_authorized);
192
193            // Check that other accounts are not authorized
194            let is_auth_call = ITIP403Registry::isAuthorizedCall {
195                policyId: policy_id,
196                user: other_account,
197            };
198            let calldata = is_auth_call.abi_encode();
199            let result = registry.call(&calldata, admin).unwrap();
200            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
201            assert!(!is_authorized);
202
203            Ok(())
204        })
205    }
206
207    #[test]
208    fn test_blacklist_policy() -> eyre::Result<()> {
209        let mut storage = HashMapStorageProvider::new(1);
210        let admin = Address::random();
211        let blocked_account = Address::random();
212        let allowed_account = Address::random();
213        StorageCtx::enter(&mut storage, || {
214            let mut registry = TIP403Registry::new();
215
216            // Create blacklist policy
217            let call = ITIP403Registry::createPolicyCall {
218                admin,
219                policyType: ITIP403Registry::PolicyType::BLACKLIST,
220            };
221            let calldata = call.abi_encode();
222            let result = registry.call(&calldata, admin).unwrap();
223            let policy_id: u64 =
224                ITIP403Registry::createPolicyCall::abi_decode_returns(&result.bytes).unwrap();
225
226            // Initially, all accounts should be authorized (empty blacklist)
227            let is_auth_call = ITIP403Registry::isAuthorizedCall {
228                policyId: policy_id,
229                user: blocked_account,
230            };
231            let calldata = is_auth_call.abi_encode();
232            let result = registry.call(&calldata, admin).unwrap();
233            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
234            assert!(is_authorized);
235
236            // Add account to blacklist
237            let modify_call = ITIP403Registry::modifyPolicyBlacklistCall {
238                policyId: policy_id,
239                account: blocked_account,
240                restricted: true,
241            };
242            let calldata = modify_call.abi_encode();
243            registry.call(&calldata, admin).unwrap();
244
245            // Now blocked account should not be authorized
246            let is_auth_call = ITIP403Registry::isAuthorizedCall {
247                policyId: policy_id,
248                user: blocked_account,
249            };
250            let calldata = is_auth_call.abi_encode();
251            let result = registry.call(&calldata, admin).unwrap();
252            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
253            assert!(!is_authorized);
254
255            // Other accounts should still be authorized
256            let is_auth_call = ITIP403Registry::isAuthorizedCall {
257                policyId: policy_id,
258                user: allowed_account,
259            };
260            let calldata = is_auth_call.abi_encode();
261            let result = registry.call(&calldata, admin).unwrap();
262            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
263            assert!(is_authorized);
264
265            // Remove account from blacklist
266            let modify_call = ITIP403Registry::modifyPolicyBlacklistCall {
267                policyId: policy_id,
268                account: blocked_account,
269                restricted: false,
270            };
271            let calldata = modify_call.abi_encode();
272            registry.call(&calldata, admin).unwrap();
273
274            // Account should be authorized again
275            let is_auth_call = ITIP403Registry::isAuthorizedCall {
276                policyId: policy_id,
277                user: blocked_account,
278            };
279            let calldata = is_auth_call.abi_encode();
280            let result = registry.call(&calldata, admin).unwrap();
281            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
282            assert!(is_authorized);
283
284            Ok(())
285        })
286    }
287
288    #[test]
289    fn test_modify_policy_whitelist() -> eyre::Result<()> {
290        let mut storage = HashMapStorageProvider::new(1);
291        let admin = Address::random();
292        let account1 = Address::random();
293        let account2 = Address::random();
294        StorageCtx::enter(&mut storage, || {
295            let mut registry = TIP403Registry::new();
296
297            // Create whitelist policy
298            let call = ITIP403Registry::createPolicyCall {
299                admin,
300                policyType: ITIP403Registry::PolicyType::WHITELIST,
301            };
302            let calldata = call.abi_encode();
303            let result = registry.call(&calldata, admin).unwrap();
304            let policy_id: u64 =
305                ITIP403Registry::createPolicyCall::abi_decode_returns(&result.bytes).unwrap();
306
307            // Add multiple accounts to whitelist
308            let modify_call1 = ITIP403Registry::modifyPolicyWhitelistCall {
309                policyId: policy_id,
310                account: account1,
311                allowed: true,
312            };
313            let calldata = modify_call1.abi_encode();
314            registry.call(&calldata, admin).unwrap();
315
316            let modify_call2 = ITIP403Registry::modifyPolicyWhitelistCall {
317                policyId: policy_id,
318                account: account2,
319                allowed: true,
320            };
321            let calldata = modify_call2.abi_encode();
322            registry.call(&calldata, admin).unwrap();
323
324            // Both accounts should be authorized
325            let is_auth_call = ITIP403Registry::isAuthorizedCall {
326                policyId: policy_id,
327                user: account1,
328            };
329            let calldata = is_auth_call.abi_encode();
330            let result = registry.call(&calldata, admin).unwrap();
331            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
332            assert!(is_authorized);
333
334            let is_auth_call = ITIP403Registry::isAuthorizedCall {
335                policyId: policy_id,
336                user: account2,
337            };
338            let calldata = is_auth_call.abi_encode();
339            let result = registry.call(&calldata, admin).unwrap();
340            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
341            assert!(is_authorized);
342
343            // Remove one account from whitelist
344            let modify_call = ITIP403Registry::modifyPolicyWhitelistCall {
345                policyId: policy_id,
346                account: account1,
347                allowed: false,
348            };
349            let calldata = modify_call.abi_encode();
350            registry.call(&calldata, admin).unwrap();
351
352            // Account1 should not be authorized, account2 should still be
353            let is_auth_call = ITIP403Registry::isAuthorizedCall {
354                policyId: policy_id,
355                user: account1,
356            };
357            let calldata = is_auth_call.abi_encode();
358            let result = registry.call(&calldata, admin).unwrap();
359            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
360            assert!(!is_authorized);
361
362            let is_auth_call = ITIP403Registry::isAuthorizedCall {
363                policyId: policy_id,
364                user: account2,
365            };
366            let calldata = is_auth_call.abi_encode();
367            let result = registry.call(&calldata, admin).unwrap();
368            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
369            assert!(is_authorized);
370
371            Ok(())
372        })
373    }
374
375    #[test]
376    fn test_set_policy_admin() -> eyre::Result<()> {
377        let mut storage = HashMapStorageProvider::new(1);
378        let admin = Address::random();
379        let new_admin = Address::random();
380        StorageCtx::enter(&mut storage, || {
381            let mut registry = TIP403Registry::new();
382
383            // Create a policy
384            let call = ITIP403Registry::createPolicyCall {
385                admin,
386                policyType: ITIP403Registry::PolicyType::WHITELIST,
387            };
388            let calldata = call.abi_encode();
389            let result = registry.call(&calldata, admin).unwrap();
390            let policy_id: u64 =
391                ITIP403Registry::createPolicyCall::abi_decode_returns(&result.bytes).unwrap();
392
393            // Get initial policy data
394            let policy_data_call = ITIP403Registry::policyDataCall {
395                policyId: policy_id,
396            };
397            let calldata = policy_data_call.abi_encode();
398            let result = registry.call(&calldata, admin).unwrap();
399            let policy_data =
400                ITIP403Registry::policyDataCall::abi_decode_returns(&result.bytes).unwrap();
401            assert_eq!(policy_data.admin, admin);
402
403            // Change policy admin
404            let set_admin_call = ITIP403Registry::setPolicyAdminCall {
405                policyId: policy_id,
406                admin: new_admin,
407            };
408            let calldata = set_admin_call.abi_encode();
409            registry.call(&calldata, admin).unwrap();
410
411            // Verify policy admin was changed
412            let policy_data_call = ITIP403Registry::policyDataCall {
413                policyId: policy_id,
414            };
415            let calldata = policy_data_call.abi_encode();
416            let result = registry.call(&calldata, admin).unwrap();
417            let policy_data =
418                ITIP403Registry::policyDataCall::abi_decode_returns(&result.bytes).unwrap();
419            assert_eq!(policy_data.admin, new_admin);
420
421            Ok(())
422        })
423    }
424
425    #[test]
426    fn test_special_policy_ids() -> eyre::Result<()> {
427        let mut storage = HashMapStorageProvider::new(1);
428        let user = Address::random();
429        StorageCtx::enter(&mut storage, || {
430            let mut registry = TIP403Registry::new();
431
432            // Test policy 0 (always deny)
433            let is_auth_call = ITIP403Registry::isAuthorizedCall { policyId: 0, user };
434            let calldata = is_auth_call.abi_encode();
435            let result = registry.call(&calldata, Address::ZERO).unwrap();
436            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
437            assert!(!is_authorized);
438
439            // Test policy 1 (always allow)
440            let is_auth_call = ITIP403Registry::isAuthorizedCall { policyId: 1, user };
441            let calldata = is_auth_call.abi_encode();
442            let result = registry.call(&calldata, Address::ZERO).unwrap();
443            let is_authorized = bool::abi_decode(&result.bytes).unwrap();
444            assert!(is_authorized);
445
446            Ok(())
447        })
448    }
449
450    #[test]
451    fn test_invalid_selector() -> eyre::Result<()> {
452        let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Moderato);
453        let sender = Address::random();
454        StorageCtx::enter(&mut storage, || {
455            let mut registry = TIP403Registry::new();
456
457            // Test with invalid selector - should return Ok with reverted status
458            let invalid_data = vec![0x12, 0x34, 0x56, 0x78];
459            let result = registry.call(&invalid_data, sender);
460            assert!(result.is_ok());
461            assert!(result.unwrap().reverted);
462
463            // Test with insufficient data
464            let short_data = vec![0x12, 0x34];
465            let result = registry.call(&short_data, sender);
466            assert!(result.is_err());
467
468            Ok(())
469        })
470    }
471
472    #[test]
473    fn test_create_multiple_policies() -> eyre::Result<()> {
474        let mut storage = HashMapStorageProvider::new(1);
475        let admin = Address::random();
476        StorageCtx::enter(&mut storage, || {
477            let mut registry = TIP403Registry::new();
478
479            // Create multiple policies with different types
480            let whitelist_call = ITIP403Registry::createPolicyCall {
481                admin,
482                policyType: ITIP403Registry::PolicyType::WHITELIST,
483            };
484            let calldata = whitelist_call.abi_encode();
485            let result = registry.call(&calldata, admin).unwrap();
486            let whitelist_id: u64 =
487                ITIP403Registry::createPolicyCall::abi_decode_returns(&result.bytes).unwrap();
488
489            let blacklist_call = ITIP403Registry::createPolicyCall {
490                admin,
491                policyType: ITIP403Registry::PolicyType::BLACKLIST,
492            };
493            let calldata = blacklist_call.abi_encode();
494            let result = registry.call(&calldata, admin).unwrap();
495            let blacklist_id: u64 =
496                ITIP403Registry::createPolicyCall::abi_decode_returns(&result.bytes).unwrap();
497
498            // Verify IDs are sequential
499            assert_eq!(whitelist_id, 2);
500            assert_eq!(blacklist_id, 3);
501
502            // Verify counter has been updated
503            let counter_call = ITIP403Registry::policyIdCounterCall {};
504            let calldata = counter_call.abi_encode();
505            let result = registry.call(&calldata, admin).unwrap();
506            let counter = u64::abi_decode(&result.bytes).unwrap();
507            assert_eq!(counter, 4);
508
509            Ok(())
510        })
511    }
512
513    #[test]
514    fn test_selector_coverage() -> eyre::Result<()> {
515        let mut storage = HashMapStorageProvider::new(1);
516        StorageCtx::enter(&mut storage, || {
517            let mut registry = TIP403Registry::new();
518
519            let unsupported = check_selector_coverage(
520                &mut registry,
521                ITIP403RegistryCalls::SELECTORS,
522                "ITIP403Registry",
523                ITIP403RegistryCalls::name_by_selector,
524            );
525
526            assert_full_coverage([unsupported]);
527
528            Ok(())
529        })
530    }
531}