Skip to content

Commit

Permalink
Merge pull request #9 from Cyfrin/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ubermensch3dot0 authored Mar 10, 2025
2 parents 85f2e48 + 5ac542b commit fcbd121
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zkgov-check"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
authors = ["ubermensch3dot0"]
description = "ZkSync Upgrade Verification Tool"
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ zkgov-check get-upgrades <tx-hash> --rpc-url $ZKSYNC_RPC_URL --decode
```
**Output**: A detailed list of targets, values, and calldata, plus any Ethereum transactions if `sendToL1` is called.

### 3. Compute the Ethereum Proposal ID
### 3. Compute the Ethereum Proposal ID from a transaction hash:
Generate the Ethereum-side proposal hash for verification:
```bash
zkgov-check get-eth-id <tx-hash> --rpc-url $ZKSYNC_RPC_URL
Expand All @@ -79,6 +79,18 @@ zkgov-check get-eth-id <tx-hash> --rpc-url $ZKSYNC_RPC_URL
Ethereum proposal ID #1: 0x5ebd899d...
```

### 4. Compute the Ethereum Proposal ID from a proposale file (json):
Generate the Ethereum-side proposal hash for verification:
```bash
zkgov-check get-eth-id --from-file <json-file>
```
**Output**: The Keccak-256 hash for the calls in the json file (see the file structure [here](/test-data/zip5.json)), e.g.:
```
Ethereum Proposal ID
Proposal ID: 0x123456...
Encoded Proposal: 0x0000...
```

## Practical Examples

Using the ZKsync Upgrade Verification Tool can significantly enhance the security of governance operations. Below is a step-by-step guide for verifying a proposal like [ZIP-7](https://www.tally.xyz/gov/zksync/proposal/53064417471903525695516096129021600825622830249245179379231067906906888383956):
Expand Down
96 changes: 87 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ use eyre::{eyre, Result};
use clap::{Parser, Subcommand};
use std::env;
use std::str::FromStr;
use std::fs;
use serde::{Deserialize, Serialize};
use reqwest;

const VERSION: &str = "0.0.6";
const VERSION: &str = "0.0.2";
const PROPOSAL_CREATED_TOPIC: &str = "0x7d84a6263ae0d98d3329bd7b46bb4e8d6f98cd35a7adb45c274c8b7fd5ebd5e0";
const DEFAULT_GOVERNOR: &str = "0x76705327e682F2d96943280D99464Ab61219e34f";

Expand All @@ -22,21 +24,49 @@ struct Cli {
#[command(subcommand)]
command: Commands,

#[arg(long, global = true)]
#[arg(long, global = true, help = "RPC URL for zkSync Era (can also be set via ZKSYNC_RPC_URL env var)")]
rpc_url: Option<String>,

#[arg(long, global = true, default_value = DEFAULT_GOVERNOR)]
#[arg(long, global = true, default_value = DEFAULT_GOVERNOR, help = "Governor contract address")]
governor: String,

#[arg(long, global = true)]
#[arg(long, global = true, help = "Decode calldata for each transaction")]
decode: bool,
}

#[derive(Subcommand)]
enum Commands {
GetZkId { tx_hash: String },
GetUpgrades { tx_hash: String },
GetEthId { tx_hash: String },
#[command(about = "Get the zkSync proposal ID from a transaction hash")]
GetZkId {
#[arg(help = "Transaction hash to analyze")]
tx_hash: String
},
#[command(about = "List proposal actions and Ethereum transactions")]
GetUpgrades {
#[arg(help = "Transaction hash to analyze")]
tx_hash: String
},
#[command(about = "Compute the Ethereum proposal ID from a transaction hash or JSON file")]
GetEthId {
#[arg(help = "Transaction hash to analyze (not required if --from-file is used)")]
tx_hash: Option<String>,
#[arg(long, help = "Path to a JSON file containing proposal data")]
from_file: Option<String>,
},
}

#[derive(Serialize, Deserialize, Debug)]
struct ProposalCall {
target: String,
value: String,
data: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct ProposalData {
executor: String,
salt: String,
calls: Vec<ProposalCall>,
}

async fn get_provider(rpc_url: &str) -> Result<Provider<Http>> {
Expand Down Expand Up @@ -465,6 +495,48 @@ pub async fn get_eth_id(tx_hash: &str, rpc_url: &str, _governor: &str) -> Result
Ok(())
}

pub async fn get_eth_id_from_file(file_path: &str) -> Result<()> {
// Read and parse the JSON file
let file_content = fs::read_to_string(file_path)?;
let proposal: ProposalData = serde_json::from_str(&file_content)?;

// Convert the proposal data into the format needed for encoding
let mut call_tokens = Vec::new();
for call in proposal.calls.iter() {
let target = Address::from_str(&call.target)?;
let value = U256::from_str(&call.value)?;
let data = hex::decode(call.data.trim_start_matches("0x"))?;

call_tokens.push(Token::Tuple(vec![
Token::Address(target),
Token::Uint(value),
Token::Bytes(data),
]));
}

let executor = Address::from_str(&proposal.executor)?;
let salt = hex::decode(proposal.salt.trim_start_matches("0x"))?;
if salt.len() != 32 {
return Err(eyre!("Salt must be 32 bytes"));
}

let proposal_token = Token::Tuple(vec![
Token::Array(call_tokens),
Token::Address(executor),
Token::FixedBytes(salt),
]);

// Encode and hash the proposal
let encoded_proposal = encode(&[proposal_token]);
let hash = keccak256(&encoded_proposal);

print_header("Ethereum Proposal ID");
print_field("Proposal ID", &format!("0x{}", hex::encode(hash)));
print_field("Encoded Proposal", &format!("0x{}", hex::encode(&encoded_proposal)));

Ok(())
}

fn print_header(header: &str) {
println!("\n{}", header.underline().bold());
}
Expand Down Expand Up @@ -494,8 +566,14 @@ async fn main() -> Result<()> {
Commands::GetUpgrades { tx_hash } => {
get_upgrades(&tx_hash, &rpc_url, cli.decode).await?;
}
Commands::GetEthId { tx_hash } => {
get_eth_id(&tx_hash, &rpc_url, &cli.governor).await?;
Commands::GetEthId { tx_hash, from_file } => {
if let Some(file_path) = from_file {
get_eth_id_from_file(&file_path).await?;
} else if let Some(tx_hash) = tx_hash {
get_eth_id(&tx_hash, &rpc_url, &cli.governor).await?;
} else {
return Err(eyre!("{}", "Either --from-file or transaction hash must be provided".red().bold()));
}
}
}
Ok(())
Expand Down
31 changes: 31 additions & 0 deletions test-data/zip5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"executor": "0xECE8e30bFc92c2A8e11e6cb2e17B70868572E3f6",
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"calls": [
{
"target": "0x303a465b659cbb0ab36ee643ea362c509eeb5213",
"value": "0x00",
"data": "0x79ba5097"
},
{
"target": "0xc2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c",
"value": "0x00",
"data": "0x79ba5097"
},
{
"target": "0xd7f9f54194c633f36ccd5f3da84ad4a1c38cb2cb",
"value": "0x00",
"data": "0x79ba5097"
},
{
"target": "0x5d8ba173dc6c3c90c8f7c04c9288bef5fdbad06e",
"value": "0x00",
"data": "0x79ba5097"
},
{
"target": "0xf553e6d903aa43420ed7e3bc2313be9286a8f987",
"value": "0x00",
"data": "0x79ba5097"
}
]
}

0 comments on commit fcbd121

Please sign in to comment.