tempo_precompiles/tip403_registry/
mod.rs1pub mod dispatch;
2
3pub use tempo_contracts::precompiles::{ITIP403Registry, TIP403RegistryError, TIP403RegistryEvent};
4use tempo_precompiles_macros::{Storable, contract};
5
6use crate::{
7 TIP403_REGISTRY_ADDRESS,
8 error::{Result, TempoPrecompileError},
9 storage::{Mapping, PrecompileStorageProvider},
10};
11use alloy::primitives::{Address, Bytes, IntoLogData};
12use revm::state::Bytecode;
13
14#[contract]
15pub struct TIP403Registry {
16 policy_id_counter: u64,
17 policy_data: Mapping<u64, PolicyData>,
18 policy_set: Mapping<u64, Mapping<Address, bool>>,
19}
20
21#[derive(Debug, Clone, Storable)]
22pub struct PolicyData {
23 pub policy_type: u8,
25 pub admin: Address,
26}
27
28impl<'a, S: PrecompileStorageProvider> TIP403Registry<'a, S> {
29 pub fn new(storage: &'a mut S) -> Self {
33 Self::_new(TIP403_REGISTRY_ADDRESS, storage)
34 }
35
36 pub fn initialize(&mut self) -> Result<()> {
38 self.storage.set_code(
39 TIP403_REGISTRY_ADDRESS,
40 Bytecode::new_legacy(Bytes::from_static(&[0xef])),
41 )?;
42
43 Ok(())
44 }
45
46 pub fn policy_id_counter(&mut self) -> Result<u64> {
48 let counter_val = self.sload_policy_id_counter()?;
49 Ok(counter_val.max(2))
51 }
52
53 pub fn policy_data(
54 &mut self,
55 call: ITIP403Registry::policyDataCall,
56 ) -> Result<ITIP403Registry::policyDataReturn> {
57 let data = self.get_policy_data(call.policyId)?;
58 Ok(ITIP403Registry::policyDataReturn {
59 policyType: data
60 .policy_type
61 .try_into()
62 .map_err(|_| TempoPrecompileError::under_overflow())?,
63 admin: data.admin,
64 })
65 }
66
67 pub fn is_authorized(&mut self, call: ITIP403Registry::isAuthorizedCall) -> Result<bool> {
68 self.is_authorized_internal(call.policyId, call.user)
69 }
70
71 pub fn create_policy(
73 &mut self,
74 msg_sender: Address,
75 call: ITIP403Registry::createPolicyCall,
76 ) -> Result<u64> {
77 let new_policy_id = self.policy_id_counter()?;
78
79 self.sstore_policy_id_counter(
81 new_policy_id
82 .checked_add(1)
83 .ok_or(TempoPrecompileError::under_overflow())?,
84 )?;
85
86 self.sstore_policy_data(
88 new_policy_id,
89 PolicyData {
90 policy_type: call.policyType as u8,
91 admin: call.admin,
92 },
93 )?;
94
95 self.storage.emit_event(
97 TIP403_REGISTRY_ADDRESS,
98 TIP403RegistryEvent::PolicyCreated(ITIP403Registry::PolicyCreated {
99 policyId: new_policy_id,
100 updater: msg_sender,
101 policyType: call.policyType,
102 })
103 .into_log_data(),
104 )?;
105
106 self.storage.emit_event(
107 TIP403_REGISTRY_ADDRESS,
108 TIP403RegistryEvent::PolicyAdminUpdated(ITIP403Registry::PolicyAdminUpdated {
109 policyId: new_policy_id,
110 updater: msg_sender,
111 admin: call.admin,
112 })
113 .into_log_data(),
114 )?;
115
116 Ok(new_policy_id)
117 }
118
119 pub fn create_policy_with_accounts(
120 &mut self,
121 msg_sender: Address,
122 call: ITIP403Registry::createPolicyWithAccountsCall,
123 ) -> Result<u64> {
124 let (admin, policy_type) = (call.admin, call.policyType);
125 let new_policy_id = self.policy_id_counter()?;
126
127 self.sstore_policy_id_counter(
129 new_policy_id
130 .checked_add(1)
131 .ok_or(TempoPrecompileError::under_overflow())?,
132 )?;
133
134 self.set_policy_data(
136 new_policy_id,
137 PolicyData {
138 policy_type: policy_type as u8,
139 admin,
140 },
141 )?;
142
143 for account in call.accounts.iter() {
145 self.set_policy_set(new_policy_id, *account, true)?;
146
147 match policy_type {
148 ITIP403Registry::PolicyType::WHITELIST => {
149 self.storage.emit_event(
150 TIP403_REGISTRY_ADDRESS,
151 TIP403RegistryEvent::WhitelistUpdated(ITIP403Registry::WhitelistUpdated {
152 policyId: new_policy_id,
153 updater: msg_sender,
154 account: *account,
155 allowed: true,
156 })
157 .into_log_data(),
158 )?;
159 }
160 ITIP403Registry::PolicyType::BLACKLIST => {
161 self.storage.emit_event(
162 TIP403_REGISTRY_ADDRESS,
163 TIP403RegistryEvent::BlacklistUpdated(ITIP403Registry::BlacklistUpdated {
164 policyId: new_policy_id,
165 updater: msg_sender,
166 account: *account,
167 restricted: true,
168 })
169 .into_log_data(),
170 )?;
171 }
172 ITIP403Registry::PolicyType::__Invalid => {
173 return Err(TIP403RegistryError::incompatible_policy_type().into());
174 }
175 }
176 }
177
178 self.storage.emit_event(
180 TIP403_REGISTRY_ADDRESS,
181 TIP403RegistryEvent::PolicyCreated(ITIP403Registry::PolicyCreated {
182 policyId: new_policy_id,
183 updater: msg_sender,
184 policyType: call.policyType,
185 })
186 .into_log_data(),
187 )?;
188
189 self.storage.emit_event(
190 TIP403_REGISTRY_ADDRESS,
191 TIP403RegistryEvent::PolicyAdminUpdated(ITIP403Registry::PolicyAdminUpdated {
192 policyId: new_policy_id,
193 updater: msg_sender,
194 admin,
195 })
196 .into_log_data(),
197 )?;
198
199 Ok(new_policy_id)
200 }
201
202 pub fn set_policy_admin(
203 &mut self,
204 msg_sender: Address,
205 call: ITIP403Registry::setPolicyAdminCall,
206 ) -> Result<()> {
207 let data = self.get_policy_data(call.policyId)?;
208
209 if data.admin != msg_sender {
211 return Err(TIP403RegistryError::unauthorized().into());
212 }
213
214 self.set_policy_data(
216 call.policyId,
217 PolicyData {
218 admin: call.admin,
219 ..data
220 },
221 )?;
222
223 self.storage.emit_event(
224 TIP403_REGISTRY_ADDRESS,
225 TIP403RegistryEvent::PolicyAdminUpdated(ITIP403Registry::PolicyAdminUpdated {
226 policyId: call.policyId,
227 updater: msg_sender,
228 admin: call.admin,
229 })
230 .into_log_data(),
231 )
232 }
233
234 pub fn modify_policy_whitelist(
235 &mut self,
236 msg_sender: Address,
237 call: ITIP403Registry::modifyPolicyWhitelistCall,
238 ) -> Result<()> {
239 let data = self.get_policy_data(call.policyId)?;
240
241 if data.admin != msg_sender {
243 return Err(TIP403RegistryError::unauthorized().into());
244 }
245
246 if data.policy_type != ITIP403Registry::PolicyType::WHITELIST as u8 {
248 return Err(TIP403RegistryError::incompatible_policy_type().into());
249 }
250
251 self.set_policy_set(call.policyId, call.account, call.allowed)?;
252
253 self.storage.emit_event(
254 TIP403_REGISTRY_ADDRESS,
255 TIP403RegistryEvent::WhitelistUpdated(ITIP403Registry::WhitelistUpdated {
256 policyId: call.policyId,
257 updater: msg_sender,
258 account: call.account,
259 allowed: call.allowed,
260 })
261 .into_log_data(),
262 )
263 }
264
265 pub fn modify_policy_blacklist(
266 &mut self,
267 msg_sender: Address,
268 call: ITIP403Registry::modifyPolicyBlacklistCall,
269 ) -> Result<()> {
270 let data = self.get_policy_data(call.policyId)?;
271
272 if data.admin != msg_sender {
274 return Err(TIP403RegistryError::unauthorized().into());
275 }
276
277 if data.policy_type != ITIP403Registry::PolicyType::BLACKLIST as u8 {
279 return Err(TIP403RegistryError::incompatible_policy_type().into());
280 }
281
282 self.set_policy_set(call.policyId, call.account, call.restricted)?;
283
284 self.storage.emit_event(
285 TIP403_REGISTRY_ADDRESS,
286 TIP403RegistryEvent::BlacklistUpdated(ITIP403Registry::BlacklistUpdated {
287 policyId: call.policyId,
288 updater: msg_sender,
289 account: call.account,
290 restricted: call.restricted,
291 })
292 .into_log_data(),
293 )
294 }
295
296 fn get_policy_data(&mut self, policy_id: u64) -> Result<PolicyData> {
298 self.sload_policy_data(policy_id)
299 }
300
301 fn set_policy_data(&mut self, policy_id: u64, data: PolicyData) -> Result<()> {
302 self.sstore_policy_data(policy_id, data)
303 }
304
305 fn set_policy_set(&mut self, policy_id: u64, account: Address, value: bool) -> Result<()> {
306 self.sstore_policy_set(policy_id, account, value)
307 }
308
309 fn is_authorized_internal(&mut self, policy_id: u64, user: Address) -> Result<bool> {
311 if policy_id < 2 {
313 return Ok(policy_id == 1);
316 }
317
318 let data = self.get_policy_data(policy_id)?;
319 let is_in_set = self.sload_policy_set(policy_id, user)?;
320
321 let auth = match data
322 .policy_type
323 .try_into()
324 .map_err(|_| TempoPrecompileError::under_overflow())?
325 {
326 ITIP403Registry::PolicyType::WHITELIST => is_in_set,
327 ITIP403Registry::PolicyType::BLACKLIST => !is_in_set,
328 ITIP403Registry::PolicyType::__Invalid => false,
329 };
330 Ok(auth)
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use crate::storage::hashmap::HashMapStorageProvider;
337
338 use super::*;
339
340 #[test]
341 fn test_create_policy() -> eyre::Result<()> {
342 let mut storage = HashMapStorageProvider::new(1);
343 let mut registry = TIP403Registry::new(&mut storage);
344 let admin = Address::from([1u8; 20]);
345
346 assert_eq!(registry.policy_id_counter()?, 2);
348
349 let result = registry.create_policy(
351 admin,
352 ITIP403Registry::createPolicyCall {
353 admin,
354 policyType: ITIP403Registry::PolicyType::WHITELIST,
355 },
356 );
357 assert!(result.is_ok());
358 assert_eq!(result?, 2);
359
360 assert_eq!(registry.policy_id_counter()?, 3);
362
363 let data = registry.policy_data(ITIP403Registry::policyDataCall { policyId: 2 })?;
365 assert_eq!(data.policyType, ITIP403Registry::PolicyType::WHITELIST);
366 assert_eq!(data.admin, admin);
367 Ok(())
368 }
369
370 #[test]
371 fn test_is_authorized_special_policies() -> eyre::Result<()> {
372 let mut storage = HashMapStorageProvider::new(1);
373 let mut registry = TIP403Registry::new(&mut storage);
374 let user = Address::from([1u8; 20]);
375
376 assert!(!registry.is_authorized(ITIP403Registry::isAuthorizedCall { policyId: 0, user })?);
378
379 assert!(registry.is_authorized(ITIP403Registry::isAuthorizedCall { policyId: 1, user })?);
381 Ok(())
382 }
383
384 #[test]
385 fn test_whitelist_policy() -> eyre::Result<()> {
386 let mut storage = HashMapStorageProvider::new(1);
387 let mut registry = TIP403Registry::new(&mut storage);
388 let admin = Address::from([1u8; 20]);
389 let user = Address::from([2u8; 20]);
390
391 let policy_id = registry.create_policy(
393 admin,
394 ITIP403Registry::createPolicyCall {
395 admin,
396 policyType: ITIP403Registry::PolicyType::WHITELIST,
397 },
398 )?;
399
400 assert!(!registry.is_authorized(ITIP403Registry::isAuthorizedCall {
402 policyId: policy_id,
403 user,
404 })?);
405
406 registry.modify_policy_whitelist(
408 admin, ITIP403Registry::modifyPolicyWhitelistCall {
410 policyId: policy_id,
411 account: user,
412 allowed: true,
413 },
414 )?;
415
416 assert!(registry.is_authorized(ITIP403Registry::isAuthorizedCall {
418 policyId: policy_id,
419 user,
420 })?);
421
422 Ok(())
423 }
424
425 #[test]
426 fn test_blacklist_policy() -> eyre::Result<()> {
427 let mut storage = HashMapStorageProvider::new(1);
428 let mut registry = TIP403Registry::new(&mut storage);
429 let admin = Address::from([1u8; 20]);
430 let user = Address::from([2u8; 20]);
431
432 let policy_id = registry.create_policy(
434 admin,
435 ITIP403Registry::createPolicyCall {
436 admin,
437 policyType: ITIP403Registry::PolicyType::BLACKLIST,
438 },
439 )?;
440
441 assert!(registry.is_authorized(ITIP403Registry::isAuthorizedCall {
443 policyId: policy_id,
444 user,
445 })?);
446
447 registry.modify_policy_blacklist(
449 admin,
450 ITIP403Registry::modifyPolicyBlacklistCall {
451 policyId: policy_id,
452 account: user,
453 restricted: true,
454 },
455 )?;
456
457 assert!(!registry.is_authorized(ITIP403Registry::isAuthorizedCall {
459 policyId: policy_id,
460 user,
461 })?);
462
463 Ok(())
464 }
465}