diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index ada766f5..abb8aef3 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -10,6 +10,7 @@ on: - mainnet - testnet - "release/**" + - "feat/**" permissions: packages: read diff --git a/contracts/multi-chains/RoninTrustedOrganization.sol b/contracts/multi-chains/RoninTrustedOrganization.sol index 940772b9..9b4f8978 100644 --- a/contracts/multi-chains/RoninTrustedOrganization.sol +++ b/contracts/multi-chains/RoninTrustedOrganization.sol @@ -249,7 +249,6 @@ contract RoninTrustedOrganization is IRoninTrustedOrganization, HasProxyAdmin, H return getTrustedOrganizationAt(i); } } - revert ErrQueryForNonExistentConsensusAddress(); } /** diff --git a/foundry.toml b/foundry.toml index 877b1566..d66af837 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,7 +13,7 @@ libs = [ # See more config options https://github.com/foundry-rs/foundry/tree/master/config -solc = '0.8.21' +solc = '0.8.22' extra_output = ["devdoc", "userdoc", "storagelayout"] evm_version = 'istanbul' cache_path = 'cache_foundry' diff --git a/test/foundry/forking/REP-004/ChangeConsensusAddress.t.sol b/test/foundry/forking/REP-004/ChangeConsensusAddress.t.sol new file mode 100644 index 00000000..3e821ded --- /dev/null +++ b/test/foundry/forking/REP-004/ChangeConsensusAddress.t.sol @@ -0,0 +1,1005 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { Test } from "forge-std/Test.sol"; +import { console2 } from "forge-std/console2.sol"; +import { StdStyle } from "forge-std/StdStyle.sol"; +import { TConsensus } from "@ronin/contracts/udvts/Types.sol"; +import { Maintenance } from "@ronin/contracts/ronin/Maintenance.sol"; +import { ContractType } from "@ronin/contracts/utils/ContractType.sol"; +import { MockPrecompile } from "@ronin/contracts/mocks/MockPrecompile.sol"; +import { IProfile, Profile } from "@ronin/contracts/ronin/profile/Profile.sol"; +import { Profile_Testnet } from "@ronin/contracts/ronin/profile/Profile_Testnet.sol"; +import { Profile_Mainnet } from "@ronin/contracts/ronin/profile/Profile_Mainnet.sol"; +import { IBaseStaking, Staking } from "@ronin/contracts/ronin/staking/Staking.sol"; +import { HasContracts } from "@ronin/contracts/extensions/collections/HasContracts.sol"; +import { CandidateManager } from "@ronin/contracts/ronin/validator/CandidateManager.sol"; +import { EmergencyExitBallot } from "@ronin/contracts/libraries/EmergencyExitBallot.sol"; +import { SlashIndicator } from "@ronin/contracts/ronin/slash-indicator/SlashIndicator.sol"; +import { ICandidateManagerCallback, ICandidateManager, RoninValidatorSet } from "@ronin/contracts/ronin/validator/RoninValidatorSet.sol"; +import { TransparentUpgradeableProxy, TransparentUpgradeableProxyV2 } from "@ronin/contracts/extensions/TransparentUpgradeableProxyV2.sol"; +import { IRoninGovernanceAdmin, RoninGovernanceAdmin } from "@ronin/contracts/ronin/RoninGovernanceAdmin.sol"; +import { IRoninTrustedOrganization, RoninTrustedOrganization } from "@ronin/contracts/multi-chains/RoninTrustedOrganization.sol"; +import { Proposal } from "@ronin/contracts/libraries/Proposal.sol"; +import { Ballot } from "@ronin/contracts/libraries/Ballot.sol"; + +contract ChangeConsensusAddressForkTest is Test { + string constant RONIN_TEST_RPC = "https://saigon-archive.roninchain.com/rpc"; + string constant RONIN_MAIN_RPC = "https://api-archived.roninchain.com/rpc"; + bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + Profile internal _profile; + Staking internal _staking; + Maintenance internal _maintenance; + RoninValidatorSet internal _validator; + RoninGovernanceAdmin internal _roninGA; + SlashIndicator internal _slashIndicator; + RoninTrustedOrganization internal _roninTO; + + modifier upgrade() { + _upgradeContracts(); + _; + } + + function _upgradeContracts() internal { + _upgradeProfile(); + _upgradeStaking(); + _upgradeValidator(); + _upgradeMaintenance(); + _upgradeSlashIndicator(); + _upgradeRoninTO(); + } + + function setUp() external { + MockPrecompile mockPrecompile = new MockPrecompile(); + vm.etch(address(0x68), address(mockPrecompile).code); + vm.makePersistent(address(0x68)); + + vm.createSelectFork(RONIN_TEST_RPC, 21901973); + // vm.createSelectFork(RONIN_MAIN_RPC, 29225255); + + if (block.chainid == 2021) { + _profile = Profile(0x3b67c8D22a91572a6AB18acC9F70787Af04A4043); + _maintenance = Maintenance(0x4016C80D97DDCbe4286140446759a3f0c1d20584); + _staking = Staking(payable(0x9C245671791834daf3885533D24dce516B763B28)); + _roninGA = RoninGovernanceAdmin(0x53Ea388CB72081A3a397114a43741e7987815896); + _slashIndicator = SlashIndicator(0xF7837778b6E180Df6696C8Fa986d62f8b6186752); + _roninTO = RoninTrustedOrganization(0x7507dc433a98E1fE105d69f19f3B40E4315A4F32); + _validator = RoninValidatorSet(payable(0x54B3AC74a90E64E8dDE60671b6fE8F8DDf18eC9d)); + } + if (block.chainid == 2020) { + // Mainnet + _profile = Profile(0x840EBf1CA767CB690029E91856A357a43B85d035); + _maintenance = Maintenance(0x6F45C1f8d84849D497C6C0Ac4c3842DC82f49894); + _staking = Staking(payable(0x545edb750eB8769C868429BE9586F5857A768758)); + _roninGA = RoninGovernanceAdmin(0x946397deDFd2f79b75a72B322944a21C3240c9c3); + _slashIndicator = SlashIndicator(0xEBFFF2b32fA0dF9C5C8C5d5AAa7e8b51d5207bA3); + _roninTO = RoninTrustedOrganization(0x98D0230884448B3E2f09a177433D60fb1E19C090); + _validator = RoninValidatorSet(payable(0x617c5d73662282EA7FfD231E020eCa6D2B0D552f)); + } + + vm.label(address(_profile), "Profile"); + vm.label(address(_staking), "Staking"); + vm.label(address(_validator), "Validator"); + vm.label(address(_maintenance), "Maintenance"); + vm.label(address(_roninGA), "GovernanceAdmin"); + vm.label(address(_roninTO), "TrustedOrganizations"); + vm.label(address(_slashIndicator), "SlashIndicator"); + } + + function testFork_AfterUpgraded_AddNewTrustedOrg_CanVoteProposal() external upgrade { + _cheatSetRoninGACode(); + // add trusted org + address consensus = makeAddr("consensus"); + address governor = makeAddr("governor"); + IRoninTrustedOrganization.TrustedOrganization memory newTrustedOrg = IRoninTrustedOrganization.TrustedOrganization( + TConsensus.wrap(consensus), + governor, + address(0x0), + 1000, + 0 + ); + _addTrustedOrg(newTrustedOrg); + + address newLogic = address(new RoninValidatorSet()); + address[] memory targets = new address[](1); + targets[0] = address(_validator); + uint256[] memory values = new uint256[](1); + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = abi.encodeCall(TransparentUpgradeableProxy.upgradeTo, newLogic); + uint256[] memory gasAmounts = new uint256[](1); + gasAmounts[0] = 1_000_000; + Ballot.VoteType support = Ballot.VoteType.For; + + vm.startPrank(governor); + _roninGA.proposeProposalForCurrentNetwork( + block.timestamp + 5 minutes, + targets, + values, + calldatas, + gasAmounts, + support + ); + vm.stopPrank(); + } + + function testFork_RevertWhen_AfterUpgraded_ApplyValidatorCandidateC1_AddNewTrustedOrgC1_ChangeC1ToC2_RenounceC2() + external + upgrade + { + // apply validator candidate + _applyValidatorCandidate("candidate-admin", "c1"); + + // add trusted org + address consensus = makeAddr("c1"); + address governor = makeAddr("governor"); + IRoninTrustedOrganization.TrustedOrganization memory newTrustedOrg = IRoninTrustedOrganization.TrustedOrganization( + TConsensus.wrap(consensus), + governor, + address(0x0), + 1000, + 0 + ); + _addTrustedOrg(newTrustedOrg); + + address newConsensus = makeAddr("c2"); + address admin = makeAddr("candidate-admin"); + vm.startPrank(admin); + _profile.requestChangeConsensusAddr(consensus, TConsensus.wrap(newConsensus)); + vm.expectRevert(ICandidateManagerCallback.ErrTrustedOrgCannotRenounce.selector); + _staking.requestRenounce(TConsensus.wrap(newConsensus)); + vm.stopPrank(); + } + + /** + * R4-P-09 + */ + function testFork_AfterUpgraded_AsTrustedOrg_AfterRenouncedAndRemovedFromTO_ReAddAsTrustedOrg() external upgrade { + // address[] memory validatorCids = _validator.getValidatorCandidates(); + // TConsensus standardConsensus; + // address standardId; + // for (uint i; i < validatorCids.length; i++) { + // if (_roninTO.getConsensusWeightById(validatorCids[i]) == 0) { + // standardId = validatorCids[i]; + // standardConsensus = _profile.getId2Profile(standardId).consensus; + // break; + // } + // } + + (, TConsensus standardConsensus) = _pickOneStandardCandidate(); + + (address admin, , ) = _staking.getPoolDetail(standardConsensus); + vm.prank(admin); + _staking.requestRenounce(standardConsensus); + + vm.warp(block.timestamp + 7 days); + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + assertFalse(_validator.isValidatorCandidate(standardConsensus)); + + IRoninTrustedOrganization.TrustedOrganization memory newTrustedOrg = IRoninTrustedOrganization.TrustedOrganization( + standardConsensus, + makeAddr("governor"), + address(0x0), + 1000, + 0 + ); + _addTrustedOrg(newTrustedOrg); + } + + function testFork_AfterUpgraded_AddNewTrustedOrgBefore_ApplyValidatorCandidateAfter() external upgrade { + uint256 newWeight = 1000; + + // add trusted org + address consensus = makeAddr("consensus"); + address governor = makeAddr("governor"); + IRoninTrustedOrganization.TrustedOrganization memory newTrustedOrg = IRoninTrustedOrganization.TrustedOrganization( + TConsensus.wrap(consensus), + governor, + address(0x0), + newWeight, + 0 + ); + IRoninTrustedOrganization.TrustedOrganization[] memory trustedOrgs = _addTrustedOrg(newTrustedOrg); + + // apply validator candidate + _applyValidatorCandidate("candidate-admin", "consensus"); + + address newAdmin = makeAddr("new-admin"); + address admin = makeAddr("candidate-admin"); + address newTreasury = makeAddr("new-treasury"); + address newConsensus = makeAddr("new-consensus"); + vm.startPrank(admin); + _profile.requestChangeConsensusAddr(consensus, TConsensus.wrap(newConsensus)); + _profile.requestChangeTreasuryAddr(consensus, payable(newTreasury)); + _profile.requestChangeAdminAddress(consensus, newAdmin); + vm.stopPrank(); + + // change new governor + address newGovernor = makeAddr("new-governor"); + trustedOrgs[0].governor = newGovernor; + trustedOrgs[0].consensusAddr = TConsensus.wrap(newConsensus); + vm.prank(_getProxyAdmin(address(_roninTO))); + TransparentUpgradeableProxyV2(payable(address(_roninTO))).functionDelegateCall( + abi.encodeCall(RoninTrustedOrganization.updateTrustedOrganizations, trustedOrgs) + ); + + IProfile.CandidateProfile memory profile = _profile.getId2Profile(consensus); + IRoninTrustedOrganization.TrustedOrganization memory trustedOrg = _roninTO.getTrustedOrganization( + TConsensus.wrap(newConsensus) + ); + + // assert eq to updated address + // 1. + assertEq(trustedOrg.governor, newGovernor); + assertEq(TConsensus.unwrap(trustedOrg.consensusAddr), newConsensus); + assertEq(profile.id, consensus); + assertEq(profile.treasury, payable(newTreasury)); + assertEq(profile.__reservedGovernor, address(0x0)); + assertEq(TConsensus.unwrap(profile.consensus), newConsensus); + + // 2. + __assertWeight(TConsensus.wrap(newConsensus), consensus, newWeight); + __assertWeight(TConsensus.wrap(consensus), consensus, 0); + } + + function testFork_AfterUpgraded_ApplyValidatorCandidateBefore_AddNewTrustedOrgAfter() external upgrade { + uint256 newWeight = 1000; + + // apply validator candidate + _applyValidatorCandidate("candidate-admin", "consensus"); + + // add trusted org + address consensus = makeAddr("consensus"); + address governor = makeAddr("governor"); + IRoninTrustedOrganization.TrustedOrganization memory newTrustedOrg = IRoninTrustedOrganization.TrustedOrganization( + TConsensus.wrap(consensus), + governor, + address(0x0), + newWeight, + 0 + ); + IRoninTrustedOrganization.TrustedOrganization[] memory trustedOrgs = _addTrustedOrg(newTrustedOrg); + + address newAdmin = makeAddr("new-admin"); + address admin = makeAddr("candidate-admin"); + address newTreasury = makeAddr("new-treasury"); + address newConsensus = makeAddr("new-consensus"); + vm.startPrank(admin); + _profile.requestChangeConsensusAddr(consensus, TConsensus.wrap(newConsensus)); + _profile.requestChangeTreasuryAddr(consensus, payable(newTreasury)); + _profile.requestChangeAdminAddress(consensus, newAdmin); + vm.stopPrank(); + + // change new governor + address newGovernor = makeAddr("new-governor"); + trustedOrgs[0].governor = newGovernor; + trustedOrgs[0].consensusAddr = TConsensus.wrap(newConsensus); + vm.prank(_getProxyAdmin(address(_roninTO))); + TransparentUpgradeableProxyV2(payable(address(_roninTO))).functionDelegateCall( + abi.encodeCall(RoninTrustedOrganization.updateTrustedOrganizations, trustedOrgs) + ); + + IProfile.CandidateProfile memory profile = _profile.getId2Profile(consensus); + IRoninTrustedOrganization.TrustedOrganization memory trustedOrg = _roninTO.getTrustedOrganization( + TConsensus.wrap(newConsensus) + ); + + // assert eq to updated address + assertEq(trustedOrg.governor, newGovernor); + assertEq(TConsensus.unwrap(trustedOrg.consensusAddr), newConsensus); + assertEq(profile.id, consensus); + assertEq(profile.treasury, payable(newTreasury)); + assertEq(profile.__reservedGovernor, address(0x0)); + assertEq(TConsensus.unwrap(profile.consensus), newConsensus); + + // 2. + __assertWeight(TConsensus.wrap(newConsensus), consensus, newWeight); + __assertWeight(TConsensus.wrap(consensus), consensus, 0); + } + + function __assertWeight(TConsensus consensus, address id, uint256 weight) private { + TConsensus[] memory consensuses = new TConsensus[](1); + address[] memory ids = new address[](1); + + consensuses[0] = consensus; + ids[0] = id; + + if (weight == 0) { + assertEq(_roninTO.getConsensusWeight(consensuses[0]), weight); + assertEq(_roninTO.getConsensusWeight(consensuses[0]), _roninTO.getConsensusWeight(consensuses[0])); + } else { + assertEq(_roninTO.getConsensusWeight(consensuses[0]), weight); + assertEq(_roninTO.getConsensusWeight(consensuses[0]), _roninTO.getConsensusWeights(consensuses)[0]); + assertEq(_roninTO.getConsensusWeights(consensuses)[0], _roninTO.getConsensusWeightById(ids[0])); + assertEq(_roninTO.getConsensusWeightById(ids[0]), _roninTO.getConsensusWeightsById(ids)[0]); + assertEq(_roninTO.getConsensusWeightsById(ids)[0], _roninTO.getConsensusWeight(consensuses[0])); + } + } + + function testFork_AfterUpgraded_WithdrawableFund_execEmergencyExit() external upgrade { + // TODO(bao): @TuDo1403 please enhance this test + _cheatSetRoninGACode(); + IRoninTrustedOrganization.TrustedOrganization[] memory trustedOrgs = _roninTO.getAllTrustedOrganizations(); + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address validatorCandidate = validatorCandidates[2]; + ICandidateManager.ValidatorCandidate memory oldCandidate = _validator.getCandidateInfo( + TConsensus.wrap(validatorCandidate) + ); + + (address admin, , ) = _staking.getPoolDetail(TConsensus.wrap(validatorCandidate)); + console2.log("admin", admin); + + address newAdmin = makeAddr("new-admin"); + address payable newTreasury = payable(makeAddr("new-treasury")); + TConsensus newConsensusAddr = TConsensus.wrap(makeAddr("new-consensus")); + + uint256 proposalRequestAt = block.timestamp; + uint256 proposalExpiredAt = proposalRequestAt + _validator.emergencyExpiryDuration(); + bytes32 voteHash = EmergencyExitBallot.hash( + TConsensus.unwrap(oldCandidate.__shadowedConsensus), + oldCandidate.__shadowedTreasury, + proposalRequestAt, + proposalExpiredAt + ); + + vm.startPrank(admin); + vm.expectEmit(address(_roninGA)); + emit IRoninGovernanceAdmin.EmergencyExitPollCreated( + voteHash, + TConsensus.unwrap(oldCandidate.__shadowedConsensus), + oldCandidate.__shadowedTreasury, + proposalRequestAt, + proposalExpiredAt + ); + _staking.requestEmergencyExit(TConsensus.wrap(validatorCandidate)); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensusAddr); + _profile.requestChangeTreasuryAddr(validatorCandidate, newTreasury); + _profile.requestChangeAdminAddress(validatorCandidate, newAdmin); + vm.stopPrank(); + + // NOTE: locked fund refunded to the old treasury + console2.log("recipient", oldCandidate.__shadowedTreasury); + uint256 balanceBefore = oldCandidate.__shadowedTreasury.balance; + console2.log("balanceBefore", balanceBefore); + + for (uint256 i; i < trustedOrgs.length; ++i) { + if (trustedOrgs[i].governor != validatorCandidate) { + vm.prank(trustedOrgs[i].governor); + _roninGA.voteEmergencyExit( + voteHash, + TConsensus.unwrap(oldCandidate.__shadowedConsensus), + oldCandidate.__shadowedTreasury, + proposalRequestAt, + proposalExpiredAt + ); + } + } + + uint256 balanceAfter = oldCandidate.__shadowedTreasury.balance; + console2.log("balanceAfter", balanceAfter); + uint256 fundReceived = balanceAfter - balanceBefore; + console2.log("fundReceived", fundReceived); + + assertTrue(fundReceived != 0); + } + + function testFork_AsTrustedOrg_AfterUpgraded_AfterChangeConsensus_requestRenounce() external upgrade { + TConsensus trustedOrg = _roninTO.getAllTrustedOrganizations()[0].consensusAddr; + console2.log("trustedOrgConsensus", TConsensus.unwrap(trustedOrg)); + address admin = _validator.getCandidateInfo(trustedOrg).__shadowedAdmin; + + TConsensus newConsensus = TConsensus.wrap(makeAddr("new-consensus")); + vm.prank(admin); + _profile.requestChangeConsensusAddr(TConsensus.unwrap(trustedOrg), newConsensus); + + (address poolAdmin, , ) = _staking.getPoolDetail(newConsensus); + console2.log("poolAdmin", poolAdmin); + + vm.expectRevert(); + vm.prank(poolAdmin); + _staking.requestRenounce(newConsensus); + } + + function testFork_AsTrustedOrg_AfterUpgraded_AfterChangeConsensus_execEmergencyExit() external upgrade { + TConsensus trustedOrg = _roninTO.getAllTrustedOrganizations()[0].consensusAddr; + console2.log("trustedOrgConsensus", TConsensus.unwrap(trustedOrg)); + address admin = _validator.getCandidateInfo(trustedOrg).__shadowedAdmin; + + TConsensus newConsensus = TConsensus.wrap(makeAddr("new-consensus")); + vm.prank(admin); + _profile.requestChangeConsensusAddr(TConsensus.unwrap(trustedOrg), newConsensus); + + (address poolAdmin, , ) = _staking.getPoolDetail(newConsensus); + console2.log("poolAdmin", poolAdmin); + + vm.prank(poolAdmin); + _staking.requestEmergencyExit(newConsensus); + } + + function testFork_NotReceiveReward_BeforeAndAfterUpgraded_execEmergencyExit() external { + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address validatorCandidate = validatorCandidates[2]; + address recipient = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedTreasury; + + uint256 snapshotId = vm.snapshot(); + + (address admin, , ) = _staking.getPoolDetail(TConsensus.wrap(validatorCandidate)); + console2.log("before-upgrade-admin", admin); + vm.prank(admin); + _staking.requestEmergencyExit(TConsensus.wrap(validatorCandidate)); + + uint256 adminBalanceBefore = admin.balance; + console2.log("before-upgrade:adminBalanceBefore", adminBalanceBefore); + + vm.warp(block.timestamp + 7 days); + _bulkWrapUpEpoch(1); + + uint256 adminBalanceAfter = admin.balance; + console2.log("before-upgrade:adminBalanceAfter", adminBalanceAfter); + + assertFalse(_validator.isValidatorCandidate(TConsensus.wrap(validatorCandidate))); + console2.log("before-upgrade:recipient", recipient); + uint256 balanceBefore = recipient.balance; + console2.log("before-upgrade:balanceBefore", balanceBefore); + + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + uint256 balanceAfter = recipient.balance; + console2.log("before-upgrade:balanceAfter", balanceAfter); + uint256 rewardBeforeUpgrade = balanceAfter - balanceBefore; + uint256 beforeUpgradeAdminStakingAmount = adminBalanceAfter - adminBalanceBefore; + console2.log("before-upgrade:adminStakingAmount", beforeUpgradeAdminStakingAmount); + console2.log("before-upgrade:reward", rewardBeforeUpgrade); + + assertEq(rewardBeforeUpgrade, 0); + + vm.revertTo(snapshotId); + _upgradeContracts(); + + (admin, , ) = _staking.getPoolDetail(TConsensus.wrap(validatorCandidate)); + console2.log("after-upgrade-admin", admin); + vm.prank(admin); + _staking.requestEmergencyExit(TConsensus.wrap(validatorCandidate)); + + adminBalanceBefore = admin.balance; + console2.log("after-upgrade:adminBalanceBefore", adminBalanceBefore); + + vm.warp(block.timestamp + 7 days); + _bulkWrapUpEpoch(1); + + adminBalanceAfter = admin.balance; + console2.log("after-upgrade:adminBalanceAfter", adminBalanceAfter); + + uint256 afterUpgradeAdminStakingAmount = adminBalanceAfter - adminBalanceBefore; + console2.log("after-upgrade:adminStakingAmount", afterUpgradeAdminStakingAmount); + assertFalse(_validator.isValidatorCandidate(TConsensus.wrap(validatorCandidate))); + console2.log("after-upgrade:recipient", recipient); + balanceBefore = recipient.balance; + console2.log("after-upgrade:balanceBefore", balanceBefore); + + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + balanceAfter = recipient.balance; + console2.log("after-upgrade:balanceAfter", balanceAfter); + uint256 rewardAfterUpgrade = balanceAfter - balanceBefore; + console2.log("after-upgrade:reward", rewardAfterUpgrade); + + assertEq(rewardAfterUpgrade, 0); + assertEq(beforeUpgradeAdminStakingAmount, afterUpgradeAdminStakingAmount); + } + + function testFork_AfterUpgraded_RevertWhen_ReapplySameAddress_Renounce() external upgrade { + (, TConsensus standardConsensus) = _pickOneStandardCandidate(); + address recipient = _validator.getCandidateInfo(standardConsensus).__shadowedTreasury; + + (address admin, , ) = _staking.getPoolDetail(standardConsensus); + vm.prank(admin); + _staking.requestRenounce(standardConsensus); + + vm.warp(block.timestamp + 7 days); + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + assertFalse(_validator.isValidatorCandidate(standardConsensus)); + + // re-apply same admin + uint256 amount = _staking.minValidatorStakingAmount(); + vm.deal(admin, amount); + vm.expectRevert(); + vm.prank(admin); + _staking.applyValidatorCandidate{ value: amount }( + admin, + TConsensus.wrap(makeAddr("new-consensus")), + payable(admin), + 2500, + "new-consensus" + ); + // re-apply same consensus + address newAdmin = makeAddr("new-admin"); + vm.deal(newAdmin, amount); + vm.expectRevert(); + vm.prank(newAdmin); + _staking.applyValidatorCandidate{ value: amount }( + newAdmin, + standardConsensus, + payable(newAdmin), + 2500, + "new-admin" + ); + + console2.log("recipient", recipient); + uint256 balanceBefore = recipient.balance; + console2.log("balanceBefore", balanceBefore); + + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + uint256 balanceAfter = recipient.balance; + console2.log("balanceAfter", balanceAfter); + uint256 reward = balanceAfter - balanceBefore; + console2.log("reward", reward); + + assertEq(reward, 0); + } + + function testFork_AfterUpgraded_ChangeConsensusAddress() external upgrade { + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address validatorCandidate = validatorCandidates[0]; + address candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + TConsensus newConsensus = TConsensus.wrap(makeAddr("new-consensus-0")); + + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + _bulkWrapUpEpoch(1); + + validatorCandidate = validatorCandidates[1]; + candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + newConsensus = TConsensus.wrap(makeAddr("new-consensus-1")); + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + _bulkWrapUpEpoch(1); + } + + function testFork_AfterUpgraded_WrapUpEpochAndNonWrapUpEpoch_ChangeAdmin_ChangeConsensus_ChangeTreasury() + external + upgrade + { + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address cid = validatorCandidates[0]; + address candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(cid)).__shadowedAdmin; + + // change validator admin + address newAdmin = makeAddr("new-admin"); + address newConsensus = makeAddr("new-consensus"); + address payable newTreasury = payable(makeAddr("new-treasury")); + + vm.startPrank(candidateAdmin); + _profile.requestChangeConsensusAddr(cid, TConsensus.wrap(newConsensus)); + _profile.requestChangeTreasuryAddr(cid, newTreasury); + _profile.requestChangeAdminAddress(cid, newAdmin); + vm.stopPrank(); + + // store snapshot state + uint256 snapshotId = vm.snapshot(); + + // wrap up epoch + _bulkWrapUpEpoch(1); + + ICandidateManager.ValidatorCandidate memory wrapUpInfo = _validator.getCandidateInfo(TConsensus.wrap(newConsensus)); + ICandidateManager.ValidatorCandidate[] memory wrapUpInfos = _validator.getCandidateInfos(); + + // revert to state before wrap up + vm.revertTo(snapshotId); + ICandidateManager.ValidatorCandidate memory nonWrapUpInfo = _validator.getCandidateInfo( + TConsensus.wrap(newConsensus) + ); + ICandidateManager.ValidatorCandidate[] memory nonWrapUpInfos = _validator.getCandidateInfos(); + + assertEq(wrapUpInfo.__shadowedAdmin, nonWrapUpInfo.__shadowedAdmin); + assertEq(wrapUpInfo.__shadowedAdmin, newAdmin); + assertEq(TConsensus.unwrap(wrapUpInfo.__shadowedConsensus), TConsensus.unwrap(nonWrapUpInfo.__shadowedConsensus)); + assertEq(TConsensus.unwrap(wrapUpInfo.__shadowedConsensus), newConsensus); + assertEq(wrapUpInfo.__shadowedTreasury, nonWrapUpInfo.__shadowedTreasury); + assertEq(wrapUpInfo.__shadowedTreasury, newTreasury); + assertEq(wrapUpInfo.commissionRate, nonWrapUpInfo.commissionRate); + assertEq(wrapUpInfo.revokingTimestamp, nonWrapUpInfo.revokingTimestamp); + assertEq(wrapUpInfo.topupDeadline, nonWrapUpInfo.topupDeadline); + + IProfile.CandidateProfile memory mProfile = _profile.getId2Profile(cid); + assertEq(mProfile.id, cid); + assertEq(TConsensus.unwrap(mProfile.consensus), newConsensus); + assertEq(mProfile.admin, newAdmin); + assertEq(mProfile.treasury, newTreasury); + + assertEq(wrapUpInfos.length, nonWrapUpInfos.length); + for (uint256 i; i < wrapUpInfos.length; ++i) { + assertEq(keccak256(abi.encode(wrapUpInfos[i])), keccak256(abi.encode(nonWrapUpInfos[i]))); + } + } + + function testFork_SlashIndicator_BeforeAndAfterUpgrade() external { + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address validatorCandidate = validatorCandidates[0]; + address candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + + uint256 snapshotId = vm.snapshot(); + + address recipient = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedTreasury; + console2.log("before-upgrade:recipient", recipient); + uint256 balanceBefore = recipient.balance; + console2.log("before-upgrade:balanceBefore", balanceBefore); + + _bulkSubmitBlockReward(1); + _bulkSlashIndicator(validatorCandidate, 150); + + _bulkWrapUpEpoch(1); + + uint256 balanceAfter = recipient.balance; + console2.log("before-upgrade:balanceAfter", balanceAfter); + uint256 beforeUpgradeReward = balanceAfter - balanceBefore; + console2.log("before-upgrade:reward", beforeUpgradeReward); + assertFalse(_validator.isBlockProducer(TConsensus.wrap(validatorCandidate))); + + vm.revertTo(snapshotId); + _upgradeContracts(); + TConsensus newConsensus = TConsensus.wrap(makeAddr("consensus")); + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + _bulkSubmitBlockReward(1); + _bulkSlashIndicator(TConsensus.unwrap(newConsensus), 150); + + console2.log("new-consensus", TConsensus.unwrap(newConsensus)); + + recipient = _validator.getCandidateInfo(newConsensus).__shadowedTreasury; + console2.log("after-upgrade:recipient", recipient); + + balanceBefore = recipient.balance; + console2.log("after-upgrade:balanceBefore", balanceBefore); + + _bulkWrapUpEpoch(1); + + balanceAfter = recipient.balance; + console2.log("after-upgrade:balanceAfter", balanceBefore); + uint256 afterUpgradedReward = balanceAfter - balanceBefore; + console2.log("after-upgrade:reward", afterUpgradedReward); + + assertFalse(_validator.isBlockProducer(newConsensus)); + assertEq(afterUpgradedReward, beforeUpgradeReward, "afterUpgradedReward != beforeUpgradeReward"); + } + + function testFork_Maintenance_BeforeAndAfterUpgrade() external { + IRoninTrustedOrganization.TrustedOrganization memory trustedOrg = _roninTO.getTrustedOrganizationAt(0); + address validatorCandidate = TConsensus.unwrap(trustedOrg.consensusAddr); + address candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + + // check balance before wrapup epoch + address recipient = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedTreasury; + console2.log("before-upgrade:recipient", recipient); + uint256 balanceBefore = recipient.balance; + console2.log("before-upgrade:balanceBefore", balanceBefore); + uint256 minOffsetToStartSchedule = _maintenance.minOffsetToStartSchedule(); + + // save snapshot state before wrapup + uint256 snapshotId = vm.snapshot(); + + _bulkSubmitBlockReward(1); + uint256 latestEpoch = _validator.getLastUpdatedBlock() + 200; + uint256 startMaintenanceBlock = latestEpoch + 1 + minOffsetToStartSchedule; + uint256 endMaintenanceBlock = latestEpoch + minOffsetToStartSchedule + _maintenance.minMaintenanceDurationInBlock(); + this.schedule(candidateAdmin, validatorCandidate, startMaintenanceBlock, endMaintenanceBlock); + vm.roll(latestEpoch + minOffsetToStartSchedule + 200); + + _bulkSlashIndicator(validatorCandidate, 150); + _bulkWrapUpEpoch(1); + + // assertFalse(_maintenance.checkMaintained(TConsensus.wrap(validatorCandidate), block.number + 1)); + assertFalse(_validator.isBlockProducer(TConsensus.wrap(validatorCandidate))); + uint256 balanceAfter = recipient.balance; + console2.log("before-upgrade:balanceAfter", balanceAfter); + uint256 beforeUpgradeReward = balanceAfter - balanceBefore; + console2.log("before-upgrade:reward", beforeUpgradeReward); + + // revert to previous state + console2.log( + StdStyle.blue("==============================================================================================") + ); + vm.revertTo(snapshotId); + _upgradeContracts(); + // change consensus address + TConsensus newConsensus = TConsensus.wrap(makeAddr("consensus")); + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + recipient = _validator.getCandidateInfo(newConsensus).__shadowedTreasury; + console2.log("after-upgrade:recipient", recipient); + balanceBefore = recipient.balance; + console2.log("after-upgrade:balanceBefore", balanceBefore); + + _bulkSubmitBlockReward(1); + latestEpoch = _validator.getLastUpdatedBlock() + 200; + startMaintenanceBlock = latestEpoch + 1 + minOffsetToStartSchedule; + endMaintenanceBlock = latestEpoch + minOffsetToStartSchedule + _maintenance.minMaintenanceDurationInBlock(); + + this.schedule(candidateAdmin, TConsensus.unwrap(newConsensus), startMaintenanceBlock, endMaintenanceBlock); + vm.roll(latestEpoch + minOffsetToStartSchedule + 200); + + _bulkSlashIndicator(TConsensus.unwrap(newConsensus), 150); + _bulkWrapUpEpoch(1); + + assertFalse(_maintenance.checkMaintained(TConsensus.wrap(validatorCandidate), block.number + 1)); + assertFalse(_validator.isBlockProducer(newConsensus)); + balanceAfter = recipient.balance; + console2.log("after-upgrade:balanceAfter", balanceBefore); + uint256 afterUpgradedReward = balanceAfter - balanceBefore; + console2.log("after-upgrade:reward", afterUpgradedReward); + assertEq(afterUpgradedReward, beforeUpgradeReward, "afterUpgradedReward != beforeUpgradeReward"); + } + + function testFork_ShareSameSameReward_BeforeAndAfterUpgrade() external { + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address validatorCandidate = validatorCandidates[0]; + address candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + //address recipient = candidateAdmin; + address recipient = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedTreasury; + console2.log("before-upgrade:recipient", recipient); + uint256 balanceBefore = recipient.balance; + console2.log("before-upgrade:balanceBefore", balanceBefore); + uint256 snapshotId = vm.snapshot(); + + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + uint256 balanceAfter = recipient.balance; + console2.log("before-upgrade:balanceAfter", balanceAfter); + uint256 beforeUpgradeReward = balanceAfter - balanceBefore; + console2.log("before-upgrade:reward", beforeUpgradeReward); + + vm.revertTo(snapshotId); + _upgradeContracts(); + TConsensus newConsensus = TConsensus.wrap(makeAddr("consensus")); + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + console2.log("new-consensus", TConsensus.unwrap(newConsensus)); + + recipient = _validator.getCandidateInfo(newConsensus).__shadowedTreasury; + console2.log("after-upgrade:recipient", recipient); + + balanceBefore = recipient.balance; + console2.log("after-upgrade:balanceBefore", balanceBefore); + + _bulkSubmitBlockReward(1); + _bulkWrapUpEpoch(1); + + balanceAfter = recipient.balance; + console2.log("after-upgrade:balanceAfter", balanceBefore); + uint256 afterUpgradedReward = balanceAfter - balanceBefore; + console2.log("after-upgrade:reward", afterUpgradedReward); + + assertEq(afterUpgradedReward, beforeUpgradeReward, "afterUpgradedReward != beforeUpgradeReward"); + } + + function testFailFork_RevertWhen_AfterUpgraded_DifferentAdmins_ShareSameConsensusAddr() external upgrade { + address[] memory validatorCandidates = _validator.getValidatorCandidates(); + address validatorCandidate = validatorCandidates[0]; + address candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + TConsensus newConsensus = TConsensus.wrap(makeAddr("same-consensus")); + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + _bulkWrapUpEpoch(1); + + validatorCandidate = validatorCandidates[1]; + candidateAdmin = _validator.getCandidateInfo(TConsensus.wrap(validatorCandidate)).__shadowedAdmin; + newConsensus = TConsensus.wrap(makeAddr("same-consensus")); + vm.prank(candidateAdmin); + _profile.requestChangeConsensusAddr(validatorCandidate, newConsensus); + + _bulkWrapUpEpoch(1); + } + + function testFork_AfterUpgraded_applyValidatorCandidate() external upgrade { + _applyValidatorCandidate("candidate-admin-0", "consensus-0"); + _applyValidatorCandidate("candidate-admin-1", "consensus-1"); + _bulkWrapUpEpoch(1); + } + + function testFork_AfterUpgraded_applyValidatorCandidateByPeriod() external upgrade { + _applyValidatorCandidate("candidate-admin-0", "consensus-0"); + _bulkWrapUpEpoch(1); + _applyValidatorCandidate("candidate-admin-1", "consensus-1"); + } + + function testFailFork_RevertWhen_AfterUpgraded_ReapplyValidatorCandidateByPeriod() external upgrade { + _applyValidatorCandidate("candidate-admin", "consensus"); + _bulkWrapUpEpoch(1); + _applyValidatorCandidate("candidate-admin", "consensus"); + } + + function testFailFork_RevertWhen_AfterUpgraded_ReapplyValidatorCandidate() external upgrade { + _applyValidatorCandidate("candidate-admin", "consensus"); + _applyValidatorCandidate("candidate-admin", "consensus"); + } + + function schedule(address admin, address consensus, uint256 startAtBlock, uint256 endedAtBlock) external { + vm.prank(admin); + _maintenance.schedule(TConsensus.wrap(consensus), startAtBlock, endedAtBlock); + } + + function _bulkWrapUpEpoch(uint256 times) internal { + for (uint256 i; i < times; ++i) { + _fastForwardToNextDay(); + _wrapUpEpoch(); + } + } + + function _bulkSlashIndicator(address consensus, uint256 times) internal { + vm.startPrank(block.coinbase); + for (uint256 i; i < times; ++i) { + _slashIndicator.slashUnavailability(TConsensus.wrap(consensus)); + vm.roll(block.number + 1); + } + vm.stopPrank(); + } + + function _bulkSubmitBlockReward(uint256 times) internal { + for (uint256 i; i < times; ++i) { + vm.roll(block.number + 1); + vm.deal(block.coinbase, 1000 ether); + vm.prank(block.coinbase); + _validator.submitBlockReward{ value: 1000 ether }(); + } + } + + function _upgradeProfile() internal { + Profile logic; + + if (block.chainid == 2020) { + logic = new Profile_Mainnet(); + } + if (block.chainid == 2021) { + logic = new Profile_Testnet(); + } + + uint gl1 = gasleft(); + console2.log("gasleft 1", gl1); + + vm.prank(_getProxyAdmin(address(_profile))); + TransparentUpgradeableProxyV2(payable(address(_profile))).upgradeToAndCall( + address(logic), + abi.encodeCall(Profile.initializeV2, (address(_staking), address(_roninTO))) + ); + + uint gl2 = gasleft(); + console2.log("gasleft 2", gl2); + console2.log("consume", gl1 - gl2); + } + + function _cheatSetRoninGACode() internal { + RoninGovernanceAdmin logic = new RoninGovernanceAdmin( + block.chainid, + address(_roninTO), + address(_validator), + type(uint256).max + ); + vm.etch(address(_roninGA), address(logic).code); + + vm.startPrank(address(_roninGA)); + _roninGA.setContract(ContractType.VALIDATOR, address(_validator)); + _roninGA.setContract(ContractType.RONIN_TRUSTED_ORGANIZATION, address(_roninTO)); + vm.stopPrank(); + } + + function _upgradeMaintenance() internal { + Maintenance logic = new Maintenance(); + vm.prank(_getProxyAdmin(address(_maintenance))); + TransparentUpgradeableProxyV2(payable(address(_maintenance))).upgradeToAndCall( + address(logic), + abi.encodeCall(Maintenance.initializeV3, (address(_profile))) + ); + } + + function _upgradeRoninTO() internal { + RoninTrustedOrganization logic = new RoninTrustedOrganization(); + vm.prank(_getProxyAdmin(address(_roninTO))); + TransparentUpgradeableProxyV2(payable(address(_roninTO))).upgradeToAndCall( + address(logic), + abi.encodeCall(RoninTrustedOrganization.initializeV2, (address(_profile))) + ); + } + + function _upgradeSlashIndicator() internal { + SlashIndicator logic = new SlashIndicator(); + vm.prank(_getProxyAdmin(address(_slashIndicator))); + TransparentUpgradeableProxyV2(payable(address(_slashIndicator))).upgradeTo(address(logic)); + } + + function _upgradeStaking() internal { + Staking logic = new Staking(); + vm.prank(_getProxyAdmin(address(_staking))); + TransparentUpgradeableProxyV2(payable(_staking)).upgradeToAndCall( + address(logic), + abi.encodeCall(Staking.initializeV3, (address(_profile))) + ); + } + + function _upgradeValidator() internal { + RoninValidatorSet logic = new RoninValidatorSet(); + vm.prank(_getProxyAdmin(address(_validator))); + TransparentUpgradeableProxyV2(payable(_validator)).upgradeToAndCall( + address(logic), + abi.encodeCall(RoninValidatorSet.initializeV4, (address(_profile))) + ); + } + + function _getProxyAdmin(address proxy) internal view returns (address payable proxyAdmin) { + return payable(address(uint160(uint256(vm.load(address(proxy), ADMIN_SLOT))))); + } + + function _wrapUpEpoch() internal { + vm.prank(block.coinbase); + _validator.wrapUpEpoch(); + } + + function _fastForwardToNextDay() internal { + vm.warp(block.timestamp + 3 seconds); + vm.roll(block.number + 1); + + uint256 numberOfBlocksInEpoch = _validator.numberOfBlocksInEpoch(); + + uint256 epochEndingBlockNumber = block.number + + (numberOfBlocksInEpoch - 1) - + (block.number % numberOfBlocksInEpoch); + uint256 nextDayTimestamp = block.timestamp + 1 days; + + // fast forward to next day + vm.warp(nextDayTimestamp); + vm.roll(epochEndingBlockNumber); + } + + function _addTrustedOrg( + IRoninTrustedOrganization.TrustedOrganization memory trustedOrg + ) internal returns (IRoninTrustedOrganization.TrustedOrganization[] memory trustedOrgs) { + trustedOrgs = new IRoninTrustedOrganization.TrustedOrganization[](1); + trustedOrgs[0] = trustedOrg; + vm.prank(_getProxyAdmin(address(_roninTO))); + TransparentUpgradeableProxyV2(payable(address(_roninTO))).functionDelegateCall( + abi.encodeCall(RoninTrustedOrganization.addTrustedOrganizations, trustedOrgs) + ); + } + + function _applyValidatorCandidate(string memory candidateAdminLabel, string memory consensusLabel) internal { + address candidateAdmin = makeAddr(candidateAdminLabel); + TConsensus consensusAddr = TConsensus.wrap(makeAddr(consensusLabel)); + bytes memory pubKey = bytes(candidateAdminLabel); + + uint256 amount = _staking.minValidatorStakingAmount(); + vm.deal(candidateAdmin, amount); + vm.prank(candidateAdmin, candidateAdmin); + _staking.applyValidatorCandidate{ value: amount }( + candidateAdmin, + consensusAddr, + payable(candidateAdmin), + 15_00, + pubKey + ); + } + + function _pickOneStandardCandidate() internal view returns (address standardId, TConsensus standardConsensus) { + address[] memory validatorCids = _validator.getValidatorCandidates(); + for (uint i; i < validatorCids.length; i++) { + if (_roninTO.getConsensusWeightById(validatorCids[i]) == 0) { + standardId = validatorCids[i]; + standardConsensus = _profile.getId2Profile(standardId).consensus; + break; + } + } + } +} diff --git a/upload-sig.sh b/upload-sig.sh new file mode 100755 index 00000000..3d0894d2 --- /dev/null +++ b/upload-sig.sh @@ -0,0 +1,71 @@ +# Default network value +networkName="ronin-testnet" +# Function to print usage and exit +usage() { + echo "Usage: $0 -c <network>" + echo " -c: Specify the network (ronin-testnet or ronin-mainnet)" + exit 1 +} +# Parse command-line options +while getopts "c:" opt; do + case $opt in + c) + case "$OPTARG" in + ronin-testnet) + child_folder="ronin-testnet" + networkName="ronin-testnet" + ;; + ronin-mainnet) + child_folder="ronin-mainnet" + networkName="ronin-mainnet" + ;; + *) + echo "Unknown network specified: $OPTARG" + usage + ;; + esac + ;; + *) + usage + ;; + esac +done +# Shift the processed options out of the argument list +shift $((OPTIND - 1)) +# Define the deployments folder by concatenating it with the child folder +folder="deployments/$child_folder" +# Check if the specified folder exists +if [ ! -d "$folder" ]; then + echo "Error: The specified folder does not exist for the selected network." + exit 1 +fi +for file in "$folder"/*.json; do + # Check if the file exists and is a regular file + if [ -f "$file" ] && [ "$(basename "$file")" != ".chainId" ]; then + # Extract contractName and address from the JSON file + contractName=$(jq -r '.contractName' "$file") + # Check if contractName and address are not empty + if [ -n "$contractName" ]; then + # Initialize arrays to store events and errors keys + events_keys=() + errors_keys=() + # Get events and errors JSON data + events=$(forge inspect $contractName events) + errors=$(forge inspect $contractName errors) + # Extract keys and populate the arrays + while read -r key; do + events_keys+=("\"event $key\"") + done <<<"$(echo "$events" | jq -r 'keys[]')" + while read -r key; do + errors_keys+=("\"$key\"") + done <<<"$(echo "$errors" | jq -r 'keys[]')" + # Combine keys from events and errors + all_keys=("${events_keys[@]}" "${errors_keys[@]}") + # Call cast upload-signature + cast upload-signature "${all_keys[@]}" + else + echo "Error: Missing contractName or address in $file" + fi + fi +done +forge selectors upload --all