test_random.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use crate::Process; 17 use alphavm_ledger_committee::{MIN_DELEGATOR_STAKE, MIN_VALIDATOR_SELF_STAKE, MIN_VALIDATOR_STAKE}; 18 #[cfg(feature = "rocks")] 19 use alphavm_ledger_store::{FinalizeMode, FinalizeStorage, FinalizeStore, atomic_finalize}; 20 #[cfg(not(feature = "rocks"))] 21 use alphavm_ledger_store::{ 22 FinalizeMode, 23 FinalizeStorage, 24 FinalizeStore, 25 atomic_finalize, 26 helpers::memory::FinalizeMemory, 27 }; 28 use console::{ 29 account::{Address, PrivateKey}, 30 network::{MainnetV0, prelude::*}, 31 }; 32 use rand::seq::IteratorRandom; 33 34 use indexmap::{IndexMap, IndexSet}; 35 36 use super::test_utils::*; 37 38 /// Returns `value` with probability 0.2, and a uniform value in the closed interval 39 /// [0, 1.2 * value] with probability 0.8. 40 fn random_below_with_blowup(value: u64, rng: &mut impl Rng) -> u64 { 41 const BLOWUP: u64 = 20; 42 let upper_bound = value + value / BLOWUP; 43 44 match rng.gen_range(0..100) { 45 0..=79 => rng.gen_range(0..=upper_bound), 46 _ => value, 47 } 48 } 49 50 /// If `default` is `Some(value)` this function returns `value` with probability 51 /// 0.8 and a uniformly random value in `values` with probability 0.2. If 52 /// `default` is `None`, it returns a uniformly random value from `values`. 53 fn random_with_default<'a, T>( 54 values: impl Iterator<Item = &'a T>, 55 default: Option<&'a T>, 56 rng: &mut impl Rng, 57 ) -> &'a T { 58 match rng.gen_range(0..100) { 59 0..=79 => default.unwrap_or(values.choose(rng).unwrap()), 60 _ => values.choose(rng).unwrap(), 61 } 62 } 63 64 /// A bonding or unbonding operation. 65 #[derive(Clone)] 66 enum Operation { 67 /// Bond funds as a validator. 68 BondValidator { 69 private_key: PrivateKey<MainnetV0>, 70 withdrawal_address: Address<MainnetV0>, 71 amount: u64, 72 commission: u8, 73 }, 74 /// Bond funds as a delegator. 75 BondPublic { 76 private_key: PrivateKey<MainnetV0>, 77 validator_address: Address<MainnetV0>, 78 withdrawal_address: Address<MainnetV0>, 79 amount: u64, 80 }, 81 /// Unbond funds. 82 UnbondPublic { withdrawal_private_key: PrivateKey<MainnetV0>, staker_address: Address<MainnetV0>, amount: u64 }, 83 } 84 85 impl Debug for Operation { 86 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 87 match self { 88 Operation::BondValidator { private_key, amount, .. } => { 89 let validator_address = Address::<MainnetV0>::try_from(private_key).unwrap().to_string(); 90 write!(f, "BondValidator {{ {}, {amount} }}", &validator_address[..12]) 91 } 92 Operation::BondPublic { private_key, validator_address, amount, .. } => { 93 let delegator_address = Address::<MainnetV0>::try_from(private_key).unwrap().to_string(); 94 let validator_address = validator_address.to_string(); 95 write!(f, "BondPublic {{ {}, {}, {amount} }}", &delegator_address[..12], &validator_address[..12]) 96 } 97 Operation::UnbondPublic { staker_address, amount, .. } => { 98 let staker_address = staker_address.to_string(); 99 write!(f, "UnbondPublic {{ {}, {amount} }}", &staker_address[..12]) 100 } 101 } 102 } 103 } 104 105 impl Operation { 106 /// Returns a random operation. Note that the operation may not be valid in 107 /// the sense that it is not guaranteed to execute successfully. 108 pub fn random(state: &State, rng: &mut impl Rng) -> Self { 109 match rng.gen_range(0..3) { 110 0 => Operation::random_bond_validator(state, rng), 111 1 => Operation::random_bond_public(state, rng), 112 _ => Operation::random_unbond_public(state, rng), 113 } 114 } 115 116 /// Returns a random `bond_validator` operation. 117 fn random_bond_validator(state: &State, rng: &mut impl Rng) -> Self { 118 let validator = state.stakers().choose(rng).unwrap(); 119 let account_balance = state.account_balance(validator.address()); 120 121 Operation::BondValidator { 122 private_key: *validator.private_key(), 123 withdrawal_address: *random_with_default(state.stakers(), Some(validator), rng).withdrawal_address(), 124 amount: random_below_with_blowup(account_balance, rng), 125 commission: random_below_with_blowup(100, rng) as u8, 126 } 127 } 128 129 /// Returns a random `bond_public` operation. 130 fn random_bond_public(state: &State, rng: &mut impl Rng) -> Self { 131 let delegator = state.stakers().choose(rng).unwrap(); 132 let validator_addresses = state.stakers().map(|staker| staker.address()); 133 let default_address = state.bonded_to(delegator.address()); 134 let account_balance = state.account_balance(delegator.address()); 135 136 Operation::BondPublic { 137 private_key: *delegator.private_key(), 138 validator_address: *random_with_default(validator_addresses, default_address.as_ref(), rng), 139 withdrawal_address: *random_with_default(state.stakers(), Some(delegator), rng).withdrawal_address(), 140 amount: random_below_with_blowup(account_balance, rng), 141 } 142 } 143 144 /// Returns a random `unbond_public` operation. 145 fn random_unbond_public(state: &State, rng: &mut impl Rng) -> Self { 146 let staker = state.stakers().choose(rng).unwrap(); 147 let withdrawal_private_key = match state.bonded_to(staker.address()) { 148 Some(validator_address) => { 149 let validator = state.staker_with_address(&validator_address).unwrap(); 150 [*staker.withdrawal_private_key(), *validator.withdrawal_private_key()].into_iter().choose(rng).unwrap() 151 } 152 None => *state.stakers().choose(rng).unwrap().withdrawal_private_key(), 153 }; 154 let account_balance = state.account_balance(staker.address()); 155 156 Operation::UnbondPublic { 157 withdrawal_private_key, 158 staker_address: *staker.address(), 159 amount: random_below_with_blowup(account_balance, rng), 160 } 161 } 162 } 163 164 /// A staker in the network with `(address, amount, withdrawal_private_key, withdrawal_address)`. 165 #[derive(Copy, Clone, Debug)] 166 struct Staker(PrivateKey<MainnetV0>, Address<MainnetV0>, PrivateKey<MainnetV0>, Address<MainnetV0>, u64); 167 168 impl Staker { 169 fn private_key(&self) -> &PrivateKey<MainnetV0> { 170 &self.0 171 } 172 173 fn address(&self) -> &Address<MainnetV0> { 174 &self.1 175 } 176 177 fn withdrawal_private_key(&self) -> &PrivateKey<MainnetV0> { 178 &self.2 179 } 180 181 fn withdrawal_address(&self) -> &Address<MainnetV0> { 182 &self.3 183 } 184 185 fn initial_balance(&self) -> u64 { 186 self.4 187 } 188 } 189 190 #[derive(Default)] 191 struct State { 192 /// Maps staker addresses to stakers in the system. 193 stakers: IndexMap<Address<MainnetV0>, Staker>, 194 /// Maps addresses to registered commissions. 195 commissions: IndexMap<Address<MainnetV0>, u8>, 196 /// Maps stakers to the corresponding validator. 197 bonded_to: IndexMap<Address<MainnetV0>, Address<MainnetV0>>, 198 /// Contains the bonded amount for each staker. 199 bonded_amounts: IndexMap<Address<MainnetV0>, u64>, 200 /// Contains the account balance for each staker. 201 account_balances: IndexMap<Address<MainnetV0>, u64>, 202 /// Contains the unbonding amount for each staker. 203 unbonding_amounts: IndexMap<Address<MainnetV0>, u64>, 204 /// Contains each staker who have had funds delegated to them at some point. 205 /// (This is used to mimic the behavior of the `delegated_state` function.) 206 has_delegated_state: IndexSet<Address<MainnetV0>>, 207 // Contains the withdrawal address registered by each staker. (Note that 208 // this may be different from the withdrawal address on the staker.) 209 withdrawal_addresses: IndexMap<Address<MainnetV0>, Address<MainnetV0>>, 210 } 211 212 impl State { 213 /// Returns a new [`State`] with `num_stakers` stakers. 214 pub fn new<F: FinalizeStorage<MainnetV0>>( 215 num_stakers: u32, 216 store: &FinalizeStore<MainnetV0, F>, 217 rng: &mut TestRng, 218 ) -> Self { 219 let (validators, _) = initialize_stakers(store, num_stakers, 0, rng).unwrap(); 220 let stakers: IndexMap<Address<MainnetV0>, Staker> = validators 221 .into_iter() 222 .map(|(private_key, (address, initial_balance, withdrawal_private_key, withdrawal_address))| { 223 (address, Staker(private_key, address, withdrawal_private_key, withdrawal_address, initial_balance)) 224 }) 225 .collect(); 226 let account_balances = stakers.iter().map(|(address, staker)| (*address, staker.initial_balance())).collect(); 227 Self { stakers, account_balances, ..Default::default() } 228 } 229 230 /// An iterator over the set of potential stakers. 231 fn stakers(&self) -> impl Iterator<Item = &Staker> { 232 self.stakers.iter().map(|(_, staker)| staker) 233 } 234 235 /// Returns `true` if the address is in the committee. 236 fn in_committee(&self, address: &Address<MainnetV0>) -> bool { 237 self.delegated_amount(address) > MIN_VALIDATOR_STAKE 238 && self.bonded_amount(address) > MIN_VALIDATOR_SELF_STAKE 239 && self.bonded_to(address) == Some(*address) 240 } 241 242 /// Returns `true` if the address has bonded funds. 243 fn is_bonded(&self, address: &Address<MainnetV0>) -> bool { 244 self.bonded_to.contains_key(address) 245 } 246 247 /// Returns `true` if the address is unbonding. 248 fn is_unbonding(&self, address: &Address<MainnetV0>) -> bool { 249 self.unbonding_amounts.contains_key(address) 250 } 251 252 /// Returns `Some(validator_address)` if the given address has bonded funds 253 /// to a validator, or `None` otherwise. 254 fn bonded_to(&self, address: &Address<MainnetV0>) -> Option<Address<MainnetV0>> { 255 self.bonded_to.get(address).copied() 256 } 257 258 /// Returns the amount (in microcredits) that the address has bonded. 259 fn bonded_amount(&self, address: &Address<MainnetV0>) -> u64 { 260 self.bonded_amounts.get(address).copied().unwrap_or_default() 261 } 262 263 /// Returns `Some((validator_address, amount))` if the given address has 264 /// bonded `amount` microcredits to `validator_address`. 265 fn bond_state(&self, address: &Address<MainnetV0>) -> Option<(Address<MainnetV0>, u64)> { 266 self.bonded_to(address).zip(Some(self.bonded_amount(address))) 267 } 268 269 /// Returns the account balance (amount of available unbonded funds) of the 270 /// given address. 271 fn account_balance(&self, address: &Address<MainnetV0>) -> u64 { 272 self.account_balances.get(address).copied().unwrap_or_default() 273 } 274 275 /// Returns `Some((amount, height))` if the given address is unbonding 276 /// `amount` microcredits. 277 fn unbond_state(&self, address: &Address<MainnetV0>) -> Option<(u64, u32)> { 278 self.unbonding_amounts.get(address).copied().zip(Some(10360)) 279 } 280 281 /// Returns the total amount of funds bonded to the given validator address. 282 fn delegated_amount(&self, validator_address: &Address<MainnetV0>) -> u64 { 283 self.stakers() 284 .filter_map(|staker| { 285 if self.bonded_to(staker.address()) == Some(*validator_address) { 286 Some(self.bonded_amount(staker.address())) 287 } else { 288 None 289 } 290 }) 291 .sum() 292 } 293 294 /// Returns the total amount of funds bonded to the given validator address 295 /// if it is non-zero, or `None`. 296 fn delegated_state(&self, validator_address: &Address<MainnetV0>) -> Option<u64> { 297 if self.has_delegated_state.contains(validator_address) { 298 Some(self.delegated_amount(validator_address)) 299 } else { 300 None 301 } 302 } 303 304 /// Returns the staker with the given address. 305 fn staker_with_address(&self, address: &Address<MainnetV0>) -> Option<Staker> { 306 self.stakers.get(address).copied() 307 } 308 309 /// Returns the staker with the given private key. 310 fn staker_with_private_key(&self, private_key: &PrivateKey<MainnetV0>) -> Option<Staker> { 311 Address::try_from(private_key).ok().and_then(|address| self.stakers.get(&address)).copied() 312 } 313 314 /// Returns `Some(withdrawal_address)` if the address has registered a 315 /// withdrawal address, or `None` if no withdrawal address is registered. 316 fn withdrawal_address(&self, address: &Address<MainnetV0>) -> Option<Address<MainnetV0>> { 317 self.withdrawal_addresses.get(address).copied() 318 } 319 320 /// Returns `Some(commission)` if the address has registered a commission, 321 /// or `None` otherwise. 322 fn commision(&self, address: &Address<MainnetV0>) -> Option<u8> { 323 self.commissions.get(address).copied() 324 } 325 326 /// Rust reference implementation of the `bond_validator` validation. 327 fn check_bond_validator( 328 &self, 329 private_key: &PrivateKey<MainnetV0>, 330 withdrawal_address: &Address<MainnetV0>, 331 amount: u64, 332 commission: u8, 333 ) -> Result<()> { 334 let validator = self.staker_with_private_key(private_key).unwrap(); 335 336 // Amount is at least 1 credit. 337 ensure!(amount >= 1_000_000u64); 338 // Commision is at most 100%. 339 ensure!(commission <= 100u8); 340 // Validator has enough funds. 341 ensure!(amount <= self.account_balance(validator.address())); 342 343 if self.is_bonded(validator.address()) { 344 // The validator is not bonded to someone else. 345 ensure!(self.bonded_to(validator.address()) == Some(*validator.address())); 346 // The withdrawal address is not updated. 347 ensure!(self.withdrawal_address(validator.address()) == Some(*withdrawal_address)); 348 // The commission is not updated. 349 ensure!(self.commision(validator.address()) == Some(commission)); 350 } else { 351 // The initial bonded amount is at least 100 credits. 352 ensure!(amount >= MIN_VALIDATOR_SELF_STAKE); 353 // The total delegated amount is at least 10 000 000 credits. 354 ensure!(self.delegated_amount(validator.address()) + amount >= MIN_VALIDATOR_STAKE); 355 // The validator is not unbonding. 356 ensure!(!self.is_unbonding(validator.address())); 357 // The withdrawal address is not updated. 358 ensure!( 359 self.withdrawal_address(validator.address()).is_none() 360 || self.withdrawal_address(validator.address()) == Some(*withdrawal_address) 361 ); 362 } 363 Ok(()) 364 } 365 366 /// Rust reference implementation of the `bond_public` validation. 367 fn check_bond_public( 368 &self, 369 private_key: &PrivateKey<MainnetV0>, 370 validator_address: &Address<MainnetV0>, 371 withdrawal_address: &Address<MainnetV0>, 372 amount: u64, 373 ) -> Result<()> { 374 let delegator = self.staker_with_private_key(private_key).unwrap(); 375 376 // Amount is at least 1 credit. 377 ensure!(amount >= 1_000_000u64); 378 // Delegator has enough funds. 379 ensure!(amount <= self.account_balance(delegator.address())); 380 // The delegator is different from the validator. 381 ensure!(delegator.address() != validator_address); 382 // The validator is not unbonding. 383 ensure!(!self.is_unbonding(validator_address)); 384 385 if self.is_bonded(delegator.address()) { 386 // The delegator is not bonded to someone else. 387 ensure!(self.bonded_to(delegator.address()) == Some(*validator_address)); 388 // The withdrawal address is not updated. 389 ensure!(self.withdrawal_address(delegator.address()) == Some(*withdrawal_address)); 390 // TODO: We don't currently track the `is_open` flag or the total number of delegators. 391 } else { 392 // The initial bonded amount is at least 10 000 000 credits. 393 ensure!(amount >= MIN_DELEGATOR_STAKE); 394 // The withdrawal address is not updated. 395 ensure!( 396 self.withdrawal_address(delegator.address()).is_none() 397 || self.withdrawal_address(delegator.address()) == Some(*withdrawal_address) 398 ); 399 } 400 Ok(()) 401 } 402 403 /// Rust reference implementation of the `unbond_public` validation. 404 fn check_unbond_public( 405 &self, 406 withdrawal_private_key: &PrivateKey<MainnetV0>, 407 staker_address: &Address<MainnetV0>, 408 amount: u64, 409 ) -> Result<()> { 410 let staker = self.staker_with_address(staker_address).unwrap(); 411 let validator_address = self.bonded_to(staker_address).ok_or(anyhow!("staker is not bonded"))?; 412 let validator = self.staker_with_address(&validator_address).unwrap(); 413 let withdrawal_address = Address::try_from(withdrawal_private_key).unwrap(); 414 415 // The caller is the withdrawal address of the staker or the corresponding validator. 416 ensure!( 417 self.withdrawal_address(staker.address()) == Some(withdrawal_address) 418 || self.withdrawal_address(validator.address()) == Some(withdrawal_address) 419 ); 420 // The staker has bonded enough funds. 421 ensure!(amount <= self.bonded_amount(staker_address)); 422 423 Ok(()) 424 } 425 426 /// Rust reference implementation of the `bond_validator` state update. 427 fn bond_validator( 428 &mut self, 429 private_key: &PrivateKey<MainnetV0>, 430 withdrawal_address: &Address<MainnetV0>, 431 amount: u64, 432 commission: u8, 433 ) { 434 let validator = self.staker_with_private_key(private_key).unwrap(); 435 self.commissions.insert(*validator.address(), commission); 436 self.withdrawal_addresses.insert(*validator.address(), *withdrawal_address); 437 self.bonded_to.insert(*validator.address(), *validator.address()); 438 *self.bonded_amounts.entry(*validator.address()).or_default() += amount; 439 self.account_balances[validator.address()] -= amount; 440 self.has_delegated_state.insert(*validator.address()); 441 } 442 443 /// Rust reference implementation of the `bond_public` state update. 444 fn bond_public( 445 &mut self, 446 private_key: &PrivateKey<MainnetV0>, 447 validator_address: &Address<MainnetV0>, 448 withdrawal_address: &Address<MainnetV0>, 449 amount: u64, 450 ) { 451 let delegator = self.staker_with_private_key(private_key).unwrap(); 452 self.bonded_to.insert(*delegator.address(), *validator_address); 453 *self.bonded_amounts.entry(*delegator.address()).or_default() += amount; 454 self.account_balances[delegator.address()] -= amount; 455 self.withdrawal_addresses.insert(*delegator.address(), *withdrawal_address); 456 self.has_delegated_state.insert(*validator_address); 457 } 458 459 /// Rust reference implementation of the `unbond_public` state update. 460 fn unbond_public( 461 &mut self, 462 withdrawal_private_key: &PrivateKey<MainnetV0>, 463 staker_address: &Address<MainnetV0>, 464 amount: u64, 465 _height: u64, 466 ) { 467 let staker = self.staker_with_address(staker_address).unwrap(); 468 let validator_address = self.bonded_to(staker_address).unwrap(); 469 let validator = self.staker_with_address(&validator_address).unwrap(); 470 let withdrawal_address = Address::try_from(withdrawal_private_key).unwrap(); 471 let validator_in_committee = self.in_committee(&validator_address); 472 473 self.bonded_amounts[staker.address()] -= amount; 474 *self.unbonding_amounts.entry(*staker.address()).or_default() += amount; 475 476 if *staker_address != validator_address { 477 // # Unbonding a delegator (staker != validator) 478 // 479 // - If the delegator stake falls below the minimum delegator stake, 480 // the delegators entire stake is unbonded. 481 // - If the validator unbonds the delegator, the delegators entire 482 // stake is unbonded. 483 // 484 // - If the validator is in the committee *and* the total delegated 485 // stake falls below the minimum validator stake, the validators 486 // entire stake is unbonded. 487 // 488 // TODO: Can the `validator_in_committee` check be removed? 489 if self.bonded_amount(staker.address()) < MIN_DELEGATOR_STAKE 490 || self.withdrawal_address(&validator_address) == Some(withdrawal_address) 491 { 492 // The resulting stake is less than the minimum stake, *or* the 493 // unbond operation is initiated by the validator. 494 *self.unbonding_amounts.entry(*staker.address()).or_default() += 495 self.bonded_amounts.swap_remove(staker.address()).unwrap(); 496 self.bonded_to.swap_remove(staker.address()); 497 } 498 if validator_in_committee && self.delegated_amount(validator.address()) < MIN_VALIDATOR_STAKE { 499 // The validator is in the committee *and* the total delegated 500 // stake falls below the minimum validator stake. 501 *self.unbonding_amounts.entry(*validator.address()).or_default() += 502 self.bonded_amounts.swap_remove(validator.address()).unwrap(); 503 self.bonded_to.swap_remove(validator.address()); 504 self.commissions.swap_remove(validator.address()); 505 } 506 } else { 507 // # Unbonding a validator (staker == validator) 508 // 509 // - If the validator stake falls below the minimum self-stake, the 510 // validators entire stake is unbonded. 511 // - If the total delegated stake falls below the minim validator 512 // stake, the validators entire stake is unbonded. 513 if self.bonded_amount(staker.address()) < MIN_VALIDATOR_SELF_STAKE 514 || self.delegated_amount(staker.address()) < MIN_VALIDATOR_STAKE 515 { 516 *self.unbonding_amounts.entry(*staker.address()).or_default() += 517 self.bonded_amounts.swap_remove(staker.address()).unwrap(); 518 self.bonded_to.swap_remove(staker.address()); 519 self.commissions.swap_remove(staker.address()); 520 } 521 } 522 } 523 524 /// Executes the given operation and updates the store or returns an error if 525 /// the execution failed. 526 pub fn execute_operation(&mut self, op: &Operation) -> Result<()> { 527 match op { 528 Operation::BondValidator { private_key, withdrawal_address, amount, commission } => { 529 self.check_bond_validator(private_key, withdrawal_address, *amount, *commission)?; 530 self.bond_validator(private_key, withdrawal_address, *amount, *commission); 531 } 532 Operation::BondPublic { private_key, validator_address, withdrawal_address, amount } => { 533 self.check_bond_public(private_key, validator_address, withdrawal_address, *amount)?; 534 self.bond_public(private_key, validator_address, withdrawal_address, *amount); 535 } 536 Operation::UnbondPublic { withdrawal_private_key, staker_address, amount } => { 537 self.check_unbond_public(withdrawal_private_key, staker_address, *amount)?; 538 self.unbond_public(withdrawal_private_key, staker_address, *amount, 10_000); 539 } 540 } 541 Ok(()) 542 } 543 } 544 545 /// Executes the given operation and updates the store or returns an error if 546 /// the execution failed. 547 fn execute_operation<F: FinalizeStorage<MainnetV0>>( 548 process: &Process<MainnetV0>, 549 store: &FinalizeStore<MainnetV0, F>, 550 op: &Operation, 551 rng: &mut TestRng, 552 ) -> Result<()> { 553 match op { 554 Operation::BondValidator { private_key, withdrawal_address, amount, commission } => { 555 bond_validator(process, store, private_key, withdrawal_address, *amount, *commission, rng)?; 556 } 557 Operation::BondPublic { private_key, validator_address, withdrawal_address, amount } => { 558 bond_public(process, store, private_key, validator_address, withdrawal_address, *amount, rng)?; 559 } 560 Operation::UnbondPublic { withdrawal_private_key, staker_address, amount } => { 561 unbond_public(process, store, withdrawal_private_key, staker_address, *amount, 10_000, rng)?; 562 } 563 } 564 Ok(()) 565 } 566 567 /// Returns `Ok(())` if the given store is consistent with the current state. 568 fn validate_state<F: FinalizeStorage<MainnetV0>>(state: &State, store: &FinalizeStore<MainnetV0, F>) -> Result<()> { 569 for staker in state.stakers() { 570 // Account balances match. 571 ensure!(account_balance(store, staker.address()).unwrap() == state.account_balance(staker.address())); 572 // Bonded amounts and corresponding validator addresses match. 573 ensure!(bond_state(store, staker.address()).unwrap() == state.bond_state(staker.address())); 574 // Unbonding amounts match. 575 ensure!(unbond_state(store, staker.address()).unwrap() == state.unbond_state(staker.address())); 576 // Delegated amounts match. 577 ensure!(delegated_state(store, staker.address()).unwrap() == state.delegated_state(staker.address())); 578 } 579 Ok(()) 580 } 581 582 fn print_operations(ops: &[Operation]) { 583 eprintln!("[💸]: Operations:"); 584 for (i, op) in ops.iter().enumerate() { 585 eprintln!(" [{i}]: {op:?}"); 586 } 587 } 588 589 #[ignore] // Ignoring as it causes CI timeouts 590 #[test] 591 fn test_random_operations() { 592 loop { 593 let mut rng = TestRng::default(); 594 let process = Process::<MainnetV0>::load().unwrap(); 595 let (store, _) = sample_finalize_store!(); 596 let mut state = State::new(8, &store, &mut rng); 597 598 let mut executed_ops = Vec::new(); 599 test_atomic_finalize!(store, FinalizeMode::RealRun, { 600 while executed_ops.len() < 64 { 601 let op = Operation::random(&state, &mut rng); 602 let expected_result = state.execute_operation(&op); 603 let actual_result = execute_operation(&process, &store, &op, &mut rng); 604 605 if actual_result.is_ok() != expected_result.is_ok() { 606 eprintln!("[💥]: State divergence detected."); 607 eprintln!(" Expected result: {expected_result:?}"); 608 eprintln!(" Actual result: {actual_result:?}"); 609 executed_ops.push(op); 610 print_operations(&executed_ops); 611 panic!(); 612 } 613 if let Err(error) = validate_state(&state, &store) { 614 eprintln!("[💥]: State divergence detected."); 615 eprintln!(" Error: {error:?}"); 616 executed_ops.push(op); 617 print_operations(&executed_ops); 618 panic!(); 619 } 620 if actual_result.is_ok() { 621 executed_ops.push(op); 622 } 623 } 624 Ok(()) 625 }) 626 .unwrap(); 627 } 628 }