1pub mod dispatch;
2
3use tempo_contracts::precompiles::VALIDATOR_CONFIG_ADDRESS;
4pub use tempo_contracts::precompiles::{IValidatorConfig, ValidatorConfigError};
5use tempo_precompiles_macros::{Storable, contract};
6
7use crate::{
8 error::TempoPrecompileError,
9 storage::{Mapping, PrecompileStorageProvider, Slot, VecSlotExt},
10};
11use alloy::primitives::{Address, B256, Bytes};
12use revm::state::Bytecode;
13use tracing::trace;
14
15#[derive(Debug, Storable)]
17struct Validator {
18 public_key: B256,
19 active: bool,
20 index: u64,
21 validator_address: Address,
22 inbound_address: String,
25 outbound_address: String,
28}
29
30type ValidatorsArray = Slot<Vec<Address>>;
32
33#[contract]
35pub struct ValidatorConfig {
36 owner: Address,
37 validator_count: u64,
38 validators_array: Vec<Address>,
39 validators: Mapping<Address, Validator>,
40}
41
42impl<'a, S: PrecompileStorageProvider> ValidatorConfig<'a, S> {
43 pub fn new(storage: &'a mut S) -> Self {
44 Self::_new(VALIDATOR_CONFIG_ADDRESS, storage)
45 }
46
47 pub fn initialize(&mut self, owner: Address) -> Result<(), TempoPrecompileError> {
49 trace!(address=%self.address, %owner, "Initializing validator config precompile");
50
51 self.storage.set_code(
53 self.address,
54 Bytecode::new_legacy(Bytes::from_static(&[0xef])),
55 )?;
56
57 self.sstore_owner(owner)?;
58
59 Ok(())
60 }
61
62 pub fn owner(&mut self) -> Result<Address, TempoPrecompileError> {
64 self.sload_owner()
65 }
66
67 pub fn check_owner(&mut self, caller: Address) -> Result<(), TempoPrecompileError> {
69 if self.owner()? != caller {
70 return Err(ValidatorConfigError::unauthorized())?;
71 }
72 Ok(())
73 }
74
75 pub fn change_owner(
77 &mut self,
78 sender: Address,
79 call: IValidatorConfig::changeOwnerCall,
80 ) -> Result<(), TempoPrecompileError> {
81 self.check_owner(sender)?;
82 self.sstore_owner(call.newOwner)
83 }
84
85 fn validator_count(&mut self) -> Result<u64, TempoPrecompileError> {
87 self.sload_validator_count()
88 }
89
90 fn validator_exists(&mut self, validator: Address) -> Result<bool, TempoPrecompileError> {
93 let validator = self.sload_validators(validator)?;
94 Ok(!validator.public_key.is_zero())
95 }
96
97 pub fn get_validators(
99 &mut self,
100 _call: IValidatorConfig::getValidatorsCall,
101 ) -> Result<Vec<IValidatorConfig::Validator>, TempoPrecompileError> {
102 let count = self.validator_count()?;
103 let mut validators = Vec::new();
104
105 let validators_array = ValidatorsArray::new(slots::VALIDATORS_ARRAY);
106 for i in 0..count {
107 let validator_address = validators_array.read_at(self, i as usize)?;
109
110 let Validator {
111 public_key,
112 active,
113 index,
114 validator_address: _,
115 inbound_address,
116 outbound_address,
117 } = self.sload_validators(validator_address)?;
118
119 validators.push(IValidatorConfig::Validator {
120 publicKey: public_key,
121 active,
122 index,
123 validatorAddress: validator_address,
124 inboundAddress: inbound_address,
125 outboundAddress: outbound_address,
126 });
127 }
128
129 Ok(validators)
130 }
131
132 pub fn add_validator(
134 &mut self,
135 sender: Address,
136 call: IValidatorConfig::addValidatorCall,
137 ) -> Result<(), TempoPrecompileError> {
138 self.check_owner(sender)?;
140
141 if self.validator_exists(call.newValidatorAddress)? {
143 return Err(ValidatorConfigError::validator_already_exists())?;
144 }
145
146 ensure_address_is_ip_port(&call.inboundAddress).map_err(|err| {
148 ValidatorConfigError::not_host_port(
149 "inboundAddress".to_string(),
150 call.inboundAddress.clone(),
151 format!("{err:?}"),
152 )
153 })?;
154
155 ensure_address_is_ip_port(&call.outboundAddress).map_err(|err| {
156 ValidatorConfigError::not_ip_port(
157 "outboundAddress".to_string(),
158 call.outboundAddress.clone(),
159 format!("{err:?}"),
160 )
161 })?;
162
163 let count = self.validator_count()?;
165 let validator = Validator {
166 public_key: call.publicKey,
167 active: call.active,
168 index: count,
169 validator_address: call.newValidatorAddress,
170 inbound_address: call.inboundAddress,
171 outbound_address: call.outboundAddress,
172 };
173 self.sstore_validators(call.newValidatorAddress, validator)?;
174
175 ValidatorsArray::new(slots::VALIDATORS_ARRAY).push(self, call.newValidatorAddress)?;
177
178 self.sstore_validator_count(
180 count
181 .checked_add(1)
182 .ok_or(TempoPrecompileError::under_overflow())?,
183 )?;
184
185 Ok(())
186 }
187
188 pub fn update_validator(
190 &mut self,
191 sender: Address,
192 call: IValidatorConfig::updateValidatorCall,
193 ) -> Result<(), TempoPrecompileError> {
194 if !self.validator_exists(sender)? {
196 return Err(ValidatorConfigError::validator_not_found())?;
197 }
198
199 let old_validator = self.sload_validators(sender)?;
201
202 if call.newValidatorAddress != sender {
204 if self.validator_exists(call.newValidatorAddress)? {
205 return Err(ValidatorConfigError::validator_already_exists())?;
206 }
207
208 ValidatorsArray::new(slots::VALIDATORS_ARRAY).write_at(
210 self,
211 old_validator.index as usize,
212 call.newValidatorAddress,
213 )?;
214
215 self.clear_validators(sender)?;
217 }
218
219 ensure_address_is_ip_port(&call.inboundAddress).map_err(|err| {
220 ValidatorConfigError::not_host_port(
221 "inboundAddress".to_string(),
222 call.inboundAddress.clone(),
223 format!("{err:?}"),
224 )
225 })?;
226
227 ensure_address_is_ip_port(&call.outboundAddress).map_err(|err| {
228 ValidatorConfigError::not_ip_port(
229 "outboundAddress".to_string(),
230 call.outboundAddress.clone(),
231 format!("{err:?}"),
232 )
233 })?;
234
235 let updated_validator = Validator {
236 public_key: call.publicKey,
237 active: old_validator.active,
238 index: old_validator.index,
239 validator_address: call.newValidatorAddress,
240 inbound_address: call.inboundAddress,
241 outbound_address: call.outboundAddress,
242 };
243
244 self.sstore_validators(call.newValidatorAddress, updated_validator)?;
245
246 Ok(())
247 }
248
249 pub fn change_validator_status(
251 &mut self,
252 sender: Address,
253 call: IValidatorConfig::changeValidatorStatusCall,
254 ) -> Result<(), TempoPrecompileError> {
255 self.check_owner(sender)?;
256
257 if !self.validator_exists(call.validator)? {
258 return Err(ValidatorConfigError::validator_not_found())?;
259 }
260
261 let mut validator = self.sload_validators(call.validator)?;
262 validator.active = call.active;
263 self.sstore_validators(call.validator, validator)?;
264
265 Ok(())
266 }
267}
268
269#[derive(Debug, thiserror::Error)]
270#[error("input was not of the form `<ip>:<port>`")]
271pub struct IpWithPortParseError {
272 #[from]
273 source: std::net::AddrParseError,
274}
275
276pub fn ensure_address_is_ip_port(input: &str) -> Result<(), IpWithPortParseError> {
277 input.parse::<std::net::SocketAddr>()?;
279 Ok(())
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use crate::storage::hashmap::HashMapStorageProvider;
286 use alloy::primitives::Address;
287 use alloy_primitives::FixedBytes;
288
289 #[test]
290 fn test_owner_initialization_and_change() {
291 let mut storage = HashMapStorageProvider::new(1);
292 let owner1 = Address::from([0x11; 20]);
293 let owner2 = Address::from([0x22; 20]);
294
295 let mut validator_config = ValidatorConfig::new(&mut storage);
296
297 validator_config.initialize(owner1).unwrap();
299
300 let current_owner = validator_config.owner().unwrap();
302 assert_eq!(
303 current_owner, owner1,
304 "Owner should be owner1 after initialization"
305 );
306
307 validator_config
309 .change_owner(
310 owner1,
311 IValidatorConfig::changeOwnerCall { newOwner: owner2 },
312 )
313 .expect("Should change owner");
314
315 let current_owner = validator_config.owner().unwrap();
317 assert_eq!(current_owner, owner2, "Owner should be owner2 after change");
318 }
319
320 #[test]
321 fn test_owner_only_functions() {
322 let mut storage = HashMapStorageProvider::new(1);
323 let owner1 = Address::from([0x11; 20]);
324 let owner2 = Address::from([0x22; 20]);
325 let validator1 = Address::from([0x33; 20]);
326
327 let mut validator_config = ValidatorConfig::new(&mut storage);
328
329 validator_config.initialize(owner1).unwrap();
331
332 let public_key = FixedBytes::<32>::from([0x44; 32]);
334 let result = validator_config.add_validator(
335 owner1,
336 IValidatorConfig::addValidatorCall {
337 newValidatorAddress: validator1,
338 publicKey: public_key,
339 inboundAddress: "192.168.1.1:8000".to_string(),
340 active: true,
341 outboundAddress: "192.168.1.1:9000".to_string(),
342 },
343 );
344 assert!(result.is_ok(), "Owner should be able to add validator");
345
346 let validators = validator_config
348 .get_validators(IValidatorConfig::getValidatorsCall {})
349 .expect("Should get validators");
350 assert_eq!(validators.len(), 1, "Should have 1 validator");
351 assert_eq!(validators[0].validatorAddress, validator1);
352 assert_eq!(validators[0].publicKey, public_key);
353 assert!(validators[0].active, "New validator should be active");
354
355 let result = validator_config.change_validator_status(
357 owner1,
358 IValidatorConfig::changeValidatorStatusCall {
359 validator: validator1,
360 active: false,
361 },
362 );
363 assert!(
364 result.is_ok(),
365 "Owner should be able to change validator status"
366 );
367
368 let validators = validator_config
370 .get_validators(IValidatorConfig::getValidatorsCall {})
371 .expect("Should get validators");
372 assert!(!validators[0].active, "Validator should be inactive");
373
374 let validator2 = Address::from([0x55; 20]);
376 let result = validator_config.add_validator(
377 owner2,
378 IValidatorConfig::addValidatorCall {
379 newValidatorAddress: validator2,
380 publicKey: FixedBytes::<32>::from([0x66; 32]),
381 inboundAddress: "192.168.1.2:8000".to_string(),
382 active: true,
383 outboundAddress: "192.168.1.2:9000".to_string(),
384 },
385 );
386 assert!(
387 result.is_err(),
388 "Non-owner should not be able to add validator"
389 );
390 assert_eq!(
391 result.unwrap_err(),
392 ValidatorConfigError::unauthorized().into(),
393 "Should return Unauthorized error"
394 );
395
396 let result = validator_config.change_validator_status(
398 owner2,
399 IValidatorConfig::changeValidatorStatusCall {
400 validator: validator1,
401 active: true,
402 },
403 );
404 assert!(
405 result.is_err(),
406 "Non-owner should not be able to change validator status"
407 );
408 assert_eq!(
409 result.unwrap_err(),
410 ValidatorConfigError::unauthorized().into(),
411 "Should return Unauthorized error"
412 );
413 }
414
415 #[test]
416 fn test_validator_lifecycle() {
417 let mut storage = HashMapStorageProvider::new(1);
418 let owner = Address::from([0x01; 20]);
419
420 let mut validator_config = ValidatorConfig::new(&mut storage);
421 validator_config.initialize(owner).unwrap();
422
423 let validator1 = Address::from([0x11; 20]);
424 let public_key1 = FixedBytes::<32>::from([0x21; 32]);
425 let inbound1 = "192.168.1.1:8000".to_string();
426 let outbound1 = "192.168.1.1:9000".to_string();
427 validator_config
428 .add_validator(
429 owner,
430 IValidatorConfig::addValidatorCall {
431 newValidatorAddress: validator1,
432 publicKey: public_key1,
433 inboundAddress: inbound1.clone(),
434 active: true,
435 outboundAddress: outbound1,
436 },
437 )
438 .expect("should add validator1");
439
440 let result = validator_config.add_validator(
442 owner,
443 IValidatorConfig::addValidatorCall {
444 newValidatorAddress: validator1,
445 publicKey: FixedBytes::<32>::from([0x22; 32]),
446 inboundAddress: "192.168.1.1:8000".to_string(),
447 active: true,
448 outboundAddress: "192.168.1.1:9000".to_string(),
449 },
450 );
451 assert!(result.is_err(), "Should not allow duplicate validator");
452 assert_eq!(
453 result.unwrap_err(),
454 ValidatorConfigError::validator_already_exists().into(),
455 "Should return ValidatorAlreadyExists error"
456 );
457
458 let validator2 = Address::from([0x12; 20]);
460 let public_key2 = FixedBytes::<32>::from([0x22; 32]);
461 validator_config
462 .add_validator(
463 owner,
464 IValidatorConfig::addValidatorCall {
465 newValidatorAddress: validator2,
466 publicKey: public_key2,
467 inboundAddress: "192.168.1.2:8000".to_string(),
468 active: true,
469 outboundAddress: "192.168.1.2:9000".to_string(),
470 },
471 )
472 .expect("Should add validator2");
473
474 let validator3 = Address::from([0x13; 20]);
475 let public_key3 = FixedBytes::<32>::from([0x23; 32]);
476 validator_config
477 .add_validator(
478 owner,
479 IValidatorConfig::addValidatorCall {
480 newValidatorAddress: validator3,
481 publicKey: public_key3,
482 inboundAddress: "192.168.1.3:8000".to_string(),
483 active: false,
484 outboundAddress: "192.168.1.3:9000".to_string(),
485 },
486 )
487 .expect("Should add validator3");
488
489 let validator4 = Address::from([0x14; 20]);
490 let public_key4 = FixedBytes::<32>::from([0x24; 32]);
491 validator_config
492 .add_validator(
493 owner,
494 IValidatorConfig::addValidatorCall {
495 newValidatorAddress: validator4,
496 publicKey: public_key4,
497 inboundAddress: "192.168.1.4:8000".to_string(),
498 active: true,
499 outboundAddress: "192.168.1.4:9000".to_string(),
500 },
501 )
502 .expect("Should add validator4");
503
504 let validator5 = Address::from([0x15; 20]);
505 let public_key5 = FixedBytes::<32>::from([0x25; 32]);
506 validator_config
507 .add_validator(
508 owner,
509 IValidatorConfig::addValidatorCall {
510 newValidatorAddress: validator5,
511 publicKey: public_key5,
512 inboundAddress: "192.168.1.5:8000".to_string(),
513 active: true,
514 outboundAddress: "192.168.1.5:9000".to_string(),
515 },
516 )
517 .expect("Should add validator5");
518
519 let mut validators = validator_config
521 .get_validators(IValidatorConfig::getValidatorsCall {})
522 .expect("Should get validators");
523
524 assert_eq!(validators.len(), 5, "Should have 5 validators");
526
527 validators.sort_by_key(|v| v.validatorAddress);
529
530 assert_eq!(validators[0].validatorAddress, validator1);
532 assert_eq!(validators[0].publicKey, public_key1);
533 assert_eq!(validators[0].inboundAddress, inbound1);
534 assert!(validators[0].active);
535
536 assert_eq!(validators[1].validatorAddress, validator2);
537 assert_eq!(validators[1].publicKey, public_key2);
538 assert_eq!(validators[1].inboundAddress, "192.168.1.2:8000");
539 assert!(validators[1].active);
540
541 assert_eq!(validators[2].validatorAddress, validator3);
542 assert_eq!(validators[2].publicKey, public_key3);
543 assert_eq!(validators[2].inboundAddress, "192.168.1.3:8000");
544 assert!(!validators[2].active);
545
546 assert_eq!(validators[3].validatorAddress, validator4);
547 assert_eq!(validators[3].publicKey, public_key4);
548 assert_eq!(validators[3].inboundAddress, "192.168.1.4:8000");
549 assert!(validators[3].active);
550
551 assert_eq!(validators[4].validatorAddress, validator5);
552 assert_eq!(validators[4].publicKey, public_key5);
553 assert_eq!(validators[4].inboundAddress, "192.168.1.5:8000");
554 assert!(validators[4].active);
555
556 let public_key1_new = FixedBytes::<32>::from([0x31; 32]);
558 let short_inbound1 = "10.0.0.1:8000".to_string();
559 let short_outbound1 = "10.0.0.1:9000".to_string();
560 validator_config
561 .update_validator(
562 validator1,
563 IValidatorConfig::updateValidatorCall {
564 newValidatorAddress: validator1,
565 publicKey: public_key1_new,
566 inboundAddress: short_inbound1.clone(),
567 outboundAddress: short_outbound1,
568 },
569 )
570 .expect("Should update validator1");
571
572 let validator2_new = Address::from([0x22; 20]);
574 validator_config
575 .update_validator(
576 validator2,
577 IValidatorConfig::updateValidatorCall {
578 newValidatorAddress: validator2_new,
579 publicKey: public_key2,
580 inboundAddress: "192.168.1.2:8000".to_string(),
581 outboundAddress: "192.168.1.2:9000".to_string(),
582 },
583 )
584 .expect("Should rotate validator2 address");
585
586 let validator3_new = Address::from([0x23; 20]);
588 let long_inbound3 = "192.169.1.3:8000".to_string();
589 let long_outbound3 = "192.168.1.3:9000".to_string();
590 validator_config
591 .update_validator(
592 validator3,
593 IValidatorConfig::updateValidatorCall {
594 newValidatorAddress: validator3_new,
595 publicKey: public_key3,
596 inboundAddress: long_inbound3.clone(),
597 outboundAddress: long_outbound3,
598 },
599 )
600 .expect("Should rotate validator3 address and update IP");
601
602 let mut validators = validator_config
604 .get_validators(IValidatorConfig::getValidatorsCall {})
605 .expect("Should get validators");
606
607 assert_eq!(validators.len(), 5, "Should still have 5 validators");
609
610 validators.sort_by_key(|v| v.validatorAddress);
612
613 assert_eq!(validators[0].validatorAddress, validator1);
615 assert_eq!(
616 validators[0].publicKey, public_key1_new,
617 "PublicKey should be updated"
618 );
619 assert_eq!(
620 validators[0].inboundAddress, short_inbound1,
621 "Address should be updated to short"
622 );
623 assert!(validators[0].active);
624
625 assert_eq!(validators[1].validatorAddress, validator4);
627 assert_eq!(validators[1].publicKey, public_key4);
628 assert_eq!(validators[1].inboundAddress, "192.168.1.4:8000");
629 assert!(validators[1].active);
630
631 assert_eq!(validators[2].validatorAddress, validator5);
633 assert_eq!(validators[2].publicKey, public_key5);
634 assert_eq!(validators[2].inboundAddress, "192.168.1.5:8000");
635 assert!(validators[2].active);
636
637 assert_eq!(validators[3].validatorAddress, validator2_new);
639 assert_eq!(
640 validators[3].publicKey, public_key2,
641 "PublicKey should be same"
642 );
643 assert_eq!(
644 validators[3].inboundAddress, "192.168.1.2:8000",
645 "IP should be same"
646 );
647 assert!(validators[3].active);
648
649 assert_eq!(validators[4].validatorAddress, validator3_new);
651 assert_eq!(
652 validators[4].publicKey, public_key3,
653 "PublicKey should be same"
654 );
655 assert_eq!(
656 validators[4].inboundAddress, long_inbound3,
657 "Address should be updated to long"
658 );
659 assert!(!validators[4].active);
660 }
661
662 #[test]
663 fn test_owner_cannot_update_validator() {
664 let mut storage = HashMapStorageProvider::new(1);
665 let owner = Address::from([0x01; 20]);
666 let validator = Address::from([0x11; 20]);
667
668 let mut validator_config = ValidatorConfig::new(&mut storage);
669 validator_config.initialize(owner).unwrap();
670
671 let public_key = FixedBytes::<32>::from([0x21; 32]);
673 validator_config
674 .add_validator(
675 owner,
676 IValidatorConfig::addValidatorCall {
677 newValidatorAddress: validator,
678 publicKey: public_key,
679 inboundAddress: "192.168.1.1:8000".to_string(),
680 active: true,
681 outboundAddress: "192.168.1.1:9000".to_string(),
682 },
683 )
684 .expect("Should add validator");
685
686 let result = validator_config.update_validator(
688 owner,
689 IValidatorConfig::updateValidatorCall {
690 newValidatorAddress: validator,
691 publicKey: FixedBytes::<32>::from([0x22; 32]),
692 inboundAddress: "10.0.0.1:8000".to_string(),
693 outboundAddress: "10.0.0.1:9000".to_string(),
694 },
695 );
696
697 assert!(
698 result.is_err(),
699 "Owner should not be able to update validator"
700 );
701 assert_eq!(
702 result.unwrap_err(),
703 ValidatorConfigError::validator_not_found().into(),
704 "Should return ValidatorNotFound error"
705 );
706 }
707
708 #[test]
709 fn test_validator_rotation_clears_all_slots() {
710 let mut storage = HashMapStorageProvider::new(1);
711 let owner = Address::from([0x01; 20]);
712 let validator1 = Address::from([0x11; 20]);
713 let validator2 = Address::from([0x22; 20]);
714
715 let mut validator_config = ValidatorConfig::new(&mut storage);
716 validator_config.initialize(owner).unwrap();
717
718 let long_inbound = "192.168.1.1:8000".to_string();
720 let long_outbound = "192.168.1.1:9000".to_string();
721 let public_key = FixedBytes::<32>::from([0x21; 32]);
722
723 validator_config
724 .add_validator(
725 owner,
726 IValidatorConfig::addValidatorCall {
727 newValidatorAddress: validator1,
728 publicKey: public_key,
729 inboundAddress: long_inbound,
730 active: true,
731 outboundAddress: long_outbound,
732 },
733 )
734 .expect("Should add validator with long addresses");
735
736 validator_config
738 .update_validator(
739 validator1,
740 IValidatorConfig::updateValidatorCall {
741 newValidatorAddress: validator2,
742 publicKey: public_key,
743 inboundAddress: "10.0.0.1:8000".to_string(),
744 outboundAddress: "10.0.0.1:9000".to_string(),
745 },
746 )
747 .expect("Should rotate validator");
748
749 let validator = validator_config
751 .sload_validators(validator1)
752 .expect("Could not load validator");
753
754 assert_eq!(
756 validator.public_key,
757 B256::ZERO,
758 "Old validator public key should be cleared"
759 );
760 assert_eq!(
761 validator.validator_address,
762 Address::ZERO,
763 "Old validator address should be cleared"
764 );
765 assert_eq!(validator.index, 0, "Old validator index should be cleared");
766 assert!(!validator.active, "Old validator should be inactive");
767 assert_eq!(
768 validator.inbound_address,
769 String::default(),
770 "Old validator inbound address should be cleared"
771 );
772 assert_eq!(
773 validator.outbound_address,
774 String::default(),
775 "Old validator outbound address should be cleared"
776 );
777 }
778
779 #[test]
780 fn ipv4_with_port_is_host_port() {
781 ensure_address_is_ip_port("127.0.0.1:8000").unwrap();
782 }
783
784 #[test]
785 fn ipv6_with_port_is_host_port() {
786 ensure_address_is_ip_port("[::1]:8000").unwrap();
787 }
788}