Skip to content

feat: multichain deploy scripts #1487

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

Open
wants to merge 17 commits into
base: release-dev/multichain
Choose a base branch
from
Open
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 .github/workflows/validate-deployment-scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
- name: Validate Solidity Scripts
run: |
# Find all .sol files under /script/releases
RELEASE_FILES=$(find script/releases -type f -name "*.sol" ! -name "Env.sol" 2>/dev/null || echo "")
RELEASE_FILES=$(find script/releases -type f -name "*.sol" ! -name "Env.sol" ! -name "CrosschainDeployLib.sol" 2>/dev/null || echo "")

# Combine file lists
FILES="$RELEASE_FILES"
Expand Down
130 changes: 130 additions & 0 deletions script/releases/CrosschainDeployLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "src/test/mocks/EmptyContract.sol";

/// @dev https://github.com/pcaversaccio/createx/tree/main
ICreateX constant createx = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);

interface ICreateX {
function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address newContract);
function computeCreate2Address(
bytes32 salt,
bytes32 initCodeHash
) external view returns (address computedAddress);
}

library CrosschainDeployLib {
using CrosschainDeployLib for *;

/// -----------------------------------------------------------------------
/// Write
/// -----------------------------------------------------------------------

/*
* @notice Deploys a crosschain empty contract.
* @dev The empty contract MUST stay consistent across all chains/deployments.
* @dev The empty contract MUST always be deployed with the same salt.
*/
function deployEmptyContract(
address deployer
) internal returns (address) {
return _deployCrosschain(deployer, type(EmptyContract).creationCode, type(EmptyContract).name);
}

/*
* @notice Deploys a crosschain `TransparentUpgradeableProxy` using CreateX.
* @dev The initial admin is the deployer.
* @dev The implementation MUST also be deterministic to ensure the contract can be deployed on all chains.
* @dev The salt MUST be unique for each proxy deployment sharing the same implementation otherwise address collisions WILL occur.
* @dev The `admin` is also assumed to be the deployer.
*
* @dev Example usage:
* ```solidity
* bytes11 salt = bytes11(uint88(0xffffffffffffffffffffff));
* address emptyContract = type(EmptyContract).creationCode.deployCrosschain(deployer);
* address proxy = emptyContract.deployCrosschainProxy(deployer, salt);
* ITransparentUpgradeableProxy(address(proxy)).upgradeTo(address(implementation));
* ITransparentUpgradeableProxy(address(proxy)).changeAdmin(address(admin));
* ```
*/
function deployCrosschainProxy(
address adminAndDeployer,
address implementation,
string memory name
) internal returns (ITransparentUpgradeableProxy) {
return ITransparentUpgradeableProxy(
_deployCrosschain(adminAndDeployer, computeUpgradeableProxyInitCode(implementation, adminAndDeployer), name)
);
}

/*
* @notice Deploys a crosschain contract with CreateX.
*
* @dev Example usage:
* ```solidity
* type(EmptyContract).creationCode.deployCrosschain(deployer, EMPTY_CONTRACT_SALT)
* ```
*/
function _deployCrosschain(address deployer, bytes memory initCode, string memory name) private returns (address) {
return createx.deployCreate2(computeProtectedSalt(deployer, name), initCode);
}

/// -----------------------------------------------------------------------
/// Helpers
/// -----------------------------------------------------------------------

/*
* @notice Returns an encoded CreateX salt.
* @dev The deployer is the only account that can use this salt via CreateX hence the name "protected".
* @dev The salt is structured as: Deployer EOA (20 bytes) | Cross-chain flag (1 byte) | Entropy (11 bytes)
* @dev Example: 0xbebebebebebebebebebebebebebebebebebebebe|ff|1212121212121212121212
*/
function computeProtectedSalt(address deployer, bytes11 salt) internal pure returns (bytes32) {
return bytes32(
bytes.concat(
bytes20(deployer),
bytes1(uint8(0)), // Cross-chain redeploy protection enabled (0: false, 1: true)
bytes11(salt)
)
);
}

/// @dev Helper to compute the protected salt for a given name.
function computeProtectedSalt(address deployer, string memory name) internal pure returns (bytes32) {
return computeProtectedSalt(deployer, bytes11(keccak256(bytes(name))));
}

/*
* @notice Returns the initialization code for a transparent upgradeable proxy.
* @dev The returned init code does not include metadata typically appended by the compiler.
*/
function computeUpgradeableProxyInitCode(
address implementation,
address admin
) internal pure returns (bytes memory) {
return abi.encodePacked(type(TransparentUpgradeableProxy).creationCode, abi.encode(implementation, admin, ""));
}

function computeCrosschainAddress(
address deployer,
bytes32 initCodeHash,
string memory name
) internal view returns (address) {
return createx.computeCreate2Address(computeProtectedSalt(deployer, name), initCodeHash);
}

/*
* @notice Returns the predicted address of a `TransparentUpgradeableProxy` deployed with CreateX.
*/
function computeCrosschainUpgradeableProxyAddress(
address adminAndDeployer,
address implementation,
string memory name
) internal view returns (address) {
return computeCrosschainAddress(
adminAndDeployer, keccak256(computeUpgradeableProxyInitCode(implementation, adminAndDeployer)), name
);
}
}
142 changes: 142 additions & 0 deletions script/releases/Env.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "zeus-templates/utils/ZEnvHelpers.sol";

