From 029fa3647f26617dc1ce7e6ceb02adf4b31ce15e Mon Sep 17 00:00:00 2001 From: Sergey <2901744+evercoinx@users.noreply.github.com.> Date: Thu, 26 Sep 2024 18:02:23 +0200 Subject: [PATCH] Extend error handling on ValidatorRegistry contract --- contracts/MaticX.sol | 10 +- contracts/ValidatorRegistry.sol | 48 ++- contracts/interfaces/IMaticX.sol | 6 +- contracts/interfaces/IValidatorRegistry.sol | 4 +- test/MaticX.forking.spec.ts | 119 +++--- test/ValidatorRegistry.forking.spec.ts | 378 +++++++++++++++----- 6 files changed, 385 insertions(+), 180 deletions(-) diff --git a/contracts/MaticX.sol b/contracts/MaticX.sol index c106eebe..80ac440e 100644 --- a/contracts/MaticX.sol +++ b/contracts/MaticX.sol @@ -430,11 +430,6 @@ contract MaticX is ); } - /// @notice Toggles the paused status of this contract. - function togglePause() external override onlyRole(DEFAULT_ADMIN_ROLE) { - paused() ? _unpause() : _pause(); - } - /// ------------------------------ Setters --------------------------------- /// @notice Sets a fee percent. @@ -498,6 +493,11 @@ contract MaticX is emit SetVersion(_version); } + /// @notice Toggles the paused status of this contract. + function togglePause() external override onlyRole(DEFAULT_ADMIN_ROLE) { + paused() ? _unpause() : _pause(); + } + /// ------------------------------ Getters --------------------------------- /// @notice Converts an amount of MaticX shares to POL tokens. diff --git a/contracts/ValidatorRegistry.sol b/contracts/ValidatorRegistry.sol index f86250c7..34d5562a 100644 --- a/contracts/ValidatorRegistry.sol +++ b/contracts/ValidatorRegistry.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.7; import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; import { IStakeManager } from "./interfaces/IStakeManager.sol"; import { IValidatorShare } from "./interfaces/IValidatorShare.sol"; import { IValidatorRegistry } from "./interfaces/IValidatorRegistry.sol"; @@ -16,22 +17,28 @@ contract ValidatorRegistry is AccessControlUpgradeable, ReentrancyGuardUpgradeable { + using StringsUpgradeable for string; + bytes32 public constant BOT = keccak256("BOT"); - address private stakeManager; + IStakeManager private stakeManager; address private maticToken; address private maticX; - string public override version; uint256 public override preferredDepositValidatorId; uint256 public override preferredWithdrawalValidatorId; mapping(uint256 => bool) public override validatorIdExists; - uint256[] private validators; address private polToken; /// ------------------------------ Modifiers ------------------------------- + /// @notice Checks if the given validator id is not zero. + modifier validatoIdIsZero(uint256 _validatorId) { + require(_validatorId != 0, "Zero validator id"); + _; + } + /// @notice Checks if the given validator id exists in the registry. /// @param _validatorId - Validator id modifier whenValidatorIdExists(uint256 _validatorId) { @@ -75,7 +82,7 @@ contract ValidatorRegistry is PausableUpgradeable.__Pausable_init(); require(_stakeManager != address(0), "Zero stake manager address"); - stakeManager = _stakeManager; + stakeManager = IStakeManager(_stakeManager); require(_maticToken != address(0), "Zero matic token address"); maticToken = _maticToken; @@ -111,19 +118,20 @@ contract ValidatorRegistry is external override whenNotPaused - whenValidatorIdDoesNotExist(_validatorId) onlyRole(DEFAULT_ADMIN_ROLE) + validatoIdIsZero(_validatorId) + whenValidatorIdDoesNotExist(_validatorId) { - IStakeManager.Validator memory smValidator = IStakeManager(stakeManager) - .validators(_validatorId); - + IStakeManager.Validator memory validator = stakeManager.validators( + _validatorId + ); require( - smValidator.contractAddress != address(0), + validator.contractAddress != address(0), "Validator has no validator share" ); require( - (smValidator.status == IStakeManager.Status.Active) && - smValidator.deactivationEpoch == 0, + (validator.status == IStakeManager.Status.Active) && + validator.deactivationEpoch == 0, "Validator isn't active" ); @@ -142,8 +150,9 @@ contract ValidatorRegistry is external override whenNotPaused - whenValidatorIdExists(_validatorId) onlyRole(DEFAULT_ADMIN_ROLE) + validatoIdIsZero(_validatorId) + whenValidatorIdExists(_validatorId) { require( preferredDepositValidatorId != _validatorId, @@ -154,8 +163,9 @@ contract ValidatorRegistry is "Can't remove a preferred validator for withdrawals" ); - address validatorShare = IStakeManager(stakeManager) - .getValidatorContract(_validatorId); + address validatorShare = stakeManager.getValidatorContract( + _validatorId + ); (uint256 validatorBalance, ) = IValidatorShare(validatorShare) .getTotalStake(maticX); require(validatorBalance == 0, "Validator has some shares left"); @@ -166,6 +176,7 @@ contract ValidatorRegistry is validators[i] = validators[iterationCount]; break; } + unchecked { ++i; } @@ -187,8 +198,9 @@ contract ValidatorRegistry is external override whenNotPaused - whenValidatorIdExists(_validatorId) onlyRole(BOT) + validatoIdIsZero(_validatorId) + whenValidatorIdExists(_validatorId) { preferredDepositValidatorId = _validatorId; @@ -203,8 +215,9 @@ contract ValidatorRegistry is external override whenNotPaused - whenValidatorIdExists(_validatorId) onlyRole(BOT) + validatoIdIsZero(_validatorId) + whenValidatorIdExists(_validatorId) { preferredWithdrawalValidatorId = _validatorId; @@ -227,6 +240,7 @@ contract ValidatorRegistry is function setVersion( string memory _version ) external override onlyRole(DEFAULT_ADMIN_ROLE) { + require(!_version.equal(""), "Empty version"); version = _version; emit SetVersion(_version); @@ -249,7 +263,7 @@ contract ValidatorRegistry is view override returns ( - address _stakeManager, + IStakeManager _stakeManager, address _maticToken, address _maticX, address _polToken diff --git a/contracts/interfaces/IMaticX.sol b/contracts/interfaces/IMaticX.sol index d7d81079..bbae3cc8 100644 --- a/contracts/interfaces/IMaticX.sol +++ b/contracts/interfaces/IMaticX.sol @@ -148,9 +148,6 @@ interface IMaticX is IERC20Upgradeable { uint256 _amount ) external; - /// @notice Toggles the paused status of this contract. - function togglePause() external; - /// @notice Sets a fee percent. /// @param _feePercent - Fee percent (10 = 10%) function setFeePercent(uint8 _feePercent) external; @@ -171,6 +168,9 @@ interface IMaticX is IERC20Upgradeable { /// @param _version - New version of this contract function setVersion(string calldata _version) external; + /// @notice Toggles the paused status of this contract. + function togglePause() external; + /// @notice Converts an amount of MaticX shares to POL tokens. /// @param _balance - Balance in MaticX shares /// @return Balance in POL tokens diff --git a/contracts/interfaces/IValidatorRegistry.sol b/contracts/interfaces/IValidatorRegistry.sol index d89a031a..a70336a1 100644 --- a/contracts/interfaces/IValidatorRegistry.sol +++ b/contracts/interfaces/IValidatorRegistry.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; +import { IStakeManager } from "./IStakeManager.sol"; + /// @title IValidatorRegistry /// @notice Defines a public interface for the ValidatorRegistry contract. interface IValidatorRegistry { @@ -80,7 +82,7 @@ interface IValidatorRegistry { external view returns ( - address _stakeManager, + IStakeManager _stakeManager, address _maticToken, address _maticX, address _polToken diff --git a/test/MaticX.forking.spec.ts b/test/MaticX.forking.spec.ts index ab0f4594..998ae550 100644 --- a/test/MaticX.forking.spec.ts +++ b/test/MaticX.forking.spec.ts @@ -23,6 +23,7 @@ const envVars = extractEnvironmentVariables(); describe("MaticX (Forking)", function () { const stakeAmount = ethers.utils.parseUnits("100", 18); const tripleStakeAmount = stakeAmount.mul(3); + const version = "1"; async function deployFixture(fullMaticXInitialization = true) { await reset(envVars.ROOT_CHAIN_RPC, envVars.FORKING_ROOT_BLOCK_NUMBER); @@ -585,61 +586,6 @@ describe("MaticX (Forking)", function () { }); }); - describe("Toggle pause", function () { - describe("Negative", function () { - it("Should revert with the right error if called by a non admin", async function () { - const { maticX, executor, defaultAdminRole } = - await loadFixture(deployFixture); - - const promise = maticX.connect(executor).togglePause(); - await expect(promise).to.be.revertedWith( - `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` - ); - }); - }); - - describe("Positive", function () { - it("Should emit the Paused event if pausing", async function () { - const { maticX, manager } = await loadFixture(deployFixture); - - const promise = maticX.connect(manager).togglePause(); - await expect(promise) - .to.emit(maticX, "Paused") - .withArgs(manager.address); - }); - - it("Should emit the Unpaused event if pausing", async function () { - const { maticX, manager } = await loadFixture(deployFixture); - - await maticX.connect(manager).togglePause(); - - const promise = maticX.connect(manager).togglePause(); - await expect(promise) - .to.emit(maticX, "Unpaused") - .withArgs(manager.address); - }); - - it("Should return the right paused status if toggling once", async function () { - const { maticX, manager } = await loadFixture(deployFixture); - - await maticX.connect(manager).togglePause(); - - const paused = await maticX.paused(); - expect(paused).to.be.true; - }); - - it("Should return the right paused status if toggling twice", async function () { - const { maticX, manager } = await loadFixture(deployFixture); - - await maticX.connect(manager).togglePause(); - await maticX.connect(manager).togglePause(); - - const paused = await maticX.paused(); - expect(paused).to.be.false; - }); - }); - }); - describe("Initialize V2", function () { describe("Negative", function () { it("Should revert with the right error if reinitializing", async function () { @@ -2283,16 +2229,14 @@ describe("MaticX (Forking)", function () { }); describe("Set a version", function () { - const version = "1"; - describe("Negative", function () { it("Should revert with the right error if called by a non admin", async function () { - const { maticX, stakerA, defaultAdminRole } = + const { maticX, executor, defaultAdminRole } = await loadFixture(deployFixture); - const promise = maticX.connect(stakerA).setVersion(version); + const promise = maticX.connect(executor).setVersion(version); await expect(promise).to.be.revertedWith( - `AccessControl: account ${stakerA.address.toLowerCase()} is missing role ${defaultAdminRole}` + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` ); }); @@ -2315,4 +2259,59 @@ describe("MaticX (Forking)", function () { }); }); }); + + describe("Toggle a pause", function () { + describe("Negative", function () { + it("Should revert with the right error if called by a non admin", async function () { + const { maticX, executor, defaultAdminRole } = + await loadFixture(deployFixture); + + const promise = maticX.connect(executor).togglePause(); + await expect(promise).to.be.revertedWith( + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` + ); + }); + }); + + describe("Positive", function () { + it("Should emit the Paused event if pausing", async function () { + const { maticX, manager } = await loadFixture(deployFixture); + + const promise = maticX.connect(manager).togglePause(); + await expect(promise) + .to.emit(maticX, "Paused") + .withArgs(manager.address); + }); + + it("Should emit the Unpaused event if pausing", async function () { + const { maticX, manager } = await loadFixture(deployFixture); + + await maticX.connect(manager).togglePause(); + + const promise = maticX.connect(manager).togglePause(); + await expect(promise) + .to.emit(maticX, "Unpaused") + .withArgs(manager.address); + }); + + it("Should return the right paused status if toggling once", async function () { + const { maticX, manager } = await loadFixture(deployFixture); + + await maticX.connect(manager).togglePause(); + + const paused = await maticX.paused(); + expect(paused).to.be.true; + }); + + it("Should return the right paused status if toggling twice", async function () { + const { maticX, manager } = await loadFixture(deployFixture); + + await maticX.connect(manager).togglePause(); + await maticX.connect(manager).togglePause(); + + const paused = await maticX.paused(); + expect(paused).to.be.false; + }); + }); + }); }); diff --git a/test/ValidatorRegistry.forking.spec.ts b/test/ValidatorRegistry.forking.spec.ts index 6e8565ae..617d9d46 100644 --- a/test/ValidatorRegistry.forking.spec.ts +++ b/test/ValidatorRegistry.forking.spec.ts @@ -19,8 +19,9 @@ const envVars = extractEnvironmentVariables(); describe("ValidatorRegistry (Forking)", function () { const validatorIds = [128, 72]; + const version = "1"; - async function deployFixture(fullMaticXInitialization = true) { + async function deployFixture(fullValidatorRegistryInitialization = true) { await reset(envVars.ROOT_CHAIN_RPC, envVars.FORKING_ROOT_BLOCK_NUMBER); // EOA definitions @@ -63,7 +64,7 @@ describe("ValidatorRegistry (Forking)", function () { )) as ValidatorRegistry; // Contract initializations - if (fullMaticXInitialization) { + if (fullValidatorRegistryInitialization) { await validatorRegistry.connect(manager).initializeV2(pol.address); } @@ -465,119 +466,89 @@ describe("ValidatorRegistry (Forking)", function () { }); }); - describe("Toggle pause", function () { + describe("Initialize V2", function () { describe("Negative", function () { - it("Should revert with the right error if called by a non admin", async function () { - const { validatorRegistry, executor, defaultAdminRole } = - await loadFixture(deployFixture); - - const promise = validatorRegistry - .connect(executor) - .togglePause(); - await expect(promise).to.be.revertedWith( - `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` + it("Should revert with the right error if reinitializing", async function () { + const { validatorRegistry, pol, manager } = await loadFixture( + deployFixture.bind(null, false) ); - }); - }); - - describe("Positive", function () { - it("Should emit the Paused event if pausing", async function () { - const { validatorRegistry, manager } = - await loadFixture(deployFixture); - const promise = validatorRegistry + await validatorRegistry .connect(manager) - .togglePause(); - await expect(promise) - .to.emit(validatorRegistry, "Paused") - .withArgs(manager.address); - }); - - it("Should emit the Unpaused event if pausing", async function () { - const { validatorRegistry, manager } = - await loadFixture(deployFixture); - - await validatorRegistry.connect(manager).togglePause(); + .initializeV2(pol.address); const promise = validatorRegistry .connect(manager) - .togglePause(); - await expect(promise) - .to.emit(validatorRegistry, "Unpaused") - .withArgs(manager.address); - }); - - it("Should return the right paused status if toggling once", async function () { - const { validatorRegistry, manager } = - await loadFixture(deployFixture); - - await validatorRegistry.connect(manager).togglePause(); - - const paused = await validatorRegistry.paused(); - expect(paused).to.be.true; - }); - - it("Should return the right paused status if toggling twice", async function () { - const { validatorRegistry, manager } = - await loadFixture(deployFixture); - - await validatorRegistry.connect(manager).togglePause(); - await validatorRegistry.connect(manager).togglePause(); - - const paused = await validatorRegistry.paused(); - expect(paused).to.be.false; + .initializeV2(pol.address); + await expect(promise).to.be.revertedWith( + "Initializable: contract is already initialized" + ); }); - }); - }); - describe("Set the MaticX address", function () { - const maticXAddress = generateRandomAddress(); - - describe("Negative", function () { it("Should revert with the right error if called by a non admin", async function () { - const { validatorRegistry, executor, defaultAdminRole } = - await loadFixture(deployFixture); + const { validatorRegistry, pol, executor, defaultAdminRole } = + await loadFixture(deployFixture.bind(null, false)); const promise = validatorRegistry .connect(executor) - .setMaticX(maticXAddress); + .initializeV2(pol.address); await expect(promise).to.be.revertedWith( `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` ); }); + + it("Should revert with the right error if passing the zero pol token address", async function () { + const { validatorRegistry, manager } = await loadFixture( + deployFixture.bind(null, false) + ); + + const promise = validatorRegistry + .connect(manager) + .initializeV2(ethers.constants.AddressZero); + await expect(promise).to.be.revertedWith( + "Zero POL token address" + ); + }); }); describe("Positive", function () { - it("Should emit the SetMaticX event", async function () { - const { validatorRegistry, manager } = - await loadFixture(deployFixture); + it("Should emit the Initialized event", async function () { + const { validatorRegistry, pol, manager } = await loadFixture( + deployFixture.bind(null, false) + ); const promise = validatorRegistry .connect(manager) - .setMaticX(maticXAddress); + .initializeV2(pol.address); await expect(promise) - .to.emit(validatorRegistry, "SetMaticX") - .withArgs(maticXAddress); + .to.emit(validatorRegistry, "Initialized") + .withArgs(2); }); - it("Should return the right MaticX address", async function () { - const { validatorRegistry, manager } = - await loadFixture(deployFixture); - - const [, , initialMaticXAddress] = - await validatorRegistry.getContracts(); - await validatorRegistry - .connect(manager) - .setMaticX(maticXAddress); + it("Should return the right contract addresses", async function () { + const { + validatorRegistry, + stakeManager, + matic, + maticX, + pol, + manager, + } = await loadFixture(deployFixture.bind(null, false)); await validatorRegistry .connect(manager) - .setMaticX(maticXAddress); + .initializeV2(pol.address); - const [, , currentMaticXAddress] = - await validatorRegistry.getContracts(); - expect(currentMaticXAddress).not.to.equal(initialMaticXAddress); - expect(currentMaticXAddress).to.equal(maticXAddress); + const [ + stakeManagerAddress, + maticAddress, + maticXAddress, + polAddress, + ] = await validatorRegistry.getContracts(); + expect(stakeManagerAddress).to.equal(stakeManager.address); + expect(maticAddress).to.equal(matic.address); + expect(maticXAddress).to.equal(maticX.address); + expect(polAddress).to.equal(pol.address); }); }); }); @@ -608,6 +579,16 @@ describe("ValidatorRegistry (Forking)", function () { ); }); + it("Should revert with the right error if passing the zero validator id", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(manager) + .addValidator(0); + await expect(promise).to.revertedWith("Zero validator id"); + }); + it("Should revert with the right error if having an already existing validator", async function () { const { validatorRegistry, manager } = await loadFixture(deployFixture); @@ -630,7 +611,7 @@ describe("ValidatorRegistry (Forking)", function () { const promise = validatorRegistry .connect(manager) - .addValidator(0); + .addValidator(10_000); await expect(promise).to.be.revertedWith( "Validator has no validator share" ); @@ -766,6 +747,20 @@ describe("ValidatorRegistry (Forking)", function () { ); }); + it("Should revert with the right error if passing the zero validator id", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + await validatorRegistry + .connect(manager) + .addValidator(validatorIds[0]); + + const promise = validatorRegistry + .connect(manager) + .removeValidator(0); + await expect(promise).to.revertedWith("Zero validator id"); + }); + it("Should revert with the right error if having no existing validator", async function () { const { validatorRegistry, manager } = await loadFixture(deployFixture); @@ -858,20 +853,24 @@ describe("ValidatorRegistry (Forking)", function () { await expect(promise).to.be.revertedWith("Pausable: paused"); }); - it("Should revert with the right error if having no existing validator", async function () { - const { validatorRegistry, bot } = + it("Should revert with the right error if called by a non admin", async function () { + const { validatorRegistry, executor, manager, botRole } = await loadFixture(deployFixture); + await validatorRegistry + .connect(manager) + .addValidator(validatorIds[0]); + const promise = validatorRegistry - .connect(bot) + .connect(executor) .setPreferredDepositValidatorId(validatorIds[0]); await expect(promise).to.be.revertedWith( - "Validator id doesn't exist in our registry" + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${botRole}` ); }); - it("Should revert with the right error if called by a non admin", async function () { - const { validatorRegistry, executor, manager, botRole } = + it("Should revert with the right error if passing the zero validator id", async function () { + const { validatorRegistry, manager, bot } = await loadFixture(deployFixture); await validatorRegistry @@ -879,10 +878,20 @@ describe("ValidatorRegistry (Forking)", function () { .addValidator(validatorIds[0]); const promise = validatorRegistry - .connect(executor) + .connect(bot) + .setPreferredDepositValidatorId(0); + await expect(promise).to.revertedWith("Zero validator id"); + }); + + it("Should revert with the right error if having no existing validator", async function () { + const { validatorRegistry, bot } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(bot) .setPreferredDepositValidatorId(validatorIds[0]); await expect(promise).to.be.revertedWith( - `AccessControl: account ${executor.address.toLowerCase()} is missing role ${botRole}` + "Validator id doesn't exist in our registry" ); }); }); @@ -943,6 +952,20 @@ describe("ValidatorRegistry (Forking)", function () { ); }); + it("Should revert with the right error if passing the zero validator id", async function () { + const { validatorRegistry, manager, bot } = + await loadFixture(deployFixture); + + await validatorRegistry + .connect(manager) + .addValidator(validatorIds[0]); + + const promise = validatorRegistry + .connect(bot) + .setPreferredWithdrawalValidatorId(0); + await expect(promise).to.revertedWith("Zero validator id"); + }); + it("Should revert with the right error if having no existing validator", async function () { const { validatorRegistry, bot } = await loadFixture(deployFixture); @@ -977,4 +1000,171 @@ describe("ValidatorRegistry (Forking)", function () { }); }); }); + + describe("Set the MaticX address", function () { + const maticXAddress = generateRandomAddress(); + + describe("Negative", function () { + it("Should revert with the right error if called by a non admin", async function () { + const { validatorRegistry, executor, defaultAdminRole } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(executor) + .setMaticX(maticXAddress); + await expect(promise).to.be.revertedWith( + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` + ); + }); + + it("Should revert with the right error if passing the zero MaticX address", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(manager) + .setMaticX(ethers.constants.AddressZero); + await expect(promise).to.revertedWith("Zero MaticX address"); + }); + }); + + describe("Positive", function () { + it("Should emit the SetMaticX event", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(manager) + .setMaticX(maticXAddress); + await expect(promise) + .to.emit(validatorRegistry, "SetMaticX") + .withArgs(maticXAddress); + }); + + it("Should return the right MaticX address", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const [, , initialMaticXAddress] = + await validatorRegistry.getContracts(); + await validatorRegistry + .connect(manager) + .setMaticX(maticXAddress); + + await validatorRegistry + .connect(manager) + .setMaticX(maticXAddress); + + const [, , currentMaticXAddress] = + await validatorRegistry.getContracts(); + expect(currentMaticXAddress).not.to.equal(initialMaticXAddress); + expect(currentMaticXAddress).to.equal(maticXAddress); + }); + }); + }); + + describe("Set a version", function () { + describe("Negative", function () { + it("Should revert with the right error if called by a non admin", async function () { + const { validatorRegistry, executor, defaultAdminRole } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(executor) + .setVersion(version); + await expect(promise).to.be.revertedWith( + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` + ); + }); + + it("Should revert with the right error if passing an empty version", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(manager) + .setVersion(""); + await expect(promise).to.be.revertedWith("Empty version"); + }); + }); + + describe("Positive", function () { + it("Should emit the SetVersion event", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(manager) + .setVersion(version); + await expect(promise) + .to.emit(validatorRegistry, "SetVersion") + .withArgs(version); + }); + }); + }); + + describe("Toggle a pause", function () { + describe("Negative", function () { + it("Should revert with the right error if called by a non admin", async function () { + const { validatorRegistry, executor, defaultAdminRole } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(executor) + .togglePause(); + await expect(promise).to.be.revertedWith( + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` + ); + }); + }); + + describe("Positive", function () { + it("Should emit the Paused event if pausing", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + const promise = validatorRegistry + .connect(manager) + .togglePause(); + await expect(promise) + .to.emit(validatorRegistry, "Paused") + .withArgs(manager.address); + }); + + it("Should emit the Unpaused event if pausing", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + await validatorRegistry.connect(manager).togglePause(); + + const promise = validatorRegistry + .connect(manager) + .togglePause(); + await expect(promise) + .to.emit(validatorRegistry, "Unpaused") + .withArgs(manager.address); + }); + + it("Should return the right paused status if toggling once", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + await validatorRegistry.connect(manager).togglePause(); + + const paused = await validatorRegistry.paused(); + expect(paused).to.be.true; + }); + + it("Should return the right paused status if toggling twice", async function () { + const { validatorRegistry, manager } = + await loadFixture(deployFixture); + + await validatorRegistry.connect(manager).togglePause(); + await validatorRegistry.connect(manager).togglePause(); + + const paused = await validatorRegistry.paused(); + expect(paused).to.be.false; + }); + }); + }); });