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