Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3192a98
feat: add pauser
ypatil12 Jun 30, 2025
10ed54b
chore: bindings
ypatil12 Jun 30, 2025
a331a53
feat: add pauser to update funcs
ypatil12 Jul 1, 2025
9655e85
chore: initial implementation
ypatil12 Jun 24, 2025
e50caff
feat: use toml for initialization
ypatil12 Jun 25, 2025
adbfc9f
chore: typo
ypatil12 Jun 25, 2025
2bc96a8
chore: format
ypatil12 Jun 25, 2025
f75b56b
refactor: split source and dest
ypatil12 Jun 26, 2025
e3a5474
chore: split up deploy script
ypatil12 Jun 27, 2025
abe288f
chore: upgrade
ypatil12 Jun 27, 2025
00bc2eb
chore: cleanup import
ypatil12 Jun 27, 2025
1ac08be
chore: add todo
ypatil12 Jun 27, 2025
4e0a556
feat: multichain deployments (#1474)
0xClandestine Jun 27, 2025
0c1e8ab
chore: push
ypatil12 Jun 27, 2025
0bbc97f
chore: push
ypatil12 Jun 28, 2025
032d905
chore: push
ypatil12 Jun 28, 2025
4254e55
chore: tests
ypatil12 Jun 28, 2025
dc25722
chore: fix import
ypatil12 Jun 28, 2025
df092cd
chore: remove import
ypatil12 Jun 28, 2025
f2da533
chore: update workflow
ypatil12 Jun 28, 2025
fbf9bb6
chore: add generation scripts
ypatil12 Jun 30, 2025
23b1eb6
chore: format
ypatil12 Jun 30, 2025
5f20d5a
chore: add base sepolia to validation
ypatil12 Jun 30, 2025
c729422
test: `CrosschainDeployLib` same addy all chains
0xClandestine Jun 30, 2025
ca53880
fix: helpers
0xClandestine Jun 30, 2025
38ffcb6
test: getters
0xClandestine Jun 30, 2025
09a7d02
chore: format
ypatil12 Jun 30, 2025
841d246
test: fix ci
ypatil12 Jul 1, 2025
9db155a
test: removing val
ypatil12 Jul 1, 2025
a6d6640
chore: update rpc
ypatil12 Jul 1, 2025
0f41503
fix: compile
ypatil12 Jul 1, 2025
587cc72
feat: add ccr configuration
ypatil12 Jul 1, 2025
ce3cf88
chore: format
ypatil12 Jul 1, 2025
0706a59
chore: typo
ypatil12 Jul 1, 2025
362b7c4
chore: bindings
ypatil12 Jul 1, 2025
151d651
chore: upgrade json
ypatil12 Jul 1, 2025
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
3 changes: 2 additions & 1 deletion .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ jobs:
esac
env:
FOUNDRY_PROFILE: ${{ matrix.suite == 'Fork' && 'forktest' || 'medium' }}
RPC_MAINNET: https://billowing-capable-sound.quiknode.pro/
RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }}

# -----------------------------------------------------------------------
# Forge Storage Diff
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/validate-deployment-scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: true
matrix:
env: [preprod, testnet, mainnet, testnet-sepolia, testnet-hoodi]
env: [preprod, testnet, mainnet, testnet-sepolia, testnet-hoodi, testnet-base-sepolia]

steps:
# Check out repository with all submodules for complete codebase access.
Expand Down 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 All @@ -88,6 +88,8 @@ jobs:
RPC_URL="${{ secrets.RPC_SEPOLIA }}"
elif [ "${{ matrix.env }}" = "testnet-hoodi" ]; then
RPC_URL="${{ secrets.RPC_HOODI }}"
elif [ "${{ matrix.env }}" = "testnet-base-sepolia" ]; then
RPC_URL="${{ secrets.RPC_BASE_SEPOLIA }}"
elif [ "${{ matrix.env }}" = "mainnet" ]; then
RPC_URL="${{ secrets.RPC_MAINNET }}"
fi
Expand Down
203 changes: 200 additions & 3 deletions pkg/bindings/IReleaseManager/binding.go

Large diffs are not rendered by default.

205 changes: 201 additions & 4 deletions pkg/bindings/ReleaseManager/binding.go

Large diffs are not rendered by default.

203 changes: 200 additions & 3 deletions pkg/bindings/ReleaseManagerStorage/binding.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions script/deploy/multichain/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ignore wallet files to prevent sensitive information from being committed
*.wallet.json
179 changes: 179 additions & 0 deletions script/deploy/multichain/deploy_globalRootConfirmerSet.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "@openzeppelin/contracts/utils/Strings.sol";
import "src/test/utils/OperatorWalletLib.sol";
import "src/test/utils/Random.sol";
import "src/contracts/interfaces/IOperatorTableCalculator.sol";
import "src/contracts/interfaces/ICrossChainRegistry.sol";

