Skip to content

Instantly share code, notes, and snippets.

@pcaversaccio
Last active September 12, 2024 19:12
Show Gist options
  • Save pcaversaccio/0ef8fb8034594e012a4903dfa992369e to your computer and use it in GitHub Desktop.
Save pcaversaccio/0ef8fb8034594e012a4903dfa992369e to your computer and use it in GitHub Desktop.
Calculate the proposal ID for the ZKsync emergency upgrade.

Proposal ID Calculation for executeEmergencyUpgrade

The function executeEmergencyUpgrade will be invoked in the following contract:

/// @dev Represents a call to be made during an upgrade.
/// @param target The address to which the call will be made.
/// @param value The amount of Ether (in wei) to be sent along with the call.
/// @param data The calldata to be executed on the `target` address.
struct Call {
    address target;
    uint256 value;
    bytes data;
}

/// @dev Defines the structure of an upgrade that is executed by Protocol Upgrade Handler.
/// @param executor The L1 address that is authorized to perform the upgrade execution (if address(0) then anyone).
/// @param calls An array of `Call` structs, each representing a call to be made during the upgrade execution.
/// @param salt A bytes32 value used for creating unique upgrade proposal hashes.
struct UpgradeProposal {
    Call[] calls;
    address executor;
    bytes32 salt;
}

/// @notice Executes an emergency upgrade proposal initiated by the emergency upgrade board.
/// @param _proposal The upgrade proposal details including proposed actions and the executor address.
function executeEmergencyUpgrade(UpgradeProposal calldata _proposal) external payable onlyEmergencyUpgradeBoard {
    bytes32 id = keccak256(abi.encode(_proposal));
    UpgradeState upgState = upgradeState(id);
    // 1. Checks
    require(upgState == UpgradeState.None, "Upgrade already exists");
    require(_proposal.executor == msg.sender, "msg.sender is not authorized to perform the upgrade");
    // 2. Effects
    upgradeStatus[id].executed = true;
    // Clear the freeze
    lastFreezeStatusInUpgradeCycle = FreezeStatus.None;
    protocolFrozenUntil = 0;
    _unfreeze();
    // 3. Interactions
    _execute(_proposal.calls);
    emit Unfreeze();
    emit EmergencyUpgradeExecuted(id);
}

In order the retrieve the proposal ID, we need to calculate:

keccak256(abi.encode(_proposal));

The proposal ID, given by the ZKsync Era UI is 0xdd9aadc3b6e3297fed40a2cf0a7e655ff5af02c9ce918ed0e86f538c1c53ce9d. So we need to verify that one.

From the docs here, we know:

bytes32 salt = 0x646563656e7472616c697a6174696f6e206973206e6f74206f7074696f6e616c

The executor in our case is 0xdEFd1eDEE3E8c5965216bd59C866f7f5307C9b29, the EmergencyUpgradeBoard contract.

Save the following Solidity code as ProposalId.sol in your root directory.

// SPDX-License-Identifier: WTFPL
pragma solidity ^0.8.27;

contract ProposalId {
    struct Call {
        address target;
        uint256 value;
        bytes data;
    }

    struct UpgradeProposal {
        Call[] calls;
        address executor;
        bytes32 salt;
    }

    function computeProposalId() external pure returns (bytes32) {
        Call[] memory calls = new Call[](4);
        calls[0] = Call({target: 0xD7f9f54194C633F36CCD5F3da84ad4a1c38cB2cB, value: 0, data: hex"79ba5097"});
        calls[1] = Call({target: 0x303a465B659cBB0ab36eE643eA362c509EEb5213, value: 0, data: hex"79ba5097"});
        calls[2] = Call({target: 0xc2eE6b6af7d616f6e27ce7F4A451Aedc2b0F5f5C, value: 0, data: hex"79ba5097"});
        calls[3] = Call({target: 0x5D8ba173Dc6C3c90C8f7C04C9288BeF5FDbAd06E, value: 0, data: hex"79ba5097"});

        address executor = 0xdEFd1eDEE3E8c5965216bd59C866f7f5307C9b29;
        bytes32 salt = hex"646563656e7472616c697a6174696f6e206973206e6f74206f7074696f6e616c";

        UpgradeProposal memory upgradeProposal = UpgradeProposal({calls: calls, executor: executor, salt: salt});

        return keccak256(abi.encode(upgradeProposal));
    }
}

Now invoke:

forge script ProposalId.sol --target-contract ProposalId --sig "computeProposalId()"

which will output:

== Return ==
0: bytes32 0xdd9aadc3b6e3297fed40a2cf0a7e655ff5af02c9ce918ed0e86f538c1c53ce9d

Q.E.D. 🥳🥳

@pcaversaccio
Copy link
Author

I also created a Bash script to calculate the proposal ID:

#!/bin/bash

# Define the parameters for the `UpgradeProposal` struct.
CALLS="[(0xD7f9f54194C633F36CCD5F3da84ad4a1c38cB2cB,0,0x79ba5097),(0x303a465B659cBB0ab36eE643eA362c509EEb5213,0,0x79ba5097),(0xc2eE6b6af7d616f6e27ce7F4A451Aedc2b0F5f5C,0,0x79ba5097),(0x5D8ba173Dc6C3c90C8f7C04C9288BeF5FDbAd06E,0,0x79ba5097)]"

EXECUTOR="0xdEFd1eDEE3E8c5965216bd59C866f7f5307C9b29"

# Salt value ("decentralization is not optional") as a hex string.
SALT="0x646563656e7472616c697a6174696f6e206973206e6f74206f7074696f6e616c"

# Encode the `UpgradeProposal` struct.
ENCODED_PROPOSAL=$(cast abi-encode "UpgradeProposal(((address,uint256,bytes)[],address,bytes32))" "($CALLS,$EXECUTOR,$SALT)")

# Compute the `keccak256` hash of the encoded proposal.
PROPOSAL_ID=$(cast keccak "$ENCODED_PROPOSAL")

# Save the proposal ID to a file.
echo "$PROPOSAL_ID" > proposal_id.txt

# Output the result.
echo "Encoded \`UpgradeProposal\` struct: $ENCODED_PROPOSAL"
echo "Proposal ID: $PROPOSAL_ID"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment