Skip to content

Commit

Permalink
Add Upgrade ExoCapsule and WithdrawalValidator Script (#108)
Browse files Browse the repository at this point in the history
* Add Upgrade ExoCapsule and  WithdrawalValidator Script

* update

* update

* change script name

* fix format

* fix format

* refactor
  • Loading branch information
cloud8little authored Oct 11, 2024
1 parent dca9a69 commit 25ba9e0
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 30 deletions.
32 changes: 13 additions & 19 deletions script/13_DepositValidator.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract DepositScript is BaseScript {

function setUp() public virtual override {
super.setUp();

string memory validatorInfo = vm.readFile("script/validatorProof_staker1_testnetV6.json");
string memory deployedContracts = vm.readFile("script/deployedContracts.json");

clientGateway =
Expand All @@ -44,19 +44,19 @@ contract DepositScript is BaseScript {
require(address(beaconOracle) != address(0), "beacon oracle address should not be empty");

// load beacon chain validator container and proof from json file
_loadValidatorContainer();
_loadValidatorProof();

if (!useExocorePrecompileMock) {
_bindPrecompileMocks();
}
_loadValidatorContainer(validatorInfo);
_loadValidatorProof(validatorInfo);

// transfer some gas fee to depositor, relayer and exocore gateway
clientChain = vm.createSelectFork(clientChainRPCURL);
_topUpPlayer(clientChain, address(0), deployer, depositor.addr, 0.2 ether);

exocore = vm.createSelectFork(exocoreRPCURL);
_topUpPlayer(exocore, address(0), exocoreGenesis, address(exocoreGateway), 1 ether);

if (!useExocorePrecompileMock) {
_bindPrecompileMocks();
}
}

function run() public {
Expand Down Expand Up @@ -88,26 +88,20 @@ contract DepositScript is BaseScript {
vm.stopBroadcast();
}

function _loadValidatorContainer() internal {
string memory validatorInfo = vm.readFile("script/validator_container_proof_1711400.json");

validatorContainer = stdJson.readBytes32Array(validatorInfo, ".ValidatorFields");
function _loadValidatorContainer(string memory validatorInfo) internal {
validatorContainer = stdJson.readBytes32Array(validatorInfo, ".validatorContainer");
require(validatorContainer.length > 0, "validator container should not be empty");
}

function _loadValidatorProof() internal {
string memory validatorInfo = vm.readFile("script/validator_container_proof_1711400.json");

function _loadValidatorProof(string memory validatorInfo) internal {
uint256 slot = stdJson.readUint(validatorInfo, ".slot");
validatorProof.beaconBlockTimestamp = GENESIS_BLOCK_TIMESTAMP + SECONDS_PER_SLOT * slot;

validatorProof.stateRoot = stdJson.readBytes32(validatorInfo, ".beaconStateRoot");
validatorProof.stateRoot = stdJson.readBytes32(validatorInfo, ".stateRoot");
require(validatorProof.stateRoot != bytes32(0), "state root should not be empty");
validatorProof.stateRootProof =
stdJson.readBytes32Array(validatorInfo, ".StateRootAgainstLatestBlockHeaderProof");
validatorProof.stateRootProof = stdJson.readBytes32Array(validatorInfo, ".stateRootProof");
require(validatorProof.stateRootProof.length == 3, "state root proof should have 3 nodes");
validatorProof.validatorContainerRootProof =
stdJson.readBytes32Array(validatorInfo, ".WithdrawalCredentialProof");
validatorProof.validatorContainerRootProof = stdJson.readBytes32Array(validatorInfo, ".validatorContainerProof");
require(validatorProof.validatorContainerRootProof.length == 46, "validator root proof should have 46 nodes");
validatorProof.validatorIndex = stdJson.readUint(validatorInfo, ".validatorIndex");
require(validatorProof.validatorIndex != 0, "validator root index should not be 0");
Expand Down
34 changes: 34 additions & 0 deletions script/16_UpgradeExoCapsule.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
pragma solidity ^0.8.19;

import "../src/core/ExoCapsule.sol";
import "./BaseScript.sol";
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "forge-std/Script.sol";

contract UpgradeExoCapsuleScript is BaseScript {

UpgradeableBeacon capsuleBeaconContract;

function setUp() public virtual override {
super.setUp();

string memory deployedContracts = vm.readFile("script/deployedContracts.json");

capsuleBeaconContract =
UpgradeableBeacon((stdJson.readAddress(deployedContracts, ".clientChain.capsuleBeacon")));
require(address(capsuleBeaconContract) != address(0), "capsuleBeacon address should not be empty");
clientChain = vm.createSelectFork(clientChainRPCURL);
}

function run() public {
vm.selectFork(clientChain);
vm.startBroadcast(deployer.privateKey);
console.log("owner", capsuleBeaconContract.owner());
ExoCapsule capsule = new ExoCapsule();
capsuleBeaconContract.upgradeTo(address(capsule));
vm.stopBroadcast();

console.log("new Exocapsule Implementation address: ", address(capsule));
}

}
156 changes: 156 additions & 0 deletions script/17_WithdrawalValidator.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
pragma solidity ^0.8.19;

import "../src/core/ExoCapsule.sol";
import "../src/interfaces/IClientChainGateway.sol";

import "../src/interfaces/IExoCapsule.sol";
import "../src/interfaces/IExocoreGateway.sol";
import "../src/interfaces/IVault.sol";

import "../src/storage/GatewayStorage.sol";
import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol";
import "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol";
import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol";
import "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol";
import {ERC20PresetFixedSupply} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
import "forge-std/Script.sol";

import "src/libraries/Endian.sol";

import {BaseScript} from "./BaseScript.sol";

import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "forge-std/StdJson.sol";
import "src/libraries/BeaconChainProofs.sol";

contract WithdrawalValidatorScript is BaseScript {

using AddressCast for address;
using Endian for bytes32;

bytes32[] validatorContainer;
BeaconChainProofs.ValidatorContainerProof validatorProof;
bytes32[] withdrawalContainer;
BeaconChainProofs.WithdrawalProof withdrawalProof;

uint256 internal constant GENESIS_BLOCK_TIMESTAMP = 1_695_902_400;
uint256 internal constant SECONDS_PER_SLOT = 12;
uint256 constant GWEI_TO_WEI = 1e9;

function setUp() public virtual override {
super.setUp();

string memory deployedContracts = vm.readFile("script/deployedContracts.json");

clientGateway =
IClientChainGateway(payable(stdJson.readAddress(deployedContracts, ".clientChain.clientChainGateway")));
require(address(clientGateway) != address(0), "clientGateway address should not be empty");

beaconOracle = EigenLayerBeaconOracle(stdJson.readAddress(deployedContracts, ".clientChain.beaconOracle"));
require(address(beaconOracle) != address(0), "beacon oracle address should not be empty");

// load beacon chain validator container and proof from json file
_loadValidatorContainer();
_loadValidatorProof();

_loadWithdrawalContainer();
_loadWithdrawalProof();

if (!useExocorePrecompileMock) {
_bindPrecompileMocks();
}

// transfer some gas fee to depositor, relayer and exocore gateway
clientChain = vm.createSelectFork(clientChainRPCURL);
_topUpPlayer(clientChain, address(0), deployer, depositor.addr, 0.2 ether);

exocore = vm.createSelectFork(exocoreRPCURL);
_topUpPlayer(exocore, address(0), exocoreGenesis, address(exocoreGateway), 1 ether);
}

function run() public {
bytes memory root = abi.encodePacked(hex"c0fa1dc87438211f4f73fab438558794947572b771f68c905eee959dba104877");
vm.mockCall(
address(beaconOracle), abi.encodeWithSelector(beaconOracle.timestampToBlockRoot.selector), abi.encode(root)
);
vm.selectFork(clientChain);

console.log("block.timestamp", validatorProof.beaconBlockTimestamp);
vm.startBroadcast(depositor.privateKey);
(bool success,) = address(beaconOracle).call(
abi.encodeWithSelector(beaconOracle.addTimestamp.selector, validatorProof.beaconBlockTimestamp)
);
vm.stopBroadcast();

vm.startBroadcast(depositor.privateKey);
bytes memory dummyInput = new bytes(97);
uint256 nativeFee = clientGateway.quote(dummyInput);
clientGateway.processBeaconChainWithdrawal{value: nativeFee}(
validatorContainer, validatorProof, withdrawalContainer, withdrawalProof
);

vm.stopBroadcast();
}

function _loadValidatorContainer() internal {
string memory validatorInfo = vm.readFile("script/withdrawalProof_fullwithdraw_2495260_2472449.json");

validatorContainer = stdJson.readBytes32Array(validatorInfo, ".validatorContainer");
require(validatorContainer.length > 0, "validator container should not be empty");
}

function _loadValidatorProof() internal {
string memory validatorInfo = vm.readFile("script/withdrawalProof_fullwithdraw_2495260_2472449.json");

uint256 slot = stdJson.readUint(validatorInfo, ".slot");
validatorProof.beaconBlockTimestamp = GENESIS_BLOCK_TIMESTAMP + SECONDS_PER_SLOT * slot;

validatorProof.stateRoot = stdJson.readBytes32(validatorInfo, ".stateRoot");
require(validatorProof.stateRoot != bytes32(0), "state root should not be empty");
validatorProof.stateRootProof = stdJson.readBytes32Array(validatorInfo, ".stateRootProof");
require(validatorProof.stateRootProof.length == 3, "state root proof should have 3 nodes");
validatorProof.validatorContainerRootProof = stdJson.readBytes32Array(validatorInfo, ".validatorContainerProof");
require(validatorProof.validatorContainerRootProof.length == 46, "validator root proof should have 46 nodes");
validatorProof.validatorIndex = stdJson.readUint(validatorInfo, ".validatorIndex");
require(validatorProof.validatorIndex != 0, "validator root index should not be 0");
}

function _loadWithdrawalContainer() internal {
string memory withdrawalInfo = vm.readFile("script/withdrawalProof_fullwithdraw_2495260_2472449.json");

withdrawalContainer = stdJson.readBytes32Array(withdrawalInfo, ".withdrawalContainer");
require(withdrawalContainer.length > 0, "withdrawal container should not be empty");
}

function _loadWithdrawalProof() internal {
string memory withdrawalInfo = vm.readFile("script/withdrawalProof_fullwithdraw_2495260_2472449.json");

withdrawalProof.withdrawalContainerRootProof =
stdJson.readBytes32Array(withdrawalInfo, ".withdrawalContainerProof");

console.log("withdrawalContainerProof");
console.logBytes32(withdrawalProof.withdrawalContainerRootProof[0]);
withdrawalProof.slotProof = stdJson.readBytes32Array(withdrawalInfo, ".slotRootProof");
withdrawalProof.executionPayloadRootProof =
stdJson.readBytes32Array(withdrawalInfo, ".executionPayloadRootProof");
withdrawalProof.timestampProof = stdJson.readBytes32Array(withdrawalInfo, ".timestampRootProof");
withdrawalProof.historicalSummaryBlockRootProof =
stdJson.readBytes32Array(withdrawalInfo, ".historicalSummaryBlockRootProof");
withdrawalProof.blockRootIndex = stdJson.readUint(withdrawalInfo, ".blockRootIndex");
require(withdrawalProof.blockRootIndex != 0, "block root index should not be 0");
withdrawalProof.historicalSummaryIndex = stdJson.readUint(withdrawalInfo, ".historicalSummaryIndex");
withdrawalProof.withdrawalIndex = stdJson.readUint(withdrawalInfo, ".withdrawalIndexWithinBlock");
withdrawalProof.blockRoot = stdJson.readBytes32(withdrawalInfo, ".historicalSummaryBlockRoot");
require(withdrawalProof.blockRoot != bytes32(0), "block root should not be empty");
withdrawalProof.slotRoot = stdJson.readBytes32(withdrawalInfo, ".slotRoot");
withdrawalProof.timestampRoot = stdJson.readBytes32(withdrawalInfo, ".timestampRoot");
withdrawalProof.executionPayloadRoot = stdJson.readBytes32(withdrawalInfo, ".executionPayloadRoot");
withdrawalProof.stateRoot = stdJson.readBytes32(withdrawalInfo, ".stateRoot");
// console.logBytes32("load withdrawal proof stateRoot", withdrawalProof.stateRoot);
}

function _getEffectiveBalance(bytes32[] storage vc) internal view returns (uint64) {
return vc[2].fromLittleEndianUint64();
}

}
24 changes: 13 additions & 11 deletions script/3_Setup.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,22 @@ contract SetupScript is BaseScript {
restakeToken = ERC20PresetFixedSupply(stdJson.readAddress(deployedContracts, ".clientChain.erc20Token"));
require(address(restakeToken) != address(0), "restakeToken address should not be empty");

vault = IVault(stdJson.readAddress(deployedContracts, ".clientChain.resVault"));
require(address(vault) != address(0), "vault address should not be empty");

exocoreGateway = IExocoreGateway(payable(stdJson.readAddress(deployedContracts, ".exocore.exocoreGateway")));
require(address(exocoreGateway) != address(0), "exocoreGateway address should not be empty");

exocoreLzEndpoint = ILayerZeroEndpointV2(stdJson.readAddress(deployedContracts, ".exocore.lzEndpoint"));
require(address(exocoreLzEndpoint) != address(0), "exocoreLzEndpoint address should not be empty");

if (!useExocorePrecompileMock) {
_bindPrecompileMocks();
}

// transfer some gas fee to contract owner
clientChain = vm.createSelectFork(clientChainRPCURL);
_topUpPlayer(clientChain, address(0), deployer, exocoreValidatorSet.addr, 0.2 ether);

exocore = vm.createSelectFork(exocoreRPCURL);
_topUpPlayer(exocore, address(0), exocoreGenesis, exocoreValidatorSet.addr, 0.2 ether);

if (!useExocorePrecompileMock) {
_bindPrecompileMocks();
}
}

function run() public {
Expand Down Expand Up @@ -88,9 +85,10 @@ contract SetupScript is BaseScript {
exocoreGateway.registerOrUpdateClientChain(
clientChainId, address(clientGateway).toBytes32(), 20, "ClientChain", "EVM compatible network", "secp256k1"
);
vm.stopBroadcast();

// 3. adding tokens to the whtielist of both Exocore and client chain gateway to enable restaking

vm.selectFork(clientChain);
// first we read decimals from client chain ERC20 token contract to prepare for token data
bytes32[] memory whitelistTokensBytes32 = new bytes32[](2);
uint8[] memory decimals = new uint8[](2);
Expand All @@ -104,24 +102,28 @@ contract SetupScript is BaseScript {
decimals[0] = restakeToken.decimals();
names[0] = "RestakeToken";
metaDatas[0] = "ERC20 LST token";
oracleInfos[0] = "{'a': 'b'}";
oracleInfos[0] = "ETH,Ethereum,8";
tvlLimits[0] = uint128(restakeToken.totalSupply() / 5); // in phases of 20%

// this stands for Native Restaking for ETH
whitelistTokensBytes32[1] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS));
decimals[1] = 18;
names[1] = "StakedETH";
metaDatas[1] = "natively staked ETH on Ethereum";
oracleInfos[1] = "{'b': 'a'}";
oracleInfos[1] = "ETH,Ethereum,8"; //[tokenName],[chainName],[tokenDecimal](,[interval],[contract](,[ChainDesc:{...}],[TokenDesc:{...}]))
tvlLimits[1] = 0; // irrelevant for native restaking

// second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking,
// and this would also add token addresses to client chain gateway's whitelist
vm.selectFork(exocore);
vm.startBroadcast(exocoreValidatorSet.privateKey);
uint256 nativeFee;
for (uint256 i = 0; i < whitelistTokensBytes32.length; i++) {
nativeFee = exocoreGateway.quote(
clientChainId,
abi.encodePacked(Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokensBytes32[i]))
abi.encodePacked(
Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokensBytes32[i], tvlLimits[i])
)
);
exocoreGateway.addWhitelistToken{value: nativeFee}(
clientChainId,
Expand Down
Loading

0 comments on commit 25ba9e0

Please sign in to comment.