1use std::{collections::HashMap, io::Write, time::Duration};
2
3use alloy::{
4 contract::RawCallBuilder,
5 dyn_abi::{DynSolType, DynSolValue, JsonAbiExt},
6 hex::{FromHex, ToHexExt},
7 json_abi::Function,
8 network::{Ethereum, EthereumWallet, TransactionBuilder},
9 primitives::{Address, B256, Bytes, U256},
10 providers::{
11 Provider, ProviderBuilder, RootProvider,
12 fillers::{FillProvider, JoinFill, WalletFiller},
13 utils::JoinedRecommendedFillers,
14 },
15 rpc::{client::RpcClient, types::TransactionReceipt},
16 signers::{
17 ledger::LedgerSigner,
18 local::{MnemonicBuilder, PrivateKeySigner, coins_bip39::English},
19 },
20 transports::http::reqwest::Url,
21};
22use anyhow::{Context, Result, anyhow};
23use clap::{Parser, ValueEnum, builder::OsStr};
24use derive_more::Display;
25use espresso_types::{v0_1::L1Client, v0_3::Fetcher};
26use hotshot_contract_adapter::sol_types::*;
27
28pub mod builder;
29pub mod impersonate_filler;
30pub mod network_config;
31pub mod output;
32pub mod proposals;
33pub mod provider;
34
35pub type HttpProviderWithWallet = FillProvider<
39 JoinFill<JoinedRecommendedFillers, WalletFiller<EthereumWallet>>,
40 RootProvider,
41 Ethereum,
42>;
43
44pub fn build_provider(
47 mnemonic: impl AsRef<str>,
48 account_index: u32,
49 url: Url,
50 poll_interval: Option<Duration>,
51) -> HttpProviderWithWallet {
52 let signer = build_signer(mnemonic.as_ref(), account_index);
53 let wallet = EthereumWallet::from(signer);
54
55 if let Some(interval) = poll_interval {
60 tracing::info!("Using custom L1 poll interval: {interval:?}");
61 let client = RpcClient::new_http(url.clone()).with_poll_interval(interval);
62 ProviderBuilder::new().wallet(wallet).connect_client(client)
63 } else {
64 tracing::info!("Using default L1 poll interval");
65 ProviderBuilder::new().wallet(wallet).connect_http(url)
66 }
67}
68
69pub fn build_provider_ledger(
72 signer: LedgerSigner,
73 url: Url,
74 poll_interval: Option<Duration>,
75) -> HttpProviderWithWallet {
76 let wallet = EthereumWallet::from(signer);
77
78 if let Some(interval) = poll_interval {
83 tracing::info!("Using custom L1 poll interval: {interval:?}");
84 let client = RpcClient::new_http(url.clone()).with_poll_interval(interval);
85 ProviderBuilder::new().wallet(wallet).connect_client(client)
86 } else {
87 tracing::info!("Using default L1 poll interval");
88 ProviderBuilder::new().wallet(wallet).connect_http(url)
89 }
90}
91
92pub fn build_signer(mnemonic: impl AsRef<str>, account_index: u32) -> PrivateKeySigner {
93 MnemonicBuilder::<English>::default()
94 .phrase(mnemonic.as_ref())
95 .index(account_index)
96 .expect("wrong mnemonic or index")
97 .build()
98 .expect("fail to build signer")
99}
100
101pub fn build_random_provider(url: Url) -> HttpProviderWithWallet {
103 let signer = MnemonicBuilder::<English>::default()
104 .build_random()
105 .expect("fail to build signer");
106 let wallet = EthereumWallet::from(signer);
107 ProviderBuilder::new().wallet(wallet).connect_http(url)
108}
109
110const LIBRARY_PLACEHOLDER_ADDRESS: &str = "ffffffffffffffffffffffffffffffffffffffff";
112pub const MAX_HISTORY_RETENTION_SECONDS: u32 = 864000;
114pub const DEFAULT_EXIT_ESCROW_PERIOD_SECONDS: u64 = 172800;
116pub const MAX_RETRY_ATTEMPTS: u32 = 5;
118pub const RETRY_INITIAL_DELAY_MS: u64 = 500;
120
121#[derive(Clone, Debug, Parser)]
123pub struct DeployedContracts {
124 #[clap(long, env = Contract::PlonkVerifier)]
126 plonk_verifier: Option<Address>,
127
128 #[clap(long, env = Contract::OpsTimelock)]
130 ops_timelock: Option<Address>,
131
132 #[clap(long, env = Contract::SafeExitTimelock)]
134 safe_exit_timelock: Option<Address>,
135
136 #[clap(long, env = Contract::PlonkVerifierV2)]
138 plonk_verifier_v2: Option<Address>,
139
140 #[clap(long, env = Contract::PlonkVerifierV3)]
142 plonk_verifier_v3: Option<Address>,
143
144 #[clap(long, env = Contract::LightClient)]
146 light_client: Option<Address>,
147
148 #[clap(long, env = Contract::LightClientV2)]
150 light_client_v2: Option<Address>,
151
152 #[clap(long, env = Contract::LightClientV3)]
154 light_client_v3: Option<Address>,
155
156 #[clap(long, env = Contract::LightClientProxy)]
158 light_client_proxy: Option<Address>,
159
160 #[clap(long, env = Contract::FeeContract)]
162 fee_contract: Option<Address>,
163
164 #[clap(long, env = Contract::FeeContractProxy)]
166 fee_contract_proxy: Option<Address>,
167
168 #[clap(long, env = Contract::EspToken)]
170 esp_token: Option<Address>,
171
172 #[clap(long, env = Contract::EspTokenV2)]
174 esp_token_v2: Option<Address>,
175
176 #[clap(long, env = Contract::EspTokenProxy)]
178 esp_token_proxy: Option<Address>,
179
180 #[clap(long, env = Contract::StakeTable)]
182 stake_table: Option<Address>,
183
184 #[clap(long, env = Contract::StakeTableV2)]
186 stake_table_v2: Option<Address>,
187
188 #[clap(long, env = Contract::StakeTableProxy)]
190 stake_table_proxy: Option<Address>,
191
192 #[clap(long, env = Contract::RewardClaim)]
194 reward_claim: Option<Address>,
195
196 #[clap(long, env = Contract::RewardClaimProxy)]
198 reward_claim_proxy: Option<Address>,
199}
200
201#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Hash)]
203pub enum Contract {
204 #[display("ESPRESSO_SEQUENCER_PLONK_VERIFIER_ADDRESS")]
205 PlonkVerifier,
206 #[display("ESPRESSO_SEQUENCER_OPS_TIMELOCK_ADDRESS")]
207 OpsTimelock,
208 #[display("ESPRESSO_SEQUENCER_SAFE_EXIT_TIMELOCK_ADDRESS")]
209 SafeExitTimelock,
210 #[display("ESPRESSO_SEQUENCER_PLONK_VERIFIER_V2_ADDRESS")]
211 PlonkVerifierV2,
212 #[display("ESPRESSO_SEQUENCER_PLONK_VERIFIER_V3_ADDRESS")]
213 PlonkVerifierV3,
214 #[display("ESPRESSO_SEQUENCER_LIGHT_CLIENT_ADDRESS")]
215 LightClient,
216 #[display("ESPRESSO_SEQUENCER_LIGHT_CLIENT_V2_ADDRESS")]
217 LightClientV2,
218 #[display("ESPRESSO_SEQUENCER_LIGHT_CLIENT_V3_ADDRESS")]
219 LightClientV3,
220 #[display("ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS")]
221 LightClientProxy,
222 #[display("ESPRESSO_SEQUENCER_FEE_CONTRACT_ADDRESS")]
223 FeeContract,
224 #[display("ESPRESSO_SEQUENCER_FEE_CONTRACT_PROXY_ADDRESS")]
225 FeeContractProxy,
226 #[display("ESPRESSO_SEQUENCER_ESP_TOKEN_ADDRESS")]
227 EspToken,
228 #[display("ESPRESSO_SEQUENCER_ESP_TOKEN_V2_ADDRESS")]
229 EspTokenV2,
230 #[display("ESPRESSO_SEQUENCER_ESP_TOKEN_PROXY_ADDRESS")]
231 EspTokenProxy,
232 #[display("ESPRESSO_SEQUENCER_STAKE_TABLE_ADDRESS")]
233 StakeTable,
234 #[display("ESPRESSO_SEQUENCER_STAKE_TABLE_V2_ADDRESS")]
235 StakeTableV2,
236 #[display("ESPRESSO_SEQUENCER_STAKE_TABLE_PROXY_ADDRESS")]
237 StakeTableProxy,
238 #[display("ESPRESSO_SEQUENCER_REWARD_CLAIM_ADDRESS")]
239 RewardClaim,
240 #[display("ESPRESSO_SEQUENCER_REWARD_CLAIM_PROXY_ADDRESS")]
241 RewardClaimProxy,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
245#[clap(rename_all = "kebab-case")]
246pub enum OwnableContract {
247 #[value(alias = "feecontract", alias = "FeeContract")]
248 FeeContractProxy,
249 #[value(alias = "lightclient", alias = "LightClient")]
250 LightClientProxy,
251 #[value(alias = "staketable", alias = "StakeTable")]
252 StakeTableProxy,
253 #[value(alias = "esptoken", alias = "EspToken")]
254 EspTokenProxy,
255 #[value(alias = "rewardclaim", alias = "RewardClaim")]
256 RewardClaimProxy,
257}
258
259impl From<OwnableContract> for Contract {
260 fn from(c: OwnableContract) -> Contract {
261 match c {
262 OwnableContract::FeeContractProxy => Contract::FeeContractProxy,
263 OwnableContract::LightClientProxy => Contract::LightClientProxy,
264 OwnableContract::StakeTableProxy => Contract::StakeTableProxy,
265 OwnableContract::EspTokenProxy => Contract::EspTokenProxy,
266 OwnableContract::RewardClaimProxy => Contract::RewardClaimProxy,
267 }
268 }
269}
270
271impl From<Contract> for OsStr {
272 fn from(c: Contract) -> OsStr {
273 c.to_string().into()
274 }
275}
276
277#[derive(Debug, Clone)]
279pub struct Contracts {
280 addresses: HashMap<Contract, Address>,
281 deploy_cooldown: Duration,
284}
285
286impl Default for Contracts {
287 fn default() -> Self {
288 Self {
289 addresses: HashMap::new(),
290 deploy_cooldown: Duration::ZERO,
291 }
292 }
293}
294
295impl From<DeployedContracts> for Contracts {
296 fn from(deployed: DeployedContracts) -> Self {
297 let mut m = HashMap::new();
298 if let Some(addr) = deployed.plonk_verifier {
299 m.insert(Contract::PlonkVerifier, addr);
300 }
301 if let Some(addr) = deployed.plonk_verifier_v2 {
302 m.insert(Contract::PlonkVerifierV2, addr);
303 }
304 if let Some(addr) = deployed.plonk_verifier_v3 {
305 m.insert(Contract::PlonkVerifierV3, addr);
306 }
307 if let Some(addr) = deployed.safe_exit_timelock {
308 m.insert(Contract::SafeExitTimelock, addr);
309 }
310 if let Some(addr) = deployed.ops_timelock {
311 m.insert(Contract::OpsTimelock, addr);
312 }
313 if let Some(addr) = deployed.light_client {
314 m.insert(Contract::LightClient, addr);
315 }
316 if let Some(addr) = deployed.light_client_v2 {
317 m.insert(Contract::LightClientV2, addr);
318 }
319 if let Some(addr) = deployed.light_client_v3 {
320 m.insert(Contract::LightClientV3, addr);
321 }
322 if let Some(addr) = deployed.light_client_proxy {
323 m.insert(Contract::LightClientProxy, addr);
324 }
325 if let Some(addr) = deployed.fee_contract {
326 m.insert(Contract::FeeContract, addr);
327 }
328 if let Some(addr) = deployed.fee_contract_proxy {
329 m.insert(Contract::FeeContractProxy, addr);
330 }
331 if let Some(addr) = deployed.esp_token {
332 m.insert(Contract::EspToken, addr);
333 }
334 if let Some(addr) = deployed.esp_token_v2 {
335 m.insert(Contract::EspTokenV2, addr);
336 }
337 if let Some(addr) = deployed.esp_token_proxy {
338 m.insert(Contract::EspTokenProxy, addr);
339 }
340 if let Some(addr) = deployed.stake_table {
341 m.insert(Contract::StakeTable, addr);
342 }
343 if let Some(addr) = deployed.stake_table_v2 {
344 m.insert(Contract::StakeTableV2, addr);
345 }
346 if let Some(addr) = deployed.stake_table_proxy {
347 m.insert(Contract::StakeTableProxy, addr);
348 }
349 if let Some(addr) = deployed.reward_claim {
350 m.insert(Contract::RewardClaim, addr);
351 }
352 if let Some(addr) = deployed.reward_claim_proxy {
353 m.insert(Contract::RewardClaimProxy, addr);
354 }
355 Self {
356 addresses: m,
357 deploy_cooldown: Duration::ZERO,
358 }
359 }
360}
361
362impl Contracts {
363 pub fn new() -> Self {
364 Self::default()
365 }
366
367 pub fn with_cooldown(cooldown: Duration) -> Self {
368 Self {
369 addresses: HashMap::new(),
370 deploy_cooldown: cooldown,
371 }
372 }
373
374 pub fn set_cooldown(&mut self, cooldown: Duration) {
375 self.deploy_cooldown = cooldown;
376 }
377
378 pub fn address(&self, contract: Contract) -> Option<Address> {
379 self.addresses.get(&contract).copied()
380 }
381
382 pub fn get(&self, contract: &Contract) -> Option<&Address> {
383 self.addresses.get(contract)
384 }
385
386 pub fn iter(&self) -> impl Iterator<Item = (&Contract, &Address)> {
387 self.addresses.iter()
388 }
389
390 pub fn remove(&mut self, contract: &Contract) -> Option<Address> {
391 self.addresses.remove(contract)
392 }
393
394 pub async fn deploy<P>(&mut self, name: Contract, tx: RawCallBuilder<P>) -> Result<Address>
399 where
400 P: Provider,
401 {
402 if let Some(addr) = self.addresses.get(&name) {
403 tracing::info!("skipping deployment of {name}, already deployed at {addr:#x}");
404 return Ok(*addr);
405 }
406 tracing::info!("deploying {name}");
407 let pending_tx = tx.send().await?;
408 let tx_hash = *pending_tx.tx_hash();
409 tracing::info!(%tx_hash, "waiting for tx to be mined");
410 let receipt = pending_tx.get_receipt().await?;
411 if !receipt.inner.is_success() {
412 anyhow::bail!("Deployment transaction failed: {:?}", receipt);
413 }
414 tracing::info!(%receipt.gas_used, %tx_hash, "tx mined");
415 let addr = receipt
416 .contract_address
417 .ok_or(alloy::contract::Error::ContractNotDeployed)?;
418
419 tracing::info!("deployed {name} at {addr:#x}");
420
421 if !self.deploy_cooldown.is_zero() {
422 tokio::time::sleep(self.deploy_cooldown).await;
423 tracing::info!("cooldown {:?} after deployment", self.deploy_cooldown);
424 }
425
426 self.addresses.insert(name, addr);
427 Ok(addr)
428 }
429
430 pub fn write(&self, mut w: impl Write) -> Result<()> {
432 for (contract, address) in &self.addresses {
433 writeln!(w, "{contract}={address:#x}")?;
434 }
435 Ok(())
436 }
437}
438
439pub(crate) async fn deploy_light_client_contract(
449 provider: impl Provider,
450 contracts: &mut Contracts,
451 mock: bool,
452) -> Result<Address> {
453 let plonk_verifier_addr = contracts
455 .deploy(
456 Contract::PlonkVerifier,
457 PlonkVerifier::deploy_builder(&provider),
458 )
459 .await?;
460
461 assert!(is_contract(&provider, plonk_verifier_addr).await?);
462
463 let target_lc_bytecode = if mock {
466 LightClientMock::BYTECODE.encode_hex()
467 } else {
468 LightClient::BYTECODE.encode_hex()
469 };
470 let lc_linked_bytecode = {
471 match target_lc_bytecode
472 .matches(LIBRARY_PLACEHOLDER_ADDRESS)
473 .count()
474 {
475 0 => return Err(anyhow!("lib placeholder not found")),
476 1 => Bytes::from_hex(target_lc_bytecode.replacen(
477 LIBRARY_PLACEHOLDER_ADDRESS,
478 &plonk_verifier_addr.encode_hex(),
479 1,
480 ))?,
481 _ => {
482 return Err(anyhow!(
483 "more than one lib placeholder found, consider using a different value"
484 ));
485 },
486 }
487 };
488
489 let light_client_addr = if mock {
491 let addr = LightClientMock::deploy_builder(&provider)
493 .map(|req| req.with_deploy_code(lc_linked_bytecode))
494 .deploy()
495 .await?;
496 tracing::info!("deployed LightClientMock at {addr:#x}");
497 addr
498 } else {
499 contracts
500 .deploy(
501 Contract::LightClient,
502 LightClient::deploy_builder(&provider)
503 .map(|req| req.with_deploy_code(lc_linked_bytecode)),
504 )
505 .await?
506 };
507 Ok(light_client_addr)
508}
509
510pub async fn deploy_light_client_proxy(
516 provider: impl Provider,
517 contracts: &mut Contracts,
518 mock: bool,
519 genesis_state: LightClientStateSol,
520 genesis_stake: StakeTableStateSol,
521 admin: Address,
522 prover: Option<Address>,
523) -> Result<Address> {
524 let impl_addr = deploy_light_client_contract(&provider, contracts, mock).await?;
526 let lc = LightClient::new(impl_addr, &provider);
527
528 let init_data = lc
530 .initialize(
531 genesis_state,
532 genesis_stake,
533 MAX_HISTORY_RETENTION_SECONDS,
534 admin,
535 )
536 .calldata()
537 .to_owned();
538 let lc_proxy_addr = contracts
540 .deploy(
541 Contract::LightClientProxy,
542 ERC1967Proxy::deploy_builder(&provider, impl_addr, init_data),
543 )
544 .await?;
545
546 if !is_proxy_contract(&provider, lc_proxy_addr).await? {
548 panic!("LightClientProxy detected not as a proxy, report error!");
549 }
550
551 let lc_proxy = LightClient::new(lc_proxy_addr, &provider);
553
554 if let Some(prover) = prover {
556 tracing::info!(%lc_proxy_addr, %prover, "Set permissioned prover ");
557 lc_proxy
558 .setPermissionedProver(prover)
559 .send()
560 .await?
561 .get_receipt()
562 .await?;
563 }
564
565 assert_eq!(lc_proxy.getVersion().call().await?.majorVersion, 1);
567 assert_eq!(lc_proxy.owner().call().await?, admin);
568 if let Some(prover) = prover {
569 assert_eq!(lc_proxy.permissionedProver().call().await?, prover);
570 }
571 assert_eq!(lc_proxy.stateHistoryRetentionPeriod().call().await?, 864000);
572 assert_eq!(
573 lc_proxy.currentBlockNumber().call().await?,
574 U256::from(provider.get_block_number().await?)
575 );
576
577 Ok(lc_proxy_addr)
578}
579
580pub async fn upgrade_light_client_v2(
590 provider: impl Provider,
591 contracts: &mut Contracts,
592 is_mock: bool,
593 blocks_per_epoch: u64,
594 epoch_start_block: u64,
595) -> Result<TransactionReceipt> {
596 match contracts.address(Contract::LightClientProxy) {
597 None => Err(anyhow!("LightClientProxy not found, can't upgrade")),
599 Some(proxy_addr) => {
600 let proxy = LightClient::new(proxy_addr, &provider);
601
602 let curr_version = proxy.getVersion().call().await?;
603 if curr_version.majorVersion > 2 {
604 anyhow::bail!(
605 "Expected LightClient V1 or V2 for upgrade to V2, found V{}",
606 curr_version.majorVersion
607 );
608 }
609 if curr_version.majorVersion == 2 {
611 tracing::warn!(
612 "Re-applying LightClient V2 (patch upgrade). This will deploy a fresh \
613 implementation."
614 );
615 }
616 let state_history_retention_period = proxy.stateHistoryRetentionPeriod().call().await?;
617 let pv2_addr = contracts
619 .deploy(
620 Contract::PlonkVerifierV2,
621 PlonkVerifierV2::deploy_builder(&provider),
622 )
623 .await?;
624
625 assert!(is_contract(&provider, pv2_addr).await?);
626
627 let target_lcv2_bytecode = if is_mock {
629 LightClientV2Mock::BYTECODE.encode_hex()
630 } else {
631 LightClientV2::BYTECODE.encode_hex()
632 };
633 let lcv2_linked_bytecode = {
634 match target_lcv2_bytecode
635 .matches(LIBRARY_PLACEHOLDER_ADDRESS)
636 .count()
637 {
638 0 => return Err(anyhow!("lib placeholder not found")),
639 1 => Bytes::from_hex(target_lcv2_bytecode.replacen(
640 LIBRARY_PLACEHOLDER_ADDRESS,
641 &pv2_addr.encode_hex(),
642 1,
643 ))?,
644 _ => {
645 return Err(anyhow!(
646 "more than one lib placeholder found, consider using a different value"
647 ));
648 },
649 }
650 };
651 let lcv2_addr = if is_mock {
652 let addr = LightClientV2Mock::deploy_builder(&provider)
653 .map(|req| req.with_deploy_code(lcv2_linked_bytecode))
654 .deploy()
655 .await?;
656 tracing::info!("deployed LightClientV2Mock at {addr:#x}");
657 addr
658 } else {
659 let is_patch_upgrade = curr_version.majorVersion == 2;
661 if is_patch_upgrade {
662 let cached_lcv2_addr = contracts.address(Contract::LightClientV2);
663 if let Some(addr) = cached_lcv2_addr {
667 anyhow::bail!(
668 "LightClientV2 implementation address is already set in cache \
669 ({:#x}). For patch upgrades, the implementation must be redeployed. \
670 Please unset ESPRESSO_SEQUENCER_LIGHT_CLIENT_V2_ADDRESS or remove it \
671 from the cache first.",
672 addr
673 );
674 }
675 }
676
677 contracts
678 .deploy(
679 Contract::LightClientV2,
680 LightClientV2::deploy_builder(&provider)
681 .map(|req| req.with_deploy_code(lcv2_linked_bytecode)),
682 )
683 .await?
684 };
685
686 let owner = proxy.owner().call().await?;
688 let owner_addr = owner;
689 tracing::info!("Proxy owner: {owner_addr:#x}");
690
691 let lcv2 = LightClientV2::new(lcv2_addr, &provider);
696 let init_data = if already_initialized(&provider, proxy_addr, 2).await? {
697 vec![].into()
698 } else {
699 lcv2.initializeV2(blocks_per_epoch, epoch_start_block)
700 .calldata()
701 .to_owned()
702 };
703 let receipt = proxy
705 .upgradeToAndCall(lcv2_addr, init_data)
706 .send()
707 .await?
708 .get_receipt()
709 .await?;
710 let proxy_as_v2 = LightClientV2::new(proxy_addr, &provider);
711
712 if receipt.inner.is_success() {
713 let is_complete = retry_until_true("LightClientProxy V2 version check", || async {
715 Ok(proxy_as_v2.getVersion().call().await?.majorVersion == 2)
716 })
717 .await?;
718
719 if !is_complete {
720 anyhow::bail!(
721 "LightClientProxy version check failed after retries: expected V2"
722 );
723 }
724
725 assert_eq!(proxy_as_v2.blocksPerEpoch().call().await?, blocks_per_epoch);
727 assert_eq!(
728 proxy_as_v2.epochStartBlock().call().await?,
729 epoch_start_block
730 );
731 assert_eq!(
732 proxy_as_v2.stateHistoryRetentionPeriod().call().await?,
733 state_history_retention_period
734 );
735 assert_eq!(
736 proxy_as_v2.currentBlockNumber().call().await?,
737 U256::from(provider.get_block_number().await?)
738 );
739
740 tracing::info!(%lcv2_addr, "LightClientProxy successfully upgraded to V2");
741 tracing::info!(
742 "blocksPerEpoch: {}",
743 proxy_as_v2.blocksPerEpoch().call().await?
744 );
745 tracing::info!(
746 "epochStartBlock: {}",
747 proxy_as_v2.epochStartBlock().call().await?
748 );
749 } else {
750 tracing::error!("LightClientProxy upgrade failed: {:?}", receipt);
751 }
752
753 Ok(receipt)
754 },
755 }
756}
757
758pub async fn upgrade_light_client_v3(
766 provider: impl Provider,
767 contracts: &mut Contracts,
768 is_mock: bool,
769) -> Result<TransactionReceipt> {
770 match contracts.address(Contract::LightClientProxy) {
771 None => Err(anyhow!("LightClientProxy not found, can't upgrade")),
773 Some(proxy_addr) => {
774 let proxy = LightClient::new(proxy_addr, &provider);
775
776 let version = proxy.getVersion().call().await?;
780 if version.majorVersion < 2 {
781 anyhow::bail!(
782 "LightClientProxy is V{}, can't upgrade to V3. Must upgrade to V2 first.",
783 version.majorVersion
784 );
785 }
786
787 let pv3_addr = contracts
789 .deploy(
790 Contract::PlonkVerifierV3,
791 PlonkVerifierV3::deploy_builder(&provider),
792 )
793 .await?;
794 assert!(is_contract(&provider, pv3_addr).await?);
795
796 let target_lcv3_bytecode = if is_mock {
798 LightClientV3Mock::BYTECODE.encode_hex()
799 } else {
800 LightClientV3::BYTECODE.encode_hex()
801 };
802 let lcv3_linked_bytecode = {
803 match target_lcv3_bytecode
804 .matches(LIBRARY_PLACEHOLDER_ADDRESS)
805 .count()
806 {
807 0 => return Err(anyhow!("lib placeholder not found")),
808 1 => Bytes::from_hex(target_lcv3_bytecode.replacen(
809 LIBRARY_PLACEHOLDER_ADDRESS,
810 &pv3_addr.encode_hex(),
811 1,
812 ))?,
813 _ => {
814 return Err(anyhow!(
815 "more than one lib placeholder found, consider using a different value"
816 ));
817 },
818 }
819 };
820 let lcv3_addr = if is_mock {
821 let addr = LightClientV3Mock::deploy_builder(&provider)
822 .map(|req| req.with_deploy_code(lcv3_linked_bytecode))
823 .deploy()
824 .await?;
825 tracing::info!("deployed LightClientV3Mock at {addr:#x}");
826 addr
827 } else {
828 contracts
829 .deploy(
830 Contract::LightClientV3,
831 LightClientV3::deploy_builder(&provider)
832 .map(|req| req.with_deploy_code(lcv3_linked_bytecode)),
833 )
834 .await?
835 };
836
837 let owner = proxy.owner().call().await?;
839 let owner_addr = owner;
840 tracing::info!("Proxy owner: {owner_addr:#x}");
841
842 let lcv3 = LightClientV3::new(lcv3_addr, &provider);
843
844 let init_data = if already_initialized(&provider, proxy_addr, 3).await? {
849 vec![].into()
850 } else {
851 lcv3.initializeV3().calldata().to_owned()
852 };
853
854 let receipt = proxy
856 .upgradeToAndCall(lcv3_addr, init_data)
857 .send()
858 .await?
859 .get_receipt()
860 .await?;
861
862 let proxy_as_v3 = LightClientV3::new(proxy_addr, &provider);
863
864 if receipt.inner.is_success() {
865 let version_is_v3 =
867 retry_until_true("LightClientProxy V3 version check", || async {
868 Ok(proxy_as_v3.getVersion().call().await?.majorVersion == 3)
869 })
870 .await?;
871
872 if !version_is_v3 {
873 anyhow::bail!(
874 "LightClientProxy version check failed after retries: expected V3"
875 );
876 }
877
878 tracing::info!(%lcv3_addr, "LightClientProxy successfully upgraded to V3");
879 } else {
880 tracing::error!("LightClientProxy upgrade failed: {:?}", receipt);
881 }
882
883 Ok(receipt)
884 },
885 }
886}
887
888async fn already_initialized(
889 provider: impl Provider,
890 proxy_addr: Address,
891 expected_major_version: u8,
892) -> Result<bool> {
893 let initialized = get_proxy_initialized_version(&provider, proxy_addr).await?;
894 tracing::info!("Initialized version: {}", initialized);
895
896 let contract_proxy = LightClientV2::new(proxy_addr, &provider);
898 let contract_major_version = contract_proxy.getVersion().call().await?.majorVersion;
899
900 Ok(initialized == contract_major_version && contract_major_version == expected_major_version)
901}
902
903pub async fn deploy_fee_contract_proxy(
910 provider: impl Provider,
911 contracts: &mut Contracts,
912 admin: Address,
913) -> Result<Address> {
914 let fee_addr = contracts
916 .deploy(
917 Contract::FeeContract,
918 FeeContract::deploy_builder(&provider),
919 )
920 .await?;
921 let fee = FeeContract::new(fee_addr, &provider);
922
923 let init_data = fee.initialize(admin).calldata().to_owned();
925 let fee_proxy_addr = contracts
927 .deploy(
928 Contract::FeeContractProxy,
929 ERC1967Proxy::deploy_builder(&provider, fee_addr, init_data),
930 )
931 .await?;
932 if !is_proxy_contract(&provider, fee_proxy_addr).await? {
934 panic!("FeeContractProxy detected not as a proxy, report error!");
935 }
936
937 let fee_proxy = FeeContract::new(fee_proxy_addr, &provider);
939 assert_eq!(fee_proxy.getVersion().call().await?.majorVersion, 1);
940 assert_eq!(fee_proxy.owner().call().await?, admin);
941
942 Ok(fee_proxy_addr)
943}
944
945pub async fn deploy_token_proxy(
947 provider: impl Provider,
948 contracts: &mut Contracts,
949 owner: Address,
950 init_grant_recipient: Address,
951 initial_supply: U256,
952 name: &str,
953 symbol: &str,
954) -> Result<Address> {
955 let token_addr = contracts
956 .deploy(Contract::EspToken, EspToken::deploy_builder(&provider))
957 .await?;
958 let token = EspToken::new(token_addr, &provider);
959
960 let init_data = token
961 .initialize(
962 owner,
963 init_grant_recipient,
964 initial_supply,
965 name.to_string(),
966 symbol.to_string(),
967 )
968 .calldata()
969 .to_owned();
970
971 let token_proxy_addr = contracts
972 .deploy(
973 Contract::EspTokenProxy,
974 ERC1967Proxy::deploy_builder(&provider, token_addr, init_data),
975 )
976 .await?;
977
978 if !is_proxy_contract(&provider, token_proxy_addr).await? {
979 panic!("EspTokenProxy detected not as a proxy, report error!");
980 }
981
982 let token_proxy = EspToken::new(token_proxy_addr, &provider);
984 assert_eq!(token_proxy.getVersion().call().await?.majorVersion, 1);
985 assert_eq!(token_proxy.owner().call().await?, owner);
986 assert_eq!(token_proxy.symbol().call().await?, symbol);
987 assert_eq!(token_proxy.decimals().call().await?, 18);
988 assert_eq!(token_proxy.name().call().await?, name);
989 let total_supply = token_proxy.totalSupply().call().await?;
990 assert_eq!(
991 token_proxy.balanceOf(init_grant_recipient).call().await?,
992 total_supply
993 );
994
995 Ok(token_proxy_addr)
996}
997
998pub async fn upgrade_esp_token_v2(
1000 provider: impl Provider,
1001 contracts: &mut Contracts,
1002) -> Result<TransactionReceipt> {
1003 let Some(proxy_addr) = contracts.address(Contract::EspTokenProxy) else {
1004 anyhow::bail!("EspTokenProxy not found, can't upgrade")
1005 };
1006
1007 let proxy = EspToken::new(proxy_addr, &provider);
1008 let v2_addr = contracts
1010 .deploy(Contract::EspTokenV2, EspTokenV2::deploy_builder(&provider))
1011 .await?;
1012
1013 assert!(is_contract(&provider, v2_addr).await?);
1014
1015 let reward_claim_addr = contracts
1017 .address(Contract::RewardClaimProxy)
1018 .ok_or_else(|| anyhow!("RewardClaimProxy not found"))?;
1019 let proxy_as_v2 = EspTokenV2::new(proxy_addr, &provider);
1020 let init_data = proxy_as_v2
1021 .initializeV2(reward_claim_addr)
1022 .calldata()
1023 .to_owned();
1024
1025 let receipt = proxy
1027 .upgradeToAndCall(v2_addr, init_data)
1028 .send()
1029 .await?
1030 .get_receipt()
1031 .await?;
1032
1033 if receipt.inner.is_success() {
1034 let version_is_v2 = retry_until_true("EspTokenProxy V2 version check", || async {
1036 Ok(proxy_as_v2.getVersion().call().await?.majorVersion == 2)
1037 })
1038 .await?;
1039
1040 if !version_is_v2 {
1041 anyhow::bail!("EspTokenProxy version check failed after retries: expected V2");
1042 }
1043
1044 assert_eq!(proxy_as_v2.name().call().await?, "Espresso");
1046 assert_eq!(proxy_as_v2.rewardClaim().call().await?, reward_claim_addr);
1047 tracing::info!(%v2_addr, "EspToken successfully upgraded to");
1048 } else {
1049 anyhow::bail!("EspToken upgrade failed: {:?}", receipt);
1050 }
1051
1052 Ok(receipt)
1053}
1054
1055pub async fn deploy_stake_table_proxy(
1057 provider: impl Provider,
1058 contracts: &mut Contracts,
1059 token_addr: Address,
1060 light_client_addr: Address,
1061 exit_escrow_period: U256,
1062 owner: Address,
1063) -> Result<Address> {
1064 let stake_table_addr = contracts
1065 .deploy(Contract::StakeTable, StakeTable::deploy_builder(&provider))
1066 .await?;
1067 let stake_table = StakeTable::new(stake_table_addr, &provider);
1068
1069 if !is_contract(&provider, token_addr).await? {
1075 anyhow::bail!("Token address is not a contract, can't deploy StakeTableProxy");
1076 }
1077
1078 let init_data = stake_table
1079 .initialize(token_addr, light_client_addr, exit_escrow_period, owner)
1080 .calldata()
1081 .to_owned();
1082
1083 let st_proxy_addr = contracts
1084 .deploy(
1085 Contract::StakeTableProxy,
1086 ERC1967Proxy::deploy_builder(&provider, stake_table_addr, init_data),
1087 )
1088 .await?;
1089
1090 if !is_proxy_contract(&provider, st_proxy_addr).await? {
1091 panic!("StakeTableProxy detected not as a proxy, report error!");
1092 }
1093
1094 let st_proxy = StakeTable::new(st_proxy_addr, &provider);
1095 assert_eq!(st_proxy.getVersion().call().await?.majorVersion, 1);
1096 assert_eq!(st_proxy.owner().call().await?, owner);
1097 assert_eq!(st_proxy.token().call().await?, token_addr);
1098 assert_eq!(st_proxy.lightClient().call().await?, light_client_addr);
1099 assert_eq!(
1100 st_proxy.exitEscrowPeriod().call().await?,
1101 exit_escrow_period
1102 );
1103
1104 Ok(st_proxy_addr)
1105}
1106
1107pub async fn deploy_reward_claim_proxy(
1109 provider: impl Provider,
1110 contracts: &mut Contracts,
1111 esp_token_addr: Address,
1112 light_client_addr: Address,
1113 admin: Address,
1114 pauser: Address,
1115) -> Result<Address> {
1116 let reward_claim_addr = contracts
1117 .deploy(
1118 Contract::RewardClaim,
1119 RewardClaim::deploy_builder(&provider),
1120 )
1121 .await?;
1122 let reward_claim = RewardClaim::new(reward_claim_addr, &provider);
1123
1124 if !is_contract(&provider, esp_token_addr).await? {
1126 anyhow::bail!("EspToken address is not a contract, can't deploy RewardClaimProxy");
1127 }
1128
1129 if !is_contract(&provider, light_client_addr).await? {
1131 anyhow::bail!("LightClient address is not a contract, can't deploy RewardClaimProxy");
1132 }
1133
1134 let init_data = reward_claim
1135 .initialize(admin, esp_token_addr, light_client_addr, pauser)
1136 .calldata()
1137 .to_owned();
1138 let reward_claim_proxy_addr = contracts
1139 .deploy(
1140 Contract::RewardClaimProxy,
1141 ERC1967Proxy::deploy_builder(&provider, reward_claim_addr, init_data),
1142 )
1143 .await?;
1144
1145 if !is_proxy_contract(&provider, reward_claim_proxy_addr).await? {
1146 panic!("RewardClaimProxy detected not as a proxy, report error!");
1147 }
1148
1149 let reward_claim_proxy = RewardClaim::new(reward_claim_proxy_addr, &provider);
1150 assert_eq!(
1151 reward_claim_proxy.getVersion().call().await?,
1152 (1, 0, 0).into()
1153 );
1154 let admin_role = reward_claim_proxy.DEFAULT_ADMIN_ROLE().call().await?;
1156 assert!(
1157 reward_claim_proxy.hasRole(admin_role, admin).call().await?,
1158 "admin should have DEFAULT_ADMIN_ROLE"
1159 );
1160 assert_eq!(reward_claim_proxy.espToken().call().await?, esp_token_addr);
1161 assert_eq!(
1162 reward_claim_proxy.lightClient().call().await?,
1163 light_client_addr
1164 );
1165
1166 Ok(reward_claim_proxy_addr)
1167}
1168
1169pub async fn fetch_stake_table_for_stake_table_storage_migration(
1174 l1_client: L1Client,
1175 stake_table_address: Address,
1176) -> Result<(U256, Vec<StakeTableV2::InitialCommission>)> {
1177 let stake_table = StakeTable::new(stake_table_address, &l1_client);
1178
1179 let version = stake_table.getVersion().call().await?;
1181 if version.majorVersion != 1 {
1182 anyhow::bail!(
1183 "Expected StakeTable V1 for migration, found V{}",
1184 version.majorVersion
1185 );
1186 }
1187
1188 tracing::info!("Fetching all validators from StakeTable V1 contract");
1189
1190 let latest_block = l1_client.provider.get_block_number().await?;
1192
1193 let (validators, _stake_table_hash) =
1195 Fetcher::fetch_all_validators_from_contract(l1_client, stake_table_address, latest_block)
1196 .await
1197 .context("Failed to fetch validators from V1 contract")?;
1198
1199 let active_stake = validators.values().map(|v| v.stake).sum::<U256>();
1201
1202 let commissions = validators
1204 .values()
1205 .map(|v| StakeTableV2::InitialCommission {
1206 validator: v.account,
1207 commission: v.commission,
1208 })
1209 .collect::<Vec<_>>();
1210
1211 tracing::info!(
1212 "Found {} active stake and {} commissions from {} validators to migrate from V1",
1213 active_stake,
1214 commissions.len(),
1215 validators.len()
1216 );
1217
1218 Ok((active_stake, commissions))
1219}
1220
1221pub async fn prepare_stake_table_v2_upgrade(
1227 l1_client: L1Client,
1228 proxy_addr: Address,
1229 pauser: Address,
1230 admin: Address,
1231) -> Result<(
1232 Option<Vec<StakeTableV2::InitialCommission>>,
1233 Option<U256>,
1234 Option<Bytes>,
1235)> {
1236 let proxy = StakeTable::new(proxy_addr, &l1_client);
1237
1238 let current_version = proxy.getVersion().call().await?;
1239 let target_version = 2;
1240 if current_version.majorVersion > target_version {
1241 anyhow::bail!(
1242 "Expected StakeTable V1 or V2, found V{}",
1243 current_version.majorVersion
1244 );
1245 }
1246
1247 let needs_initialization =
1249 !already_initialized(&l1_client.provider, proxy_addr, target_version).await?;
1250 assert_eq!(
1251 needs_initialization,
1252 current_version.majorVersion < target_version,
1253 "unexpected version initialized"
1254 );
1255
1256 if needs_initialization {
1257 tracing::info!("Fetching stake table data from V1 contract for migration");
1258 let (active_stake, commissions) =
1259 fetch_stake_table_for_stake_table_storage_migration(l1_client.clone(), proxy_addr)
1260 .await?;
1261
1262 tracing::info!(
1263 %pauser,
1264 %admin,
1265 active_stake = %format!("{:?} wei", active_stake),
1266 commission_count = commissions.len(),
1267 "Init Data to be signed. Function: initializeV2",
1268 );
1269
1270 let data = StakeTableV2::new(Address::ZERO, &l1_client)
1272 .initializeV2(pauser, admin, active_stake, commissions.clone())
1273 .calldata()
1274 .to_owned();
1275
1276 Ok((Some(commissions), Some(active_stake), Some(data)))
1277 } else {
1278 tracing::info!(
1279 "Proxy was already initialized for version {}",
1280 target_version
1281 );
1282 Ok((None, None, None))
1283 }
1284}
1285
1286pub async fn upgrade_stake_table_v2(
1288 provider: impl Provider,
1289 l1_client: L1Client,
1290 contracts: &mut Contracts,
1291 pauser: Address,
1292 admin: Address,
1293) -> Result<TransactionReceipt> {
1294 tracing::info!("Upgrading StakeTableProxy to StakeTableV2 with EOA admin");
1295 let Some(proxy_addr) = contracts.address(Contract::StakeTableProxy) else {
1296 anyhow::bail!("StakeTableProxy not found, can't upgrade")
1297 };
1298
1299 let (init_commissions, init_active_stake, init_data) =
1301 prepare_stake_table_v2_upgrade(l1_client.clone(), proxy_addr, pauser, admin).await?;
1302
1303 let v2_addr = contracts
1305 .deploy(
1306 Contract::StakeTableV2,
1307 StakeTableV2::deploy_builder(&provider),
1308 )
1309 .await?;
1310
1311 let proxy = StakeTable::new(proxy_addr, &provider);
1312
1313 let receipt = proxy
1314 .upgradeToAndCall(v2_addr, init_data.unwrap_or_default())
1315 .send()
1316 .await?
1317 .get_receipt()
1318 .await?;
1319
1320 let proxy_as_v2 = StakeTableV2::new(proxy_addr, &provider);
1321
1322 if receipt.inner.is_success() {
1323 let version_is_v2 = retry_until_true("StakeTableProxy V2 version check", || async {
1326 Ok(proxy_as_v2.getVersion().call().await?.majorVersion == 2)
1327 })
1328 .await?;
1329 if !version_is_v2 {
1330 anyhow::bail!("StakeTableProxy version check failed after retries: expected V2");
1331 }
1332
1333 let pauser_role = proxy_as_v2.PAUSER_ROLE().call().await?;
1335 assert!(
1336 proxy_as_v2.hasRole(pauser_role, pauser).call().await?,
1337 "pauser should have PAUSER_ROLE"
1338 );
1339
1340 let admin_role = proxy_as_v2.DEFAULT_ADMIN_ROLE().call().await?;
1341 assert!(proxy_as_v2.hasRole(admin_role, admin).call().await?,);
1342
1343 if let Some(migrated) = init_commissions {
1344 tracing::info!("Verifying migrated commissions, may take a minute");
1345 for init_comm in migrated {
1346 let tracking = proxy_as_v2
1347 .commissionTracking(init_comm.validator)
1348 .call()
1349 .await?;
1350 assert_eq!(tracking.commission, init_comm.commission);
1351 }
1352 }
1353
1354 tracing::info!("Verifying migrated active stake");
1355 assert_eq!(
1356 proxy_as_v2.activeStake().call().await?,
1357 init_active_stake.unwrap_or_default(),
1358 "migrated active stake does not match"
1359 );
1360
1361 tracing::info!(%v2_addr, "StakeTable successfully upgraded to");
1362 } else {
1363 anyhow::bail!("StakeTable upgrade failed: {:?}", receipt);
1364 }
1365
1366 Ok(receipt)
1367}
1368
1369pub async fn upgrade_fee_v1(
1372 provider: impl Provider,
1373 contracts: &mut Contracts,
1374) -> Result<TransactionReceipt> {
1375 tracing::info!("Upgrading FeeContract to FeeContractV1.0.1 with EOA admin");
1376 let Some(fee_contract_proxy_addr) = contracts.address(Contract::FeeContractProxy) else {
1377 anyhow::bail!("FeeContractProxy not found, can't upgrade")
1378 };
1379
1380 let fee_contract_proxy = FeeContract::new(fee_contract_proxy_addr, &provider);
1381
1382 let owner = fee_contract_proxy.owner().call().await?;
1383 if is_contract(&provider, owner).await? {
1384 anyhow::bail!(
1385 "FeeContract owner ({:#x}) is not an EOA, can't upgrade",
1386 owner
1387 );
1388 }
1389
1390 let curr_version = fee_contract_proxy.getVersion().call().await?;
1391 if curr_version.majorVersion != 1 {
1392 anyhow::bail!(
1393 "Expected FeeContract V1 for upgrade, found V{}.{}.{}",
1394 curr_version.majorVersion,
1395 curr_version.minorVersion,
1396 curr_version.patchVersion
1397 );
1398 }
1399
1400 let cached_fee_contract_addr = contracts.address(Contract::FeeContract);
1401
1402 if let Some(cached_fee_contract_addr) = cached_fee_contract_addr {
1406 anyhow::bail!(
1407 "FeeContract implementation address is already set in cache ({:#x}). For patch \
1408 upgrades, the implementation must be redeployed. Please unset \
1409 ESPRESSO_FEE_CONTRACT_ADDRESS or remove it from the cache first.",
1410 cached_fee_contract_addr
1411 );
1412 }
1413
1414 let new_fee_contract_addr = contracts
1416 .deploy(
1417 Contract::FeeContract,
1418 FeeContract::deploy_builder(&provider),
1419 )
1420 .await?;
1421
1422 let receipt = fee_contract_proxy
1423 .upgradeToAndCall(new_fee_contract_addr, vec![].into())
1424 .send()
1425 .await?
1426 .get_receipt()
1427 .await
1428 .context("Failed to get upgrade transaction receipt")?;
1429
1430 if receipt.inner.is_success() {
1431 let new_version = fee_contract_proxy.getVersion().call().await?;
1432 if new_version != (1, 0, 1).into() {
1433 anyhow::bail!(
1434 "Upgrade transaction succeeded but version is incorrect: V{}.{}.{} (expected \
1435 V1.0.1). Proxy: {fee_contract_proxy_addr:#x}, New impl: \
1436 {new_fee_contract_addr:#x}",
1437 new_version.majorVersion,
1438 new_version.minorVersion,
1439 new_version.patchVersion
1440 );
1441 }
1442 tracing::info!(
1443 proxy = %fee_contract_proxy_addr,
1444 impl = %new_fee_contract_addr,
1445 "FeeContract successfully upgraded to v1.0.1"
1446 );
1447 } else {
1448 anyhow::bail!("FeeContract upgrade failed: {:?}", receipt);
1449 }
1450
1451 Ok(receipt)
1452}
1453
1454pub async fn transfer_ownership(
1456 provider: impl Provider,
1457 target_contract: Contract,
1458 target_address: Address,
1459 new_owner: Address,
1460) -> Result<TransactionReceipt> {
1461 let ownable = OwnableUpgradeable::new(target_address, &provider);
1464
1465 let current_owner = ownable.owner().call().await.context(format!(
1467 "Contract at {target_address:#x} does not implement Ownable interface"
1468 ))?;
1469
1470 tracing::info!(%target_contract, %target_address, current_owner = %current_owner, new_owner = %new_owner, "Transferring ownership of {target_contract}");
1471
1472 let receipt = ownable
1473 .transferOwnership(new_owner)
1474 .send()
1475 .await?
1476 .get_receipt()
1477 .await?;
1478
1479 let tx_hash = receipt.transaction_hash;
1480 tracing::info!(%receipt.gas_used, %tx_hash, "ownership transferred");
1481 Ok(receipt)
1482}
1483
1484pub async fn grant_admin_role(
1487 provider: impl Provider,
1488 target_contract: Contract,
1489 target_address: Address,
1490 new_admin: Address,
1491) -> Result<TransactionReceipt> {
1492 let access_control = AccessControlUpgradeable::new(target_address, &provider);
1494
1495 let admin_role = access_control
1497 .DEFAULT_ADMIN_ROLE()
1498 .call()
1499 .await
1500 .context(format!(
1501 "Contract at {target_address:#x} does not implement AccessControl interface"
1502 ))?;
1503
1504 let already_has_role = access_control.hasRole(admin_role, new_admin).call().await?;
1506
1507 tracing::info!(
1508 %target_contract,
1509 %target_address,
1510 new_admin = %new_admin,
1511 already_has_role = %already_has_role,
1512 "Granting DEFAULT_ADMIN_ROLE for {target_contract}"
1513 );
1514
1515 let receipt = access_control
1517 .grantRole(admin_role, new_admin)
1518 .send()
1519 .await?
1520 .get_receipt()
1521 .await?;
1522
1523 let tx_hash = receipt.transaction_hash;
1524 tracing::info!(%receipt.gas_used, %tx_hash, "admin role granted");
1525 Ok(receipt)
1526}
1527
1528pub async fn is_proxy_contract(provider: impl Provider, addr: Address) -> Result<bool> {
1530 Ok(read_proxy_impl(provider, addr).await? != Address::default())
1532}
1533
1534pub async fn read_proxy_impl(provider: impl Provider, addr: Address) -> Result<Address> {
1535 let impl_slot = U256::from_str_radix(
1538 "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
1539 16,
1540 )?;
1541
1542 let is_readable = retry_until_true("Read proxy implementation", || async {
1544 match provider.get_storage_at(addr, impl_slot).await {
1545 Ok(_) => {
1546 Ok(true)
1548 },
1549 Err(e) => {
1550 tracing::debug!("Storage read failed (will retry): {}", e);
1551 Ok(false)
1552 },
1553 }
1554 })
1555 .await?;
1556
1557 if !is_readable {
1558 anyhow::bail!(
1559 "Proxy implementation storage is not readable after retries at address {addr:#x}"
1560 );
1561 }
1562
1563 let storage = provider.get_storage_at(addr, impl_slot).await?;
1565 let impl_addr = Address::from_slice(&storage.to_be_bytes_vec()[12..]);
1566
1567 Ok(impl_addr)
1568}
1569
1570pub async fn is_contract(provider: impl Provider, address: Address) -> Result<bool> {
1571 if address == Address::ZERO {
1572 return Ok(false);
1573 }
1574 Ok(!provider.get_code_at(address).await?.is_empty())
1575}
1576
1577pub async fn get_proxy_initialized_version(
1578 provider: impl Provider,
1579 proxy_addr: Address,
1580) -> Result<u8> {
1581 let slot: B256 = "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00"
1583 .parse()
1584 .context("Failed to parse OpenZeppelin Initializable slot")?;
1585 let value = provider.get_storage_at(proxy_addr, slot.into()).await?;
1586 let initialized = value.as_le_bytes()[0]; Ok(initialized)
1588}
1589
1590pub async fn deploy_ops_timelock(
1598 provider: impl Provider,
1599 contracts: &mut Contracts,
1600 min_delay: U256,
1601 proposers: Vec<Address>,
1602 executors: Vec<Address>,
1603 admin: Address,
1604) -> Result<Address> {
1605 tracing::info!(
1606 "OpsTimelock will be deployed with the following parameters: min_delay: {:?}, proposers: \
1607 {:?}, executors: {:?}, admin: {:?}",
1608 min_delay,
1609 proposers,
1610 executors,
1611 admin
1612 );
1613 let timelock_addr = contracts
1614 .deploy(
1615 Contract::OpsTimelock,
1616 OpsTimelock::deploy_builder(
1617 &provider,
1618 min_delay,
1619 proposers.clone(),
1620 executors.clone(),
1621 admin,
1622 ),
1623 )
1624 .await?;
1625
1626 let timelock = OpsTimelock::new(timelock_addr, &provider);
1628
1629 assert_eq!(timelock.getMinDelay().call().await?, min_delay);
1631 assert!(
1632 timelock
1633 .hasRole(timelock.PROPOSER_ROLE().call().await?, proposers[0])
1634 .call()
1635 .await?
1636 );
1637 assert!(
1638 timelock
1639 .hasRole(timelock.EXECUTOR_ROLE().call().await?, executors[0])
1640 .call()
1641 .await?
1642 );
1643
1644 let default_admin_role = U256::ZERO;
1646 assert!(
1647 timelock
1648 .hasRole(default_admin_role.into(), admin)
1649 .call()
1650 .await?
1651 );
1652
1653 Ok(timelock_addr)
1654}
1655
1656pub async fn deploy_safe_exit_timelock(
1664 provider: impl Provider,
1665 contracts: &mut Contracts,
1666 min_delay: U256,
1667 proposers: Vec<Address>,
1668 executors: Vec<Address>,
1669 admin: Address,
1670) -> Result<Address> {
1671 tracing::info!(
1672 "SafeExitTimelock will be deployed with the following parameters: min_delay: {:?}, \
1673 proposers: {:?}, executors: {:?}, admin: {:?}",
1674 min_delay,
1675 proposers,
1676 executors,
1677 admin
1678 );
1679 let timelock_addr = contracts
1680 .deploy(
1681 Contract::SafeExitTimelock,
1682 SafeExitTimelock::deploy_builder(
1683 &provider,
1684 min_delay,
1685 proposers.clone(),
1686 executors.clone(),
1687 admin,
1688 ),
1689 )
1690 .await?;
1691
1692 let timelock = SafeExitTimelock::new(timelock_addr, &provider);
1694
1695 assert_eq!(timelock.getMinDelay().call().await?, min_delay);
1697 assert!(
1698 timelock
1699 .hasRole(timelock.PROPOSER_ROLE().call().await?, proposers[0])
1700 .call()
1701 .await?
1702 );
1703 assert!(
1704 timelock
1705 .hasRole(timelock.EXECUTOR_ROLE().call().await?, executors[0])
1706 .call()
1707 .await?
1708 );
1709
1710 let default_admin_role = U256::ZERO;
1712 assert!(
1713 timelock
1714 .hasRole(default_admin_role.into(), admin)
1715 .call()
1716 .await?
1717 );
1718
1719 Ok(timelock_addr)
1720}
1721
1722pub fn encode_function_call(signature: &str, args: Vec<String>) -> Result<Bytes> {
1731 let func = Function::parse(signature)?;
1732
1733 if args.len() != func.inputs.len() {
1735 anyhow::bail!(
1736 "Mismatch between argument count ({}) and parameter count ({})",
1737 args.len(),
1738 func.inputs.len()
1739 );
1740 }
1741
1742 let arg_values: Vec<DynSolValue> =
1744 func.inputs
1745 .iter()
1746 .enumerate()
1747 .map(|(i, param)| {
1748 let arg_str = &args[i];
1749 let dyn_type: DynSolType =
1750 param.ty.to_string().parse().map_err(|e| {
1751 anyhow!("Failed to parse parameter type '{}': {}", param.ty, e)
1752 })?;
1753 dyn_type.coerce_str(arg_str).map_err(|e| {
1754 anyhow!(
1755 "Failed to coerce argument '{}' to type '{}': {}",
1756 arg_str,
1757 param.ty,
1758 e
1759 )
1760 })
1761 })
1762 .collect::<Result<Vec<_>>>()?;
1763
1764 let encoded_input = func.abi_encode_input(&arg_values)?;
1765 let data = Bytes::from(encoded_input);
1766 Ok(data)
1767}
1768
1769pub async fn retry_until_true<F, Fut>(check_name: &str, mut check_fn: F) -> Result<bool>
1778where
1779 F: FnMut() -> Fut,
1780 Fut: std::future::Future<Output = Result<bool>>,
1781{
1782 for attempt in 0..MAX_RETRY_ATTEMPTS {
1783 match check_fn().await {
1784 Ok(true) => return Ok(true),
1785 Ok(false) | Err(_) if attempt < MAX_RETRY_ATTEMPTS - 1 => {
1786 let delay_ms = RETRY_INITIAL_DELAY_MS * (1 << attempt);
1787 tracing::warn!("{} not ready, retrying in {}ms...", check_name, delay_ms);
1788 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
1789 },
1790 Ok(false) => {
1791 tracing::error!(
1792 "{} not ready after {} attempts, returning false",
1793 check_name,
1794 MAX_RETRY_ATTEMPTS
1795 );
1796 return Ok(false);
1797 },
1798 Err(e) => {
1799 tracing::error!(
1800 "{} not ready after {} attempts, (treating as not ready): {e:#}",
1801 check_name,
1802 MAX_RETRY_ATTEMPTS
1803 );
1804 return Ok(false);
1805 },
1806 }
1807 }
1808 Ok(false)
1810}
1811
1812#[cfg(test)]
1813mod tests {
1814 use std::sync::Arc;
1815
1816 use alloy::{
1817 node_bindings::{Anvil, AnvilInstance},
1818 primitives::utils::parse_ether,
1819 providers::{ProviderBuilder, ext::AnvilApi, layers::AnvilProvider},
1820 sol_types::SolValue,
1821 };
1822 use espresso_types::testing::TestValidator;
1823 use hotshot_contract_adapter::sol_types::{FeeContract, StakeTableV2};
1824
1825 use super::*;
1826 use crate::{
1827 Contracts,
1828 builder::DeployerArgsBuilder,
1829 impersonate_filler::ImpersonateFiller,
1830 proposals::{
1831 multisig::{
1832 LightClientV2UpgradeParams, MultisigOwnerCheck, StakeTableV2UpgradeParams,
1833 TransferOwnershipParams, transfer_ownership_from_multisig_to_timelock,
1834 upgrade_esp_token_v2_multisig_owner, upgrade_fee_contract_multisig_owner,
1835 upgrade_light_client_v2_multisig_owner, upgrade_stake_table_v2_multisig_owner,
1836 },
1837 timelock::{
1838 TimelockOperationParams, TimelockOperationPayload, TimelockOperationType,
1839 derive_timelock_address_from_contract_type, perform_timelock_operation,
1840 },
1841 },
1842 };
1843
1844 trait ProviderBuilderExt: Sized {
1845 fn connect_anvil_with_l1_client(
1846 self,
1847 ) -> Result<(AnvilInstance, HttpProviderWithWallet, L1Client)> {
1848 let anvil = Anvil::new().spawn();
1849 let wallet = anvil.wallet().unwrap();
1850 let provider = ProviderBuilder::new()
1851 .wallet(wallet)
1852 .connect_http(anvil.endpoint_url());
1853 let l1_client = L1Client::anvil(&anvil)?;
1854 Ok((anvil, provider, l1_client))
1855 }
1856 }
1857
1858 impl<L, F, N> ProviderBuilderExt for ProviderBuilder<L, F, N> {}
1859
1860 #[test_log::test(tokio::test)]
1861 async fn test_is_contract() -> Result<(), anyhow::Error> {
1862 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
1863
1864 let zero_address = Address::ZERO;
1866 assert!(!is_contract(&provider, zero_address).await?);
1867
1868 let random_address = Address::random();
1870 assert!(!is_contract(&provider, random_address).await?);
1871
1872 let fee_contract = FeeContract::deploy(&provider).await?;
1874 let contract_address = *fee_contract.address();
1875 assert!(is_contract(&provider, contract_address).await?);
1876
1877 Ok(())
1878 }
1879
1880 #[test_log::test(tokio::test)]
1881 async fn test_is_proxy_contract() -> Result<()> {
1882 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
1883 let deployer = provider.get_accounts().await?[0];
1884
1885 let fee_contract = FeeContract::deploy(&provider).await?;
1886 let init_data = fee_contract.initialize(deployer).calldata().clone();
1887 let proxy =
1888 ERC1967Proxy::deploy(&provider, *fee_contract.address(), init_data.clone()).await?;
1889
1890 assert!(is_proxy_contract(&provider, *proxy.address()).await?);
1891 assert!(!is_proxy_contract(&provider, *fee_contract.address()).await?);
1892 Ok(())
1893 }
1894
1895 #[test_log::test(tokio::test)]
1896 async fn test_deploy_light_client() -> Result<()> {
1897 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
1898 let mut contracts = Contracts::new();
1899
1900 let mock_lc_addr = deploy_light_client_contract(&provider, &mut contracts, true).await?;
1902 let pv_addr = contracts.address(Contract::PlonkVerifier).unwrap();
1903
1904 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
1906 assert_ne!(mock_lc_addr, lc_addr);
1907 assert_eq!(contracts.address(Contract::PlonkVerifier).unwrap(), pv_addr);
1909 Ok(())
1910 }
1911
1912 #[test_log::test(tokio::test)]
1913 async fn test_deploy_mock_light_client_proxy() -> Result<()> {
1914 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
1915 let mut contracts = Contracts::new();
1916
1917 let genesis_state = LightClientStateSol::dummy_genesis();
1919 let genesis_stake = StakeTableStateSol::dummy_genesis();
1920 let admin = provider.get_accounts().await?[0];
1921 let prover = admin;
1922
1923 let lc_proxy_addr = deploy_light_client_proxy(
1924 &provider,
1925 &mut contracts,
1926 true, genesis_state.clone(),
1928 genesis_stake.clone(),
1929 admin,
1930 Some(prover),
1931 )
1932 .await?;
1933
1934 let lc = LightClientMock::new(lc_proxy_addr, &provider);
1936 let finalized_state: LightClientStateSol = lc.finalizedState().call().await?.into();
1937 assert_eq!(
1938 genesis_state.abi_encode_params(),
1939 finalized_state.abi_encode_params()
1940 );
1941 let new_state = LightClientStateSol {
1943 viewNum: 10,
1944 blockHeight: 10,
1945 blockCommRoot: U256::from(42),
1946 };
1947 lc.setFinalizedState(new_state.clone().into())
1948 .send()
1949 .await?
1950 .watch()
1951 .await?;
1952 let finalized_state: LightClientStateSol = lc.finalizedState().call().await?.into();
1953 assert_eq!(
1954 new_state.abi_encode_params(),
1955 finalized_state.abi_encode_params()
1956 );
1957
1958 Ok(())
1959 }
1960
1961 #[test_log::test(tokio::test)]
1962 async fn test_deploy_light_client_proxy() -> Result<()> {
1963 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
1964 let mut contracts = Contracts::new();
1965
1966 let genesis_state = LightClientStateSol::dummy_genesis();
1968 let genesis_stake = StakeTableStateSol::dummy_genesis();
1969 let admin = provider.get_accounts().await?[0];
1970 let prover = Address::random();
1971
1972 let lc_proxy_addr = deploy_light_client_proxy(
1973 &provider,
1974 &mut contracts,
1975 false,
1976 genesis_state.clone(),
1977 genesis_stake.clone(),
1978 admin,
1979 Some(prover),
1980 )
1981 .await?;
1982
1983 let lc = LightClient::new(lc_proxy_addr, &provider);
1985 let finalized_state = lc.finalizedState().call().await?;
1986 assert_eq!(finalized_state.viewNum, genesis_state.viewNum);
1987 assert_eq!(finalized_state.blockHeight, genesis_state.blockHeight);
1988 assert_eq!(&finalized_state.blockCommRoot, &genesis_state.blockCommRoot);
1989
1990 let fetched_stake = lc.genesisStakeTableState().call().await?;
1991 assert_eq!(fetched_stake.blsKeyComm, genesis_stake.blsKeyComm);
1992 assert_eq!(fetched_stake.schnorrKeyComm, genesis_stake.schnorrKeyComm);
1993 assert_eq!(fetched_stake.amountComm, genesis_stake.amountComm);
1994 assert_eq!(fetched_stake.threshold, genesis_stake.threshold);
1995
1996 let fetched_prover = lc.permissionedProver().call().await?;
1997 assert_eq!(fetched_prover, prover);
1998
1999 let multisig = Address::random();
2001 let _receipt = transfer_ownership(
2002 &provider,
2003 Contract::LightClientProxy,
2004 lc_proxy_addr,
2005 multisig,
2006 )
2007 .await?;
2008 assert_eq!(lc.owner().call().await?, multisig);
2009
2010 Ok(())
2011 }
2012
2013 #[test_log::test(tokio::test)]
2014 async fn test_deploy_fee_contract_proxy() -> Result<()> {
2015 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2016 let mut contracts = Contracts::new();
2017 let admin = provider.get_accounts().await?[0];
2018 let alice = Address::random();
2019
2020 let fee_proxy_addr = deploy_fee_contract_proxy(&provider, &mut contracts, alice).await?;
2021
2022 let fee = FeeContract::new(fee_proxy_addr, &provider);
2024 let fetched_owner = fee.owner().call().await?;
2025 assert_eq!(fetched_owner, alice);
2026
2027 contracts = Contracts::new();
2029 let fee_proxy_addr = deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
2030 let fee = FeeContract::new(fee_proxy_addr, &provider);
2031
2032 let multisig = Address::random();
2034 let _receipt = transfer_ownership(
2035 &provider,
2036 Contract::FeeContractProxy,
2037 fee_proxy_addr,
2038 multisig,
2039 )
2040 .await?;
2041 assert_eq!(fee.owner().call().await?, multisig);
2042
2043 Ok(())
2044 }
2045
2046 async fn test_upgrade_light_client_to_v2_helper(is_mock: bool) -> Result<()> {
2047 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2048 let mut contracts = Contracts::new();
2049 let blocks_per_epoch = 10; let epoch_start_block = 22;
2051
2052 let genesis_state = LightClientStateSol::dummy_genesis();
2054 let genesis_stake = StakeTableStateSol::dummy_genesis();
2055 let admin = provider.get_accounts().await?[0];
2056 let prover = Address::random();
2057
2058 let lc_proxy_addr = deploy_light_client_proxy(
2060 &provider,
2061 &mut contracts,
2062 false,
2063 genesis_state.clone(),
2064 genesis_stake.clone(),
2065 admin,
2066 Some(prover),
2067 )
2068 .await?;
2069
2070 let state_history_retention_period = LightClient::new(lc_proxy_addr, &provider)
2071 .stateHistoryRetentionPeriod()
2072 .call()
2073 .await?;
2074
2075 upgrade_light_client_v2(
2077 &provider,
2078 &mut contracts,
2079 is_mock,
2080 blocks_per_epoch,
2081 epoch_start_block,
2082 )
2083 .await?;
2084
2085 let lc = LightClientV2::new(lc_proxy_addr, &provider);
2087 let finalized_state: LightClientStateSol = lc.finalizedState().call().await?.into();
2088 assert_eq!(
2089 genesis_state.abi_encode_params(),
2090 finalized_state.abi_encode_params()
2091 );
2092 let next_stake: StakeTableStateSol = lc.votingStakeTableState().call().await?.into();
2094 assert_eq!(
2095 genesis_stake.abi_encode_params(),
2096 next_stake.abi_encode_params()
2097 );
2098 assert_eq!(lc.getVersion().call().await?.majorVersion, 2);
2099 assert_eq!(lc.blocksPerEpoch().call().await?, blocks_per_epoch);
2100 assert_eq!(lc.epochStartBlock().call().await?, epoch_start_block);
2101 assert_eq!(
2102 lc.stateHistoryRetentionPeriod().call().await?,
2103 state_history_retention_period
2104 );
2105
2106 if is_mock {
2108 let lc_mock = LightClientV2Mock::new(lc_proxy_addr, &provider);
2110 let new_blocks_per_epoch = blocks_per_epoch + 10;
2111 lc_mock
2112 .setBlocksPerEpoch(new_blocks_per_epoch)
2113 .send()
2114 .await?
2115 .watch()
2116 .await?;
2117 assert_eq!(new_blocks_per_epoch, lc_mock.blocksPerEpoch().call().await?);
2118 }
2119 Ok(())
2120 }
2121
2122 #[test_log::test(tokio::test)]
2123 async fn test_upgrade_light_client_to_v2() -> Result<()> {
2124 test_upgrade_light_client_to_v2_helper(false).await
2125 }
2126
2127 #[test_log::test(tokio::test)]
2128 async fn test_upgrade_mock_light_client_v2() -> Result<()> {
2129 test_upgrade_light_client_to_v2_helper(true).await
2130 }
2131
2132 #[test_log::test(tokio::test)]
2133 async fn test_fetch_stake_table_for_stake_table_storage_migration() -> Result<()> {
2134 let (_anvil, provider, l1_client) =
2135 ProviderBuilder::new().connect_anvil_with_l1_client()?;
2136 let mut contracts = Contracts::new();
2137 let owner = provider.get_accounts().await?[0];
2138
2139 let token_addr = deploy_token_proxy(
2140 &provider,
2141 &mut contracts,
2142 owner,
2143 owner,
2144 U256::from(10_000_000u64),
2145 "Test Token",
2146 "TEST",
2147 )
2148 .await?;
2149 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
2150 let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
2151
2152 let stake_table_proxy_addr = deploy_stake_table_proxy(
2153 &provider,
2154 &mut contracts,
2155 token_addr,
2156 lc_addr,
2157 exit_escrow_period,
2158 owner,
2159 )
2160 .await?;
2161
2162 let stake_table = StakeTableV2::new(stake_table_proxy_addr, &provider);
2164
2165 let accounts = provider.get_accounts().await?;
2166 let validators = [
2167 TestValidator::random_update_keys(accounts[4], 1234),
2168 TestValidator::random_update_keys(accounts[5], 4567),
2169 ];
2170
2171 for validator in validators.iter() {
2172 let receipt = stake_table
2173 .registerValidator(
2174 validator.bls_vk,
2175 validator.schnorr_vk,
2176 validator.bls_sig.into(),
2177 validator.commission,
2178 )
2179 .from(validator.account)
2180 .send()
2181 .await?
2182 .get_receipt()
2183 .await?;
2184 assert!(receipt.status());
2185 }
2186
2187 let delegators = [
2188 (
2189 accounts[0],
2190 validators[0].account,
2191 parse_ether("10").unwrap(),
2192 ),
2193 (
2194 accounts[1],
2195 validators[1].account,
2196 parse_ether("10").unwrap(),
2197 ),
2198 (
2199 accounts[2],
2200 validators[0].account,
2201 parse_ether("100").unwrap(),
2202 ),
2203 ];
2204
2205 let token = EspToken::new(token_addr, &provider);
2206
2207 for (delegator, validator, amount) in delegators.iter() {
2208 let receipt = token
2209 .transfer(*delegator, *amount)
2210 .from(Address::from(*owner))
2211 .send()
2212 .await?
2213 .get_receipt()
2214 .await?;
2215 assert!(receipt.status());
2216
2217 let receipt = token
2218 .approve(stake_table_proxy_addr, *amount)
2219 .from(*delegator)
2220 .send()
2221 .await?
2222 .get_receipt()
2223 .await?;
2224 assert!(receipt.status());
2225
2226 let receipt = stake_table
2227 .delegate(*validator, *amount)
2228 .from(*delegator)
2229 .send()
2230 .await?
2231 .get_receipt()
2232 .await?;
2233 assert!(receipt.status());
2234 }
2235
2236 let receipt = stake_table
2237 .deregisterValidator()
2238 .from(validators[1].account)
2239 .send()
2240 .await?
2241 .get_receipt()
2242 .await?;
2243 assert!(receipt.status());
2244
2245 let undelegations = [
2246 (
2247 accounts[0],
2248 validators[0].account,
2249 parse_ether("5").unwrap(),
2250 ),
2251 (
2252 accounts[2],
2253 validators[0].account,
2254 parse_ether("3").unwrap(),
2255 ),
2256 ];
2257
2258 for (delegator_addr, validator_addr, amount) in undelegations.iter() {
2259 let receipt = stake_table
2260 .undelegate(*validator_addr, *amount)
2261 .from(*delegator_addr)
2262 .send()
2263 .await?
2264 .get_receipt()
2265 .await?;
2266 assert!(receipt.status());
2267 }
2268
2269 let (fetched_active_stake, fetched_commissions) =
2270 fetch_stake_table_for_stake_table_storage_migration(
2271 l1_client.clone(),
2272 stake_table_proxy_addr,
2273 )
2274 .await?;
2275
2276 let expected_active_stake: U256 = delegators
2277 .iter()
2278 .map(|(_, _, amount)| *amount)
2279 .sum::<U256>()
2280 - delegators
2281 .iter()
2282 .filter(|(_, validator, _)| *validator == validators[1].account)
2283 .map(|(_, _, amount)| *amount)
2284 .sum::<U256>()
2285 - undelegations
2286 .iter()
2287 .map(|(_, _, amount)| *amount)
2288 .sum::<U256>();
2289
2290 assert_eq!(fetched_active_stake, expected_active_stake);
2291 assert!(fetched_active_stake <= token.balanceOf(stake_table_proxy_addr).call().await?);
2292
2293 assert_eq!(fetched_commissions.len(), 1);
2294 assert_eq!(fetched_commissions[0].validator, validators[0].account);
2295 assert_eq!(fetched_commissions[0].commission, validators[0].commission);
2296
2297 let stake_table_v2 = StakeTableV2::deploy(&provider).await?;
2299 let err = fetch_stake_table_for_stake_table_storage_migration(
2300 l1_client.clone(),
2301 *stake_table_v2.address(),
2302 )
2303 .await
2304 .unwrap_err();
2305 assert!(err.to_string().contains("Expected StakeTable V1"));
2306
2307 Ok(())
2308 }
2309
2310 #[ignore]
2316 #[test_log::test(tokio::test)]
2317 async fn test_fetch_stake_table_sepolia() -> Result<()> {
2318 let rpc_url: Url = std::env::var("RPC_URL")
2319 .expect("RPC_URL environment variable not set")
2320 .parse()?;
2321 let provider = ProviderBuilder::new().connect_http(rpc_url.clone());
2322 let l1_client = L1Client::new(vec![rpc_url])?;
2323
2324 let stake_table_address: Address = "0x40304FbE94D5E7D1492Dd90c53a2D63E8506a037".parse()?;
2326 let (fetched_active_stake, fetched_commissions) =
2327 fetch_stake_table_for_stake_table_storage_migration(l1_client, stake_table_address)
2328 .await?;
2329
2330 assert!(!fetched_commissions.is_empty());
2331 assert!(!fetched_active_stake.is_zero());
2332
2333 println!(
2334 "Fetched {} commissions from Sepolia StakeTable",
2335 fetched_commissions.len()
2336 );
2337 for commission in &fetched_commissions {
2338 println!(
2339 " Validator: {}, Commission: {}",
2340 commission.validator, commission.commission
2341 );
2342 }
2343 println!("Fetched active stake: {}", fetched_active_stake);
2344
2345 let pauser = Address::random();
2346 let admin = Address::random();
2347 let init_v2_calldata = StakeTableV2::new(stake_table_address, &provider)
2348 .initializeV2(pauser, admin, fetched_active_stake, fetched_commissions)
2349 .calldata()
2350 .clone();
2351 println!("Calldata size: {} bytes", init_v2_calldata.len());
2352
2353 assert!(init_v2_calldata.len() < 32 * 1024);
2356 Ok(())
2357 }
2358
2359 impl Contracts {
2360 fn insert(&mut self, name: Contract, address: Address) -> Option<Address> {
2361 self.addresses.insert(name, address)
2362 }
2363 }
2364
2365 #[ignore]
2373 #[test_log::test(tokio::test)]
2374 async fn test_upgrade_decaf_stake_table_fork() -> Result<()> {
2375 let rpc_url = std::env::var("RPC_URL").expect("RPC_URL environment variable not set");
2376
2377 let stake_table_address: Address = "0x40304FbE94D5E7D1492Dd90c53a2D63E8506a037".parse()?;
2379 let anvil = Anvil::new()
2380 .fork(rpc_url)
2381 .arg("--retries")
2382 .arg("20")
2383 .spawn();
2384
2385 let provider = ProviderBuilder::new().connect_http(anvil.endpoint().parse()?);
2386 let proxy = StakeTable::new(stake_table_address, &provider);
2387 let proxy_owner = proxy.owner().call().await?;
2388 tracing::info!("Proxy owner address: {proxy_owner:#x}");
2389
2390 let provider = ProviderBuilder::new()
2392 .filler(ImpersonateFiller::new(proxy_owner))
2393 .connect_http(anvil.endpoint().parse()?);
2394 let l1_client = L1Client::anvil(&anvil)?;
2395 let anvil_arc = Arc::new(anvil);
2396 let anvil_provider = AnvilProvider::new(provider.clone(), anvil_arc);
2397 anvil_provider.anvil_auto_impersonate_account(true).await?;
2398 anvil_provider
2399 .anvil_set_balance(proxy_owner, parse_ether("100")?)
2400 .await?;
2401
2402 let mut contracts = Contracts::new();
2404 contracts.insert(Contract::StakeTableProxy, stake_table_address);
2405 let pauser = Address::random();
2406 let admin = proxy_owner;
2407
2408 upgrade_stake_table_v2(&provider, l1_client, &mut contracts, pauser, admin).await?;
2409 Ok(())
2410 }
2411
2412 async fn test_upgrade_light_client_to_v3_helper(options: UpgradeTestOptions) -> Result<()> {
2413 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2414 let mut contracts = Contracts::new();
2415 let blocks_per_epoch = 10; let epoch_start_block = 22;
2417
2418 let genesis_state = LightClientStateSol::dummy_genesis();
2420 let genesis_stake = StakeTableStateSol::dummy_genesis();
2421 let admin = provider.get_accounts().await?[0];
2422 let prover = Address::random();
2423
2424 let lc_proxy_addr = deploy_light_client_proxy(
2426 &provider,
2427 &mut contracts,
2428 false,
2429 genesis_state.clone(),
2430 genesis_stake.clone(),
2431 admin,
2432 Some(prover),
2433 )
2434 .await?;
2435
2436 upgrade_light_client_v2(
2438 &provider,
2439 &mut contracts,
2440 options.is_mock,
2441 blocks_per_epoch,
2442 epoch_start_block,
2443 )
2444 .await?;
2445
2446 upgrade_light_client_v3(&provider, &mut contracts, options.is_mock).await?;
2448
2449 let lc = LightClientV3::new(lc_proxy_addr, &provider);
2451 let finalized_state: LightClientStateSol = lc.finalizedState().call().await?.into();
2452 assert_eq!(
2453 genesis_state.abi_encode_params(),
2454 finalized_state.abi_encode_params()
2455 );
2456
2457 let next_stake: StakeTableStateSol = lc.votingStakeTableState().call().await?.into();
2459 assert_eq!(
2460 genesis_stake.abi_encode_params(),
2461 next_stake.abi_encode_params()
2462 );
2463
2464 assert_eq!(lc.getVersion().call().await?.majorVersion, 3);
2466
2467 let lc_as_v2 = LightClientV2::new(lc_proxy_addr, &provider);
2469 assert_eq!(lc_as_v2.blocksPerEpoch().call().await?, blocks_per_epoch);
2470 assert_eq!(lc_as_v2.epochStartBlock().call().await?, epoch_start_block);
2471
2472 if options.is_mock {
2474 let lc_mock = LightClientV3Mock::new(lc_proxy_addr, &provider);
2476 let new_blocks_per_epoch = blocks_per_epoch + 10;
2478 lc_mock
2479 .setBlocksPerEpoch(new_blocks_per_epoch)
2480 .send()
2481 .await?
2482 .watch()
2483 .await?;
2484 assert_eq!(new_blocks_per_epoch, lc_mock.blocksPerEpoch().call().await?);
2485 }
2486 Ok(())
2487 }
2488
2489 #[test_log::test(tokio::test)]
2490 async fn test_upgrade_light_client_to_v3() -> Result<()> {
2491 test_upgrade_light_client_to_v3_helper(UpgradeTestOptions {
2492 is_mock: false,
2493 upgrade_count: UpgradeCount::Once,
2494 })
2495 .await
2496 }
2497
2498 #[test_log::test(tokio::test)]
2499 async fn test_upgrade_mock_light_client_v3() -> Result<()> {
2500 test_upgrade_light_client_to_v3_helper(UpgradeTestOptions {
2501 is_mock: true,
2502 upgrade_count: UpgradeCount::Once,
2503 })
2504 .await
2505 }
2506
2507 #[derive(Debug, Clone, Copy)]
2508 pub enum UpgradeCount {
2509 Once,
2510 Twice,
2511 }
2512
2513 #[derive(Debug, Clone, Copy)]
2514 pub struct UpgradeTestOptions {
2515 pub is_mock: bool,
2516 pub upgrade_count: UpgradeCount,
2517 }
2518 async fn test_upgrade_light_client_to_v2_multisig_owner_helper(
2524 options: UpgradeTestOptions,
2525 ) -> Result<()> {
2526 let multisig_admin = Address::random();
2527 let mnemonic = "test test test test test test test test test test test junk".to_string();
2528 let account_index = 0u32;
2529 let anvil = Anvil::default().spawn();
2530
2531 let mut contracts = Contracts::new();
2532 let blocks_per_epoch = 10; let epoch_start_block = 22;
2534 let admin_signer = MnemonicBuilder::<English>::default()
2535 .phrase(mnemonic)
2536 .index(account_index)
2537 .expect("wrong mnemonic or index")
2538 .build()?;
2539 let admin = admin_signer.address();
2540 let provider = ProviderBuilder::new()
2541 .wallet(admin_signer)
2542 .connect(&anvil.endpoint())
2543 .await?;
2544
2545 let genesis_state = LightClientStateSol::dummy_genesis();
2547 let genesis_stake = StakeTableStateSol::dummy_genesis();
2548
2549 let prover = Address::random();
2550
2551 let lc_proxy_addr = deploy_light_client_proxy(
2553 &provider,
2554 &mut contracts,
2555 false,
2556 genesis_state.clone(),
2557 genesis_stake.clone(),
2558 admin,
2559 Some(prover),
2560 )
2561 .await?;
2562 if matches!(options.upgrade_count, UpgradeCount::Twice) {
2563 upgrade_light_client_v2(
2565 &provider,
2566 &mut contracts,
2567 options.is_mock,
2568 blocks_per_epoch,
2569 epoch_start_block,
2570 )
2571 .await?;
2572 }
2573
2574 let _receipt = transfer_ownership(
2576 &provider,
2577 Contract::LightClientProxy,
2578 lc_proxy_addr,
2579 multisig_admin,
2580 )
2581 .await?;
2582 let lc = LightClient::new(lc_proxy_addr, &provider);
2583 assert_eq!(lc.owner().call().await?, multisig_admin);
2584
2585 let calldata = upgrade_light_client_v2_multisig_owner(
2587 &provider,
2588 &mut contracts,
2589 LightClientV2UpgradeParams {
2590 blocks_per_epoch,
2591 epoch_start_block,
2592 },
2593 options.is_mock,
2594 MultisigOwnerCheck::Skip,
2595 )
2596 .await?;
2597 tracing::info!(
2598 "Encoded calldata for LightClientProxy upgrade: to={:#x}, data={}",
2599 calldata.to,
2600 calldata.data
2601 );
2602 assert_eq!(calldata.to, lc_proxy_addr);
2604 assert!(!calldata.data.is_empty());
2606
2607 Ok(())
2608 }
2609
2610 #[test_log::test(tokio::test)]
2611 async fn test_upgrade_light_client_to_v2_multisig_owner() -> Result<()> {
2612 test_upgrade_light_client_to_v2_multisig_owner_helper(UpgradeTestOptions {
2613 is_mock: false,
2614 upgrade_count: UpgradeCount::Once,
2615 })
2616 .await
2617 }
2618
2619 #[test_log::test(tokio::test)]
2621 async fn test_upgrade_light_client_to_v2_twice_multisig_owner() -> Result<()> {
2622 test_upgrade_light_client_to_v2_multisig_owner_helper(UpgradeTestOptions {
2623 is_mock: false,
2624 upgrade_count: UpgradeCount::Twice,
2625 })
2626 .await
2627 }
2628
2629 #[test_log::test(tokio::test)]
2630 async fn test_deploy_token_proxy() -> Result<()> {
2631 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2632 let mut contracts = Contracts::new();
2633
2634 let init_recipient = provider.get_accounts().await?[0];
2635 let rand_owner = Address::random();
2636 let initial_supply = U256::from(3590000000u64);
2637 let name = "Espresso";
2638 let symbol = "ESP";
2639
2640 let addr = deploy_token_proxy(
2641 &provider,
2642 &mut contracts,
2643 rand_owner,
2644 init_recipient,
2645 initial_supply,
2646 name,
2647 symbol,
2648 )
2649 .await?;
2650 let token = EspToken::new(addr, &provider);
2651
2652 assert_eq!(token.owner().call().await?, rand_owner);
2653 let total_supply = token.totalSupply().call().await?;
2654 assert_eq!(
2655 total_supply,
2656 parse_ether(&initial_supply.to_string()).unwrap()
2657 );
2658 assert_eq!(token.balanceOf(init_recipient).call().await?, total_supply);
2659 assert_eq!(token.name().call().await?, name);
2660 assert_eq!(token.symbol().call().await?, symbol);
2661
2662 Ok(())
2663 }
2664
2665 #[test_log::test(tokio::test)]
2666 async fn test_deploy_stake_table_proxy() -> Result<()> {
2667 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2668 let mut contracts = Contracts::new();
2669
2670 let init_recipient = provider.get_accounts().await?[0];
2672 let token_owner = Address::random();
2673 let token_name = "Espresso";
2674 let token_symbol = "ESP";
2675 let initial_supply = U256::from(3590000000u64);
2676 let token_addr = deploy_token_proxy(
2677 &provider,
2678 &mut contracts,
2679 token_owner,
2680 init_recipient,
2681 initial_supply,
2682 token_name,
2683 token_symbol,
2684 )
2685 .await?;
2686
2687 let lc_proxy_addr = deploy_light_client_proxy(
2689 &provider,
2690 &mut contracts,
2691 false,
2692 LightClientStateSol::dummy_genesis(),
2693 StakeTableStateSol::dummy_genesis(),
2694 init_recipient,
2695 Some(init_recipient),
2696 )
2697 .await?;
2698
2699 let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
2701 let owner = init_recipient;
2702 let stake_table_addr = deploy_stake_table_proxy(
2703 &provider,
2704 &mut contracts,
2705 token_addr,
2706 lc_proxy_addr,
2707 exit_escrow_period,
2708 owner,
2709 )
2710 .await?;
2711 let stake_table = StakeTable::new(stake_table_addr, &provider);
2712
2713 assert_eq!(
2714 stake_table.exitEscrowPeriod().call().await?,
2715 exit_escrow_period
2716 );
2717 assert_eq!(stake_table.owner().call().await?, owner);
2718 assert_eq!(stake_table.token().call().await?, token_addr);
2719 assert_eq!(stake_table.lightClient().call().await?, lc_proxy_addr);
2720 Ok(())
2721 }
2722
2723 #[test_log::test(tokio::test)]
2724 async fn test_upgrade_stake_table_v2() -> Result<()> {
2725 let (_anvil, provider, l1_client) =
2726 ProviderBuilder::new().connect_anvil_with_l1_client()?;
2727 let mut contracts = Contracts::new();
2728
2729 let init_recipient = provider.get_accounts().await?[0];
2731 let token_owner = Address::random();
2732 let token_name = "Espresso";
2733 let token_symbol = "ESP";
2734 let initial_supply = U256::from(3590000000u64);
2735 let token_addr = deploy_token_proxy(
2736 &provider,
2737 &mut contracts,
2738 token_owner,
2739 init_recipient,
2740 initial_supply,
2741 token_name,
2742 token_symbol,
2743 )
2744 .await?;
2745
2746 let lc_proxy_addr = deploy_light_client_proxy(
2748 &provider,
2749 &mut contracts,
2750 false,
2751 LightClientStateSol::dummy_genesis(),
2752 StakeTableStateSol::dummy_genesis(),
2753 init_recipient,
2754 Some(init_recipient),
2755 )
2756 .await?;
2757
2758 let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
2760 let owner = init_recipient;
2761 let stake_table_addr = deploy_stake_table_proxy(
2762 &provider,
2763 &mut contracts,
2764 token_addr,
2765 lc_proxy_addr,
2766 exit_escrow_period,
2767 owner,
2768 )
2769 .await?;
2770 let stake_table_v1 = StakeTable::new(stake_table_addr, &provider);
2771 assert_eq!(stake_table_v1.getVersion().call().await?, (1, 0, 0).into());
2772
2773 let mut contracts_v1 = contracts.clone();
2776
2777 let pauser = Address::random();
2779 upgrade_stake_table_v2(&provider, l1_client.clone(), &mut contracts, pauser, owner).await?;
2780
2781 let stake_table_v2 = StakeTableV2::new(stake_table_addr, &provider);
2782
2783 assert_eq!(stake_table_v2.getVersion().call().await?, (2, 0, 0).into());
2784 assert_eq!(stake_table_v2.owner().call().await?, owner);
2785 assert_eq!(stake_table_v2.token().call().await?, token_addr);
2786 assert_eq!(stake_table_v2.lightClient().call().await?, lc_proxy_addr);
2787
2788 let pauser_role = stake_table_v2.PAUSER_ROLE().call().await?;
2790 assert!(
2791 stake_table_v2.hasRole(pauser_role, pauser).call().await?,
2792 "pauser should have PAUSER_ROLE"
2793 );
2794
2795 let admin_role = stake_table_v2.DEFAULT_ADMIN_ROLE().call().await?;
2797 assert!(stake_table_v2.hasRole(admin_role, owner).call().await?,);
2798
2799 let current_impl = read_proxy_impl(&provider, stake_table_addr).await?;
2801 upgrade_stake_table_v2(&provider, l1_client, &mut contracts_v1, pauser, owner).await?;
2802 assert_ne!(
2803 read_proxy_impl(&provider, stake_table_addr).await?,
2804 current_impl
2805 );
2806
2807 Ok(())
2808 }
2809
2810 async fn test_upgrade_stake_table_to_v2_multisig_owner_helper() -> Result<()> {
2819 let multisig_admin = Address::random();
2820 let (_anvil, provider, l1_client) =
2821 ProviderBuilder::new().connect_anvil_with_l1_client()?;
2822 let mut contracts = Contracts::new();
2823 let init_recipient = provider.get_accounts().await?[0];
2824 let token_owner = Address::random();
2825 let initial_supply = U256::from(3590000000u64);
2826
2827 let token_addr = deploy_token_proxy(
2829 &provider,
2830 &mut contracts,
2831 token_owner,
2832 init_recipient,
2833 initial_supply,
2834 "Espresso",
2835 "ESP",
2836 )
2837 .await?;
2838 let lc_proxy_addr = deploy_light_client_proxy(
2840 &provider,
2841 &mut contracts,
2842 false,
2843 LightClientStateSol::dummy_genesis(),
2844 StakeTableStateSol::dummy_genesis(),
2845 init_recipient,
2846 Some(init_recipient),
2847 )
2848 .await?;
2849
2850 let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
2851 let owner = init_recipient;
2852 let stake_table_proxy_addr = deploy_stake_table_proxy(
2853 &provider,
2854 &mut contracts,
2855 token_addr,
2856 lc_proxy_addr,
2857 exit_escrow_period,
2858 owner,
2859 )
2860 .await?;
2861 let _receipt = transfer_ownership(
2863 &provider,
2864 Contract::StakeTableProxy,
2865 stake_table_proxy_addr,
2866 multisig_admin,
2867 )
2868 .await?;
2869 let stake_table = StakeTable::new(stake_table_proxy_addr, &provider);
2870 assert_eq!(stake_table.owner().call().await?, multisig_admin);
2871 let pauser = Address::random();
2873 let calldata = upgrade_stake_table_v2_multisig_owner(
2874 &provider,
2875 l1_client,
2876 &mut contracts,
2877 StakeTableV2UpgradeParams {
2878 multisig_address: multisig_admin,
2879 pauser,
2880 },
2881 MultisigOwnerCheck::Skip,
2882 )
2883 .await?;
2884 assert_eq!(calldata.to, stake_table_proxy_addr);
2885
2886 Ok(())
2887 }
2888
2889 #[test_log::test(tokio::test)]
2890 async fn test_upgrade_stake_table_to_v2_multisig_owner() -> Result<()> {
2891 test_upgrade_stake_table_to_v2_multisig_owner_helper().await
2892 }
2893
2894 #[test_log::test(tokio::test)]
2895 async fn test_deploy_ops_timelock() -> Result<()> {
2896 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2897 let mut contracts = Contracts::new();
2898
2899 let min_delay = U256::from(86400); let admin = provider.get_accounts().await?[0];
2902 let proposers = vec![Address::random()];
2903 let executors = vec![Address::random()];
2904
2905 let timelock_addr = deploy_ops_timelock(
2906 &provider,
2907 &mut contracts,
2908 min_delay,
2909 proposers.clone(),
2910 executors.clone(),
2911 admin,
2912 )
2913 .await?;
2914
2915 let timelock = OpsTimelock::new(timelock_addr, &provider);
2917 assert_eq!(timelock.getMinDelay().call().await?, min_delay);
2918
2919 assert_eq!(timelock.getMinDelay().call().await?, min_delay);
2921 assert!(
2922 timelock
2923 .hasRole(timelock.PROPOSER_ROLE().call().await?, proposers[0])
2924 .call()
2925 .await?
2926 );
2927 assert!(
2928 timelock
2929 .hasRole(timelock.EXECUTOR_ROLE().call().await?, executors[0])
2930 .call()
2931 .await?
2932 );
2933
2934 let default_admin_role = U256::ZERO;
2936 assert!(
2937 timelock
2938 .hasRole(default_admin_role.into(), admin)
2939 .call()
2940 .await?
2941 );
2942 Ok(())
2943 }
2944
2945 #[test_log::test(tokio::test)]
2946 async fn test_deploy_safe_exit_timelock() -> Result<()> {
2947 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2948 let mut contracts = Contracts::new();
2949
2950 let min_delay = U256::from(86400); let admin = provider.get_accounts().await?[0];
2953 let proposers = vec![Address::random()];
2954 let executors = vec![Address::random()];
2955
2956 let timelock_addr = deploy_safe_exit_timelock(
2957 &provider,
2958 &mut contracts,
2959 min_delay,
2960 proposers.clone(),
2961 executors.clone(),
2962 admin,
2963 )
2964 .await?;
2965
2966 let timelock = SafeExitTimelock::new(timelock_addr, &provider);
2968 assert_eq!(timelock.getMinDelay().call().await?, min_delay);
2969
2970 assert_eq!(timelock.getMinDelay().call().await?, min_delay);
2972 assert!(
2973 timelock
2974 .hasRole(timelock.PROPOSER_ROLE().call().await?, proposers[0])
2975 .call()
2976 .await?
2977 );
2978 assert!(
2979 timelock
2980 .hasRole(timelock.EXECUTOR_ROLE().call().await?, executors[0])
2981 .call()
2982 .await?
2983 );
2984
2985 let default_admin_role = U256::ZERO;
2987 assert!(
2988 timelock
2989 .hasRole(default_admin_role.into(), admin)
2990 .call()
2991 .await?
2992 );
2993 Ok(())
2994 }
2995
2996 #[test_log::test(tokio::test)]
2997 async fn test_upgrade_esp_token_v2() -> Result<()> {
2998 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
2999 let mut contracts = Contracts::new();
3000
3001 let init_recipient = provider.get_accounts().await?[1];
3003 let token_owner = provider.get_accounts().await?[0];
3004 let token_name = "Espresso";
3005 let token_symbol = "ESP";
3006 let initial_supply = U256::from(3590000000u64);
3007 let token_proxy_addr = deploy_token_proxy(
3008 &provider,
3009 &mut contracts,
3010 token_owner,
3011 init_recipient,
3012 initial_supply,
3013 token_name,
3014 token_symbol,
3015 )
3016 .await?;
3017 let esp_token = EspToken::new(token_proxy_addr, &provider);
3018 assert_eq!(esp_token.name().call().await?, token_name);
3019
3020 let fake_reward_claim = Address::random();
3021 contracts.insert(Contract::RewardClaimProxy, fake_reward_claim);
3022
3023 upgrade_esp_token_v2(&provider, &mut contracts).await?;
3025
3026 let esp_token_v2 = EspTokenV2::new(token_proxy_addr, &provider);
3027
3028 assert_eq!(esp_token_v2.getVersion().call().await?, (2, 0, 0).into());
3029 assert_eq!(esp_token_v2.owner().call().await?, token_owner);
3030
3031 assert_eq!(esp_token_v2.name().call().await?, "Espresso");
3033 assert_eq!(esp_token_v2.symbol().call().await?, "ESP");
3034 assert_eq!(esp_token_v2.decimals().call().await?, 18);
3035
3036 let initial_supply_in_wei = parse_ether(&initial_supply.to_string()).unwrap();
3037 assert_eq!(
3038 esp_token_v2.totalSupply().call().await?,
3039 initial_supply_in_wei
3040 );
3041 assert_eq!(
3042 esp_token_v2.balanceOf(init_recipient).call().await?,
3043 initial_supply_in_wei
3044 );
3045 assert_eq!(
3046 esp_token_v2.balanceOf(token_owner).call().await?,
3047 U256::ZERO
3048 );
3049
3050 assert_eq!(esp_token_v2.rewardClaim().call().await?, fake_reward_claim);
3051
3052 Ok(())
3053 }
3054
3055 #[test_log::test(tokio::test)]
3057 async fn test_upgrade_esp_token_v2_multisig_owner() -> Result<()> {
3058 test_upgrade_esp_token_v2_multisig_owner_helper(UpgradeTestOptions {
3059 is_mock: false,
3060 upgrade_count: UpgradeCount::Once,
3061 })
3062 .await
3063 }
3064
3065 async fn test_upgrade_esp_token_v2_multisig_owner_helper(
3071 options: UpgradeTestOptions,
3072 ) -> Result<()> {
3073 let multisig_admin = Address::random();
3074 let mnemonic = "test test test test test test test test test test test junk".to_string();
3075 let account_index = 0u32;
3076 let anvil = Anvil::default().spawn();
3077
3078 let mut contracts = Contracts::new();
3079 let admin_signer = MnemonicBuilder::<English>::default()
3080 .phrase(mnemonic)
3081 .index(account_index)
3082 .expect("wrong mnemonic or index")
3083 .build()?;
3084 let admin = admin_signer.address();
3085 let provider = ProviderBuilder::new()
3086 .wallet(admin_signer)
3087 .connect(&anvil.endpoint())
3088 .await?;
3089 let init_recipient = provider.get_accounts().await?[0];
3090 let initial_supply = U256::from(3590000000u64);
3091 let token_name = "Espresso";
3092 let token_symbol = "ESP";
3093
3094 let esp_token_proxy_addr = deploy_token_proxy(
3096 &provider,
3097 &mut contracts,
3098 admin,
3099 init_recipient,
3100 initial_supply,
3101 token_name,
3102 token_symbol,
3103 )
3104 .await?;
3105 if matches!(options.upgrade_count, UpgradeCount::Twice) {
3106 upgrade_esp_token_v2(&provider, &mut contracts).await?;
3108 }
3109
3110 let _receipt = transfer_ownership(
3112 &provider,
3113 Contract::EspTokenProxy,
3114 esp_token_proxy_addr,
3115 multisig_admin,
3116 )
3117 .await?;
3118 let esp_token = EspToken::new(esp_token_proxy_addr, &provider);
3119 assert_eq!(esp_token.owner().call().await?, multisig_admin);
3120
3121 let fake_reward_claim = Address::random();
3122 contracts.insert(Contract::RewardClaimProxy, fake_reward_claim);
3123
3124 let calldata = upgrade_esp_token_v2_multisig_owner(
3126 &provider,
3127 &mut contracts,
3128 MultisigOwnerCheck::Skip,
3129 )
3130 .await?;
3131 tracing::info!(
3132 "Encoded calldata for EspTokenProxy upgrade: to={:#x}, data={}",
3133 calldata.to,
3134 calldata.data
3135 );
3136 assert_eq!(calldata.to, esp_token_proxy_addr);
3137 assert!(!calldata.data.is_empty());
3138
3139 Ok(())
3140 }
3141
3142 #[test_log::test(tokio::test)]
3143 async fn test_schedule_and_execute_timelock_operation() -> Result<()> {
3144 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
3145 let mut contracts = Contracts::new();
3146 let delay = U256::from(0);
3147
3148 let provider_wallet = provider.get_accounts().await?[0];
3150
3151 let proposers = vec![provider_wallet];
3152 let executors = vec![provider_wallet];
3153
3154 let timelock_addr = deploy_ops_timelock(
3155 &provider,
3156 &mut contracts,
3157 delay,
3158 proposers,
3159 executors,
3160 provider_wallet, )
3162 .await?;
3163
3164 let fee_contract_proxy_addr =
3166 deploy_fee_contract_proxy(&provider, &mut contracts, timelock_addr).await?;
3167
3168 let proxy = FeeContract::new(fee_contract_proxy_addr, &provider);
3169 let upgrade_data = proxy
3170 .transferOwnership(provider_wallet)
3171 .calldata()
3172 .to_owned();
3173
3174 let mut operation = TimelockOperationPayload {
3176 target: fee_contract_proxy_addr,
3177 value: U256::ZERO,
3178 data: upgrade_data,
3179 predecessor: B256::ZERO,
3180 salt: B256::ZERO,
3181 delay,
3182 };
3183 let operation_id = perform_timelock_operation(
3184 &provider,
3185 Contract::FeeContractProxy,
3186 operation.clone(),
3187 TimelockOperationType::Schedule,
3188 TimelockOperationParams::default(),
3189 )
3190 .await?;
3191
3192 let timelock = OpsTimelock::new(timelock_addr, &provider);
3194 assert!(timelock.isOperationPending(operation_id).call().await?);
3195 assert!(timelock.isOperationReady(operation_id).call().await?);
3196 assert!(!timelock.isOperationDone(operation_id).call().await?);
3197 assert!(timelock.getTimestamp(operation_id).call().await? > U256::ZERO);
3198
3199 perform_timelock_operation(
3201 &provider,
3202 Contract::FeeContractProxy,
3203 operation.clone(),
3204 TimelockOperationType::Execute,
3205 TimelockOperationParams::default(),
3206 )
3207 .await?;
3208
3209 assert!(timelock.isOperationDone(operation_id).call().await?);
3211 assert!(!timelock.isOperationPending(operation_id).call().await?);
3212 assert!(!timelock.isOperationReady(operation_id).call().await?);
3213 let fee_contract = FeeContract::new(operation.target, &provider);
3215 assert_eq!(fee_contract.owner().call().await?, provider_wallet);
3216
3217 operation.value = U256::from(1);
3218 let tx_receipt = fee_contract
3220 .transferOwnership(timelock_addr)
3221 .send()
3222 .await?
3223 .get_receipt()
3224 .await?;
3225 assert!(tx_receipt.inner.is_success());
3226
3227 let operation_id = perform_timelock_operation(
3228 &provider,
3229 Contract::FeeContractProxy,
3230 operation.clone(),
3231 TimelockOperationType::Schedule,
3232 TimelockOperationParams::default(),
3233 )
3234 .await?;
3235
3236 perform_timelock_operation(
3237 &provider,
3238 Contract::FeeContractProxy,
3239 operation.clone(),
3240 TimelockOperationType::Cancel,
3241 TimelockOperationParams::default(),
3242 )
3243 .await?;
3244
3245 timelock
3247 .hashOperation(
3248 operation.target,
3249 operation.value,
3250 operation.data.clone(),
3251 operation.predecessor,
3252 operation.salt,
3253 )
3254 .call()
3255 .await?;
3256 assert!(timelock.getTimestamp(operation_id).call().await? == U256::ZERO);
3257
3258 let operation_id = perform_timelock_operation(
3259 &provider,
3260 Contract::FeeContractProxy,
3261 operation.clone(),
3262 TimelockOperationType::Schedule,
3263 TimelockOperationParams::default(),
3264 )
3265 .await?;
3266
3267 perform_timelock_operation(
3269 &provider,
3270 Contract::FeeContractProxy,
3271 TimelockOperationPayload {
3273 target: fee_contract_proxy_addr, value: U256::ZERO,
3275 data: Bytes::new(),
3276 predecessor: B256::ZERO,
3277 salt: B256::ZERO,
3278 delay: U256::ZERO,
3279 },
3280 TimelockOperationType::Cancel,
3281 TimelockOperationParams {
3282 operation_id: Some(operation_id), ..Default::default()
3284 },
3285 )
3286 .await?;
3287 assert!(timelock.getTimestamp(operation_id).call().await? == U256::ZERO);
3288 Ok(())
3289 }
3290
3291 async fn test_transfer_ownership_helper(contract_type: Contract) -> Result<()> {
3292 let multisig_admin = Address::random();
3293 let timelock = Address::random();
3294 let mnemonic = "test test test test test test test test test test test junk".to_string();
3295 let account_index = 0u32;
3296 let anvil = Anvil::default().spawn();
3297
3298 let mut contracts = Contracts::new();
3299 let admin_signer = MnemonicBuilder::<English>::default()
3300 .phrase(mnemonic)
3301 .index(account_index)
3302 .expect("wrong mnemonic or index")
3303 .build()?;
3304 let admin = admin_signer.address();
3305 let provider = ProviderBuilder::new()
3306 .wallet(admin_signer)
3307 .connect_http(anvil.endpoint_url());
3308
3309 let genesis_state = LightClientStateSol::dummy_genesis();
3311 let genesis_stake = StakeTableStateSol::dummy_genesis();
3312
3313 let prover = Address::random();
3314
3315 let proxy_addr = match contract_type {
3317 Contract::LightClientProxy => {
3318 deploy_light_client_proxy(
3319 &provider,
3320 &mut contracts,
3321 false,
3322 genesis_state.clone(),
3323 genesis_stake.clone(),
3324 admin,
3325 Some(prover),
3326 )
3327 .await?
3328 },
3329 Contract::FeeContractProxy => {
3330 deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?
3331 },
3332 Contract::EspTokenProxy => {
3333 deploy_token_proxy(
3334 &provider,
3335 &mut contracts,
3336 admin,
3337 multisig_admin,
3338 U256::from(0u64),
3339 "Test",
3340 "TEST",
3341 )
3342 .await?
3343 },
3344 Contract::StakeTableProxy => {
3345 let token_addr = deploy_token_proxy(
3346 &provider,
3347 &mut contracts,
3348 admin,
3349 admin,
3350 U256::from(0u64),
3351 "Test",
3352 "TEST",
3353 )
3354 .await?;
3355 let initial_admin = provider.get_accounts().await?[0];
3356 let lc_proxy_addr = deploy_light_client_proxy(
3358 &provider,
3359 &mut contracts,
3360 false,
3361 LightClientStateSol::dummy_genesis(),
3362 StakeTableStateSol::dummy_genesis(),
3363 initial_admin,
3364 Some(prover),
3365 )
3366 .await?;
3367 let blocks_per_epoch = 50;
3369 let epoch_start_block = 50;
3370 upgrade_light_client_v2(
3371 &provider,
3372 &mut contracts,
3373 false,
3374 blocks_per_epoch,
3375 epoch_start_block,
3376 )
3377 .await?;
3378 let lc_v2 = LightClientV2::new(lc_proxy_addr, &provider);
3379 assert_eq!(lc_v2.getVersion().call().await?.majorVersion, 2);
3380 assert_eq!(lc_v2.blocksPerEpoch().call().await?, blocks_per_epoch);
3381 assert_eq!(lc_v2.epochStartBlock().call().await?, epoch_start_block);
3382
3383 deploy_stake_table_proxy(
3384 &provider,
3385 &mut contracts,
3386 token_addr,
3387 lc_proxy_addr,
3388 U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS),
3389 admin,
3390 )
3391 .await?
3392 },
3393 _ => anyhow::bail!("Not a proxy contract, can't transfer ownership"),
3394 };
3395
3396 let _receipt =
3398 transfer_ownership(&provider, contract_type, proxy_addr, multisig_admin).await?;
3399 assert_eq!(
3400 OwnableUpgradeable::new(proxy_addr, &provider)
3401 .owner()
3402 .call()
3403 .await?,
3404 multisig_admin
3405 );
3406
3407 let calldata = transfer_ownership_from_multisig_to_timelock(
3409 &mut contracts,
3410 contract_type,
3411 TransferOwnershipParams {
3412 new_owner: timelock,
3413 },
3414 )?;
3415 tracing::info!(
3416 "Transfer ownership calldata: to={:#x}, data={}",
3417 calldata.to,
3418 calldata.data
3419 );
3420 assert_eq!(calldata.to, proxy_addr);
3421
3422 let expected_calldata =
3423 crate::encode_function_call("transferOwnership(address)", vec![timelock.to_string()])
3424 .unwrap();
3425 assert_eq!(calldata.data, expected_calldata);
3426
3427 let impersonation_provider = ProviderBuilder::new()
3429 .filler(ImpersonateFiller::new(multisig_admin))
3430 .connect_http(anvil.endpoint_url());
3431 let anvil_provider = AnvilProvider::new(impersonation_provider.clone(), Arc::new(anvil));
3432 anvil_provider.anvil_auto_impersonate_account(true).await?;
3433 anvil_provider
3434 .anvil_set_balance(multisig_admin, parse_ether("100")?)
3435 .await?;
3436
3437 let tx = alloy::rpc::types::TransactionRequest::default()
3438 .to(calldata.to)
3439 .input(calldata.data.into());
3440 impersonation_provider
3441 .send_transaction(tx)
3442 .await?
3443 .get_receipt()
3444 .await?;
3445
3446 assert_eq!(
3447 OwnableUpgradeable::new(proxy_addr, &impersonation_provider)
3448 .owner()
3449 .call()
3450 .await?,
3451 timelock,
3452 "ownership should have transferred to timelock"
3453 );
3454
3455 Ok(())
3456 }
3457
3458 #[test_log::test(tokio::test)]
3459 async fn test_encode_function_call() -> Result<()> {
3460 let function_signature = "transfer(address,uint256)".to_string();
3461 let values = vec![
3462 "0x000000000000000000000000000000000000dead".to_string(),
3463 "1000".to_string(),
3464 ];
3465 let expected = "0xa9059cbb000000000000000000000000000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000003e8".parse::<Bytes>()?;
3466 let encoded = encode_function_call(&function_signature, values).expect("encoding failed");
3467
3468 assert_eq!(encoded, expected);
3469 Ok(())
3470 }
3471
3472 #[test_log::test(tokio::test)]
3473 async fn test_encode_function_call_upgrade_to_and_call() -> Result<()> {
3474 let function_signature = "upgradeToAndCall(address,bytes)".to_string();
3475 let values = vec![
3476 "0xe1f131b07550a689d6a11f21d9e9238a5c466996".to_string(),
3477 "0x".to_string(),
3478 ];
3479 let expected = "0x4f1ef286000000000000000000000000e1f131b07550a689d6a11f21d9e9238a5c46699600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000".parse::<Bytes>()?;
3480 let encoded = encode_function_call(&function_signature, values).expect("encoding failed");
3481
3482 assert_eq!(encoded, expected);
3483 Ok(())
3484 }
3485
3486 #[test_log::test(tokio::test)]
3487 async fn test_encode_function_call_with_bytes32() -> Result<()> {
3488 let function_signature = "setHash(bytes32)".to_string();
3489 let values =
3490 vec!["0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string()];
3491 let expected = "0x0c4c42850123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
3492 .parse::<Bytes>()?;
3493 let encoded = encode_function_call(&function_signature, values).expect("encoding failed");
3494
3495 assert_eq!(encoded, expected);
3496 Ok(())
3497 }
3498
3499 #[test_log::test(tokio::test)]
3500 async fn test_encode_function_call_with_bytes() -> Result<()> {
3501 let function_signature = "emitData(bytes)".to_string();
3502 let values = vec!["0xdeadbeef".to_string()];
3503 let expected = "0xd836083e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000".parse::<Bytes>()?;
3504 let encoded = encode_function_call(&function_signature, values).expect("encoding failed");
3505
3506 assert_eq!(encoded, expected);
3507 Ok(())
3508 }
3509
3510 #[test_log::test(tokio::test)]
3511 async fn test_encode_function_call_with_bool() -> Result<()> {
3512 let function_signature = "setFlag(bool)".to_string();
3513 let mut values = vec!["true".to_string()];
3514 let mut expected =
3515 "0x3927f6af0000000000000000000000000000000000000000000000000000000000000001"
3516 .parse::<Bytes>()?;
3517 let mut encoded =
3518 encode_function_call(&function_signature, values).expect("encoding failed");
3519
3520 assert_eq!(encoded, expected);
3521
3522 values = vec!["false".to_string()];
3523 expected = "0x3927f6af0000000000000000000000000000000000000000000000000000000000000000"
3524 .parse::<Bytes>()?;
3525 encoded = encode_function_call(&function_signature, values).expect("encoding failed");
3526
3527 assert_eq!(encoded, expected);
3528 Ok(())
3529 }
3530
3531 #[test_log::test(tokio::test)]
3532 async fn test_encode_function_call_with_string() -> Result<()> {
3533 let function_signature = "logMessage(string)".to_string();
3534 let values = vec!["Hello, world!".to_string()];
3535 let expected = "0x7c9900520000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000".parse::<Bytes>()?;
3536 let encoded = encode_function_call(&function_signature, values).expect("encoding failed");
3537
3538 assert_eq!(encoded, expected);
3539 Ok(())
3540 }
3541
3542 #[test_log::test(tokio::test)]
3543 async fn test_transfer_ownership_light_client_proxy() -> Result<()> {
3544 test_transfer_ownership_helper(Contract::LightClientProxy).await
3545 }
3546
3547 #[test_log::test(tokio::test)]
3548 async fn test_transfer_ownership_fee_contract_proxy() -> Result<()> {
3549 test_transfer_ownership_helper(Contract::FeeContractProxy).await
3550 }
3551
3552 #[test_log::test(tokio::test)]
3553 async fn test_transfer_ownership_esp_token_proxy() -> Result<()> {
3554 test_transfer_ownership_helper(Contract::EspTokenProxy).await
3555 }
3556
3557 #[test_log::test(tokio::test)]
3558 async fn test_transfer_ownership_stake_table_proxy() -> Result<()> {
3559 test_transfer_ownership_helper(Contract::StakeTableProxy).await
3560 }
3561
3562 #[test_log::test(tokio::test)]
3563 async fn test_get_proxy_initialized_version_initialized() -> Result<()> {
3564 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
3565 let mut contracts = Contracts::new();
3566 let owner = provider.get_accounts().await?[0];
3567
3568 let token_addr = deploy_token_proxy(
3569 &provider,
3570 &mut contracts,
3571 owner,
3572 owner,
3573 U256::from(10_000_000u64),
3574 "Test Token",
3575 "TEST",
3576 )
3577 .await?;
3578
3579 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3580 let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
3581
3582 let stake_table_proxy_addr = deploy_stake_table_proxy(
3583 &provider,
3584 &mut contracts,
3585 token_addr,
3586 lc_addr,
3587 exit_escrow_period,
3588 owner,
3589 )
3590 .await?;
3591
3592 let version = get_proxy_initialized_version(&provider, stake_table_proxy_addr).await?;
3593 assert_eq!(version, 1, "Initialized proxy should return version 1");
3594
3595 Ok(())
3596 }
3597
3598 #[test_log::test(tokio::test)]
3599 async fn test_get_proxy_initialized_version_reinitialized() -> Result<()> {
3600 let (_anvil, provider, l1_client) =
3601 ProviderBuilder::new().connect_anvil_with_l1_client()?;
3602 let mut contracts = Contracts::new();
3603 let owner = l1_client.get_accounts().await?[0];
3604
3605 let token_addr = deploy_token_proxy(
3606 &provider,
3607 &mut contracts,
3608 owner,
3609 owner,
3610 U256::from(10_000_000u64),
3611 "Test Token",
3612 "TEST",
3613 )
3614 .await?;
3615
3616 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3617 let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
3618
3619 let stake_table_proxy_addr = deploy_stake_table_proxy(
3620 &provider,
3621 &mut contracts,
3622 token_addr,
3623 lc_addr,
3624 exit_escrow_period,
3625 owner,
3626 )
3627 .await?;
3628
3629 let pauser = Address::random();
3630 let admin = Address::random();
3631 upgrade_stake_table_v2(&provider, l1_client.clone(), &mut contracts, pauser, admin).await?;
3632
3633 let version = get_proxy_initialized_version(&l1_client, stake_table_proxy_addr).await?;
3634 assert_eq!(version, 2, "Reinitialized proxy should return version 2");
3635
3636 Ok(())
3637 }
3638
3639 #[test_log::test(tokio::test)]
3640 async fn test_grant_admin_role_reward_claim() -> Result<()> {
3641 let (_anvil, provider, l1_client) =
3642 ProviderBuilder::new().connect_anvil_with_l1_client()?;
3643
3644 let mut contracts = Contracts::new();
3645 let deployer = l1_client.get_accounts().await?[0];
3646 let new_admin = Address::random();
3647
3648 let esp_token_addr = deploy_token_proxy(
3650 &provider,
3651 &mut contracts,
3652 deployer,
3653 deployer,
3654 U256::from(10_000_000u64),
3655 "Test Token",
3656 "TEST",
3657 )
3658 .await?;
3659 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3660 let reward_claim_addr = deploy_reward_claim_proxy(
3661 &provider,
3662 &mut contracts,
3663 esp_token_addr,
3664 lc_addr,
3665 deployer,
3666 deployer, )
3668 .await?;
3669
3670 let reward_claim = RewardClaim::new(reward_claim_addr, &provider);
3672 let admin_role = reward_claim.DEFAULT_ADMIN_ROLE().call().await?;
3673 assert!(reward_claim.hasRole(admin_role, deployer).call().await?);
3674 assert!(!reward_claim.hasRole(admin_role, new_admin).call().await?);
3675
3676 let receipt = grant_admin_role(
3678 &provider,
3679 Contract::RewardClaimProxy,
3680 reward_claim_addr,
3681 new_admin,
3682 )
3683 .await?;
3684
3685 assert!(receipt.inner.is_success());
3686
3687 assert!(reward_claim.hasRole(admin_role, new_admin).call().await?);
3689 assert!(!reward_claim.hasRole(admin_role, deployer).call().await?);
3690
3691 Ok(())
3692 }
3693
3694 #[test_log::test(tokio::test)]
3695 async fn test_transfer_ownership_from_eoa_reward_claim_routes_to_grant_role() -> Result<()> {
3696 let (anvil, provider, l1_client) = ProviderBuilder::new().connect_anvil_with_l1_client()?;
3697
3698 let mut contracts = Contracts::new();
3699 let deployer = l1_client.get_accounts().await?[0];
3700 let new_admin = Address::random();
3701
3702 let esp_token_addr = deploy_token_proxy(
3704 &provider,
3705 &mut contracts,
3706 deployer,
3707 deployer,
3708 U256::from(10_000_000u64),
3709 "Test Token",
3710 "TEST",
3711 )
3712 .await?;
3713 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3714 let reward_claim_addr = deploy_reward_claim_proxy(
3715 &provider,
3716 &mut contracts,
3717 esp_token_addr,
3718 lc_addr,
3719 deployer,
3720 deployer, )
3722 .await?;
3723
3724 let reward_claim = RewardClaim::new(reward_claim_addr, &provider);
3726 let admin_role = reward_claim.DEFAULT_ADMIN_ROLE().call().await?;
3727 assert!(reward_claim.hasRole(admin_role, deployer).call().await?);
3728 assert!(!reward_claim.hasRole(admin_role, new_admin).call().await?);
3729
3730 use builder::DeployerArgsBuilder;
3731
3732 let mut args_builder = DeployerArgsBuilder::default();
3733 args_builder
3734 .deployer(provider.clone())
3735 .rpc_url(anvil.endpoint_url())
3736 .transfer_ownership_from_eoa(true)
3737 .target_contract(OwnableContract::RewardClaimProxy)
3738 .transfer_ownership_new_owner(new_admin);
3739 let args = args_builder.build()?;
3740
3741 args.transfer_ownership_from_eoa(&mut contracts).await?;
3742
3743 assert!(reward_claim.hasRole(admin_role, new_admin).call().await?);
3745 assert!(!reward_claim.hasRole(admin_role, deployer).call().await?);
3746
3747 Ok(())
3748 }
3749
3750 #[test_log::test(tokio::test)]
3751 async fn test_perform_timelock_operation_reward_claim() -> Result<()> {
3752 let (anvil, provider, _l1_client) =
3753 ProviderBuilder::new().connect_anvil_with_l1_client()?;
3754 let mut contracts = Contracts::new();
3755 let delay = U256::from(0);
3756 let provider_wallet = provider.get_accounts().await?[0];
3757
3758 let timelock_addr = deploy_safe_exit_timelock(
3760 &provider,
3761 &mut contracts,
3762 delay,
3763 vec![provider_wallet],
3764 vec![provider_wallet],
3765 provider_wallet,
3766 )
3767 .await?;
3768
3769 let token_addr = deploy_token_proxy(
3771 &provider,
3772 &mut contracts,
3773 provider_wallet,
3774 provider_wallet,
3775 U256::from(10_000_000u64),
3776 "Test Token",
3777 "TEST",
3778 )
3779 .await?;
3780 let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3781
3782 let reward_claim_addr = deploy_reward_claim_proxy(
3784 &provider,
3785 &mut contracts,
3786 token_addr,
3787 lc_addr,
3788 timelock_addr,
3789 provider_wallet,
3790 )
3791 .await?;
3792
3793 let reward_claim = RewardClaim::new(reward_claim_addr, &provider);
3794 let pauser_role = reward_claim.PAUSER_ROLE().call().await?;
3795 let new_pauser = Address::random();
3796
3797 assert!(!reward_claim.hasRole(pauser_role, new_pauser).call().await?);
3799
3800 use builder::DeployerArgsBuilder;
3802 use proposals::timelock::TimelockOperationType;
3803
3804 let mut args_builder = DeployerArgsBuilder::default();
3805 args_builder
3806 .deployer(provider.clone())
3807 .rpc_url(anvil.endpoint_url())
3808 .timelock_operation_type(TimelockOperationType::Schedule)
3809 .target_contract(OwnableContract::RewardClaimProxy)
3810 .timelock_operation_value(U256::ZERO)
3811 .timelock_operation_function_signature("grantRole(bytes32,address)".to_string())
3812 .timelock_operation_function_values(vec![
3813 format!("{:#x}", pauser_role),
3814 format!("{:#x}", new_pauser),
3815 ])
3816 .timelock_operation_salt(
3817 "0x0000000000000000000000000000000000000000000000000000000000000001".to_string(),
3818 )
3819 .timelock_operation_delay(delay);
3820
3821 let args = args_builder.build()?;
3822
3823 args.propose_timelock_operation_for_contract(&mut contracts)
3825 .await?;
3826
3827 let mut args_builder = DeployerArgsBuilder::default();
3829 args_builder
3830 .deployer(provider.clone())
3831 .rpc_url(anvil.endpoint_url())
3832 .timelock_operation_type(TimelockOperationType::Execute)
3833 .target_contract(OwnableContract::RewardClaimProxy)
3834 .timelock_operation_value(U256::ZERO)
3835 .timelock_operation_function_signature("grantRole(bytes32,address)".to_string())
3836 .timelock_operation_function_values(vec![
3837 format!("{:#x}", pauser_role),
3838 format!("{:#x}", new_pauser),
3839 ])
3840 .timelock_operation_salt(
3841 "0x0000000000000000000000000000000000000000000000000000000000000001".to_string(),
3842 )
3843 .timelock_operation_delay(delay);
3844
3845 let args = args_builder.build()?;
3846 args.propose_timelock_operation_for_contract(&mut contracts)
3847 .await?;
3848
3849 assert!(reward_claim.hasRole(pauser_role, new_pauser).call().await?);
3851
3852 Ok(())
3853 }
3854
3855 #[test_log::test(tokio::test)]
3856 async fn test_derive_timelock_address_from_contracts() -> Result<()> {
3857 use builder::DeployerArgsBuilder;
3858 use proposals::timelock::{TimelockContract, get_timelock_for_contract};
3859
3860 let (anvil, provider, _l1_client) =
3861 ProviderBuilder::new().connect_anvil_with_l1_client()?;
3862 let mut contracts = Contracts::new();
3863 let delay = U256::from(0);
3864 let provider_wallet = provider.get_accounts().await?[0];
3865 let proposers = vec![provider_wallet];
3866 let executors = vec![provider_wallet];
3867 let rpc_url = anvil.endpoint_url();
3868
3869 let ops_timelock_addr = deploy_ops_timelock(
3871 &provider,
3872 &mut contracts,
3873 delay,
3874 proposers.clone(),
3875 executors.clone(),
3876 provider_wallet,
3877 )
3878 .await?;
3879
3880 let safe_exit_timelock_addr = deploy_safe_exit_timelock(
3881 &provider,
3882 &mut contracts,
3883 delay,
3884 proposers,
3885 executors,
3886 provider_wallet,
3887 )
3888 .await?;
3889
3890 let mut args_builder = DeployerArgsBuilder::default();
3893 args_builder
3894 .deployer(provider.clone())
3895 .use_timelock_owner(true)
3896 .rpc_url(rpc_url.clone());
3897 let args = args_builder.build()?;
3898
3899 args.deploy(&mut contracts, Contract::FeeContractProxy)
3901 .await?;
3902
3903 let fee_contract_derived_timelock = derive_timelock_address_from_contract_type(
3905 OwnableContract::FeeContractProxy,
3906 &contracts,
3907 )?;
3908 assert_eq!(fee_contract_derived_timelock, ops_timelock_addr);
3909
3910 let fee_contract_addr = contracts
3912 .address(Contract::FeeContractProxy)
3913 .expect("FeeContractProxy should be deployed");
3914 let fee_contract = FeeContract::new(fee_contract_addr, &provider);
3915 let actual_owner = fee_contract.owner().call().await?;
3916 assert_eq!(
3917 actual_owner, ops_timelock_addr,
3918 "FeeContractProxy should have OpsTimelock as owner"
3919 );
3920
3921 let queried_timelock =
3922 get_timelock_for_contract(&provider, Contract::FeeContractProxy, fee_contract_addr)
3923 .await?;
3924
3925 match queried_timelock {
3926 TimelockContract::OpsTimelock(addr) => {
3927 assert_eq!(
3928 addr, ops_timelock_addr,
3929 "Queried timelock should match deployed OpsTimelock"
3930 );
3931 assert_eq!(
3932 addr, fee_contract_derived_timelock,
3933 "Queried timelock should match derived timelock"
3934 );
3935 },
3936 _ => panic!(
3937 "FeeContractProxy should use OpsTimelock, got: {:?}",
3938 queried_timelock
3939 ),
3940 }
3941
3942 let _esp_token_addr = deploy_token_proxy(
3944 &provider,
3945 &mut contracts,
3946 provider_wallet,
3947 provider_wallet,
3948 U256::from(10_000_000u64),
3949 "Test Token",
3950 "TEST",
3951 )
3952 .await?;
3953
3954 let genesis_state = LightClientStateSol::dummy_genesis();
3956 let genesis_stake = StakeTableStateSol::dummy_genesis();
3957 let admin = provider.get_accounts().await?[0];
3958 let prover = admin;
3959
3960 let _lc_proxy_addr = deploy_light_client_proxy(
3961 &provider,
3962 &mut contracts,
3963 true, genesis_state.clone(),
3965 genesis_stake.clone(),
3966 admin,
3967 Some(prover),
3968 )
3969 .await?;
3970
3971 let mut args_builder2 = DeployerArgsBuilder::default();
3972 args_builder2
3973 .deployer(provider.clone())
3974 .use_timelock_owner(true)
3975 .rpc_url(rpc_url.clone());
3976 let args2 = args_builder2.build()?;
3977
3978 args2
3979 .deploy(&mut contracts, Contract::RewardClaimProxy)
3980 .await?;
3981
3982 let reward_claim_derived_timelock = derive_timelock_address_from_contract_type(
3984 OwnableContract::RewardClaimProxy,
3985 &contracts,
3986 )?;
3987 assert_eq!(reward_claim_derived_timelock, safe_exit_timelock_addr);
3988
3989 let reward_claim_addr = contracts
3991 .address(Contract::RewardClaimProxy)
3992 .expect("RewardClaimProxy should be deployed");
3993 let reward_claim = RewardClaim::new(reward_claim_addr, &provider);
3994 let actual_owner = reward_claim.currentAdmin().call().await?;
3995 assert_eq!(
3996 actual_owner, safe_exit_timelock_addr,
3997 "RewardClaimProxy should have SafeExitTimelock as owner"
3998 );
3999
4000 let queried_timelock =
4001 get_timelock_for_contract(&provider, Contract::RewardClaimProxy, reward_claim_addr)
4002 .await?;
4003
4004 match queried_timelock {
4005 TimelockContract::SafeExitTimelock(addr) => {
4006 assert_eq!(
4007 addr, safe_exit_timelock_addr,
4008 "Queried timelock should match deployed SafeExitTimelock"
4009 );
4010 assert_eq!(
4011 addr, reward_claim_derived_timelock,
4012 "Queried timelock should match derived timelock"
4013 );
4014 },
4015 _ => panic!(
4016 "RewardClaimProxy should use SafeExitTimelock, got: {:?}",
4017 queried_timelock
4018 ),
4019 }
4020
4021 let empty_contracts = Contracts::new();
4023 let result = derive_timelock_address_from_contract_type(
4024 OwnableContract::FeeContractProxy,
4025 &empty_contracts,
4026 );
4027 assert!(
4028 result.is_err(),
4029 "Should error when timelock is missing from contracts map"
4030 );
4031 let error_msg = result.unwrap_err().to_string();
4032 assert!(
4033 error_msg.contains("not found") || error_msg.contains("OpsTimelock"),
4034 "Error should mention missing timelock, got: {}",
4035 error_msg
4036 );
4037
4038 Ok(())
4042 }
4043
4044 #[test_log::test(tokio::test)]
4045 async fn test_upgrade_esp_token_v2_with_timelock_owner() -> Result<()> {
4046 use builder::DeployerArgsBuilder;
4047
4048 let (anvil, provider, _l1_client) =
4049 ProviderBuilder::new().connect_anvil_with_l1_client()?;
4050 let mut contracts = Contracts::new();
4051 let delay = U256::from(0);
4052 let provider_wallet = provider.get_accounts().await?[0];
4053 let proposers = vec![provider_wallet];
4054 let executors = vec![provider_wallet];
4055 let rpc_url = anvil.endpoint_url();
4056
4057 let safe_exit_timelock_addr = deploy_safe_exit_timelock(
4058 &provider,
4059 &mut contracts,
4060 delay,
4061 proposers,
4062 executors,
4063 provider_wallet,
4064 )
4065 .await?;
4066
4067 let token_owner = provider_wallet;
4068 let init_recipient = provider_wallet;
4069 let initial_supply = U256::from(10_000_000u64);
4070 let token_name = "Espresso";
4071 let token_symbol = "ESP";
4072
4073 let token_proxy_addr = deploy_token_proxy(
4074 &provider,
4075 &mut contracts,
4076 token_owner,
4077 init_recipient,
4078 initial_supply,
4079 token_name,
4080 token_symbol,
4081 )
4082 .await?;
4083
4084 let fake_reward_claim = Address::random();
4085 contracts.insert(Contract::RewardClaimProxy, fake_reward_claim);
4086
4087 let mut args_builder = DeployerArgsBuilder::default();
4088 args_builder
4089 .deployer(provider.clone())
4090 .use_timelock_owner(true)
4091 .rpc_url(rpc_url);
4092 let args = args_builder.build()?;
4093
4094 args.deploy(&mut contracts, Contract::EspTokenV2).await?;
4095
4096 let esp_token_v2 = EspTokenV2::new(token_proxy_addr, &provider);
4097 assert_eq!(esp_token_v2.getVersion().call().await?, (2, 0, 0).into());
4098 assert_eq!(
4099 esp_token_v2.owner().call().await?,
4100 safe_exit_timelock_addr,
4101 "EspTokenProxy should have SafeExitTimelock as owner after V2 upgrade"
4102 );
4103
4104 Ok(())
4105 }
4106
4107 #[test_log::test(tokio::test)]
4108 async fn test_upgrade_light_client_v3_with_timelock_owner() -> Result<()> {
4109 use builder::DeployerArgsBuilder;
4110
4111 let (anvil, provider, _l1_client) =
4112 ProviderBuilder::new().connect_anvil_with_l1_client()?;
4113 let mut contracts = Contracts::new();
4114 let delay = U256::from(0);
4115 let provider_wallet = provider.get_accounts().await?[0];
4116 let proposers = vec![provider_wallet];
4117 let executors = vec![provider_wallet];
4118 let rpc_url = anvil.endpoint_url();
4119
4120 let ops_timelock_addr = deploy_ops_timelock(
4121 &provider,
4122 &mut contracts,
4123 delay,
4124 proposers,
4125 executors,
4126 provider_wallet,
4127 )
4128 .await?;
4129
4130 let genesis_state = LightClientStateSol::dummy_genesis();
4131 let genesis_stake = StakeTableStateSol::dummy_genesis();
4132
4133 let lc_proxy_addr = deploy_light_client_proxy(
4134 &provider,
4135 &mut contracts,
4136 false,
4137 genesis_state,
4138 genesis_stake,
4139 provider_wallet,
4140 Some(provider_wallet),
4141 )
4142 .await?;
4143
4144 upgrade_light_client_v2(&provider, &mut contracts, false, 10, 22).await?;
4145
4146 let mut args_builder = DeployerArgsBuilder::default();
4147 args_builder
4148 .deployer(provider.clone())
4149 .use_timelock_owner(true)
4150 .rpc_url(rpc_url);
4151 let args = args_builder.build()?;
4152
4153 args.deploy(&mut contracts, Contract::LightClientV3).await?;
4154
4155 let lc_v3 = LightClientV3::new(lc_proxy_addr, &provider);
4156 assert_eq!(lc_v3.getVersion().call().await?.majorVersion, 3);
4157 assert_eq!(
4158 lc_v3.owner().call().await?,
4159 ops_timelock_addr,
4160 "LightClientProxy should have OpsTimelock as owner after V3 upgrade"
4161 );
4162
4163 Ok(())
4164 }
4165
4166 #[test_log::test(tokio::test)]
4167 async fn test_upgrade_stake_table_v2_with_timelock_owner() -> Result<()> {
4168 use builder::DeployerArgsBuilder;
4169
4170 let (anvil, provider, _l1_client) =
4171 ProviderBuilder::new().connect_anvil_with_l1_client()?;
4172 let mut contracts = Contracts::new();
4173 let delay = U256::from(0);
4174 let provider_wallet = provider.get_accounts().await?[0];
4175 let proposers = vec![provider_wallet];
4176 let executors = vec![provider_wallet];
4177 let rpc_url = anvil.endpoint_url();
4178
4179 let ops_timelock_addr = deploy_ops_timelock(
4180 &provider,
4181 &mut contracts,
4182 delay,
4183 proposers,
4184 executors,
4185 provider_wallet,
4186 )
4187 .await?;
4188
4189 let token_proxy_addr = deploy_token_proxy(
4190 &provider,
4191 &mut contracts,
4192 provider_wallet,
4193 provider_wallet,
4194 U256::from(10_000_000u64),
4195 "Espresso",
4196 "ESP",
4197 )
4198 .await?;
4199
4200 let genesis_state = LightClientStateSol::dummy_genesis();
4201 let genesis_stake = StakeTableStateSol::dummy_genesis();
4202
4203 let lc_proxy_addr = deploy_light_client_proxy(
4204 &provider,
4205 &mut contracts,
4206 false,
4207 genesis_state,
4208 genesis_stake,
4209 provider_wallet,
4210 Some(provider_wallet),
4211 )
4212 .await?;
4213
4214 let escrow_period = U256::from(crate::DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
4215 let st_proxy_addr = deploy_stake_table_proxy(
4216 &provider,
4217 &mut contracts,
4218 token_proxy_addr,
4219 lc_proxy_addr,
4220 escrow_period,
4221 provider_wallet,
4222 )
4223 .await?;
4224
4225 let mut args_builder = DeployerArgsBuilder::default();
4226 args_builder
4227 .deployer(provider.clone())
4228 .use_timelock_owner(true)
4229 .rpc_url(rpc_url);
4230 let args = args_builder.build()?;
4231
4232 args.deploy(&mut contracts, Contract::StakeTableV2).await?;
4233
4234 let st_v2 = StakeTableV2::new(st_proxy_addr, &provider);
4235 assert_eq!(st_v2.getVersion().call().await?, (2, 0, 0).into());
4236 assert_eq!(
4237 st_v2.owner().call().await?,
4238 ops_timelock_addr,
4239 "StakeTableProxy should have OpsTimelock as owner after V2 upgrade"
4240 );
4241
4242 Ok(())
4243 }
4244
4245 #[test_log::test(tokio::test)]
4246 async fn test_perform_timelock_operation_unified() -> Result<()> {
4247 use crate::proposals::timelock::{
4248 TimelockOperationParams, TimelockOperationPayload, TimelockOperationType,
4249 perform_timelock_operation,
4250 };
4251
4252 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
4253 let mut contracts = Contracts::new();
4254 let delay = U256::from(0);
4255 let provider_wallet = provider.get_accounts().await?[0];
4256 let another_wallet = provider.get_accounts().await?[1];
4257 let proposers = vec![provider_wallet];
4258 let executors = vec![provider_wallet];
4259
4260 let timelock_addr = deploy_ops_timelock(
4261 &provider,
4262 &mut contracts,
4263 delay,
4264 proposers,
4265 executors,
4266 provider_wallet,
4267 )
4268 .await?;
4269 let timelock = OpsTimelock::new(timelock_addr, &provider);
4270
4271 let fee_contract_proxy_addr =
4272 deploy_fee_contract_proxy(&provider, &mut contracts, timelock_addr).await?;
4273 let proxy = FeeContract::new(fee_contract_proxy_addr, &provider);
4274
4275 let transfer_ownership_data = proxy
4276 .transferOwnership(another_wallet)
4277 .calldata()
4278 .to_owned();
4279
4280 assert_eq!(proxy.owner().call().await?, timelock_addr);
4281
4282 let operation = TimelockOperationPayload {
4283 target: fee_contract_proxy_addr,
4284 value: U256::ZERO,
4285 data: transfer_ownership_data.clone(),
4286 predecessor: B256::ZERO,
4287 salt: B256::ZERO,
4288 delay,
4289 };
4290
4291 let mut cancel_operation = operation.clone();
4293 cancel_operation.value = U256::from(1);
4294 let cancel_params = TimelockOperationParams::default();
4295 let cancel_operation_id = perform_timelock_operation(
4296 &provider,
4297 Contract::FeeContractProxy,
4298 cancel_operation.clone(),
4299 TimelockOperationType::Schedule,
4300 cancel_params.clone(),
4301 )
4302 .await?;
4303
4304 perform_timelock_operation(
4305 &provider,
4306 Contract::FeeContractProxy,
4307 cancel_operation,
4308 TimelockOperationType::Cancel,
4309 cancel_params,
4310 )
4311 .await?;
4312
4313 assert!(timelock.getTimestamp(cancel_operation_id).call().await? == U256::ZERO);
4314
4315 let params = TimelockOperationParams::default();
4317 let operation_id = perform_timelock_operation(
4318 &provider,
4319 Contract::FeeContractProxy,
4320 operation.clone(),
4321 TimelockOperationType::Schedule,
4322 params,
4323 )
4324 .await?;
4325
4326 assert!(timelock.isOperationPending(operation_id).call().await?);
4327
4328 let params = TimelockOperationParams::default();
4329 perform_timelock_operation(
4330 &provider,
4331 Contract::FeeContractProxy,
4332 operation.clone(),
4333 TimelockOperationType::Execute,
4334 params,
4335 )
4336 .await?;
4337
4338 assert!(timelock.isOperationDone(operation_id).call().await?);
4339
4340 assert_eq!(proxy.owner().call().await?, another_wallet);
4342 Ok(())
4343 }
4344 #[test_log::test(tokio::test)]
4345 async fn test_upgrade_fee_contract_multisig_owner() -> Result<()> {
4346 let multisig_admin = Address::random();
4347 let (anvil, provider, _l1_client) =
4348 ProviderBuilder::new().connect_anvil_with_l1_client()?;
4349 let mut contracts = Contracts::new();
4350 let admin = provider.get_accounts().await?[0];
4351
4352 let fee_contract_proxy_addr =
4354 deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
4355
4356 let _receipt = transfer_ownership(
4358 &provider,
4359 Contract::FeeContractProxy,
4360 fee_contract_proxy_addr,
4361 multisig_admin,
4362 )
4363 .await?;
4364
4365 contracts.remove(&Contract::FeeContract);
4367
4368 let calldata = upgrade_fee_contract_multisig_owner(
4370 &provider,
4371 &mut contracts,
4372 MultisigOwnerCheck::Skip,
4373 )
4374 .await?;
4375
4376 tracing::info!(
4377 "Encoded calldata for FeeContractProxy upgrade: to={:#x}, data={}",
4378 calldata.to,
4379 calldata.data
4380 );
4381
4382 assert_eq!(calldata.to, fee_contract_proxy_addr);
4383 let fee_impl_addr = contracts.address(Contract::FeeContract).unwrap_or_default();
4385 let expected = crate::encode_function_call(
4386 "upgradeToAndCall(address,bytes)",
4387 vec![fee_impl_addr.to_string(), "0x".to_string()],
4388 )
4389 .unwrap();
4390 assert_eq!(calldata.data, expected);
4391
4392 let impersonation_provider = ProviderBuilder::new()
4394 .filler(ImpersonateFiller::new(multisig_admin))
4395 .connect_http(anvil.endpoint_url());
4396 let anvil_provider = AnvilProvider::new(impersonation_provider.clone(), Arc::new(anvil));
4397 anvil_provider.anvil_auto_impersonate_account(true).await?;
4398 anvil_provider
4399 .anvil_set_balance(multisig_admin, parse_ether("100")?)
4400 .await?;
4401
4402 let tx = alloy::rpc::types::TransactionRequest::default()
4403 .to(calldata.to)
4404 .input(calldata.data.into());
4405 let receipt = impersonation_provider
4406 .send_transaction(tx)
4407 .await?
4408 .get_receipt()
4409 .await?;
4410 assert!(receipt.inner.is_success(), "upgrade tx failed");
4411
4412 let new_impl = read_proxy_impl(&impersonation_provider, fee_contract_proxy_addr).await?;
4414 assert_eq!(new_impl, fee_impl_addr, "proxy should point to new impl");
4415
4416 Ok(())
4417 }
4418
4419 #[test_log::test(tokio::test)]
4420 async fn test_upgrade_fee_contract_v1_0_1_eoa() -> Result<()> {
4421 let (_anvil, provider, _l1_client) =
4422 ProviderBuilder::new().connect_anvil_with_l1_client()?;
4423 let mut contracts = Contracts::new();
4424 let admin = provider.get_accounts().await?[0];
4425
4426 let fee_contract_proxy_addr =
4427 deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
4428 let fee_contract_proxy = FeeContract::new(fee_contract_proxy_addr, &provider);
4429 let curr_version = fee_contract_proxy.getVersion().call().await?;
4430 assert_eq!(curr_version, (1, 0, 1).into()); let cached_impl_addr = contracts.address(Contract::FeeContract);
4433
4434 contracts.remove(&Contract::FeeContract);
4436
4437 let receipt = upgrade_fee_v1(&provider, &mut contracts).await?;
4439
4440 assert!(receipt.inner.is_success());
4441
4442 let new_impl_addr = contracts.address(Contract::FeeContract);
4444 if let Some(old_addr) = cached_impl_addr {
4445 assert_ne!(
4446 old_addr,
4447 new_impl_addr.expect("New implementation should be deployed"),
4448 "New implementation should have a different address"
4449 );
4450 }
4451
4452 let new_proxy_impl = read_proxy_impl(&provider, fee_contract_proxy_addr).await?;
4454 assert_eq!(
4455 new_proxy_impl,
4456 new_impl_addr.expect("New implementation should be deployed"),
4457 "Proxy should point to the new implementation address"
4458 );
4459
4460 let new_version = fee_contract_proxy.getVersion().call().await?;
4462 assert_eq!(new_version, (1, 0, 1).into());
4463
4464 Ok(())
4465 }
4466
4467 #[test_log::test(tokio::test)]
4468 async fn test_upgrade_light_client_v2_twice_checks_impl_address() -> Result<()> {
4469 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
4470 let mut contracts = Contracts::new();
4471 let blocks_per_epoch = 10;
4472 let epoch_start_block = 22;
4473
4474 let genesis_state = LightClientStateSol::dummy_genesis();
4476 let genesis_stake = StakeTableStateSol::dummy_genesis();
4477 let admin = provider.get_accounts().await?[0];
4478 let prover = Address::random();
4479
4480 let lc_proxy_addr = deploy_light_client_proxy(
4482 &provider,
4483 &mut contracts,
4484 false,
4485 genesis_state.clone(),
4486 genesis_stake.clone(),
4487 admin,
4488 Some(prover),
4489 )
4490 .await?;
4491
4492 upgrade_light_client_v2(
4494 &provider,
4495 &mut contracts,
4496 false, blocks_per_epoch,
4498 epoch_start_block,
4499 )
4500 .await?;
4501
4502 let first_impl_addr = read_proxy_impl(&provider, lc_proxy_addr).await?;
4504
4505 let cached_impl_addr = contracts.address(Contract::LightClientV2);
4507 assert_eq!(
4508 first_impl_addr,
4509 cached_impl_addr.expect("LightClientV2 should be in cache"),
4510 "First upgrade: proxy should point to cached implementation"
4511 );
4512
4513 contracts.remove(&Contract::LightClientV2);
4516
4517 upgrade_light_client_v2(
4519 &provider,
4520 &mut contracts,
4521 false, blocks_per_epoch,
4523 epoch_start_block,
4524 )
4525 .await?;
4526
4527 let second_impl_addr = read_proxy_impl(&provider, lc_proxy_addr).await?;
4529
4530 let cached_impl_addr_after = contracts.address(Contract::LightClientV2);
4531 assert_ne!(
4532 first_impl_addr, second_impl_addr,
4533 "LightClientV2 should have been deployed again"
4534 );
4535 assert_eq!(
4536 second_impl_addr,
4537 cached_impl_addr_after.expect("LightClientV2 should still be in cache"),
4538 "Second upgrade: proxy should point to cached implementation"
4539 );
4540
4541 Ok(())
4542 }
4543
4544 #[test_log::test(tokio::test)]
4545 async fn test_encode_multisig_transaction() -> Result<()> {
4546 let (anvil, provider, _l1_client) =
4547 ProviderBuilder::new().connect_anvil_with_l1_client()?;
4548 let mut contracts = Contracts::new();
4549 let provider_wallet = provider.get_accounts().await?[0];
4550
4551 let fee_contract_proxy_addr =
4552 deploy_fee_contract_proxy(&provider, &mut contracts, provider_wallet).await?;
4553 let new_owner = Address::random();
4554
4555 use builder::DeployerArgsBuilder;
4557
4558 let mut args_builder = DeployerArgsBuilder::default();
4559 args_builder
4560 .deployer(provider.clone())
4561 .rpc_url(anvil.endpoint_url())
4562 .multisig(provider_wallet)
4563 .multisig_transaction_target(fee_contract_proxy_addr)
4564 .multisig_transaction_function_signature("transferOwnership(address)".to_string())
4565 .multisig_transaction_function_args(vec![new_owner.to_string()])
4566 .multisig_transaction_value("0".to_string());
4567
4568 let args = args_builder.build()?;
4569
4570 let result = args.encode_multisig_transaction().await;
4571
4572 match result {
4573 Ok(_) => {
4574 tracing::info!("Multisig transaction proposal succeeded in dry_run mode");
4575 tracing::info!("Result: {:?}", result);
4576 },
4577 Err(e) => {
4578 tracing::info!("Multisig transaction proposal failed: {}", e);
4579 },
4580 }
4581
4582 Ok(())
4583 }
4584
4585 #[test_log::test(tokio::test)]
4586 async fn transfer_ownership_to_timelock_target_eoa_fails() -> Result<()> {
4587 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
4588 let multisig = Address::random();
4589 let eoa_address = Address::random();
4590
4591 let mut contracts = Contracts::new();
4593 contracts.insert(Contract::OpsTimelock, eoa_address);
4594
4595 let admin = provider.get_accounts().await?[0];
4597 deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
4598
4599 let mut args_builder = DeployerArgsBuilder::default();
4600 args_builder
4601 .deployer(provider.clone())
4602 .rpc_url(Url::parse("http://localhost:8545")?)
4603 .multisig(multisig)
4604 .target_contract(OwnableContract::FeeContractProxy);
4605
4606 let args = args_builder.build()?;
4607 assert!(
4608 args.encode_transfer_ownership_to_timelock(&mut contracts)
4609 .await
4610 .unwrap_err()
4611 .to_string()
4612 .contains("Timelock address is not a contract")
4613 );
4614
4615 Ok(())
4616 }
4617}