diff --git a/script/11_SetPeers.s.sol b/script/11_SetPeers.s.sol index 83c62bf7..de1b7a50 100644 --- a/script/11_SetPeers.s.sol +++ b/script/11_SetPeers.s.sol @@ -34,6 +34,12 @@ contract SetPeersAndUpgrade is BaseScript { ExocoreGateway gateway = ExocoreGateway(payable(exocoreGatewayAddr)); vm.selectFork(exocore); + // Foundry rejects this transaction because it reports that isRegisteredClientChain fails, no matter what + // we do. Conversely, other tools like Remix and Brownie and even cast are able to report the value + // correctly. As a workaround, have `AssetsMock` return `true` value before running this script. + if (!useExocorePrecompileMock) { + _bindPrecompileMocks(); + } vm.startBroadcast(exocoreValidatorSet.privateKey); gateway.setPeer(clientChainId, bootstrapAddr.toBytes32()); vm.stopBroadcast(); @@ -45,52 +51,14 @@ contract SetPeersAndUpgrade is BaseScript { 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++; + vm.selectFork(exocore); + 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"); } - 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" - ); + gateway.markBootstrapOnAllChains(); } } diff --git a/script/14_CorrectBootstrapErrors.s.sol b/script/14_CorrectBootstrapErrors.s.sol new file mode 100644 index 00000000..a4ca684c --- /dev/null +++ b/script/14_CorrectBootstrapErrors.s.sol @@ -0,0 +1,110 @@ +pragma solidity ^0.8.19; + +import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; + +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import "../src/core/BeaconProxyBytecode.sol"; +import {Bootstrap} from "../src/core/Bootstrap.sol"; +import {ClientChainGateway} from "../src/core/ClientChainGateway.sol"; + +import "../src/core/ExoCapsule.sol"; +import {Vault} from "../src/core/Vault.sol"; +import {ICustomProxyAdmin} from "../src/interfaces/ICustomProxyAdmin.sol"; + +import {BaseScript} from "./BaseScript.sol"; +import {ILayerZeroEndpointV2} from "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import {ERC20PresetFixedSupply} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +import "forge-std/Script.sol"; + +import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol"; + +// This script uses the address in `deployedBootstrapOnly` and redeploys on top of it. For that to work, the +// modifier for the initialize function needs to be changed from `initializer` to `reinitializer(2)`. At the same +// time, the `reinitializer` in the `ClientChainGateway` will need to be changed to `3`. +contract CorrectBootstrapErrors is BaseScript { + + address wstETH; + address proxyAddress; + address proxyAdmin; + + function setUp() public virtual override { + // load keys + super.setUp(); + // load contracts + string memory prerequisiteContracts = vm.readFile("script/prerequisiteContracts.json"); + clientChainLzEndpoint = + ILayerZeroEndpointV2(stdJson.readAddress(prerequisiteContracts, ".clientChain.lzEndpoint")); + require(address(clientChainLzEndpoint) != address(0), "Client chain endpoint not found"); + restakeToken = ERC20PresetFixedSupply(stdJson.readAddress(prerequisiteContracts, ".clientChain.erc20Token")); + require(address(restakeToken) != address(0), "Restake token not found"); + clientChain = vm.createSelectFork(clientChainRPCURL); + // we should use the pre-requisite to save gas instead of deploying our own + beaconOracle = EigenLayerBeaconOracle(stdJson.readAddress(prerequisiteContracts, ".clientChain.beaconOracle")); + require(address(beaconOracle) != address(0), "Beacon oracle not found"); + // same for BeaconProxyBytecode + beaconProxyBytecode = + BeaconProxyBytecode(stdJson.readAddress(prerequisiteContracts, ".clientChain.beaconProxyBytecode")); + require(address(beaconProxyBytecode) != address(0), "Beacon proxy bytecode not found"); + // wstETH on Sepolia + // https://docs.lido.fi/deployed-contracts/sepolia/ + wstETH = stdJson.readAddress(prerequisiteContracts, ".clientChain.wstETH"); + require(wstETH != address(0), "wstETH not found"); + + string memory deployed = vm.readFile("script/deployedBootstrapOnly.json"); + proxyAddress = stdJson.readAddress(deployed, ".clientChain.bootstrap"); + require(address(proxyAddress) != address(0), "bootstrap address should not be empty"); + proxyAdmin = stdJson.readAddress(deployed, ".clientChain.proxyAdmin"); + require(address(proxyAdmin) != address(0), "proxy admin address should not be empty"); + vaultImplementation = Vault(stdJson.readAddress(deployed, ".clientChain.vaultImplementation")); + require(address(vaultImplementation) != address(0), "vault implementation should not be empty"); + vaultBeacon = UpgradeableBeacon(stdJson.readAddress(deployed, ".clientChain.vaultBeacon")); + require(address(vaultBeacon) != address(0), "vault beacon should not be empty"); + } + + function run() public { + address[] memory emptyList; + + vm.selectFork(clientChain); + vm.startBroadcast(exocoreValidatorSet.privateKey); + ProxyAdmin proxyAdmin = ProxyAdmin(proxyAdmin); + Bootstrap bootstrapLogic = new Bootstrap( + address(clientChainLzEndpoint), exocoreChainId, address(vaultBeacon), address(beaconProxyBytecode) + ); + bytes memory data = abi.encodeCall( + Bootstrap.initialize, + ( + exocoreValidatorSet.addr, + // 1 week from now + block.timestamp + 168 hours, + 2 seconds, + emptyList, + address(proxyAdmin) + ) + ); + proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(proxyAddress), address(bootstrapLogic), data); + Bootstrap bootstrap = Bootstrap(payable(proxyAddress)); + vm.stopBroadcast(); + + string memory clientChainContracts = "clientChainContracts"; + vm.serializeAddress(clientChainContracts, "lzEndpoint", address(clientChainLzEndpoint)); + vm.serializeAddress(clientChainContracts, "erc20Token", address(restakeToken)); + vm.serializeAddress(clientChainContracts, "wstETH", wstETH); + vm.serializeAddress(clientChainContracts, "proxyAdmin", address(proxyAdmin)); + vm.serializeAddress(clientChainContracts, "vaultImplementation", address(vaultImplementation)); + vm.serializeAddress(clientChainContracts, "vaultBeacon", address(vaultBeacon)); + vm.serializeAddress(clientChainContracts, "beaconProxyBytecode", address(beaconProxyBytecode)); + vm.serializeAddress(clientChainContracts, "bootstrapLogic", address(bootstrapLogic)); + vm.serializeAddress(clientChainContracts, "bootstrap", address(bootstrap)); + string memory clientChainContractsOutput = + vm.serializeAddress(clientChainContracts, "beaconOracle", address(beaconOracle)); + + string memory deployedContracts = "deployedContracts"; + string memory finalJson = vm.serializeString(deployedContracts, "clientChain", clientChainContractsOutput); + + vm.writeJson(finalJson, "script/correctBootstrapErrors.json"); + } + +} diff --git a/script/7_DeployBootstrap.s.sol b/script/7_DeployBootstrap.s.sol index 97e28751..775cd342 100644 --- a/script/7_DeployBootstrap.s.sol +++ b/script/7_DeployBootstrap.s.sol @@ -19,6 +19,8 @@ import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol"; contract DeployBootstrapOnly is BaseScript { + address wstETH; + function setUp() public virtual override { // load keys super.setUp(); @@ -37,12 +39,17 @@ contract DeployBootstrapOnly is BaseScript { beaconProxyBytecode = BeaconProxyBytecode(stdJson.readAddress(prerequisiteContracts, ".clientChain.beaconProxyBytecode")); require(address(beaconProxyBytecode) != address(0), "Beacon proxy bytecode not found"); + // wstETH on Sepolia + // https://docs.lido.fi/deployed-contracts/sepolia/ + wstETH = stdJson.readAddress(prerequisiteContracts, ".clientChain.wstETH"); + require(wstETH != address(0), "wstETH not found"); } function run() public { vm.selectFork(clientChain); vm.startBroadcast(exocoreValidatorSet.privateKey); whitelistTokens.push(address(restakeToken)); + whitelistTokens.push(wstETH); // proxy deployment CustomProxyAdmin proxyAdmin = new CustomProxyAdmin(); @@ -64,8 +71,9 @@ contract DeployBootstrapOnly is BaseScript { Bootstrap.initialize, ( exocoreValidatorSet.addr, - block.timestamp + 365 days + 24 hours, - 24 hours, + // 1 week from now + block.timestamp + 168 hours, + 2 seconds, whitelistTokens, // vault is auto deployed address(proxyAdmin) ) @@ -100,6 +108,7 @@ contract DeployBootstrapOnly is BaseScript { string memory clientChainContracts = "clientChainContracts"; vm.serializeAddress(clientChainContracts, "lzEndpoint", address(clientChainLzEndpoint)); vm.serializeAddress(clientChainContracts, "erc20Token", address(restakeToken)); + vm.serializeAddress(clientChainContracts, "wstETH", wstETH); vm.serializeAddress(clientChainContracts, "proxyAdmin", address(proxyAdmin)); vm.serializeAddress(clientChainContracts, "vaultImplementation", address(vaultImplementation)); vm.serializeAddress(clientChainContracts, "vaultBeacon", address(vaultBeacon)); diff --git a/script/BaseScript.sol b/script/BaseScript.sol index ca63e372..2c0af011 100644 --- a/script/BaseScript.sol +++ b/script/BaseScript.sol @@ -16,6 +16,10 @@ import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; import {ERC20PresetFixedSupply, IERC20} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; import "forge-std/Script.sol"; +import "../test/mocks/AssetsMock.sol"; +import "../test/mocks/ClaimRewardMock.sol"; +import "../test/mocks/DelegationMock.sol"; + contract BaseScript is Script { struct Player { diff --git a/script/correctBootstrapErrors.json b/script/correctBootstrapErrors.json new file mode 100644 index 00000000..48c2173e --- /dev/null +++ b/script/correctBootstrapErrors.json @@ -0,0 +1,14 @@ +{ + "clientChain": { + "beaconOracle": "0xd3D285cd1516038dAED61B8BF7Ae2daD63662492", + "beaconProxyBytecode": "0xA15Ce26ba8E50ac21ecDa1791BAa3bf22a95b575", + "bootstrap": "0x53f39D2ECd900Fb018180dB692A9fc29c8efD38E", + "bootstrapLogic": "0x3C9928feC16faDD504ab663a202c43cebC29f88D", + "erc20Token": "0x83E6850591425e3C1E263c054f4466838B9Bd9e4", + "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f", + "proxyAdmin": "0x266517829c443D5F8d2dB3782d3F5f6D8Ed40903", + "vaultBeacon": "0xad6F09ED99aE9E2f47892e2b49b18E024fC2938C", + "vaultImplementation": "0x4B7422868F4574b9c1830aD6466D406FF7503290", + "wstETH": "0xB82381A3fBD3FaFA77B3a7bE693342618240067b" + } +} \ No newline at end of file diff --git a/script/deployedBootstrapOnly.json b/script/deployedBootstrapOnly.json index 01935b82..b49a6961 100644 --- a/script/deployedBootstrapOnly.json +++ b/script/deployedBootstrapOnly.json @@ -2,15 +2,16 @@ "clientChain": { "beaconOracle": "0xd3D285cd1516038dAED61B8BF7Ae2daD63662492", "beaconProxyBytecode": "0xA15Ce26ba8E50ac21ecDa1791BAa3bf22a95b575", - "bootstrap": "0x53E91EB5105ec8C1c22055F790616cB8F82c664e", - "bootstrapLogic": "0x417CaBa1E4a63D1202dCc6E19F7c3eC79b31EC45", - "capsuleBeacon": "0xe87e516C7116eC4DcFC5408c05618De6e1Cd4c10", - "capsuleImplementation": "0xBe26AfFF7EC33d6F4BC8175d3F6f404692e82443", - "clientGatewayLogic": "0xcE10583b1Efe34319812d96c7edFFD71E8403ba2", + "bootstrap": "0x53f39D2ECd900Fb018180dB692A9fc29c8efD38E", + "bootstrapLogic": "0x46F02C2c5F8E30D88C3f53437051f4eB47EF8abD", + "capsuleBeacon": "0xB43b4344D48A3096f3d8c974E610a676C73B4fEB", + "capsuleImplementation": "0x8D3c21EBef5992041De2DebAf01f4B16Cf9f8913", + "clientGatewayLogic": "0x538Dc35cd5debEbA9D70577C3429438ee5ACaA15", "erc20Token": "0x83E6850591425e3C1E263c054f4466838B9Bd9e4", "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f", - "proxyAdmin": "0xE9591d5b1Ea9733ad36834cd0bDe40ce0028AE33", - "vaultBeacon": "0x2899181D6EB55847165cfa3288E4708dA5070751", - "vaultImplementation": "0xF22097E6799DF7D8b25CCeF6E64DA3CB9133012D" + "proxyAdmin": "0x266517829c443D5F8d2dB3782d3F5f6D8Ed40903", + "vaultBeacon": "0xad6F09ED99aE9E2f47892e2b49b18E024fC2938C", + "vaultImplementation": "0x4B7422868F4574b9c1830aD6466D406FF7503290", + "wstETH": "0xB82381A3fBD3FaFA77B3a7bE693342618240067b" } } \ No newline at end of file diff --git a/script/deployedExocoreGatewayOnly.json b/script/deployedExocoreGatewayOnly.json index 2d038ade..05ff8847 100644 --- a/script/deployedExocoreGatewayOnly.json +++ b/script/deployedExocoreGatewayOnly.json @@ -1,7 +1,7 @@ { "exocore": { - "exocoreGateway": "0xe13Ef2fE9B4bC1A3bBB62Df6bB19d6aD79525036", - "exocoreGatewayLogic": "0x617c588c3FaAA105cec3438D0c031E143A8B23fd", + "exocoreGateway": "0xbf5497Deb7C409623Ad58D798E73a865a80873DD", + "exocoreGatewayLogic": "0xdd359906110Ae307eeCdE5d1179ab59461152348", "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f" } } \ No newline at end of file diff --git a/script/generate.js b/script/generate.js index 13366e64..7eedbb83 100644 --- a/script/generate.js +++ b/script/generate.js @@ -9,10 +9,17 @@ const clientChainInfo = { 'layer_zero_chain_id': 40161, 'address_length': 20, }; - +// this must be in the same order as whitelistTokens const tokenMetaInfos = [ - 'Exocore testnet ETH', + 'Exocore testnet ETH', // first we did push exoETH + 'Lido wrapped staked ETH', // then push wstETH ]; +// this must be in the same order as whitelistTokens +// they are provided because the symbol may not match what we are using from the price feeder. +// for example, exoETH is not a real token and we are using the price feed for ETH. +const tokenNamesForOracle = [ + 'ETH', 'wstETH' // not case sensitive +] const exocoreBech32Prefix = 'exo'; @@ -37,7 +44,7 @@ const isValidBech32 = (address) => { // Load variables from .env file -const { SEPOLIA_RPC, BOOTSTRAP_ADDRESS, BASE_GENESIS_FILE_PATH, RESULT_GENESIS_FILE_PATH, EXCHANGE_RATES } = process.env; +const { CLIENT_CHAIN_RPC, BOOTSTRAP_ADDRESS, BASE_GENESIS_FILE_PATH, RESULT_GENESIS_FILE_PATH, EXCHANGE_RATES } = process.env; async function updateGenesisFile() { try { @@ -46,7 +53,7 @@ async function updateGenesisFile() { const contractABI = JSON.parse(await fs.readFile(abiPath, 'utf8')).abi; // Set up Web3 - const web3 = new Web3(SEPOLIA_RPC); + const web3 = new Web3(CLIENT_CHAIN_RPC); // Create contract instance const myContract = new web3.eth.Contract(contractABI, BOOTSTRAP_ADDRESS); @@ -58,7 +65,7 @@ async function updateGenesisFile() { const genesisData = await fs.readFile(BASE_GENESIS_FILE_PATH); const genesisJSON = JSON.parse(genesisData); - const chainId = genesisJSON.chain_id; + const height = parseInt(genesisJSON.initial_height, 10); const bootstrapped = await myContract.methods.bootstrapped().call(); if (bootstrapped) { throw new Error('The contract has already been bootstrapped.'); @@ -100,9 +107,26 @@ async function updateGenesisFile() { const clientChainSuffix = '_0x' + clientChainInfo.layer_zero_chain_id.toString(16); // x/assets: tokens (client_chain_asset.go) + // x/oracle if (!genesisJSON.app_state.assets.tokens) { genesisJSON.app_state.assets.tokens = []; } + if (!genesisJSON.app_state.oracle.params.tokens) { + throw new Error( + 'The tokens section is missing from the oracle params.' + ); + } else if (genesisJSON.app_state.oracle.params.tokens.length > 1) { + // remove the ETH default token + genesisJSON.app_state.oracle.params.tokens = genesisJSON.app_state.oracle.params.tokens.slice(0, 1); + } + if (!genesisJSON.app_state.oracle.params.token_feeders) { + throw new Error( + 'The token_feeders section is missing from the oracle params.' + ); + } else if (genesisJSON.app_state.oracle.params.token_feeders.length > 1) { + // remove the ETH default token + genesisJSON.app_state.oracle.params.token_feeders = genesisJSON.app_state.oracle.params.token_feeders.slice(0, 1); + } const supportedTokensCount = await myContract.methods.getWhitelistedTokensCount().call(); if (supportedTokensCount != tokenMetaInfos.length) { throw new Error( @@ -110,6 +134,12 @@ async function updateGenesisFile() { does not match the number of token meta infos (${tokenMetaInfos.length}).` ); } + if (supportedTokensCount != tokenNamesForOracle.length) { + throw new Error( + `The number of tokens in the contract (${supportedTokensCount}) + does not match the number of token names for the oracle (${tokenNamesForOracle.length}).` + ); + } const decimals = []; const supportedTokens = []; const assetIds = []; @@ -123,7 +153,7 @@ async function updateGenesisFile() { decimals: token.decimals.toString(), total_supply: token.totalSupply.toString(), layer_zero_chain_id: clientChainInfo.layer_zero_chain_id, - // exocore_chain_index unused + exocore_chain_index: i.toString(), // unused meta_info: tokenMetaInfos[i], }, // set this to 0 intentionally, since the total amount will be provided @@ -133,6 +163,24 @@ async function updateGenesisFile() { supportedTokens[i] = tokenCleaned; decimals.push(token.decimals); assetIds.push(token.tokenAddress.toLowerCase() + clientChainSuffix); + const oracleToken = { + name: tokenNamesForOracle[i], + chain_id: 1, // constant intentionally, representing the first chain in the list + contract_address: token.tokenAddress, + active: true, + asset_id: token.tokenAddress.toLowerCase() + clientChainSuffix, + decimal: 8, // price decimals, not token decimals + } + genesisJSON.app_state.oracle.params.tokens.push(oracleToken); + const oracleTokenFeeder = { + token_id: (i + 1).toString(), // first is reserved + rule_id: "1", + start_round_id: "1", + start_base_block: (height + 10000).toString(), + interval: "30", + end_block: "0", + } + genesisJSON.app_state.oracle.params.token_feeders.push(oracleTokenFeeder); // break; } supportedTokens.sort((a, b) => { @@ -145,6 +193,8 @@ async function updateGenesisFile() { return 0; }); genesisJSON.app_state.assets.tokens = supportedTokens; + // do not sort x/oracle params since the order is related for + // the token objects and the token feeders. // x/assets: deposits (staker_asset.go) if (!genesisJSON.app_state.assets.deposits) { @@ -208,24 +258,27 @@ async function updateGenesisFile() { if (!genesisJSON.app_state.operator.operators) { genesisJSON.app_state.operator.operators = []; } - // x/operator: operator_records (consensus_keys.go) - if (!genesisJSON.app_state.operator.operator_records) { - genesisJSON.app_state.operator.operator_records = []; - } - // x/dogfood: initial_val_set (validators.go) + + // x/dogfood: val_set (validators.go) if (!genesisJSON.app_state.dogfood) { throw new Error('The dogfood section is missing from the genesis file.'); } - if (!genesisJSON.app_state.dogfood.initial_val_set) { - genesisJSON.app_state.dogfood.initial_val_set = []; + if (!genesisJSON.app_state.dogfood.val_set) { + genesisJSON.app_state.dogfood.val_set = []; + } + // check min_self_delegation + const minSelfDelegation = new Decimal(genesisJSON.app_state.dogfood.params.min_self_delegation); + // x/delegation: associations + if (!genesisJSON.app_state.delegation.associations) { + genesisJSON.app_state.delegation.associations = []; } const validators = []; const operators = []; - const operatorRecords = []; - const operatorsCount = await myContract.methods.getOperatorsCount().call(); + const associations = []; + const operatorsCount = await myContract.methods.getValidatorsCount().call(); for (let i = 0; i < operatorsCount; i++) { // operators - const operatorAddress = await myContract.methods.registeredOperators(i).call(); + const operatorAddress = await myContract.methods.registeredValidators(i).call(); const opAddressBech32 = await myContract.methods.ethToExocoreAddress( operatorAddress ).call(); @@ -233,7 +286,7 @@ async function updateGenesisFile() { console.log(`Skipping operator with invalid bech32 address: ${opAddressBech32}`); continue; } - const operatorInfo = await myContract.methods.operators(opAddressBech32).call(); + const operatorInfo = await myContract.methods.validators(opAddressBech32).call(); const operatorCleaned = { earnings_addr: opAddressBech32, // approve_addr unset @@ -262,27 +315,22 @@ async function updateGenesisFile() { } } operators.push(operatorCleaned); - // operator_records - const operatorRecord = { - operator_address: opAddressBech32, - chains: [ - { - chain_id: chainId, // this is the exocore chain id - consensus_key: operatorInfo.consensusPublicKey, - } - ], - }; - operatorRecords.push(operatorRecord); - // dogfood: initial_val_set + // dogfood: val_set // TODO: once the oracle module is set up, move away from this solution // and instead, load the asset prices into the oracle module genesis // and let the dogfood module pull the vote power from the rest of the system // at genesis. let amount = new Decimal(0); + if (exchangeRates.length != supportedTokens.length) { + throw new Error( + `The number of exchange rates (${exchangeRates.length}) + does not match the number of supported tokens (${supportedTokens.length}).` + ); + } for(let j = 0; j < supportedTokens.length; j++) { const tokenAddress = (await myContract.methods.getWhitelistedTokenAtIndex(j).call()).tokenAddress; - const perTokenDelegation = await myContract.methods.delegationsByOperator( + const perTokenDelegation = await myContract.methods.delegationsByValidator( opAddressBech32, tokenAddress ).call(); amount = amount.plus( @@ -292,11 +340,22 @@ async function updateGenesisFile() { ); // break; } - validators.push({ - public_key: operatorInfo.consensusPublicKey, - power: amount, // do not convert to int yet. - }); - // break; + // only mark as validator if the amount is greater than min_self_delegation + if (amount.gte(minSelfDelegation)) { + validators.push({ + public_key: operatorInfo.consensusPublicKey, + power: amount, // do not convert to int yet. + operator_acc_addr: opAddressBech32, + }); + } else { + console.log(`Skipping operator ${opAddressBech32} due to insufficient self delegation.`); + } + let stakerId = operatorAddress.toLowerCase() + clientChainSuffix; + let association = { + staker_id: stakerId, + operator: opAddressBech32, + }; + associations.push(association); } // operators operators.sort((a, b) => { @@ -309,27 +368,46 @@ async function updateGenesisFile() { return 0; }); genesisJSON.app_state.operator.operators = operators; - // operator_records - operatorRecords.sort((a, b) => { - if (a.operator_address < b.operator_address) { - return -1; - } - if (a.operator_address > b.operator_address) { - return 1; - } - return 0; - }); - genesisJSON.app_state.operator.operator_records = operatorRecords; - // dogfood: initial_val_set + // dogfood: val_set validators.sort((a, b) => { + // even though operator_acc_addr is unique, we have to still + // check for power first. this is because we pick the top N + // validators by power. + // if the powers are equal, we sort by operator_acc_addr in + // ascending order. + if (b.power.cmp(a.power) === 0) { + if (a.operator_acc_addr < b.operator_acc_addr) { + return -1; + } + if (a.operator_acc_addr > b.operator_acc_addr) { + return 1; + } + return 0; + } return b.power.cmp(a.power); }); + // pick top N by vote power validators.slice(0, genesisJSON.app_state.dogfood.params.max_validators); + let totalPower = 0; validators.forEach((val) => { + // truncate val.power = val.power.toFixed(0); + totalPower += parseInt(val.power, 10); }); - genesisJSON.app_state.dogfood.initial_val_set = validators; + genesisJSON.app_state.dogfood.val_set = validators; genesisJSON.app_state.dogfood.params.asset_ids = assetIds; + genesisJSON.app_state.dogfood.last_total_power = totalPower.toString(); + // associations: staker_id is unique, so no further sorting is needed. + associations.sort((a, b) => { + if (a.staker_id < b.staker_id) { + return -1; + } + if (a.staker_id > b.staker_id) { + return 1; + } + return 0; + }); + genesisJSON.app_state.delegation.associations = associations; // x/delegation: delegations_by_staker_asset_operator (delegation_state.go) if (!genesisJSON.app_state.delegation.delegations) { @@ -339,8 +417,9 @@ async function updateGenesisFile() { const baseLevel = []; for(let i = 0; i < depositorsCount; i++) { const staker = await myContract.methods.depositors(i).call(); + const stakerId = staker.toLowerCase() + clientChainSuffix; let level1 = { - staker_id: staker.toLowerCase() + clientChainSuffix, + staker_id: stakerId, delegations: [], } for(let j = 0; j < supportedTokens.length; j++) { @@ -351,7 +430,7 @@ async function updateGenesisFile() { per_operator_amounts: [], } for(let k = 0; k < operatorsCount; k++) { - const operatorEth = await myContract.methods.registeredOperators(k).call(); + const operatorEth = await myContract.methods.registeredValidators(k).call(); const operator = await myContract.methods.ethToExocoreAddress(operatorEth).call(); if (!isValidBech32(operator)) { console.log(`Skipping operator with invalid bech32 address: ${operator}`); @@ -368,7 +447,6 @@ async function updateGenesisFile() { } } level2.per_operator_amounts.push(level3); - // break; } } level2.per_operator_amounts.sort((a, b) => { diff --git a/script/prerequisiteContracts.json b/script/prerequisiteContracts.json index 0861061e..a8a5c5e8 100644 --- a/script/prerequisiteContracts.json +++ b/script/prerequisiteContracts.json @@ -3,13 +3,14 @@ "beaconOracle": "0xd3D285cd1516038dAED61B8BF7Ae2daD63662492", "beaconProxyBytecode": "0xA15Ce26ba8E50ac21ecDa1791BAa3bf22a95b575", "erc20Token": "0x83E6850591425e3C1E263c054f4466838B9Bd9e4", - "lzEndpoint": "0x43B57f4fA1E4445296c133B1C3814655E2d77c35" + "wstETH": "0xB82381A3fBD3FaFA77B3a7bE693342618240067b", + "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f" }, "exocore": { "claimRewardPrecompileMock": "0xE66204F6BdE875035C63437dbfbf1B497e8CF455", "delegationPrecompileMock": "0x0b63680102cba1F0eD462028e2DBDde4234c1C7B", "depositPrecompileMock": "0x07097210995b2ec23582Feeb0a8c234BB0c50787", - "lzEndpoint": "0x90153d62284B67A2D8e9F3473F9CaF2078731da2", + "lzEndpoint": "0x6EDCE65403992e310A62460808c4b910D972f10f", "withdrawPrecompileMock": "0x07097210995b2ec23582Feeb0a8c234BB0c50787" } } \ No newline at end of file diff --git a/script/redeployClientChainGateway.json b/script/redeployClientChainGateway.json index c450ae21..edcde415 100644 --- a/script/redeployClientChainGateway.json +++ b/script/redeployClientChainGateway.json @@ -1,5 +1,5 @@ { "clientChain": { - "clientGatewayLogic": "0xdC51F6d62ce78EfF7c98f3BD59227B4D0785C6ef" + "clientGatewayLogic": "0x7B06d8032021a828F0DaD25808bf4F3177cC67cB" } } \ No newline at end of file diff --git a/test/mocks/NonShortCircuitEndpointV2Mock.sol b/test/mocks/NonShortCircuitEndpointV2Mock.sol index 3da36f56..8744ec7f 100644 --- a/test/mocks/NonShortCircuitEndpointV2Mock.sol +++ b/test/mocks/NonShortCircuitEndpointV2Mock.sol @@ -717,9 +717,8 @@ contract NonShortCircuitEndpointV2Mock is ILayerZeroEndpointV2, MessagingContext } } else if (optionType == ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION) { // ordered = true; - } else { - revert IExecutorFeeLib.Executor_UnsupportedOptionType(optionType); } + else revert IExecutorFeeLib.Executor_UnsupportedOptionType(optionType); } if (cursor != _options.length) {