import "src/contracts/libraries/Merkle.sol";

import "forge-std/Script.sol";
import "forge-std/Test.sol";

// forge script script/deploy/multichain/deploy_globalRootConfirmerSet.s.sol --sig "run(string memory)" $NETWORK
contract DeployGlobalRootConfirmerSet is Script, Test {
using Strings for *;
using Merkle for bytes32[];
using BN254 for BN254.G1Point;

address internal constant AVS = 0xDA29BB71669f46F2a779b4b62f03644A84eE3479;

function run(string memory network, string memory salt) public {
/**
*
* WALLET CREATION
*
*/
require(_strEq(network, "preprod") || _strEq(network, "testnet"), "Invalid network");

// 1. Create a BN254 Wallet using random salt
Operator memory operator = OperatorWalletLib.createOperator(salt);

/**
*
* Create the `BN254OperatorInfo` struct
*
*/

// 1. Generate the `BN254OperatorInfo` struct
IOperatorTableCalculatorTypes.BN254OperatorSetInfo memory operatorSetInfo;

// 2. Set the numOperators and totalWeights
operatorSetInfo.numOperators = 1;
uint256[] memory weights = new uint256[](1);
weights[0] = 1;
operatorSetInfo.totalWeights = weights;

// 3. Set the apk
BN254.G1Point memory aggregatePubkey;
aggregatePubkey = aggregatePubkey.plus(operator.signingKey.publicKeyG1);
operatorSetInfo.aggregatePubkey = aggregatePubkey;

// 4. Set the operatorInfoTreeRoot
bytes32[] memory operatorInfoLeaves = new bytes32[](1);
operatorInfoLeaves[0] = keccak256(
abi.encode(
IOperatorTableCalculatorTypes.BN254OperatorInfo({
pubkey: operator.signingKey.publicKeyG1,
weights: weights
})
)
);
operatorSetInfo.operatorInfoTreeRoot = operatorInfoLeaves.merkleizeKeccak();

/**
*
* Create the `operatorSetConfig` struct
*
*/
ICrossChainRegistry.OperatorSetConfig memory operatorSetConfig;
operatorSetConfig.owner = operator.key.addr;
operatorSetConfig.maxStalenessPeriod = 0;

/**
*
* OUTPUT - OPERATOR SET INFO (TOML FORMAT)
*
*/

// Write operator set info to TOML file
_writeOperatorSetToml(network, operatorSetInfo, operatorSetConfig);

/**
*
* OUTPUT - BLS WALLET
*
*/

// Write operator data to a separate function to avoid stack too deep
_writeOperatorData(operator, network);
}

function _writeOperatorData(Operator memory operator, string memory network) internal {
string memory operator_object = "operator";

// Serialize regular wallet info
string memory wallet_object = "wallet";
vm.serializeUint(wallet_object, "privateKey", operator.key.privateKey);
string memory walletOutput = vm.serializeAddress(wallet_object, "address", operator.key.addr);

// Serialize BLS wallet info
string memory blsWallet_object = "blsWallet";
vm.serializeUint(blsWallet_object, "privateKey", operator.signingKey.privateKey);

// Serialize publicKeyG1
string memory publicKeyG1_object = "publicKeyG1";
vm.serializeUint(publicKeyG1_object, "x", operator.signingKey.publicKeyG1.X);
string memory publicKeyG1Output = vm.serializeUint(publicKeyG1_object, "y", operator.signingKey.publicKeyG1.Y);
vm.serializeString(blsWallet_object, "publicKeyG1", publicKeyG1Output);

// Serialize publicKeyG2
string memory publicKeyG2_object = "publicKeyG2";
vm.serializeUint(publicKeyG2_object, "x0", operator.signingKey.publicKeyG2.X[0]);
vm.serializeUint(publicKeyG2_object, "x1", operator.signingKey.publicKeyG2.X[1]);
vm.serializeUint(publicKeyG2_object, "y0", operator.signingKey.publicKeyG2.Y[0]);
string memory publicKeyG2Output =
vm.serializeUint(publicKeyG2_object, "y1", operator.signingKey.publicKeyG2.Y[1]);
string memory blsWalletOutput = vm.serializeString(blsWallet_object, "publicKeyG2", publicKeyG2Output);

// Combine wallet and blsWallet into operator object
vm.serializeString(operator_object, "wallet", walletOutput);
string memory operatorOutput = vm.serializeString(operator_object, "blsWallet", blsWalletOutput);

// Write to separate file
string memory walletOutputPath = string.concat("script/deploy/multichain/", network, ".wallet.json");
vm.writeJson(operatorOutput, walletOutputPath);
}

function _writeOperatorSetToml(
string memory network,
IOperatorTableCalculatorTypes.BN254OperatorSetInfo memory operatorSetInfo,
ICrossChainRegistry.OperatorSetConfig memory operatorSetConfig
) internal {
// Build JSON object using serializeJson
string memory json_obj = "toml_output";

// Top level fields
vm.serializeUint(json_obj, "globalRootConfirmationThreshold", 10_000);
vm.serializeUint(json_obj, "referenceTimestamp", block.timestamp);

// globalRootConfirmerSet object
string memory confirmerSet_obj = "globalRootConfirmerSet";
vm.serializeString(confirmerSet_obj, "avs", AVS.toHexString());
string memory confirmerSetOutput = vm.serializeUint(confirmerSet_obj, "id", 0);
vm.serializeString(json_obj, "globalRootConfirmerSet", confirmerSetOutput);

// globalRootConfirmerSetConfig object
string memory confirmerSetConfig_obj = "globalRootConfirmerSetConfig";
vm.serializeUint(confirmerSetConfig_obj, "maxStalenessPeriod", operatorSetConfig.maxStalenessPeriod);
string memory confirmerSetConfigOutput =
vm.serializeAddress(confirmerSetConfig_obj, "owner", operatorSetConfig.owner);
vm.serializeString(json_obj, "globalRootConfirmerSetConfig", confirmerSetConfigOutput);

// globalRootConfirmerSetInfo object
string memory confirmerSetInfo_obj = "globalRootConfirmerSetInfo";
vm.serializeUint(confirmerSetInfo_obj, "numOperators", operatorSetInfo.numOperators);
vm.serializeBytes32(confirmerSetInfo_obj, "operatorInfoTreeRoot", operatorSetInfo.operatorInfoTreeRoot);
vm.serializeUint(confirmerSetInfo_obj, "totalWeights", operatorSetInfo.totalWeights);

// aggregatePubkey nested object
string memory aggregatePubkey_obj = "aggregatePubkey";
vm.serializeString(aggregatePubkey_obj, "X", operatorSetInfo.aggregatePubkey.X.toString());
string memory aggregatePubkeyOutput =
vm.serializeString(aggregatePubkey_obj, "Y", operatorSetInfo.aggregatePubkey.Y.toString());

string memory confirmerSetInfoOutput =
vm.serializeString(confirmerSetInfo_obj, "aggregatePubkey", aggregatePubkeyOutput);
string memory finalJson = vm.serializeString(json_obj, "globalRootConfirmerSetInfo", confirmerSetInfoOutput);

// Write TOML file using writeToml
string memory outputPath = string.concat("script/releases/v1.7.0-multichain/configs/", network, ".toml");
vm.writeToml(finalJson, outputPath);
}

function _strEq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}
141 changes: 141 additions & 0 deletions script/releases/CrosschainDeployLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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, string memory name) internal pure returns (bytes32) {
return bytes32(
bytes.concat(
bytes20(deployer),
bytes1(uint8(0)), // Cross-chain redeploy protection enabled (0: false, 1: true)
bytes11(keccak256(bytes(name))) // salt
)
);
}