import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

/// core/
import "src/contracts/core/AllocationManager.sol";
Expand All @@ -14,6 +16,7 @@ import "src/contracts/core/DelegationManager.sol";
import "src/contracts/core/RewardsCoordinator.sol";
import "src/contracts/interfaces/IRewardsCoordinator.sol";
import "src/contracts/core/StrategyManager.sol";
import "src/contracts/core/ReleaseManager.sol";

/// slashEscrow/
import "src/contracts/core/SlashEscrow.sol";
Expand All @@ -22,6 +25,7 @@ import "src/contracts/core/SlashEscrowFactory.sol";
/// permissions/
import "src/contracts/permissions/PauserRegistry.sol";
import "src/contracts/permissions/PermissionController.sol";
import "src/contracts/permissions/KeyRegistrar.sol";

/// pods/
import "src/contracts/pods/EigenPod.sol";
Expand All @@ -39,6 +43,15 @@ import "src/contracts/interfaces/IBackingEigen.sol";
import "src/contracts/token/Eigen.sol";
import "src/contracts/token/BackingEigen.sol";

/// multichain/
import "src/contracts/multichain/CrossChainRegistry.sol";
import "src/contracts/multichain/OperatorTableUpdater.sol";
import "src/contracts/multichain/ECDSACertificateVerifier.sol";
import "src/contracts/multichain/BN254CertificateVerifier.sol";

// For destination chains
import "src/test/mocks/EmptyContract.sol";

