1use std::{
2 collections::{HashMap, HashSet, VecDeque},
3 fmt,
4};
5
6use alloy::{
7 contract::Error as ContractError,
8 network::{Ethereum, EthereumWallet},
9 primitives::{
10 utils::{format_ether, parse_ether},
11 Address, U256,
12 },
13 providers::{PendingTransactionBuilder, Provider, ProviderBuilder, WalletProvider},
14 rpc::{client::RpcClient, types::TransactionReceipt},
15 signers::local::PrivateKeySigner,
16 transports::{http::Http, TransportError},
17};
18use anyhow::Result;
19use clap::ValueEnum;
20use espresso_contract_deployer::{build_provider, build_signer, HttpProviderWithWallet};
21use futures_util::future;
22use hotshot_contract_adapter::{
23 sol_types::{EspToken, StakeTableV2},
24 stake_table::StakeTableContractVersion,
25};
26use hotshot_types::{light_client::StateKeyPair, signature_key::BLSKeyPair};
27use rand::{Rng, SeedableRng};
28use rand_chacha::ChaCha20Rng;
29use thiserror::Error;
30use url::Url;
31
32use crate::{
33 delegation::{approve, delegate},
34 funding::{send_esp, send_eth},
35 info::fetch_token_address,
36 parse::{parse_bls_priv_key, parse_state_priv_key, Commission, ParseCommissionError},
37 receipt::ReceiptExt as _,
38 registration::register_validator,
39 signature::NodeSignatures,
40 Config,
41};
42
43#[derive(Debug, Error)]
44pub enum CreateTransactionsError {
45 #[error(
46 "insufficient ESP balance: have {have} ESP, need {need} ESP to fund {delegators} \
47 delegators"
48 )]
49 InsufficientEsp {
50 have: String,
51 need: String,
52 delegators: usize,
53 },
54 #[error(
55 "insufficient ETH balance: have {have} ETH, need {need} ETH (including gas buffer) to \
56 fund {recipients} recipients"
57 )]
58 InsufficientEth {
59 have: String,
60 need: String,
61 recipients: usize,
62 },
63 #[error("delegation amount {amount} ESP is below minimum of {min} ESP")]
64 DelegationBelowMinimum { amount: String, min: String },
65 #[error(transparent)]
66 Transport(#[from] TransportError),
67 #[error(transparent)]
68 Contract(#[from] ContractError),
69 #[error(transparent)]
70 Commission(#[from] ParseCommissionError),
71 #[error(transparent)]
72 Other(#[from] anyhow::Error),
73}
74
75#[derive(Debug, Clone, Copy, Default, ValueEnum)]
76pub enum DelegationConfig {
77 EqualAmounts,
78 #[default]
79 VariableAmounts,
80 MultipleDelegators,
81 NoSelfDelegation,
82}
83
84impl fmt::Display for DelegationConfig {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let s = match self {
88 DelegationConfig::EqualAmounts => "equal-amounts",
89 DelegationConfig::VariableAmounts => "variable-amounts",
90 DelegationConfig::MultipleDelegators => "multiple-delegators",
91 DelegationConfig::NoSelfDelegation => "no-self-delegation",
92 };
93 write!(f, "{s}")
94 }
95}
96
97#[derive(Clone, Debug)]
103pub struct RegistrationInfo {
104 pub from: Address,
105 pub commission: Commission,
106 pub payload: NodeSignatures,
107}
108
109#[derive(Clone, Debug)]
115pub struct DelegationInfo {
116 pub from: Address,
117 pub validator: Address,
118 pub amount: U256,
119}
120
121#[derive(Clone, Debug)]
122enum StakeTableTx {
123 SendEth {
124 to: Address,
125 amount: U256,
126 },
127 SendEsp {
128 to: Address,
129 amount: U256,
130 },
131 RegisterValidator {
132 from: Address,
133 commission: Commission,
134 payload: Box<NodeSignatures>,
135 },
136 Approve {
137 from: Address,
138 amount: U256,
139 },
140 Delegate {
141 from: Address,
142 validator: Address,
143 amount: U256,
144 },
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148enum SetupPhase {
149 Funding,
150 Approval,
151 Registration,
152 Delegation,
153}
154
155impl SetupPhase {
156 fn next(self) -> Option<Self> {
157 match self {
158 Self::Funding => Some(Self::Approval),
159 Self::Approval => Some(Self::Registration),
160 Self::Registration => Some(Self::Delegation),
161 Self::Delegation => None,
162 }
163 }
164}
165
166struct ValidatorConfig {
167 signer: PrivateKeySigner,
168 commission: Commission,
169 bls_key_pair: BLSKeyPair,
170 state_key_pair: StateKeyPair,
171 index: usize,
172}
173
174struct DelegatorConfig {
175 validator: Address,
176 signer: PrivateKeySigner,
177 delegate_amount: U256,
178}
179
180#[derive(Clone, Debug)]
181struct TransactionQueues {
182 funding: VecDeque<StakeTableTx>,
183 approvals: VecDeque<StakeTableTx>,
184 registration: VecDeque<StakeTableTx>,
185 delegations: VecDeque<StakeTableTx>,
186 current_phase: SetupPhase,
187}
188
189impl TransactionQueues {
190 fn current_group_mut(&mut self) -> &mut VecDeque<StakeTableTx> {
191 match self.current_phase {
192 SetupPhase::Funding => &mut self.funding,
193 SetupPhase::Approval => &mut self.approvals,
194 SetupPhase::Registration => &mut self.registration,
195 SetupPhase::Delegation => &mut self.delegations,
196 }
197 }
198
199 fn pop_next(&mut self) -> Option<StakeTableTx> {
200 loop {
201 if let Some(tx) = self.current_group_mut().pop_front() {
202 return Some(tx);
203 }
204 self.current_phase = self.current_phase.next()?;
205 }
206 }
207}
208
209#[derive(Clone, Debug)]
210struct TransactionProcessor<P> {
211 providers: HashMap<Address, P>,
212 funder: P,
213 stake_table: Address,
214 token: Address,
215}
216
217impl<P: Provider + Clone> TransactionProcessor<P> {
218 fn provider(&self, address: Address) -> Result<&P> {
219 self.providers
220 .get(&address)
221 .ok_or_else(|| anyhow::anyhow!("provider not found for {address}"))
222 }
223
224 async fn send_next(&self, tx: StakeTableTx) -> Result<PendingTransactionBuilder<Ethereum>> {
225 match tx {
226 StakeTableTx::SendEth { to, amount } => send_eth(&self.funder, to, amount).await,
227 StakeTableTx::SendEsp { to, amount } => {
228 send_esp(&self.funder, self.token, to, amount).await
229 },
230 StakeTableTx::RegisterValidator {
231 from,
232 commission,
233 payload,
234 } => {
235 let metadata_uri = "https://example.com/metadata".parse()?;
236 register_validator(
237 self.provider(from)?,
238 self.stake_table,
239 commission,
240 metadata_uri,
241 *payload,
242 )
243 .await
244 },
245 StakeTableTx::Approve { from, amount } => {
246 approve(self.provider(from)?, self.token, self.stake_table, amount).await
247 },
248 StakeTableTx::Delegate {
249 from,
250 validator,
251 amount,
252 } => delegate(self.provider(from)?, self.stake_table, validator, amount).await,
253 }
254 }
255
256 async fn process_group(
257 &self,
258 txs: &mut VecDeque<StakeTableTx>,
259 ) -> Result<Vec<TransactionReceipt>> {
260 let mut pending = vec![];
261 while let Some(tx) = txs.pop_front() {
262 pending.push(self.send_next(tx).await?);
263 }
264 future::try_join_all(
265 pending
266 .into_iter()
267 .map(|p| async move { p.assert_success().await }),
268 )
269 .await
270 }
271}
272
273#[derive(Clone, Debug)]
274pub struct StakingTransactions<P> {
275 processor: TransactionProcessor<P>,
276 queues: TransactionQueues,
277}
278
279impl<P: Provider + Clone> StakingTransactions<P> {
280 pub async fn apply_all(&mut self) -> Result<Vec<TransactionReceipt>> {
298 let mut receipts = Vec::new();
299
300 for queue in [
301 &mut self.queues.funding,
302 &mut self.queues.approvals,
303 &mut self.queues.registration,
304 &mut self.queues.delegations,
305 ] {
306 receipts.extend(self.processor.process_group(queue).await?);
307 }
308
309 tracing::info!("completed all staking transactions");
310
311 Ok(receipts)
312 }
313
314 pub async fn apply_prerequisites(&mut self) -> Result<Vec<TransactionReceipt>> {
322 if !matches!(self.queues.current_phase, SetupPhase::Funding) {
323 return Err(anyhow::anyhow!("apply_prerequisites must be called first"));
324 }
325
326 let mut receipts = Vec::new();
327
328 for queue in [&mut self.queues.funding, &mut self.queues.approvals] {
329 receipts.extend(self.processor.process_group(queue).await?);
330 }
331
332 self.queues.current_phase = SetupPhase::Registration;
333
334 Ok(receipts)
335 }
336
337 pub async fn apply_one(&mut self) -> Result<Option<TransactionReceipt>> {
342 let Some(tx) = self.queues.pop_next() else {
343 return Ok(None);
344 };
345 let pending = self.processor.send_next(tx).await?;
346 Ok(Some(pending.assert_success().await?))
347 }
348
349 pub fn registrations(&self) -> Vec<RegistrationInfo> {
354 self.queues
355 .registration
356 .iter()
357 .filter_map(|tx| {
358 if let StakeTableTx::RegisterValidator {
359 from,
360 commission,
361 payload,
362 } = tx
363 {
364 Some(RegistrationInfo {
365 from: *from,
366 commission: *commission,
367 payload: *payload.clone(),
368 })
369 } else {
370 None
371 }
372 })
373 .collect()
374 }
375
376 pub fn delegations(&self) -> Vec<DelegationInfo> {
381 self.queues
382 .delegations
383 .iter()
384 .filter_map(|tx| {
385 if let StakeTableTx::Delegate {
386 from,
387 validator,
388 amount,
389 } = tx
390 {
391 Some(DelegationInfo {
392 from: *from,
393 validator: *validator,
394 amount: *amount,
395 })
396 } else {
397 None
398 }
399 })
400 .collect()
401 }
402
403 pub fn provider(&self, address: Address) -> Option<&P> {
412 self.processor.providers.get(&address)
413 }
414}
415
416impl StakingTransactions<HttpProviderWithWallet> {
417 pub async fn create(
444 rpc_url: Url,
445 token_holder: &(impl Provider + WalletProvider<Wallet = EthereumWallet>),
446 stake_table: Address,
447 validators: Vec<(PrivateKeySigner, BLSKeyPair, StateKeyPair)>,
448 num_delegators_per_validator: Option<u64>,
449 config: DelegationConfig,
450 ) -> Result<Self, CreateTransactionsError> {
451 tracing::info!(%stake_table, "staking to stake table contract for demo");
452
453 let token = fetch_token_address(rpc_url.clone(), stake_table).await?;
454
455 let shared_client = RpcClient::new(Http::new(rpc_url), true);
458
459 let token_holder_provider = ProviderBuilder::new()
460 .wallet(token_holder.wallet().clone())
461 .connect_client(shared_client.clone());
462
463 tracing::info!("ESP token address: {token}");
464 let token_holder_addr = token_holder.default_signer_address();
465 let token_balance = EspToken::new(token, &token_holder_provider)
466 .balanceOf(token_holder_addr)
467 .call()
468 .await?;
469 tracing::info!(
470 "token distributor account {} balance: {} ESP",
471 token_holder_addr,
472 format_ether(token_balance)
473 );
474
475 let fund_amount_esp = parse_ether("1000").unwrap();
476 let fund_amount_eth = parse_ether("10").unwrap();
477
478 let seed = [42u8; 32];
479 let mut rng = ChaCha20Rng::from_seed(seed);
480
481 let mut validator_info = vec![];
482 for (val_index, (signer, bls_key_pair, state_key_pair)) in
483 validators.into_iter().enumerate()
484 {
485 let commission = Commission::try_from(100u64 + 10u64 * val_index as u64)?;
486
487 validator_info.push(ValidatorConfig {
488 signer,
489 commission,
490 bls_key_pair,
491 state_key_pair,
492 index: val_index,
493 });
494 }
495
496 let mut delegator_info = vec![];
497
498 for validator in &validator_info {
499 let delegate_amount = match config {
500 DelegationConfig::EqualAmounts => Some(parse_ether("100").unwrap()),
501 DelegationConfig::MultipleDelegators | DelegationConfig::VariableAmounts => {
502 Some(parse_ether("100").unwrap() * U256::from(validator.index % 5 + 1))
503 },
504 DelegationConfig::NoSelfDelegation => None,
505 };
506
507 if let Some(amount) = delegate_amount {
508 delegator_info.push(DelegatorConfig {
509 validator: validator.signer.address(),
510 signer: validator.signer.clone(),
511 delegate_amount: amount,
512 });
513 }
514 }
515
516 if matches!(
517 config,
518 DelegationConfig::MultipleDelegators | DelegationConfig::NoSelfDelegation
519 ) {
520 for validator in &validator_info {
521 let delegators_per_validator = num_delegators_per_validator
522 .map(|n| n as usize)
523 .unwrap_or_else(|| rng.gen_range(2..=5));
524 for _ in 0..delegators_per_validator {
525 let random_amount: u64 = rng.gen_range(100..=500);
526 delegator_info.push(DelegatorConfig {
527 validator: validator.signer.address(),
528 signer: PrivateKeySigner::random(),
529 delegate_amount: parse_ether(&random_amount.to_string()).unwrap(),
530 });
531 }
532 }
533 }
534
535 let st = StakeTableV2::new(stake_table, &token_holder_provider);
536 let version: StakeTableContractVersion = st.getVersion().call().await?.try_into()?;
537 if let StakeTableContractVersion::V2 = version {
538 let min_delegate_amount = st.minDelegateAmount().call().await?;
539 for delegator in &delegator_info {
540 if delegator.delegate_amount < min_delegate_amount {
541 return Err(CreateTransactionsError::DelegationBelowMinimum {
542 amount: format_ether(delegator.delegate_amount),
543 min: format_ether(min_delegate_amount),
544 });
545 }
546 }
547 }
548
549 let mut funding = VecDeque::new();
550
551 let eth_recipients: HashSet<Address> = validator_info
552 .iter()
553 .map(|v| v.signer.address())
554 .chain(delegator_info.iter().map(|d| d.signer.address()))
555 .collect();
556
557 for &address in ð_recipients {
558 funding.push_back(StakeTableTx::SendEth {
559 to: address,
560 amount: fund_amount_eth,
561 });
562 }
563
564 for delegator in &delegator_info {
565 funding.push_back(StakeTableTx::SendEsp {
566 to: delegator.signer.address(),
567 amount: fund_amount_esp,
568 });
569 }
570
571 let mut providers: HashMap<Address, _> = HashMap::new();
573
574 let mut registration = VecDeque::new();
575
576 for validator in &validator_info {
577 let address = validator.signer.address();
578 providers.entry(address).or_insert_with(|| {
579 ProviderBuilder::new()
580 .wallet(EthereumWallet::from(validator.signer.clone()))
581 .connect_client(shared_client.clone())
582 });
583
584 let payload =
585 NodeSignatures::create(address, &validator.bls_key_pair, &validator.state_key_pair);
586 registration.push_back(StakeTableTx::RegisterValidator {
587 from: address,
588 commission: validator.commission,
589 payload: Box::new(payload),
590 });
591 }
592
593 let mut approvals = VecDeque::new();
594 let mut delegations = VecDeque::new();
595
596 for delegator in &delegator_info {
597 let address = delegator.signer.address();
598 providers.entry(address).or_insert_with(|| {
599 ProviderBuilder::new()
600 .wallet(EthereumWallet::from(delegator.signer.clone()))
601 .connect_client(shared_client.clone())
602 });
603
604 approvals.push_back(StakeTableTx::Approve {
605 from: address,
606 amount: delegator.delegate_amount,
607 });
608
609 delegations.push_back(StakeTableTx::Delegate {
610 from: address,
611 validator: delegator.validator,
612 amount: delegator.delegate_amount,
613 });
614 }
615
616 let esp_required = fund_amount_esp * U256::from(delegator_info.len());
617 let eth_required = fund_amount_eth * U256::from(eth_recipients.len()) * U256::from(2);
618
619 if token_balance < esp_required {
620 return Err(CreateTransactionsError::InsufficientEsp {
621 have: format_ether(token_balance),
622 need: format_ether(esp_required),
623 delegators: delegator_info.len(),
624 });
625 }
626
627 let eth_balance = token_holder_provider.get_balance(token_holder_addr).await?;
628 if eth_balance < eth_required {
629 return Err(CreateTransactionsError::InsufficientEth {
630 have: format_ether(eth_balance),
631 need: format_ether(eth_required),
632 recipients: eth_recipients.len(),
633 });
634 }
635
636 Ok(StakingTransactions {
637 processor: TransactionProcessor {
638 providers,
639 funder: token_holder_provider,
640 stake_table,
641 token,
642 },
643 queues: TransactionQueues {
644 funding,
645 approvals,
646 registration,
647 delegations,
648 current_phase: SetupPhase::Funding,
649 },
650 })
651 }
652}
653
654pub async fn stake_for_demo(
661 config: &Config,
662 num_validators: u16,
663 num_delegators_per_validator: Option<u64>,
664 delegation_config: DelegationConfig,
665) -> Result<()> {
666 tracing::info!("staking to stake table contract for demo");
667
668 let grant_recipient = build_provider(
670 config.signer.mnemonic.clone().unwrap(),
671 config.signer.account_index.unwrap(),
672 config.rpc_url.clone(),
673 None,
674 );
675
676 tracing::info!(
677 "grant recipient account for token funding: {}",
678 grant_recipient.default_signer_address()
679 );
680
681 let token_address =
682 fetch_token_address(config.rpc_url.clone(), config.stake_table_address).await?;
683 tracing::info!("ESP token address: {}", token_address);
684 let stake_table_address = config.stake_table_address;
685 tracing::info!("stake table address: {}", stake_table_address);
686
687 let mut validator_keys = vec![];
688 for val_index in 0..num_validators {
689 let signer = build_signer(
690 config.signer.mnemonic.clone().unwrap(),
691 20u32 + val_index as u32,
692 );
693
694 let consensus_private_key = parse_bls_priv_key(&dotenvy::var(format!(
695 "ESPRESSO_DEMO_SEQUENCER_STAKING_PRIVATE_KEY_{val_index}"
696 ))?)?
697 .into();
698 let state_private_key = parse_state_priv_key(&dotenvy::var(format!(
699 "ESPRESSO_DEMO_SEQUENCER_STATE_PRIVATE_KEY_{val_index}"
700 ))?)?;
701 validator_keys.push((
702 signer,
703 consensus_private_key,
704 StateKeyPair::from_sign_key(state_private_key),
705 ));
706 }
707
708 StakingTransactions::create(
709 config.rpc_url.clone(),
710 &grant_recipient,
711 config.stake_table_address,
712 validator_keys,
713 num_delegators_per_validator,
714 delegation_config,
715 )
716 .await?
717 .apply_all()
718 .await?;
719
720 Ok(())
721}
722
723#[cfg(test)]
724mod test {
725 use alloy::providers::ext::AnvilApi as _;
726 use espresso_types::v0_3::Validator;
727 use hotshot_types::signature_key::BLSPubKey;
728 use pretty_assertions::assert_matches;
729 use rand::rngs::StdRng;
730
731 use super::*;
732 use crate::{deploy::TestSystem, info::stake_table_info};
733
734 async fn shared_setup(
735 config: DelegationConfig,
736 ) -> Result<(Validator<BLSPubKey>, Validator<BLSPubKey>)> {
737 let system = TestSystem::deploy().await?;
738
739 let mut rng = StdRng::from_seed([42u8; 32]);
740 let keys = vec![
741 TestSystem::gen_keys(&mut rng),
742 TestSystem::gen_keys(&mut rng),
743 ];
744
745 StakingTransactions::create(
746 system.rpc_url.clone(),
747 &system.provider,
748 system.stake_table,
749 keys,
750 None,
751 config,
752 )
753 .await?
754 .apply_all()
755 .await?;
756 let l1_block_number = system.provider.get_block_number().await?;
757 let st = stake_table_info(system.rpc_url, system.stake_table, l1_block_number).await?;
758
759 assert_eq!(st.len(), 2);
761 let val1 = st[0].clone();
762 let val2 = st[1].clone();
763
764 assert_ne!(val1.account, val2.account);
766
767 Ok((val1, val2))
768 }
769
770 #[test_log::test(tokio::test)]
771 async fn test_stake_for_demo_equal_amounts() -> Result<()> {
772 let (val1, val2) = shared_setup(DelegationConfig::EqualAmounts).await?;
773
774 assert_eq!(val1.delegators.get(&val1.account), Some(&val1.stake));
776 assert_eq!(val2.delegators.get(&val2.account), Some(&val2.stake));
777
778 assert_eq!(val1.delegators.len(), 1);
780 assert_eq!(val2.delegators.len(), 1);
781
782 assert_eq!(val1.stake, val2.stake);
784
785 Ok(())
786 }
787
788 #[test_log::test(tokio::test)]
789 async fn test_stake_for_demo_variable_amounts() -> Result<()> {
790 let (val1, val2) = shared_setup(DelegationConfig::VariableAmounts).await?;
791
792 assert_eq!(val1.delegators.get(&val1.account), Some(&val1.stake));
794 assert_eq!(val2.delegators.get(&val2.account), Some(&val2.stake));
795
796 assert_eq!(val1.delegators.len(), 1);
798 assert_eq!(val2.delegators.len(), 1);
799
800 assert_ne!(val1.stake, val2.stake);
802
803 Ok(())
804 }
805
806 #[test_log::test(tokio::test)]
807 async fn test_stake_for_demo_multiple_delegators() -> Result<()> {
808 let (val1, val2) = shared_setup(DelegationConfig::MultipleDelegators).await?;
809
810 assert_ne!(val1.delegators.get(&val1.account), Some(&val1.stake));
812 assert_ne!(val2.delegators.get(&val2.account), Some(&val2.stake));
813
814 assert!(val1.delegators.len() > 1);
816 assert!(val2.delegators.len() > 1);
817
818 assert_ne!(val1.stake, val2.stake);
820
821 Ok(())
822 }
823
824 #[test_log::test(tokio::test)]
825 async fn test_stake_for_demo_no_self_delegation() -> Result<()> {
826 let (val1, val2) = shared_setup(DelegationConfig::NoSelfDelegation).await?;
827
828 assert_eq!(val1.delegators.get(&val1.account), None);
830 assert_eq!(val2.delegators.get(&val2.account), None);
831
832 assert!(val1.delegators.len() > 1);
834 assert!(val2.delegators.len() > 1);
835
836 assert_ne!(val1.stake, val2.stake);
838
839 Ok(())
840 }
841
842 #[test_log::test(tokio::test)]
843 async fn test_configurable_delegators_per_validator() -> Result<()> {
844 let system = TestSystem::deploy().await?;
845
846 let mut rng = StdRng::from_seed([42u8; 32]);
847 let keys = vec![
848 TestSystem::gen_keys(&mut rng),
849 TestSystem::gen_keys(&mut rng),
850 ];
851
852 StakingTransactions::create(
853 system.rpc_url.clone(),
854 &system.provider,
855 system.stake_table,
856 keys,
857 Some(10),
858 DelegationConfig::MultipleDelegators,
859 )
860 .await?
861 .apply_all()
862 .await?;
863 let l1_block_number = system.provider.get_block_number().await?;
864 let st = stake_table_info(system.rpc_url, system.stake_table, l1_block_number).await?;
865
866 assert_eq!(st.len(), 2);
867
868 let val1 = &st[0];
869 let val2 = &st[1];
870
871 assert_eq!(val1.delegators.len(), 11);
873 assert_eq!(val2.delegators.len(), 11);
874
875 Ok(())
876 }
877
878 enum Failure {
879 Esp,
880 Eth,
881 }
882
883 #[rstest::rstest]
884 #[case::esp(Failure::Esp)]
885 #[case::eth(Failure::Eth)]
886 #[test_log::test(tokio::test)]
887 async fn test_insufficient_balance(#[case] case: Failure) -> Result<()> {
888 let system = TestSystem::deploy().await?;
889
890 let drain_address = PrivateKeySigner::random().address();
891
892 match case {
893 Failure::Esp => {
894 let balance = system
895 .balance(system.provider.default_signer_address())
896 .await?;
897 system.transfer(drain_address, balance).await?;
898 },
899 Failure::Eth => {
900 let eth_balance = system
901 .provider
902 .get_balance(system.provider.default_signer_address())
903 .await?;
904 let drain_amount = eth_balance - parse_ether("1").unwrap();
906 system.transfer_eth(drain_address, drain_amount).await?;
907 },
908 }
909
910 let mut rng = StdRng::from_seed([42u8; 32]);
911 let keys = vec![TestSystem::gen_keys(&mut rng)];
912
913 let result = StakingTransactions::create(
914 system.rpc_url.clone(),
915 &system.provider,
916 system.stake_table,
917 keys,
918 None,
919 DelegationConfig::EqualAmounts,
920 )
921 .await;
922
923 let err = result.expect_err("should fail with insufficient balance");
924 match case {
925 Failure::Esp => assert_matches!(err, CreateTransactionsError::InsufficientEsp { .. }),
926 Failure::Eth => assert_matches!(err, CreateTransactionsError::InsufficientEth { .. }),
927 };
928
929 Ok(())
930 }
931
932 #[test_log::test(tokio::test)]
933 async fn test_delegation_below_minimum() -> Result<()> {
934 let system = TestSystem::deploy().await?;
935
936 let high_min = parse_ether("2000")?;
938 system.set_min_delegate_amount(high_min).await?;
939
940 let mut rng = StdRng::from_seed([42u8; 32]);
941 let keys = vec![TestSystem::gen_keys(&mut rng)];
942
943 let result = StakingTransactions::create(
946 system.rpc_url.clone(),
947 &system.provider,
948 system.stake_table,
949 keys,
950 None,
951 DelegationConfig::EqualAmounts,
952 )
953 .await;
954
955 assert_matches!(
956 result,
957 Err(CreateTransactionsError::DelegationBelowMinimum { .. })
958 );
959
960 Ok(())
961 }
962
963 #[rstest::rstest]
964 #[case::equal_amounts(DelegationConfig::EqualAmounts)]
965 #[case::variable_amounts(DelegationConfig::VariableAmounts)]
966 #[case::multiple_delegators(DelegationConfig::MultipleDelegators)]
967 #[case::no_self_delegation(DelegationConfig::NoSelfDelegation)]
968 #[test_log::test(tokio::test)]
969 async fn test_setup_with_slow_blocks(#[case] config: DelegationConfig) -> Result<()> {
970 let system = TestSystem::deploy().await?;
971 system.provider.anvil_set_auto_mine(false).await?;
972 system.provider.anvil_set_interval_mining(1).await?;
973
974 let mut rng = StdRng::from_seed([42u8; 32]);
975 let keys = vec![
976 TestSystem::gen_keys(&mut rng),
977 TestSystem::gen_keys(&mut rng),
978 ];
979
980 StakingTransactions::create(
981 system.rpc_url.clone(),
982 &system.provider,
983 system.stake_table,
984 keys,
985 None,
986 config,
987 )
988 .await?
989 .apply_all()
990 .await?;
991 let l1_block_number = system.provider.get_block_number().await?;
992 let st = stake_table_info(system.rpc_url, system.stake_table, l1_block_number).await?;
993
994 assert_eq!(st.len(), 2);
995 assert!(st[0].stake > U256::ZERO);
996 assert!(st[1].stake > U256::ZERO);
997
998 if let DelegationConfig::NoSelfDelegation = config {
999 assert!(!st[0].delegators.contains_key(&st[0].account));
1000 assert!(!st[1].delegators.contains_key(&st[1].account));
1001 }
1002
1003 Ok(())
1004 }
1005}