/*
* @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, ""));
}

/*
* @notice Computes the deterministic address where a crosschain contract will be deployed.
* @dev Uses CreateX's computeCreate2Address with a protected salt to ensure deterministic deployment.
* @param deployer The address of the deployer account.
* @param initCodeHash The keccak256 hash of the contract's initialization code.
* @param name The name used to generate the protected salt.
* @return The deterministic address where the contract will be deployed.
*/
function computeCrosschainAddress(
address deployer,
bytes32 initCodeHash,
string memory name
) internal view returns (address) {
return createx.computeCreate2Address(
keccak256(abi.encodePacked(bytes32(uint256(uint160(deployer))), computeProtectedSalt(deployer, name))),
initCodeHash
);
}

/*
* @notice Computes the deterministic address where a crosschain upgradeable proxy will be deployed.
* @dev Computes the init code hash for a transparent upgradeable proxy and calls computeCrosschainAddress.
* @param adminAndDeployer The address that will be both the admin and deployer of the proxy.
* @param implementation The address of the implementation contract.
* @param name The name used to generate the protected salt.
* @return The deterministic address where the upgradeable proxy will be deployed.
*/
function computeCrosschainUpgradeableProxyAddress(
address adminAndDeployer,
address implementation,
string memory name
) internal view returns (address) {
return computeCrosschainAddress(
adminAndDeployer, keccak256(computeUpgradeableProxyInitCode(implementation, adminAndDeployer)), name
);
}
}
Loading