diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index cf203af6..0fb48529 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -4,3 +4,7 @@ a88906eaf3e08acc1f5517ddcbfaa26af7b8559b 04cca08e8242d2eff83fd895c4b56b456a52b37c # style(automated): post-commit forge fmt (*.sol) 138b0b7fb7ebf761275fe9917243c89201f226c4 +# style(automated): post-commit forge fmt (*.sol) +d8895a30b9b984a530b1a9060757d954db2e65f8 +# style(automated): post-commit forge fmt (*.sol) +43e64bf94c48a2f6442eb0a6616573da3851dc72 diff --git a/.gitignore b/.gitignore index 219ca6c0..8abcbf32 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,7 @@ deployments/**/exported_address config/config.json config/config.yaml dependencies -deployments/ronin-mainnet-shadow +deployments/*-shadow bin/ronin-random-beacon +broadcast/**/6063/* +broadcast/**/31337/* diff --git a/.husky/post-commit b/.husky/post-commit index 41611313..459479e2 100755 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -5,7 +5,7 @@ HAS_PENDING_CHANGES=false # If there are any changes in the working directory, stash them if [[ $(git status -s) ]]; then HAS_PENDING_CHANGES=true - echo "\033[33m[post-commit] Stashing pending changes\033[0m" + echo -e "\033[33m[post-commit] Stashing pending changes\033[0m" git stash push -m "post-commit: Stash changes" fi @@ -13,13 +13,13 @@ GIT_BLAME_IGNORE_REVS_FILE=".git-blame-ignore-revs" # If `.git-blame-ignore-revs` does not exist, create it if [ ! -f $GIT_BLAME_IGNORE_REVS_FILE ]; then - echo "\033[33m[post-commit] Creating $GIT_BLAME_IGNORE_REVS_FILE\033[0m" + echo -e "\033[33m[post-commit] Creating $GIT_BLAME_IGNORE_REVS_FILE\033[0m" touch $GIT_BLAME_IGNORE_REVS_FILE git add $GIT_BLAME_IGNORE_REVS_FILE HUSKY=0 git commit -m "chore(automated): create .git-blame-ignore-revs" -n - echo "\033[33m[post-commit] Configuring git blame to ignore $GIT_BLAME_IGNORE_REVS_FILE\033[0m" + echo -e "\033[33m[post-commit] Configuring git blame to ignore $GIT_BLAME_IGNORE_REVS_FILE\033[0m" # Config to ignore the changes in `.git-blame-ignore-revs` git config blame.ignoreRevsFile $GIT_BLAME_IGNORE_REVS_FILE fi @@ -28,7 +28,7 @@ forge fmt # Check if there are any changes after forge fmt if [[ $(git status -s) ]]; then - echo "\033[33m[post-commit] Forge fmt changes detected\033[0m" + echo -e "\033[33m[post-commit] Forge fmt changes detected\033[0m" git add src git add script git add test/foundry @@ -40,13 +40,13 @@ forge build # Check if `GIT_BLAME_IGNORE_REVS_FILE` has any changes if [[ $(git status -s $GIT_BLAME_IGNORE_REVS_FILE) ]]; then - echo "\033[33m[post-commit] .git-blame-ignore-revs changes detected\033[0m" + echo -e "\033[33m[post-commit] .git-blame-ignore-revs changes detected\033[0m" git add $GIT_BLAME_IGNORE_REVS_FILE HUSKY=0 git commit -m "chore(automated): post-commit update .git-blame-ignore-revs" -n fi # If there were any pending changes, unstash them if [ "$HAS_PENDING_CHANGES" = true ]; then - echo "\033[33m[post-commit] Unstashing pending changes\033[0m" + echo -e "\033[33m[post-commit] Unstashing pending changes\033[0m" git stash pop fi diff --git a/generate-artifact.sh b/generate-artifact.sh new file mode 100755 index 00000000..5fcc3616 --- /dev/null +++ b/generate-artifact.sh @@ -0,0 +1 @@ +source dependencies/fdk-0.3.5-beta/generate-artifact.sh diff --git a/remappings.txt b/remappings.txt index e62f8130..7d2ef2c7 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,4 @@ forge-std/=dependencies/fdk-0.3.5-beta/dependencies/forge-std-1.9.3/src/ @solady/=dependencies/fdk-0.3.5-beta/dependencies/solady-0.0.228/src/ @fdk/=dependencies/fdk-0.3.5-beta/script/ @chainlink/contracts/=dependencies/chainlink-1.6.0/contracts/ -@safe/contracts/=dependencies/safe-1.4.1/contracts/ \ No newline at end of file +@safe/contracts/=dependencies/safe-1.4.1/contracts/ diff --git a/script/GeneralConfig.sol b/script/GeneralConfig.sol index 5f5c527f..840e617e 100644 --- a/script/GeneralConfig.sol +++ b/script/GeneralConfig.sol @@ -16,7 +16,8 @@ contract GeneralConfig is BaseGeneralConfig { setNetworkInfo(Network.Goerli.data()); setNetworkInfo(Network.EthMainnet.data()); setNetworkInfo(Network.RoninDevnet.data()); - setNetworkInfo(Network.ShadowForkMainnet.data()); + setNetworkInfo(Network.RoninMainnetShadow.data()); + setNetworkInfo(Network.RoninTestnetShadow.data()); } function _setUpContracts() internal virtual override { diff --git a/script/PostChecker.sol b/script/PostChecker.sol index 5ac8645b..e9375f2d 100644 --- a/script/PostChecker.sol +++ b/script/PostChecker.sol @@ -13,6 +13,7 @@ import { PostChecker_Staking } from "./post-check/PostChecker_Staking.sol"; import { Contract } from "./utils/Contract.sol"; import { BaseMigration } from "@fdk/BaseMigration.s.sol"; import { ScriptExtended } from "@fdk/extensions/ScriptExtended.s.sol"; +import { IHasContracts } from "src/interfaces/collections/IHasContracts.sol"; import { ProxyInterface } from "@fdk/libraries/LibDeploy.sol"; import { LibErrorHandler } from "@fdk/libraries/LibErrorHandler.sol"; @@ -187,6 +188,7 @@ contract PostChecker is console.log("Submitting block reward at next block number after REP10 activated...".yellow()); VmSafe.Log[] memory logs = _randomlySubmitBlockReward({ validatorSet: validatorSet, txFee: 0.2 ether }); + _randomlySubmitL2BlockReward({ validatorSet: validatorSet, txFee: 0.3 ether }); console.log("FF Percentage after REP-10".yellow(), stakingVesting.fastFinalityRewardPercentage()); bool emitted; @@ -215,16 +217,37 @@ contract PostChecker is assertEq(stakingVesting.fastFinalityRewardPercentage(), newPercentage, "REP10 percentage not match"); _randomlySubmitBlockReward({ validatorSet: validatorSet, txFee: 0.3 ether }); + _randomlySubmitL2BlockReward({ validatorSet: validatorSet, txFee: 0.4 ether }); } console.log(StdStyle.green("Cheat fast forward to 1 epoch ...\n")); LibWrapUpEpoch.wrapUpEpoch(); } + function _randomlySubmitL2BlockReward( + IRoninValidatorSet validatorSet, + uint256 txFee + ) internal returns (VmSafe.Log[] memory logs) { + console.log( + "Submitting L2 block reward at: - Period:", validatorSet.currentPeriod(), " - Block:", vm.getBlockNumber() + 1 + ); + address feePlaza = IHasContracts(address(validatorSet)).getContract(ContractType.ZK_FEE_PLAZA); + uint256 currUnixTimestamp = vm.unixTime(); + address[] memory allCids = validatorSet.getValidatorCandidateIds(); + address randomCid = allCids[currUnixTimestamp % allCids.length]; + + vm.deal(feePlaza, txFee); + vme.rollUpTo(vm.getBlockNumber() + 1); + vm.prank(feePlaza); + vm.recordLogs(); + validatorSet.onL2BlockRewardSubmitted{ value: txFee }(randomCid); + logs = vm.getRecordedLogs(); + } + function _randomlySubmitBlockReward( IRoninValidatorSet validatorSet, uint256 txFee - ) private returns (VmSafe.Log[] memory logs) { + ) internal returns (VmSafe.Log[] memory logs) { console.log( "Submitting block reward at: - Period:", validatorSet.currentPeriod(), " - Block:", vm.getBlockNumber() + 1 ); diff --git a/script/RoninMigration.s.sol b/script/RoninMigration.s.sol index 5dd7910a..ae4a6bbc 100644 --- a/script/RoninMigration.s.sol +++ b/script/RoninMigration.s.sol @@ -52,9 +52,11 @@ contract RoninMigration is BaseMigration { if ( network() == DefaultNetwork.LocalHost.key() || network() == DefaultNetwork.RoninTestnet.key() - || network() == Network.RoninDevnet.key() || network() == Network.ShadowForkMainnet.key() + || network() == Network.RoninDevnet.key() || network() == Network.RoninMainnetShadow.key() + || network() == Network.RoninTestnetShadow.key() ) { param.initialOwner = makeAddr("initial-owner"); + _setProfileParam(param.profile); _setStakingParam(param.staking); _setMaintenanceParam(param.maintenance); _setStakingVestingParam(param.stakingVesting); @@ -73,6 +75,13 @@ contract RoninMigration is BaseMigration { rawCallData = abi.encode(param); } + function _setProfileParam( + ISharedArgument.ProfileParam memory param + ) internal { + param.cooldown = vm.envOr("COOLDOWN", uint256(0 days)); + param.rollupManager = makeAddr("rollup-manager"); + } + function _setRoninValidatorSetREP10Migrator( ISharedArgument.RoninValidatorSetREP10MigratorParam memory param ) internal view { @@ -191,7 +200,7 @@ contract RoninMigration is BaseMigration { function _setRoninValidatorSetParam( ISharedArgument.RoninValidatorSetParam memory param - ) internal view { + ) internal { param.maxValidatorNumber = vm.envOr("MAX_VALIDATOR_NUMBER", uint256(15)); param.maxPrioritizedValidatorNumber = vm.envOr("MAX_PRIORITIZED_VALIDATOR_NUMBER", uint256(4)); param.numberOfBlocksInEpoch = vm.envOr("NUMBER_OF_BLOCKS_IN_EPOCH", uint256(200)); @@ -199,6 +208,7 @@ contract RoninMigration is BaseMigration { param.minEffectiveDaysOnwards = vm.envOr("MIN_EFFECTIVE_DAYS_ONWARDS", uint256(7)); param.emergencyExitLockedAmount = vm.envOr("EMERGENCY_EXIT_LOCKED_AMOUNT", uint256(500)); param.emergencyExpiryDuration = vm.envOr("EMERGENCY_EXPIRY_DURATION", uint256(14 days)); + param.zkFeePlaza = makeAddr("zk-fee-plaza"); } function _setGovernanceAdminParam( @@ -296,7 +306,7 @@ contract RoninMigration is BaseMigration { if ( currentNetwork == DefaultNetwork.RoninTestnet.key() || currentNetwork == DefaultNetwork.RoninMainnet.key() || currentNetwork == Network.RoninDevnet.key() || currentNetwork == DefaultNetwork.LocalHost.key() - || currentNetwork == Network.ShadowForkMainnet.key() + || currentNetwork == Network.RoninMainnetShadow.key() || currentNetwork == Network.RoninTestnetShadow.key() ) { // handle for ronin network console.log(StdStyle.yellow("Voting on RoninGovernanceAdmin for upgrading...")); diff --git a/script/deploy-dpos/DeployDPoS.s.sol b/script/deploy-dpos/DeployDPoS.s.sol index 223194d1..ca7276f0 100644 --- a/script/deploy-dpos/DeployDPoS.s.sol +++ b/script/deploy-dpos/DeployDPoS.s.sol @@ -94,7 +94,7 @@ contract DeployDPoS is RoninMigration { _initStaking(param.staking); _initTrustedOrg(param.roninTrustedOrganization); _initValidatorSet(param.roninValidatorSet); - _initProfile(); + _initProfile(param.profile); _initMaintenance(param.maintenance); _initSlashIndicator(param.slashIndicator); _initStakingVesting(param.stakingVesting); @@ -223,10 +223,14 @@ contract DeployDPoS is RoninMigration { } } - function _initProfile() internal logFn("_initProfile") { + function _initProfile( + ISharedArgument.ProfileParam memory param + ) internal logFn("_initProfile") { vm.startBroadcast(sender()); profile.initialize(address(validatorSet)); profile.initializeV2(address(staking), address(trustedOrg)); + profile.initializeV3(param.cooldown); + profile.initializeV4(param.rollupManager); vm.stopBroadcast(); } @@ -321,6 +325,7 @@ contract DeployDPoS is RoninMigration { // validatorSet.initializeV2(); validatorSet.initializeV3(address(fastFinalityTracking)); validatorSet.initializeV4(address(profile)); + validatorSet.initializeV5(param.zkFeePlaza); vm.stopBroadcast(); UpgradeInfo({ diff --git a/script/interfaces/ISharedArgument.sol b/script/interfaces/ISharedArgument.sol index bc5d8c61..2d2e6cfd 100644 --- a/script/interfaces/ISharedArgument.sol +++ b/script/interfaces/ISharedArgument.sol @@ -76,6 +76,7 @@ interface ISharedArgument is IGeneralConfig { uint256 minEffectiveDaysOnwards; uint256 emergencyExitLockedAmount; uint256 emergencyExpiryDuration; + address zkFeePlaza; } struct RoninGovernanceAdminParam { @@ -90,6 +91,7 @@ interface ISharedArgument is IGeneralConfig { struct ProfileParam { uint256 cooldown; + address rollupManager; } struct RoninRandomBeaconParam { diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/01_Upgrade_ShadowFork_Mainnet_v0_8_0.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/01_Upgrade_ShadowFork_Mainnet_v0_8_0.s.sol index fecd682a..00e42db9 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/01_Upgrade_ShadowFork_Mainnet_v0_8_0.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/01_Upgrade_ShadowFork_Mainnet_v0_8_0.s.sol @@ -73,7 +73,7 @@ contract Migration__01_Upgrade_ShadowForkMainnet_Release_V0_8_0 is RoninMigratio IRoninGovernanceAdmin private roninGovernanceAdmin; IRoninTrustedOrganization private roninTrustedOrganization; - function run() public onlyOn(Network.ShadowForkMainnet.key()) { + function run() public onlyOn(Network.RoninMainnetShadow.key()) { staking = IStaking(loadContract(Contract.Staking.key())); slashIndicator = ISlashIndicator(loadContract(Contract.SlashIndicator.key())); roninValidatorSet = IRoninValidatorSet(loadContract(Contract.RoninValidatorSet.key())); diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/02_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/02_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol index b42de48d..e49c418e 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/02_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/02_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol @@ -25,7 +25,7 @@ contract Migration_02_SetupVRFKeyHash_ShadowFork_Mainnet is RoninMigration { IRoninTrustedOrganization private trustedOrg; LibVRFProof.VRFKey[] private keys; - function run() public onlyOn(Network.ShadowForkMainnet.key()) { + function run() public onlyOn(Network.RoninMainnetShadow.key()) { profile = IProfile(loadContract(Contract.Profile.key())); trustedOrg = IRoninTrustedOrganization(loadContract(Contract.RoninTrustedOrganization.key())); diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/02_ProposeAndExecuteNewProposal_ShadowMainnet.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/02_ProposeAndExecuteNewProposal_ShadowMainnet.s.sol index 392eb9f2..639c9048 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/02_ProposeAndExecuteNewProposal_ShadowMainnet.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/02_ProposeAndExecuteNewProposal_ShadowMainnet.s.sol @@ -9,7 +9,7 @@ contract Migration__02_ProposeAndExecuteNewProposal_ShadowMainnet_Release_V0_8_1 Proposal.ProposalDetail internal _proposal; - function run() public virtual override onlyOn(Network.ShadowForkMainnet.key()) { + function run() public virtual override onlyOn(Network.RoninMainnetShadow.key()) { super.run(); vm.chainId(2020); address payable[] memory allContracts = config.getAllAddresses(network()); diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/03_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/03_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol index d2d230ed..9a93f50f 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/03_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/03_SetUpVRFKeyHash_ShadowFork_Mainnet.s.sol @@ -25,7 +25,7 @@ contract Migration_03_SetupVRFKeyHash_ShadowFork_Mainnet is RoninMigration { IRoninTrustedOrganization private trustedOrg; LibVRFProof.VRFKey[] private keys; - function run() public onlyOn(Network.ShadowForkMainnet.key()) { + function run() public onlyOn(Network.RoninMainnetShadow.key()) { profile = IProfile(loadContract(Contract.Profile.key())); trustedOrg = IRoninTrustedOrganization(loadContract(Contract.RoninTrustedOrganization.key())); diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/04_ProposeAndExecuteProposalPatch_ShadowMainnet.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/04_ProposeAndExecuteProposalPatch_ShadowMainnet.s.sol index 3a38ba20..afad96d6 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/04_ProposeAndExecuteProposalPatch_ShadowMainnet.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/04_ProposeAndExecuteProposalPatch_ShadowMainnet.s.sol @@ -12,7 +12,7 @@ contract Migration__04_ProposeAndExecuteProposalPatch_ShadowMainnet_Release_V0_8 Proposal.ProposalDetail internal _proposal; - function run() public virtual override onlyOn(Network.ShadowForkMainnet.key()) { + function run() public virtual override onlyOn(Network.RoninMainnetShadow.key()) { super.run(); roninRandomBeacon = IRandomBeacon(loadContract(Contract.RoninRandomBeacon.key())); vm.chainId(2020); diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/05_Propose_HotFix_FastFinalityTracking_ShadowMainnet.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/05_Propose_HotFix_FastFinalityTracking_ShadowMainnet.s.sol index e2edc2ac..30116791 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/05_Propose_HotFix_FastFinalityTracking_ShadowMainnet.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/05_Propose_HotFix_FastFinalityTracking_ShadowMainnet.s.sol @@ -12,7 +12,7 @@ contract Migration__05_Propose_HotFix_ShadowMainnet_Release_V0_8_1C is REP10_Con Proposal.ProposalDetail internal _proposal; - function run() public virtual override onlyOn(Network.ShadowForkMainnet.key()) { + function run() public virtual override onlyOn(Network.RoninMainnetShadow.key()) { vm.chainId(2020); super.run(); diff --git a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/REP10_Config_Mainnet_Base.s.sol b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/REP10_Config_Mainnet_Base.s.sol index 448dceee..73eee743 100644 --- a/script/release-v0.8.0/ronin-mainnet-shadow/20242206/REP10_Config_Mainnet_Base.s.sol +++ b/script/release-v0.8.0/ronin-mainnet-shadow/20242206/REP10_Config_Mainnet_Base.s.sol @@ -77,7 +77,7 @@ abstract contract REP10_Config_Mainnet_Base is RoninMigration { IRoninGovernanceAdmin internal roninGovernanceAdmin; IRoninTrustedOrganization internal roninTrustedOrganization; - function run() public virtual onlyOn(Network.ShadowForkMainnet.key()) { + function run() public virtual onlyOn(Network.RoninMainnetShadow.key()) { staking = IStaking(loadContract(Contract.Staking.key())); stakingVesting = IStakingVesting(loadContract(Contract.StakingVesting.key())); slashIndicator = ISlashIndicator(loadContract(Contract.SlashIndicator.key())); diff --git a/script/release-v0.8.2/ronin-testnet-shadow/01_Upgrade_TestnetShadow_Release_v0_8_2.s.sol b/script/release-v0.8.2/ronin-testnet-shadow/01_Upgrade_TestnetShadow_Release_v0_8_2.s.sol new file mode 100644 index 00000000..a89f9834 --- /dev/null +++ b/script/release-v0.8.2/ronin-testnet-shadow/01_Upgrade_TestnetShadow_Release_v0_8_2.s.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { TransparentUpgradeableProxy } from + "@openzeppelin-v4/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { DefaultNetwork } from "@fdk/utils/DefaultNetwork.sol"; + +import { IProfile } from "src/interfaces/IProfile.sol"; +import { IRoninGovernanceAdmin } from "src/interfaces/IRoninGovernanceAdmin.sol"; +import { IRoninTrustedOrganization } from "src/interfaces/IRoninTrustedOrganization.sol"; + +import { Proposal } from "src/libraries/Proposal.sol"; +import { RoninValidatorSetConstructor } from "src/ronin/validator/RoninValidatorSetConstructor.sol"; + +import { RoninMigration } from "script/RoninMigration.s.sol"; + +import { ISharedArgument } from "script/interfaces/ISharedArgument.sol"; +import { LibProposal } from "script/shared/libraries/LibProposal.sol"; + +import { Contract } from "script/utils/Contract.sol"; +import { Network } from "script/utils/Network.sol"; + +contract Migration_01_Upgrade_TestnetShadow_Release_v0_8_2 is RoninMigration { + uint256 internal constant DEFAULT_EXPIRY = 1 hours; + + function run() public virtual onlyOn(Network.RoninTestnetShadow.key()) { + address zkFeePlaza = 0x3aB093b72EbD8B3B5222641b300Bd09EcBC6A6a6; + address zkRollupManager = 0x3F087034ca3bA5792591cbB93CB1CCFb650Abf85; + + // Set the chain ID to the Ronin Testnet chain ID + vm.chainId(vme.getNetworkData(DefaultNetwork.RoninTestnet.key()).chainId); + + address profileLogic = _deployLogic(Contract.Profile.key()); + address validatorSetInitializer = vm.deployCode("RoninValidatorSetConstructor.sol"); + address validatorSetLogic = _deployLogic(Contract.RoninValidatorSet.key()); + + address[] memory targets = new address[](3); + targets[0] = loadContract(Contract.Profile.key()); + targets[1] = loadContract(Contract.RoninValidatorSet.key()); + targets[2] = targets[1]; + + uint256[] memory values = new uint256[](3); + values[0] = 0; + values[1] = 0; + values[2] = 0; + + bytes[] memory callDatas = new bytes[](3); + callDatas[0] = abi.encodeCall( + TransparentUpgradeableProxy.upgradeToAndCall, + (profileLogic, abi.encodeCall(IProfile.initializeV4, (zkRollupManager))) + ); + callDatas[1] = abi.encodeCall( + TransparentUpgradeableProxy.upgradeToAndCall, + (validatorSetInitializer, abi.encodeCall(RoninValidatorSetConstructor.initializeV5, (zkFeePlaza))) + ); + callDatas[2] = abi.encodeCall(TransparentUpgradeableProxy.upgradeTo, (validatorSetLogic)); + + address gov = loadContract(Contract.RoninGovernanceAdmin.key()); + address trustedOrg = loadContract(Contract.RoninTrustedOrganization.key()); + + Proposal.ProposalDetail memory proposal = LibProposal.buildProposal( + IRoninGovernanceAdmin(gov), block.timestamp + DEFAULT_EXPIRY, targets, values, callDatas + ); + + LibProposal.executeProposal(IRoninGovernanceAdmin(gov), IRoninTrustedOrganization(trustedOrg), proposal); + } + + function _afterRunningScript() internal virtual override { + // Do nothing + } +} diff --git a/script/shared/libraries/LibProposal.sol b/script/shared/libraries/LibProposal.sol index 7ef3c644..517db35e 100644 --- a/script/shared/libraries/LibProposal.sol +++ b/script/shared/libraries/LibProposal.sol @@ -57,6 +57,9 @@ library LibProposal { Ballot.VoteType support ) internal { IRoninTrustedOrganization.TrustedOrganization[] memory allTrustedOrgs = roninTrustedOrg.getAllTrustedOrganizations(); + for (uint256 i; i < allTrustedOrgs.length; ++i) { + console.log("Governor[%s]: %s", i, vm.toString(allTrustedOrgs[i].governor)); + } bool shouldPrankOnly = config.isPostChecking(); @@ -82,6 +85,7 @@ library LibProposal { break; } + console.log("%s is voting for the proposal", vm.toString(iTrustedOrg)); if (shouldPrankOnly) { vm.prank(iTrustedOrg); } else { diff --git a/script/shared/libraries/LibWrapUpEpoch.sol b/script/shared/libraries/LibWrapUpEpoch.sol index c8dbe2d1..d5065b8b 100644 --- a/script/shared/libraries/LibWrapUpEpoch.sol +++ b/script/shared/libraries/LibWrapUpEpoch.sol @@ -110,6 +110,9 @@ library LibWrapUpEpoch { if (logs[i].topics[0] == ICoinbaseExecution.FastFinalityRewardDistributionFailed.selector) { revert("PANIC: Fast finality reward distribution failed for validator"); } + if (logs[i].topics[0] == ICoinbaseExecution.L2MiningRewardDistributionFailed.selector) { + revert("PANIC: L2 mining reward distribution failed for validator"); + } } } diff --git a/script/utils/Network.sol b/script/utils/Network.sol index 96144359..5ebca1cb 100644 --- a/script/utils/Network.sol +++ b/script/utils/Network.sol @@ -8,7 +8,8 @@ enum Network { Goerli, EthMainnet, RoninDevnet, - ShadowForkMainnet + RoninMainnetShadow, + RoninTestnetShadow } using { key, chainId, chainAlias, explorer, data } for Network global; @@ -31,7 +32,8 @@ function blockTime( if (network == Network.Goerli) return 15; if (network == Network.EthMainnet) return 15; if (network == Network.RoninDevnet) return 3; - if (network == Network.ShadowForkMainnet) return 3; + if (network == Network.RoninMainnetShadow) return 3; + if (network == Network.RoninTestnetShadow) return 3; revert("Network: Unknown block time"); } @@ -41,7 +43,8 @@ function chainId( if (network == Network.Goerli) return 5; if (network == Network.EthMainnet) return 1; if (network == Network.RoninDevnet) return 2021; - if (network == Network.ShadowForkMainnet) return 6060; + if (network == Network.RoninMainnetShadow) return 6060; + if (network == Network.RoninTestnetShadow) return 6063; revert("Network: Unknown chain id"); } @@ -56,7 +59,9 @@ function explorer( ) pure returns (string memory link) { if (network == Network.Goerli) return "https://goerli.etherscan.io/"; if (network == Network.EthMainnet) return "https://etherscan.io/"; - if (network == Network.ShadowForkMainnet) return "https://app.roninchain.com/"; + if (network == Network.RoninMainnetShadow) return "https://app.roninchain.com/"; + if (network == Network.RoninDevnet) return "https://ronindev-explorer.sandbox.roninchain.com/"; + if (network == Network.RoninTestnetShadow) return "https://shadow.sandbox.roninchain.com/"; } function chainAlias( @@ -65,6 +70,7 @@ function chainAlias( if (network == Network.Goerli) return "goerli"; if (network == Network.EthMainnet) return "ethereum"; if (network == Network.RoninDevnet) return "ronin-devnet"; - if (network == Network.ShadowForkMainnet) return "ronin-mainnet-shadow"; + if (network == Network.RoninMainnetShadow) return "ronin-mainnet-shadow"; + if (network == Network.RoninTestnetShadow) return "ronin-testnet-shadow"; revert("Network: Unknown network alias"); } diff --git a/src/interfaces/IProfile.sol b/src/interfaces/IProfile.sol index fef04f1f..22fe9342 100644 --- a/src/interfaces/IProfile.sol +++ b/src/interfaces/IProfile.sol @@ -37,6 +37,13 @@ interface IProfile { bytes32 vrfKeyHash; /// @dev Timestamp of last change of VRF key hash. Only used in the logic of Beacon. Not used for checking for cooldown of updating the profile. uint256 vrfKeyHashLastChange; + /////////////////// ZkEVM Related Variables ////////////////////// + /// @dev The rollup id of the candidate. + uint32 rollupId; + /// @dev The trusted aggregator to verify the zk proof. + address aggregator; + /// @dev The trusted sequencer to sequence batch of L2 transactions. + address sequencer; } /// @dev Event emitted when a profile with `id` is added. @@ -53,6 +60,12 @@ interface IProfile { event VRFKeyHashChanged(address indexed id, bytes32 vrfKeyHash); /// @dev Event emitted when the pubkey is verified successfully. event PubkeyVerified(bytes pubkey, bytes proofOfPossession); + /// @dev Event emitted when the validator create new rollup contract. + event RollupCreated(address indexed id, uint32 indexed rollupId); + /// @dev Event emitted when the validator change the trusted aggregator. + event AggregatorChanged(address indexed id, address indexed aggregator); + /// @dev Event emitted when the validator change the trusted sequencer. + event SequencerChanged(address indexed id, address indexed sequencer); /// @dev Error of already existed profile. error ErrExistentProfile(); @@ -74,7 +87,11 @@ interface IProfile { error ErrInvalidProofOfPossession(bytes pubkey, bytes proofOfPossession); error ErrLookUpIdFailed(TConsensus consensus); error ErrLookUpIdFromVRFKeyFailed(bytes32 vrfKeyHash); + error ErrLookUpIdFromRollupIdFailed(uint32 rollupId); error ErrValidatorOnRenunciation(address cid); + error ErrExistentRollup(address cid, uint32 createdRollupId); + error ErrZeroRollupId(address cid); + error ErrRollupIdAlreadyRegistered(uint32 rollupId); function initialize( address validatorContract @@ -86,6 +103,43 @@ interface IProfile { uint256 cooldown ) external; + function initializeV4( + address rollupManager + ) external; + + /// @dev Getter to query `id` from `rollupId`. + function getRollupId2Id( + uint32 rollupId + ) external view returns (address id); + + /// @dev Getter to query `rollupId` from `id` address. + function getId2RollupId( + address id + ) external view returns (uint32); + + /// @dev Getter to query `aggregator` from `id` address. + function getId2Aggregator( + address id + ) external view returns (address); + + /// @dev Getter to query `sequencer` from `id` address. + function getId2Sequencer( + address id + ) external view returns (address); + + /* + * @dev Cross-contract function to add new rollup contract of a candidate. + * + * Requirements: + * - Only `rollupManager` can call this method. + * - On renounce, the candidate cannot create a new rollup contract. + * - `id` must be a valid candidate. + * - `rollupId` must be unique. + * - `rollupId` must be greater than 0. + * - Candidate must not have any rollup id before. + */ + function execCreateRollup(address id, uint32 rollupId) external; + /// @dev Getter to query full `profile` from `id` address. function getId2Profile( address id @@ -186,6 +240,11 @@ interface IProfile { bytes32 vrfKeyHash ) external view returns (bool found, address id); + /// @dev Getter to backward query from `rollupId` to `id` address. + function tryGetRollupId2Id( + uint32 rollupId + ) external view returns (bool found, address id); + /// @dev Getter to backward batch query from `consensus` address to `id` address. function getManyConsensus2Id( TConsensus[] memory consensus @@ -206,6 +265,26 @@ interface IProfile { bytes calldata proofOfPossession ) external; + /** + * @dev Updated the `sequencer` address of candidate id `id` immediately without waiting time. + * Requirements: + * - Only admin can call this method. + * - The profile must be existed. + * - The new sequencer address must not be duplicated or zero. + * - Must have created rollup contract before. + */ + function changeSequencerAddr(address id, address newSequencer) external; + + /** + * @dev Updated the `aggregator` address of candidate id `id` immediately without waiting time. + * Requirements: + * - Only admin can call this method. + * - The profile must be existed. + * - The new aggregator address must not be duplicated or zero. + * - Must have created rollup contract before. + */ + function changeAggregatorAddr(address id, address newAggregator) external; + /** * @dev Updated the treasury address of candidate id `id` immediately without waiting time. * diff --git a/src/interfaces/validator/ICandidateManager.sol b/src/interfaces/validator/ICandidateManager.sol index f73883d8..1d62cb47 100644 --- a/src/interfaces/validator/ICandidateManager.sol +++ b/src/interfaces/validator/ICandidateManager.sol @@ -71,6 +71,8 @@ interface ICandidateManager { error ErrInvalidCommissionRate(); /// @dev Error of invalid min effective days onwards. error ErrInvalidMinEffectiveDaysOnwards(); + /// @dev Error of validator who is rollup owner cannot renounce. + error ErrRollupOwnerCannotRenounce(); /** * @dev Returns the maximum number of validator candidate. diff --git a/src/interfaces/validator/ICoinbaseExecution.sol b/src/interfaces/validator/ICoinbaseExecution.sol index 43158659..4b830e80 100644 --- a/src/interfaces/validator/ICoinbaseExecution.sol +++ b/src/interfaces/validator/ICoinbaseExecution.sol @@ -17,16 +17,22 @@ interface ICoinbaseExecution is ISlashingExecution { event EmptyValidatorSet(uint256 indexed period, uint256 indexed epoch, address[] fallbackCids); /// @dev Emitted when the validator set is updated event ValidatorSetUpdated(uint256 indexed period, uint256 indexed epoch, address[] cids); - /// @dev Emitted when the bridge operator set is updated, to mirror the in-jail and maintaining status of the validator. + /// @dev Emitted when the block producer operator set is updated, to mirror the in-jail and maintaining status of the validator. event BlockProducerSetUpdated(uint256 indexed period, uint256 indexed epoch, address[] cids); - /// @dev Emitted when the bridge operator set is updated. - event BridgeOperatorSetUpdated(uint256 indexed period, uint256 indexed epoch, address[] bridgeOperators); /// @dev Emitted when the reward of the block producer is deprecated. event BlockRewardDeprecated(address indexed cid, uint256 rewardAmount, BlockRewardDeprecatedType deprecatedType); /// @dev Emitted when the block reward is submitted. event BlockRewardSubmitted(address indexed cid, uint256 submittedAmount, uint256 bonusAmount); + /// @dev Emitted when the L2 tx fee is submitted. + event L2BlockRewardSubmitted(address indexed cid, uint256 submittedAmount); + /// @dev Emitted when the mining reward of corresponding l2 is distributed. + event L2MiningRewardDistributed(address indexed cid, address indexed recipient, uint256 amount); + /// @dev Emitted when the contract fails when distributing the mining reward of corresponding l2. + event L2MiningRewardDistributionFailed( + address indexed cid, address indexed recipient, uint256 amount, uint256 contractBalance + ); /// @dev Emitted when the block producer reward is distributed. event MiningRewardDistributed(address indexed cid, address indexed recipient, uint256 amount); /// @dev Emitted when the contract fails when distributing the block producer reward. @@ -34,19 +40,6 @@ interface ICoinbaseExecution is ISlashingExecution { address indexed cid, address indexed recipient, uint256 amount, uint256 contractBalance ); - /// @dev Emitted when the bridge operator reward is distributed. - event BridgeOperatorRewardDistributed( - address indexed cid, address indexed bridgeOperator, address indexed recipientAddr, uint256 amount - ); - /// @dev Emitted when the contract fails when distributing the bridge operator reward. - event BridgeOperatorRewardDistributionFailed( - address indexed cid, - address indexed bridgeOperator, - address indexed recipient, - uint256 amount, - uint256 contractBalance - ); - /// @dev Emitted when the fast finality reward is distributed to validator. event FastFinalityRewardDistributed(address indexed cid, address indexed recipient, uint256 amount); /// @dev Emitted when the contract fails when distributing the fast finality reward to validator. @@ -54,6 +47,12 @@ interface ICoinbaseExecution is ISlashingExecution { address indexed cid, address indexed recipient, uint256 amount, uint256 contractBalance ); + /// @dev Emitted when the L2 tx fee is distributed to the delegator. + event L2MiningRewardDelegatorsDistributed(address[] cids, uint256[] delegatingAmounts); + /// @dev Emitted when the contract fails when distributing the L2 tx fee to the delegator. + event L2MiningRewardDelegatorsDistributionFailed( + address[] cids, uint256[] delegatingAmounts, uint256 contractBalance + ); /// @dev Emitted when the amount of block mining reward is distributed to staking contract for delegators. event MiningRewardDelegatorsDistributed(address[] cids, uint256[] delegatingAmounts); /// @dev Emitted when the contracts fails when distributing the amount of RON to the staking contract for delegators. @@ -85,6 +84,20 @@ interface ICoinbaseExecution is ISlashingExecution { */ function submitBlockReward() external payable; + /** + * @dev Receives L2 tx fee from `ZkEVMFeePlazaL1` contract. + * + * - L2 fee will be distributed to both `validator` and their corresponding `delegator`. + * - L2 fee is shared among the validators and delegators based on `commissionRate`. + * + * Requirements: + * - The method caller is `ZkEVMFeePlazaL1` contract. + * @param cid The candidate id (owner) of the rollup contract. + */ + function onL2BlockRewardSubmitted( + address cid + ) external payable; + /** * @dev Wraps up the current epoch. * @@ -96,7 +109,6 @@ interface ICoinbaseExecution is ISlashingExecution { * Emits the event `MiningRewardDistributed` when some validator has reward distributed. * Emits the event `StakingRewardDistributed` when some staking pool has reward distributed. * Emits the event `BlockProducerSetUpdated` when the epoch is wrapped up. - * Emits the event `BridgeOperatorSetUpdated` when the epoch is wrapped up at period ending. * Emits the event `ValidatorSetUpdated` when the epoch is wrapped up at period ending, and the validator set gets updated. * Emits the event `WrappedUpEpoch`. * diff --git a/src/interfaces/validator/IRoninValidatorSet.sol b/src/interfaces/validator/IRoninValidatorSet.sol index 7985af4d..726771a6 100644 --- a/src/interfaces/validator/IRoninValidatorSet.sol +++ b/src/interfaces/validator/IRoninValidatorSet.sol @@ -46,4 +46,7 @@ interface IRoninValidatorSet is function initializeV4( address profileContract ) external; + function initializeV5( + address zkFeePlazaContract + ) external; } diff --git a/src/libraries/LibArray.sol b/src/libraries/LibArray.sol index 0946771c..dabb538f 100644 --- a/src/libraries/LibArray.sol +++ b/src/libraries/LibArray.sol @@ -29,14 +29,16 @@ library LibArray { */ function addAndSum( uint256[] memory arr1, - uint256[] memory arr2 + uint256[] memory arr2, + uint256[] memory arr3 ) internal pure returns (uint256[] memory res, uint256 total) { uint256 length = arr1.length; if (length != arr2.length) revert ErrLengthMismatch(); + if (length != arr3.length) revert ErrLengthMismatch(); res = new uint256[](length); for (uint256 i; i < length; ++i) { - res[i] = arr1[i] + arr2[i]; + res[i] = arr1[i] + arr2[i] + arr3[i]; total += res[i]; } } diff --git a/src/mocks/MockProfile.sol b/src/mocks/MockProfile.sol index 71fd0261..36d324ee 100644 --- a/src/mocks/MockProfile.sol +++ b/src/mocks/MockProfile.sol @@ -10,9 +10,9 @@ import "./MockPrecompile.sol"; contract MockProfile is Profile { bool internal _verificationFailed; - function addNewProfile( + function exposed_addNewProfile( CandidateProfile memory profile - ) external onlyAdmin { + ) external { CandidateProfile storage _profile = _id2Profile[profile.id]; if (_profile.id != address(0)) revert ErrExistentProfile(); _addNewProfile(_profile, profile); diff --git a/src/mocks/validator/MockValidatorSet.sol b/src/mocks/validator/MockValidatorSet.sol index 55a54005..e638c668 100644 --- a/src/mocks/validator/MockValidatorSet.sol +++ b/src/mocks/validator/MockValidatorSet.sol @@ -63,8 +63,16 @@ contract MockValidatorSet is address profileContract ) external { } + function initializeV5( + address zkFeePlazaContract + ) external { } + function submitBlockReward() external payable override { } + function onL2BlockRewardSubmitted( + address cid + ) external payable override { } + function getPeriodEndBlock( uint256 period ) external view override returns (uint256) { } diff --git a/src/ronin/profile/Profile.sol b/src/ronin/profile/Profile.sol index bcd47c46..722216cb 100644 --- a/src/ronin/profile/Profile.sol +++ b/src/ronin/profile/Profile.sol @@ -50,6 +50,12 @@ contract Profile is IProfile, ProfileXComponents, Initializable { _setCooldownConfig(cooldown); } + function initializeV4( + address rollupManager + ) external reinitializer(4) { + _setContract(ContractType.ZK_ROLLUP_MANAGER, rollupManager); + } + /** * @dev Add addresses of renounced candidates into registry. Only called during {initializeV2}. */ @@ -71,6 +77,44 @@ contract Profile is IProfile, ProfileXComponents, Initializable { emit ProfileMigrated(id, candidateAdmin, treasury); } + /** + * @inheritdoc IProfile + */ + function getId2RollupId( + address id + ) public view returns (uint32) { + return _id2Profile[id].rollupId; + } + + /** + * @inheritdoc IProfile + */ + function getRollupId2Id( + uint32 rollupId + ) external view returns (address) { + (bool found, address id) = _tryGetRollupId2Id(rollupId); + if (!found) revert ErrLookUpIdFromRollupIdFailed(rollupId); + return id; + } + + /** + * @inheritdoc IProfile + */ + function getId2Aggregator( + address id + ) external view returns (address) { + return _id2Profile[id].aggregator; + } + + /** + * @inheritdoc IProfile + */ + function getId2Sequencer( + address id + ) external view returns (address) { + return _id2Profile[id].sequencer; + } + /** * @inheritdoc IProfile */ @@ -253,6 +297,15 @@ contract Profile is IProfile, ProfileXComponents, Initializable { return _getVRFKeyHash2Id(vrfKeyHash); } + /** + * @inheritdoc IProfile + */ + function tryGetRollupId2Id( + uint32 rollupId + ) external view returns (bool found, address id) { + return _tryGetRollupId2Id(rollupId); + } + /** * @inheritdoc IProfile */ @@ -313,6 +366,16 @@ contract Profile is IProfile, ProfileXComponents, Initializable { found = id != address(0); } + /** + * @dev Try Look up the `id` by `rollupId`, return a boolean indicating whether the query success. + */ + function _tryGetRollupId2Id( + uint32 rollupId + ) internal view returns (bool found, address id) { + id = _rollupId2Id[rollupId]; + found = id != address(0); + } + /** * @inheritdoc IProfile */ @@ -449,6 +512,28 @@ contract Profile is IProfile, ProfileXComponents, Initializable { _setVRFKeyHash(_profile, vrfKeyHash); } + /** + * @inheritdoc IProfile + */ + function changeSequencerAddr(address id, address sequencer) external onlyAdmin { + CandidateProfile storage _profile = _getId2ProfileHelper(id); + + _requireNonZeroAndNonDuplicated(RoleAccess.SEQUENCER, sequencer); + _requireRollupCreated(_profile); + _setSequencer(_profile, sequencer); + } + + /** + * @inheritdoc IProfile + */ + function changeAggregatorAddr(address id, address aggregator) external onlyAdmin { + CandidateProfile storage _profile = _getId2ProfileHelper(id); + + _requireNonZeroAndNonDuplicated(RoleAccess.AGGREGATOR, aggregator); + _requireRollupCreated(_profile); + _setAggregator(_profile, aggregator); + } + function _requireCandidateAdmin( CandidateProfile storage sProfile ) internal view { @@ -458,13 +543,6 @@ contract Profile is IProfile, ProfileXComponents, Initializable { ) revert ErrUnauthorized(msg.sig, RoleAccess.CANDIDATE_ADMIN); } - function _requireNotOnRenunciation( - address id - ) internal view { - IRoninValidatorSet validatorContract = IRoninValidatorSet(getContract(ContractType.VALIDATOR)); - if (validatorContract.getCandidateInfoById(id).revokingTimestamp > 0) revert ErrValidatorOnRenunciation(id); - } - /** * @inheritdoc IProfile */ diff --git a/src/ronin/profile/ProfileHandler.sol b/src/ronin/profile/ProfileHandler.sol index fd4372f9..69f49517 100644 --- a/src/ronin/profile/ProfileHandler.sol +++ b/src/ronin/profile/ProfileHandler.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.9; +import { IRoninValidatorSet } from "../../interfaces/validator/IRoninValidatorSet.sol"; import { PCUVerifyBLSPublicKey } from "../../precompile-usages/PCUVerifyBLSPublicKey.sol"; import "../../udvts/Types.sol"; +import { ContractType } from "../../utils/ContractType.sol"; import "../../utils/RoleAccess.sol"; import { ProfileStorage } from "./ProfileStorage.sol"; @@ -25,6 +27,13 @@ abstract contract ProfileHandler is PCUVerifyBLSPublicKey, ProfileStorage { _requireNonDuplicatedPubkey(profile.pubkey); } + function _requireNotOnRenunciation( + address id + ) internal view { + IRoninValidatorSet validatorContract = IRoninValidatorSet(getContract(ContractType.VALIDATOR)); + if (validatorContract.getCandidateInfoById(id).revokingTimestamp > 0) revert ErrValidatorOnRenunciation(id); + } + function _requireNonZeroAndNonDuplicated(RoleAccess addressType, address addr) internal view { if (addr == address(0)) revert ErrZeroAddress(addressType); _requireNonDuplicated(addressType, addr); @@ -86,6 +95,15 @@ abstract contract ProfileHandler is PCUVerifyBLSPublicKey, ProfileStorage { } } + /** + * @dev Checks if the candidate has a rollup contract created and reverts if not. + */ + function _requireRollupCreated( + CandidateProfile storage _profile + ) internal view { + if (_profile.rollupId == 0) revert ErrZeroRollupId(_profile.id); + } + function _requireCooldownPassedAndStartCooldown( CandidateProfile storage _profile ) internal { diff --git a/src/ronin/profile/ProfileStorage.sol b/src/ronin/profile/ProfileStorage.sol index 7f164a10..493a02f2 100644 --- a/src/ronin/profile/ProfileStorage.sol +++ b/src/ronin/profile/ProfileStorage.sol @@ -29,8 +29,11 @@ abstract contract ProfileStorage is IProfile, HasContracts { /// @dev Mapping from vrf key hash => id address. mapping(bytes32 vrfKeyHash => address cid) internal _vrfKeyHash2Id; + /// @dev Mapping from rollup id => id address. + mapping(uint32 rollupId => address cid) internal _rollupId2Id; + /// @dev Upgradeable gap. - bytes32[46] __gap; + bytes32[45] __gap; /** * @dev Add a profile from memory to storage. @@ -120,6 +123,26 @@ abstract contract ProfileStorage is IProfile, HasContracts { emit VRFKeyHashChanged(_profile.id, vrfKeyHash); } + /** + * @dev Change the aggregator address of the profile. + */ + function _setAggregator(CandidateProfile storage _profile, address aggregator) internal { + _profile.aggregator = aggregator; + _registry[uint256(uint160(address(aggregator)))] = true; + + emit AggregatorChanged(_profile.id, aggregator); + } + + /** + * @dev Change the sequencer address of the profile. + */ + function _setSequencer(CandidateProfile storage _profile, address sequencer) internal { + _profile.sequencer = sequencer; + _registry[uint256(uint160(address(sequencer)))] = true; + + emit SequencerChanged(_profile.id, sequencer); + } + function _startCooldown( CandidateProfile storage _profile ) internal { diff --git a/src/ronin/profile/ProfileXComponents.sol b/src/ronin/profile/ProfileXComponents.sol index e1394934..27959045 100644 --- a/src/ronin/profile/ProfileXComponents.sol +++ b/src/ronin/profile/ProfileXComponents.sol @@ -9,6 +9,29 @@ import "./ProfileHandler.sol"; pragma solidity ^0.8.9; abstract contract ProfileXComponents is IProfile, ProfileHandler { + /** + * @inheritdoc IProfile + */ + function execCreateRollup(address id, uint32 rollupId) external onlyContract(ContractType.ZK_ROLLUP_MANAGER) { + CandidateProfile storage _profile = _getId2ProfileHelper(id); + + _requireNotOnRenunciation(id); + + if (_registry[rollupId]) revert ErrRollupIdAlreadyRegistered(rollupId); + if (rollupId == 0) revert ErrZeroRollupId(id); + if (_profile.id == address(0)) revert ErrNonExistentProfile(); + if (_profile.rollupId != 0) revert ErrExistentRollup(id, _profile.rollupId); + + _profile.rollupId = rollupId; + _registry[rollupId] = true; + + // By default, the aggregator and sequencer are the same as the profile id. + _setAggregator(_profile, id); + _setSequencer(_profile, id); + + emit RollupCreated(id, rollupId); + } + /** * @inheritdoc IProfile */ @@ -36,7 +59,10 @@ abstract contract ProfileXComponents is IProfile, ProfileHandler { oldConsensus: TConsensus.wrap(address(0)), registeredAt: block.timestamp, vrfKeyHash: 0x0, - vrfKeyHashLastChange: 0 + vrfKeyHashLastChange: 0, + rollupId: 0, + aggregator: address(0), + sequencer: address(0) }); _requireNonDuplicatedInRegistry(profile); diff --git a/src/ronin/validator/CandidateManager.sol b/src/ronin/validator/CandidateManager.sol index 3bb05b56..ec71bd03 100644 --- a/src/ronin/validator/CandidateManager.sol +++ b/src/ronin/validator/CandidateManager.sol @@ -91,6 +91,15 @@ abstract contract CandidateManager is return _candidateIndex[cid] != 0; } + /** + * @dev Checks whether the candidate has created a rollup before. + */ + function _isRollupOwner( + address cid + ) internal view returns (bool) { + return IProfile(getContract(ContractType.PROFILE)).getId2RollupId(cid) != 0; + } + /** * @inheritdoc ICandidateManager */ @@ -300,6 +309,7 @@ abstract contract CandidateManager is function _setRevokingTimestamp(ValidatorCandidate storage _candidate, uint256 timestamp) internal { address cid = __css2cid(_candidate.__shadowedConsensus); if (!_isValidatorCandidateById(cid)) revert ErrNonExistentCandidate(); + if (_isRollupOwner(cid)) revert ErrRollupOwnerCannotRenounce(); _candidate.revokingTimestamp = timestamp; emit CandidateRevokingTimestampUpdated(cid, timestamp); } diff --git a/src/ronin/validator/CoinbaseExecution.sol b/src/ronin/validator/CoinbaseExecution.sol index 39760034..d260779f 100644 --- a/src/ronin/validator/CoinbaseExecution.sol +++ b/src/ronin/validator/CoinbaseExecution.sol @@ -38,10 +38,6 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe modifier oncePerEpoch() { if (epochOf(_lastUpdatedBlock) >= epochOf(block.number)) revert ErrAlreadyWrappedEpoch(); _lastUpdatedBlock = block.number; - // TODO: remove this line in the next upgrade - if (_firstTrackedPeriodEnd == 0) { - _firstTrackedPeriodEnd = _lastUpdatedPeriod; - } _; } @@ -96,6 +92,20 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe _delegatorMiningReward[id] += delegatorMiningReward; } + /** + * @inheritdoc ICoinbaseExecution + */ + function onL2BlockRewardSubmitted( + address cid + ) external payable onlyContract(ContractType.ZK_FEE_PLAZA) { + (uint256 validatorL2Fee, uint256 delegatorL2Fee) = _calcCommissionReward(cid, msg.value); + + _validatorL2MiningReward[cid] += validatorL2Fee; + _delegatorL2MiningReward[cid] += delegatorL2Fee; + + emit L2BlockRewardSubmitted(cid, msg.value); + } + /** * @inheritdoc ICoinbaseExecution */ @@ -123,10 +133,13 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe randomBeacon.execRecordAndSlashUnavailability(lastPeriod, newPeriod, address(slashIndicatorContract), allCids); slashIndicatorContract.execUpdateCreditScores(allCids, lastPeriod); - (uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) = - _distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(lastPeriod, allCids); + ( + uint256[] memory delegatorBlockMiningRewards, + uint256[] memory delegatorL2BlockMiningRewards, + uint256[] memory delegatorFastFinalityRewards + ) = _distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(lastPeriod, allCids); _settleAndTransferDelegatingRewards( - lastPeriod, allCids, delegatorBlockMiningRewards, delegatorFastFinalityRewards + lastPeriod, allCids, delegatorBlockMiningRewards, delegatorL2BlockMiningRewards, delegatorFastFinalityRewards ); _tryRecycleLockedFundsFromEmergencyExits(); _recycleDeprecatedRewards(); @@ -206,12 +219,20 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe function _distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward( uint256 lastPeriod, address[] memory cids - ) private returns (uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) { + ) + private + returns ( + uint256[] memory delegatorBlockMiningRewards, + uint256[] memory delegatorL2BlockMiningRewards, + uint256[] memory delegatorFastFinalityRewards + ) + { address vId; // validator id address payable treasury; uint256 length = cids.length; delegatorBlockMiningRewards = new uint256[](length); + delegatorL2BlockMiningRewards = new uint256[](length); delegatorFastFinalityRewards = new uint256[](length); (uint256 minRate, uint256 maxRate) = IStaking(getContract(ContractType.STAKING)).getCommissionRateRange(); @@ -220,6 +241,10 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe vId = cids[i]; treasury = _candidateInfo[vId].__shadowedTreasury; + // Distribute the L2 mining reward to the treasury address of the validator regardless of the jail status. + delegatorL2BlockMiningRewards[i] = _delegatorL2MiningReward[vId]; + _distributeL2MiningReward(vId, treasury); + if (!_isJailedById(vId) && !_miningRewardDeprecatedById(vId, lastPeriod)) { (uint256 validatorFFReward, uint256 delegatorFFReward) = _calcCommissionReward({ vId: vId, @@ -240,6 +265,28 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe delete _delegatorMiningReward[vId]; delete _validatorMiningReward[vId]; delete _fastFinalityReward[vId]; + delete _delegatorL2MiningReward[vId]; + delete _validatorL2MiningReward[vId]; + } + } + + /** + * @dev Distributes the L2 mining reward to the validator's treasury address. + * + * Emits the `MiningRewardL2Distributed` once the reward is distributed successfully. + * Emits the `MiningRewardL2DistributionFailed` once the contract fails to distribute reward. + * + * Note: This method should be called once in the end of each period. + */ + function _distributeL2MiningReward(address cid, address payable treasury) private { + uint256 amount = _validatorL2MiningReward[cid]; + if (amount > 0) { + if (_unsafeSendRONLimitGas(treasury, amount, DEFAULT_ADDITION_GAS)) { + emit L2MiningRewardDistributed(cid, treasury, amount); + return; + } + + emit L2MiningRewardDistributionFailed(cid, treasury, amount, address(this).balance); } } @@ -298,10 +345,12 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe uint256 period, address[] memory cids, uint256[] memory delegatorMiningRewards, + uint256[] memory delegatorL2MiningRewards, uint256[] memory delegatorFFRewards ) private { IStaking staking = IStaking(getContract(ContractType.STAKING)); - (uint256[] memory totalRewards, uint256 sumReward) = LibArray.addAndSum(delegatorMiningRewards, delegatorFFRewards); + (uint256[] memory totalRewards, uint256 sumReward) = + LibArray.addAndSum(delegatorMiningRewards, delegatorL2MiningRewards, delegatorFFRewards); if (sumReward != 0) { if (_unsafeSendRON(payable(address(staking)), sumReward)) { @@ -309,6 +358,7 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe emit FastFinalityRewardDelegatorsDistributed(cids, delegatorFFRewards); emit MiningRewardDelegatorsDistributed(cids, delegatorMiningRewards); + emit L2MiningRewardDelegatorsDistributed(cids, delegatorL2MiningRewards); return; } @@ -316,6 +366,7 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe uint256 selfBalance = address(this).balance; emit FastFinalityRewardDelegatorsDistributionFailed(cids, delegatorFFRewards, selfBalance); emit MiningRewardDelegatorsDistributionFailed(cids, delegatorMiningRewards, selfBalance); + emit L2MiningRewardDelegatorsDistributionFailed(cids, delegatorL2MiningRewards, selfBalance); } } @@ -440,7 +491,6 @@ abstract contract CoinbaseExecution is ICoinbaseExecution, CoinbaseExecutionDepe * - This method is called at the end of each epoch * * Emits the `BlockProducerSetUpdated` event. - * Emits the `BridgeOperatorSetUpdated` event. * */ function _updateApplicableValidatorToBlockProducerSet( diff --git a/src/ronin/validator/RoninValidatorSetConstructor.sol b/src/ronin/validator/RoninValidatorSetConstructor.sol index 04d69031..bc56d449 100644 --- a/src/ronin/validator/RoninValidatorSetConstructor.sol +++ b/src/ronin/validator/RoninValidatorSetConstructor.sol @@ -79,6 +79,12 @@ contract RoninValidatorSetConstructor is Initializable, CoinbaseExecutionDependa _setContract(ContractType.PROFILE, profileContract); } + function initializeV5( + address zkFeePlazaContract + ) external reinitializer(5) { + _setContract(ContractType.ZK_FEE_PLAZA, zkFeePlazaContract); + } + /** * @dev Only receives RON from staking vesting contract (for topping up bonus), and from staking contract (for transferring * deducting amount on slashing). diff --git a/src/ronin/validator/storage-fragments/CommonStorage.sol b/src/ronin/validator/storage-fragments/CommonStorage.sol index 13d48e65..cbc744f1 100644 --- a/src/ronin/validator/storage-fragments/CommonStorage.sol +++ b/src/ronin/validator/storage-fragments/CommonStorage.sol @@ -8,15 +8,15 @@ import "./TimingStorage.sol"; import "./ValidatorInfoStorageV2.sol"; abstract contract CommonStorage is ICommonInfo, TimingStorage, JailingStorage, ValidatorInfoStorageV2 { - /// @dev Mapping from consensus address => pending reward from producing block - mapping(address consensus => uint256 miningReward) internal _validatorMiningReward; - /// @dev Mapping from consensus address => pending reward from producing block for delegator - mapping(address consensus => uint256 miningReward) internal _delegatorMiningReward; + /// @dev Mapping from cid address => pending reward from producing block + mapping(address cid => uint256 miningReward) internal _validatorMiningReward; + /// @dev Mapping from cid address => pending reward from producing block for delegator + mapping(address cid => uint256 miningReward) internal _delegatorMiningReward; /// @dev The total reward for fast finality uint256 internal _totalFastFinalityReward; /// @dev Mapping from consensus address => pending reward for fast finality - mapping(address => uint256) internal _fastFinalityReward; + mapping(address cid => uint256) internal _fastFinalityReward; /// @dev The deprecated reward that has not been withdrawn by admin uint256 internal _totalDeprecatedReward; @@ -27,16 +27,21 @@ abstract contract CommonStorage is ICommonInfo, TimingStorage, JailingStorage, V uint256 internal _emergencyExpiryDuration; /// @dev The address list of consensus addresses that being locked fund. address[] internal _lockedConsensusList; - /// @dev Mapping from consensus => request exist info - mapping(address => EmergencyExitInfo) internal _exitInfo; - /// @dev Mapping from consensus => flag indicating whether the locked fund is released - mapping(address => bool) internal _lockedFundReleased; + /// @dev Mapping from cid => request exist info + mapping(address cid => EmergencyExitInfo) internal _exitInfo; + /// @dev Mapping from cid => flag indicating whether the locked fund is released + mapping(address cid => bool) internal _lockedFundReleased; + + /// @dev Mapping from candidate id => L2 tx fee for validator + mapping(address cid => uint256 l2TxFee) internal _validatorL2MiningReward; + /// @dev Mapping from candidate id => L2 tx fee for delegator + mapping(address cid => uint256 l2TxFee) internal _delegatorL2MiningReward; /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. */ - uint256[44] private ______gap; + uint256[42] private ______gap; /** * @inheritdoc ICommonInfo diff --git a/src/utils/ContractType.sol b/src/utils/ContractType.sol index 5b629b65..93f9420d 100644 --- a/src/utils/ContractType.sol +++ b/src/utils/ContractType.sol @@ -35,5 +35,9 @@ enum ContractType { /* 15 */ PROFILE, /* 16 */ - RANDOM_BEACON + RANDOM_BEACON, + /* 17 */ + ZK_FEE_PLAZA, + /* 18 */ + ZK_ROLLUP_MANAGER } diff --git a/src/utils/RoleAccess.sol b/src/utils/RoleAccess.sol index 4ea496c3..9326d5d8 100644 --- a/src/utils/RoleAccess.sol +++ b/src/utils/RoleAccess.sol @@ -23,5 +23,9 @@ enum RoleAccess { /* 9 */ CONSENSUS, /* 10 */ - TREASURY + TREASURY, + /* 11 */ + SEQUENCER, + /* 12 */ + AGGREGATOR } diff --git a/test/foundry/mocks/MockValidatorSet.sol b/test/foundry/mocks/MockValidatorSet.sol index da1ba54c..f93c121c 100644 --- a/test/foundry/mocks/MockValidatorSet.sol +++ b/test/foundry/mocks/MockValidatorSet.sol @@ -4,7 +4,37 @@ pragma solidity >=0.8.17 <0.9.0; import { TConsensus } from "src/udvts/Types.sol"; contract MockValidatorSet { - uint256 _period; + struct ValidatorCandidate { + /** + * @dev The address of the candidate admin. + * @custom shadowed-storage This storage slot is always kept in sync with {Profile-CandidateProfile}.admin. + */ + address __shadowedAdmin; + /** + * @dev Address of the validator that produces block, e.g. block.coinbase. This is so-called validator address. + * @custom shadowed-storage This storage slot is always kept in sync with {Profile-CandidateProfile}.consensus. + */ + TConsensus __shadowedConsensus; + /** + * @dev Address that receives mining reward of the validator + * @custom shadowed-storage This storage slot is always kept in sync with {Profile-CandidateProfile}.treasury. + */ + address payable __shadowedTreasury; + /// @dev Address of the bridge operator corresponding to the candidate + address ____deprecatedBridgeOperatorAddr; + /** + * @dev The percentage of reward that validators can be received, the rest goes to the delegators. + * Values in range [0; 100_00] stands for 0-100% + */ + uint256 commissionRate; + /// @dev The timestamp that scheduled to revoke the candidate (no schedule=0) + uint256 revokingTimestamp; + /// @dev The deadline that the candidate must top up staking amount to keep it larger than or equal to the threshold (no deadline=0) + uint256 topupDeadline; + } + + uint256 private _period; + mapping(address id => ValidatorCandidate) private _candidate; function setPeriod( uint256 period @@ -27,4 +57,18 @@ contract MockValidatorSet { function isCandidateAdminById(address, /*cid*/ address /*admin*/ ) external pure returns (bool) { return true; } + + function getCandidateInfoById( + address id + ) external view returns (ValidatorCandidate memory validatorCandidate) { + return _candidate[id]; + } + + function exposed_setValidatorCandidate(address id, ValidatorCandidate memory candidate) external { + _candidate[id] = candidate; + } + + function exposed_setRenounceStatus(address id, uint256 revokingTimestamp) external { + _candidate[id].revokingTimestamp = revokingTimestamp; + } } diff --git a/test/foundry/unit/libraries/LibArray.t.sol b/test/foundry/unit/libraries/LibArray.t.sol index 6966c1e1..be37ed28 100644 --- a/test/foundry/unit/libraries/LibArray.t.sol +++ b/test/foundry/unit/libraries/LibArray.t.sol @@ -86,20 +86,26 @@ contract LibArrayTest is Test { assertEq(normSum, 196_128_750_000_000_000_000_000, "incorrect expected normSum"); } - function testFuzz_AddAndSum(uint256[1000] memory arr1_, uint256[1000] memory arr2_) public pure { + function testFuzz_AddAndSum( + uint256[1000] memory arr1_, + uint256[1000] memory arr2_, + uint256[1000] memory arr3_ + ) public pure { uint256[] memory arr1 = new uint256[](arr1_.length); uint256[] memory arr2 = new uint256[](arr2_.length); + uint256[] memory arr3 = new uint256[](arr3_.length); for (uint256 i; i < arr1.length; ++i) { arr1[i] = bound(arr1_[i], 0, type(uint128).max); arr2[i] = bound(arr2_[i], 0, type(uint128).max); + arr3[i] = bound(arr3_[i], 0, type(uint128).max); } uint256[] memory expected = new uint256[](arr1.length); for (uint256 i; i < arr1.length; ++i) { - expected[i] = arr1[i] + arr2[i]; + expected[i] = arr1[i] + arr2[i] + arr3[i]; } - (uint256[] memory actual, uint256 totalActual) = LibArray.addAndSum(arr1, arr2); + (uint256[] memory actual, uint256 totalActual) = LibArray.addAndSum(arr1, arr2, arr3); for (uint256 i; i < arr1.length; ++i) { assertEq(actual[i], expected[i], "actual[i] == expected[i]"); diff --git a/test/foundry/unit/ronin/profile/Profile.base.unit.t.sol b/test/foundry/unit/ronin/profile/Profile.base.unit.t.sol index 8b137891..6f45a179 100644 --- a/test/foundry/unit/ronin/profile/Profile.base.unit.t.sol +++ b/test/foundry/unit/ronin/profile/Profile.base.unit.t.sol @@ -1 +1,54 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.17 <0.9.0; +import { Test } from "forge-std/Test.sol"; + +import { TransparentUpgradeableProxyV2 } from "src/extensions/TransparentUpgradeableProxyV2.sol"; +import { IProfile } from "src/interfaces/IProfile.sol"; +import { MockProfile } from "src/mocks/MockProfile.sol"; +import { TConsensus } from "src/udvts/Types.sol"; +import { MockValidatorSet } from "test/foundry/mocks/MockValidatorSet.sol"; + +abstract contract Profile_Base_Unit_Test is Test { + MockProfile internal _profile; + MockValidatorSet internal _validatorSet; + address internal _id = makeAddr("default-id"); + address internal _proxyAdmin = makeAddr("proxy-admin"); + address internal _staking = makeAddr("staking"); + address internal _validatorAdmin = makeAddr("default-admin"); + address internal _zkRollupManager = makeAddr("zk-rollup-manager"); + bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + function setUp() public virtual { + _validatorSet = new MockValidatorSet(); + + MockProfile _profileLogic = new MockProfile(); + _profile = MockProfile(address(new TransparentUpgradeableProxyV2(address(_profileLogic), _proxyAdmin, ""))); + _profile.initialize(address(_validatorSet)); + _profile.initializeV2(_staking, address(0)); + _profile.initializeV3(10); + _profile.initializeV4(_zkRollupManager); + + _profile.exposed_addNewProfile( + IProfile.CandidateProfile({ + id: _id, + consensus: TConsensus.wrap(_id), + admin: _validatorAdmin, + treasury: payable(_id), + __reservedGovernor: address(0), + vrfKeyHash: 0x0, + pubkey: "", + profileLastChange: 0, + oldPubkey: "", + oldConsensus: TConsensus.wrap(address(0)), + registeredAt: 0, + vrfKeyHashLastChange: 0, + rollupId: 0, + aggregator: address(0), + sequencer: address(0) + }) + ); + + vm.stopPrank(); + } +} diff --git a/test/foundry/unit/ronin/profile/Profile.concrete.unit.t.sol b/test/foundry/unit/ronin/profile/Profile.concrete.unit.t.sol index 73f2a933..c16e0af4 100644 --- a/test/foundry/unit/ronin/profile/Profile.concrete.unit.t.sol +++ b/test/foundry/unit/ronin/profile/Profile.concrete.unit.t.sol @@ -1,56 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.17 <0.9.0; -import { Test } from "forge-std/Test.sol"; - -import { TransparentUpgradeableProxyV2 } from "src/extensions/TransparentUpgradeableProxyV2.sol"; -import { IProfile } from "src/interfaces/IProfile.sol"; -import { MockProfile } from "src/mocks/MockProfile.sol"; -import { TConsensus } from "src/udvts/Types.sol"; -import { MockValidatorSet } from "test/foundry/mocks/MockValidatorSet.sol"; - -contract Profile_Concrete_Unit_Test is Test { - MockProfile internal _profile; - MockValidatorSet internal _validatorSetContract; - address internal immutable _stakingContract = address(0x10000); - address internal immutable _validatorAdmin = address(0x20001); - bytes32 constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - - function setUp() public virtual { - _validatorSetContract = new MockValidatorSet(); - - MockProfile _profileLogic = new MockProfile(); - _profile = MockProfile(address(new TransparentUpgradeableProxyV2(address(_profileLogic), address(1), ""))); - _profile.initialize(address(_validatorSetContract)); - _profile.initializeV2(_stakingContract, address(0)); - _profile.initializeV3(10); - - vm.startPrank(address(1)); - - TransparentUpgradeableProxyV2 _proxy = TransparentUpgradeableProxyV2(payable(address(_profile))); - _proxy.functionDelegateCall( - abi.encodeWithSelector( - MockProfile.addNewProfile.selector, - IProfile.CandidateProfile({ - id: address(0x20000), - consensus: TConsensus.wrap(address(0x20000)), - admin: _validatorAdmin, - treasury: payable(address(0x20000)), - __reservedGovernor: address(0), - vrfKeyHash: 0x0, - pubkey: "", - profileLastChange: 0, - oldPubkey: "", - oldConsensus: TConsensus.wrap(address(0)), - registeredAt: 0, - vrfKeyHashLastChange: 0 - }) - ) - ); - - vm.stopPrank(); - } +import "./Profile.base.unit.t.sol"; +contract Profile_Concrete_Unit_Test is Profile_Base_Unit_Test { function testConcrete_RevertWhen_ChangePubkey() external { IProfile.CandidateProfile memory _validatorProfile; @@ -71,10 +24,10 @@ contract Profile_Concrete_Unit_Test is Test { vm.stopPrank(); } - function test_RevertWhen_ApplyValidatorCandidate() external { + function testConcrete_RevertWhen_ApplyValidatorCandidate() external { _profile.setVerificationFailed(true); - vm.startPrank(_stakingContract); + vm.startPrank(_staking); vm.expectRevert(abi.encodeWithSelector(IProfile.ErrInvalidProofOfPossession.selector, "0xcc", "")); _profile.execApplyValidatorCandidate({ admin: address(0x30000), @@ -96,7 +49,7 @@ contract Profile_Concrete_Unit_Test is Test { vm.stopPrank(); } - function test_RevertWhen_ChangePubkeyCooldownNotEnded() external { + function testConcrete_RevertWhen_ChangePubkeyCooldownNotEnded() external { vm.startPrank(_validatorAdmin); vm.warp(block.timestamp + 11); @@ -115,8 +68,8 @@ contract Profile_Concrete_Unit_Test is Test { vm.stopPrank(); } - function test_ArePublicKeysRegistered() external { - vm.startPrank(_stakingContract); + function testConcrete_ArePublicKeysRegistered() external { + vm.startPrank(_staking); _profile.setVerificationFailed(false); _profile.execApplyValidatorCandidate({ diff --git a/test/foundry/unit/ronin/profile/profile-x-zk/Profile.zk-integration.concrete.unit.t.sol b/test/foundry/unit/ronin/profile/profile-x-zk/Profile.zk-integration.concrete.unit.t.sol new file mode 100644 index 00000000..6fc92268 --- /dev/null +++ b/test/foundry/unit/ronin/profile/profile-x-zk/Profile.zk-integration.concrete.unit.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.17 <0.9.0; + +import { RoleAccess } from "src/utils/RoleAccess.sol"; + +import "../Profile.base.unit.t.sol"; + +contract Profile_ZkIntegration_Concrete_Unit_Test is Profile_Base_Unit_Test { + address internal _cid1 = makeAddr("cid-1"); + address internal _cid2 = makeAddr("cid-2"); + + function setUp() public virtual override { + super.setUp(); + + // Prank setup candidate #1 + MockValidatorSet.ValidatorCandidate memory candidate1; + candidate1.__shadowedAdmin = makeAddr("admin-1"); + candidate1.__shadowedConsensus = TConsensus.wrap(makeAddr("consensus-1")); + candidate1.__shadowedTreasury = payable(makeAddr("treasury-1")); + candidate1.commissionRate = 100; + _validatorSet.exposed_setValidatorCandidate(_cid1, candidate1); + IProfile.CandidateProfile memory profile1; + profile1.id = _cid1; + profile1.consensus = TConsensus.wrap(_cid1); + profile1.admin = candidate1.__shadowedAdmin; + profile1.treasury = candidate1.__shadowedTreasury; + _profile.exposed_addNewProfile(profile1); + + // Prank setup candidate #2 + MockValidatorSet.ValidatorCandidate memory candidate2; + candidate2.__shadowedAdmin = _validatorAdmin; + candidate2.__shadowedConsensus = TConsensus.wrap(makeAddr("consensus-2")); + candidate2.__shadowedTreasury = payable(makeAddr("treasury-2")); + candidate2.commissionRate = 100; + candidate2.revokingTimestamp = 0; + candidate2.topupDeadline = 0; + _validatorSet.exposed_setValidatorCandidate(_cid2, candidate2); + IProfile.CandidateProfile memory profile2; + profile2.id = _cid2; + profile2.consensus = TConsensus.wrap(_cid2); + profile2.admin = candidate2.__shadowedAdmin; + profile2.treasury = candidate2.__shadowedTreasury; + _profile.exposed_addNewProfile(profile2); + } + + function testConcrete_execCreateRollup() external { + uint32 rollupId = 1; + + vm.expectEmit(address(_profile)); + emit IProfile.AggregatorChanged(_cid1, _cid1); + vm.expectEmit(address(_profile)); + emit IProfile.SequencerChanged(_cid1, _cid1); + vm.expectEmit(address(_profile)); + emit IProfile.RollupCreated(_cid1, rollupId); + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + assertEq(_profile.getId2Profile(_cid1).rollupId, rollupId, "rollupId should be set"); + assertEq(_profile.getId2Profile(_cid1).aggregator, _cid1, "aggregator should be set"); + assertEq(_profile.getId2Profile(_cid1).sequencer, _cid1, "sequencer should be set"); + } + + function testConcrete_RevertIf_OnRenunciation_execCreateRollup() external { + uint32 rollupId = 1; + + _validatorSet.exposed_setRenounceStatus(_cid1, block.timestamp + 1 days); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrValidatorOnRenunciation.selector, _cid1)); + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + } + + function testConcrete_RevertIf_RegisterRollupIdTwice() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrRollupIdAlreadyRegistered.selector, rollupId)); + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + } + + function testConcrete_execCreateRollup_ForCid1AndCid2() external { + uint32 rollupId1 = 1; + uint32 rollupId2 = 2; + + vm.expectEmit(address(_profile)); + emit IProfile.AggregatorChanged(_cid1, _cid1); + vm.expectEmit(address(_profile)); + emit IProfile.SequencerChanged(_cid1, _cid1); + vm.expectEmit(address(_profile)); + emit IProfile.RollupCreated(_cid1, rollupId1); + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId1); + + assertEq(_profile.getId2Profile(_cid1).rollupId, rollupId1, "rollupId should be set"); + assertEq(_profile.getId2Profile(_cid1).aggregator, _cid1, "aggregator should be set"); + assertEq(_profile.getId2Profile(_cid1).sequencer, _cid1, "sequencer should be set"); + + vm.expectEmit(address(_profile)); + emit IProfile.AggregatorChanged(_cid2, _cid2); + vm.expectEmit(address(_profile)); + emit IProfile.SequencerChanged(_cid2, _cid2); + vm.expectEmit(address(_profile)); + emit IProfile.RollupCreated(_cid2, rollupId2); + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid2, rollupId2); + + assertEq(_profile.getId2Profile(_cid2).rollupId, rollupId2, "rollupId should be set"); + assertEq(_profile.getId2Profile(_cid2).aggregator, _cid2, "aggregator should be set"); + assertEq(_profile.getId2Profile(_cid2).sequencer, _cid2, "sequencer should be set"); + } + + function testConcrete_RevertIf_RegisterRollupIdZero() external { + uint32 rollupId = 0; + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrZeroRollupId.selector, _cid1)); + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + } + + function testConcrete_RevertIf_ChangeSequencer_WhenRollupNotRegistered() external { + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrZeroRollupId.selector, _cid1)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeSequencerAddr, (_cid1, makeAddr("new-sequencer"))) + ); + } + + function testConcrete_RevertIf_ChangeSequencer_ToZero() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrZeroAddress.selector, RoleAccess.SEQUENCER)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeSequencerAddr, (_cid1, address(0))) + ); + } + + function testConcrete_RevertIf_ChangeAggregator_ToZero() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrZeroAddress.selector, RoleAccess.AGGREGATOR)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeAggregatorAddr, (_cid1, address(0))) + ); + } + + function testConcrete_RevertIf_ChangeAggregator_WhenRollupNotRegistered() external { + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrZeroRollupId.selector, _cid1)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeAggregatorAddr, (_cid1, makeAddr("new-aggregator"))) + ); + } + + function testConcrete_RevertIf_ChangeAggregator_DuplicateToAnotherCid() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrDuplicatedInfo.selector, RoleAccess.AGGREGATOR, _cid2)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeAggregatorAddr, (_cid1, _cid2)) + ); + } + + function testConcrete_ReVertIf_ChangeSequencer_DuplicateToAnotherCid() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrDuplicatedInfo.selector, RoleAccess.SEQUENCER, _cid2)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeSequencerAddr, (_cid1, _cid2)) + ); + } + + function testConcrete_RevertIf_ChangeAggregator_ReUpdate() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrDuplicatedInfo.selector, RoleAccess.AGGREGATOR, _cid1)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeAggregatorAddr, (_cid1, _cid1)) + ); + } + + function testConcrete_RevertIf_ChangeSequencer_ReUpdate() external { + uint32 rollupId = 1; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrDuplicatedInfo.selector, RoleAccess.SEQUENCER, _cid1)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeSequencerAddr, (_cid1, _cid1)) + ); + } + + function testConcrete_RevertIf_ChangeAggregator_DuplicateWithAnotherAggregator() external { + uint32 rollupId1 = 1; + uint32 rollupId2 = 2; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId1); + + address newAggregator1 = makeAddr("new-aggregator-1"); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeAggregatorAddr, (_cid1, newAggregator1)) + ); + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid2, rollupId2); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrDuplicatedInfo.selector, RoleAccess.AGGREGATOR, newAggregator1)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeAggregatorAddr, (_cid2, newAggregator1)) + ); + } + + function testConcrete_RevertIf_ChangeSequencer_DuplicateWithAnotherSequencer() external { + uint32 rollupId1 = 1; + uint32 rollupId2 = 2; + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid1, rollupId1); + + address newSequencer1 = makeAddr("new-sequencer-1"); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeSequencerAddr, (_cid1, newSequencer1)) + ); + + vm.prank(_zkRollupManager); + _profile.execCreateRollup(_cid2, rollupId2); + + vm.expectRevert(abi.encodeWithSelector(IProfile.ErrDuplicatedInfo.selector, RoleAccess.SEQUENCER, newSequencer1)); + vm.prank(_proxyAdmin); + TransparentUpgradeableProxyV2(payable(address(_profile))).functionDelegateCall( + abi.encodeCall(IProfile.changeSequencerAddr, (_cid2, newSequencer1)) + ); + } +}