Skip to content

Commit

Permalink
feat(eth-watch): Change protocol upgrade schema (#3435)
Browse files Browse the repository at this point in the history
## What ❔

Copies protocol upgrade schema changes from sync-layer-stable in a
non-breaking way.

## Why ❔

Support post-gateway upgrade schema.

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `zkstack dev fmt` and `zkstack dev
lint`.
  • Loading branch information
perekopskiy authored Jan 7, 2025
1 parent 0731f60 commit 2c778fd
Show file tree
Hide file tree
Showing 13 changed files with 663 additions and 136 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions core/lib/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ zksync_mini_merkle_tree.workspace = true
zksync_protobuf.workspace = true
zksync_crypto_primitives.workspace = true

async-trait.workspace = true
anyhow.workspace = true
chrono = { workspace = true, features = ["serde"] }
derive_more = { workspace = true, features = ["debug"] }
Expand Down
276 changes: 247 additions & 29 deletions core/lib/types/src/abi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Context as _;
use zksync_basic_types::protocol_version::ProtocolSemanticVersion;

use crate::{
bytecode::BytecodeHash,
Expand Down Expand Up @@ -185,17 +186,19 @@ impl NewPriorityRequest {
}

/// `VerifierParams` from `l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol`.
#[derive(Default, PartialEq)]
#[derive(Debug, Default, PartialEq)]
pub struct VerifierParams {
pub recursion_node_level_vk_hash: [u8; 32],
pub recursion_leaf_level_vk_hash: [u8; 32],
pub recursion_circuits_set_vks_hash: [u8; 32],
}

/// `ProposedUpgrade` from, `l1-contracts/contracts/upgrades/BazeZkSyncUpgrade.sol`.
#[derive(Debug)]
pub struct ProposedUpgrade {
pub l2_protocol_upgrade_tx: Box<L2CanonicalTransaction>,
pub factory_deps: Vec<Vec<u8>>,
// Factory deps are set only pre-gateway upgrades.
pub factory_deps: Option<Vec<Vec<u8>>>,
pub bootloader_hash: [u8; 32],
pub default_account_hash: [u8; 32],
pub verifier: Address,
Expand Down Expand Up @@ -250,8 +253,8 @@ impl VerifierParams {
}

impl ProposedUpgrade {
/// RLP schema of the `ProposedUpgrade`.
pub fn schema() -> ParamType {
/// Pre-gateway RLP schema of the `ProposedUpgrade`.
pub fn schema_pre_gateway() -> ParamType {
ParamType::Tuple(vec![
L2CanonicalTransaction::schema(), // transaction data
ParamType::Array(ParamType::Bytes.into()), // factory deps
Expand All @@ -266,16 +269,39 @@ impl ProposedUpgrade {
])
}

/// Post-gateway RLP schema of the `ProposedUpgrade`.
pub fn schema_post_gateway() -> ParamType {
ParamType::Tuple(vec![
L2CanonicalTransaction::schema(), // transaction data
ParamType::FixedBytes(32), // bootloader code hash
ParamType::FixedBytes(32), // default account code hash
ParamType::Address, // verifier address
VerifierParams::schema(), // verifier params
ParamType::Bytes, // l1 custom data
ParamType::Bytes, // l1 post-upgrade custom data
ParamType::Uint(256), // timestamp
ParamType::Uint(256), // version id
])
}

/// Encodes `ProposedUpgrade` to a RLP token.
pub fn encode(&self) -> Token {
Token::Tuple(vec![
self.l2_protocol_upgrade_tx.encode(),
Token::Array(
let mut tokens = vec![self.l2_protocol_upgrade_tx.encode()];

let protocol_version = ProtocolSemanticVersion::try_from_packed(self.new_protocol_version)
.expect("Version is not supported")
.minor;
if protocol_version.is_pre_gateway() {
tokens.push(Token::Array(
self.factory_deps
.iter()
.map(|b| Token::Bytes(b.clone()))
.clone()
.expect("Factory deps should be present in pre-gateway upgrade data")
.into_iter()
.map(Token::Bytes)
.collect(),
),
));
}
tokens.extend([
Token::FixedBytes(self.bootloader_hash.into()),
Token::FixedBytes(self.default_account_hash.into()),
Token::Address(self.verifier),
Expand All @@ -284,32 +310,52 @@ impl ProposedUpgrade {
Token::Bytes(self.post_upgrade_calldata.clone()),
Token::Uint(self.upgrade_timestamp),
Token::Uint(self.new_protocol_version),
])
]);

Token::Tuple(tokens)
}

/// Decodes `ProposedUpgrade` from a RLP token.
/// Returns an error if token doesn't match the `schema()`.
pub fn decode(token: Token) -> anyhow::Result<Self> {
let tokens = token.into_tuple().context("not a tuple")?;
anyhow::ensure!(tokens.len() == 10);
let tokens_len = tokens.len();
anyhow::ensure!(tokens_len >= 9);
let mut t = tokens.into_iter();
let mut next = || t.next().unwrap();
Ok(Self {
l2_protocol_upgrade_tx: L2CanonicalTransaction::decode(next())
.context("l2_protocol_upgrade_tx")?
.into(),
factory_deps: next()
.into_array()
.context("factory_deps")?
.into_iter()
.enumerate()
.map(|(i, b)| b.into_bytes().context(i))
.collect::<Result<_, _>>()
.context("factory_deps")?,
bootloader_hash: next()
.into_fixed_bytes()
.and_then(|b| b.try_into().ok())
.context("bootloader_hash")?,

let l2_protocol_upgrade_tx = L2CanonicalTransaction::decode(next())
.context("l2_protocol_upgrade_tx")?
.into();
let next_token = next();
let (factory_deps, bootloader_hash) = match next_token {
Token::Array(tokens) => {
anyhow::ensure!(tokens_len == 10);
(
Some(
tokens
.into_iter()
.enumerate()
.map(|(i, b)| b.into_bytes().context(i))
.collect::<Result<_, _>>()
.context("factory_deps")?,
),
next().into_fixed_bytes(),
)
}
Token::FixedBytes(bytes) => {
anyhow::ensure!(tokens_len == 9);
(None, Some(bytes))
}
_ => anyhow::bail!("Unexpected type of the second token"),
};
let bootloader_hash = bootloader_hash
.and_then(|b| b.try_into().ok())
.context("bootloader_hash")?;
let upgrade = Self {
l2_protocol_upgrade_tx,
factory_deps,
bootloader_hash,
default_account_hash: next()
.into_fixed_bytes()
.and_then(|b| b.try_into().ok())
Expand All @@ -322,7 +368,16 @@ impl ProposedUpgrade {
post_upgrade_calldata: next().into_bytes().context("post_upgrade_calldata")?,
upgrade_timestamp: next().into_uint().context("upgrade_timestamp")?,
new_protocol_version: next().into_uint().context("new_protocol_version")?,
})
};

let protocol_version =
ProtocolSemanticVersion::try_from_packed(upgrade.new_protocol_version)
.map_err(|err| anyhow::anyhow!(err))
.context("Version is not supported")?
.minor;
anyhow::ensure!(protocol_version.is_pre_gateway() == upgrade.factory_deps.is_some());

Ok(upgrade)
}
}

Expand Down Expand Up @@ -365,3 +420,166 @@ impl Transaction {
})
}
}

pub struct ForceDeployment {
pub bytecode_hash: H256,
pub new_address: Address,
pub call_constructor: bool,
pub value: U256,
pub input: Vec<u8>,
}

impl ForceDeployment {
/// ABI schema of the `ForceDeployment`.
pub fn schema() -> ParamType {
ParamType::Tuple(vec![
ParamType::FixedBytes(32),
ParamType::Address,
ParamType::Bool,
ParamType::Uint(256),
ParamType::Bytes,
])
}

/// Encodes `ForceDeployment` to a RLP token.
pub fn encode(&self) -> Token {
Token::Tuple(vec![
Token::FixedBytes(self.bytecode_hash.0.to_vec()),
Token::Address(self.new_address),
Token::Bool(self.call_constructor),
Token::Uint(self.value),
Token::Bytes(self.input.clone()),
])
}

/// Decodes `ForceDeployment` from a RLP token.
/// Returns an error if token doesn't match the `schema()`.
pub fn decode(token: Token) -> anyhow::Result<Self> {
let tokens = token.into_tuple().context("not a tuple")?;
anyhow::ensure!(tokens.len() == 5);
let mut t = tokens.into_iter();
let mut next = || t.next().unwrap();
Ok(Self {
bytecode_hash: next()
.into_fixed_bytes()
.and_then(|b| Some(H256(b.try_into().ok()?)))
.context("bytecode_hash")?,
new_address: next().into_address().context("new_address")?,
call_constructor: next().into_bool().context("call_constructor")?,
value: next().into_uint().context("value")?,
input: next().into_bytes().context("input")?,
})
}
}

pub struct GatewayUpgradeEncodedInput {
pub force_deployments: Vec<ForceDeployment>,
pub l2_gateway_upgrade_position: usize,
pub fixed_force_deployments_data: Vec<u8>,
pub ctm_deployer: Address,
pub old_validator_timelock: Address,
pub new_validator_timelock: Address,
pub wrapped_base_token_store: Address,
}

impl GatewayUpgradeEncodedInput {
/// ABI schema of the `GatewayUpgradeEncodedInput`.
pub fn schema() -> ParamType {
ParamType::Tuple(vec![
ParamType::Array(Box::new(ForceDeployment::schema())),
ParamType::Uint(256),
ParamType::Bytes,
ParamType::Address,
ParamType::Address,
ParamType::Address,
ParamType::Address,
])
}

/// Decodes `GatewayUpgradeEncodedInput` from a RLP token.
/// Returns an error if token doesn't match the `schema()`.
pub fn decode(token: Token) -> anyhow::Result<Self> {
let tokens = token.into_tuple().context("not a tuple")?;
anyhow::ensure!(tokens.len() == 7);
let mut t = tokens.into_iter();
let mut next = || t.next().unwrap();

let force_deployments_array = next().into_array().context("force_deployments_array")?;
let mut force_deployments = vec![];
for token in force_deployments_array {
force_deployments.push(ForceDeployment::decode(token)?);
}

Ok(Self {
force_deployments,
l2_gateway_upgrade_position: next()
.into_uint()
.context("l2_gateway_upgrade_position")?
.as_usize(),
fixed_force_deployments_data: next()
.into_bytes()
.context("fixed_force_deployments_data")?,
ctm_deployer: next().into_address().context("ctm_deployer")?,
old_validator_timelock: next().into_address().context("old_validator_timelock")?,
new_validator_timelock: next().into_address().context("new_validator_timelock")?,
wrapped_base_token_store: next().into_address().context("wrapped_base_token_store")?,
})
}
}

#[derive(Debug, Clone)]
pub struct ZkChainSpecificUpgradeData {
pub base_token_asset_id: H256,
pub l2_legacy_shared_bridge: Address,
pub predeployed_l2_weth_address: Address,
pub base_token_l1_address: Address,
pub base_token_name: String,
pub base_token_symbol: String,
}

impl ZkChainSpecificUpgradeData {
pub fn from_partial_components(
base_token_asset_id: Option<H256>,
l2_legacy_shared_bridge: Option<Address>,
predeployed_l2_weth_address: Option<Address>,
base_token_l1_address: Option<Address>,
base_token_name: Option<String>,
base_token_symbol: Option<String>,
) -> Option<Self> {
Some(Self {
base_token_asset_id: base_token_asset_id?,
l2_legacy_shared_bridge: l2_legacy_shared_bridge?,
// Note, that some chains may not contain previous deployment of L2 wrapped base
// token. For those, zero address is used.
predeployed_l2_weth_address: predeployed_l2_weth_address.unwrap_or_default(),
base_token_l1_address: base_token_l1_address?,
base_token_name: base_token_name?,
base_token_symbol: base_token_symbol?,
})
}

/// ABI schema of the `ZkChainSpecificUpgradeData`.
pub fn schema() -> ParamType {
ParamType::Tuple(vec![
ParamType::FixedBytes(32),
ParamType::Address,
ParamType::Address,
])
}

/// Encodes `ZkChainSpecificUpgradeData` to a RLP token.
pub fn encode(&self) -> Token {
Token::Tuple(vec![
Token::FixedBytes(self.base_token_asset_id.0.to_vec()),
Token::Address(self.l2_legacy_shared_bridge),
Token::Address(self.predeployed_l2_weth_address),
Token::Address(self.base_token_l1_address),
Token::String(self.base_token_name.clone()),
Token::String(self.base_token_symbol.clone()),
])
}

pub fn encode_bytes(&self) -> Vec<u8> {
ethabi::encode(&[self.encode()])
}
}
Loading

0 comments on commit 2c778fd

Please sign in to comment.