The function executeEmergencyUpgrade
will be invoked in the following contract:
ProtocolUpgradeHandler
:0x8f7a9912416e8AdC4D9c21FAe1415D3318A11897
/// @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. 🥳🥳
I also created a Bash script to calculate the proposal ID: