diff --git a/contracts/HATClaimsManager.sol b/contracts/HATClaimsManager.sol index 072a4412..02dc7276 100644 --- a/contracts/HATClaimsManager.sol +++ b/contracts/HATClaimsManager.sol @@ -138,10 +138,9 @@ contract HATClaimsManager is IHATClaimsManager, OwnableUpgradeable, ReentrancyGu arbitratorCanChangeBeneficiary = _params.arbitratorCanChangeBeneficiary; arbitratorCanSubmitClaims = _params.arbitratorCanSubmitClaims; isTokenLockRevocable = _params.isTokenLockRevocable; + _setHATBountySplit(_params.bountyGovernanceHAT, _params.bountyHackerHATVested); // Set vault to use default registry values where applicable - bountyGovernanceHAT = NULL_UINT16; - bountyHackerHATVested = NULL_UINT16; challengePeriod = NULL_UINT32; challengeTimeOutPeriod = NULL_UINT32; } @@ -402,12 +401,7 @@ contract HATClaimsManager is IHATClaimsManager, OwnableUpgradeable, ReentrancyGu /** @notice See {IHATClaimsManager-setHATBountySplit}. */ function setHATBountySplit(uint16 _bountyGovernanceHAT, uint16 _bountyHackerHATVested) external onlyRegistryOwner { - bountyGovernanceHAT = _bountyGovernanceHAT; - bountyHackerHATVested = _bountyHackerHATVested; - - registry.validateHATSplit(getBountyGovernanceHAT(), getBountyHackerHATVested()); - - emit SetHATBountySplit(_bountyGovernanceHAT, _bountyHackerHATVested); + _setHATBountySplit(_bountyGovernanceHAT, _bountyHackerHATVested); } /** @notice See {IHATClaimsManager-setArbitrator}. */ @@ -526,6 +520,15 @@ contract HATClaimsManager is IHATClaimsManager, OwnableUpgradeable, ReentrancyGu emit SetVestingParams(_duration, _periods); } + function _setHATBountySplit(uint16 _bountyGovernanceHAT, uint16 _bountyHackerHATVested) internal { + bountyGovernanceHAT = _bountyGovernanceHAT; + bountyHackerHATVested = _bountyHackerHATVested; + + registry.validateHATSplit(getBountyGovernanceHAT(), getBountyHackerHATVested()); + + emit SetHATBountySplit(_bountyGovernanceHAT, _bountyHackerHATVested); + } + /** * @dev calculate the specific bounty payout distribution, according to the * predefined bounty split and the given bounty percentage diff --git a/contracts/interfaces/IHATClaimsManager.sol b/contracts/interfaces/IHATClaimsManager.sol index b419506c..3ea309df 100644 --- a/contracts/interfaces/IHATClaimsManager.sol +++ b/contracts/interfaces/IHATClaimsManager.sol @@ -108,6 +108,8 @@ interface IHATClaimsManager { * hacker vested, and committee. * Each entry is a number between 0 and `HUNDRED_PERCENT`. * Total splits should be equal to `HUNDRED_PERCENT`. + * @param bountyGovernanceHAT The HAT bounty for governance + * @param bountyHackerHATVested The HAT bounty vested for the hacker * @param asset The vault's native token * @param owner The address of the vault's owner * @param committee The address of the vault's committee @@ -123,6 +125,8 @@ interface IHATClaimsManager { uint32 vestingPeriods; uint16 maxBounty; BountySplit bountySplit; + uint16 bountyGovernanceHAT; + uint16 bountyHackerHATVested; address owner; address committee; address arbitrator; diff --git a/contracts/mocks/PoolsManagerMock.sol b/contracts/mocks/PoolsManagerMock.sol index d118ea34..82aa73cc 100644 --- a/contracts/mocks/PoolsManagerMock.sol +++ b/contracts/mocks/PoolsManagerMock.sol @@ -43,7 +43,9 @@ contract VaultsManagerMock { maxBounty: _maxBounty, bountySplit: _bountySplit, vestingDuration: 86400, - vestingPeriods: 10 + vestingPeriods: 10, + bountyGovernanceHAT: type(uint16).max, + bountyHackerHATVested: type(uint16).max })); _rewardController.setAllocPoint(vault, _allocPoint); } diff --git a/test/common.js b/test/common.js index c570ae05..a6d28c97 100644 --- a/test/common.js +++ b/test/common.js @@ -12,6 +12,9 @@ const utils = require("./utils.js"); const { deployHATVaults } = require("../scripts/deployments/hatvaultsregistry-deploy"); const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + +const MAX_UINT16 = 65535; + let epochRewardPerBlock = [ web3.utils.toWei("441.3"), web3.utils.toWei("441.3"), @@ -156,6 +159,8 @@ const setup = async function( isTokenLockRevocable: options.isTokenLockRevocable, maxBounty: options.maxBounty, bountySplit: options.bountySplit, + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -264,5 +269,6 @@ module.exports = { advanceToNonSafetyPeriod, submitClaim, assertFunctionRaisesException, - ZERO_ADDRESS + ZERO_ADDRESS, + MAX_UINT16 }; diff --git a/test/hattimelockcontroller.js b/test/hattimelockcontroller.js index afb3f5b0..1f37a745 100644 --- a/test/hattimelockcontroller.js +++ b/test/hattimelockcontroller.js @@ -31,6 +31,7 @@ const { advanceToNonSafetyPeriod, submitClaim, assertFunctionRaisesException, + MAX_UINT16 } = require("./common.js"); const setup = async function( @@ -113,6 +114,8 @@ const setup = async function( isTokenLockRevocable: false, maxBounty: maxBounty, bountySplit: bountySplit, + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -478,6 +481,8 @@ contract("HatTimelockController", (accounts) => { isTokenLockRevocable: false, maxBounty: maxBounty, bountySplit: bountySplit, + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } diff --git a/test/hatvaults.js b/test/hatvaults.js index 676095cc..b5a45d19 100644 --- a/test/hatvaults.js +++ b/test/hatvaults.js @@ -25,7 +25,8 @@ const { epochRewardPerBlock, setup, submitClaim, - ZERO_ADDRESS + ZERO_ADDRESS, + MAX_UINT16 } = require("./common.js"); const { assert } = require("chai"); const { web3 } = require("hardhat"); @@ -332,6 +333,8 @@ contract("HatVaults", (accounts) => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -779,6 +782,8 @@ contract("HatVaults", (accounts) => { isTokenLockRevocable: false, maxBounty: maxBounty, bountySplit: bountySplit, + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -841,6 +846,8 @@ contract("HatVaults", (accounts) => { arbitrator: accounts[2], maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, arbitratorCanChangeBounty: true, arbitratorCanChangeBeneficiary: false, arbitratorCanSubmitClaims: false, @@ -878,6 +885,8 @@ contract("HatVaults", (accounts) => { arbitrator: accounts[2], maxBounty: maxBounty, bountySplit: bountySplit, + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -888,6 +897,85 @@ contract("HatVaults", (accounts) => { } }); + it("cannot create vault with wrong hat bounty split", async () => { + await setUpGlobalVars(accounts); + + let maxBounty = 8000; + let bountySplit = [7000, 2500, 500]; + let stakingToken2 = await ERC20Mock.new("Staking", "STK"); + + try { + await hatVaultsRegistry.createVault( + { + asset: stakingToken2.address, + name: "VAULT", + symbol: "VLT", + rewardControllers: [rewardController.address], + owner: await hatVaultsRegistry.owner(), + isPaused: false, + descriptionHash: "_descriptionHash1", + }, + { + owner: await hatVaultsRegistry.owner(), + committee: accounts[3], + arbitrator: accounts[2], + maxBounty: maxBounty, + bountySplit: bountySplit, + bountyGovernanceHAT: 1000, + bountyHackerHATVested: 1001, + vestingDuration: 86400, + vestingPeriods: 10 + } + ); + assert(false, "cannot create vault with wrong hat bounty split"); + } catch (ex) { + assertVMException(ex, "TotalHatsSplitPercentageShouldBeUpToMaxHATSplit"); + } + + let tx = await hatVaultsRegistry.createVault( + { + asset: stakingToken2.address, + name: "VAULT", + symbol: "VLT", + rewardControllers: [rewardController.address], + owner: await hatVaultsRegistry.owner(), + isPaused: false, + descriptionHash: "_descriptionHash1", + }, + { + owner: await hatVaultsRegistry.owner(), + committee: accounts[3], + arbitrator: accounts[2], + maxBounty: maxBounty, + bountySplit: bountySplit, + bountyGovernanceHAT: 100, + bountyHackerHATVested: 101, + vestingDuration: 86400, + vestingPeriods: 10 + } + ); + + let newClaimsManager = await HATClaimsManager.at(tx.logs[2].args._claimsManager); + + let logs = await newClaimsManager.getPastEvents('SetHATBountySplit', { + fromBlock: tx.blockNumber, + toBlock: tx.blockNumber + }); + + assert.equal(logs[0].event, "SetHATBountySplit"); + assert.equal(logs[0].args._bountyGovernanceHAT, "100"); + assert.equal(logs[0].args._bountyHackerHATVested, "101"); + + assert.equal( + (await newClaimsManager.getBountyGovernanceHAT()).toString(), + "100" + ); + assert.equal( + (await newClaimsManager.getBountyHackerHATVested()).toString(), + "101" + ); + }); + it("setCommittee", async () => { await setUpGlobalVars(accounts); assert.equal(await claimsManager.committee(), accounts[1]); @@ -927,6 +1015,8 @@ contract("HatVaults", (accounts) => { isTokenLockRevocable: false, maxBounty: maxBounty, bountySplit: bountySplit, + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -1457,6 +1547,8 @@ contract("HatVaults", (accounts) => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 }, @@ -1570,6 +1662,8 @@ contract("HatVaults", (accounts) => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 }, @@ -5047,6 +5141,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -5179,6 +5275,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -6205,6 +6303,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [8400, 1500, 100], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -6933,6 +7033,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -6990,6 +7092,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -7054,6 +7158,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 10, vestingPeriods: 86400 } @@ -7084,6 +7190,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 121 * 24 * 3600, vestingPeriods: 10 } @@ -7114,6 +7222,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 0 } @@ -7142,6 +7252,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -7227,6 +7339,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [8400, 1500, 100], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -7400,6 +7514,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 } @@ -7508,6 +7624,8 @@ it("getVaultReward - no vault updates will return 0 ", async () => { isTokenLockRevocable: false, maxBounty: 8000, bountySplit: [7000, 2500, 500], + bountyGovernanceHAT: MAX_UINT16, + bountyHackerHATVested: MAX_UINT16, vestingDuration: 86400, vestingPeriods: 10 }