Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(zk_toolbox): resume functionality #2376

Merged
merged 6 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts
16 changes: 13 additions & 3 deletions zk_toolbox/crates/common/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
ffi::OsStr,
fmt::{Display, Formatter},
io,
process::{Command, Output, Stdio},
string::FromUtf8Error,
Expand All @@ -21,10 +22,19 @@ pub struct Cmd<'a> {
}

#[derive(thiserror::Error, Debug)]
#[error("Cmd error: {source} {stderr:?}")]
pub struct CmdError {
stderr: Option<String>,
source: anyhow::Error,
pub stderr: Option<String>,
pub source: anyhow::Error,
}

impl Display for CmdError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut data = format!("{}", &self.source);
if let Some(stderr) = &self.stderr {
data = format!("{data}\n{stderr}");
}
write!(f, "{}", data)
}
}

impl From<xshell::Error> for CmdError {
Expand Down
70 changes: 66 additions & 4 deletions zk_toolbox/crates/common/src/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ use std::{

use clap::{Parser, ValueEnum};
use ethers::{
core::types::Bytes,
middleware::Middleware,
prelude::{LocalWallet, Signer},
types::{Address, H256, U256},
utils::hex::ToHex,
utils::{hex, hex::ToHex},
};
use serde::{Deserialize, Serialize};
use strum_macros::Display;
use xshell::{cmd, Shell};

use crate::{cmd::Cmd, ethereum::create_ethers_client};
use crate::{
cmd::{Cmd, CmdResult},
ethereum::create_ethers_client,
};

/// Forge is a wrapper around the forge binary.
pub struct Forge {
Expand Down Expand Up @@ -54,8 +58,24 @@ impl ForgeScript {
pub fn run(mut self, shell: &Shell) -> anyhow::Result<()> {
let _dir_guard = shell.push_dir(&self.base_path);
let script_path = self.script_path.as_os_str();
let args = self.args.build();
Ok(Cmd::new(cmd!(shell, "forge script {script_path} --legacy {args...}")).run()?)
let args_no_resume = self.args.build();
if self.args.resume {
let mut args = args_no_resume.clone();
args.push(ForgeScriptArg::Resume.to_string());
let res = Cmd::new(cmd!(shell, "forge script {script_path} --legacy {args...}")).run();
if !res.resume_not_successful_because_has_not_began() {
return Ok(res?);
}
}
let res = Cmd::new(cmd!(
shell,
"forge script {script_path} --legacy {args_no_resume...}"
))
.run();
if res.proposal_error() {
return Ok(());
}
Ok(res?)
}

pub fn wallet_args_passed(&self) -> bool {
Expand Down Expand Up @@ -87,6 +107,13 @@ impl ForgeScript {
self
}

pub fn with_calldata(mut self, calldata: &Bytes) -> Self {
self.args.add_arg(ForgeScriptArg::Sig {
sig: hex::encode(calldata),
});
self
}

/// Makes sure a transaction is sent, only after its previous one has been confirmed and succeeded.
pub fn with_slow(mut self) -> Self {
self.args.add_arg(ForgeScriptArg::Slow);
Expand Down Expand Up @@ -208,6 +235,7 @@ pub enum ForgeScriptArg {
url: String,
},
Verify,
Resume,
}

/// ForgeScriptArgs is a set of arguments that can be passed to the forge script command.
Expand All @@ -229,6 +257,8 @@ pub struct ForgeScriptArgs {
/// Verifier API key
#[clap(long)]
pub verifier_api_key: Option<String>,
#[clap(long)]
pub resume: bool,
/// List of additional arguments that can be passed through the CLI.
///
/// e.g.: `zk_inception init -a --private-key=<PRIVATE_KEY>`
Expand Down Expand Up @@ -348,3 +378,35 @@ pub enum ForgeVerifier {
Blockscout,
Oklink,
}

// Trait for handling forge errors. Required for implementing method for CmdResult
trait ForgeErrorHandler {
// Resume doesn't work if the forge script has never been started on this chain before.
// So we want to catch it and try again without resume arg if it's the case
fn resume_not_successful_because_has_not_began(&self) -> bool;
// Catch the error if upgrade tx has already been processed. We do execute much of
// txs using upgrade mechanism and if this particular upgrade has already been processed we could assume
// it as a success
fn proposal_error(&self) -> bool;
}

impl ForgeErrorHandler for CmdResult<()> {
fn resume_not_successful_because_has_not_began(&self) -> bool {
let text = "Deployment not found for chain";
check_error(self, text)
}

fn proposal_error(&self) -> bool {
let text = "revert: Operation with this proposal id already exists";
check_error(self, text)
}
}

fn check_error(cmd_result: &CmdResult<()>, error_text: &str) -> bool {
if let Err(cmd_error) = &cmd_result {
if let Some(stderr) = &cmd_error.stderr {
return stderr.contains(error_text);
}
}
false
}
76 changes: 36 additions & 40 deletions zk_toolbox/crates/zk_inception/src/accept_ownership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@ use common::{
forge::{Forge, ForgeScript, ForgeScriptArgs},
spinner::Spinner,
};
use config::{
forge_interface::{
accept_ownership::AcceptOwnershipInput, script_params::ACCEPT_GOVERNANCE_SCRIPT_PARAMS,
},
traits::SaveConfig,
EcosystemConfig,
use config::{forge_interface::script_params::ACCEPT_GOVERNANCE_SCRIPT_PARAMS, EcosystemConfig};
use ethers::{
abi::parse_abi,
contract::BaseContract,
types::{Address, H256},
};
use ethers::types::{Address, H256};
use lazy_static::lazy_static;
use xshell::Shell;

use crate::{
messages::MSG_ACCEPTING_GOVERNANCE_SPINNER,
utils::forge::{check_the_balance, fill_forge_private_key},
};

lazy_static! {
static ref ACCEPT_ADMIN: BaseContract = BaseContract::from(
parse_abi(&[
"function acceptOwner(address governor, address target) public",
"function acceptAdmin(address governor, address target) public"
])
.unwrap(),
);
}

pub async fn accept_admin(
shell: &Shell,
ecosystem_config: &EcosystemConfig,
Expand All @@ -26,6 +35,15 @@ pub async fn accept_admin(
forge_args: &ForgeScriptArgs,
l1_rpc_url: String,
) -> anyhow::Result<()> {
// Resume for accept admin doesn't work properly. Foundry assumes that if signature of the function is the same,
// than it's the same call, but because we are calling this function multiple times during the init process,
// code assumes that doing only once is enough, but actually we need to accept admin multiple times
let mut forge_args = forge_args.clone();
forge_args.resume = false;

let calldata = ACCEPT_ADMIN
.encode("acceptAdmin", (governor_contract, target_address))
.unwrap();
let foundry_contracts_path = ecosystem_config.path_to_foundry();
let forge = Forge::new(&foundry_contracts_path)
.script(
Expand All @@ -35,16 +53,8 @@ pub async fn accept_admin(
.with_ffi()
.with_rpc_url(l1_rpc_url)
.with_broadcast()
.with_signature("acceptAdmin()");
accept_ownership(
shell,
ecosystem_config,
governor_contract,
governor,
target_address,
forge,
)
.await
.with_calldata(&calldata);
accept_ownership(shell, governor, forge).await
}

pub async fn accept_owner(
Expand All @@ -56,6 +66,13 @@ pub async fn accept_owner(
forge_args: &ForgeScriptArgs,
l1_rpc_url: String,
) -> anyhow::Result<()> {
// resume doesn't properly work here.
let mut forge_args = forge_args.clone();
forge_args.resume = false;

let calldata = ACCEPT_ADMIN
.encode("acceptOwner", (governor_contract, target_address))
.unwrap();
let foundry_contracts_path = ecosystem_config.path_to_foundry();
let forge = Forge::new(&foundry_contracts_path)
.script(
Expand All @@ -65,37 +82,16 @@ pub async fn accept_owner(
.with_ffi()
.with_rpc_url(l1_rpc_url)
.with_broadcast()
.with_signature("acceptOwner()");
accept_ownership(
shell,
ecosystem_config,
governor_contract,
governor,
target_address,
forge,
)
.await
.with_calldata(&calldata);
accept_ownership(shell, governor, forge).await
}

async fn accept_ownership(
shell: &Shell,
ecosystem_config: &EcosystemConfig,
governor_contract: Address,
governor: Option<H256>,
target_address: Address,
mut forge: ForgeScript,
) -> anyhow::Result<()> {
let input = AcceptOwnershipInput {
target_addr: target_address,
governor: governor_contract,
};
input.save(
shell,
ACCEPT_GOVERNANCE_SCRIPT_PARAMS.input(&ecosystem_config.link_to_code),
)?;

forge = fill_forge_private_key(forge, governor)?;

check_the_balance(&forge).await?;
let spinner = Spinner::new(MSG_ACCEPTING_GOVERNANCE_SPINNER);
forge.run(shell)?;
Expand Down
Loading