Skip to main content

tempo_precompiles/validator_config_v2/
mod.rs

1//! Validator Config V2 precompile – index-canonical, on-chain, [consensus] validator registry with
2//! signature-gated operations, IP uniqueness enforcement, and migration support from V1.
3//!
4//! [consensus]: <https://docs.tempo.xyz/protocol/blockspace/consensus>
5
6pub mod dispatch;
7
8pub use tempo_contracts::precompiles::{IValidatorConfigV2, ValidatorConfigV2Error};
9use tempo_contracts::precompiles::{VALIDATOR_CONFIG_V2_ADDRESS, ValidatorConfigV2Event};
10use tempo_precompiles_macros::{Storable, contract};
11
12use crate::{
13    error::{Result, TempoPrecompileError},
14    ip_validation::{IpWithPortParseError, ensure_address_is_ip, ensure_address_is_ip_port},
15    storage::{Handler, Mapping},
16    validator_config::ValidatorConfig,
17};
18use alloy::primitives::{Address, B256, Keccak256};
19use commonware_codec::DecodeExt;
20use commonware_cryptography::{
21    Verifier,
22    ed25519::{PublicKey, Signature},
23};
24use tracing::trace;
25
26/// Signature namespace for `addValidator` operations.
27pub const VALIDATOR_NS_ADD: &[u8] = b"TEMPO_VALIDATOR_CONFIG_V2_ADD_VALIDATOR";
28/// Signature namespace for `rotateValidator` operations.
29pub const VALIDATOR_NS_ROTATE: &[u8] = b"TEMPO_VALIDATOR_CONFIG_V2_ROTATE_VALIDATOR";
30
31/// Distinguishes `addValidator` from `rotateValidator` signatures at the type level.
32enum SignatureKind {
33    Add { fee_recipient: Address },
34    Rotate,
35}
36
37/// Contract-level configuration: ownership, initialization state, and migration bookkeeping.
38#[derive(Debug, Storable)]
39struct Config {
40    /// Contract admin address.
41    owner: Address,
42    /// `true` once the contract is fully initialized (post-migration or direct init).
43    is_init: bool,
44    /// Block height at which initialization completed.
45    init_at_height: u64,
46    /// Number of V1 validators skipped during migration (bad pubkey, duplicate, etc).
47    /// Packed alongside `is_init` and `init_at_height` since all are migration lifecycle state.
48    migration_skipped_count: u8,
49    /// Snapshotted V1 validator count, captured on the first `migrateValidator` call.
50    /// Used for index validation so V1 mutations cannot break migration ordering.
51    v1_validator_count: u8,
52}
53
54impl Config {
55    fn new(owner: Address, is_init: bool, init_at_height: u64) -> Self {
56        Self {
57            owner,
58            is_init,
59            init_at_height,
60            migration_skipped_count: 0,
61            v1_validator_count: 0,
62        }
63    }
64
65    fn is_owner(&self, addr: Address) -> bool {
66        self.owner == addr
67    }
68
69    fn require_init(self) -> Result<Self> {
70        if self.is_init {
71            return Ok(self);
72        }
73        Err(ValidatorConfigV2Error::not_initialized())?
74    }
75
76    fn require_not_init(self) -> Result<Self> {
77        if self.is_init {
78            Err(ValidatorConfigV2Error::already_initialized())?
79        }
80        Ok(self)
81    }
82
83    fn require_owner(self, caller: Address) -> Result<Self> {
84        if !self.is_owner(caller) {
85            Err(ValidatorConfigV2Error::unauthorized())?
86        }
87        Ok(self)
88    }
89
90    fn require_owner_or_validator(self, caller: Address, validator: Address) -> Result<Self> {
91        if caller != validator && !self.is_owner(caller) {
92            Err(ValidatorConfigV2Error::unauthorized())?
93        }
94        Ok(self)
95    }
96}
97
98/// A single entry in the `validators` vector.
99///
100/// ## Lifecycle
101///
102/// A record is created in one of three ways:
103/// - `add_validator`: active entry (`deactivated_at_height = 0`, `active_idx != 0`)
104/// - `migrate_validator`: active or born-deactivated, depending on V1 state
105/// - `rotate_validator`: appends a born-deactivated snapshot of the old identity
106///   and overwrites the original slot in-place with the new identity
107///
108/// A record is deactivated via `deactivate_validator`, which sets
109/// `deactivated_at_height` to the current block height and clears `active_idx` to 0.
110/// This transition is one-way — a deactivated record never becomes active again.
111#[derive(Debug, Storable)]
112struct ValidatorRecord {
113    /// Ed25519 communication public key (unique across all records, reserved forever).
114    public_key: B256,
115    /// Ethereum-style address of the validator (unique among active validators).
116    validator_address: Address,
117    /// Inbound address for peer connections (`<ip>:<port>`).
118    ingress: String,
119    /// Outbound IP for firewall whitelisting (`<ip>`).
120    egress: String,
121    /// Address that receives execution-layer fees for this validator.
122    fee_recipient: Address,
123    /// Position in the `validators` array. Stable across rotations for the in-place slot;
124    /// snapshots get the tail position at the time they were appended.
125    index: u64,
126    /// 1-indexed position in `active_indices` (0 = not active).
127    /// Used as a backpointer for O(1) swap-and-pop removal on deactivation.
128    active_idx: u64,
129    /// Block height at which this record was created (or overwritten during rotation).
130    added_at_height: u64,
131    /// Block height at which this record was deactivated (0 = still active).
132    deactivated_at_height: u64,
133}
134
135/// Validator Config V2 precompile — manages consensus validators with append-only,
136/// delete-once semantics.
137///
138/// Replaces V1's mutable state with immutable height-based tracking (`addedAtHeight`,
139/// `deactivatedAtHeight`) to enable historical validator set reconstruction without
140/// requiring historical state access.
141///
142/// ## Storage design
143///
144/// The `validators` vec is the source of truth (append-only). Two auxiliary mappings
145/// provide O(1) lookups:
146/// - `address_to_index`: validator address -> 1-indexed position (0 = not found)
147/// - `pubkey_to_index`: public key -> 1-indexed position (0 = not found)
148///
149/// A separate `active_indices` vec stores 1-indexed global positions of active validators,
150/// enabling O(active_count) enumeration without scanning deactivated entries. Each validator
151/// stores an `active_idx` backpointer into this vec for O(1) swap-and-pop removal.
152///
153/// The struct fields define the on-chain storage layout; the `#[contract]` macro generates the
154/// storage handlers which provide an ergonomic way to interact with the EVM state.
155#[contract(addr = VALIDATOR_CONFIG_V2_ADDRESS)]
156pub struct ValidatorConfigV2 {
157    /// Contract-level config: ownership, initialization state, and migration bookkeeping.
158    config: Config,
159    /// Append-only array of all validators ever registered (including deactivated snapshots).
160    validators: Vec<ValidatorRecord>,
161    /// Validator address → 1-indexed position in `validators` (0 = not found).
162    /// After deactivation the mapping still points to the old (now-deactivated) entry.
163    /// Overwritten when the address is reused by a new validator, or when ownership is transferred.
164    address_to_index: Mapping<Address, u64>,
165    /// Ed25519 public key -> 1-indexed position in `validators` (0 = not found).
166    /// Public keys are reserved forever — even deactivated entries keep their mapping.
167    pubkey_to_index: Mapping<B256, u64>,
168    /// Epoch at which a DKG ceremony will run that rotates the network identity.
169    next_network_identity_rotation_epoch: u64,
170    /// Prevents two active validators from sharing the same ingress IP address.
171    active_ingress_ips: Mapping<B256, bool>,
172    /// Compact list of 1-indexed global positions of currently active validators.
173    /// Order is NOT stable (swap-and-pop on deactivation).
174    active_indices: Vec<u64>,
175}
176
177impl ValidatorConfigV2 {
178    /// Initializes the validator config V2 precompile.
179    ///
180    /// The contract is fully operational immediately: `is_init` is set to `true` and all mutating
181    /// functions (`add_validator`, `rotate_validator`, etc.) are unlocked.
182    ///
183    /// For V1 migration, the contract is NOT initialized — instead `migrate_validator` manually
184    /// copies validators and `initialize_if_migrated` flips `is_init` once all have been migrated.
185    pub fn initialize(&mut self, owner: Address) -> Result<()> {
186        trace!(address=%self.address, %owner, "Initializing validator config v2 precompile");
187        self.__initialize()?;
188
189        let config = Config::new(owner, true, self.storage.block_number());
190
191        self.config.write(config)
192    }
193
194    // =========================================================================
195    // Config accessors and guards — each reads config once (1 SLOAD)
196    // =========================================================================
197
198    /// Returns the current owner of the contract.
199    pub fn owner(&self) -> Result<Address> {
200        self.config.owner.read()
201    }
202
203    /// Returns the block height at which the contract was initialized.
204    ///
205    /// Only meaningful when [`is_initialized`](Self::is_initialized) returns `true`.
206    pub fn get_initialized_at_height(&self) -> Result<u64> {
207        self.config.init_at_height.read()
208    }
209
210    /// Returns whether V2 has been initialized (either directly or via migration).
211    ///
212    /// When `false`, the CL reads from V1 and mutating operations (except
213    /// `deactivate_validator` and migration functions) are blocked.
214    pub fn is_initialized(&self) -> Result<bool> {
215        self.config.is_init.read()
216    }
217
218    /// Returns the total number of validators ever added, including deactivated
219    /// entries and rotation snapshots.
220    pub fn validator_count(&self) -> Result<u64> {
221        Ok(self.validators.len()? as u64)
222    }
223
224    /// Returns the validator at the given global index, or errors if the index
225    /// is out of bounds or the validator has been deactivated.
226    fn get_active_validator(&self, idx: u64) -> Result<ValidatorRecord> {
227        if idx >= self.validators.len()? as u64 {
228            Err(ValidatorConfigV2Error::validator_not_found())?
229        }
230        let v = self.validators[idx as usize].read()?;
231        if v.deactivated_at_height != 0 {
232            Err(ValidatorConfigV2Error::validator_already_deactivated())?
233        }
234        Ok(v)
235    }
236
237    fn read_validator_at(&self, index: u64) -> Result<IValidatorConfigV2::Validator> {
238        debug_assert!(index < self.validator_count()?, "OOB index");
239
240        let v = self.validators[index as usize].read()?;
241        Ok(IValidatorConfigV2::Validator {
242            publicKey: v.public_key,
243            validatorAddress: v.validator_address,
244            ingress: v.ingress,
245            egress: v.egress,
246            feeRecipient: v.fee_recipient,
247            index: v.index,
248            addedAtHeight: v.added_at_height,
249            deactivatedAtHeight: v.deactivated_at_height,
250        })
251    }
252
253    /// Returns the validator registry at the given global index in the `validators` array.
254    ///
255    /// # Errors
256    /// - `ValidatorNotFound` — `index` is out of bounds
257    pub fn validator_by_index(&self, index: u64) -> Result<IValidatorConfigV2::Validator> {
258        if index >= self.validator_count()? {
259            Err(ValidatorConfigV2Error::validator_not_found())?
260        }
261        self.read_validator_at(index)
262    }
263
264    /// Looks up a validator registry by its address.
265    ///
266    /// Returns the current entry for the address (after any rotations or transfers).
267    ///
268    /// # Errors
269    /// - `ValidatorNotFound` — the address has never been registered
270    pub fn validator_by_address(&self, addr: Address) -> Result<IValidatorConfigV2::Validator> {
271        let idx1 = self.address_to_index[addr].read()?;
272        if idx1 == 0 {
273            Err(ValidatorConfigV2Error::validator_not_found())?
274        }
275        self.read_validator_at(idx1 - 1)
276    }
277
278    /// Looks up a validator by its Ed25519 public key.
279    ///
280    /// For rotated validators, the old public key resolves to the deactivated snapshot, while the
281    /// new key resolves to the in-place active entry.
282    ///
283    /// # Errors
284    /// - `ValidatorNotFound` — the public key has never been registered
285    pub fn validator_by_public_key(&self, pubkey: B256) -> Result<IValidatorConfigV2::Validator> {
286        let idx1 = self.pubkey_to_index[pubkey].read()?;
287        if idx1 == 0 {
288            Err(ValidatorConfigV2Error::validator_not_found())?
289        }
290        self.read_validator_at(idx1 - 1)
291    }
292
293    /// Returns only active validators (where `deactivatedAtHeight == 0`).
294    ///
295    /// NOTE: the order of returned validator records is NOT stable and should NOT be relied upon.
296    pub fn get_active_validators(&self) -> Result<Vec<IValidatorConfigV2::Validator>> {
297        let count = self.active_indices.len()?;
298        let mut out = Vec::with_capacity(count);
299        for i in 0..count {
300            let global_idx1 = self.active_indices[i].read()?;
301            out.push(self.read_validator_at(global_idx1 - 1)?);
302        }
303        Ok(out)
304    }
305
306    /// Returns the epoch at which a network identity rotation will be triggered.
307    ///
308    /// See [`set_network_identity_rotation_epoch`](Self::set_network_identity_rotation_epoch).
309    pub fn get_next_network_identity_rotation_epoch(&self) -> Result<u64> {
310        self.next_network_identity_rotation_epoch.read()
311    }
312
313    fn validate_endpoints(ingress: &str, egress: &str) -> Result<()> {
314        ensure_address_is_ip_port(ingress).map_err(|err| {
315            TempoPrecompileError::from(ValidatorConfigV2Error::not_ip_port(
316                ingress.to_string(),
317                err.to_string(),
318            ))
319        })?;
320
321        ensure_address_is_ip(egress).map_err(|err| {
322            TempoPrecompileError::from(ValidatorConfigV2Error::not_ip(
323                egress.to_string(),
324                err.to_string(),
325            ))
326        })
327    }
328
329    /// Parses `ingress` as an `<ip>:<port>` pair and returns the hash of the
330    /// ingress' binary representation.
331    ///
332    /// For V4 addresses, that's `keccak256(octets(ip) || big_endian(port))`.
333    ///
334    /// For V6 addresses, that's `keccak256(octets(ip) || big_endian(scope_id) || big_endian(port))`.
335    fn ingress_key(ingress: &str) -> Result<B256> {
336        let ingress = ingress
337            .parse::<std::net::SocketAddr>()
338            .map_err(IpWithPortParseError::from)
339            .map_err(|err| {
340                TempoPrecompileError::from(ValidatorConfigV2Error::not_ip_port(
341                    ingress.to_string(),
342                    err.to_string(),
343                ))
344            })?;
345        let mut hasher = Keccak256::new();
346        match ingress {
347            std::net::SocketAddr::V4(v4) => {
348                hasher.update(v4.ip().octets());
349                hasher.update(v4.port().to_be_bytes());
350            }
351            std::net::SocketAddr::V6(v6) => {
352                hasher.update(v6.ip().octets());
353                hasher.update(v6.scope_id().to_be_bytes());
354                hasher.update(v6.port().to_be_bytes());
355            }
356        }
357        Ok(hasher.finalize())
358    }
359
360    fn require_unique_ingress(&self, ingress: &str) -> Result<B256> {
361        let ingress_hash = Self::ingress_key(ingress)?;
362        if self.active_ingress_ips[ingress_hash].read()? {
363            Err(ValidatorConfigV2Error::ingress_already_exists(
364                ingress.to_string(),
365            ))?
366        }
367        Ok(ingress_hash)
368    }
369
370    fn update_ingress_ip_tracking(&mut self, old_ingress: &str, new_ingress: &str) -> Result<()> {
371        let old_ingress_hash = Self::ingress_key(old_ingress)?;
372        let new_ingress_hash = Self::ingress_key(new_ingress)?;
373
374        if old_ingress_hash != new_ingress_hash {
375            if self.active_ingress_ips[new_ingress_hash].read()? {
376                Err(ValidatorConfigV2Error::ingress_already_exists(
377                    new_ingress.to_string(),
378                ))?
379            }
380            self.active_ingress_ips[old_ingress_hash].delete()?;
381            self.active_ingress_ips[new_ingress_hash].write(true)?;
382        }
383
384        Ok(())
385    }
386
387    #[allow(clippy::too_many_arguments)]
388    fn append_validator(
389        &mut self,
390        addr: Address,
391        pubkey: B256,
392        ingress: String,
393        egress: String,
394        fee_recipient: Address,
395        added_at_height: u64,
396        deactivated_at_height: u64,
397    ) -> Result<u64> {
398        let count = self.validator_count()?;
399        let mut active_idx = 0u64;
400
401        if deactivated_at_height == 0 {
402            self.active_indices.push(count + 1)?; // 1-indexed
403            active_idx = self.active_indices.len()? as u64; // 1-indexed
404        }
405
406        let v = ValidatorRecord {
407            public_key: pubkey,
408            validator_address: addr,
409            ingress,
410            egress,
411            fee_recipient,
412            index: count,
413            active_idx,
414            added_at_height,
415            deactivated_at_height,
416        };
417
418        self.validators.push(v)?;
419
420        self.pubkey_to_index[pubkey].write(count + 1)?;
421        // for any dups the prev entries must be deactivated since we check above
422        self.address_to_index[addr].write(count + 1)?;
423
424        Ok(count)
425    }
426
427    /// Allows reusing addresses of deactivated validators.
428    fn require_new_address(&self, addr: Address) -> Result<()> {
429        if addr.is_zero() {
430            Err(ValidatorConfigV2Error::invalid_validator_address())?
431        }
432        let idx1 = self.address_to_index[addr].read()?;
433        if idx1 != 0
434            && self.validators[(idx1 - 1) as usize]
435                .deactivated_at_height
436                .read()?
437                == 0
438        {
439            Err(ValidatorConfigV2Error::address_already_has_validator())?
440        }
441        Ok(())
442    }
443
444    fn require_new_pubkey(&self, pubkey: B256) -> Result<()> {
445        if pubkey.is_zero() {
446            Err(ValidatorConfigV2Error::invalid_public_key())?
447        }
448        if self.pubkey_to_index[pubkey].read()? != 0 {
449            Err(ValidatorConfigV2Error::public_key_already_exists())?
450        }
451        Ok(())
452    }
453
454    /// Verifies a validator signature for add or rotate operations.
455    ///
456    /// Constructs the message according to the validator config v2 specification and verifies
457    /// the Ed25519 signature using the appropriate namespace.
458    fn verify_validator_signature(
459        &self,
460        kind: SignatureKind,
461        pubkey: &B256,
462        signature: &[u8],
463        validator_address: Address,
464        ingress: &str,
465        egress: &str,
466    ) -> Result<()> {
467        let sig = Signature::decode(signature)
468            .map_err(|_| ValidatorConfigV2Error::invalid_signature_format())?;
469
470        let mut hasher = Keccak256::new();
471        hasher.update(self.storage.chain_id().to_be_bytes());
472        hasher.update(VALIDATOR_CONFIG_V2_ADDRESS.as_slice());
473        hasher.update(validator_address.as_slice());
474        hasher.update([
475            u8::try_from(ingress.len()).expect("validator ingress length must fit in uint8")
476        ]);
477        hasher.update(ingress.as_bytes());
478        hasher.update([
479            u8::try_from(egress.len()).expect("validator egress length must fit in uint8")
480        ]);
481        hasher.update(egress.as_bytes());
482
483        let namespace = match kind {
484            SignatureKind::Add { fee_recipient } => {
485                hasher.update(fee_recipient.as_slice());
486                VALIDATOR_NS_ADD
487            }
488            SignatureKind::Rotate => VALIDATOR_NS_ROTATE,
489        };
490        let message = hasher.finalize();
491
492        let public_key = PublicKey::decode(pubkey.as_slice())
493            .map_err(|_| ValidatorConfigV2Error::invalid_public_key())?;
494        if !public_key.verify(namespace, message.as_slice(), &sig) {
495            Err(ValidatorConfigV2Error::invalid_signature())?
496        }
497
498        Ok(())
499    }
500
501    // =========================================================================
502    // Owner-only mutating functions
503    // =========================================================================
504
505    /// Adds a new validator to the set (owner only).
506    ///
507    /// Requires a valid Ed25519 signature, using the [`VALIDATOR_NS_ADD`] namespace, over
508    /// `keccak256(chainId || contractAddr || validatorAddr || len(ingress) || ingress || len(egress) || egress || feeRecipient)`
509    /// which proves that the caller controls the private key corresponding to `publicKey`.
510    ///
511    /// # Errors
512    /// - `NotInitialized` — the contract has not been initialized
513    /// - `Unauthorized` — `sender` is not the owner
514    /// - `InvalidPublicKey` — `publicKey` is zero or not a valid Ed25519 key
515    /// - `PublicKeyAlreadyExists` — the public key is already registered
516    /// - `InvalidValidatorAddress` — `validatorAddress` is zero
517    /// - `AddressAlreadyHasValidator` — the address belongs to an active validator
518    /// - `NotIpPort` / `NotIp` — endpoints fail validation
519    /// - `IngressAlreadyExists` — the new ingress is already in use
520    /// - `InvalidSignature` — signature verification fails
521    pub fn add_validator(
522        &mut self,
523        sender: Address,
524        call: IValidatorConfigV2::addValidatorCall,
525    ) -> Result<u64> {
526        self.config.read()?.require_init()?.require_owner(sender)?;
527        self.require_new_pubkey(call.publicKey)?;
528        self.require_new_address(call.validatorAddress)?;
529        Self::validate_endpoints(&call.ingress, &call.egress)?;
530        let ingress_hash = self.require_unique_ingress(&call.ingress)?;
531
532        self.verify_validator_signature(
533            SignatureKind::Add {
534                fee_recipient: call.feeRecipient,
535            },
536            &call.publicKey,
537            &call.signature,
538            call.validatorAddress,
539            &call.ingress,
540            &call.egress,
541        )?;
542
543        let block_height = self.storage.block_number();
544
545        self.active_ingress_ips[ingress_hash].write(true)?;
546
547        let index = self.append_validator(
548            call.validatorAddress,
549            call.publicKey,
550            call.ingress.clone(),
551            call.egress.clone(),
552            call.feeRecipient,
553            block_height,
554            0,
555        )?;
556
557        self.emit_event(ValidatorConfigV2Event::ValidatorAdded(
558            IValidatorConfigV2::ValidatorAdded {
559                index,
560                validatorAddress: call.validatorAddress,
561                publicKey: call.publicKey,
562                ingress: call.ingress,
563                egress: call.egress,
564                feeRecipient: call.feeRecipient,
565            },
566        ))?;
567
568        Ok(index)
569    }
570
571    /// Deactivates a validator by setting its `deactivatedAtHeight` to the current
572    /// block height (owner or the validator itself).
573    ///
574    /// The validator's entry remains in storage for historical queries and its
575    /// public key stays reserved forever. The ingress IP is freed for reuse.
576    ///
577    /// Does NOT require initialization — can be called during the migration window.
578    ///
579    /// Uses swap-and-pop on `active_indices` for O(1) removal.
580    ///
581    /// # Errors
582    /// - `ValidatorNotFound` — `idx` is out of bounds
583    /// - `ValidatorAlreadyDeleted` — the validator is already deactivated
584    /// - `Unauthorized` — `sender` is neither the owner nor the validator
585    pub fn deactivate_validator(
586        &mut self,
587        sender: Address,
588        call: IValidatorConfigV2::deactivateValidatorCall,
589    ) -> Result<()> {
590        let v = self.get_active_validator(call.idx)?;
591        self.config
592            .read()?
593            .require_owner_or_validator(sender, v.validator_address)?;
594
595        self.active_ingress_ips[Self::ingress_key(&v.ingress)?].delete()?;
596
597        let block_height = self.storage.block_number();
598        self.validators[call.idx as usize]
599            .deactivated_at_height
600            .write(block_height)?;
601
602        // Swap-and-pop for active_indices
603        let active_index = (v.active_idx - 1) as usize;
604        let last_pos = self.active_indices.len()? - 1;
605
606        if active_index != last_pos {
607            let moved_val = self.active_indices[last_pos].read()?;
608            self.active_indices[active_index].write(moved_val)?;
609            self.validators[(moved_val - 1) as usize]
610                .active_idx
611                .write((active_index + 1) as u64)?;
612        }
613        self.active_indices.pop()?;
614        self.validators[call.idx as usize].active_idx.write(0)?;
615
616        self.emit_event(ValidatorConfigV2Event::ValidatorDeactivated(
617            IValidatorConfigV2::ValidatorDeactivated {
618                index: call.idx,
619                validatorAddress: v.validator_address,
620            },
621        ))
622    }
623
624    /// Transfers ownership of the contract to a new address (owner only).
625    ///
626    /// # Errors
627    /// - `InvalidOwner` — `newOwner` is `address(0)`
628    /// - `Unauthorized` — `sender` is not the owner
629    /// - `NotInitialized` — the contract has not been initialized
630    pub fn transfer_ownership(
631        &mut self,
632        sender: Address,
633        call: IValidatorConfigV2::transferOwnershipCall,
634    ) -> Result<()> {
635        if call.newOwner.is_zero() {
636            Err(ValidatorConfigV2Error::invalid_owner())?
637        }
638        let mut config = self.config.read()?.require_init()?.require_owner(sender)?;
639        let old_owner = config.owner;
640        config.owner = call.newOwner;
641        self.config.write(config)?;
642
643        self.emit_event(ValidatorConfigV2Event::OwnershipTransferred(
644            IValidatorConfigV2::OwnershipTransferred {
645                oldOwner: old_owner,
646                newOwner: call.newOwner,
647            },
648        ))
649    }
650
651    /// Sets the epoch at which a rotation of the network identity will be triggered.
652    ///
653    /// If `E` is ahead of the network's current epoch, the network will perform a
654    /// Distribute-Key-Generation (DKG) ceremony to rotate its identity at the new epoch `E`.
655    /// - If the DKG ceremony is successful, then epoch `E+1` will run with a new network identity.
656    /// - If `E` is not ahead of the network epoch this value is ignored.
657    pub fn set_network_identity_rotation_epoch(
658        &mut self,
659        sender: Address,
660        call: IValidatorConfigV2::setNetworkIdentityRotationEpochCall,
661    ) -> Result<()> {
662        self.config.read()?.require_init()?.require_owner(sender)?;
663        let previous_epoch = self.next_network_identity_rotation_epoch.read()?;
664        self.next_network_identity_rotation_epoch
665            .write(call.epoch)?;
666        self.emit_event(ValidatorConfigV2Event::NetworkIdentityRotationEpochSet(
667            IValidatorConfigV2::NetworkIdentityRotationEpochSet {
668                previousEpoch: previous_epoch,
669                nextEpoch: call.epoch,
670            },
671        ))
672    }
673
674    // =========================================================================
675    // Dual-auth functions (owner or validator)
676    // =========================================================================
677
678    /// Rotates a validator to a new identity (owner or the validator itself).
679    ///
680    /// Atomically:
681    /// 1. Appends a deactivated snapshot of the old identity to the tail of `validators`
682    /// 2. Overwrites the slot in-place with new pubkey, endpoints, and `addedAtHeight = now`
683    ///
684    /// The validator's global index, `active_idx`, `address_to_index` pointer, and
685    /// position in `active_indices` are all preserved — only `pubkey_to_index` is
686    /// updated (old key -> snapshot, new key -> original slot).
687    ///
688    /// Requires a valid Ed25519 signature, using the [`VALIDATOR_NS_ROTATE`] namespace, over
689    /// `keccak256(chainId || contractAddr || validatorAddr || len(ingress) || ingress || len(egress) || egress)`
690    /// which proves that the caller controls the private key corresponding to `publicKey`.
691    ///
692    /// # Errors
693    /// - `ValidatorNotFound` / `ValidatorAlreadyDeleted` — `idx` is invalid
694    /// - `NotInitialized` / `Unauthorized` — auth failure
695    /// - `InvalidPublicKey` / `PublicKeyAlreadyExists` — the new key is invalid
696    /// - `NotIpPort` / `NotIp` / `IngressAlreadyExists` — endpoint validation failure
697    /// - `InvalidSignature` — signature verification fails
698    pub fn rotate_validator(
699        &mut self,
700        sender: Address,
701        call: IValidatorConfigV2::rotateValidatorCall,
702    ) -> Result<()> {
703        let v = self.get_active_validator(call.idx)?;
704        self.config
705            .read()?
706            .require_init()?
707            .require_owner_or_validator(sender, v.validator_address)?;
708        self.require_new_pubkey(call.publicKey)?;
709        Self::validate_endpoints(&call.ingress, &call.egress)?;
710
711        self.require_unique_ingress(&call.ingress)?;
712        self.verify_validator_signature(
713            SignatureKind::Rotate,
714            &call.publicKey,
715            &call.signature,
716            v.validator_address,
717            &call.ingress,
718            &call.egress,
719        )?;
720
721        let block_height = self.storage.block_number();
722
723        self.update_ingress_ip_tracking(&v.ingress, &call.ingress)?;
724
725        // Append deactivated snapshot of the old validator
726        let appended_idx = self.validators.len()? as u64;
727        let snapshot = ValidatorRecord {
728            public_key: v.public_key,
729            validator_address: v.validator_address,
730            ingress: v.ingress,
731            egress: v.egress,
732            fee_recipient: v.fee_recipient,
733            index: appended_idx,
734            active_idx: 0,
735            added_at_height: v.added_at_height,
736            deactivated_at_height: block_height,
737        };
738        self.validators.push(snapshot)?;
739
740        // Update pubkey_to_index: old pubkey → appended_idx + 1
741        self.pubkey_to_index[v.public_key].write(appended_idx + 1)?;
742
743        // Modify in-place at the original index
744        let mut updated = self.validators[call.idx as usize].read()?;
745        updated.public_key = call.publicKey;
746        updated.ingress = call.ingress.clone();
747        updated.egress = call.egress.clone();
748        updated.added_at_height = block_height;
749        self.validators[call.idx as usize].write(updated)?;
750
751        // Set pubkey_to_index for new pubkey
752        self.pubkey_to_index[call.publicKey].write(call.idx + 1)?;
753
754        self.emit_event(ValidatorConfigV2Event::ValidatorRotated(
755            IValidatorConfigV2::ValidatorRotated {
756                index: call.idx,
757                deactivatedIndex: appended_idx,
758                validatorAddress: v.validator_address,
759                oldPublicKey: v.public_key,
760                newPublicKey: call.publicKey,
761                ingress: call.ingress,
762                egress: call.egress,
763                caller: sender,
764            },
765        ))
766    }
767
768    /// Updates the fee recipient address for a validator (owner or the validator itself).
769    ///
770    /// # Errors
771    /// - `NotInitialized` — the contract has not been initialized
772    /// - `ValidatorNotFound` / `ValidatorAlreadyDeleted` — `idx` is invalid
773    /// - `Unauthorized` — `sender` is neither the owner nor the validator
774    pub fn set_fee_recipient(
775        &mut self,
776        sender: Address,
777        call: IValidatorConfigV2::setFeeRecipientCall,
778    ) -> Result<()> {
779        let mut v = self.get_active_validator(call.idx)?;
780        self.config
781            .read()?
782            .require_init()?
783            .require_owner_or_validator(sender, v.validator_address)?;
784
785        v.fee_recipient = call.feeRecipient;
786        self.validators[call.idx as usize].write(v)?;
787
788        self.emit_event(ValidatorConfigV2Event::FeeRecipientUpdated(
789            IValidatorConfigV2::FeeRecipientUpdated {
790                index: call.idx,
791                feeRecipient: call.feeRecipient,
792                caller: sender,
793            },
794        ))
795    }
796
797    /// Updates a validator's ingress and egress addresses (owner or the validator itself).
798    ///
799    /// # Errors
800    /// - `NotInitialized` — the contract has not been initialized
801    /// - `ValidatorNotFound` / `ValidatorAlreadyDeleted` — `idx` is invalid
802    /// - `Unauthorized` — `sender` is neither the owner nor the validator
803    /// - `NotIpPort` / `NotIp` — the new endpoints fail validation
804    /// - `IngressAlreadyExists` — the new ingress is already in use.
805    pub fn set_ip_addresses(
806        &mut self,
807        sender: Address,
808        call: IValidatorConfigV2::setIpAddressesCall,
809    ) -> Result<()> {
810        let mut v = self.get_active_validator(call.idx)?;
811        self.config
812            .read()?
813            .require_init()?
814            .require_owner_or_validator(sender, v.validator_address)?;
815
816        Self::validate_endpoints(&call.ingress, &call.egress)?;
817
818        self.update_ingress_ip_tracking(&v.ingress, &call.ingress)?;
819
820        v.ingress = call.ingress.clone();
821        v.egress = call.egress.clone();
822        self.validators[call.idx as usize].write(v)?;
823
824        self.emit_event(ValidatorConfigV2Event::IpAddressesUpdated(
825            IValidatorConfigV2::IpAddressesUpdated {
826                index: call.idx,
827                ingress: call.ingress,
828                egress: call.egress,
829                caller: sender,
830            },
831        ))
832    }
833
834    /// Transfers a validator entry to a new address (owner or the validator itself).
835    ///
836    /// Updates the validator's address in the lookup maps: deletes the old `address_to_index`
837    /// entry and creates a new one pointing to the same slot.
838    ///
839    /// # Errors
840    /// - `ValidatorNotFound` / `ValidatorAlreadyDeleted` — `idx` is invalid
841    /// - `NotInitialized` / `Unauthorized` — auth failure
842    /// - `InvalidValidatorAddress` — `newAddress` is zero
843    /// - `AddressAlreadyHasValidator` — `newAddress` belongs to an active validator
844    pub fn transfer_validator_ownership(
845        &mut self,
846        sender: Address,
847        call: IValidatorConfigV2::transferValidatorOwnershipCall,
848    ) -> Result<()> {
849        let mut v = self.get_active_validator(call.idx)?;
850        self.config
851            .read()?
852            .require_init()?
853            .require_owner_or_validator(sender, v.validator_address)?;
854        self.require_new_address(call.newAddress)?;
855
856        let old_address = v.validator_address;
857        v.validator_address = call.newAddress;
858        self.validators[call.idx as usize].write(v)?;
859
860        self.address_to_index[old_address].delete()?;
861        self.address_to_index[call.newAddress].write(call.idx + 1)?;
862
863        self.emit_event(ValidatorConfigV2Event::ValidatorOwnershipTransferred(
864            IValidatorConfigV2::ValidatorOwnershipTransferred {
865                index: call.idx,
866                oldAddress: old_address,
867                newAddress: call.newAddress,
868                caller: sender,
869            },
870        ))
871    }
872
873    // =========================================================================
874    // Migration
875    // =========================================================================
876
877    /// On the very first migration call the V2 owner is still zero, so we copy it from V1
878    /// before checking authorization. Returns the (potentially updated) config for reuse.
879    ///
880    /// # Errors
881    /// - `AlreadyInitialized` — V2 is already initialized
882    /// - `Unauthorized` — `caller` is not the owner (after copying from V1 if needed)
883    fn require_migration_owner(&mut self, caller: Address) -> Result<Config> {
884        let mut config = self.config.read()?.require_not_init()?;
885
886        if config.owner.is_zero() {
887            let v1 = v1();
888            config.owner = v1.owner()?;
889            let v1_count = v1.validator_count()?;
890            if v1_count == 0 {
891                Err(ValidatorConfigV2Error::empty_v1_validator_set())?
892            }
893            config.v1_validator_count = v1_count as u8;
894            self.config.write(Config {
895                owner: config.owner,
896                is_init: false,
897                init_at_height: 0,
898                migration_skipped_count: 0,
899                v1_validator_count: config.v1_validator_count,
900            })?;
901        }
902
903        config.require_owner(caller)
904    }
905
906    /// Migrates a single validator from V1 to V2 (owner only).
907    ///
908    /// Must be called once per V1 validator, in reverse index order.
909    /// On the first call, copies the owner from V1 if V2's owner is `address(0)`.
910    ///
911    /// Validators are skipped (not reverted) when:
912    /// - The public key is not a valid Ed25519 key
913    /// - The egress address cannot be parsed as a `SocketAddr`
914    /// - The public key or ingress IP is a duplicate of an already-migrated entry
915    ///
916    /// Active V1 validators get `deactivatedAtHeight = 0`; inactive ones get
917    /// `deactivatedAtHeight = block.number`.
918    ///
919    /// # Errors
920    /// - `Unauthorized` — `sender` is not the owner
921    /// - `AlreadyInitialized` — V2 is already initialized
922    /// - `InvalidMigrationIndex` — `idx` is out of order
923    pub fn migrate_validator(
924        &mut self,
925        sender: Address,
926        call: IValidatorConfigV2::migrateValidatorCall,
927    ) -> Result<()> {
928        let config = self.require_migration_owner(sender)?;
929        let block_height = self.storage.block_number();
930
931        let v1 = v1();
932        let v1_count = u64::from(config.v1_validator_count);
933        let migrated = self.validator_count()?;
934        let skipped = config.migration_skipped_count;
935
936        let total_processed = migrated + u64::from(skipped);
937        if total_processed >= v1_count || call.idx != v1_count - total_processed - 1 {
938            Err(ValidatorConfigV2Error::invalid_migration_index())?
939        }
940
941        let v1_val = v1.validators(v1.validators_array(call.idx)?)?;
942
943        // Closure to skipping a validator when one of the checks fails
944        let skip = |s: &mut Self| {
945            s.emit_event(ValidatorConfigV2Event::SkippedValidatorMigration(
946                IValidatorConfigV2::SkippedValidatorMigration {
947                    index: call.idx,
948                    validatorAddress: v1_val.validatorAddress,
949                    publicKey: v1_val.publicKey,
950                },
951            ))?;
952            s.config
953                .migration_skipped_count
954                .write(skipped.saturating_add(1))
955        };
956
957        // Skip if public key decoding fails
958        if PublicKey::decode(v1_val.publicKey.as_slice()).is_err() {
959            return skip(self);
960        }
961
962        // Skip if egress decoding fails
963        let egress = match v1_val.outboundAddress.parse::<std::net::SocketAddr>() {
964            Ok(sa) => sa.ip().to_string(),
965            Err(_) => return skip(self),
966        };
967
968        // Skip if public key is a duplicate of an existing entry
969        if self.pubkey_to_index[v1_val.publicKey].read()? != 0 {
970            return skip(self);
971        }
972
973        // Skip if address is a duplicate of an existing entry
974        let addr_idx = self.address_to_index[v1_val.validatorAddress].read()?;
975        if addr_idx != 0
976            && self.validators[(addr_idx - 1) as usize]
977                .deactivated_at_height
978                .read()?
979                == 0
980        {
981            Err(ValidatorConfigV2Error::address_already_has_validator())?
982        }
983
984        let now_active = v1_val.active;
985        let ingress_hash = Self::ingress_key(&v1_val.inboundAddress)?;
986
987        // Skip if ingress ip hash is a duplicate of an existing entry
988        if now_active && self.active_ingress_ips[ingress_hash].read()? {
989            return skip(self);
990        }
991
992        let migrated_idx = self.append_validator(
993            v1_val.validatorAddress,
994            v1_val.publicKey,
995            v1_val.inboundAddress,
996            egress,
997            Address::ZERO,
998            block_height,
999            if now_active { 0 } else { block_height },
1000        )?;
1001
1002        if now_active {
1003            self.active_ingress_ips[ingress_hash].write(true)?;
1004        }
1005
1006        self.emit_event(ValidatorConfigV2Event::ValidatorMigrated(
1007            IValidatorConfigV2::ValidatorMigrated {
1008                index: migrated_idx,
1009                validatorAddress: v1_val.validatorAddress,
1010                publicKey: v1_val.publicKey,
1011            },
1012        ))
1013    }
1014
1015    /// Finalizes V1 -> V2 migration by setting `is_init = true`.
1016    ///
1017    /// Should only be called after all V1 validators have been migrated via
1018    /// [`migrate_validator`](Self::migrate_validator). Copies `nextDkgCeremony`
1019    /// from V1. After this call, the CL reads from V2 instead of V1 and all
1020    /// mutating functions are unlocked.
1021    ///
1022    /// # Errors
1023    /// - `Unauthorized` — `sender` is not the owner
1024    /// - `AlreadyInitialized` — V2 is already initialized
1025    /// - `MigrationNotComplete` — `validator_count + skipped < v1.validator_count`
1026    pub fn initialize_if_migrated(&mut self, sender: Address) -> Result<()> {
1027        let mut config = self.require_migration_owner(sender)?;
1028
1029        // NOTE: this count comparison is sufficient because `add_validator` and
1030        // `rotate_validator` are blocked until the contract is initialized.
1031        if config.v1_validator_count == 0
1032            || self.validator_count()? + u64::from(config.migration_skipped_count)
1033                < u64::from(config.v1_validator_count)
1034        {
1035            Err(ValidatorConfigV2Error::migration_not_complete())?
1036        }
1037
1038        let v1 = v1();
1039        let v1_next_dkg = v1.get_next_full_dkg_ceremony()?;
1040        self.next_network_identity_rotation_epoch
1041            .write(v1_next_dkg)?;
1042
1043        trace!(address=%self.address, "Initializing validator config v2 precompile after migration");
1044
1045        // Initialize the precompile config
1046        let height = self.storage.block_number();
1047        config.init_at_height = height;
1048        config.is_init = true;
1049        self.config.write(config)?;
1050
1051        self.emit_event(ValidatorConfigV2Event::Initialized(
1052            IValidatorConfigV2::Initialized { height },
1053        ))
1054    }
1055}
1056
1057fn v1() -> ValidatorConfig {
1058    ValidatorConfig::new()
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063    use super::*;
1064    use crate::storage::{StorageCtx, hashmap::HashMapStorageProvider};
1065    use alloy::primitives::Address;
1066    use alloy_primitives::FixedBytes;
1067    use commonware_codec::Encode;
1068    use commonware_cryptography::{Signer, ed25519::PrivateKey};
1069
1070    /// Generate a test Ed25519 key pair and create a valid signature
1071    fn make_test_keypair_and_signature(
1072        validator_address: Address,
1073        ingress: &str,
1074        egress: &str,
1075        kind: SignatureKind,
1076    ) -> (FixedBytes<32>, Vec<u8>) {
1077        // Generate a random private key for testing
1078        let seed = rand_08::random::<u64>();
1079        let private_key = PrivateKey::from_seed(seed);
1080        let public_key = private_key.public_key();
1081
1082        let mut hasher = Keccak256::new();
1083        hasher.update(1u64.to_be_bytes());
1084        hasher.update(VALIDATOR_CONFIG_V2_ADDRESS.as_slice());
1085        hasher.update(validator_address.as_slice());
1086        hasher.update([
1087            u8::try_from(ingress.len()).expect("validator ingress length must fit in uint8")
1088        ]);
1089        hasher.update(ingress.as_bytes());
1090        hasher.update([
1091            u8::try_from(egress.len()).expect("validator egress length must fit in uint8")
1092        ]);
1093        hasher.update(egress.as_bytes());
1094        let namespace = match kind {
1095            SignatureKind::Add { fee_recipient } => {
1096                hasher.update(fee_recipient.as_slice());
1097                VALIDATOR_NS_ADD
1098            }
1099            SignatureKind::Rotate => VALIDATOR_NS_ROTATE,
1100        };
1101        let message = hasher.finalize();
1102
1103        // Sign with namespace
1104        let signature = private_key.sign(namespace, message.as_slice());
1105
1106        // Encode public key to bytes
1107        let pubkey_bytes = public_key.encode();
1108        let mut pubkey_array = [0u8; 32];
1109        pubkey_array.copy_from_slice(&pubkey_bytes);
1110
1111        (
1112            FixedBytes::<32>::from(pubkey_array),
1113            signature.encode().to_vec(),
1114        )
1115    }
1116
1117    fn make_add_call(
1118        addr: Address,
1119        pubkey: FixedBytes<32>,
1120        ingress: &str,
1121        egress: &str,
1122        fee_recipient: Address,
1123        signature: Vec<u8>,
1124    ) -> IValidatorConfigV2::addValidatorCall {
1125        IValidatorConfigV2::addValidatorCall {
1126            validatorAddress: addr,
1127            publicKey: pubkey,
1128            ingress: ingress.to_string(),
1129            egress: egress.to_string(),
1130            feeRecipient: fee_recipient,
1131            signature: signature.into(),
1132        }
1133    }
1134
1135    /// Helper to make a complete add call with generated keys
1136    fn make_valid_add_call(
1137        addr: Address,
1138        ingress: &str,
1139        egress: &str,
1140        fee_recipient: Address,
1141    ) -> IValidatorConfigV2::addValidatorCall {
1142        let (pubkey, signature) = make_test_keypair_and_signature(
1143            addr,
1144            ingress,
1145            egress,
1146            SignatureKind::Add { fee_recipient },
1147        );
1148        make_add_call(addr, pubkey, ingress, egress, fee_recipient, signature)
1149    }
1150
1151    #[test]
1152    fn test_owner_initialization() -> eyre::Result<()> {
1153        let mut storage = HashMapStorageProvider::new(1);
1154        let owner = Address::random();
1155        StorageCtx::enter(&mut storage, || {
1156            let mut vc = ValidatorConfigV2::new();
1157            vc.initialize(owner)?;
1158
1159            assert_eq!(vc.owner()?, owner);
1160            assert!(vc.is_initialized()?);
1161            assert_eq!(vc.get_initialized_at_height()?, 0);
1162            assert_eq!(vc.validator_count()?, 0);
1163
1164            Ok(())
1165        })
1166    }
1167
1168    #[test]
1169    fn test_add_validator() -> eyre::Result<()> {
1170        let mut storage = HashMapStorageProvider::new(1);
1171        let owner = Address::random();
1172        let validator = Address::random();
1173        StorageCtx::enter(&mut storage, || {
1174            let mut vc = ValidatorConfigV2::new();
1175            vc.initialize(owner)?;
1176
1177            let (pubkey, signature) = make_test_keypair_and_signature(
1178                validator,
1179                "192.168.1.1:8000",
1180                "192.168.1.1",
1181                SignatureKind::Add {
1182                    fee_recipient: validator,
1183                },
1184            );
1185            vc.storage.set_block_number(200);
1186            vc.add_validator(
1187                owner,
1188                make_add_call(
1189                    validator,
1190                    pubkey,
1191                    "192.168.1.1:8000",
1192                    "192.168.1.1",
1193                    validator,
1194                    signature,
1195                ),
1196            )?;
1197
1198            assert_eq!(vc.validator_count()?, 1);
1199
1200            let v = vc.validator_by_index(0)?;
1201            assert_eq!(v.publicKey, pubkey);
1202            assert_eq!(v.validatorAddress, validator);
1203            assert_eq!(v.addedAtHeight, 200);
1204            assert_eq!(v.deactivatedAtHeight, 0);
1205            assert_eq!(v.index, 0);
1206
1207            let v2 = vc.validator_by_address(validator)?;
1208            assert_eq!(v2.publicKey, pubkey);
1209
1210            let v3 = vc.validator_by_public_key(pubkey)?;
1211            assert_eq!(v3.validatorAddress, validator);
1212
1213            Ok(())
1214        })
1215    }
1216
1217    #[test]
1218    fn test_add_validator_rejects_unauthorized() -> eyre::Result<()> {
1219        let mut storage = HashMapStorageProvider::new(1);
1220        let owner = Address::random();
1221        let non_owner = Address::random();
1222        StorageCtx::enter(&mut storage, || {
1223            let mut vc = ValidatorConfigV2::new();
1224            vc.initialize(owner)?;
1225
1226            let result = vc.add_validator(
1227                non_owner,
1228                make_valid_add_call(
1229                    Address::random(),
1230                    "192.168.1.1:8000",
1231                    "192.168.1.1",
1232                    Address::random(),
1233                ),
1234            );
1235            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
1236
1237            Ok(())
1238        })
1239    }
1240
1241    #[test]
1242    fn test_add_validator_rejects_zero_pubkey() -> eyre::Result<()> {
1243        let mut storage = HashMapStorageProvider::new(1);
1244        let owner = Address::random();
1245        StorageCtx::enter(&mut storage, || {
1246            let mut vc = ValidatorConfigV2::new();
1247            vc.initialize(owner)?;
1248
1249            let result = vc.add_validator(
1250                owner,
1251                make_add_call(
1252                    Address::random(),
1253                    FixedBytes::<32>::ZERO,
1254                    "192.168.1.1:8000",
1255                    "192.168.1.1",
1256                    Address::random(),
1257                    vec![0u8; 64],
1258                ),
1259            );
1260            assert_eq!(
1261                result,
1262                Err(ValidatorConfigV2Error::invalid_public_key().into())
1263            );
1264
1265            Ok(())
1266        })
1267    }
1268
1269    #[test]
1270    fn test_add_validator_rejects_duplicate_address() -> eyre::Result<()> {
1271        let mut storage = HashMapStorageProvider::new(1);
1272        let owner = Address::random();
1273        let validator = Address::random();
1274        StorageCtx::enter(&mut storage, || {
1275            let mut vc = ValidatorConfigV2::new();
1276            vc.initialize(owner)?;
1277
1278            vc.storage.set_block_number(200);
1279            vc.add_validator(
1280                owner,
1281                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
1282            )?;
1283
1284            vc.storage.set_block_number(201);
1285            let result = vc.add_validator(
1286                owner,
1287                make_valid_add_call(validator, "192.168.1.2:8000", "192.168.1.2", validator),
1288            );
1289            assert_eq!(
1290                result,
1291                Err(ValidatorConfigV2Error::address_already_has_validator().into())
1292            );
1293
1294            Ok(())
1295        })
1296    }
1297
1298    #[test]
1299    fn test_add_validator_rejects_duplicate_pubkey() -> eyre::Result<()> {
1300        let mut storage = HashMapStorageProvider::new(1);
1301        let owner = Address::random();
1302        StorageCtx::enter(&mut storage, || {
1303            let mut vc = ValidatorConfigV2::new();
1304            vc.initialize(owner)?;
1305
1306            // First validator
1307            let addr1 = Address::random();
1308            let (pubkey, sig1) = make_test_keypair_and_signature(
1309                addr1,
1310                "192.168.1.1:8000",
1311                "192.168.1.1",
1312                SignatureKind::Add {
1313                    fee_recipient: addr1,
1314                },
1315            );
1316            vc.storage.set_block_number(200);
1317            vc.add_validator(
1318                owner,
1319                make_add_call(
1320                    addr1,
1321                    pubkey,
1322                    "192.168.1.1:8000",
1323                    "192.168.1.1",
1324                    addr1,
1325                    sig1,
1326                ),
1327            )?;
1328
1329            // Try to add second validator with same public key (but different signature for different address)
1330            let addr2 = Address::random();
1331            let (_, sig2) = make_test_keypair_and_signature(
1332                addr2,
1333                "192.168.1.2:8000",
1334                "192.168.1.2",
1335                SignatureKind::Add {
1336                    fee_recipient: addr2,
1337                },
1338            );
1339            vc.storage.set_block_number(201);
1340            let result = vc.add_validator(
1341                owner,
1342                make_add_call(
1343                    addr2,
1344                    pubkey,
1345                    "192.168.1.2:8000",
1346                    "192.168.1.2",
1347                    addr2,
1348                    sig2,
1349                ),
1350            );
1351            assert_eq!(
1352                result,
1353                Err(ValidatorConfigV2Error::public_key_already_exists().into())
1354            );
1355
1356            Ok(())
1357        })
1358    }
1359
1360    #[test]
1361    fn test_deactivate_validator() -> eyre::Result<()> {
1362        let mut storage = HashMapStorageProvider::new(1);
1363        let owner = Address::random();
1364        let validator = Address::random();
1365        StorageCtx::enter(&mut storage, || {
1366            let mut vc = ValidatorConfigV2::new();
1367            vc.initialize(owner)?;
1368
1369            vc.storage.set_block_number(200);
1370            vc.add_validator(
1371                owner,
1372                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
1373            )?;
1374
1375            vc.storage.set_block_number(300);
1376            vc.deactivate_validator(
1377                owner,
1378                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
1379            )?;
1380
1381            let v = vc.validator_by_index(0)?;
1382            assert_eq!(v.deactivatedAtHeight, 300);
1383
1384            // Double deactivation fails
1385            vc.storage.set_block_number(301);
1386            let result = vc.deactivate_validator(
1387                owner,
1388                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
1389            );
1390            assert_eq!(
1391                result,
1392                Err(ValidatorConfigV2Error::validator_already_deactivated().into())
1393            );
1394
1395            Ok(())
1396        })
1397    }
1398
1399    #[test]
1400    fn test_deactivate_validator_dual_auth() -> eyre::Result<()> {
1401        let mut storage = HashMapStorageProvider::new(1);
1402        let owner = Address::random();
1403        let v1 = Address::random();
1404        let v2 = Address::random();
1405        let third_party = Address::random();
1406        StorageCtx::enter(&mut storage, || {
1407            let mut vc = ValidatorConfigV2::new();
1408            vc.initialize(owner)?;
1409
1410            vc.storage.set_block_number(200);
1411            vc.add_validator(
1412                owner,
1413                make_valid_add_call(v1, "192.168.1.1:8000", "192.168.1.1", v1),
1414            )?;
1415            vc.add_validator(
1416                owner,
1417                make_valid_add_call(v2, "192.168.1.2:8000", "192.168.1.2", v2),
1418            )?;
1419
1420            // Third party cannot deactivate
1421            vc.storage.set_block_number(300);
1422            let result = vc.deactivate_validator(
1423                third_party,
1424                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
1425            );
1426            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
1427
1428            // Validator can deactivate itself
1429            vc.deactivate_validator(v1, IValidatorConfigV2::deactivateValidatorCall { idx: 0 })?;
1430            assert_eq!(vc.validator_by_index(0)?.deactivatedAtHeight, 300);
1431
1432            // Owner can deactivate another validator
1433            vc.storage.set_block_number(301);
1434            vc.deactivate_validator(
1435                owner,
1436                IValidatorConfigV2::deactivateValidatorCall { idx: 1 },
1437            )?;
1438            assert_eq!(vc.validator_by_index(1)?.deactivatedAtHeight, 301);
1439
1440            Ok(())
1441        })
1442    }
1443
1444    #[test]
1445    fn test_rotate_validator() -> eyre::Result<()> {
1446        let mut storage = HashMapStorageProvider::new(1);
1447        let owner = Address::random();
1448        let validator = Address::random();
1449        StorageCtx::enter(&mut storage, || {
1450            let mut vc = ValidatorConfigV2::new();
1451            vc.initialize(owner)?;
1452
1453            // Add initial validator and track the old key
1454            let (old_pubkey, old_sig) = make_test_keypair_and_signature(
1455                validator,
1456                "192.168.1.1:8000",
1457                "192.168.1.1",
1458                SignatureKind::Add {
1459                    fee_recipient: validator,
1460                },
1461            );
1462            vc.storage.set_block_number(200);
1463            vc.add_validator(
1464                owner,
1465                make_add_call(
1466                    validator,
1467                    old_pubkey,
1468                    "192.168.1.1:8000",
1469                    "192.168.1.1",
1470                    validator,
1471                    old_sig,
1472                ),
1473            )?;
1474
1475            // Rotate to new key
1476            let (new_pubkey, new_sig) = make_test_keypair_and_signature(
1477                validator,
1478                "10.0.0.1:8000",
1479                "10.0.0.1",
1480                SignatureKind::Rotate,
1481            );
1482            vc.storage.set_block_number(300);
1483            vc.rotate_validator(
1484                owner,
1485                IValidatorConfigV2::rotateValidatorCall {
1486                    idx: 0,
1487                    publicKey: new_pubkey,
1488                    ingress: "10.0.0.1:8000".to_string(),
1489                    egress: "10.0.0.1".to_string(),
1490                    signature: new_sig.into(),
1491                },
1492            )?;
1493
1494            // Should now have 2 entries
1495            assert_eq!(vc.validator_count()?, 2);
1496
1497            // Original slot updated in-place with new key
1498            let updated = vc.validator_by_index(0)?;
1499            assert_eq!(updated.deactivatedAtHeight, 0);
1500            assert_eq!(updated.publicKey, new_pubkey);
1501            assert_eq!(updated.validatorAddress, validator);
1502            assert_eq!(updated.addedAtHeight, 300);
1503
1504            // Appended snapshot of old validator deactivated
1505            let snapshot = vc.validator_by_index(1)?;
1506            assert_eq!(snapshot.deactivatedAtHeight, 300);
1507            assert_eq!(snapshot.publicKey, old_pubkey);
1508
1509            // address_to_index still points to the original slot
1510            let by_addr = vc.validator_by_address(validator)?;
1511            assert_eq!(by_addr.publicKey, new_pubkey);
1512
1513            // Old pubkey resolves to the appended snapshot
1514            let by_old_pk = vc.validator_by_public_key(old_pubkey)?;
1515            assert_eq!(by_old_pk.deactivatedAtHeight, 300);
1516
1517            Ok(())
1518        })
1519    }
1520
1521    #[test]
1522    fn test_get_active_validators() -> eyre::Result<()> {
1523        let mut storage = HashMapStorageProvider::new(1);
1524        let owner = Address::random();
1525        let v1 = Address::random();
1526        let v2 = Address::random();
1527        StorageCtx::enter(&mut storage, || {
1528            let mut vc = ValidatorConfigV2::new();
1529            vc.initialize(owner)?;
1530
1531            vc.storage.set_block_number(200);
1532            vc.add_validator(
1533                owner,
1534                make_valid_add_call(v1, "192.168.1.1:8000", "192.168.1.1", v1),
1535            )?;
1536            vc.storage.set_block_number(201);
1537            vc.add_validator(
1538                owner,
1539                make_valid_add_call(v2, "192.168.1.2:8000", "192.168.1.2", v2),
1540            )?;
1541
1542            assert_eq!(vc.get_active_validators()?.len(), 2);
1543
1544            vc.storage.set_block_number(300);
1545            vc.deactivate_validator(
1546                owner,
1547                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
1548            )?;
1549
1550            let active = vc.get_active_validators()?;
1551            assert_eq!(active.len(), 1);
1552            assert_eq!(active[0].validatorAddress, v2);
1553
1554            assert_eq!(vc.validator_count()?, 2);
1555
1556            Ok(())
1557        })
1558    }
1559
1560    #[test]
1561    fn test_set_fee_recipient() -> eyre::Result<()> {
1562        let mut storage = HashMapStorageProvider::new(1);
1563        let owner = Address::random();
1564        let validator = Address::random();
1565        StorageCtx::enter(&mut storage, || {
1566            let mut vc = ValidatorConfigV2::new();
1567            vc.initialize(owner)?;
1568
1569            vc.storage.set_block_number(200);
1570            vc.add_validator(
1571                owner,
1572                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
1573            )?;
1574
1575            let fee_recipient_1 = Address::random();
1576            vc.set_fee_recipient(
1577                owner,
1578                IValidatorConfigV2::setFeeRecipientCall {
1579                    idx: 0,
1580                    feeRecipient: fee_recipient_1,
1581                },
1582            )?;
1583
1584            let v = vc.validator_by_address(validator)?;
1585            assert_eq!(v.feeRecipient, fee_recipient_1);
1586
1587            // Validator can update its own
1588            let fee_recipient_2 = Address::random();
1589            vc.set_fee_recipient(
1590                validator,
1591                IValidatorConfigV2::setFeeRecipientCall {
1592                    idx: 0,
1593                    feeRecipient: fee_recipient_2,
1594                },
1595            )?;
1596
1597            let v = vc.validator_by_address(validator)?;
1598            assert_eq!(v.feeRecipient, fee_recipient_2);
1599
1600            Ok(())
1601        })
1602    }
1603
1604    #[test]
1605    fn test_set_ip_addresses() -> eyre::Result<()> {
1606        let mut storage = HashMapStorageProvider::new(1);
1607        let owner = Address::random();
1608        let validator = Address::random();
1609        StorageCtx::enter(&mut storage, || {
1610            let mut vc = ValidatorConfigV2::new();
1611            vc.initialize(owner)?;
1612
1613            vc.storage.set_block_number(200);
1614            vc.add_validator(
1615                owner,
1616                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
1617            )?;
1618
1619            vc.set_ip_addresses(
1620                owner,
1621                IValidatorConfigV2::setIpAddressesCall {
1622                    idx: 0,
1623                    ingress: "10.0.0.1:8000".to_string(),
1624                    egress: "10.0.0.1".to_string(),
1625                },
1626            )?;
1627
1628            let v = vc.validator_by_address(validator)?;
1629            assert_eq!(v.ingress, "10.0.0.1:8000");
1630            assert_eq!(v.egress, "10.0.0.1");
1631
1632            // Validator can update its own
1633            vc.set_ip_addresses(
1634                validator,
1635                IValidatorConfigV2::setIpAddressesCall {
1636                    idx: 0,
1637                    ingress: "10.0.0.2:8000".to_string(),
1638                    egress: "10.0.0.2".to_string(),
1639                },
1640            )?;
1641
1642            let v = vc.validator_by_address(validator)?;
1643            assert_eq!(v.ingress, "10.0.0.2:8000");
1644
1645            Ok(())
1646        })
1647    }
1648
1649    #[test]
1650    fn test_transfer_ownership() -> eyre::Result<()> {
1651        let mut storage = HashMapStorageProvider::new(1);
1652        let owner = Address::random();
1653        let new_owner = Address::random();
1654        let non_owner = Address::random();
1655        StorageCtx::enter(&mut storage, || {
1656            let mut vc = ValidatorConfigV2::new();
1657
1658            // Rejects pre-init
1659            let result = vc.transfer_ownership(
1660                owner,
1661                IValidatorConfigV2::transferOwnershipCall {
1662                    newOwner: new_owner,
1663                },
1664            );
1665            assert_eq!(
1666                result,
1667                Err(ValidatorConfigV2Error::not_initialized().into())
1668            );
1669
1670            vc.initialize(owner)?;
1671
1672            // Rejects zero address
1673            let result = vc.transfer_ownership(
1674                owner,
1675                IValidatorConfigV2::transferOwnershipCall {
1676                    newOwner: Address::ZERO,
1677                },
1678            );
1679            assert_eq!(result, Err(ValidatorConfigV2Error::invalid_owner().into()));
1680            assert_eq!(vc.owner()?, owner);
1681
1682            // Rejects non-owner
1683            let result = vc.transfer_ownership(
1684                non_owner,
1685                IValidatorConfigV2::transferOwnershipCall {
1686                    newOwner: new_owner,
1687                },
1688            );
1689            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
1690
1691            // Succeeds for owner
1692            vc.transfer_ownership(
1693                owner,
1694                IValidatorConfigV2::transferOwnershipCall {
1695                    newOwner: new_owner,
1696                },
1697            )?;
1698            assert_eq!(vc.owner()?, new_owner);
1699
1700            // Old owner can no longer transfer
1701            let result = vc.transfer_ownership(
1702                owner,
1703                IValidatorConfigV2::transferOwnershipCall {
1704                    newOwner: Address::random(),
1705                },
1706            );
1707            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
1708
1709            Ok(())
1710        })
1711    }
1712
1713    #[test]
1714    fn test_transfer_validator_ownership() -> eyre::Result<()> {
1715        let mut storage = HashMapStorageProvider::new(1);
1716        let owner = Address::random();
1717        let validator = Address::random();
1718        let new_address = Address::random();
1719        StorageCtx::enter(&mut storage, || {
1720            let mut vc = ValidatorConfigV2::new();
1721            vc.initialize(owner)?;
1722
1723            let (pubkey, sig) = make_test_keypair_and_signature(
1724                validator,
1725                "192.168.1.1:8000",
1726                "192.168.1.1",
1727                SignatureKind::Add {
1728                    fee_recipient: validator,
1729                },
1730            );
1731            vc.storage.set_block_number(200);
1732            vc.add_validator(
1733                owner,
1734                make_add_call(
1735                    validator,
1736                    pubkey,
1737                    "192.168.1.1:8000",
1738                    "192.168.1.1",
1739                    validator,
1740                    sig,
1741                ),
1742            )?;
1743
1744            vc.transfer_validator_ownership(
1745                owner,
1746                IValidatorConfigV2::transferValidatorOwnershipCall {
1747                    idx: 0,
1748                    newAddress: new_address,
1749                },
1750            )?;
1751
1752            // Old address lookup gone
1753            let result = vc.validator_by_address(validator);
1754            assert_eq!(
1755                result,
1756                Err(ValidatorConfigV2Error::validator_not_found().into())
1757            );
1758
1759            // New address works
1760            let v = vc.validator_by_address(new_address)?;
1761            assert_eq!(v.publicKey, pubkey);
1762            assert_eq!(v.validatorAddress, new_address);
1763
1764            Ok(())
1765        })
1766    }
1767
1768    #[test]
1769    fn test_transfer_validator_ownership_rejects_deactivated() -> eyre::Result<()> {
1770        let mut storage = HashMapStorageProvider::new(1);
1771        let owner = Address::random();
1772        let validator = Address::random();
1773        StorageCtx::enter(&mut storage, || {
1774            let mut vc = ValidatorConfigV2::new();
1775            vc.initialize(owner)?;
1776
1777            vc.storage.set_block_number(200);
1778            vc.add_validator(
1779                owner,
1780                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
1781            )?;
1782
1783            vc.storage.set_block_number(300);
1784            vc.deactivate_validator(
1785                owner,
1786                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
1787            )?;
1788
1789            let result = vc.transfer_validator_ownership(
1790                owner,
1791                IValidatorConfigV2::transferValidatorOwnershipCall {
1792                    idx: 0,
1793                    newAddress: Address::random(),
1794                },
1795            );
1796            assert_eq!(
1797                result,
1798                Err(ValidatorConfigV2Error::validator_already_deactivated().into())
1799            );
1800
1801            Ok(())
1802        })
1803    }
1804
1805    #[test]
1806    fn test_set_network_identity_rotation_epoch() -> eyre::Result<()> {
1807        let mut storage = HashMapStorageProvider::new(1);
1808        let owner = Address::random();
1809        StorageCtx::enter(&mut storage, || {
1810            let mut vc = ValidatorConfigV2::new();
1811            vc.initialize(owner)?;
1812
1813            assert_eq!(vc.get_next_network_identity_rotation_epoch()?, 0);
1814
1815            vc.set_network_identity_rotation_epoch(
1816                owner,
1817                IValidatorConfigV2::setNetworkIdentityRotationEpochCall { epoch: 42 },
1818            )?;
1819            assert_eq!(vc.get_next_network_identity_rotation_epoch()?, 42);
1820
1821            let non_owner = Address::random();
1822            let result = vc.set_network_identity_rotation_epoch(
1823                non_owner,
1824                IValidatorConfigV2::setNetworkIdentityRotationEpochCall { epoch: 100 },
1825            );
1826            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
1827
1828            Ok(())
1829        })
1830    }
1831
1832    #[test]
1833    fn test_not_initialized_errors() -> eyre::Result<()> {
1834        let mut storage = HashMapStorageProvider::new(1);
1835        let owner = Address::random();
1836        StorageCtx::enter(&mut storage, || {
1837            let mut vc = ValidatorConfigV2::new();
1838
1839            let result = vc.add_validator(
1840                owner,
1841                make_valid_add_call(
1842                    Address::random(),
1843                    "192.168.1.1:8000",
1844                    "192.168.1.1",
1845                    Address::random(),
1846                ),
1847            );
1848            assert_eq!(
1849                result,
1850                Err(ValidatorConfigV2Error::not_initialized().into())
1851            );
1852
1853            Ok(())
1854        })
1855    }
1856
1857    #[test]
1858    fn test_egress_validates_ip_without_port() -> eyre::Result<()> {
1859        let mut storage = HashMapStorageProvider::new(1);
1860        let owner = Address::random();
1861        StorageCtx::enter(&mut storage, || {
1862            let mut vc = ValidatorConfigV2::new();
1863            vc.initialize(owner)?;
1864
1865            let addr1 = Address::random();
1866            let (pubkey1, sig1) = make_test_keypair_and_signature(
1867                addr1,
1868                "192.168.1.1:8000",
1869                "192.168.1.1:9000",
1870                SignatureKind::Add {
1871                    fee_recipient: addr1,
1872                },
1873            );
1874
1875            // IP:port for egress should fail (egress validation happens before signature)
1876            let result = vc.add_validator(
1877                owner,
1878                make_add_call(
1879                    addr1,
1880                    pubkey1,
1881                    "192.168.1.1:8000",
1882                    "192.168.1.1:9000",
1883                    addr1,
1884                    sig1,
1885                ),
1886            );
1887            assert!(result.is_err(), "egress with port should be rejected");
1888
1889            // Plain IP for egress should succeed
1890            vc.storage.set_block_number(200);
1891            let result = vc.add_validator(
1892                owner,
1893                make_valid_add_call(
1894                    Address::random(),
1895                    "192.168.1.1:8000",
1896                    "192.168.1.1",
1897                    Address::random(),
1898                ),
1899            );
1900            assert!(result.is_ok(), "egress with plain IP should succeed");
1901
1902            Ok(())
1903        })
1904    }
1905
1906    #[test]
1907    fn test_migration_from_v1() -> eyre::Result<()> {
1908        let mut storage = HashMapStorageProvider::new(1);
1909        let owner = Address::random();
1910        let v1_addr = Address::random();
1911        let v2_addr = Address::random();
1912
1913        StorageCtx::enter(&mut storage, || {
1914            // Set up V1 with some validators
1915            let mut v1 = v1();
1916            v1.initialize(owner)?;
1917
1918            v1.add_validator(
1919                owner,
1920                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
1921                    newValidatorAddress: v1_addr,
1922                    publicKey: FixedBytes::<32>::from([0x11; 32]),
1923                    active: true,
1924                    inboundAddress: "192.168.1.1:8000".to_string(),
1925                    outboundAddress: "192.168.1.1:9000".to_string(),
1926                },
1927            )?;
1928
1929            v1.add_validator(
1930                owner,
1931                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
1932                    newValidatorAddress: v2_addr,
1933                    publicKey: FixedBytes::<32>::from([0x22; 32]),
1934                    active: false,
1935                    inboundAddress: "192.168.1.2:8000".to_string(),
1936                    outboundAddress: "192.168.1.2:9000".to_string(),
1937                },
1938            )?;
1939
1940            // Now migrate to V2 (not initialized — migration mode)
1941            let mut v2 = ValidatorConfigV2::new();
1942
1943            // Migrate second validator first (reverse order)
1944            v2.storage.set_block_number(100);
1945            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 1 })?;
1946
1947            assert_eq!(v2.validator_count()?, 1);
1948            let migrated = v2.validator_by_index(0)?;
1949            assert_eq!(migrated.validatorAddress, v2_addr);
1950            assert_eq!(migrated.publicKey, FixedBytes::<32>::from([0x22; 32]));
1951            assert_eq!(migrated.deactivatedAtHeight, 100);
1952
1953            // Migrate first validator
1954            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
1955
1956            assert_eq!(v2.validator_count()?, 2);
1957
1958            // Initialize V2
1959            v2.storage.set_block_number(400);
1960            v2.initialize_if_migrated(owner)?;
1961
1962            assert!(v2.is_initialized()?);
1963
1964            // Migration should be blocked after initialization
1965            v2.storage.set_block_number(100);
1966            let result =
1967                v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 });
1968            assert_eq!(
1969                result,
1970                Err(ValidatorConfigV2Error::already_initialized().into())
1971            );
1972
1973            Ok(())
1974        })
1975    }
1976
1977    /// V1 stores outboundAddress as ip:port, but V2 egress is plain IP.
1978    /// Migration must strip the port so migrated data satisfies V2 validation.
1979    #[test]
1980    fn test_migration_strips_port_from_v1_outbound_address() -> eyre::Result<()> {
1981        let mut storage = HashMapStorageProvider::new(1);
1982        let owner = Address::random();
1983        let v1_addr = Address::random();
1984
1985        StorageCtx::enter(&mut storage, || {
1986            // V1 validator with outboundAddress = ip:port
1987            let mut v1 = v1();
1988            v1.initialize(owner)?;
1989            v1.add_validator(
1990                owner,
1991                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
1992                    newValidatorAddress: v1_addr,
1993                    publicKey: FixedBytes::<32>::from([0x11; 32]),
1994                    active: true,
1995                    inboundAddress: "192.168.1.1:8000".to_string(),
1996                    outboundAddress: "192.168.1.1:9000".to_string(),
1997                },
1998            )?;
1999
2000            // Migrate to V2 (not initialized — migration mode)
2001            let mut v2 = ValidatorConfigV2::new();
2002
2003            v2.storage.set_block_number(100);
2004            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
2005            v2.storage.set_block_number(400);
2006            v2.initialize_if_migrated(owner)?;
2007
2008            // Egress should be plain IP (port stripped from V1's "192.168.1.1:9000")
2009            let migrated = v2.validator_by_index(0)?;
2010            assert_eq!(
2011                migrated.egress, "192.168.1.1",
2012                "migration should strip port from V1 outboundAddress"
2013            );
2014
2015            // Ingress preserved as-is (both V1 and V2 use ip:port)
2016            assert_eq!(migrated.ingress, "192.168.1.1:8000");
2017
2018            // setIpAddresses should accept the migrated egress value
2019            v2.set_ip_addresses(
2020                owner,
2021                IValidatorConfigV2::setIpAddressesCall {
2022                    idx: 0,
2023                    ingress: "192.168.1.1:8000".to_string(),
2024                    egress: migrated.egress,
2025                },
2026            )?;
2027
2028            Ok(())
2029        })
2030    }
2031
2032    #[test]
2033    fn test_migration_out_of_order_fails() -> eyre::Result<()> {
2034        let mut storage = HashMapStorageProvider::new(1);
2035        let owner = Address::random();
2036
2037        StorageCtx::enter(&mut storage, || {
2038            // Set up V1 with validators
2039            let mut v1 = v1();
2040            v1.initialize(owner)?;
2041
2042            v1.add_validator(
2043                owner,
2044                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2045                    newValidatorAddress: Address::random(),
2046                    publicKey: FixedBytes::<32>::from([0x11; 32]),
2047                    active: true,
2048                    inboundAddress: "192.168.1.1:8000".to_string(),
2049                    outboundAddress: "192.168.1.1:9000".to_string(),
2050                },
2051            )?;
2052
2053            v1.add_validator(
2054                owner,
2055                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2056                    newValidatorAddress: Address::random(),
2057                    publicKey: FixedBytes::<32>::from([0x22; 32]),
2058                    active: true,
2059                    inboundAddress: "192.168.1.2:8000".to_string(),
2060                    outboundAddress: "192.168.1.2:9000".to_string(),
2061                },
2062            )?;
2063
2064            // Try to migrate out of order (should start at idx 1, not idx 0)
2065            let mut v2 = ValidatorConfigV2::new();
2066            v2.storage.set_block_number(100);
2067            let result =
2068                v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 });
2069
2070            assert_eq!(
2071                result,
2072                Err(ValidatorConfigV2Error::invalid_migration_index().into())
2073            );
2074
2075            Ok(())
2076        })
2077    }
2078
2079    #[test]
2080    fn test_initialize_before_migration_complete_fails() -> eyre::Result<()> {
2081        let mut storage = HashMapStorageProvider::new(1);
2082        let owner = Address::random();
2083
2084        StorageCtx::enter(&mut storage, || {
2085            // Set up V1 with 2 validators
2086            let mut v1 = v1();
2087            v1.initialize(owner)?;
2088
2089            v1.add_validator(
2090                owner,
2091                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2092                    newValidatorAddress: Address::random(),
2093                    publicKey: FixedBytes::<32>::from([0x11; 32]),
2094                    active: true,
2095                    inboundAddress: "192.168.1.1:8000".to_string(),
2096                    outboundAddress: "192.168.1.1:9000".to_string(),
2097                },
2098            )?;
2099
2100            v1.add_validator(
2101                owner,
2102                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2103                    newValidatorAddress: Address::random(),
2104                    publicKey: FixedBytes::<32>::from([0x22; 32]),
2105                    active: true,
2106                    inboundAddress: "192.168.1.2:8000".to_string(),
2107                    outboundAddress: "192.168.1.2:9000".to_string(),
2108                },
2109            )?;
2110
2111            // Only migrate second validator (reverse order starts at idx 1)
2112            let mut v2 = ValidatorConfigV2::new();
2113            v2.storage.set_block_number(100);
2114            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 1 })?;
2115
2116            // Try to initialize with incomplete migration
2117            v2.storage.set_block_number(400);
2118            let result = v2.initialize_if_migrated(owner);
2119
2120            assert_eq!(
2121                result,
2122                Err(ValidatorConfigV2Error::migration_not_complete().into())
2123            );
2124
2125            Ok(())
2126        })
2127    }
2128
2129    #[test]
2130    fn test_add_validator_reuses_deactivated_address() -> eyre::Result<()> {
2131        let mut storage = HashMapStorageProvider::new(1);
2132        let owner = Address::random();
2133        let validator_addr = Address::random();
2134        StorageCtx::enter(&mut storage, || {
2135            let mut vc = ValidatorConfigV2::new();
2136            vc.initialize(owner)?;
2137
2138            // Add first validator
2139            let (pubkey1, sig1) = make_test_keypair_and_signature(
2140                validator_addr,
2141                "192.168.1.1:8000",
2142                "192.168.1.1",
2143                SignatureKind::Add {
2144                    fee_recipient: validator_addr,
2145                },
2146            );
2147            vc.storage.set_block_number(200);
2148            vc.add_validator(
2149                owner,
2150                make_add_call(
2151                    validator_addr,
2152                    pubkey1,
2153                    "192.168.1.1:8000",
2154                    "192.168.1.1",
2155                    validator_addr,
2156                    sig1,
2157                ),
2158            )?;
2159
2160            // Deactivate it
2161            vc.storage.set_block_number(300);
2162            vc.deactivate_validator(
2163                owner,
2164                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
2165            )?;
2166
2167            // Now add new validator with SAME address but different pubkey - should succeed
2168            let (pubkey2, sig2) = make_test_keypair_and_signature(
2169                validator_addr,
2170                "192.168.1.2:8000",
2171                "192.168.1.2",
2172                SignatureKind::Add {
2173                    fee_recipient: validator_addr,
2174                },
2175            );
2176            vc.storage.set_block_number(400);
2177            vc.add_validator(
2178                owner,
2179                make_add_call(
2180                    validator_addr,
2181                    pubkey2,
2182                    "192.168.1.2:8000",
2183                    "192.168.1.2",
2184                    validator_addr,
2185                    sig2,
2186                ),
2187            )?;
2188
2189            // Should have 2 validators
2190            assert_eq!(vc.validator_count()?, 2);
2191
2192            // First one is deactivated
2193            let v1 = vc.validator_by_index(0)?;
2194            assert_eq!(v1.validatorAddress, validator_addr);
2195            assert_eq!(v1.publicKey, pubkey1);
2196            assert_eq!(v1.deactivatedAtHeight, 300);
2197
2198            // Second one is active with same address
2199            let v2 = vc.validator_by_index(1)?;
2200            assert_eq!(v2.validatorAddress, validator_addr);
2201            assert_eq!(v2.publicKey, pubkey2);
2202            assert_eq!(v2.deactivatedAtHeight, 0);
2203
2204            // Lookup by address returns the NEW active validator
2205            let by_addr = vc.validator_by_address(validator_addr)?;
2206            assert_eq!(by_addr.publicKey, pubkey2);
2207            assert_eq!(by_addr.deactivatedAtHeight, 0);
2208
2209            // Lookup by old pubkey returns old deactivated validator
2210            let by_old_pk = vc.validator_by_public_key(pubkey1)?;
2211            assert_eq!(by_old_pk.deactivatedAtHeight, 300);
2212
2213            // Lookup by new pubkey returns new active validator
2214            let by_new_pk = vc.validator_by_public_key(pubkey2)?;
2215            assert_eq!(by_new_pk.deactivatedAtHeight, 0);
2216
2217            Ok(())
2218        })
2219    }
2220
2221    #[test]
2222    fn test_add_validator_rejects_duplicate_ingress() -> eyre::Result<()> {
2223        let mut storage = HashMapStorageProvider::new(1);
2224        let owner = Address::random();
2225        StorageCtx::enter(&mut storage, || {
2226            let mut vc = ValidatorConfigV2::new();
2227            vc.initialize(owner)?;
2228
2229            vc.storage.set_block_number(200);
2230            vc.add_validator(
2231                owner,
2232                make_valid_add_call(
2233                    Address::random(),
2234                    "192.168.1.1:8000",
2235                    "192.168.1.1",
2236                    Address::random(),
2237                ),
2238            )?;
2239
2240            vc.storage.set_block_number(201);
2241            let result = vc.add_validator(
2242                owner,
2243                make_valid_add_call(
2244                    Address::random(),
2245                    "192.168.1.1:8000",
2246                    "192.168.2.1",
2247                    Address::random(),
2248                ),
2249            );
2250
2251            assert!(result.is_err());
2252            Ok(())
2253        })
2254    }
2255
2256    #[test]
2257    fn test_ingress_reuse_after_deactivation() -> eyre::Result<()> {
2258        let mut storage = HashMapStorageProvider::new(1);
2259        let owner = Address::random();
2260        let v1 = Address::random();
2261        StorageCtx::enter(&mut storage, || {
2262            let mut vc = ValidatorConfigV2::new();
2263            vc.initialize(owner)?;
2264
2265            vc.storage.set_block_number(200);
2266            vc.add_validator(
2267                owner,
2268                make_valid_add_call(v1, "192.168.1.1:8000", "192.168.1.1", v1),
2269            )?;
2270
2271            vc.storage.set_block_number(300);
2272            vc.deactivate_validator(
2273                owner,
2274                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
2275            )?;
2276
2277            // Should allow IP reuse after deactivation
2278            vc.storage.set_block_number(400);
2279            vc.add_validator(
2280                owner,
2281                make_valid_add_call(
2282                    Address::random(),
2283                    "192.168.1.1:8000",
2284                    "192.168.1.1",
2285                    Address::random(),
2286                ),
2287            )?;
2288
2289            Ok(())
2290        })
2291    }
2292
2293    #[test]
2294    fn test_ingress_reuse_after_rotation() -> eyre::Result<()> {
2295        let mut storage = HashMapStorageProvider::new(1);
2296        let owner = Address::random();
2297        let v1 = Address::random();
2298        StorageCtx::enter(&mut storage, || {
2299            let mut vc = ValidatorConfigV2::new();
2300            vc.initialize(owner)?;
2301
2302            vc.storage.set_block_number(200);
2303            vc.add_validator(
2304                owner,
2305                make_valid_add_call(v1, "[2001:db8::1]:8000", "2001:db8::1", Address::random()),
2306            )?;
2307
2308            vc.storage.set_block_number(300);
2309
2310            // Rotate to different ingress
2311            let (new_pubkey, new_sig) = make_test_keypair_and_signature(
2312                v1,
2313                "[2001:db8::1]:8001",
2314                "2001:db8::1",
2315                SignatureKind::Rotate,
2316            );
2317            vc.rotate_validator(
2318                owner,
2319                IValidatorConfigV2::rotateValidatorCall {
2320                    idx: 0,
2321                    publicKey: new_pubkey,
2322                    ingress: "[2001:db8::1]:8001".to_string(),
2323                    egress: "2001:db8::1".to_string(),
2324                    signature: new_sig.into(),
2325                },
2326            )?;
2327            let v = vc.validator_by_address(v1)?;
2328            assert_eq!(v.ingress, "[2001:db8::1]:8001");
2329
2330            // Should allow ingress reuse after rotation.
2331            vc.storage.set_block_number(400);
2332            vc.set_ip_addresses(
2333                owner,
2334                IValidatorConfigV2::setIpAddressesCall {
2335                    idx: 0,
2336                    ingress: "[2001:db8::1]:8000".to_string(),
2337                    egress: "2001:db8::1".to_string(),
2338                },
2339            )?;
2340            let v = vc.validator_by_address(v1)?;
2341            assert_eq!(v.ingress, "[2001:db8::1]:8000");
2342
2343            Ok(())
2344        })
2345    }
2346
2347    #[test]
2348    fn test_set_ip_addresses_rejects_pre_init() -> eyre::Result<()> {
2349        let mut storage = HashMapStorageProvider::new(1);
2350        let owner = Address::random();
2351        let v1_addr = Address::random();
2352
2353        StorageCtx::enter(&mut storage, || {
2354            // Setup V1
2355            let mut v1 = v1();
2356            v1.initialize(owner)?;
2357            v1.add_validator(
2358                owner,
2359                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2360                    newValidatorAddress: v1_addr,
2361                    publicKey: FixedBytes::<32>::from([0x11; 32]),
2362                    active: true,
2363                    inboundAddress: "192.168.1.1:8000".to_string(),
2364                    outboundAddress: "192.168.1.1:9000".to_string(),
2365                },
2366            )?;
2367
2368            // Migrate to V2
2369            let mut v2 = ValidatorConfigV2::new();
2370            v2.storage.set_block_number(100);
2371            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
2372
2373            let result = v2.set_ip_addresses(
2374                owner,
2375                IValidatorConfigV2::setIpAddressesCall {
2376                    idx: 0,
2377                    ingress: "10.0.0.1:8000".to_string(),
2378                    egress: "10.0.0.1".to_string(),
2379                },
2380            );
2381
2382            assert_eq!(
2383                result,
2384                Err(ValidatorConfigV2Error::not_initialized().into())
2385            );
2386            Ok(())
2387        })
2388    }
2389
2390    #[test]
2391    fn test_rotate_removes_and_checks_ips() -> eyre::Result<()> {
2392        let mut storage = HashMapStorageProvider::new(1);
2393        let owner = Address::random();
2394        let v1 = Address::random();
2395        let v2 = Address::random();
2396
2397        StorageCtx::enter(&mut storage, || {
2398            let mut vc = ValidatorConfigV2::new();
2399            vc.initialize(owner)?;
2400
2401            vc.storage.set_block_number(200);
2402            vc.add_validator(
2403                owner,
2404                make_valid_add_call(v1, "192.168.1.1:8000", "192.168.1.1", v1),
2405            )?;
2406            vc.add_validator(
2407                owner,
2408                make_valid_add_call(v2, "192.168.2.1:8000", "192.168.2.1", v2),
2409            )?;
2410
2411            // Rotate v1 to v2's IPs should fail
2412            let (new_pk, sig) = make_test_keypair_and_signature(
2413                v1,
2414                "192.168.2.1:8000",
2415                "192.168.2.1",
2416                SignatureKind::Rotate,
2417            );
2418
2419            vc.storage.set_block_number(300);
2420            let result = vc.rotate_validator(
2421                owner,
2422                IValidatorConfigV2::rotateValidatorCall {
2423                    idx: 0,
2424                    publicKey: new_pk,
2425                    ingress: "192.168.2.1:8000".to_string(),
2426                    egress: "192.168.2.1".to_string(),
2427                    signature: sig.into(),
2428                },
2429            );
2430
2431            assert!(result.is_err());
2432            Ok(())
2433        })
2434    }
2435
2436    #[test]
2437    fn test_migrate_skips_duplicate_ingress() -> eyre::Result<()> {
2438        let mut storage = HashMapStorageProvider::new(1);
2439        let owner = Address::random();
2440
2441        StorageCtx::enter(&mut storage, || {
2442            let mut v1 = v1();
2443            v1.initialize(owner)?;
2444            v1.add_validator(
2445                owner,
2446                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2447                    newValidatorAddress: Address::random(),
2448                    publicKey: FixedBytes::<32>::from([0x11; 32]),
2449                    active: true,
2450                    inboundAddress: "192.168.1.1:8000".to_string(),
2451                    outboundAddress: "192.168.1.1:9000".to_string(),
2452                },
2453            )?;
2454            v1.add_validator(
2455                owner,
2456                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2457                    newValidatorAddress: Address::random(),
2458                    publicKey: FixedBytes::<32>::from([0x22; 32]),
2459                    active: true,
2460                    inboundAddress: "192.168.1.1:8000".to_string(),
2461                    outboundAddress: "192.168.2.1:9000".to_string(),
2462                },
2463            )?;
2464
2465            let mut v2 = ValidatorConfigV2::new();
2466            v2.storage.set_block_number(100);
2467            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 1 })?;
2468
2469            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
2470            assert_eq!(v2.validator_count()?, 1);
2471            assert_eq!(v2.config.migration_skipped_count.read()?, 1);
2472
2473            v2.storage.set_block_number(400);
2474            v2.initialize_if_migrated(owner)?;
2475            assert!(v2.is_initialized()?);
2476
2477            Ok(())
2478        })
2479    }
2480
2481    #[test]
2482    fn test_migrate_skips_invalid_ed25519_pubkey() -> eyre::Result<()> {
2483        let mut storage = HashMapStorageProvider::new(1);
2484        let owner = Address::random();
2485
2486        StorageCtx::enter(&mut storage, || {
2487            let mut v1 = v1();
2488            v1.initialize(owner)?;
2489            v1.add_validator(
2490                owner,
2491                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2492                    newValidatorAddress: Address::random(),
2493                    publicKey: FixedBytes::<32>::from([0xDD; 32]),
2494                    active: true,
2495                    inboundAddress: "192.168.1.1:8000".to_string(),
2496                    outboundAddress: "192.168.1.1:9000".to_string(),
2497                },
2498            )?;
2499            v1.add_validator(
2500                owner,
2501                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2502                    newValidatorAddress: Address::random(),
2503                    publicKey: FixedBytes::<32>::from([0x22; 32]),
2504                    active: true,
2505                    inboundAddress: "192.168.1.2:8000".to_string(),
2506                    outboundAddress: "192.168.1.2:9000".to_string(),
2507                },
2508            )?;
2509
2510            let mut v2 = ValidatorConfigV2::new();
2511            v2.storage.set_block_number(100);
2512
2513            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 1 })?;
2514            assert_eq!(v2.validator_count()?, 1);
2515
2516            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
2517            assert_eq!(v2.validator_count()?, 1);
2518            assert_eq!(v2.config.migration_skipped_count.read()?, 1);
2519
2520            v2.storage.set_block_number(400);
2521            v2.initialize_if_migrated(owner)?;
2522            assert!(v2.is_initialized()?);
2523
2524            Ok(())
2525        })
2526    }
2527
2528    #[test]
2529    fn test_migrate_overwrites_duplicate_pubkey() -> eyre::Result<()> {
2530        let mut storage = HashMapStorageProvider::new(1);
2531        let owner = Address::random();
2532        let addr1 = Address::random();
2533        let addr2 = Address::random();
2534
2535        StorageCtx::enter(&mut storage, || {
2536            let mut v1 = v1();
2537            v1.initialize(owner)?;
2538            v1.add_validator(
2539                owner,
2540                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2541                    newValidatorAddress: addr1,
2542                    publicKey: FixedBytes::<32>::from([0x11; 32]),
2543                    active: true,
2544                    inboundAddress: "192.168.1.1:8000".to_string(),
2545                    outboundAddress: "192.168.1.1:9000".to_string(),
2546                },
2547            )?;
2548            v1.add_validator(
2549                owner,
2550                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
2551                    newValidatorAddress: addr2,
2552                    publicKey: FixedBytes::<32>::from([0x11; 32]),
2553                    active: true,
2554                    inboundAddress: "192.168.1.2:8000".to_string(),
2555                    outboundAddress: "192.168.1.2:9000".to_string(),
2556                },
2557            )?;
2558
2559            let mut v2 = ValidatorConfigV2::new();
2560            v2.storage.set_block_number(100);
2561
2562            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 1 })?;
2563            assert_eq!(v2.validator_count()?, 1);
2564
2565            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
2566            assert_eq!(v2.validator_count()?, 1);
2567            assert_eq!(v2.config.migration_skipped_count.read()?, 1);
2568
2569            let migrated = v2.validator_by_index(0)?;
2570            assert_eq!(migrated.validatorAddress, addr2);
2571            assert_eq!(migrated.ingress, "192.168.1.2:8000");
2572            assert_eq!(migrated.egress, "192.168.1.2");
2573
2574            v2.storage.set_block_number(400);
2575            v2.initialize_if_migrated(owner)?;
2576            assert!(v2.is_initialized()?);
2577
2578            Ok(())
2579        })
2580    }
2581
2582    #[test]
2583    fn test_add_validator_rejects_third_party() -> eyre::Result<()> {
2584        let mut storage = HashMapStorageProvider::new(1);
2585        let owner = Address::random();
2586        let validator = Address::random();
2587        let third_party = Address::random();
2588        StorageCtx::enter(&mut storage, || {
2589            let mut vc = ValidatorConfigV2::new();
2590            vc.initialize(owner)?;
2591
2592            vc.storage.set_block_number(200);
2593            vc.add_validator(
2594                owner,
2595                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2596            )?;
2597
2598            // Third party is neither owner nor validator_owner — should be rejected
2599            let result = vc.set_ip_addresses(
2600                third_party,
2601                IValidatorConfigV2::setIpAddressesCall {
2602                    idx: 0,
2603                    ingress: "10.0.0.1:8000".to_string(),
2604                    egress: "10.0.0.1".to_string(),
2605                },
2606            );
2607            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
2608
2609            let result = vc.transfer_validator_ownership(
2610                third_party,
2611                IValidatorConfigV2::transferValidatorOwnershipCall {
2612                    idx: 0,
2613                    newAddress: Address::random(),
2614                },
2615            );
2616            assert_eq!(result, Err(ValidatorConfigV2Error::unauthorized().into()));
2617
2618            Ok(())
2619        })
2620    }
2621
2622    #[test]
2623    fn test_rotate_validator_to_different_ingress() -> eyre::Result<()> {
2624        let mut storage = HashMapStorageProvider::new(1);
2625        let owner = Address::random();
2626        let validator = Address::random();
2627        StorageCtx::enter(&mut storage, || {
2628            let mut vc = ValidatorConfigV2::new();
2629            vc.initialize(owner)?;
2630
2631            vc.storage.set_block_number(200);
2632            vc.add_validator(
2633                owner,
2634                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2635            )?;
2636
2637            // Rotate keeping the same ingress/egress — should succeed
2638            let (new_pubkey, new_sig) = make_test_keypair_and_signature(
2639                validator,
2640                "192.168.1.1:8001",
2641                "192.168.1.1",
2642                SignatureKind::Rotate,
2643            );
2644            vc.storage.set_block_number(300);
2645            vc.rotate_validator(
2646                owner,
2647                IValidatorConfigV2::rotateValidatorCall {
2648                    idx: 0,
2649                    publicKey: new_pubkey,
2650                    ingress: "192.168.1.1:8001".to_string(),
2651                    egress: "192.168.1.1".to_string(),
2652                    signature: new_sig.into(),
2653                },
2654            )?;
2655
2656            assert_eq!(vc.validator_count()?, 2);
2657            assert_eq!(vc.validator_by_index(0)?.deactivatedAtHeight, 0);
2658            assert_eq!(vc.validator_by_index(1)?.deactivatedAtHeight, 300);
2659            assert_eq!(vc.validator_by_address(validator)?.publicKey, new_pubkey);
2660            assert_eq!(
2661                vc.validator_by_address(validator)?.ingress,
2662                "192.168.1.1:8001"
2663            );
2664            assert_eq!(vc.validator_by_address(validator)?.egress, "192.168.1.1");
2665
2666            Ok(())
2667        })
2668    }
2669
2670    #[test]
2671    fn test_rotate_validator_rejects_same_ingress() -> eyre::Result<()> {
2672        let mut storage = HashMapStorageProvider::new(1);
2673        let owner = Address::random();
2674        let validator = Address::random();
2675        StorageCtx::enter(&mut storage, || {
2676            let mut vc = ValidatorConfigV2::new();
2677            vc.initialize(owner)?;
2678
2679            vc.storage.set_block_number(200);
2680            vc.add_validator(
2681                owner,
2682                make_valid_add_call(
2683                    validator,
2684                    "192.168.1.1:8000",
2685                    "192.168.1.1",
2686                    Address::random(),
2687                ),
2688            )?;
2689
2690            // Rotate keeping the same ingress/egress — should succeed
2691            let (new_pubkey, new_sig) = make_test_keypair_and_signature(
2692                validator,
2693                "192.168.1.1:8000",
2694                "192.168.1.1",
2695                SignatureKind::Rotate,
2696            );
2697            vc.storage.set_block_number(300);
2698            assert!(
2699                vc.rotate_validator(
2700                    owner,
2701                    IValidatorConfigV2::rotateValidatorCall {
2702                        idx: 0,
2703                        publicKey: new_pubkey,
2704                        ingress: "192.168.1.1:8000".to_string(),
2705                        egress: "192.168.1.1".to_string(),
2706                        signature: new_sig.into(),
2707                    },
2708                )
2709                .is_err()
2710            );
2711            Ok(())
2712        })
2713    }
2714
2715    #[test]
2716    fn test_set_ip_addresses_ingress_only() -> eyre::Result<()> {
2717        let mut storage = HashMapStorageProvider::new(1);
2718        let owner = Address::random();
2719        let validator = Address::random();
2720        StorageCtx::enter(&mut storage, || {
2721            let mut vc = ValidatorConfigV2::new();
2722            vc.initialize(owner)?;
2723
2724            vc.storage.set_block_number(200);
2725            vc.add_validator(
2726                owner,
2727                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2728            )?;
2729
2730            // Change ingress only, keep egress the same
2731            vc.set_ip_addresses(
2732                owner,
2733                IValidatorConfigV2::setIpAddressesCall {
2734                    idx: 0,
2735                    ingress: "10.0.0.1:8000".to_string(),
2736                    egress: "192.168.1.1".to_string(),
2737                },
2738            )?;
2739
2740            let v = vc.validator_by_address(validator)?;
2741            assert_eq!(v.ingress, "10.0.0.1:8000");
2742            assert_eq!(v.egress, "192.168.1.1");
2743
2744            Ok(())
2745        })
2746    }
2747
2748    #[test]
2749    fn test_set_ip_addresses_ingress_port_only() -> eyre::Result<()> {
2750        let mut storage = HashMapStorageProvider::new(1);
2751        let owner = Address::random();
2752        let validator = Address::random();
2753        StorageCtx::enter(&mut storage, || {
2754            let mut vc = ValidatorConfigV2::new();
2755            vc.initialize(owner)?;
2756
2757            vc.storage.set_block_number(200);
2758            vc.add_validator(
2759                owner,
2760                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2761            )?;
2762
2763            // Change ingress only, keep egress the same
2764            vc.set_ip_addresses(
2765                owner,
2766                IValidatorConfigV2::setIpAddressesCall {
2767                    idx: 0,
2768                    ingress: "192.168.1.1:8001".to_string(),
2769                    egress: "192.168.1.1".to_string(),
2770                },
2771            )?;
2772
2773            let v = vc.validator_by_address(validator)?;
2774            assert_eq!(v.ingress, "192.168.1.1:8001");
2775            assert_eq!(v.egress, "192.168.1.1");
2776
2777            Ok(())
2778        })
2779    }
2780
2781    #[test]
2782    fn test_set_ip_addresses_egress_only() -> eyre::Result<()> {
2783        let mut storage = HashMapStorageProvider::new(1);
2784        let owner = Address::random();
2785        let validator = Address::random();
2786        StorageCtx::enter(&mut storage, || {
2787            let mut vc = ValidatorConfigV2::new();
2788            vc.initialize(owner)?;
2789
2790            vc.storage.set_block_number(200);
2791            vc.add_validator(
2792                owner,
2793                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2794            )?;
2795
2796            // Change egress only, keep ingress the same
2797            vc.set_ip_addresses(
2798                owner,
2799                IValidatorConfigV2::setIpAddressesCall {
2800                    idx: 0,
2801                    ingress: "192.168.1.1:8000".to_string(),
2802                    egress: "10.0.0.1".to_string(),
2803                },
2804            )?;
2805
2806            let v = vc.validator_by_address(validator)?;
2807            assert_eq!(v.ingress, "192.168.1.1:8000");
2808            assert_eq!(v.egress, "10.0.0.1");
2809
2810            Ok(())
2811        })
2812    }
2813
2814    #[test]
2815    fn test_set_ip_addresses_rejects_duplicate_ingress() -> eyre::Result<()> {
2816        let mut storage = HashMapStorageProvider::new(1);
2817        let owner = Address::random();
2818        let v1 = Address::random();
2819        let v2 = Address::random();
2820        StorageCtx::enter(&mut storage, || {
2821            let mut vc = ValidatorConfigV2::new();
2822            vc.initialize(owner)?;
2823
2824            vc.storage.set_block_number(200);
2825            vc.add_validator(
2826                owner,
2827                make_valid_add_call(v1, "192.168.1.1:8000", "192.168.1.1", v1),
2828            )?;
2829            vc.add_validator(
2830                owner,
2831                make_valid_add_call(v2, "192.168.2.1:8000", "192.168.2.1", v2),
2832            )?;
2833
2834            let result = vc.set_ip_addresses(
2835                owner,
2836                IValidatorConfigV2::setIpAddressesCall {
2837                    idx: 1,
2838                    ingress: "192.168.1.1:8000".to_string(),
2839                    egress: "192.168.2.1".to_string(),
2840                },
2841            );
2842
2843            assert!(result.is_err());
2844            Ok(())
2845        })
2846    }
2847
2848    #[test]
2849    fn test_set_ip_addresses_allows_same_ip_different_port() -> eyre::Result<()> {
2850        let mut storage = HashMapStorageProvider::new(1);
2851        let owner = Address::random();
2852        let validator = Address::random();
2853        StorageCtx::enter(&mut storage, || {
2854            let mut vc = ValidatorConfigV2::new();
2855            vc.initialize(owner)?;
2856
2857            vc.storage.set_block_number(200);
2858            vc.add_validator(
2859                owner,
2860                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2861            )?;
2862
2863            vc.set_ip_addresses(
2864                owner,
2865                IValidatorConfigV2::setIpAddressesCall {
2866                    idx: 0,
2867                    ingress: "192.168.1.1:9000".to_string(),
2868                    egress: "192.168.1.1".to_string(),
2869                },
2870            )?;
2871
2872            let v = vc.validator_by_address(validator)?;
2873            assert_eq!(v.ingress, "192.168.1.1:9000");
2874
2875            Ok(())
2876        })
2877    }
2878
2879    #[test]
2880    fn test_transfer_validator_ownership_by_validator() -> eyre::Result<()> {
2881        let mut storage = HashMapStorageProvider::new(1);
2882        let owner = Address::random();
2883        let validator = Address::random();
2884        let new_address = Address::random();
2885        StorageCtx::enter(&mut storage, || {
2886            let mut vc = ValidatorConfigV2::new();
2887            vc.initialize(owner)?;
2888
2889            vc.storage.set_block_number(200);
2890            vc.add_validator(
2891                owner,
2892                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
2893            )?;
2894
2895            // Validator transfers its own ownership (not owner)
2896            vc.transfer_validator_ownership(
2897                validator,
2898                IValidatorConfigV2::transferValidatorOwnershipCall {
2899                    idx: 0,
2900                    newAddress: new_address,
2901                },
2902            )?;
2903
2904            let result = vc.validator_by_address(validator);
2905            assert_eq!(
2906                result,
2907                Err(ValidatorConfigV2Error::validator_not_found().into())
2908            );
2909
2910            let v = vc.validator_by_address(new_address)?;
2911            assert_eq!(v.validatorAddress, new_address);
2912
2913            Ok(())
2914        })
2915    }
2916
2917    #[test]
2918    fn test_add_validator_rejects_deleted_pubkey() -> eyre::Result<()> {
2919        let mut storage = HashMapStorageProvider::new(1);
2920        let owner = Address::random();
2921        StorageCtx::enter(&mut storage, || {
2922            let mut vc = ValidatorConfigV2::new();
2923            vc.initialize(owner)?;
2924
2925            let addr1 = Address::random();
2926            let (pubkey, sig) = make_test_keypair_and_signature(
2927                addr1,
2928                "192.168.1.1:8000",
2929                "192.168.1.1",
2930                SignatureKind::Add {
2931                    fee_recipient: addr1,
2932                },
2933            );
2934            vc.storage.set_block_number(200);
2935            vc.add_validator(
2936                owner,
2937                make_add_call(addr1, pubkey, "192.168.1.1:8000", "192.168.1.1", addr1, sig),
2938            )?;
2939
2940            // Deactivate
2941            vc.storage.set_block_number(300);
2942            vc.deactivate_validator(
2943                owner,
2944                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
2945            )?;
2946
2947            // Try to add a new validator reusing the deleted pubkey — should fail
2948            let addr2 = Address::random();
2949            let result = vc.add_validator(
2950                owner,
2951                make_add_call(
2952                    addr2,
2953                    pubkey,
2954                    "192.168.2.1:8000",
2955                    "192.168.2.1",
2956                    addr2,
2957                    vec![0u8; 64],
2958                ),
2959            );
2960            assert_eq!(
2961                result,
2962                Err(ValidatorConfigV2Error::public_key_already_exists().into())
2963            );
2964
2965            Ok(())
2966        })
2967    }
2968
2969    #[test]
2970    fn test_add_validator_with_ipv6() -> eyre::Result<()> {
2971        let mut storage = HashMapStorageProvider::new(1);
2972        let owner = Address::random();
2973        let validator = Address::random();
2974        StorageCtx::enter(&mut storage, || {
2975            let mut vc = ValidatorConfigV2::new();
2976            vc.initialize(owner)?;
2977
2978            // Add validator with IPv6 ingress
2979            vc.storage.set_block_number(200);
2980            vc.add_validator(
2981                owner,
2982                make_valid_add_call(validator, "[::1]:8000", "::1", validator),
2983            )?;
2984
2985            assert_eq!(vc.validator_count()?, 1);
2986            let v = vc.validator_by_index(0)?;
2987            assert_eq!(v.validatorAddress, validator);
2988            assert_eq!(v.ingress, "[::1]:8000");
2989            assert_eq!(v.egress, "::1");
2990
2991            Ok(())
2992        })
2993    }
2994
2995    #[test]
2996    fn test_add_validator_rejects_duplicate_ingress_ipv6() -> eyre::Result<()> {
2997        let mut storage = HashMapStorageProvider::new(1);
2998        let owner = Address::random();
2999        StorageCtx::enter(&mut storage, || {
3000            let mut vc = ValidatorConfigV2::new();
3001            vc.initialize(owner)?;
3002
3003            vc.storage.set_block_number(200);
3004            vc.add_validator(
3005                owner,
3006                make_valid_add_call(
3007                    Address::random(),
3008                    "[2001:db8::1]:8000",
3009                    "2001:db8::1",
3010                    Address::random(),
3011                ),
3012            )?;
3013
3014            // Try to add another validator with same IPv6 IP (different port)
3015            vc.storage.set_block_number(201);
3016            let result = vc.add_validator(
3017                owner,
3018                make_valid_add_call(
3019                    Address::random(),
3020                    "[2001:db8::1]:8000",
3021                    "2001:db8::2",
3022                    Address::random(),
3023                ),
3024            );
3025
3026            assert!(result.is_err());
3027            Ok(())
3028        })
3029    }
3030
3031    #[test]
3032    fn test_ipv6_reuse_after_deactivation() -> eyre::Result<()> {
3033        let mut storage = HashMapStorageProvider::new(1);
3034        let owner = Address::random();
3035        let v1 = Address::random();
3036        StorageCtx::enter(&mut storage, || {
3037            let mut vc = ValidatorConfigV2::new();
3038            vc.initialize(owner)?;
3039
3040            vc.storage.set_block_number(200);
3041            vc.add_validator(
3042                owner,
3043                make_valid_add_call(v1, "[2001:db8::1]:8000", "2001:db8::1", v1),
3044            )?;
3045
3046            vc.storage.set_block_number(300);
3047            vc.deactivate_validator(
3048                owner,
3049                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
3050            )?;
3051
3052            // Should allow IPv6 reuse after deactivation
3053            vc.storage.set_block_number(400);
3054            vc.add_validator(
3055                owner,
3056                make_valid_add_call(
3057                    Address::random(),
3058                    "[2001:db8::1]:8000",
3059                    "2001:db8::1",
3060                    Address::random(),
3061                ),
3062            )?;
3063
3064            Ok(())
3065        })
3066    }
3067
3068    #[test]
3069    fn test_rotate_validator_with_ipv6() -> eyre::Result<()> {
3070        let mut storage = HashMapStorageProvider::new(1);
3071        let owner = Address::random();
3072        let validator = Address::random();
3073        StorageCtx::enter(&mut storage, || {
3074            let mut vc = ValidatorConfigV2::new();
3075            vc.initialize(owner)?;
3076
3077            // Add initial validator with IPv4
3078            vc.storage.set_block_number(200);
3079            vc.add_validator(
3080                owner,
3081                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", validator),
3082            )?;
3083
3084            // Rotate to IPv6
3085            let (new_pubkey, new_sig) = make_test_keypair_and_signature(
3086                validator,
3087                "[2001:db8::1]:8000",
3088                "2001:db8::1",
3089                SignatureKind::Rotate,
3090            );
3091            vc.storage.set_block_number(300);
3092            vc.rotate_validator(
3093                owner,
3094                IValidatorConfigV2::rotateValidatorCall {
3095                    idx: 0,
3096                    publicKey: new_pubkey,
3097                    ingress: "[2001:db8::1]:8000".to_string(),
3098                    egress: "2001:db8::1".to_string(),
3099                    signature: new_sig.into(),
3100                },
3101            )?;
3102
3103            assert_eq!(vc.validator_count()?, 2);
3104            let updated = vc.validator_by_index(0)?;
3105            assert_eq!(updated.deactivatedAtHeight, 0);
3106            assert_eq!(updated.ingress, "[2001:db8::1]:8000");
3107            assert_eq!(updated.egress, "2001:db8::1");
3108
3109            let snapshot = vc.validator_by_index(1)?;
3110            assert_eq!(snapshot.deactivatedAtHeight, 300);
3111
3112            Ok(())
3113        })
3114    }
3115
3116    #[test]
3117    fn test_ipv6_canonical_representation() -> eyre::Result<()> {
3118        let mut storage = HashMapStorageProvider::new(1);
3119        let owner = Address::random();
3120        StorageCtx::enter(&mut storage, || {
3121            let mut vc = ValidatorConfigV2::new();
3122            vc.initialize(owner)?;
3123
3124            // Add validator with compressed IPv6 notation
3125            vc.storage.set_block_number(200);
3126            vc.add_validator(
3127                owner,
3128                make_valid_add_call(Address::random(), "[::1]:8000", "::1", Address::random()),
3129            )?;
3130
3131            // Try to add another validator with expanded IPv6 notation of same IP
3132            // This should fail because [::1] and [0:0:0:0:0:0:0:1] are the same IP
3133            vc.storage.set_block_number(201);
3134            let result = vc.add_validator(
3135                owner,
3136                make_valid_add_call(
3137                    Address::random(),
3138                    "[0:0:0:0:0:0:0:1]:8000",
3139                    "::1",
3140                    Address::random(),
3141                ),
3142            );
3143
3144            assert!(
3145                result.is_err(),
3146                "Different IPv6 notations of same IP should be rejected"
3147            );
3148
3149            // No scope and %0 are the same - should fail.
3150            vc.storage.set_block_number(202);
3151            let result = vc.add_validator(
3152                owner,
3153                make_valid_add_call(Address::random(), "[::1%0]:8000", "::1", Address::random()),
3154            );
3155
3156            assert!(
3157                result.is_err(),
3158                "Different IPv6 notations of same IP should be rejected"
3159            );
3160
3161            // Same IP/Port but different port should succeed.
3162            vc.storage.set_block_number(203);
3163            let result = vc.add_validator(
3164                owner,
3165                make_valid_add_call(Address::random(), "[::1%1]:8000", "::1", Address::random()),
3166            );
3167            assert!(result.is_ok());
3168
3169            Ok(())
3170        })
3171    }
3172
3173    #[test]
3174    fn test_add_validator_rejects_wrong_key_signature() -> eyre::Result<()> {
3175        let mut storage = HashMapStorageProvider::new(1);
3176        let owner = Address::random();
3177        let validator = Address::random();
3178        let fee_recipient = Address::random();
3179        StorageCtx::enter(&mut storage, || {
3180            let mut vc = ValidatorConfigV2::new();
3181            vc.initialize(owner)?;
3182
3183            // Generate a valid keypair for a different key
3184            let (pubkey, _) = make_test_keypair_and_signature(
3185                validator,
3186                "192.168.1.1:8000",
3187                "192.168.1.1",
3188                SignatureKind::Add { fee_recipient },
3189            );
3190
3191            // Generate signature from a completely different key
3192            let (_, wrong_sig) = make_test_keypair_and_signature(
3193                validator,
3194                "192.168.1.1:8000",
3195                "192.168.1.1",
3196                SignatureKind::Add { fee_recipient },
3197            );
3198
3199            vc.storage.set_block_number(200);
3200            let result = vc.add_validator(
3201                owner,
3202                make_add_call(
3203                    validator,
3204                    pubkey,
3205                    "192.168.1.1:8000",
3206                    "192.168.1.1",
3207                    fee_recipient,
3208                    wrong_sig,
3209                ),
3210            );
3211            assert_eq!(
3212                result,
3213                Err(ValidatorConfigV2Error::invalid_signature().into())
3214            );
3215
3216            Ok(())
3217        })
3218    }
3219
3220    #[test]
3221    fn test_add_validator_rejects_wrong_namespace_signature() -> eyre::Result<()> {
3222        let mut storage = HashMapStorageProvider::new(1);
3223        let owner = Address::random();
3224        let validator = Address::random();
3225        let fee_recipient = Address::random();
3226        StorageCtx::enter(&mut storage, || {
3227            let mut vc = ValidatorConfigV2::new();
3228            vc.initialize(owner)?;
3229
3230            // Sign with ROTATE namespace, but try to ADD
3231            let (pubkey, sig) = make_test_keypair_and_signature(
3232                validator,
3233                "192.168.1.1:8000",
3234                "192.168.1.1",
3235                SignatureKind::Rotate,
3236            );
3237
3238            vc.storage.set_block_number(200);
3239            let result = vc.add_validator(
3240                owner,
3241                make_add_call(
3242                    validator,
3243                    pubkey,
3244                    "192.168.1.1:8000",
3245                    "192.168.1.1",
3246                    fee_recipient,
3247                    sig,
3248                ),
3249            );
3250            assert_eq!(
3251                result,
3252                Err(ValidatorConfigV2Error::invalid_signature().into())
3253            );
3254
3255            Ok(())
3256        })
3257    }
3258
3259    #[test]
3260    fn test_rotate_validator_rejects_wrong_key_signature() -> eyre::Result<()> {
3261        let mut storage = HashMapStorageProvider::new(1);
3262        let owner = Address::random();
3263        let validator = Address::random();
3264        let fee_recipient = Address::random();
3265        StorageCtx::enter(&mut storage, || {
3266            let mut vc = ValidatorConfigV2::new();
3267            vc.initialize(owner)?;
3268
3269            // Add a valid validator first
3270            vc.storage.set_block_number(200);
3271            vc.add_validator(
3272                owner,
3273                make_valid_add_call(validator, "192.168.1.1:8000", "192.168.1.1", fee_recipient),
3274            )?;
3275
3276            // Generate a new pubkey for rotation
3277            let (new_pubkey, _) = make_test_keypair_and_signature(
3278                validator,
3279                "10.0.0.1:8000",
3280                "10.0.0.1",
3281                SignatureKind::Rotate,
3282            );
3283
3284            // Sign with a different key
3285            let (_, wrong_sig) = make_test_keypair_and_signature(
3286                validator,
3287                "10.0.0.1:8000",
3288                "10.0.0.1",
3289                SignatureKind::Rotate,
3290            );
3291
3292            vc.storage.set_block_number(300);
3293            let result = vc.rotate_validator(
3294                owner,
3295                IValidatorConfigV2::rotateValidatorCall {
3296                    idx: 0,
3297                    publicKey: new_pubkey,
3298                    ingress: "10.0.0.1:8000".to_string(),
3299                    egress: "10.0.0.1".to_string(),
3300                    signature: wrong_sig.into(),
3301                },
3302            );
3303            assert_eq!(
3304                result,
3305                Err(ValidatorConfigV2Error::invalid_signature().into())
3306            );
3307
3308            Ok(())
3309        })
3310    }
3311
3312    #[test]
3313    fn test_add_validator_rejects_malformed_signature() -> eyre::Result<()> {
3314        let mut storage = HashMapStorageProvider::new(1);
3315        let owner = Address::random();
3316        let validator = Address::random();
3317        let fee_recipient = Address::random();
3318        StorageCtx::enter(&mut storage, || {
3319            let mut vc = ValidatorConfigV2::new();
3320            vc.initialize(owner)?;
3321
3322            let (pubkey, _) = make_test_keypair_and_signature(
3323                validator,
3324                "192.168.1.1:8000",
3325                "192.168.1.1",
3326                SignatureKind::Add { fee_recipient },
3327            );
3328
3329            vc.storage.set_block_number(200);
3330            let result = vc.add_validator(
3331                owner,
3332                make_add_call(
3333                    validator,
3334                    pubkey,
3335                    "192.168.1.1:8000",
3336                    "192.168.1.1",
3337                    fee_recipient,
3338                    vec![0xde, 0xad],
3339                ),
3340            );
3341            assert_eq!(
3342                result,
3343                Err(ValidatorConfigV2Error::invalid_signature_format().into())
3344            );
3345
3346            Ok(())
3347        })
3348    }
3349
3350    #[test]
3351    fn test_ipv4_ipv6_different_ips() -> eyre::Result<()> {
3352        let mut storage = HashMapStorageProvider::new(1);
3353        let owner = Address::random();
3354        StorageCtx::enter(&mut storage, || {
3355            let mut vc = ValidatorConfigV2::new();
3356            vc.initialize(owner)?;
3357
3358            // Add IPv4 validator
3359            vc.storage.set_block_number(200);
3360            vc.add_validator(
3361                owner,
3362                make_valid_add_call(
3363                    Address::random(),
3364                    "192.168.1.1:8000",
3365                    "192.168.1.1",
3366                    Address::random(),
3367                ),
3368            )?;
3369
3370            // Add IPv6 validator - should succeed (different IP)
3371            vc.storage.set_block_number(201);
3372            vc.add_validator(
3373                owner,
3374                make_valid_add_call(
3375                    Address::random(),
3376                    "[2001:db8::1]:8000",
3377                    "2001:db8::1",
3378                    Address::random(),
3379                ),
3380            )?;
3381
3382            assert_eq!(vc.validator_count()?, 2);
3383            Ok(())
3384        })
3385    }
3386
3387    #[test]
3388    fn test_event_emission_owner_and_validator_actions() -> eyre::Result<()> {
3389        let mut storage = HashMapStorageProvider::new(1);
3390        let owner = Address::random();
3391        let validator = Address::random();
3392        let new_validator_address = Address::random();
3393
3394        StorageCtx::enter(&mut storage, || {
3395            let mut vc = ValidatorConfigV2::new();
3396            vc.initialize(owner)?;
3397
3398            let (pubkey, signature) = make_test_keypair_and_signature(
3399                validator,
3400                "192.168.1.1:8000",
3401                "192.168.1.1",
3402                SignatureKind::Add {
3403                    fee_recipient: validator,
3404                },
3405            );
3406
3407            vc.storage.set_block_number(100);
3408            vc.add_validator(
3409                owner,
3410                make_add_call(
3411                    validator,
3412                    pubkey,
3413                    "192.168.1.1:8000",
3414                    "192.168.1.1",
3415                    validator,
3416                    signature,
3417                ),
3418            )?;
3419            vc.assert_emitted_events(vec![ValidatorConfigV2Event::ValidatorAdded(
3420                IValidatorConfigV2::ValidatorAdded {
3421                    index: 0,
3422                    validatorAddress: validator,
3423                    publicKey: pubkey,
3424                    ingress: "192.168.1.1:8000".to_string(),
3425                    egress: "192.168.1.1".to_string(),
3426                    feeRecipient: validator,
3427                },
3428            )]);
3429
3430            vc.clear_emitted_events();
3431            vc.set_ip_addresses(
3432                validator,
3433                IValidatorConfigV2::setIpAddressesCall {
3434                    idx: 0,
3435                    ingress: "10.0.0.1:8000".to_string(),
3436                    egress: "10.0.0.1".to_string(),
3437                },
3438            )?;
3439            vc.assert_emitted_events(vec![ValidatorConfigV2Event::IpAddressesUpdated(
3440                IValidatorConfigV2::IpAddressesUpdated {
3441                    index: 0,
3442                    ingress: "10.0.0.1:8000".to_string(),
3443                    egress: "10.0.0.1".to_string(),
3444                    caller: validator,
3445                },
3446            )]);
3447
3448            vc.clear_emitted_events();
3449            vc.transfer_validator_ownership(
3450                owner,
3451                IValidatorConfigV2::transferValidatorOwnershipCall {
3452                    idx: 0,
3453                    newAddress: new_validator_address,
3454                },
3455            )?;
3456            vc.assert_emitted_events(vec![ValidatorConfigV2Event::ValidatorOwnershipTransferred(
3457                IValidatorConfigV2::ValidatorOwnershipTransferred {
3458                    index: 0,
3459                    oldAddress: validator,
3460                    newAddress: new_validator_address,
3461                    caller: owner,
3462                },
3463            )]);
3464
3465            vc.clear_emitted_events();
3466            vc.deactivate_validator(
3467                new_validator_address,
3468                IValidatorConfigV2::deactivateValidatorCall { idx: 0 },
3469            )?;
3470            vc.assert_emitted_events(vec![ValidatorConfigV2Event::ValidatorDeactivated(
3471                IValidatorConfigV2::ValidatorDeactivated {
3472                    index: 0,
3473                    validatorAddress: new_validator_address,
3474                },
3475            )]);
3476
3477            vc.clear_emitted_events();
3478            let new_owner = Address::random();
3479            vc.transfer_ownership(
3480                owner,
3481                IValidatorConfigV2::transferOwnershipCall {
3482                    newOwner: new_owner,
3483                },
3484            )?;
3485            vc.assert_emitted_events(vec![ValidatorConfigV2Event::OwnershipTransferred(
3486                IValidatorConfigV2::OwnershipTransferred {
3487                    oldOwner: owner,
3488                    newOwner: new_owner,
3489                },
3490            )]);
3491
3492            Ok(())
3493        })
3494    }
3495
3496    #[test]
3497    fn test_event_emission_rotate_and_next_dkg() -> eyre::Result<()> {
3498        let mut storage = HashMapStorageProvider::new(1);
3499        let owner = Address::random();
3500        let validator = Address::random();
3501
3502        StorageCtx::enter(&mut storage, || {
3503            let mut vc = ValidatorConfigV2::new();
3504            vc.initialize(owner)?;
3505
3506            let (old_pubkey, old_sig) = make_test_keypair_and_signature(
3507                validator,
3508                "192.168.1.1:8000",
3509                "192.168.1.1",
3510                SignatureKind::Add {
3511                    fee_recipient: validator,
3512                },
3513            );
3514
3515            vc.storage.set_block_number(200);
3516            vc.add_validator(
3517                owner,
3518                make_add_call(
3519                    validator,
3520                    old_pubkey,
3521                    "192.168.1.1:8000",
3522                    "192.168.1.1",
3523                    validator,
3524                    old_sig,
3525                ),
3526            )?;
3527
3528            vc.clear_emitted_events();
3529            let (new_pubkey, new_sig) = make_test_keypair_and_signature(
3530                validator,
3531                "10.0.0.2:8000",
3532                "10.0.0.2",
3533                SignatureKind::Rotate,
3534            );
3535            vc.storage.set_block_number(300);
3536            vc.rotate_validator(
3537                owner,
3538                IValidatorConfigV2::rotateValidatorCall {
3539                    idx: 0,
3540                    publicKey: new_pubkey,
3541                    ingress: "10.0.0.2:8000".to_string(),
3542                    egress: "10.0.0.2".to_string(),
3543                    signature: new_sig.into(),
3544                },
3545            )?;
3546            vc.assert_emitted_events(vec![ValidatorConfigV2Event::ValidatorRotated(
3547                IValidatorConfigV2::ValidatorRotated {
3548                    index: 0,
3549                    deactivatedIndex: 1,
3550                    validatorAddress: validator,
3551                    oldPublicKey: old_pubkey,
3552                    newPublicKey: new_pubkey,
3553                    ingress: "10.0.0.2:8000".to_string(),
3554                    egress: "10.0.0.2".to_string(),
3555                    caller: owner,
3556                },
3557            )]);
3558
3559            vc.clear_emitted_events();
3560            vc.set_network_identity_rotation_epoch(
3561                owner,
3562                IValidatorConfigV2::setNetworkIdentityRotationEpochCall { epoch: 42 },
3563            )?;
3564            vc.assert_emitted_events(vec![
3565                ValidatorConfigV2Event::NetworkIdentityRotationEpochSet(
3566                    IValidatorConfigV2::NetworkIdentityRotationEpochSet {
3567                        previousEpoch: 0,
3568                        nextEpoch: 42,
3569                    },
3570                ),
3571            ]);
3572
3573            Ok(())
3574        })
3575    }
3576
3577    #[test]
3578    fn test_event_emission_migration_and_initialize() -> eyre::Result<()> {
3579        let mut storage = HashMapStorageProvider::new(1);
3580        let owner = Address::random();
3581        let v1_addr = Address::random();
3582        let v1_pk = FixedBytes::<32>::from([0x11; 32]);
3583
3584        StorageCtx::enter(&mut storage, || {
3585            let mut v1 = v1();
3586            v1.initialize(owner)?;
3587            v1.add_validator(
3588                owner,
3589                tempo_contracts::precompiles::IValidatorConfig::addValidatorCall {
3590                    newValidatorAddress: v1_addr,
3591                    publicKey: v1_pk,
3592                    active: true,
3593                    inboundAddress: "192.168.1.1:8000".to_string(),
3594                    outboundAddress: "192.168.1.1:9000".to_string(),
3595                },
3596            )?;
3597
3598            let mut v2 = ValidatorConfigV2::new();
3599            v2.storage.set_block_number(500);
3600            v2.migrate_validator(owner, IValidatorConfigV2::migrateValidatorCall { idx: 0 })?;
3601            v2.assert_emitted_events(vec![ValidatorConfigV2Event::ValidatorMigrated(
3602                IValidatorConfigV2::ValidatorMigrated {
3603                    index: 0,
3604                    validatorAddress: v1_addr,
3605                    publicKey: v1_pk,
3606                },
3607            )]);
3608
3609            v2.clear_emitted_events();
3610            v2.storage.set_block_number(700);
3611            v2.initialize_if_migrated(owner)?;
3612            v2.assert_emitted_events(vec![ValidatorConfigV2Event::Initialized(
3613                IValidatorConfigV2::Initialized { height: 700 },
3614            )]);
3615
3616            Ok(())
3617        })
3618    }
3619}