library Env {
using ZEnvHelpers for *;

Expand Down Expand Up @@ -92,6 +105,14 @@ library Env {
return _envAddress("pauserMultisig");
}

function multichainDeployerMultisig() internal view returns (address) {
return _envAddress("multichainDeployerMultisig");
}

function createX() internal view returns (address) {
return _envAddress("createX");
}

function proxyAdmin() internal view returns (address) {
return _envAddress("proxyAdmin");
}
Expand Down Expand Up @@ -160,6 +181,18 @@ library Env {
return _envU32("SLASH_ESCROW_DELAY");
}

function CROSS_CHAIN_REGISTRY_PAUSE_STATUS() internal view returns (uint256) {
return _envU256("CROSS_CHAIN_REGISTRY_INIT_PAUSE_STATUS");
}

function isSourceChain() internal view returns (bool) {
return _envBool("SOURCE_CHAIN");
}

function isDestinationChain() internal view returns (bool) {
return _envBool("DESTINATION_CHAIN");
}

/**
* core/
*/
Expand Down Expand Up @@ -223,6 +256,18 @@ library Env {
return StrategyManager(_deployedImpl(type(StrategyManager).name));
}

function releaseManager(
DeployedProxy
) internal view returns (ReleaseManager) {
return ReleaseManager(_deployedProxy(type(ReleaseManager).name));
}

function releaseManager(
DeployedImpl
) internal view returns (ReleaseManager) {
return ReleaseManager(_deployedImpl(type(ReleaseManager).name));
}

/**
* permissions/
*/
Expand All @@ -244,6 +289,18 @@ library Env {
return PermissionController(_deployedImpl(type(PermissionController).name));
}

function keyRegistrar(
DeployedProxy
) internal view returns (KeyRegistrar) {
return KeyRegistrar(_deployedProxy(type(KeyRegistrar).name));
}

function keyRegistrar(
DeployedImpl
) internal view returns (KeyRegistrar) {
return KeyRegistrar(_deployedImpl(type(KeyRegistrar).name));
}

/**
* pods/
*/
Expand Down Expand Up @@ -378,6 +435,63 @@ library Env {
return IBackingEigen(_deployedImpl(type(BackingEigen).name));
}

/**
* multichain/
*/
function emptyContract(
DeployedImpl
) internal view returns (EmptyContract) {
return EmptyContract(_deployedImpl(type(EmptyContract).name));
}

function crossChainRegistry(
DeployedProxy
) internal view returns (CrossChainRegistry) {
return CrossChainRegistry(_deployedProxy(type(CrossChainRegistry).name));
}

function crossChainRegistry(
DeployedImpl
) internal view returns (CrossChainRegistry) {
return CrossChainRegistry(_deployedImpl(type(CrossChainRegistry).name));
}

function operatorTableUpdater(
DeployedProxy
) internal view returns (OperatorTableUpdater) {
return OperatorTableUpdater(_deployedProxy(type(OperatorTableUpdater).name));
}

function operatorTableUpdater(
DeployedImpl
) internal view returns (OperatorTableUpdater) {
return OperatorTableUpdater(_deployedImpl(type(OperatorTableUpdater).name));
}

function ecdsaCertificateVerifier(
DeployedProxy
) internal view returns (ECDSACertificateVerifier) {
return ECDSACertificateVerifier(_deployedProxy(type(ECDSACertificateVerifier).name));
}

function ecdsaCertificateVerifier(
DeployedImpl
) internal view returns (ECDSACertificateVerifier) {
return ECDSACertificateVerifier(_deployedImpl(type(ECDSACertificateVerifier).name));
}

function bn254CertificateVerifier(
DeployedProxy
) internal view returns (BN254CertificateVerifier) {
return BN254CertificateVerifier(_deployedProxy(type(BN254CertificateVerifier).name));
}

function bn254CertificateVerifier(
DeployedImpl
) internal view returns (BN254CertificateVerifier) {
return BN254CertificateVerifier(_deployedImpl(type(BN254CertificateVerifier).name));
}

/**
* Helpers
*/
Expand Down Expand Up @@ -439,6 +553,12 @@ library Env {
return ZEnvHelpers.state().envU16(key);
}

function _envBool(
string memory key
) private view returns (bool) {
return ZEnvHelpers.state().envBool(key);
}

address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
Vm internal constant vm = Vm(VM_ADDRESS);

Expand All @@ -447,4 +567,26 @@ library Env {
) private view returns (string memory) {
return vm.envString(key);
}

/**
* Test Helpers
*/

/// @dev Query and return `proxyAdmin.getProxyImplementation(proxy)`
function _getProxyImpl(
address _proxy
) internal view returns (address) {
return ProxyAdmin(Env.proxyAdmin()).getProxyImplementation(ITransparentUpgradeableProxy(_proxy));
}

/// @dev Query and return `proxyAdmin.getProxyAdmin(proxy)`
function _getProxyAdmin(
address _proxy
) internal view returns (address) {
return ProxyAdmin(Env.proxyAdmin()).getProxyAdmin(ITransparentUpgradeableProxy(_proxy));
}

function _strEq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}
Loading