diff --git a/script/10_DeployExocoreGatewayOnly.s.sol b/script/10_DeployExocoreGatewayOnly.s.sol new file mode 100644 index 00000000..f976f126 --- /dev/null +++ b/script/10_DeployExocoreGatewayOnly.s.sol @@ -0,0 +1,61 @@ +pragma solidity ^0.8.19; + +import {ILayerZeroEndpointV2} from "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; + +import {ProxyAdmin} from "@openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {ExocoreGateway} from "../src/core/ExocoreGateway.sol"; +import "forge-std/Script.sol"; +import {BaseScript} from "./BaseScript.sol"; + +contract DeployExocoreGatewayOnly is BaseScript { + + function setUp() public virtual override { + // load keys + super.setUp(); + // load contracts + string memory prerequisities = vm.readFile("script/prerequisiteContracts.json"); + exocoreLzEndpoint = ILayerZeroEndpointV2(stdJson.readAddress(prerequisities, ".exocore.lzEndpoint")); + require(address(exocoreLzEndpoint) != address(0), "exocore l0 endpoint should not be empty"); + // fork + exocore = vm.createSelectFork(exocoreRPCURL); + } + + function run() public { + vm.selectFork(exocore); + vm.startBroadcast(deployer.privateKey); + + ProxyAdmin exocoreProxyAdmin = new ProxyAdmin(); + ExocoreGateway exocoreGatewayLogic = new ExocoreGateway(address(exocoreLzEndpoint)); + exocoreGateway = ExocoreGateway( + payable( + address( + new TransparentUpgradeableProxy( + address(exocoreGatewayLogic), + address(exocoreProxyAdmin), + abi.encodeWithSelector( + exocoreGatewayLogic.initialize.selector, + payable(exocoreValidatorSet.addr) + ) + ) + ) + ) + ); + + vm.stopBroadcast(); + + string memory exocoreContracts = "exocoreContracts"; + vm.serializeAddress(exocoreContracts, "lzEndpoint", address(exocoreLzEndpoint)); + vm.serializeAddress(exocoreContracts, "exocoreGatewayLogic", address(exocoreGatewayLogic)); + string memory exocoreContractsOutput = + vm.serializeAddress(exocoreContracts, "exocoreGateway", address(exocoreGateway)); + + string memory deployedContracts = "deployedContracts"; + string memory finalJson = + vm.serializeString(deployedContracts, "exocore", exocoreContractsOutput); + + vm.writeJson(finalJson, "script/deployedExocoreGatewayOnly.json"); + + } +} \ No newline at end of file diff --git a/script/11_SetPeers.s.sol b/script/11_SetPeers.s.sol new file mode 100644 index 00000000..c8dac6e5 --- /dev/null +++ b/script/11_SetPeers.s.sol @@ -0,0 +1,97 @@ +pragma solidity ^0.8.19; + +import {ExocoreGateway} from "../src/core/ExocoreGateway.sol"; +import {Bootstrap} from "../src/core/Bootstrap.sol"; + +import {CLIENT_CHAINS_PRECOMPILE_ADDRESS} from "../src/interfaces/precompiles/IClientChains.sol"; + +import "forge-std/Script.sol"; +import {BaseScript} from "./BaseScript.sol"; + +import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol"; + +contract SetPeersAndUpgrade is BaseScript { + using AddressCast for address; + + address bootstrapAddr; + address exocoreGatewayAddr; + + function setUp() public virtual override { + // load keys + super.setUp(); + // load contracts + string memory deployed = vm.readFile("script/deployedBootstrapOnly.json"); + bootstrapAddr = stdJson.readAddress(deployed, ".clientChain.bootstrap"); + require(address(bootstrapAddr) != address(0), "bootstrap address should not be empty"); + deployed = vm.readFile("script/deployedExocoreGatewayOnly.json"); + exocoreGatewayAddr = stdJson.readAddress(deployed, ".exocore.exocoreGateway"); + require(address(exocoreGatewayAddr) != address(0), "exocore gateway address should not be empty"); + // forks + exocore = vm.createSelectFork(exocoreRPCURL); + clientChain = vm.createSelectFork(clientChainRPCURL); + } + + function run() public { + ExocoreGateway gateway = ExocoreGateway(payable(exocoreGatewayAddr)); + + vm.selectFork(exocore); + vm.startBroadcast(exocoreValidatorSet.privateKey); + gateway.setPeer(clientChainId, bootstrapAddr.toBytes32()); + vm.stopBroadcast(); + + Bootstrap bootstrap = Bootstrap(payable(bootstrapAddr)); + + vm.selectFork(clientChain); + vm.startBroadcast(exocoreValidatorSet.privateKey); + bootstrap.setPeer(exocoreChainId, address(exocoreGatewayAddr).toBytes32()); + vm.stopBroadcast(); + + // check that peer is set (we run with --slow but even then there's some risk) + uint256 i = 0; + uint256 tries = 5; + bool success; + while(i < tries) { + + vm.selectFork(exocore); + success = gateway.peers(clientChainId) == bootstrapAddr.toBytes32(); + + vm.selectFork(clientChain); + success = success && bootstrap.peers(exocoreChainId) == address(exocoreGatewayAddr).toBytes32(); + + if (success) { + break; + } + + i++; + } + require(i < tries, "peers not set"); + + // the upgrade does not work via script due to the precompile issue + // https://github.com/ExocoreNetwork/exocore/issues/78 + // // now that peers are set, we should upgrade the Bootstrap contract via gateway + // // but first allow simulation to run + // vm.selectFork(exocore); + // bytes memory mockCode = vm.getDeployedCode("ClientChainsMock.sol"); + // vm.etch(CLIENT_CHAINS_PRECOMPILE_ADDRESS, mockCode); + + // console.log("clientChainId", clientChainId); + // vm.startBroadcast(exocoreValidatorSet.privateKey); + // // fund the gateway + // if (exocoreGatewayAddr.balance < 1 ether) { + // (bool sent,) = exocoreGatewayAddr.call{value: 1 ether}(""); + // require(sent, "Failed to send Ether"); + // } + // // gateway.markBootstrapOnAllChains(); + + // instruct the user to upgrade manually + // this can be done even without calling x/assets UpdateParams + // because that parameter is not involved in this process. + console.log("Cross-chain upgrade command:"); + console.log( + "source .env && cast send --rpc-url $EXOCORE_TESETNET_RPC", + exocoreGatewayAddr, + "\"markBootstrapOnAllChains()\"", + "--private-key $TEST_ACCOUNT_THREE_PRIVATE_KEY" + ); + } +} \ No newline at end of file diff --git a/script/12_RedeployClientChainGateway.s.sol b/script/12_RedeployClientChainGateway.s.sol new file mode 100644 index 00000000..fa04e4ec --- /dev/null +++ b/script/12_RedeployClientChainGateway.s.sol @@ -0,0 +1,88 @@ +pragma solidity ^0.8.19; + +import {TransparentUpgradeableProxy} from "@openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {UpgradeableBeacon} from "@openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol"; + +import {Bootstrap} from "../src/core/Bootstrap.sol"; +import {ClientChainGateway} from "../src/core/ClientChainGateway.sol"; +import {CustomProxyAdmin} from "../src/core/CustomProxyAdmin.sol"; +import {Vault} from "../src/core/Vault.sol"; +import "../src/core/BeaconProxyBytecode.sol"; +import "../src/core/ExoCapsule.sol"; + +import "forge-std/Script.sol"; +import {BaseScript} from "./BaseScript.sol"; +import {ILayerZeroEndpointV2} from "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import {ERC20PresetFixedSupply} from "@openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol"; + +contract RedeployClientChainGateway is BaseScript { + Bootstrap bootstrap; + + function setUp() public virtual override { + // load keys + super.setUp(); + // load contracts + string memory prerequisiteContracts = vm.readFile("script/deployedBootstrapOnly.json"); + clientChainLzEndpoint = ILayerZeroEndpointV2( + stdJson.readAddress(prerequisiteContracts, ".clientChain.lzEndpoint") + ); + require(address(clientChainLzEndpoint) != address(0), "client chain l0 endpoint should not be empty"); + beaconOracle = EigenLayerBeaconOracle( + stdJson.readAddress(prerequisiteContracts, ".clientChain.beaconOracle") + ); + require(address(beaconOracle) != address(0), "beacon oracle should not be empty"); + vaultBeacon = UpgradeableBeacon( + stdJson.readAddress(prerequisiteContracts, ".clientChain.vaultBeacon") + ); + require(address(vaultBeacon) != address(0), "vault beacon should not be empty"); + capsuleBeacon = UpgradeableBeacon( + stdJson.readAddress(prerequisiteContracts, ".clientChain.capsuleBeacon") + ); + require(address(capsuleBeacon) != address(0), "capsule beacon should not be empty"); + beaconProxyBytecode = BeaconProxyBytecode( + stdJson.readAddress(prerequisiteContracts, ".clientChain.beaconProxyBytecode") + ); + require(address(beaconProxyBytecode) != address(0), "beacon proxy bytecode should not be empty"); + bootstrap = Bootstrap( + stdJson.readAddress(prerequisiteContracts, ".clientChain.bootstrap") + ); + require(address(bootstrap) != address(0), "bootstrap should not be empty"); + clientChain = vm.createSelectFork(clientChainRPCURL); + } + + function run() public { + vm.selectFork(clientChain); + vm.startBroadcast(exocoreValidatorSet.privateKey); + ClientChainGateway clientGatewayLogic = new ClientChainGateway( + address(clientChainLzEndpoint), + exocoreChainId, + address(beaconOracle), + address(vaultBeacon), + address(capsuleBeacon), + address(beaconProxyBytecode) + ); + // then the client chain initialization + address[] memory emptyList; + bytes memory initialization = abi.encodeWithSelector( + clientGatewayLogic.initialize.selector, + exocoreValidatorSet.addr, + emptyList + ); + bootstrap.setClientChainGatewayLogic( + address(clientGatewayLogic), + initialization + ); + vm.stopBroadcast(); + + string memory clientChainContracts = "clientChainContracts"; + string memory clientChainContractsOutput = + vm.serializeAddress(clientChainContracts, "clientGatewayLogic", address(clientGatewayLogic)); + + string memory deployedContracts = "deployedContracts"; + string memory finalJson = + vm.serializeString(deployedContracts, "clientChain", clientChainContractsOutput); + + vm.writeJson(finalJson, "script/redeployClientChainGateway.json"); + } +} \ No newline at end of file diff --git a/script/8_RegisterOperatorsAndDelegate.s.sol b/script/8_RegisterOperatorsAndDelegate.s.sol new file mode 100644 index 00000000..b9621a39 --- /dev/null +++ b/script/8_RegisterOperatorsAndDelegate.s.sol @@ -0,0 +1,100 @@ +pragma solidity ^0.8.19; + +import {Bootstrap} from "../src/core/Bootstrap.sol"; +import {Vault} from "../src/core/Vault.sol"; +import {IOperatorRegistry} from "../src/interfaces/IOperatorRegistry.sol"; + +import {ERC20PresetFixedSupply} from "@openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; + +import "forge-std/Script.sol"; + +// This script does not intentionally inherit from BaseScript, since +// that script has boilerplate that is not needed here. +contract RegisterOperatorsAndDelegate is Script { + // registration data for operators + uint256[] operatorKeys; + string[] exoAddresses; + string[] names; + bytes32[] consKeys; + // rpc settings + string clientChainRPCURL; + uint256 clientChain; + // addresses of contracts + address bootstrapAddr; + address tokenAddr; + // each subarray sums to deposits, and each item is the delegation amount + uint256[4][4] amounts = [ + [ 1500 * 1e18, 250 * 1e18, 250 * 1e18, 0 * 1e18 ], + [ 300 * 1e18, 1500 * 1e18, 0 * 1e18, 200 * 1e18 ], + [ 0 * 1e18, 0 * 1e18, 2500 * 1e18, 500 * 1e18 ], + [ 1000 * 1e18, 0 * 1e18, 0 * 1e18, 2000 * 1e18 ] + ]; + + function setUp() public { + operatorKeys = vm.envUint("OPERATOR_KEYS", ","); + exoAddresses = vm.envString("EXO_ADDRESSES", ","); + names = vm.envString("NAMES", ","); + consKeys = vm.envBytes32("CONS_KEYS", ","); + + clientChainRPCURL = vm.envString("SEPOLIA_RPC"); + clientChain = vm.createSelectFork(clientChainRPCURL); + + require( + operatorKeys.length == exoAddresses.length && + operatorKeys.length == names.length && + operatorKeys.length == consKeys.length, + "Operator registration data length mismatch" + ); + + string memory deployedContracts = vm.readFile("script/deployedBootstrapOnly.json"); + bootstrapAddr = stdJson.readAddress(deployedContracts, ".clientChain.bootstrap"); + require(bootstrapAddr != address(0), "Bootstrap address should not be empty"); + tokenAddr = stdJson.readAddress(deployedContracts, ".clientChain.erc20Token"); + require(tokenAddr != address(0), "Token address should not be empty"); + } + + function run() public { + vm.selectFork(clientChain); + IOperatorRegistry.Commission memory commission = IOperatorRegistry.Commission( + 0, 1e18, 1e18 + ); + Bootstrap bootstrap = Bootstrap(bootstrapAddr); + ERC20PresetFixedSupply token = ERC20PresetFixedSupply(tokenAddr); + address vaultAddr = address(bootstrap.tokenToVault(tokenAddr)); + for(uint256 i = 0; i < operatorKeys.length; i++) { + uint256 pk = operatorKeys[i]; + // address addr = vm.addr(pk); + string memory exoAddr = exoAddresses[i]; + string memory name = names[i]; + bytes32 consKey = consKeys[i]; + vm.startBroadcast(pk); + // register operator + bootstrap.registerOperator( + exoAddr, name, commission, consKey + ); + uint256 depositAmount = 0; + for(uint256 j = 0; j < amounts[i].length; j++) { + depositAmount += amounts[i][j]; + } + // approve + token.approve(vaultAddr, type(uint256).max); + // transfer + bootstrap.deposit(tokenAddr, depositAmount); + vm.stopBroadcast(); + } + for(uint256 i = 0; i < operatorKeys.length; i++) { + uint256 pk = operatorKeys[i]; + vm.startBroadcast(pk); + for(uint256 j = 0; j < operatorKeys.length; j++) { + uint256 amount = amounts[i][j]; + if (amount == 0) { + continue; + } + // i is the transaction sender and j is the operator + string memory exoAddr = exoAddresses[j]; + bootstrap.delegateTo(exoAddr, tokenAddr, amount); + } + vm.stopBroadcast(); + } + } +} \ No newline at end of file diff --git a/script/9_ExtendBootstrapTime.s.sol b/script/9_ExtendBootstrapTime.s.sol new file mode 100644 index 00000000..70b73827 --- /dev/null +++ b/script/9_ExtendBootstrapTime.s.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.8.19; + +import {Bootstrap} from "../src/core/Bootstrap.sol"; + +import "forge-std/Script.sol"; +import {BaseScript} from "./BaseScript.sol"; + +contract SetBootstrapTime is BaseScript { + address bootstrapAddr; + + function setUp() public virtual override { + // load keys + super.setUp(); + // load contracts + string memory deployedContracts = vm.readFile("script/deployedBootstrapOnly.json"); + bootstrapAddr = stdJson.readAddress(deployedContracts, ".clientChain.bootstrap"); + + clientChain = vm.createSelectFork(clientChainRPCURL); + } + + function run() public { + vm.selectFork(clientChain); + vm.startBroadcast(exocoreValidatorSet.privateKey); + + Bootstrap bootstrap = Bootstrap(bootstrapAddr); + bootstrap.setSpawnTime(block.timestamp + 120 seconds); + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/script/deployedBootstrapOnly.json b/script/deployedBootstrapOnly.json new file mode 100644 index 00000000..01935b82 --- /dev/null +++ b/script/deployedBootstrapOnly.json @@ -0,0 +1,16 @@ +{ + "clientChain": { + "beaconOracle": "0xd3D285cd1516038dAED61B8BF7Ae2daD63662492", + "beaconProxyBytecode": "0xA15Ce26ba8E50ac21ecDa1791BAa3bf22a95b575", + "bootstrap": "0x53E91EB5105ec8C1c22055F790616cB8F82c664e", + "bootstrapLogic": "0x417CaBa1E4a63D1202dCc6E19F7c3eC79b31EC45", + "capsuleBeacon": "0xe87e516C7116eC4DcFC5408c05618De6e1Cd4c10", + "capsuleImplementation": "0xBe26AfFF7EC33d6F4BC8175d3F6f404692e82443", + "clientGatewayLogic": "0xcE10583b1Efe34319812d96c7edFFD71E8403ba2", + "erc20Token": "0x83E6850591425e3C1E263c054f4466838B9Bd9e4", + "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "proxyAdmin": "0xE9591d5b1Ea9733ad36834cd0bDe40ce0028AE33", + "vaultBeacon": "0x2899181D6EB55847165cfa3288E4708dA5070751", + "vaultImplementation": "0xF22097E6799DF7D8b25CCeF6E64DA3CB9133012D" + } +} \ No newline at end of file diff --git a/script/deployedExocoreGatewayOnly.json b/script/deployedExocoreGatewayOnly.json new file mode 100644 index 00000000..2d038ade --- /dev/null +++ b/script/deployedExocoreGatewayOnly.json @@ -0,0 +1,7 @@ +{ + "exocore": { + "exocoreGateway": "0xe13Ef2fE9B4bC1A3bBB62Df6bB19d6aD79525036", + "exocoreGatewayLogic": "0x617c588c3FaAA105cec3438D0c031E143A8B23fd", + "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f" + } +} \ No newline at end of file diff --git a/script/generate.js b/script/generate.js index 3ce89937..8b52d42f 100644 --- a/script/generate.js +++ b/script/generate.js @@ -37,7 +37,7 @@ const isValidBech32 = (address) => { // Load variables from .env file -const { SEPOLIA_RPC, BOOTSTRAP_ADDRESS, BASE_GENESIS_FILE_PATH, EXCHANGE_RATES } = process.env; +const { SEPOLIA_RPC, BOOTSTRAP_ADDRESS, BASE_GENESIS_FILE_PATH, RESULT_GENESIS_FILE_PATH, EXCHANGE_RATES } = process.env; async function updateGenesisFile() { try { @@ -407,7 +407,7 @@ async function updateGenesisFile() { }); genesisJSON.app_state.delegation.delegations = baseLevel; - await fs.writeFile(BASE_GENESIS_FILE_PATH, JSON.stringify(genesisJSON, null, 2)); + await fs.writeFile(RESULT_GENESIS_FILE_PATH, JSON.stringify(genesisJSON, null, 2)); console.log('Genesis file updated successfully.'); } catch (error) { console.error('Error updating genesis file:', error.message); diff --git a/script/redeployClientChainGateway.json b/script/redeployClientChainGateway.json new file mode 100644 index 00000000..c450ae21 --- /dev/null +++ b/script/redeployClientChainGateway.json @@ -0,0 +1,5 @@ +{ + "clientChain": { + "clientGatewayLogic": "0xdC51F6d62ce78EfF7c98f3BD59227B4D0785C6ef" + } +} \ No newline at end of file diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index 49a5a902..847261a1 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -103,39 +103,39 @@ contract ClientChainGateway is // no risk keeping these but they are cheap to clear. delete exocoreSpawnTime; delete offsetDuration; - // TODO: are these loops even worth it? the maximum refund is 50% of the gas cost. - // if not, we can remove them. - // the lines above this set of comments are at least cheaper to clear, - // and have no utility after initialization. - for(uint i = 0; i < depositors.length; i++) { - address depositor = depositors[i]; - for(uint j = 0; j < whitelistTokens.length; j++) { - address token = whitelistTokens[j]; - delete totalDepositAmounts[depositor][token]; - delete withdrawableAmounts[depositor][token]; - for(uint k = 0; k < registeredOperators.length; k++) { - address eth = registeredOperators[k]; - string memory exo = ethToExocoreAddress[eth]; - delete delegations[depositor][exo][token]; - } - } - delete isDepositor[depositor]; - } - for(uint k = 0; k < registeredOperators.length; k++) { - address eth = registeredOperators[k]; - string memory exo = ethToExocoreAddress[eth]; - delete operators[exo]; - delete commissionEdited[exo]; - delete ethToExocoreAddress[eth]; - for(uint j = 0; j < whitelistTokens.length; j++) { - address token = whitelistTokens[j]; - delete delegationsByOperator[exo][token]; - } - } - for(uint j = 0; j < whitelistTokens.length; j++) { - address token = whitelistTokens[j]; - delete depositsByToken[token]; - } + // // TODO: are these loops even worth it? the maximum refund is 50% of the gas cost. + // // if not, we can remove them. + // // the lines above this set of comments are at least cheaper to clear, + // // and have no utility after initialization. + // for(uint i = 0; i < depositors.length; i++) { + // address depositor = depositors[i]; + // for(uint j = 0; j < whitelistTokens.length; j++) { + // address token = whitelistTokens[j]; + // delete totalDepositAmounts[depositor][token]; + // delete withdrawableAmounts[depositor][token]; + // for(uint k = 0; k < registeredOperators.length; k++) { + // address eth = registeredOperators[k]; + // string memory exo = ethToExocoreAddress[eth]; + // delete delegations[depositor][exo][token]; + // } + // } + // delete isDepositor[depositor]; + // } + // for(uint k = 0; k < registeredOperators.length; k++) { + // address eth = registeredOperators[k]; + // string memory exo = ethToExocoreAddress[eth]; + // delete operators[exo]; + // delete commissionEdited[exo]; + // delete ethToExocoreAddress[eth]; + // for(uint j = 0; j < whitelistTokens.length; j++) { + // address token = whitelistTokens[j]; + // delete delegationsByOperator[exo][token]; + // } + // } + // for(uint j = 0; j < whitelistTokens.length; j++) { + // address token = whitelistTokens[j]; + // delete depositsByToken[token]; + // } // these should also be cleared - even if the loops are not used // cheap to clear and potentially large in size. delete depositors; diff --git a/test/mocks/ClientChainsMock.sol b/test/mocks/ClientChainsMock.sol index 2528e774..26ab90b1 100644 --- a/test/mocks/ClientChainsMock.sol +++ b/test/mocks/ClientChainsMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {IClientChains} from "../../src/interfaces/precompiles/IClientChains.sol"; contract ClientChainsMock is IClientChains { - uint16 clientChainId = 2; + uint16 clientChainId = 40161; function getClientChains() external view returns (bool, uint16[] memory) { uint16[] memory res = new uint16[](1);