diff --git a/contracts/Governance/GovernorBravoDelegate.sol b/contracts/Governance/GovernorBravoDelegate.sol index 71694239..33893a60 100644 --- a/contracts/Governance/GovernorBravoDelegate.sol +++ b/contracts/Governance/GovernorBravoDelegate.sol @@ -477,6 +477,18 @@ contract GovernorBravoDelegate is GovernorBravoDelegateStorageV2, GovernorBravoE return votes; } + /** + * @notice Update address of XVS vault + * @dev Admin only. Update XVS Vault address + * @param xvsVault_ Address of XVS vault + */ + function _setXvsVault(address xvsVault_) external { + require(msg.sender == admin, "GovernorBravo::_setXvsVault: admin only"); + require(xvsVault_ != address(0), "GovernorBravo::setXvsVault: invalid xvs address"); + emit SetXvsVault(address(xvsVault), xvsVault_); + xvsVault = XvsVaultInterface(xvsVault_); + } + /** * @notice Sets the new governance guardian * @param newGuardian the address of the new guardian diff --git a/contracts/Governance/GovernorBravoInterfaces.sol b/contracts/Governance/GovernorBravoInterfaces.sol index 99ad041e..b9952f79 100644 --- a/contracts/Governance/GovernorBravoInterfaces.sol +++ b/contracts/Governance/GovernorBravoInterfaces.sol @@ -61,6 +61,9 @@ contract GovernorBravoEvents { /// @notice Emitted when the maximum number of operations in one proposal is updated event ProposalMaxOperationsUpdated(uint oldMaxOperations, uint newMaxOperations); + + ///@notice Emitted when address of XVS vault updated + event SetXvsVault(address indexed oldXvsVault, address indexed newXvsVault); } /** diff --git a/tests/Fork/GovernanceBravoStorage.ts b/tests/Fork/GovernanceBravoStorage.ts new file mode 100644 index 00000000..41309feb --- /dev/null +++ b/tests/Fork/GovernanceBravoStorage.ts @@ -0,0 +1,72 @@ +import { expect } from "chai"; +import { BigNumber, Signer } from "ethers"; +import { ethers } from "hardhat"; + +import { + GovernorBravoDelegate, + GovernorBravoDelegate__factory, + GovernorBravoDelegator, + GovernorBravoDelegator__factory, +} from "../../typechain"; +import { forking, initMainnetUser } from "./utils"; + +const delegatorProxyAddress = "0x2d56dC077072B53571b8252008C60e945108c75a"; +const guardianAddress = "0x1C2CAc6ec528c20800B2fe734820D87b581eAA6B"; +let governorBravoDelegator: GovernorBravoDelegator; +let admin: string; +let votingDelay: BigNumber; +let pendingAdmin: string; +let implementation: string; +let impersonatedGuardian: Signer; +let governorBravoDelegate: GovernorBravoDelegate; +let votingPeriod: BigNumber; +let proposalThreshold: BigNumber; +let initialProposalId: BigNumber; +let xvsVault: string; +let proposalMaxOperations: BigNumber; +let guardian: string; + +async function configureBravo() { + impersonatedGuardian = await initMainnetUser(guardianAddress, ethers.utils.parseEther("2")); + governorBravoDelegator = GovernorBravoDelegator__factory.connect(delegatorProxyAddress, impersonatedGuardian); + governorBravoDelegate = GovernorBravoDelegate__factory.connect(delegatorProxyAddress, impersonatedGuardian); +} + +const FORK_MAINNET = process.env.FORK == "true" && process.env.FORKED_NETWORK == "bscmainnet"; +if (FORK_MAINNET) { + const blockNumber = 35984931; + forking(blockNumber, async () => { + describe("Governor Bravo Storage Layout Test", async () => { + before(async () => { + await configureBravo(); + votingDelay = await governorBravoDelegate.votingDelay(); + pendingAdmin = await governorBravoDelegate.pendingAdmin(); + implementation = await governorBravoDelegate.implementation(); + admin = await governorBravoDelegate.admin(); + votingPeriod = await governorBravoDelegate.votingPeriod(); + proposalThreshold = await governorBravoDelegate.proposalThreshold(); + initialProposalId = await governorBravoDelegate.initialProposalId(); + xvsVault = await governorBravoDelegate.xvsVault(); + proposalMaxOperations = await governorBravoDelegate.proposalMaxOperations(); + guardian = await governorBravoDelegate.guardian(); + }); + it("Verify states after upgrade", async () => { + const governorBravoDelegateFactory = await ethers.getContractFactory("GovernorBravoDelegate"); + const governorBravoDelegateNew = await governorBravoDelegateFactory.deploy(); + await governorBravoDelegateNew.deployed(); + await governorBravoDelegator.connect(impersonatedGuardian)._setImplementation(governorBravoDelegateNew.address); + + expect(votingDelay).equals(await governorBravoDelegate.votingDelay()); + expect(pendingAdmin).equals(await governorBravoDelegate.pendingAdmin()); + expect(implementation).not.equals(await governorBravoDelegate.implementation()); + expect(admin).equals(await governorBravoDelegate.admin()); + expect(votingPeriod).equals(await governorBravoDelegate.votingPeriod()); + expect(proposalThreshold).equals(await governorBravoDelegate.proposalThreshold()); + expect(initialProposalId).equals(await governorBravoDelegate.initialProposalId()); + expect(xvsVault).equals(await governorBravoDelegate.xvsVault()); + expect(proposalMaxOperations).equals(await governorBravoDelegate.proposalMaxOperations()); + expect(guardian).equals(await governorBravoDelegate.guardian()); + }); + }); + }); +} diff --git a/tests/Fork/utils.ts b/tests/Fork/utils.ts new file mode 100644 index 00000000..ee5b12dd --- /dev/null +++ b/tests/Fork/utils.ts @@ -0,0 +1,35 @@ +import { impersonateAccount, setBalance } from "@nomicfoundation/hardhat-network-helpers"; +import { NumberLike } from "@nomicfoundation/hardhat-network-helpers/dist/src/types"; +import { ethers } from "hardhat"; +import { network } from "hardhat"; + +export const setForkBlock = async (blockNumber: number) => { + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: process.env[`ARCHIVE_NODE_${process.env.FORKED_NETWORK}`], + blockNumber, + }, + }, + ], + }); +}; + +export const forking = (blockNumber: number, fn: () => void) => { + describe(`At block #${blockNumber}`, () => { + before(async () => { + await setForkBlock(blockNumber); + }); + fn(); + }); +}; + +export const initMainnetUser = async (user: string, balance?: NumberLike) => { + await impersonateAccount(user); + if (balance !== undefined) { + await setBalance(user, balance); + } + return ethers.getSigner(user); +}; diff --git a/tests/Governance/GovernanceBravo/setterTest.ts b/tests/Governance/GovernanceBravo/setterTest.ts new file mode 100644 index 00000000..a9e89264 --- /dev/null +++ b/tests/Governance/GovernanceBravo/setterTest.ts @@ -0,0 +1,56 @@ +import { FakeContract, MockContract, smock } from "@defi-wonderland/smock"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import chai from "chai"; +import { Signer } from "ethers"; +import { ethers } from "hardhat"; + +import { GovernorBravoDelegate, GovernorBravoDelegate__factory, XVSVault } from "../../../typechain"; + +const { expect } = chai; +chai.use(smock.matchers); + +let root: Signer; +let user: Signer; +let governorBravoDelegate: MockContract; +let xvsVault: FakeContract; + +type GovernorBravoDelegateFixture = { + governorBravoDelegate: MockContract; + xvsVault: FakeContract; +}; + +async function governorBravoFixture(): Promise { + const GovernorBravoDelegateFactory = await smock.mock("GovernorBravoDelegate"); + const governorBravoDelegate = await GovernorBravoDelegateFactory.deploy(); + const xvsVault = await smock.fake("XVSVault"); + return { governorBravoDelegate, xvsVault }; +} +describe("Governance Bravo Setter Test", async () => { + beforeEach(async () => { + [root, user] = await ethers.getSigners(); + const contracts = await loadFixture(governorBravoFixture); + ({ governorBravoDelegate, xvsVault } = contracts); + await governorBravoDelegate.setVariable("admin", await root.getAddress()); + await governorBravoDelegate.setVariable("xvsVault", xvsVault.address); + }); + + describe("XvsVault setter in Governance Bravo", async () => { + it("Xvs vault address should be updated", async () => { + const newXvsVault = await smock.fake("XVSVault"); + expect(await governorBravoDelegate.xvsVault()).to.equal(xvsVault.address); + await governorBravoDelegate._setXvsVault(newXvsVault.address); + expect(await governorBravoDelegate.xvsVault()).to.equal(newXvsVault.address); + }); + it("Revert on unauthorized access", async () => { + const newXvsVault = await smock.fake("XVSVault"); + await expect(governorBravoDelegate.connect(user)._setXvsVault(newXvsVault.address)).to.be.revertedWith( + "GovernorBravo::_setXvsVault: admin only", + ); + }); + it("Reverts on zero address", async () => { + await expect(governorBravoDelegate._setXvsVault(ethers.constants.AddressZero)).to.be.revertedWith( + "GovernorBravo::setXvsVault: invalid xvs address", + ); + }); + }); +});