diff --git a/contracts/airdrop/MerkleDistributor.sol b/contracts/airdrop/MerkleDistributor.sol index d912a525..c0459f5a 100644 --- a/contracts/airdrop/MerkleDistributor.sol +++ b/contracts/airdrop/MerkleDistributor.sol @@ -5,7 +5,6 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import "../interfaces/airdrop/IMerkleDistributor.sol"; import "../utils/Governable.sol"; -// import "hardhat/console.sol"; contract MerkleDistributor is IMerkleDistributor, Governable { address public immutable override token; // Airdrop token @@ -75,12 +74,4 @@ contract MerkleDistributor is IMerkleDistributor, Governable { if (!IERC20(token).transfer(governance, balance)) revert FailedGovernorRecover(balance); emit GovernorRecoverAirdropTokens(balance); } -} - -// Useful resources - -// Modified from https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol -// https://github.com/Anish-Agnihotri/merkle-airdrop-starter/blob/master/contracts/src/MerkleClaimERC20.sol - -// Use Custom errors - https://blog.soliditylang.org/2021/04/21/custom-errors/ - instead of require strings -// Cheaper in deploy and runtime costs, able to convey dynamic information \ No newline at end of file +} \ No newline at end of file diff --git a/contracts/interfaces/native/GaugeStructs.sol b/contracts/interfaces/native/GaugeStructs.sol index 17fa0805..be100382 100644 --- a/contracts/interfaces/native/GaugeStructs.sol +++ b/contracts/interfaces/native/GaugeStructs.sol @@ -8,13 +8,10 @@ library GaugeStructs { } /// @dev Struct pack into single 32-byte word - /// @param _votingContractsIndex Index for _votingContracts for last incomplete updateGaugeWeights() call. - /// @param _votersIndex Index for _voters[savedIndex_votingContracts] for last incomplete updateGaugeWeights() call. - /// @param _votesIndex Index for _votes[savedIndex_votingContracts][savedIndex_voters] for last incomplete updateGaugeWeights() call. struct UpdateInfo { - uint80 _votingContractsIndex; // [0:80] - uint88 _votersIndex; // [80:168] - uint88 _votesIndex; // [168:256] + uint80 index1; // [0:80] + uint88 index2; // [80:168] + uint88 index3; // [168:256] } struct Gauge { diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol new file mode 100644 index 00000000..36c8f3cc --- /dev/null +++ b/contracts/interfaces/native/IBribeController.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.6; + +import "./GaugeStructs.sol"; +import "./IVoteListener.sol"; + +interface IBribeController is IVoteListener { + /*************************************** + STRUCTS + ***************************************/ + + struct Bribe { + address bribeToken; + uint256 bribeAmount; + } + + struct VoteForGauge { + address voter; + uint256 votePowerBPS; + } + + /*************************************** + CUSTOM ERRORS + ***************************************/ + + /// @notice Thrown when zero address is given as an argument. + /// @param contractName Name of contract for which zero address was incorrectly provided. + error ZeroAddressInput(string contractName); + + /// @notice Thrown when array arguments are mismatched in length; + error ArrayArgumentsLengthMismatch(); + + /// @notice Thrown when removeBribeToken() is attempted for non-whitelisted token. + error BribeTokenNotAdded(); + + /// @notice Thrown when provideBribe attempted for inactive gauge. + error CannotBribeForInactiveGauge(); + + /// @notice Thrown when provideBribe attempted for non-existing gauge. + error CannotBribeForNonExistentGauge(); + + /// @notice Thrown when provideBribe attempted unwhitelisted bribe token. + error CannotBribeWithNonWhitelistedToken(); + + /// @notice Thrown when attempt to claim bribes when no bribe rewards are claimable. + error NoClaimableBribes(); + + /// @notice Thrown when receiveVoteNotification() called by an address that is not the underwritingLockVoting contract. + error NotVotingContract(); + + /// @notice Thrown when voteForBribe() attempted by a non-owner or non-delegate. + error NotOwnerNorDelegate(); + + /// @notice Thrown when voteForBribe() attempted for gauge without bribe. + error NoBribesForSelectedGauge(); + + /// @notice Thrown when offerBribe() or voteForBribe() attempted before last epoch bribes are processed. + error LastEpochBribesNotProcessed(); + + /// @notice Thrown if processBribes() is called after bribes have already been successfully processed in the current epoch. + error BribesAlreadyProcessed(); + + /// @notice Thrown when processBribes is attempted before the last epoch's premiums have been successfully charged through underwritingLockVoting.chargePremiums(). + error LastEpochPremiumsNotCharged(); + + /*************************************** + EVENTS + ***************************************/ + + /// @notice Emitted when bribe is provided. + event BribeProvided(address indexed briber, uint256 indexed gaugeID, address indexed bribeToken, uint256 bribeAmount); + + /// @notice Emitted when a vote is added. + event VoteForBribeAdded(address indexed voter, uint256 indexed gaugeID, uint256 votePowerBPS); + + /// @notice Emitted when a vote is added. + event VoteForBribeChanged(address indexed voter, uint256 indexed gaugeID, uint256 newVotePowerBPS, uint256 oldVotePowerBPS); + + /// @notice Emitted when a vote is removed. + event VoteForBribeRemoved(address indexed voter, uint256 indexed gaugeID); + + /// @notice Emitted when bribe is claimed. + event BribeClaimed(address indexed briber, address indexed bribeToken, uint256 bribeAmount); + + /// @notice Emitted when registry set. + event RegistrySet(address indexed registry); + + /// @notice Emitted when bribe token added to whitelist. + event BribeTokenAdded(address indexed bribeToken); + + /// @notice Emitted when bribe token removed from whitelist. + event BribeTokenRemoved(address indexed bribeToken); + + /// @notice Emitted when token rescued. + event TokenRescued(address indexed token, address indexed receiver, uint256 balance); + + /// @notice Emitted when processBribes() does an incomplete update, and will need to be run again until completion. + event IncompleteBribesProcessing(); + + /// @notice Emitted when bribes distributed for an epoch. + event BribesProcessed(uint256 indexed epochEndTimestamp); + + /*************************************** + GLOBAL VARIABLES + ***************************************/ + + /// @notice Registry address. + function registry() external view returns (address); + + /// @notice Address of GaugeController.sol. + function gaugeController() external view returns (address); + + /// @notice Address of UnderwritingLockVoting.sol + function votingContract() external view returns (address); + + /// @notice End timestamp for last epoch that bribes were processed for all stored votes. + function lastTimeBribesProcessed() external view returns (uint256); + + /*************************************** + EXTERNAL VIEW FUNCTIONS + ***************************************/ + + /** + * @notice Get timestamp for the start of the current epoch. + * @return timestamp + */ + function getEpochStartTimestamp() external view returns (uint256 timestamp); + + /** + * @notice Get timestamp for end of the current epoch. + * @return timestamp + */ + function getEpochEndTimestamp() external view returns (uint256 timestamp); + + /** + * @notice Get unused votePowerBPS for a voter. + * @param voter_ The address of the voter to query for. + * @return unusedVotePowerBPS + */ + function getUnusedVotePowerBPS(address voter_) external view returns (uint256 unusedVotePowerBPS); + + /** + * @notice Get votePowerBPS available for voteForBribes. + * @param voter_ The address of the voter to query for. + * @return availableVotePowerBPS + */ + function getAvailableVotePowerBPS(address voter_) external view returns (uint256 availableVotePowerBPS); + /** + * @notice Get list of whitelisted bribe tokens. + * @return whitelist + */ + function getBribeTokenWhitelist() external view returns (address[] memory whitelist); + + /** + * @notice Get claimable bribes for a given voter. + * @param voter_ Voter to query for. + * @return bribes Array of claimable bribes. + */ + function getClaimableBribes(address voter_) external view returns (Bribe[] memory bribes); + + /** + * @notice Get all gaugeIDs with bribe/s offered in the present epoch. + * @return gauges Array of gaugeIDs with current bribe. + */ + function getAllGaugesWithBribe() external view returns (uint256[] memory gauges); + + /** + * @notice Get all bribes which have been offered for a given gauge. + * @param gaugeID_ GaugeID to query for. + * @return bribes Array of provided bribes. + */ + function getProvidedBribesForGauge(uint256 gaugeID_) external view returns (Bribe[] memory bribes); + + /** + * @notice Get lifetime provided bribes for a given briber. + * @param briber_ Briber to query for. + * @return bribes Array of lifetime provided bribes. + */ + function getLifetimeProvidedBribes(address briber_) external view returns (Bribe[] memory bribes); + + /** + * @notice Get all current voteForBribes for a given voter. + * @param voter_ Voter to query for. + * @return votes Array of Votes {uint256 gaugeID, uint256 votePowerBPS}. + */ + function getVotesForVoter(address voter_) external view returns (GaugeStructs.Vote[] memory votes); + + /** + * @notice Get all current voteForBribes for a given gaugeID. + * @param gaugeID_ GaugeID to query for. + * @return votes Array of VoteForGauge {address voter, uint256 votePowerBPS}. + */ + function getVotesForGauge(uint256 gaugeID_) external view returns (VoteForGauge[] memory votes); + + /** + * @notice Query whether bribing is currently open. + * @return True if bribing is open for this epoch, false otherwise. + */ + function isBribingOpen() external view returns (bool); + + /*************************************** + BRIBER FUNCTIONS + ***************************************/ + + /** + * @notice Provide bribe/s. + * @param bribeTokens_ Array of bribe token addresses. + * @param bribeAmounts_ Array of bribe token amounts. + * @param gaugeID_ Gauge ID to bribe for. + */ + function provideBribes( + address[] calldata bribeTokens_, + uint256[] calldata bribeAmounts_, + uint256 gaugeID_ + ) external; + + /*************************************** + VOTER FUNCTIONS + ***************************************/ + + /** + * @notice Vote for gaugeID with bribe. + * @param voter_ address of voter. + * @param gaugeID_ gaugeID to vote for + * @param votePowerBPS_ Vote power BPS to assign to this vote. + */ + function voteForBribe(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external; + + /** + * @notice Vote for multiple gaugeIDs with bribes. + * @param voter_ address of voter. + * @param gaugeIDs_ Array of gaugeIDs to vote for + * @param votePowerBPSs_ Array of corresponding vote power BPS values. + */ + function voteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_, uint256[] calldata votePowerBPSs_) external; + + /** + * @notice Register a single voting configuration for multiple voters. + * Can only be called by the voter or vote delegate. + * @param voters_ Array of voters. + * @param gaugeIDs_ Array of gauge IDs to vote for. + * @param votePowerBPSs_ Array of corresponding vote power BPS values. + */ + function voteForBribeForMultipleVoters(address[] calldata voters_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_) external; + + /** + * @notice Remove vote for gaugeID with bribe. + * @param voter_ address of voter. + * @param gaugeID_ The ID of the gauge to remove vote for. + */ + function removeVoteForBribe(address voter_, uint256 gaugeID_) external; + + /** + * @notice Remove multiple votes for bribes. + * @param voter_ address of voter. + * @param gaugeIDs_ Array of gaugeIDs to remove votes for + */ + function removeVotesForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external; + + /** + * @notice Remove gauge votes for multiple voters. + * @notice Votes cannot be removed while voting is frozen. + * Can only be called by the voter or vote delegate. + * @param voters_ Array of voter addresses. + * @param gaugeIDs_ Array of gauge IDs to remove votes for. + */ + function removeVotesForBribeForMultipleVoters(address[] calldata voters_, uint256[] memory gaugeIDs_) external; + + /** + * @notice Claim bribes. + */ + function claimBribes() external; + + /*************************************** + GOVERNANCE FUNCTIONS + ***************************************/ + + /** + * @notice Sets the [`Registry`](./Registry) contract address. + * @dev Requires 'uwe' and 'underwritingLocker' addresses to be set in the Registry. + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param registry_ The address of `Registry` contract. + */ + function setRegistry(address registry_) external; + + /** + * @notice Adds token to whitelist of accepted bribe tokens. + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param bribeToken_ Address of bribe token. + */ + function addBribeToken(address bribeToken_) external; + + /** + * @notice Removes tokens from whitelist of accepted bribe tokens. + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param bribeToken_ Address of bribe token. + */ + function removeBribeToken(address bribeToken_) external; + + /** + * @notice Rescues misplaced and remaining bribes (from Solidity rounding down). + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param tokens_ Array of tokens to rescue. + * @param receiver_ The receiver of the tokens. + */ + function rescueTokens(address[] memory tokens_, address receiver_) external; + + /*************************************** + UPDATER FUNCTION + ***************************************/ + + /** + * @notice Processes bribes, and makes bribes claimable by eligible voters. + * @dev Designed to be called in a while-loop with custom gas limit of 6M until `lastTimeBribesDistributed == epochStartTimestamp`. + */ + function processBribes() external; +} \ No newline at end of file diff --git a/contracts/interfaces/native/IGaugeController.sol b/contracts/interfaces/native/IGaugeController.sol index f21ac7e7..76c34592 100644 --- a/contracts/interfaces/native/IGaugeController.sol +++ b/contracts/interfaces/native/IGaugeController.sol @@ -45,6 +45,9 @@ interface IGaugeController { /// @notice Thrown if vote() is attempted for gauge ID 0. error CannotVoteForGaugeID0(); + /// @notice Thrown if attempt to setEpochLength to 0. + error CannotSetEpochLengthTo0(); + /// @notice Thrown if updateGaugeWeights() is called after gauge weights have been successfully updated in the current epoch. error GaugeWeightsAlreadyUpdated(); @@ -69,9 +72,6 @@ interface IGaugeController { /// @notice Thrown when removeTokenholder() is attempted for an address not in the tokenholder set. error TokenholderNotPresent(); - /// @notice Thrown when updateGaugeWeights() is called by neither governance nor updater, or governance is locked. - error NotUpdaterNorGovernance(); - /*************************************** EVENTS ***************************************/ @@ -103,9 +103,6 @@ interface IGaugeController { /// @notice Emitted when the epoch length is set. event EpochLengthSet(uint256 indexed weeks_); - /// @notice Emitted when the Updater is set. - event UpdaterSet(address indexed updater); - /// @notice Emitted when address added to tokenholder set. event TokenholderAdded(address indexed tokenholder); @@ -125,9 +122,6 @@ interface IGaugeController { /// @notice Underwriting equity token. function token() external view returns (address); - /// @notice Updater address. - function updater() external view returns (address); - /// @notice Insurance leverage factor. function leverageFactor() external view returns (uint256); @@ -323,13 +317,6 @@ interface IGaugeController { */ function setToken(address token_) external; - /** - * @notice Set updater address. - * Can only be called by the current [**governor**](/docs/protocol/governance). - * @param updater_ The address of the new updater. - */ - function setUpdater(address updater_) external; - /** * @notice Set epoch length (as an integer multiple of 1 week). * Can only be called by the current [**governor**](/docs/protocol/governance). @@ -363,7 +350,6 @@ interface IGaugeController { /** * @notice Updates gauge weights by processing votes for the last epoch. * @dev Designed to be called in a while-loop with custom gas limit of 6M until `lastTimePremiumsCharged == epochStartTimestamp`. - * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. */ function updateGaugeWeights() external; } diff --git a/contracts/interfaces/native/IUnderwritingLockVoting.sol b/contracts/interfaces/native/IUnderwritingLockVoting.sol index f3b04f61..4ac3492d 100644 --- a/contracts/interfaces/native/IUnderwritingLockVoting.sol +++ b/contracts/interfaces/native/IUnderwritingLockVoting.sol @@ -71,9 +71,6 @@ interface IUnderwritingLockVoting is IGaugeVoter { /// @param epochTime Timestamp of endtime for epoch already processed. error LastEpochPremiumsAlreadyProcessed(uint256 epochTime); - /// @notice Thrown when chargePremiums() is called by neither governance nor updater, or governance is locked. - error NotUpdaterNorGovernance(); - /*************************************** EVENTS ***************************************/ @@ -84,9 +81,6 @@ interface IUnderwritingLockVoting is IGaugeVoter { /// @notice Emitted when the Registry is set. event RegistrySet(address indexed registry); - /// @notice Emitted when the Updater is set. - event UpdaterSet(address indexed updater); - /// @notice Emitted when the Bribe Controller is set. event BribeControllerSet(address indexed bribeController); @@ -124,9 +118,6 @@ interface IUnderwritingLockVoting is IGaugeVoter { /// @notice Registry address function registry() external view returns (address); - /// @notice Updater address. - function updater() external view returns (address); - /** * @notice End timestamp for last epoch that premiums were charged for all stored votes. * @return timestamp_ @@ -273,13 +264,6 @@ interface IUnderwritingLockVoting is IGaugeVoter { */ function setRegistry(address registry_) external; - /** - * @notice Set updater address. - * Can only be called by the current [**governor**](/docs/protocol/governance). - * @param updater_ The address of the new updater. - */ - function setUpdater(address updater_) external; - /** * @notice Sets bribeController as per `bribeController` address stored in Registry. * @dev We do not set this in constructor, because we expect BribeController.sol to be deployed after this contract. @@ -291,7 +275,6 @@ interface IUnderwritingLockVoting is IGaugeVoter { * @notice Charge premiums for votes. * @dev Designed to be called in a while-loop with the condition being `lastTimePremiumsCharged != epochStartTimestamp` and using the maximum custom gas limit. * @dev Requires GaugeController.updateGaugeWeights() to be run to completion for the last epoch. - * Can only be called by the current [**governor**](/docs/protocol/governance). */ function chargePremiums() external; } diff --git a/contracts/interfaces/native/IUnderwritingLocker.sol b/contracts/interfaces/native/IUnderwritingLocker.sol index 236a31fc..994724e4 100644 --- a/contracts/interfaces/native/IUnderwritingLocker.sol +++ b/contracts/interfaces/native/IUnderwritingLocker.sol @@ -343,10 +343,11 @@ interface IUnderwritingLocker is IERC721Enhanced { /** * @notice Perform accounting for voting premiums to be charged by UnderwritingLockVoting.chargePremiums(). * @dev Can only be called by votingContract set in the registry. - * @param lockID_ The ID of the lock to charge premium. - * @param premium_ Amount of tokens to charge as premium. + * @dev Not meant to be called directly, but via UnderwritingLockVoting.chargePremiums(). + * @param voter_ Voter to charge premium for. + * @param premium_ The amount of token charged as premium. */ - function chargePremium(uint256 lockID_, uint256 premium_) external; + function chargePremium(address voter_, uint256 premium_) external; /*************************************** GOVERNANCE FUNCTIONS diff --git a/contracts/interfaces/native/IVoteListener.sol b/contracts/interfaces/native/IVoteListener.sol new file mode 100644 index 00000000..b9816d40 --- /dev/null +++ b/contracts/interfaces/native/IVoteListener.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.6; + +/** + * @title IVoteListener + * @author solace.fi + * @notice A standard interface for notifying a contract about votes made via UnderwritingLockVoting.sol. + */ +interface IVoteListener { + /** + * @notice Called when vote is made (hook called at the end of vote function logic). + * @param voter_ The voter address. + * @param gaugeID_ The gaugeID to vote for. + * @param votePowerBPS_ votePowerBPS value. Can be from 0-10000. + */ + function receiveVoteNotification(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external; +} \ No newline at end of file diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol new file mode 100644 index 00000000..e003157e --- /dev/null +++ b/contracts/native/BribeController.sol @@ -0,0 +1,783 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "./../interfaces/native/IBribeController.sol"; +import "./../interfaces/native/IUnderwritingLockVoting.sol"; +import "./../interfaces/native/IGaugeController.sol"; +import "./../interfaces/utils/IRegistry.sol"; +import "./../utils/EnumerableMapS.sol"; +import "./../utils/Governable.sol"; + +contract BribeController is + IBribeController, + ReentrancyGuard, + Governable + { + using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableMapS for EnumerableMapS.AddressToUintMap; + using EnumerableMapS for EnumerableMapS.UintToUintMap; + + /*************************************** + GLOBAL PUBLIC VARIABLES + ***************************************/ + + /// @notice Registry address + address public override registry; + + /// @notice GaugeController.sol address + address public override gaugeController; + + /// @notice UnderwriterLockVoting.sol address + address public override votingContract; + + /// @notice End timestamp for last epoch that bribes were processed for all stored votes. + uint256 public override lastTimeBribesProcessed; + + /*************************************** + GLOBAL INTERNAL VARIABLES + ***************************************/ + + /// @notice gaugeID => bribeToken => bribeAmount. + mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _providedBribes; + + /// @notice briber => bribeToken => lifetimeOfferedBribeAmount. + mapping(address => EnumerableMapS.AddressToUintMap) internal _lifetimeProvidedBribes; + + /// @notice voter => bribeToken => claimableBribeAmount. + mapping(address => EnumerableMapS.AddressToUintMap) internal _claimableBribes; + + /// @notice gaugeID => total vote power + EnumerableMapS.UintToUintMap internal _gaugeToTotalVotePower; + + /// @notice Collection of gauges with current bribes. + EnumerableSet.UintSet internal _gaugesWithBribes; + + /// @notice gaugeID => voter => votePowerBPS. + mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _votes; + + /// @notice Address => gaugeID => votePowerBPS + /// @dev _votes will be cleaned up in processBribes(), _votesMirror will not be. + /// @dev This will enable _voteForBribe to remove previous epoch's voteForBribes. + mapping(address => EnumerableMapS.UintToUintMap) internal _votesMirror; + + /// @notice whitelist of tokens that can be accepted as bribes + EnumerableSet.AddressSet internal _bribeTokenWhitelist; + + /// @notice State of last [`distributeBribes()`](#distributeBribes) call. + GaugeStructs.UpdateInfo internal _updateInfo; + + /*************************************** + CONSTRUCTOR + ***************************************/ + + /** + * @notice Constructs the UnderwritingLocker contract. + * @param governance_ The address of the [governor](/docs/protocol/governance). + * @param registry_ The [`Registry`](./Registry) contract address. + */ + constructor(address governance_, address registry_) + Governable(governance_) + { + _setRegistry(registry_); + _clearUpdateInfo(); + lastTimeBribesProcessed = _getEpochStartTimestamp(); + } + + /*************************************** + INTERNAL VIEW FUNCTIONS + ***************************************/ + + /** + * @notice Get timestamp for the start of the current epoch. + * @return timestamp + */ + function _getEpochStartTimestamp() internal view returns (uint256 timestamp) { + return IGaugeController(gaugeController).getEpochStartTimestamp(); + } + + /** + * @notice Get timestamp for end of the current epoch. + * @return timestamp + */ + function _getEpochEndTimestamp() internal view returns (uint256 timestamp) { + return IGaugeController(gaugeController).getEpochEndTimestamp(); + } + + /** + * @notice Get unused votePowerBPS for a voter. + * @param voter_ The address of the voter to query for. + * @return unusedVotePowerBPS + */ + function _getUnusedVotePowerBPS(address voter_) internal view returns (uint256 unusedVotePowerBPS) { + return (10000 - IUnderwritingLockVoting(votingContract).usedVotePowerBPSOf(voter_)); + } + + /** + * @notice Get votePowerBPS available for voteForBribes. + * @param voter_ The address of the voter to query for. + * @return availableVotePowerBPS + */ + function _getAvailableVotePowerBPS(address voter_) internal view returns (uint256 availableVotePowerBPS) { + (,uint256 epochEndTimestamp) = _votesMirror[voter_].tryGet(0); + if (epochEndTimestamp == _getEpochEndTimestamp()) { + return _getUnusedVotePowerBPS(voter_); + } else { + uint256 length = _votesMirror[voter_].length(); + uint256 staleVotePowerBPS = 0; + for (uint256 i = 0; i < length; i++) { + (uint256 gaugeID, uint256 votePowerBPS) = _votesMirror[voter_].at(i); + if (gaugeID != 0) {staleVotePowerBPS += votePowerBPS;} + } + return (10000 - IUnderwritingLockVoting(votingContract).usedVotePowerBPSOf(voter_) + staleVotePowerBPS); + } + } + + /*************************************** + EXTERNAL VIEW FUNCTIONS + ***************************************/ + + /** + * @notice Get timestamp for the start of the current epoch. + * @return timestamp + */ + function getEpochStartTimestamp() external view override returns (uint256 timestamp) { + return _getEpochStartTimestamp(); + } + + /** + * @notice Get timestamp for end of the current epoch. + * @return timestamp + */ + function getEpochEndTimestamp() external view override returns (uint256 timestamp) { + return _getEpochEndTimestamp(); + } + + /** + * @notice Get unused votePowerBPS for a voter. + * @param voter_ The address of the voter to query for. + * @return unusedVotePowerBPS + */ + function getUnusedVotePowerBPS(address voter_) external view override returns (uint256 unusedVotePowerBPS) { + return _getUnusedVotePowerBPS(voter_); + } + + /** + * @notice Get votePowerBPS available for voteForBribes. + * @param voter_ The address of the voter to query for. + * @return availableVotePowerBPS + */ + function getAvailableVotePowerBPS(address voter_) external view override returns (uint256 availableVotePowerBPS) { + return _getAvailableVotePowerBPS(voter_); + } + + /** + * @notice Get list of whitelisted bribe tokens. + * @return whitelist + */ + function getBribeTokenWhitelist() external view override returns (address[] memory whitelist) { + uint256 length = _bribeTokenWhitelist.length(); + whitelist = new address[](length); + for (uint256 i = 0; i < length; i++) { + whitelist[i] = _bribeTokenWhitelist.at(i); + } + } + + /** + * @notice Get claimable bribes for a given voter. + * @param voter_ Voter to query for. + * @return bribes Array of claimable bribes. + */ + function getClaimableBribes(address voter_) external view override returns (Bribe[] memory bribes) { + uint256 length = _claimableBribes[voter_].length(); + uint256 bribesLength = 0; + for (uint256 i = 0; i < length; i++) { + (, uint256 bribeAmount) = _claimableBribes[voter_].at(i); + if (bribeAmount != type(uint256).max) {bribesLength += 1;} + } + bribes = new Bribe[](bribesLength); + for (uint256 i = 0; i < length; i++) { + (address bribeToken, uint256 bribeAmount) = _claimableBribes[voter_].at(i); + if (bribeAmount == type(uint256).max) {continue;} + bribes[i] = Bribe(bribeToken, bribeAmount); + } + return bribes; + } + + /** + * @notice Get all gaugeIDs with bribe/s offered in the present epoch. + * @return gauges Array of gaugeIDs with current bribe. + */ + function getAllGaugesWithBribe() external view override returns (uint256[] memory gauges) { + uint256 length = _gaugesWithBribes.length(); + gauges = new uint256[](length); + for (uint256 i = 0; i < length; i++) { + gauges[i] = _gaugesWithBribes.at(i); + } + } + + /** + * @notice Get all bribes which have been offered for a given gauge. + * @param gaugeID_ GaugeID to query for. + * @return bribes Array of provided bribes. + */ + function getProvidedBribesForGauge(uint256 gaugeID_) external view override returns (Bribe[] memory bribes) { + uint256 length = _providedBribes[gaugeID_].length(); + bribes = new Bribe[](length); + for (uint256 i = 0; i < length; i++) { + (address bribeToken, uint256 bribeAmount) = _providedBribes[gaugeID_].at(i); + bribes[i] = Bribe(bribeToken, bribeAmount); + } + return bribes; + } + + /** + * @notice Get lifetime provided bribes for a given briber. + * @param briber_ Briber to query for. + * @return bribes Array of lifetime provided bribes. + */ + function getLifetimeProvidedBribes(address briber_) external view override returns (Bribe[] memory bribes) { + uint256 length = _lifetimeProvidedBribes[briber_].length(); + bribes = new Bribe[](length); + for (uint256 i = 0; i < length; i++) { + (address bribeToken, uint256 bribeAmount) = _lifetimeProvidedBribes[briber_].at(i); + bribes[i] = Bribe(bribeToken, bribeAmount); + } + return bribes; + } + + /** + * @notice Get all current voteForBribes for a given voter. + * @dev Inefficient implementation to avoid + * @param voter_ Voter to query for. + * @return votes Array of Votes {uint256 gaugeID, uint256 votePowerBPS}. + */ + function getVotesForVoter(address voter_) external view override returns (GaugeStructs.Vote[] memory votes) { + // Get num of votes + uint256 numVotes = 0; + + // Iterate by gauge + for (uint256 i = 0; i < _gaugeToTotalVotePower.length(); i++) { + (uint256 gaugeID,) = _gaugeToTotalVotePower.at(i); + // Iterate by vote + for (uint256 j = 0; j < _votes[gaugeID].length(); j++) { + (address voter,) = _votes[gaugeID].at(j); + if (voter == voter_) numVotes += 1; + } + } + + // Define return array + votes = new GaugeStructs.Vote[](numVotes); + uint256 votes_index = 0; + + // Iterate by gauge + for (uint256 i = 0; i < _gaugeToTotalVotePower.length(); i++) { + (uint256 gaugeID,) = _gaugeToTotalVotePower.at(i); + // Iterate by vote + for (uint256 j = 0; j < _votes[gaugeID].length(); j++) { + (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(j); + if (voter == voter_) { + votes[votes_index] = GaugeStructs.Vote(gaugeID, votePowerBPS); + votes_index += 1; + if (votes_index == numVotes) return votes; + } + } + } + } + + /** + * @notice Get all current voteForBribes for a given gaugeID. + * @param gaugeID_ GaugeID to query for. + * @return votes Array of VoteForGauge {address voter, uint256 votePowerBPS}. + */ + function getVotesForGauge(uint256 gaugeID_) external view override returns (VoteForGauge[] memory votes) { + uint256 length = _votes[gaugeID_].length(); + votes = new VoteForGauge[](length); + for (uint256 i = 0; i < length; i++) { + (address voter, uint256 votePowerBPS) = _votes[gaugeID_].at(i); + votes[i] = VoteForGauge(voter, votePowerBPS); + } + } + + /** + * @notice Query whether bribing is currently open. + * @return True if bribing is open for this epoch, false otherwise. + */ + function isBribingOpen() external view override returns (bool) { + uint256 epochStartTime = _getEpochStartTimestamp(); + return (epochStartTime == IGaugeController(gaugeController).lastTimeGaugeWeightsUpdated() + && epochStartTime == IUnderwritingLockVoting(votingContract).lastTimePremiumsCharged() + && epochStartTime == lastTimeBribesProcessed); + } + + /*************************************** + INTERNAL MUTATOR FUNCTIONS + ***************************************/ + + /** + * @notice Sets registry and related contract addresses. + * @dev Requires 'uwe' and 'underwritingLocker' addresses to be set in the Registry. + * @param _registry The registry address to set. + */ + function _setRegistry(address _registry) internal { + if(_registry == address(0x0)) revert ZeroAddressInput("registry"); + registry = _registry; + IRegistry reg = IRegistry(_registry); + // set gaugeController + (, address gaugeControllerAddr) = reg.tryGet("gaugeController"); + if(gaugeControllerAddr == address(0x0)) revert ZeroAddressInput("gaugeController"); + gaugeController = gaugeControllerAddr; + // set votingContract + (, address underwritingLockVoting) = reg.tryGet("underwritingLockVoting"); + if(underwritingLockVoting == address(0x0)) revert ZeroAddressInput("underwritingLockVoting"); + votingContract = underwritingLockVoting; + emit RegistrySet(_registry); + } + + /** + * @notice Remove vote for gaugeID with bribe. + * @param voter_ address of voter. + * @param gaugeID_ The ID of the gauge to remove vote for. + */ + function _removeVoteForBribeInternal(address voter_, uint256 gaugeID_) internal { + uint256[] memory gaugeIDs_ = new uint256[](1); + uint256[] memory votePowerBPSs_ = new uint256[](1); + gaugeIDs_[0] = gaugeID_; + votePowerBPSs_[0] = 0; + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, true); + } + + /** + * @notice Add, change or remove vote for bribe. + * Can only be called by the voter or their delegate. + * @dev Remove NonReentrant modifier from internal function => 5K gas cost saving + * @param voter_ The voter address. + * @param gaugeIDs_ The array of gaugeIDs to vote for. + * @param votePowerBPSs_ The corresponding array of votePowerBPS values. Can be from 0-10000. + * @param isInternalCall_ True if called through processBribes, false otherwise. + */ + function _voteForBribe(address voter_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_, bool isInternalCall_) internal { + // CHECKS + if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); + + // ENABLE INTERNAL CALL TO SKIP CHECKS (which otherwise block processBribes) + if (!isInternalCall_) { + if (_getEpochStartTimestamp() > lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); + if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); + + // If stale _votesMirror, empty _votesMirror and do external calls to remove vote + if (_votesMirror[voter_].length() != 0) { + (,uint256 epochEndTimestamp) = _votesMirror[voter_].tryGet(0); + if (epochEndTimestamp < _getEpochEndTimestamp()) { + while(_votesMirror[voter_].length() > 0) { + (uint256 gaugeID, uint256 votePowerBPS) = _votesMirror[voter_].at(0); + _votesMirror[voter_].remove(gaugeID); + // 'Try' here for edge case where premiums charged => voter removes vote via UnderwritingLockVoting => bribe processed => Vote exists in BribeController.sol, but not in GaugeController.sol => Following call can fail. + if (gaugeID != 0) {try IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, 0) {} catch {}} + } + } + } + } + + for(uint256 i = 0; i < gaugeIDs_.length; i++) { + uint256 gaugeID = gaugeIDs_[i]; + uint256 votePowerBPS = votePowerBPSs_[i]; + if (_providedBribes[gaugeID].length() == 0) revert NoBribesForSelectedGauge(); + // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS + (, uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); + if(!isInternalCall_) {IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS);} + // If remove vote + if (votePowerBPS == 0) { + if(!isInternalCall_) _votesMirror[voter_].remove(gaugeID); + _votes[gaugeID].remove(voter_); // This step costs 15-25K gas, wonder if more efficient implementation. + if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); // This step can cost up to 20K gas + if(!isInternalCall_) {emit VoteForBribeRemoved(voter_, gaugeID);} // 5K gas cost to emit, avoid in unbounded loop + } else { + _gaugeToTotalVotePower.set(gaugeID, 1); // Do not set to 0 to avoid SSTORE penalty for 0 slot in processBribes(). + _votes[gaugeID].set(voter_, votePowerBPS); + if ( _votesMirror[voter_].length() == 0) _votesMirror[voter_].set(0, _getEpochEndTimestamp()); + _votesMirror[voter_].set(gaugeID, votePowerBPS); + // Change vote + if(oldVotePowerBPS > 0) { + emit VoteForBribeChanged(voter_, gaugeID, votePowerBPS, oldVotePowerBPS); + // Add vote + } else { + _preInitializeClaimableBribes(gaugeID, voter_); + emit VoteForBribeAdded(voter_, gaugeID, votePowerBPS); + } + } + } + } + + /** + * @notice Pre-initialize claimableBribes mapping to save SSTORE cost for zero-slot in processBribes() + * @dev ~5% gas saving in processBribes(). + * @param gaugeID_ GaugeID. + * @param voter_ Voter. + */ + function _preInitializeClaimableBribes(uint256 gaugeID_, address voter_) internal { + uint256 numBribeTokens = _providedBribes[gaugeID_].length(); + for (uint256 i = 0; i < numBribeTokens; i++) { + (address bribeToken, ) = _providedBribes[gaugeID_].at(i); + _claimableBribes[voter_].set(bribeToken, type(uint256).max); + } + } + + /*************************************** + BRIBER FUNCTIONS + ***************************************/ + + /** + * @notice Provide bribe/s. + * @param bribeTokens_ Array of bribe token addresses. + * @param bribeAmounts_ Array of bribe token amounts. + * @param gaugeID_ Gauge ID to bribe for. + */ + function provideBribes( + address[] calldata bribeTokens_, + uint256[] calldata bribeAmounts_, + uint256 gaugeID_ + ) external override nonReentrant { + // CHECKS + if (_getEpochStartTimestamp() > lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); + if (bribeTokens_.length != bribeAmounts_.length) revert ArrayArgumentsLengthMismatch(); + try IGaugeController(gaugeController).isGaugeActive(gaugeID_) returns (bool gaugeActive) { + if (!gaugeActive) revert CannotBribeForInactiveGauge(); + } catch { + revert CannotBribeForNonExistentGauge(); + } + + uint256 length = bribeTokens_.length; + for (uint256 i = 0; i < length; i++) { + if (!_bribeTokenWhitelist.contains(bribeTokens_[i])) revert CannotBribeWithNonWhitelistedToken(); + } + + // INTERNAL STATE MUTATIONS + _gaugesWithBribes.add(gaugeID_); + + for (uint256 i = 0; i < length; i++) { + (,uint256 previousBribeSum) = _providedBribes[gaugeID_].tryGet(bribeTokens_[i]); + _providedBribes[gaugeID_].set(bribeTokens_[i], previousBribeSum + bribeAmounts_[i]); + (,uint256 lifetimeBribeTotal) = _lifetimeProvidedBribes[msg.sender].tryGet(bribeTokens_[i]); + _lifetimeProvidedBribes[msg.sender].set(bribeTokens_[i], lifetimeBribeTotal + bribeAmounts_[i]); + } + + // EXTERNAL CALLS + EVENTS + for (uint256 i = 0; i < length; i++) { + SafeERC20.safeTransferFrom( + IERC20(bribeTokens_[i]), + msg.sender, + address(this), + bribeAmounts_[i] + ); + + emit BribeProvided(msg.sender, gaugeID_, bribeTokens_[i], bribeAmounts_[i]); + } + } + + /*************************************** + VOTER FUNCTIONS + ***************************************/ + + /** + * @notice Vote for gaugeID with bribe. + * @param voter_ address of voter. + * @param gaugeID_ gaugeID to vote for + * @param votePowerBPS_ Vote power BPS to assign to this vote. + */ + function voteForBribe(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external override nonReentrant { + uint256[] memory gaugeIDs_ = new uint256[](1); + uint256[] memory votePowerBPSs_ = new uint256[](1); + gaugeIDs_[0] = gaugeID_; + votePowerBPSs_[0] = votePowerBPS_; + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); + } + + /** + * @notice Vote for multiple gaugeIDs with bribes. + * @param voter_ address of voter. + * @param gaugeIDs_ Array of gaugeIDs to vote for + * @param votePowerBPSs_ Array of corresponding vote power BPS values. + */ + function voteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_, uint256[] calldata votePowerBPSs_) external override nonReentrant { + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); + } + + /** + * @notice Register a single voting configuration for multiple voters. + * Can only be called by the voter or vote delegate. + * @param voters_ Array of voters. + * @param gaugeIDs_ Array of gauge IDs to vote for. + * @param votePowerBPSs_ Array of corresponding vote power BPS values. + */ + function voteForBribeForMultipleVoters(address[] calldata voters_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_) external override nonReentrant { + uint256 length = voters_.length; + for (uint256 i = 0; i < length; i++) { + _voteForBribe(voters_[i], gaugeIDs_, votePowerBPSs_, false); + } + } + + /** + * @notice Remove vote for gaugeID with bribe. + * @param voter_ address of voter. + * @param gaugeID_ The ID of the gauge to remove vote for. + */ + function removeVoteForBribe(address voter_, uint256 gaugeID_) external override nonReentrant { + uint256[] memory gaugeIDs_ = new uint256[](1); + uint256[] memory votePowerBPSs_ = new uint256[](1); + gaugeIDs_[0] = gaugeID_; + votePowerBPSs_[0] = 0; + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); + } + + /** + * @notice Remove multiple votes for bribes. + * @param voter_ address of voter. + * @param gaugeIDs_ Array of gaugeIDs to remove votes for + */ + function removeVotesForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external override nonReentrant { + uint256[] memory votePowerBPSs_ = new uint256[](gaugeIDs_.length); + for(uint256 i = 0; i < gaugeIDs_.length; i++) {votePowerBPSs_[i] = 0;} + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); + } + + /** + * @notice Remove gauge votes for multiple voters. + * @notice Votes cannot be removed while voting is frozen. + * Can only be called by the voter or vote delegate. + * @param voters_ Array of voter addresses. + * @param gaugeIDs_ Array of gauge IDs to remove votes for. + */ + function removeVotesForBribeForMultipleVoters(address[] calldata voters_, uint256[] memory gaugeIDs_) external override nonReentrant { + uint256 length = voters_.length; + uint256[] memory votePowerBPSs_ = new uint256[](gaugeIDs_.length); + for(uint256 i = 0; i < gaugeIDs_.length; i++) {votePowerBPSs_[i] = 0;} + for (uint256 i = 0; i < length; i++) { + _voteForBribe(voters_[i], gaugeIDs_, votePowerBPSs_, false); + } + } + + // Should delegate also be able to claim bribes for user? + /** + * @notice Claim bribes. + */ + function claimBribes() external override nonReentrant { + uint256 length = _claimableBribes[msg.sender].length(); + if (length == 0) revert NoClaimableBribes(); + while (_claimableBribes[msg.sender].length() != 0) { + (address bribeToken, uint256 bribeAmount) = _claimableBribes[msg.sender].at(0); + _claimableBribes[msg.sender].remove(bribeToken); + if (bribeAmount == type(uint256).max) {continue;} + SafeERC20.safeTransfer(IERC20(bribeToken), msg.sender, bribeAmount); + emit BribeClaimed(msg.sender, bribeToken, bribeAmount); + } + } + + /*************************************** + RECEIVE NOTIFICATION HOOK + ***************************************/ + + /** + * @notice Hook that enables this contract to be informed of votes made via UnderwritingLockVoting.sol. + * @dev Required to prevent edge case where voteForBribe made via BribeController, is then modified via this contract, and the vote modifications are not reflected in BribeController _votes and _votesMirror storage data structures. + * @dev The above will result in an edge case where a voter can claim more bribes than they are actually eligible for (votePowerBPS in BribeController _votes data structure that is processed in processBribes(), will be higher than actual votePowerBPS used.) + * @param voter_ The voter address. + * @param gaugeID_ The gaugeID to vote for. + * @param votePowerBPS_ votePowerBPS value. Can be from 0-10000. + */ + function receiveVoteNotification(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external override { + if (msg.sender != votingContract) revert NotVotingContract(); + + // Check if vote exists in _votes. + if(_votes[gaugeID_].contains(voter_)) _votes[gaugeID_].set(voter_, votePowerBPS_); + + // Check if vote exists in _votesMirror. + if(_votesMirror[voter_].contains(gaugeID_)) _votesMirror[voter_].set(gaugeID_, votePowerBPS_); + } + + /*************************************** + GOVERNANCE FUNCTIONS + ***************************************/ + + /** + * @notice Sets the [`Registry`](./Registry) contract address. + * @dev Requires 'uwe' and 'underwritingLocker' addresses to be set in the Registry. + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param registry_ The address of `Registry` contract. + */ + function setRegistry(address registry_) external override onlyGovernance { + _setRegistry(registry_); + } + + /** + * @notice Adds token to whitelist of accepted bribe tokens. + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param bribeToken_ Address of bribe token. + */ + function addBribeToken(address bribeToken_) external override onlyGovernance { + _bribeTokenWhitelist.add(bribeToken_); + emit BribeTokenAdded(bribeToken_); + } + + /** + * @notice Removes tokens from whitelist of accepted bribe tokens. + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param bribeToken_ Address of bribe token. + */ + function removeBribeToken(address bribeToken_) external override onlyGovernance { + bool success = _bribeTokenWhitelist.remove(bribeToken_); + if (!success) revert BribeTokenNotAdded(); + emit BribeTokenRemoved(bribeToken_); + } + + /** + * @notice Rescues misplaced and remaining bribes (from Solidity rounding down, and bribing rounds with no voters). + * Can only be called by the current [**governor**](/docs/protocol/governance). + * @param tokens_ Array of tokens to rescue. + * @param receiver_ The receiver of the tokens. + */ + function rescueTokens(address[] memory tokens_, address receiver_) external override onlyGovernance { + uint256 length = tokens_.length; + for(uint256 i = 0; i < length; i++) { + IERC20 token = IERC20(tokens_[i]); + uint256 balance = token.balanceOf(address(this)); + SafeERC20.safeTransfer(token, receiver_, balance); + emit TokenRescued(address(token), receiver_, balance); + } + } + + /******************************************** + UPDATER FUNCTION TO BE RUN AFTER EACH EPOCH + ********************************************/ + + /** + * @notice Processes bribes, and makes bribes claimable by eligible voters. + * @dev Designed to be called in a while-loop with custom gas limit of 6M until `lastTimeBribesProcessed == epochStartTimestamp`. + */ + function processBribes() external override { + // CHECKS + uint256 currentEpochStartTime = _getEpochStartTimestamp(); + if (lastTimeBribesProcessed >= currentEpochStartTime) revert BribesAlreadyProcessed(); + // Require gauge weights to have been updated for this epoch => ensure state we are querying from is < 1 WEEK old. + if (IUnderwritingLockVoting(votingContract).lastTimePremiumsCharged() < currentEpochStartTime) revert LastEpochPremiumsNotCharged(); + + // If no votes to process + // => early cleanup of _gaugesWithBribes and _providedBribes mappings + // => bribes stay custodied on bribing contract + // => early return + if (_gaugeToTotalVotePower.length() == 0) {return _concludeProcessBribes(currentEpochStartTime);} + + // LOOP 1 - GET TOTAL VOTE POWER CHASING BRIBES FOR EACH GAUGE + // Block-scope to avoid stack too deep error + { + uint256 numGauges = _gaugeToTotalVotePower.length(); + // Iterate by gauge + for (uint256 i = _updateInfo.index1 == type(uint80).max ? 0 : _updateInfo.index1; i < numGauges; i++) { + // Iterate by vote + (uint256 gaugeID,) = _gaugeToTotalVotePower.at(i); + uint256 numVotes = _votes[gaugeID].length(); + + // 7-13K gas per loop + for (uint256 j = _updateInfo.index2 == type(uint88).max || i != _updateInfo.index1 ? 0 : _updateInfo.index2; j < numVotes; j++) { + // Checkpoint 1 + if (gasleft() < 20000) {return _saveUpdateState(i, j, type(uint88).max);} + uint256 runningVotePowerSum = _gaugeToTotalVotePower.get(gaugeID); + (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(j); + uint256 votePower = IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter); + // State mutation 1 + _gaugeToTotalVotePower.set(gaugeID, runningVotePowerSum + (votePower * votePowerBPS) / 10000); + } + } + } + + // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS + // _gaugeToTotalVotePower, _votes and _providedBribes enumerable collections should be empty at the end. + { + // Iterate by gauge + while (_gaugeToTotalVotePower.length() > 0) { + (uint256 gaugeID, uint256 votePowerSum) = _gaugeToTotalVotePower.at(0); + + // Iterate by vote - 30-60K gas per loop + while(_votes[gaugeID].length() > 0) { + (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(0); + // `votePowerSum - 1` to nullify initiating _gaugeToTotalVotePower values at 1 rather than 0. + uint256 bribeProportion = 1e18 * (IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) * votePowerBPS / 10000) / (votePowerSum - 1); + + // Iterate by bribeToken + uint256 numBribeTokens = _providedBribes[gaugeID].length(); + for (uint256 k = _updateInfo.index3 == type(uint88).max ? 0 : _updateInfo.index3; k < numBribeTokens; k++) { + // Checkpoint 2 + if (gasleft() < 120000) { + return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k); + } + (address bribeToken, uint256 totalBribeAmount) = _providedBribes[gaugeID].at(k); + (, uint256 runningClaimableAmount) = _claimableBribes[voter].tryGet(bribeToken); + if (runningClaimableAmount == type(uint256).max) {runningClaimableAmount = 0;} + uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; + // State mutation 2 + _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); + } + if (_updateInfo.index3 != 0) {_updateInfo.index3 = type(uint88).max;} + // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. + if (gasleft() < 110000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, type(uint88).max - 1);} + _removeVoteForBribeInternal(voter, gaugeID); // 20-30K gas per call + } + } + } + + // Cleanup _gaugesWithBribes and _providedBribes enumerable collections. + return _concludeProcessBribes(currentEpochStartTime); + } + + /*************************************** + processBribes() HELPER FUNCTIONS + ***************************************/ + + /** + * @notice Save state of processing bribes to _updateInfo. + * @param loop1GaugeIndex_ Current index of _gaugeToTotalVotePower in loop 1. + * @param loop1VoteIndex_ Current index of _votes[gaugeID] in loop 1. + * @param loop2BribeTokenIndex_ Current index of _providedBribes[gaugeID] in loop 2. + */ + function _saveUpdateState(uint256 loop1GaugeIndex_, uint256 loop1VoteIndex_, uint256 loop2BribeTokenIndex_) internal { + assembly { + let updateInfo + updateInfo := or(updateInfo, shr(176, shl(176, loop1GaugeIndex_))) // [0:80] => votingContractsIndex_ + updateInfo := or(updateInfo, shr(88, shl(168, loop1VoteIndex_))) // [80:168] => votersIndex_ + updateInfo := or(updateInfo, shl(168, loop2BribeTokenIndex_)) // [168:256] => votesIndex_ + sstore(_updateInfo.slot, updateInfo) + } + emit IncompleteBribesProcessing(); + } + + /// @notice Reset _updateInfo to starting state. + /// @dev Avoid zero-value of storage slot. + function _clearUpdateInfo() internal { + uint256 bitmap = type(uint256).max; + assembly { + sstore(_updateInfo.slot, bitmap) + } + } + + /// @notice Finishing code block of processBribes. + /// @param currentEpochStartTime_ Current epoch start timestamp. + function _concludeProcessBribes(uint256 currentEpochStartTime_) internal { + while(_gaugesWithBribes.length() > 0) { + uint256 gaugeID = _gaugesWithBribes.at(0); + while(_providedBribes[gaugeID].length() > 0) { + if (gasleft() < 45000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, type(uint88).max - 1);} + (address bribeToken,) = _providedBribes[gaugeID].at(0); + _providedBribes[gaugeID].remove(bribeToken); + } + _gaugesWithBribes.remove(gaugeID); + } + + lastTimeBribesProcessed = currentEpochStartTime_; + emit BribesProcessed(currentEpochStartTime_); + _clearUpdateInfo(); + } +} \ No newline at end of file diff --git a/contracts/native/GaugeController.sol b/contracts/native/GaugeController.sol index 07d55343..2200e78a 100644 --- a/contracts/native/GaugeController.sol +++ b/contracts/native/GaugeController.sol @@ -41,10 +41,6 @@ contract GaugeController is /// @notice Underwriting equity token address public override token; - /// @notice Updater address. - /// @dev Second address that can call updateGaugeWeights (in addition to governance). - address public override updater; - /// @notice Insurance leverage factor. /// @dev 1e18 => 100%. uint256 public override leverageFactor; @@ -107,9 +103,10 @@ contract GaugeController is token = token_; leverageFactor = 1e18; // Default 1x leverage. // Pre-fill slot 0 of _gauges, ensure gaugeID 1 maps to _gauges[1] - _gauges.push(GaugeStructs.Gauge(false, 0, "")); + _gauges.push(GaugeStructs.Gauge(false, 0, "")); _clearUpdateInfo(); _epochLength = WEEK; + lastTimeGaugeWeightsUpdated = _getEpochStartTimestamp(); } /*************************************** @@ -177,14 +174,6 @@ contract GaugeController is } } - /** - * @notice Query whether msg.sender is either the governance or updater role. - * @return True if msg.sender is either governor or updater roler, and contract govenance is not locked, false otherwise. - */ - function _isUpdaterOrGovernance() internal view returns (bool) { - return ( !this.governanceIsLocked() && ( msg.sender == updater || msg.sender == this.governance() )); - } - /*************************************** EXTERNAL VIEW FUNCTIONS ***************************************/ @@ -354,6 +343,14 @@ contract GaugeController is } } + /** + * @notice Get current epoch length in seconds. + * @return epochLength + */ + function getEpochLength() external view override returns (uint256 epochLength) { + return _epochLength; + } + /*************************************** INTERNAL MUTATOR FUNCTIONS ***************************************/ @@ -406,7 +403,7 @@ contract GaugeController is */ function vote(address voter_, uint256 gaugeID_, uint256 newVotePowerBPS_) external override returns (uint256 oldVotePowerBPS) { if (gaugeID_ == 0) revert CannotVoteForGaugeID0(); - if (_getEpochStartTimestamp() != lastTimeGaugeWeightsUpdated) revert GaugeWeightsNotYetUpdated(); + if (_getEpochStartTimestamp() > lastTimeGaugeWeightsUpdated) revert GaugeWeightsNotYetUpdated(); if (gaugeID_ + 1 > _gauges.length) revert GaugeIDNotExist(); if (!_votingContracts.contains(msg.sender)) revert NotVotingContract(); // Can remove votes while gauge paused @@ -502,22 +499,15 @@ contract GaugeController is emit TokenSet(token_); } - /** - * @notice Set updater address. - * Can only be called by the current [**governor**](/docs/protocol/governance). - * @param updater_ The address of the new updater. - */ - function setUpdater(address updater_) external override onlyGovernance { - updater = updater_; - emit UpdaterSet(updater_); - } - /** * @notice Set epoch length (as an integer multiple of 1 week). + * @dev Advise caution for timing of this function call. If reducing epoch length, voting may then become closed because lastTimeGaugeWeightsUpdated < epochStartTime. + * @dev If the above case occurs, will need to run updateGaugeWeights to re-open voting. * Can only be called by the current [**governor**](/docs/protocol/governance). * @param weeks_ Integer multiple of 1 week, to set epochLength to. */ function setEpochLengthInWeeks(uint256 weeks_) external override onlyGovernance { + if(weeks_ == 0) revert CannotSetEpochLengthTo0(); _epochLength = weeks_ * WEEK; emit EpochLengthSet(weeks_); } @@ -558,26 +548,28 @@ contract GaugeController is } } + /******************************************** + UPDATER FUNCTION TO BE RUN AFTER EACH EPOCH + ********************************************/ + /** * @notice Updates gauge weights by processing votes for the last epoch. * @dev Designed to be called in a while-loop with custom gas limit of 6M until `lastTimePremiumsCharged == epochStartTimestamp`. - * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. */ function updateGaugeWeights() external override { - if (!_isUpdaterOrGovernance()) revert NotUpdaterNorGovernance(); - if ( _updateInfo._votesIndex == type(uint88).max ) {_resetVotePowerOfGaugeMapping();} // If first call for epoch, reset _votePowerOfGauge + if ( _updateInfo.index3 == type(uint88).max ) {_resetVotePowerOfGaugeMapping();} // If first call for epoch, reset _votePowerOfGauge uint256 epochStartTime = _getEpochStartTimestamp(); if (lastTimeGaugeWeightsUpdated >= epochStartTime) revert GaugeWeightsAlreadyUpdated(); uint256 numVotingContracts = _votingContracts.length(); // Iterate through voting contracts // Use ternary operator to initialise loop, to avoid setting stack-too deep error from too many local variables. - for(uint256 i = _updateInfo._votingContractsIndex == type(uint80).max ? 0 : _updateInfo._votingContractsIndex; i < numVotingContracts; i++) { + for(uint256 i = _updateInfo.index1 == type(uint80).max ? 0 : _updateInfo.index1; i < numVotingContracts; i++) { address votingContract = _votingContracts.at(i); uint256 numVoters = _voters[votingContract].length(); // Iterate through voters - for(uint256 j = _updateInfo._votersIndex == type(uint88).max || i != _updateInfo._votingContractsIndex ? 0 : _updateInfo._votersIndex ; j < numVoters; j++) { + for(uint256 j = _updateInfo.index2 == type(uint88).max || i != _updateInfo.index1 ? 0 : _updateInfo.index2 ; j < numVoters; j++) { if (gasleft() < 200000) {return _saveUpdateState(i, j, 0);} address voter = _voters[votingContract].at(j); uint256 numVotes = _votes[votingContract][voter].length(); @@ -591,7 +583,7 @@ contract GaugeController is IGaugeVoter(votingContract).cacheLastProcessedVotePower(voter, votePower); // Iterate through votes - for(uint256 k = _updateInfo._votesIndex == type(uint88).max || j != _updateInfo._votersIndex || i != _updateInfo._votingContractsIndex ? 0 : _updateInfo._votesIndex; k < numVotes; k++) { + for(uint256 k = _updateInfo.index3 == type(uint88).max || j != _updateInfo.index2 || i != _updateInfo.index1 ? 0 : _updateInfo.index3; k < numVotes; k++) { if (gasleft() < 15000) {return _saveUpdateState(i, j, k);} (uint256 gaugeID, uint256 votingPowerBPS) = _votes[votingContract][voter].at(k); // Address edge case where vote placed before gauge is paused, will be counted diff --git a/contracts/native/UnderwritingLockVoting.sol b/contracts/native/UnderwritingLockVoting.sol index f2e17779..34d6eeb3 100644 --- a/contracts/native/UnderwritingLockVoting.sol +++ b/contracts/native/UnderwritingLockVoting.sol @@ -10,6 +10,7 @@ import "./../interfaces/utils/IRegistry.sol"; import "./../interfaces/native/IUnderwritingLocker.sol"; import "./../interfaces/native/IUnderwritingLockVoting.sol"; import "./../interfaces/native/IGaugeController.sol"; +import "./../interfaces/native/IVoteListener.sol"; /** * @title UnderwritingLockVoting @@ -63,10 +64,6 @@ contract UnderwritingLockVoting is /// @notice Registry address address public override registry; - /// @notice Updater address. - /// @dev Second address that can call chargePremiums (in addition to governance). - address public override updater; - /// @notice End timestamp for last epoch that premiums were charged for all stored votes. uint256 public override lastTimePremiumsCharged; @@ -111,6 +108,7 @@ contract UnderwritingLockVoting is // Initialize as non-zero storage slots. _totalPremiumDue = type(uint256).max; _clearUpdateInfo(); + lastTimePremiumsCharged = _getEpochStartTimestamp(); } /*************************************** @@ -155,14 +153,6 @@ contract UnderwritingLockVoting is return IGaugeController(gaugeController).lastTimeGaugeWeightsUpdated(); } - /** - * @notice Query whether msg.sender is either the governance or updater role. - * @return True if msg.sender is either governor or updater roler, and contract govenance is not locked, false otherwise. - */ - function _isUpdaterOrGovernance() internal view returns (bool) { - return ( !this.governanceIsLocked() && ( msg.sender == updater || msg.sender == this.governance() )); - } - /*************************************** EXTERNAL VIEW FUNCTIONS ***************************************/ @@ -284,7 +274,7 @@ contract UnderwritingLockVoting is */ function _vote(address voter_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_) internal { // Disable voting if votes not yet processed or premiums not yet charged for this epoch - if ( _getEpochStartTimestamp() != lastTimePremiumsCharged) revert LastEpochPremiumsNotCharged(); + if ( _getEpochStartTimestamp() > lastTimePremiumsCharged) revert LastEpochPremiumsNotCharged(); if( voter_ != msg.sender && delegateOf[voter_] != msg.sender && bribeController != msg.sender) revert NotOwnerNorDelegate(); if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); @@ -311,11 +301,36 @@ contract UnderwritingLockVoting is emit VoteChanged(voter_, gaugeID, votePowerBPS, oldVotePowerBPS); } } + + if (bribeController != msg.sender) _notifyBribeController(voter_, gaugeID, votePowerBPS); } if (usedVotePowerBPSOf[voter_] > 10000) revert TotalVotePowerBPSOver10000(); } + /** + * @notice Internal function to notify BribeController contract of votes made. + * @dev Required to prevent edge case where voteForBribe made via BribeController, is then modified via this contract, and the vote modifications are not reflected in BribeController _votes and _votesMirror storage data structures. + * @dev The above will result in an edge case where a voter can claim more bribes than they are actually eligible for (votePowerBPS in BribeController _votes data structure that is processed in processBribes(), will be higher than actual votePowerBPS used.) + * @param voter_ The voter address. + * @param gaugeID_ The gaugeID to vote for. + * @param votePowerBPS_ votePowerBPS value. Can be from 0-10000. + */ + function _notifyBribeController(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) internal { + // Try-catch does not catch 'function call to a non-contract account' error. So we check if we are going to call a non-contract account. + uint256 csize = 0; + + // Our first thought is that it is more concise to write `if eq(extcodesize(bribeController.slot), 0) { return(0, 0) }` in assembly block, however 'return' in inline assembly halts code execution altogether, which is different behaviour from 'return' in high-level Solidity. + assembly { + csize := extcodesize(sload(bribeController.slot)) + } + + if (csize == 0) return; + + // Use try-catch to avoid revert + try IVoteListener(bribeController).receiveVoteNotification(voter_, gaugeID_, votePowerBPS_) {} catch {} + } + /*************************************** EXTERNAL MUTATOR FUNCTIONS ***************************************/ @@ -331,7 +346,7 @@ contract UnderwritingLockVoting is * Can only be called by the voter or vote delegate. * @param voter_ The voter address. * @param gaugeID_ The ID of the gauge to vote for. - * @param votePowerBPS_ Vote power BPS to assign to this vote + * @param votePowerBPS_ Vote power BPS to assign to this vote. */ function vote(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external override { if ( IUnderwritingLocker(underwritingLocker).balanceOf(voter_) == 0 ) revert VoterHasNoLocks(); @@ -452,16 +467,6 @@ contract UnderwritingLockVoting is _setRegistry(registry_); } - /** - * @notice Set updater address. - * Can only be called by the current [**governor**](/docs/protocol/governance). - * @param updater_ The address of the new updater. - */ - function setUpdater(address updater_) external override onlyGovernance { - updater = updater_; - emit UpdaterSet(updater_); - } - /** * @notice Sets bribeController as per `bribeController` address stored in Registry. * @dev We do not set this in constructor, because we expect BribeController.sol to be deployed after this contract. @@ -474,17 +479,19 @@ contract UnderwritingLockVoting is emit BribeControllerSet(bribeControllerAddr); } + /******************************************** + UPDATER FUNCTION TO BE RUN AFTER EACH EPOCH + ********************************************/ + /** * @notice Charge premiums for votes. * @dev Designed to be called in a while-loop with the condition being `lastTimePremiumsCharged != epochStartTimestamp` and using the maximum custom gas limit. * @dev Requires GaugeController.updateGaugeWeights() to be run to completion for the last epoch. - * Can only be called by the current [**governor**](/docs/protocol/governance). */ function chargePremiums() external override { - if (!_isUpdaterOrGovernance()) revert NotUpdaterNorGovernance(); uint256 epochStartTimestamp = _getEpochStartTimestamp(); - if(_getLastTimeGaugesUpdated() != epochStartTimestamp) revert GaugeWeightsNotYetUpdated(); - if(lastTimePremiumsCharged == epochStartTimestamp) revert LastEpochPremiumsAlreadyProcessed({epochTime: epochStartTimestamp}); + if(lastTimePremiumsCharged >= epochStartTimestamp) revert LastEpochPremiumsAlreadyProcessed({epochTime: epochStartTimestamp}); + if(_getLastTimeGaugesUpdated() < epochStartTimestamp) revert GaugeWeightsNotYetUpdated(); // Single call for universal multipliers in premium computation. uint256 insuranceCapacity = IGaugeController(gaugeController).getInsuranceCapacity(); @@ -493,7 +500,7 @@ contract UnderwritingLockVoting is // Iterate through voters address[] memory voters = IGaugeController(gaugeController).getVoters(address(this)); - for(uint256 i = _updateInfo._votersIndex == type(uint88).max ? 0 : _updateInfo._votersIndex ; i < voters.length; i++) { + for(uint256 i = _updateInfo.index2 == type(uint88).max ? 0 : _updateInfo.index2 ; i < voters.length; i++) { // _saveUpdateState(0, i, 0); // Short-circuit operator - need at least 30K gas for getVoteCount() call if (gasleft() < 40000 || gasleft() < 10000 * IGaugeController(gaugeController).getVoteCount(address(this), voters[i])) { @@ -501,18 +508,7 @@ contract UnderwritingLockVoting is } // Unbounded loop since # of votes (gauges) unbounded uint256 premium = _calculateVotePremium(voters[i], insuranceCapacity, votePowerSum, epochLength); // 87K gas for 10 votes - uint256[] memory lockIDs = IUnderwritingLocker(underwritingLocker).getAllLockIDsOf(voters[i]); - uint256 numLocks = lockIDs.length; - - // Iterate through locks - // Using _votesIndex as _lockIndex - // If either votesIndex slot is cleared, or we aren't on the same voter as when we last saved, start from index 0. - for(uint256 j = _updateInfo._votesIndex == type(uint88).max || i != _updateInfo._votersIndex ? 0 : _updateInfo._votesIndex; j < numLocks; j++) { - if (gasleft() < 20000) {return _saveUpdateState(0, i, j);} - // Split premium amongst each lock equally. - IUnderwritingLocker(underwritingLocker).chargePremium(lockIDs[j], premium / numLocks); - } - + IUnderwritingLocker(underwritingLocker).chargePremium(voters[i], premium); _totalPremiumDue -= premium; } @@ -529,9 +525,9 @@ contract UnderwritingLockVoting is emit AllPremiumsCharged(epochStartTimestamp); } - /*************************************** - updateGaugeWeights() HELPER FUNCTIONS - ***************************************/ + /*********************************** + chargePremiums() HELPER FUNCTIONS + ***********************************/ /** * @notice Save state of charging premium to _updateInfo diff --git a/contracts/native/UnderwritingLocker.sol b/contracts/native/UnderwritingLocker.sol index 9b61d7e6..6b27f8d4 100644 --- a/contracts/native/UnderwritingLocker.sol +++ b/contracts/native/UnderwritingLocker.sol @@ -709,14 +709,16 @@ contract UnderwritingLocker is * @notice Perform accounting for voting premiums to be charged by UnderwritingLockVoting.chargePremiums(). * @dev Can only be called by votingContract set in the registry. * @dev Not meant to be called directly, but via UnderwritingLockVoting.chargePremiums(). - * @dev Costs 5K gas per call to add notification functionality. This will quickly add-up in an unbounded loop so we omit it. - * @param lockID_ The ID of the lock to charge. + * @param voter_ Voter to charge premium for. * @param premium_ The amount of token charged as premium. */ - function chargePremium(uint256 lockID_, uint256 premium_) external override nonReentrant { + function chargePremium(address voter_, uint256 premium_) external override nonReentrant { if (msg.sender != votingContract) revert NotVotingContract(); - if (!_exists(lockID_)) {return;} - _locks[lockID_].amount -= premium_; + uint256[] memory lockIDs = _getAllLockIDsOf(voter_); + uint256 numLocks = lockIDs.length; + for (uint256 i = 0; i < numLocks; i++) { + _locks[lockIDs[i]].amount -= premium_ / numLocks; + } } /*************************************** diff --git a/scripts/goerli/deploy-native-locker.ts b/scripts/goerli/deploy-native-locker.ts index 42ac9bfd..649e9bc3 100644 --- a/scripts/goerli/deploy-native-locker.ts +++ b/scripts/goerli/deploy-native-locker.ts @@ -20,7 +20,7 @@ const deployer = new ethers.Wallet(JSON.parse(process.env.PRIVATE_KEYS || '[]')[ import { logContractAddress } from "../utils"; import { import_artifacts, ArtifactImports } from "../../test/utilities/artifact_importer"; -import { Registry, Erc20, UnderwritingLockVoting, UnderwritingLocker, GaugeController, DepositHelper } from "../../typechain"; +import { Registry, Erc20, UnderwritingLockVoting, UnderwritingLocker, GaugeController, DepositHelper, BribeController } from "../../typechain"; import { expectDeployed, isDeployed } from "../../test/utilities/expectDeployed"; import { getNetworkSettings } from "../getNetworkSettings"; import { create2Contract } from "../create2Contract"; @@ -30,11 +30,12 @@ const DEPLOYER_CONTRACT_ADDRESS = "0x501aCe4732E4A80CC1bc5cd081BEe7f88ff const REGISTRY_ADDRESS = "0x501ACe0f576fc4ef9C0380AA46A578eA96b85776"; const UWP_ADDRESS = "0x501ACEb41708De16FbedE3b31f3064919E9d7F23"; const UWE_ADDRESS = "0x501ACE809013C8916CAAe439e9653bc436172919"; -const REVENUE_ROUTER_ADDRESS = "0x501AcE0e8D16B92236763E2dEd7aE3bc2DFfA276"; -const UNDERWRITING_LOCKER_ADDRESS = "0x501ACeC465fEbc1b1b936Bdc937A9FD28F6E6E7E"; -const GAUGE_CONTROLLER_ADDRESS = "0x501acEB8a1D613D4aa1CD101881174bFAFAF1700"; -const UNDERWRITING_LOCK_VOTING_ADDRESS = "0x501AcE58312fa3EED60fc38Ce0E5562112E72dF0"; -const DEPOSIT_HELPER_ADDRESS = "0x501ACE3b845e959fa9b4C06e71973d9AB13853F3"; +const REVENUE_ROUTER_ADDRESS = "0x501aceB2Ff39b3aC0189ba1ACe497C3dAB486F7B"; +const UNDERWRITING_LOCKER_ADDRESS = "0x501aCeFC6a6ff5Aa21c27D7D9D58bedCA94f7BC9"; +const GAUGE_CONTROLLER_ADDRESS = "0x501acE57a87C6B4Eec1BfD2fF2d600F65C2875aB"; +const UNDERWRITING_LOCK_VOTING_ADDRESS = "0x501ACe9cc96E4eE51a4c2098d040EE15F6f3e77F"; +const DEPOSIT_HELPER_ADDRESS = "0x501acE1652Cb4d7386cdaBCd84CdE26C811F3520"; +const BRIBE_CONTROLLER_ADDRESS = "0x501Ace5093F43FBF578d081f2d93B5f42e905f90"; let GOVERNOR_ADDRESS: string; let artifacts: ArtifactImports; @@ -43,6 +44,7 @@ let underwritingLocker: UnderwritingLocker; let voting: UnderwritingLockVoting; let gaugeController: GaugeController; let depositHelper: DepositHelper; +let bribeController: BribeController; let signerAddress: string; let networkSettings: any; @@ -67,15 +69,17 @@ async function main() { registry = (await ethers.getContractAt(artifacts.Registry.abi, REGISTRY_ADDRESS)) as Registry; - await setRegistry1(); // Set 'uwe' in the registry + //await setRegistry1(); // Set 'uwe' in the registry await deployUnderwritingLocker(); await deployGaugeController(); - await setRegistry2(); // Set 'revenueRouter', 'underwritingLocker' and 'gaugeController' in the registry + //await setRegistry2(); // Set 'revenueRouter', 'underwritingLocker' and 'gaugeController' in the registry await deployUnderwritingLockVoting(); - await gaugeSetup(); - await addGauges(); + //await gaugeSetup(); await deployDepositHelper(); - await setRegistry3(); // Set registry contract + //await setRegistry3(); // set 'underwritingLockVoting' in the registry + await deployBribeController(); + //await setBribeController(); + //await updateRegistry(); // post update to registry contract // log addresses await logAddresses(); @@ -148,22 +152,6 @@ async function gaugeSetup() { await tx4.wait(networkSettings.confirmations); } -async function addGauges() { - console.log("Adding gauges to GaugeController"); - let rol = BN.from(10).pow(18).mul(250).div(10000); // 2.5% - let appIDs = ["aurora-plus", "aurigami", "bastion-protocol", "bluebit", "trisolaris", "vaporwave-finance"]; - let len = (await gaugeController.totalGauges()).toNumber(); - if(len > 0) { - console.log(`${len} gauges already found. skipping`); - return; - } - for(let i = 0; i < appIDs.length; ++i) { - let tx = await gaugeController.connect(deployer).addGauge(appIDs[i], rol, networkSettings.overrides); - await tx.wait(networkSettings.confirmations); - } - console.log("Added gauges to GaugeController"); -} - async function deployDepositHelper() { if(await isDeployed(DEPOSIT_HELPER_ADDRESS)) { depositHelper = (await ethers.getContractAt(artifacts.DepositHelper.abi, DEPOSIT_HELPER_ADDRESS)) as DepositHelper; @@ -176,12 +164,47 @@ async function deployDepositHelper() { } async function setRegistry3() { - console.log("Setting registry"); + console.log("Setting 'underwritingLockVoting' in the Registry.") + const keys = ["underwritingLockVoting"]; + const values = [voting.address]; + let tx = await registry.connect(deployer).set(keys, values, networkSettings.overrides); + await tx.wait(networkSettings.confirmations); + console.log("Set 'underwritingLockVoting' in the Registry.") +} + +async function deployBribeController() { + if(await isDeployed(BRIBE_CONTROLLER_ADDRESS)) { + bribeController = (await ethers.getContractAt(artifacts.BribeController.abi, BRIBE_CONTROLLER_ADDRESS)) as BribeController; + } else { + console.log("Deploying BribeController"); + const res = await create2Contract(deployer, artifacts.BribeController, [signerAddress, REGISTRY_ADDRESS], {}, "", DEPLOYER_CONTRACT_ADDRESS); + bribeController = (await ethers.getContractAt(artifacts.BribeController.abi, res.address)) as BribeController; + console.log(`Deployed BribeController to ${bribeController.address}`); + } +} + +async function setBribeController() { + console.log("Setting BribeController in UnderwritingLockVoting"); + let res = await registry.tryGet("bribeController"); + if(res.value != bribeController.address) { + let tx1 = await registry.connect(deployer).set(["bribeController"], [bribeController.address], networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + } + let bc = await voting.bribeController(); + if(bc != bribeController.address) { + let tx2 = await voting.connect(deployer).setBribeController(networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + } + console.log("Set BribeController in UnderwritingLockVoting"); +} + +async function updateRegistry() { + console.log("Updating registry"); let tx1 = await underwritingLocker.connect(deployer).setRegistry(registry.address, networkSettings.overrides); await tx1.wait(networkSettings.confirmations); let tx2 = await voting.connect(deployer).setRegistry(registry.address, networkSettings.overrides); await tx2.wait(networkSettings.confirmations); - console.log("Set registry"); + console.log("Updated registry"); } async function logAddresses() { @@ -192,6 +215,7 @@ async function logAddresses() { logContractAddress("GaugeController", gaugeController.address); logContractAddress("UnderwritingLockVoting", voting.address); logContractAddress("DepositHelper", depositHelper.address); + logContractAddress("BribeController", bribeController.address); } main() diff --git a/scripts/goerli/use-native.ts b/scripts/goerli/use-native.ts index 76f41616..6b3b94ca 100644 --- a/scripts/goerli/use-native.ts +++ b/scripts/goerli/use-native.ts @@ -1,9 +1,10 @@ -// deploys v3 of solace wallet coverage +// some additional setup and usage of solace native import hardhat from "hardhat"; const { waffle, ethers } = hardhat; const { provider } = waffle; const BN = ethers.BigNumber; +import axios from "axios" import { config as dotenv_config } from "dotenv"; dotenv_config(); const deployer = new ethers.Wallet(JSON.parse(process.env.PRIVATE_KEYS || '[]')[0], provider); @@ -11,7 +12,7 @@ const deployer = new ethers.Wallet(JSON.parse(process.env.PRIVATE_KEYS || '[]')[ import { logContractAddress } from "./../utils"; import { import_artifacts, ArtifactImports } from "./../../test/utilities/artifact_importer"; -import { SolaceMegaOracle, FluxMegaOracle, UnderwritingPool, UnderwritingEquity, UnderwritingLockVoting, UnderwritingLocker, GaugeController, MockErc20, DepositHelper } from "../../typechain"; +import { SolaceMegaOracle, FluxMegaOracle, UnderwritingPool, UnderwritingEquity, UnderwritingLockVoting, UnderwritingLocker, GaugeController, MockErc20, DepositHelper, BribeController } from "../../typechain"; import { expectDeployed, isDeployed } from "../../test/utilities/expectDeployed"; import { getNetworkSettings } from "../getNetworkSettings"; import { create2Contract } from "../create2Contract"; @@ -44,12 +45,12 @@ const SOLACE_MEGA_ORACLE_ADDRESS = "0x501acE1701111C26Ac718952EEFB3698bDC const FLUX_MEGA_ORACLE_ADDRESS = "0x501AcEd36232595b46A1Fb03cCF3cE5e056d5F13"; const UWP_ADDRESS = "0x501ACEb41708De16FbedE3b31f3064919E9d7F23"; const UWE_ADDRESS = "0x501ACE809013C8916CAAe439e9653bc436172919"; -const REVENUE_ROUTER_ADDRESS = "0x501AcE0e8D16B92236763E2dEd7aE3bc2DFfA276"; -const UNDERWRITING_LOCKER_ADDRESS = "0x501ACeC465fEbc1b1b936Bdc937A9FD28F6E6E7E"; -const GAUGE_CONTROLLER_ADDRESS = "0x501acEB8a1D613D4aa1CD101881174bFAFAF1700"; -const UNDERWRITING_LOCK_VOTING_ADDRESS = "0x501AcE58312fa3EED60fc38Ce0E5562112E72dF0"; -const DEPOSIT_HELPER_ADDRESS = "0x501ACE3b845e959fa9b4C06e71973d9AB13853F3"; - +const REVENUE_ROUTER_ADDRESS = "0x501aceB2Ff39b3aC0189ba1ACe497C3dAB486F7B"; +const UNDERWRITING_LOCKER_ADDRESS = "0x501aCeFC6a6ff5Aa21c27D7D9D58bedCA94f7BC9"; +const GAUGE_CONTROLLER_ADDRESS = "0x501acE57a87C6B4Eec1BfD2fF2d600F65C2875aB"; +const UNDERWRITING_LOCK_VOTING_ADDRESS = "0x501ACe9cc96E4eE51a4c2098d040EE15F6f3e77F"; +const DEPOSIT_HELPER_ADDRESS = "0x501acE1652Cb4d7386cdaBCd84CdE26C811F3520"; +const BRIBE_CONTROLLER_ADDRESS = "0x501Ace5093F43FBF578d081f2d93B5f42e905f90"; const ONE_USDC = BN.from("1000000"); const ONE_ETHER = BN.from("1000000000000000000"); @@ -66,6 +67,7 @@ let underwritingLocker: UnderwritingLocker; let underwritingLockVoting: UnderwritingLockVoting; let gaugeController: GaugeController; let depositHelper: DepositHelper; +let bribeController: BribeController; let signerAddress: string; let networkSettings: any; @@ -105,6 +107,7 @@ async function main() { await expectDeployed(UNDERWRITING_LOCK_VOTING_ADDRESS); await expectDeployed(GAUGE_CONTROLLER_ADDRESS); await expectDeployed(DEPOSIT_HELPER_ADDRESS); + await expectDeployed(BRIBE_CONTROLLER_ADDRESS); solaceMegaOracle = (await ethers.getContractAt(artifacts.SolaceMegaOracle.abi, SOLACE_MEGA_ORACLE_ADDRESS)) as SolaceMegaOracle; fluxMegaOracle = (await ethers.getContractAt(artifacts.FluxMegaOracle.abi, FLUX_MEGA_ORACLE_ADDRESS)) as FluxMegaOracle; @@ -114,147 +117,70 @@ async function main() { gaugeController = (await ethers.getContractAt(artifacts.GaugeController.abi, GAUGE_CONTROLLER_ADDRESS)) as GaugeController; underwritingLockVoting = (await ethers.getContractAt(artifacts.UnderwritingLockVoting.abi, UNDERWRITING_LOCK_VOTING_ADDRESS)) as UnderwritingLockVoting; depositHelper = (await ethers.getContractAt(artifacts.DepositHelper.abi, DEPOSIT_HELPER_ADDRESS)) as DepositHelper; + bribeController = (await ethers.getContractAt(artifacts.BribeController.abi, BRIBE_CONTROLLER_ADDRESS)) as BribeController; + + await setPriceFeeds(); + //await getUwpTokens(); + //await getTokenValues(); + //await addGauges(); + //await getGauges(); + await rolloverEpoch(); + //await getEpochTimestamps(); + await addBribeTokens(); //await depositIntoUwp(); //await depositIntoUwe(); //await useDepositHelper(); + await useDepositHelper2(); //await withdrawFromLocks(); //await withdrawFromUwe(); //await redeemFromUwp(); - - await setPriceFeeds(); - await getUwpTokens(); - //await getGauges(); - //await rolloverEpoch(); //await castVote(); - //await getEpochTimestamps(); -} - -async function depositIntoUwp() { - let usdc = (await ethers.getContractAt(artifacts.MockERC20.abi, USDC_ADDRESS)) as MockErc20; - let wbtc = (await ethers.getContractAt(artifacts.MockERC20.abi, WBTC_ADDRESS)) as MockErc20; - let weth = (await ethers.getContractAt(artifacts.MockERC20.abi, WETH_ADDRESS)) as MockErc20; - - console.log("Depositing tokens into UWP"); - let tokens = [usdc, wbtc, weth]; - let tokenAddresses = [usdc.address, wbtc.address, weth.address]; - let symbols = ["USDC", "WBTC", "WETH"]; - let depositAmounts = [ONE_USDC.mul(100), ONE_WBTC.div(100), ONE_ETHER.div(10)]; - for(var i = 0; i < tokens.length; ++i) { - if((await tokens[i].allowance(signerAddress, uwp.address)).lt(depositAmounts[i])) { - let tx = await tokens[i].connect(deployer).approve(uwp.address, ethers.constants.MaxUint256, networkSettings.overrides); - await tx.wait(networkSettings.confirmations); - } - let bal = await tokens[i].balanceOf(signerAddress); - if(bal.lt(depositAmounts[i])) { - console.log(`insufficient ${symbols[i]} balance. depositing ${ethers.utils.formatUnits(depositAmounts[i])} have ${ethers.utils.formatUnits(bal)}`); - } - } - let bal1 = await uwp.balanceOf(signerAddress); - console.log(`uwp balance before : ${ethers.utils.formatUnits(bal1)}`); - let tx2 = await uwp.connect(deployer).issue(tokenAddresses, depositAmounts, signerAddress, networkSettings.overrides); - await tx2.wait(networkSettings.confirmations); - let bal2 = await uwp.balanceOf(signerAddress); - console.log(`uwp balance after : ${ethers.utils.formatUnits(bal2)}`); - console.log("Deposited tokens into UWP"); -} - -async function redeemFromUwp() { - console.log("Redeeming UWP"); - let bal = await uwp.balanceOf(signerAddress); - let tx = await uwp.connect(deployer).redeem(bal, signerAddress, networkSettings.overrides); - await tx.wait(networkSettings.confirmations); - console.log("Redeemed UWP"); -} - -async function depositIntoUwe() { - console.log("Depositing UWP into UWE"); - let bal = await uwp.balanceOf(signerAddress); - let allowance = await uwp.allowance(signerAddress, uwe.address); - if(allowance.lt(bal)) { - let tx1 = await uwp.connect(deployer).approve(uwe.address, ethers.constants.MaxUint256, networkSettings.overrides); - await tx1.wait(networkSettings.confirmations); - } - let bal1 = await uwe.balanceOf(signerAddress); - console.log(`uwe balance before : ${ethers.utils.formatUnits(bal1)}`); - console.log(`depositing ${ethers.utils.formatUnits(bal)} uwp`) - let tx2 = await uwe.connect(deployer).deposit(bal, signerAddress, networkSettings.overrides); - await tx2.wait(networkSettings.confirmations); - let bal2 = await uwe.balanceOf(signerAddress); - console.log(`uwe balance after : ${ethers.utils.formatUnits(bal2)}`); - console.log("Deposited UWP into UWE"); + //await offerBribe(); + //await acceptBribe(); } -async function useDepositHelper() { - console.log("Depositing into new lock via DepositHelper"); - let tkn = (await ethers.getContractAt(artifacts.MockERC20.abi, DAI_ADDRESS)) as MockErc20; - let dec = 18; - let depositAmount = ONE_ETHER.mul(1000); - let bal = await tkn.balanceOf(signerAddress); - if(bal.lt(depositAmount)) { - console.log(`insufficient balance. depositing ${ethers.utils.formatUnits(depositAmount,dec)} have ${ethers.utils.formatUnits(bal,dec)}`); - return; - } - let allowance = await tkn.allowance(signerAddress, depositHelper.address); - if(allowance.lt(depositAmount)) { - let tx1 = await tkn.connect(deployer).approve(depositHelper.address, ethers.constants.MaxUint256, networkSettings.overrides); - await tx1.wait(networkSettings.confirmations); - } - let expiry = (await provider.getBlock('latest')).timestamp + 60*60*24*365*4; // 4 years from now - let tx2 = await depositHelper.connect(deployer).depositAndLock(tkn.address, depositAmount, expiry, networkSettings.overrides); - await tx2.wait(networkSettings.confirmations); - let bal2 = await underwritingLocker.balanceOf(signerAddress); - let lockID = await underwritingLocker.tokenOfOwnerByIndex(signerAddress, bal2.sub(1)); - let lock = await underwritingLocker.locks(lockID); - console.log(`created lockID=${lockID.toNumber()}. uwe=${ethers.utils.formatUnits(lock.amount)} expiry=${lock.end}`); - console.log("Deposited into new lock via DepositHelper"); -} +async function setPriceFeeds() { + console.log('Setting prices in SolaceMegaOracle'); + console.log('fetching prices'); + let tokens = [ + { address: USDC_ADDRESS, coingeckoID: "usd-coin" }, + { address: DAI_ADDRESS, coingeckoID: "dai" }, + { address: USDT_ADDRESS, coingeckoID: "tether" }, + { address: FRAX_ADDRESS, coingeckoID: "frax" }, + { address: WBTC_ADDRESS, coingeckoID: "bitcoin" }, + { address: WETH_ADDRESS, coingeckoID: "ethereum" }, + { address: NEAR_ADDRESS, coingeckoID: "near" }, + { address: SOLACE_ADDRESS, coingeckoID: "solace" }, + { address: AURORA_ADDRESS, coingeckoID: "aurora-near" }, + { address: PLY_ADDRESS, coingeckoID: "aurigami" }, + { address: BSTN_ADDRESS, coingeckoID: "bastion-protocol" }, + { address: BBT_ADDRESS, coingeckoID: "bluebit" }, + { address: TRI_ADDRESS, coingeckoID: "trisolaris" }, + { address: VWAVE_ADDRESS, coingeckoID: "vaporwave" }, + ]; + let coingeckoIDs = tokens.map(token => token.coingeckoID).join(','); + let url = `https://api.coingecko.com/api/v3/coins/markets?ids=${coingeckoIDs}&vs_currency=usd`; + let res = await axios.get(url); + + let prices = tokens.map(token => { + let price = res.data.filter((cgToken:any) => cgToken.id == token.coingeckoID)[0].current_price; + let price_normalized = ONE_ETHER.mul(Math.floor(price * 1000000000)).div(1000000000); + console.log(`${rightPad(token.coingeckoID, 20)} ${leftPad(`${price}`, 12)} ${leftPad(price_normalized.toString(), 25)}`); + return price_normalized; + }); -async function withdrawFromLocks() { - console.log("Withdrawing from locks"); - let bal = (await underwritingLocker.balanceOf(signerAddress)).toNumber(); - let lockIDs = []; - for(let i = 0; i < bal; ++i) { - lockIDs.push((await underwritingLocker.tokenOfOwnerByIndex(signerAddress, i)).toNumber()); - } - console.log(`signer has ${bal} locks: ${lockIDs}`); - console.log(`starting uwe balance: ${ethers.utils.formatUnits(await uwe.balanceOf(signerAddress))}`) - for(let i = 0; i < bal; ++i) { - let lockID = lockIDs[i]; - let lock = await underwritingLocker.locks(lockID); - let amountOut = await underwritingLocker.getWithdrawAmount(lockID); - console.log(`withdrawing from lock ${lockID}. uwe=${ethers.utils.formatUnits(lock.amount)} end=${(new Date(lock.end.toNumber()*1000)).toUTCString()} amountOut=${ethers.utils.formatUnits(amountOut)}`); - let tx = await underwritingLocker.connect(deployer).withdraw(lockID, signerAddress, {...networkSettings.overrides, gasLimit:300000}); - await tx.wait(networkSettings.confirmations); - } - console.log(`end uwe balance: ${ethers.utils.formatUnits(await uwe.balanceOf(signerAddress))}`) - console.log("Withdrew from locks"); -} + let addresses = tokens.map(token => token.address); -async function withdrawFromUwe() { - console.log("Redeeming UWE"); - let bal1p = await uwp.balanceOf(signerAddress); - let bal1e = await uwe.balanceOf(signerAddress); - console.log('before'); - console.log(`uwp balance: ${ethers.utils.formatUnits(bal1p)}`) - console.log(`uwe balance: ${ethers.utils.formatUnits(bal1e)}`) - let tx = await uwe.connect(deployer).withdraw(bal1e, signerAddress, networkSettings.overrides); + let tx = await solaceMegaOracle.connect(deployer).transmit(addresses, prices, networkSettings.overrides); await tx.wait(networkSettings.confirmations); - let bal2p = await uwp.balanceOf(signerAddress); - let bal2e = await uwe.balanceOf(signerAddress); - console.log('after'); - console.log(`uwp balance: ${ethers.utils.formatUnits(bal2p)}`) - console.log(`uwe balance: ${ethers.utils.formatUnits(bal2e)}`) - console.log("Redeemed UWE"); -} - -async function setPriceFeeds() { - console.log('Setting prices in SolaceMegaOracle'); + /* let tx = await solaceMegaOracle.connect(deployer).transmit( [NEAR_ADDRESS, SOLACE_ADDRESS, AURORA_ADDRESS, PLY_ADDRESS, BSTN_ADDRESS, BBT_ADDRESS, TRI_ADDRESS, VWAVE_ADDRESS], [ONE_ETHER.mul(4), ONE_ETHER.mul(120).div(10000), ONE_ETHER.mul(14000).div(10000), ONE_ETHER.mul(16).div(10000), ONE_ETHER.mul(36).div(10000), ONE_ETHER.mul(9).div(10000), ONE_ETHER.mul(317).div(10000), ONE_ETHER.mul(223697).div(10000)], networkSettings.overrides); await tx.wait(networkSettings.confirmations); + */ console.log('Set prices in SolaceMegaOracle'); } @@ -284,7 +210,67 @@ async function getUwpTokens() { console.log("----------------------------------------------------------------------------------------"); for(let tokenID = 0; tokenID < len; ++tokenID) { console.log(`| ${leftPad(tokenMetadata[tokenID][0],17)} | ${leftPad(tokenMetadata[tokenID][1],6)} | ${leftPad(`${tokenMetadata[tokenID][2]}`,8)} | ${leftPad(ethers.utils.formatUnits(oracleData[tokenID][0]),15)} | ${leftPad(ethers.utils.formatUnits(tokenMetadata[tokenID][3],tokenMetadata[tokenID][2]),8)} | ${leftPad(ethers.utils.formatUnits(oracleData[tokenID][1]),15)} |`) + + //console.log(`{"name": "${tokenMetadata[tokenID][0]}", "symbol": "${tokenMetadata[tokenID][1]}", "decimals": ${tokenMetadata[tokenID][2]}, "address": "${tokenData[tokenID].token}", "oracle": "${tokenData[tokenID].oracle}"},`) + } +} + +async function getTokenValues() { + let len = (await uwp.tokensLength()).toNumber(); + let tokenData = []; + let tokenMetadata = []; + let oracleData = []; + for(let tokenID = 0; tokenID < len; ++tokenID) { + let data = await uwp.tokenList(tokenID); + tokenData.push(data); + let token = (await ethers.getContractAt(artifacts.MockERC20.abi, data.token)) as MockErc20; + let metadata = await Promise.all([ + token.name(), + token.symbol(), + token.decimals(), + token.totalSupply(), + ]) + tokenMetadata.push(metadata); + let oracle2 = (await ethers.getContractAt(artifacts.FluxMegaOracle.abi, data.oracle)) as FluxMegaOracle; + oracleData.push(await Promise.all([ + oracle2.valueOfTokens(data.token, BN.from(10).pow(metadata[2])), // one token + oracle2.valueOfTokens(data.token, metadata[3]), // balance + ])); + } + console.log("| Name | Symbol | Decimals | Price | Supply | Value |"); + console.log("-----------------------------------------------------------------------------------------------"); + for(let tokenID = 0; tokenID < len; ++tokenID) { + //console.log(`| ${leftPad(tokenMetadata[tokenID][0],17)} | ${leftPad(tokenMetadata[tokenID][1],6)} | ${leftPad(`${tokenMetadata[tokenID][2]}`,8)} | ${leftPad(ethers.utils.formatUnits(oracleData[tokenID][0]),15)} | ${leftPad(ethers.utils.formatUnits(tokenMetadata[tokenID][3],tokenMetadata[tokenID][2]),15)} | ${leftPad(ethers.utils.formatUnits(oracleData[tokenID][1]),15)} |`) + + let items = [ + leftPad(tokenMetadata[tokenID][0],17), + leftPad(tokenMetadata[tokenID][1],6), + leftPad(`${tokenMetadata[tokenID][2]}`,8), + leftPad(ethers.utils.formatUnits(oracleData[tokenID][0]),15), + leftPad(Math.floor(parseInt(ethers.utils.formatUnits(tokenMetadata[tokenID][3],tokenMetadata[tokenID][2]))).toLocaleString(),15), + leftPad(Math.floor(parseInt(ethers.utils.formatUnits(oracleData[tokenID][1]))).toLocaleString(),15) + ] + let row = `| ${items.join(' | ')} |` + console.log(row); + + //console.log(`{"name": "${tokenMetadata[tokenID][0]}", "symbol": "${tokenMetadata[tokenID][1]}", "decimals": ${tokenMetadata[tokenID][2]}, "address": "${tokenData[tokenID].token}", "oracle": "${tokenData[tokenID].oracle}"},`) + } +} + +async function addGauges() { + console.log("Adding gauges to GaugeController"); + let rol = BN.from(10).pow(18).mul(250).div(10000); // 2.5% + let appIDs = ["aurora-plus", "aurigami", "bastion-protocol", "bluebit", "trisolaris", "vaporwave-finance"]; + let len = (await gaugeController.totalGauges()).toNumber(); + if(len > 0) { + console.log(`${len} gauges already found. skipping`); + return; + } + for(let i = 0; i < appIDs.length; ++i) { + let tx = await gaugeController.connect(deployer).addGauge(appIDs[i], rol, networkSettings.overrides); + await tx.wait(networkSettings.confirmations); } + console.log("Added gauges to GaugeController"); } async function getGauges() { @@ -322,19 +308,36 @@ async function rolloverEpoch() { const tx = await underwritingLockVoting.connect(deployer).chargePremiums({...networkSettings.overrides, gasLimit: 6000000}) await tx.wait(networkSettings.confirmations) } + + while(true) { + try { + let ts1 = await bribeController.getEpochStartTimestamp(); + let ts2 = await bribeController.lastTimeBribesProcessed(); + if(ts2.gte(ts1)) break; + console.log("Processing bribes"); + let tx = await bribeController.connect(deployer).processBribes(networkSettings.overrides); + await tx.wait(networkSettings.confirmations); + } catch(e) { break; } + } + console.log("Rolled over to next epoch"); } async function castVote() { console.log("Voting"); + // setup let numGauges = (await gaugeController.totalGauges()).toNumber(); - let evenWeight = Math.floor(10000 / numGauges); let gaugeIDs = []; let gaugeWeights = []; + let weightLeft = 10000; + // give all gauges equal amount. may be slightly different due to integer division for(let gaugeID = 1; gaugeID <= numGauges; ++gaugeID) { gaugeIDs.push(gaugeID); - gaugeWeights.push(evenWeight); + let nextWeight = Math.floor(weightLeft / (numGauges + 1 - gaugeID)); // weight left / gauges left + weightLeft -= nextWeight; + gaugeWeights.push(nextWeight); } + // send tx let tx = await underwritingLockVoting.connect(deployer).voteMultiple(signerAddress, gaugeIDs, gaugeWeights, networkSettings.overrides); await tx.wait(networkSettings.confirmations); console.log("Voted"); @@ -364,11 +367,295 @@ async function getEpochTimestamps() { console.log("Fetched epoch timestamps"); } +async function addBribeTokens() { + console.log("Adding bribe tokens"); + let existingWhitelist = await bribeController.getBribeTokenWhitelist(); + let desiredWhitelist = [ + { address: USDC_ADDRESS, coingeckoID: "usd-coin" }, + { address: DAI_ADDRESS, coingeckoID: "dai" }, + { address: USDT_ADDRESS, coingeckoID: "tether" }, + { address: FRAX_ADDRESS, coingeckoID: "frax" }, + { address: WBTC_ADDRESS, coingeckoID: "bitcoin" }, + { address: WETH_ADDRESS, coingeckoID: "ethereum" }, + { address: NEAR_ADDRESS, coingeckoID: "near" }, + { address: SOLACE_ADDRESS, coingeckoID: "solace" }, + { address: AURORA_ADDRESS, coingeckoID: "aurora-near" }, + { address: PLY_ADDRESS, coingeckoID: "aurigami" }, + { address: BSTN_ADDRESS, coingeckoID: "bastion-protocol" }, + { address: BBT_ADDRESS, coingeckoID: "bluebit" }, + { address: TRI_ADDRESS, coingeckoID: "trisolaris" }, + { address: VWAVE_ADDRESS, coingeckoID: "vaporwave" }, + ]; + //let desiredWhitelist = tokens.map(token => token.address); + let missingSetAddresses = []; + let missingSetNames = []; + for(var i = 0; i < desiredWhitelist.length; ++i) { + if(!existingWhitelist.includes(desiredWhitelist[i].address)) { + missingSetAddresses.push(desiredWhitelist[i].address); + missingSetNames.push(desiredWhitelist[i].coingeckoID); + } + } + if(missingSetAddresses.length == 0) { + console.log("No tokens to add"); + return; + } + console.log("Tokens to add:"); + console.log(missingSetNames.join(', ')); + for(var i = 0; i < missingSetAddresses.length; ++i) { + let tx = await bribeController.connect(deployer).addBribeToken(missingSetAddresses[i], networkSettings.overrides); + await tx.wait(networkSettings.confirmations); + } + console.log("Added bribe tokens"); +} + +async function depositIntoUwp() { + let deposits = [ + { symbol: "USDC", amount: ONE_USDC.mul(1000), address: USDC_ADDRESS, decimals: 6 }, + { symbol: "DAI", amount: ONE_ETHER.mul(1000), address: DAI_ADDRESS, decimals: 18 }, + { symbol: "USDT", amount: ONE_USDC.mul(1000), address: USDT_ADDRESS, decimals: 6 }, + { symbol: "FRAX", amount: ONE_ETHER.mul(1000), address: FRAX_ADDRESS, decimals: 18 }, + { symbol: "WBTC", amount: ONE_WBTC.div(10), address: WBTC_ADDRESS, decimals: 8 }, + { symbol: "WETH", amount: ONE_ETHER.div(1), address: WETH_ADDRESS, decimals: 18 }, + { symbol: "NEAR", amount: ONE_NEAR.mul(250), address: NEAR_ADDRESS, decimals: 24 }, + { symbol: "SOLACE", amount: ONE_ETHER.mul(100000), address: SOLACE_ADDRESS, decimals: 18 }, + { symbol: "AURORA", amount: ONE_ETHER.mul(200), address: AURORA_ADDRESS, decimals: 18 }, + { symbol: "PLY", amount: ONE_ETHER.mul(100000), address: PLY_ADDRESS, decimals: 18 }, + { symbol: "BSTN", amount: ONE_ETHER.mul(100000), address: BSTN_ADDRESS, decimals: 18 }, + { symbol: "BBT", amount: ONE_ETHER.mul(100000), address: BBT_ADDRESS, decimals: 18 }, + { symbol: "TRI", amount: ONE_ETHER.mul(10000), address: TRI_ADDRESS, decimals: 18 }, + { symbol: "VWAVE", amount: ONE_ETHER.mul(50), address: VWAVE_ADDRESS, decimals: 18 }, + ]; + let tokenAddresses = deposits.map(deposit => deposit.address); + let depositAmounts = deposits.map(deposit => deposit.amount); + + console.log("Depositing tokens into UWP"); + for(var i = 0; i < deposits.length; ++i) { + let token = (await ethers.getContractAt(artifacts.MockERC20.abi, deposits[i].address)) as MockErc20; + if((await token.allowance(signerAddress, uwp.address)).lt(deposits[i].amount)) { + console.log(`Approving ${deposits[i].symbol}`); + let tx = await token.connect(deployer).approve(uwp.address, ethers.constants.MaxUint256, networkSettings.overrides); + await tx.wait(networkSettings.confirmations); + } + let bal = await token.balanceOf(signerAddress); + if(bal.lt(deposits[i].amount)) { + console.log(`insufficient ${deposits[i].symbol} balance. depositing ${ethers.utils.formatUnits(deposits[i].amount, deposits[i].decimals)} have ${ethers.utils.formatUnits(bal, deposits[i].decimals)}`); + } + } + let bal1 = await uwp.balanceOf(signerAddress); + console.log(`uwp balance before : ${ethers.utils.formatUnits(bal1)}`); + let tx2 = await uwp.connect(deployer).issue(tokenAddresses, depositAmounts, signerAddress, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + let bal2 = await uwp.balanceOf(signerAddress); + console.log(`uwp balance after : ${ethers.utils.formatUnits(bal2)}`); + console.log("Deposited tokens into UWP"); +} + +async function redeemFromUwp() { + console.log("Redeeming UWP"); + let bal = await uwp.balanceOf(signerAddress); + let tx = await uwp.connect(deployer).redeem(bal, signerAddress, networkSettings.overrides); + await tx.wait(networkSettings.confirmations); + console.log("Redeemed UWP"); +} + +async function depositIntoUwe() { + console.log("Depositing UWP into UWE"); + let bal = await uwp.balanceOf(signerAddress); + let allowance = await uwp.allowance(signerAddress, uwe.address); + if(allowance.lt(bal)) { + let tx1 = await uwp.connect(deployer).approve(uwe.address, ethers.constants.MaxUint256, networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + } + let bal1 = await uwe.balanceOf(signerAddress); + console.log(`uwe balance before : ${ethers.utils.formatUnits(bal1)}`); + console.log(`depositing ${ethers.utils.formatUnits(bal)} uwp`) + let tx2 = await uwe.connect(deployer).deposit(bal, signerAddress, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + let bal2 = await uwe.balanceOf(signerAddress); + console.log(`uwe balance after : ${ethers.utils.formatUnits(bal2)}`); + console.log("Deposited UWP into UWE"); +} + +async function useDepositHelper() { + console.log("Depositing into new lock via DepositHelper"); + let tkn = (await ethers.getContractAt(artifacts.MockERC20.abi, DAI_ADDRESS)) as MockErc20; + let dec = 18; + let depositAmount = ONE_ETHER.mul(1000); + let bal = await tkn.balanceOf(signerAddress); + if(bal.lt(depositAmount)) { + console.log(`insufficient balance. depositing ${ethers.utils.formatUnits(depositAmount,dec)} have ${ethers.utils.formatUnits(bal,dec)}`); + return; + } + let allowance = await tkn.allowance(signerAddress, depositHelper.address); + if(allowance.lt(depositAmount)) { + let tx1 = await tkn.connect(deployer).approve(depositHelper.address, ethers.constants.MaxUint256, networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + } + let expiry = (await provider.getBlock('latest')).timestamp + 60*60*24*365*4; // 4 years from now + let tx2 = await depositHelper.connect(deployer).depositAndLock(tkn.address, depositAmount, expiry, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + let bal2 = await underwritingLocker.balanceOf(signerAddress); + let lockID = await underwritingLocker.tokenOfOwnerByIndex(signerAddress, bal2.sub(1)); + let lock = await underwritingLocker.locks(lockID); + console.log(`created lockID=${lockID.toNumber()}. uwe=${ethers.utils.formatUnits(lock.amount)} expiry=${lock.end}`); + console.log("Deposited into new lock via DepositHelper"); +} + +async function useDepositHelper2() { + console.log("Depositing into existing lock via DepositHelper"); + console.log(`time: ${new Date().toLocaleString()}`); + let lockID = 1; + let lockBefore = await underwritingLocker.locks(lockID); + console.log(`Lock starting with ${ethers.utils.formatUnits(lockBefore.amount)} UWE`); + + let uwpBal = await uwp.balanceOf(signerAddress); + if(uwpBal.gt(0)) { + console.log("Depositing UWP"); + let uwpAllow = await uwp.allowance(signerAddress, depositHelper.address); + if(uwpAllow.lt(uwpBal)) { + let tx1 = await uwp.connect(deployer).approve(depositHelper.address, ethers.constants.MaxUint256, networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + } + let tx2 = await depositHelper.connect(deployer).depositIntoLock(uwp.address, uwpBal, lockID, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + } + + let uweBal = await uwe.balanceOf(signerAddress); + if(uweBal.gt(0)) { + console.log("Depositing UWE"); + let uweAllow = await uwe.allowance(signerAddress, depositHelper.address); + if(uweAllow.lt(uweBal)) { + let tx1 = await uwe.connect(deployer).approve(depositHelper.address, ethers.constants.MaxUint256, networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + } + let tx2 = await depositHelper.connect(deployer).depositIntoLock(uwe.address, uweBal, lockID, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + } + + let deposits = [ + { symbol: "USDC", amount: ONE_USDC.mul(1000), address: USDC_ADDRESS, decimals: 6 }, + { symbol: "DAI", amount: ONE_ETHER.mul(1000), address: DAI_ADDRESS, decimals: 18 }, + { symbol: "USDT", amount: ONE_USDC.mul(1000), address: USDT_ADDRESS, decimals: 6 }, + { symbol: "FRAX", amount: ONE_ETHER.mul(1000), address: FRAX_ADDRESS, decimals: 18 }, + { symbol: "WBTC", amount: ONE_WBTC.div(10), address: WBTC_ADDRESS, decimals: 8 }, + { symbol: "WETH", amount: ONE_ETHER.div(1), address: WETH_ADDRESS, decimals: 18 }, + { symbol: "NEAR", amount: ONE_NEAR.mul(250), address: NEAR_ADDRESS, decimals: 24 }, + { symbol: "SOLACE", amount: ONE_ETHER.mul(10000), address: SOLACE_ADDRESS, decimals: 18 }, + { symbol: "AURORA", amount: ONE_ETHER.mul(200), address: AURORA_ADDRESS, decimals: 18 }, + { symbol: "PLY", amount: ONE_ETHER.mul(100000), address: PLY_ADDRESS, decimals: 18 }, + { symbol: "BSTN", amount: ONE_ETHER.mul(100000), address: BSTN_ADDRESS, decimals: 18 }, + { symbol: "BBT", amount: ONE_ETHER.mul(100000), address: BBT_ADDRESS, decimals: 18 }, + { symbol: "TRI", amount: ONE_ETHER.mul(10000), address: TRI_ADDRESS, decimals: 18 }, + { symbol: "VWAVE", amount: ONE_ETHER.mul(50), address: VWAVE_ADDRESS, decimals: 18 }, + ]; + let index = Math.floor(Math.random() * deposits.length); + let deposit = deposits[index]; + + console.log(`Depositing ${deposit.symbol}`); + let tkn = (await ethers.getContractAt(artifacts.MockERC20.abi, deposit.address)) as MockErc20; + let depositAmount = deposit.amount; + let dec = deposit.decimals; + let bal = await tkn.balanceOf(signerAddress); + if(bal.lt(depositAmount)) { + console.log(`insufficient balance. depositing ${ethers.utils.formatUnits(depositAmount,dec)} have ${ethers.utils.formatUnits(bal,dec)}`); + return; + } + let allowance = await tkn.allowance(signerAddress, depositHelper.address); + if(allowance.lt(depositAmount)) { + let tx1 = await tkn.connect(deployer).approve(depositHelper.address, ethers.constants.MaxUint256, networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + } + let tx2 = await depositHelper.connect(deployer).depositIntoLock(tkn.address, depositAmount, lockID, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + + let lockAfter = await underwritingLocker.locks(lockID); + console.log(`Lock ending with ${ethers.utils.formatUnits(lockAfter.amount)} UWE`); + + console.log("Deposited into existing lock via DepositHelper"); +} + +async function withdrawFromLocks() { + console.log("Withdrawing from locks"); + let bal = (await underwritingLocker.balanceOf(signerAddress)).toNumber(); + let lockIDs = []; + for(let i = 0; i < bal; ++i) { + lockIDs.push((await underwritingLocker.tokenOfOwnerByIndex(signerAddress, i)).toNumber()); + } + console.log(`signer has ${bal} locks: ${lockIDs}`); + console.log(`starting uwe balance: ${ethers.utils.formatUnits(await uwe.balanceOf(signerAddress))}`) + for(let i = 0; i < bal; ++i) { + let lockID = lockIDs[i]; + let lock = await underwritingLocker.locks(lockID); + let amountOut = await underwritingLocker.getWithdrawAmount(lockID); + console.log(`withdrawing from lock ${lockID}. uwe=${ethers.utils.formatUnits(lock.amount)} end=${(new Date(lock.end.toNumber()*1000)).toUTCString()} amountOut=${ethers.utils.formatUnits(amountOut)}`); + let tx = await underwritingLocker.connect(deployer).withdraw(lockID, signerAddress, {...networkSettings.overrides, gasLimit:300000}); + await tx.wait(networkSettings.confirmations); + } + console.log(`end uwe balance: ${ethers.utils.formatUnits(await uwe.balanceOf(signerAddress))}`) + console.log("Withdrew from locks"); +} + +async function withdrawFromUwe() { + console.log("Redeeming UWE"); + let bal1p = await uwp.balanceOf(signerAddress); + let bal1e = await uwe.balanceOf(signerAddress); + console.log('before'); + console.log(`uwp balance: ${ethers.utils.formatUnits(bal1p)}`) + console.log(`uwe balance: ${ethers.utils.formatUnits(bal1e)}`) + let tx = await uwe.connect(deployer).withdraw(bal1e, signerAddress, networkSettings.overrides); + await tx.wait(networkSettings.confirmations); + let bal2p = await uwp.balanceOf(signerAddress); + let bal2e = await uwe.balanceOf(signerAddress); + console.log('after'); + console.log(`uwp balance: ${ethers.utils.formatUnits(bal2p)}`) + console.log(`uwe balance: ${ethers.utils.formatUnits(bal2e)}`) + console.log("Redeemed UWE"); +} + +async function offerBribe() { + console.log("Offering Bribe"); + let depositAmount = ONE_ETHER.mul(1000); + let gaugeID = 1; + let aurora = await ethers.getContractAt(artifacts.ERC20.abi, AURORA_ADDRESS); + let tx1 = await aurora.connect(deployer).approve(bribeController.address, depositAmount, networkSettings.overrides); + await tx1.wait(networkSettings.confirmations); + let tx2 = await bribeController.connect(deployer).provideBribes([AURORA_ADDRESS], [depositAmount], gaugeID, networkSettings.overrides); + await tx2.wait(networkSettings.confirmations); + console.log("Offered Bribe"); +} +/* +async function acceptBribe() { + console.log("Accepting bribe"); + + const bribeTaker = new ethers.Wallet(JSON.parse(process.env.PRIVATE_KEYS || '[]')[1], provider); + const bribeTakerAddress = await bribeTaker.getAddress(); + console.log("Bribe taker:", bribeTakerAddress); + + let numLocks = await underwritingLocker.balanceOf(bribeTakerAddress); + if(numLocks.eq(0)) { + console.log("Creating lock for bribe taker"); + let dai = (await ethers.getContractAt(artifacts.MockERC20.abi, DAI_ADDRESS)) as MockErc20; + let depositAmount = ONE_ETHER.mul(1000); + let balance = await + let allowance = await dai.allowance(bribeTakerAddress, depositHelper.address); + if(allowance.lt(0)) + } + + let tx = await bribeController.connect(bribeTaker). + //function voteForBribe(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external override nonReentrant { + console.log("Accepted bribe"); +} +*/ function leftPad(s:string, l:number, f:string=' ') { while(s.length < l) s = `${f}${s}`; return s; } +function rightPad(s:string, l:number, f:string=' ') { + while(s.length < l) s = `${s}${f}`; + return s; +} + function logDate(date:Date) { console.log(Math.floor(date.valueOf()/1000)); console.log(date.toLocaleString()); diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts new file mode 100644 index 00000000..aa7da3be --- /dev/null +++ b/test/native/BribeController.test.ts @@ -0,0 +1,2052 @@ +import { ethers, waffle, upgrades } from "hardhat"; +const { deployContract, solidity } = waffle; +import { MockProvider } from "ethereum-waffle"; +const provider: MockProvider = waffle.provider; +import { BigNumber as BN, constants, BigNumberish, Wallet, ContractTransaction } from "ethers"; +import chai from "chai"; +const { expect } = chai; +chai.use(solidity); +import { import_artifacts, ArtifactImports } from "../utilities/artifact_importer"; +import { UnderwritingLocker, UnderwritingLockVoting, Registry, MockErc20PermitWithBurn, GaugeController, BribeController } from "../../typechain"; +import { expectDeployed } from "../utilities/expectDeployed"; +import { expectClose } from "../utilities/math"; + +/******************* + GLOBAL CONSTANTS +*******************/ +const ZERO = BN.from("0"); +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +const ONE_ETHER = BN.from("1000000000000000000"); +const ONE_MILLION_ETHER = ONE_ETHER.mul(1000000); +const ONE_YEAR = 31536000; // in seconds +const ONE_MONTH = ONE_YEAR / 12; +const ONE_WEEK = 604800; // in seconds +const DEPOSIT_AMOUNT = ONE_ETHER; +const BRIBE_AMOUNT = ONE_ETHER.mul(100); +const ONE_PERCENT = ONE_ETHER.div(100); +const ONE_HUNDRED_PERCENT = ONE_ETHER; +const CUSTOM_GAS_LIMIT = 6000000; + +describe("BribeController", function () { + const [deployer, governor, revenueRouter, voter1, voter2, voter3, voter4, delegate1, briber1, anon] = provider.getWallets(); + + /*************************** + VARIABLE DECLARATIONS + ***************************/ + let token: MockErc20PermitWithBurn; + let bribeToken1: MockErc20PermitWithBurn; + let bribeToken2: MockErc20PermitWithBurn; + let registry: Registry; + let underwritingLocker: UnderwritingLocker; + let gaugeController: GaugeController; + let voting: UnderwritingLockVoting; + let bribeController: BribeController; + let artifacts: ArtifactImports; + let snapshot: BN; + + before(async function () { + artifacts = await import_artifacts(); + snapshot = await provider.send("evm_snapshot", []); + await deployer.sendTransaction({to:deployer.address}); // for some reason this helps solidity-coverage + + // Deploy $UWE, and mint 1M $UWE to deployer + token = (await deployContract(deployer, artifacts.MockERC20PermitWithBurn, ["Underwriting Equity - Solace Native", "UWE", ONE_MILLION_ETHER, 18])) as MockErc20PermitWithBurn; + + // Deploy bribe tokens + bribeToken1 = (await deployContract(deployer, artifacts.MockERC20PermitWithBurn, ["BribeToken1", "bt1", ONE_MILLION_ETHER, 18])) as MockErc20PermitWithBurn; + bribeToken2 = (await deployContract(deployer, artifacts.MockERC20PermitWithBurn, ["BribeToken2", "bt2", ONE_MILLION_ETHER, 18])) as MockErc20PermitWithBurn; + + // Deploy registry + registry = (await deployContract(deployer, artifacts.Registry, [governor.address])) as Registry; + }); + + after(async function () { + await provider.send("evm_revert", [snapshot]); + }); + + describe("deployment", function () { + it("reverts if zero address governance", async function () { + await expect(deployContract(deployer, artifacts.BribeController, [ZERO_ADDRESS, registry.address])).to.be.revertedWith("zero address governance"); + }); + it("reverts if zero address registry", async function () { + await expect(deployContract(deployer, artifacts.BribeController, [governor.address, ZERO_ADDRESS])).to.be.revertedWith('ZeroAddressInput("registry")'); + }); + it("reverts if zero address gaugeController in Registry", async function () { + await expect(deployContract(deployer, artifacts.BribeController, [governor.address, registry.address])).to.be.revertedWith('ZeroAddressInput("gaugeController")'); + await registry.connect(governor).set(["revenueRouter"], [revenueRouter.address]); + gaugeController = (await deployContract(deployer, artifacts.GaugeController, [governor.address, token.address])) as GaugeController; + await expectDeployed(gaugeController.address); + await registry.connect(governor).set(["gaugeController"], [gaugeController.address]); + }); + it("reverts if zero address underwritingLockVoting in Registry", async function () { + await expect(deployContract(deployer, artifacts.BribeController, [governor.address, registry.address])).to.be.revertedWith('ZeroAddressInput("underwritingLockVoting")'); + await registry.connect(governor).set(["uwe"], [token.address]); + underwritingLocker = (await deployContract(deployer, artifacts.UnderwritingLocker, [governor.address, registry.address])) as UnderwritingLocker; + await expectDeployed(underwritingLocker.address); + await registry.connect(governor).set(["underwritingLocker"], [underwritingLocker.address]); + voting = (await deployContract(deployer, artifacts.UnderwritingLockVoting, [governor.address, registry.address])) as UnderwritingLockVoting; + await expectDeployed(voting.address); + await registry.connect(governor).set(["underwritingLockVoting"], [voting.address]); + }); + it("deploys", async function () { + bribeController = (await deployContract(deployer, artifacts.BribeController, [governor.address, registry.address])) as BribeController; + await expectDeployed(bribeController.address); + }); + it("initializes properly", async function () { + expect(await bribeController.registry()).eq(registry.address); + expect(await bribeController.gaugeController()).eq(gaugeController.address); + expect(await bribeController.votingContract()).eq(voting.address); + expect(await bribeController.lastTimeBribesProcessed()).eq(await bribeController.getEpochStartTimestamp()); + expect(await bribeController.getBribeTokenWhitelist()).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]); + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(10000); + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(10000); + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]); + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]); + expect(await bribeController.getLifetimeProvidedBribes(briber1.address)).deep.eq([]); + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]); + expect(await bribeController.getVotesForGauge(1)).deep.eq([]); + expect(await bribeController.isBribingOpen()).eq(true); + }); + it("getEpochStartTimestamp gets current timestamp rounded down to a multiple of WEEK ", async function () { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + const EXPECTED_EPOCH_START_TIME = BN.from(CURRENT_TIME).div(ONE_WEEK).mul(ONE_WEEK) + expect(await bribeController.getEpochStartTimestamp()).eq(EXPECTED_EPOCH_START_TIME) + }); + it("getEpochEndTimestamp() == getEpochStartTimestamp() + ONE_WEEK ", async function () { + expect(await bribeController.getEpochEndTimestamp()).eq((await bribeController.getEpochStartTimestamp()).add(ONE_WEEK)) + }); + }); + + describe("governance", () => { + it("starts with the correct governor", async () => { + expect(await gaugeController.governance()).to.equal(governor.address); + }); + it("rejects setting new governance by non governor", async () => { + await expect(gaugeController.connect(voter1).setPendingGovernance(voter1.address)).to.be.revertedWith("!governance"); + }); + it("can set new governance", async () => { + let tx = await gaugeController.connect(governor).setPendingGovernance(deployer.address); + await expect(tx).to.emit(gaugeController, "GovernancePending").withArgs(deployer.address); + expect(await gaugeController.governance()).to.equal(governor.address); + expect(await gaugeController.pendingGovernance()).to.equal(deployer.address); + }); + it("rejects governance transfer by non governor", async () => { + await expect(gaugeController.connect(voter1).acceptGovernance()).to.be.revertedWith("!pending governance"); + }); + it("can transfer governance", async () => { + let tx = await gaugeController.connect(deployer).acceptGovernance(); + await expect(tx) + .to.emit(gaugeController, "GovernanceTransferred") + .withArgs(governor.address, deployer.address); + expect(await gaugeController.governance()).to.equal(deployer.address); + await gaugeController.connect(deployer).setPendingGovernance(governor.address); + await gaugeController.connect(governor).acceptGovernance(); + }); + }); + + describe("setRegistry", () => { + let registry2: Registry; + const RANDOM_ADDRESS_1 = ethers.Wallet.createRandom().connect(provider).address; + const RANDOM_ADDRESS_2 = ethers.Wallet.createRandom().connect(provider).address; + + before(async function () { + registry2 = (await deployContract(deployer, artifacts.Registry, [governor.address])) as Registry; + }); + it("reverts if not governor", async function () { + await expect(bribeController.connect(voter1).setRegistry(registry2.address)).to.be.revertedWith("!governance"); + }) + it("reverts if zero address registry", async function () { + await expect(bribeController.connect(governor).setRegistry(ZERO_ADDRESS)).to.be.revertedWith('ZeroAddressInput("registry")'); + }); + it("reverts if zero address gaugeController in Registry", async function () { + await expect(bribeController.connect(governor).setRegistry(registry2.address)).to.be.revertedWith('ZeroAddressInput("gaugeController")'); + await registry2.connect(governor).set(["gaugeController"], [RANDOM_ADDRESS_1]); + }); + it("reverts if zero address underwritingLockVoting in Registry", async function () { + await expect(bribeController.connect(governor).setRegistry(registry2.address)).to.be.revertedWith('ZeroAddressInput("underwritingLockVoting")'); + await registry2.connect(governor).set(["underwritingLockVoting"], [RANDOM_ADDRESS_2]); + }) + it("sets registry", async function () { + const tx = await bribeController.connect(governor).setRegistry(registry2.address); + await expect(tx).to.emit(bribeController, "RegistrySet").withArgs(registry2.address); + }); + it("copies Registry addresses to own state variables", async function () { + expect(await bribeController.registry()).eq(registry2.address); + expect(await bribeController.gaugeController()).eq(RANDOM_ADDRESS_1); + expect(await bribeController.votingContract()).eq(RANDOM_ADDRESS_2); + }); + after(async function () { + await bribeController.connect(governor).setRegistry(registry.address); + }); + }); + + describe("addBribeToken", () => { + it("non governor cannot add new bribe token", async () => { + await expect(bribeController.connect(voter1).addBribeToken(bribeToken1.address)).to.be.revertedWith("!governance"); + }); + it("can add new bribe token", async () => { + let tx = await bribeController.connect(governor).addBribeToken(bribeToken1.address); + await expect(tx).to.emit(bribeController, "BribeTokenAdded").withArgs(bribeToken1.address); + expect(await bribeController.getBribeTokenWhitelist()).deep.eq([bribeToken1.address]); + await bribeController.connect(governor).addBribeToken(bribeToken2.address); + expect(await bribeController.getBribeTokenWhitelist()).deep.eq([bribeToken1.address, bribeToken2.address]); + }); + }); + + describe("removeBribeToken", () => { + it("non governor cannot remove bribe token", async () => { + await expect(bribeController.connect(voter1).removeBribeToken(bribeToken1.address)).to.be.revertedWith("!governance"); + }); + it("cannot remove a token that has not been previously added as a bribe token", async () => { + await expect(bribeController.connect(governor).removeBribeToken(deployer.address)).to.be.revertedWith("BribeTokenNotAdded"); + }); + it("can remove bribe token", async () => { + let tx = await bribeController.connect(governor).removeBribeToken(bribeToken2.address); + await expect(tx).to.emit(bribeController, "BribeTokenRemoved").withArgs(bribeToken2.address); + expect(await bribeController.getBribeTokenWhitelist()).deep.eq([bribeToken1.address]); + await bribeController.connect(governor).addBribeToken(bribeToken2.address); + expect(await bribeController.getBribeTokenWhitelist()).deep.eq([bribeToken1.address, bribeToken2.address]); + }); + }); + + /********************* + INTENTION STATEMENT + *********************/ + // briber1 will provide 100 of bribeToken1 and 100 of bribeToken2 as a bribe for gauge 1 + // create gauge2 + + describe("provideBribe", () => { + it("will throw if bribeToken and bribeAmount arrays mismatched", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1], 1)).to.be.revertedWith("ArrayArgumentsLengthMismatch"); + }); + it("will throw if bribe for paused gauge", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1, 1], 0)).to.be.revertedWith("CannotBribeForInactiveGauge"); + }); + it("will throw if bribe for non-existent gauge", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1, 1], 1)).to.be.revertedWith("CannotBribeForNonExistentGauge"); + await gaugeController.connect(governor).addGauge("1", ONE_PERCENT); + await gaugeController.connect(governor).addGauge("2", ONE_PERCENT); + }); + it("will throw if bribe for non-whitelisted token", async () => { + await expect(bribeController.connect(briber1).provideBribes([token.address, bribeToken2.address], [1, 1], 1)).to.be.revertedWith("CannotBribeWithNonWhitelistedToken"); + }); + it("can provide bribe", async () => { + const GAUGE_ID = BN.from("1") + await bribeToken1.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(100000)); + await bribeToken2.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(100000)); + await bribeToken1.connect(briber1).approve(bribeController.address, constants.MaxUint256); + await bribeToken2.connect(briber1).approve(bribeController.address, constants.MaxUint256); + + const OLD_BRIBER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(briber1.address) + const OLD_BRIBER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(briber1.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const tx = await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], GAUGE_ID) + await expect(tx).to.emit(bribeController, "BribeProvided").withArgs(briber1.address, GAUGE_ID, bribeToken1.address, BRIBE_AMOUNT); + await expect(tx).to.emit(bribeController, "BribeProvided").withArgs(briber1.address, GAUGE_ID, bribeToken2.address, BRIBE_AMOUNT); + + const NEW_BRIBER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(briber1.address) + const NEW_BRIBER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(briber1.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const BRIBER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_BRIBER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBER_BALANCE_BRIBE_TOKEN_1) + const BRIBER_BALANCE_BRIBE_TOKEN_2_CHANGE = NEW_BRIBER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBER_BALANCE_BRIBE_TOKEN_2) + const BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2_CHANGE = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + expect(BRIBER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) + expect(BRIBER_BALANCE_BRIBE_TOKEN_2_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) + expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT) + expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2_CHANGE).eq(BRIBE_AMOUNT) + + const lifetimeBribes = await bribeController.getLifetimeProvidedBribes(briber1.address); + expect(lifetimeBribes[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(lifetimeBribes[1].bribeAmount).eq(BRIBE_AMOUNT) + expect(lifetimeBribes[0].bribeToken).eq(bribeToken1.address) + expect(lifetimeBribes[1].bribeToken).eq(bribeToken2.address) + + const bribes = await bribeController.getProvidedBribesForGauge(1); + expect(bribes[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes[1].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes[0].bribeToken).eq(bribeToken1.address) + expect(bribes[1].bribeToken).eq(bribeToken2.address) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([GAUGE_ID]); + }); + it("claimBribes throws if no bribes to claim", async () => { + await expect(bribeController.connect(voter1).claimBribes()).to.be.revertedWith("NoClaimableBribes"); + }); + }); + + /******************* + STATE SUMMARY + *******************/ + /** + * gaugeID 1 => 100 of bribeToken1 and 100 and bribeToken2 provided for the current epoch + * gaugeID 2 => No bribes + */ + + /********************* + INTENTION STATEMENT + *********************/ + /** + * voter1 will create lockID 1, and make allocate votePowerBPS: + * gaugeID 1 => 1000 + * gaugeID 2 => 9000 + */ + + describe("voteForBribe", () => { + before(async function () { + await gaugeController.connect(governor).addVotingContract(voting.address) + await voting.connect(voter1).setDelegate(delegate1.address); + }); + it("will throw if called by non-lock owner or delegate", async () => { + await expect(bribeController.connect(briber1).voteForBribe(voter1.address, 1, 10000)).to.be.revertedWith("NotOwnerNorDelegate"); + }); + it("will throw if voter has no locks", async () => { + await expect(bribeController.connect(voter1).voteForBribe(voter1.address, 1, 10000)).to.be.revertedWith("VoterHasNoLocks"); + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await token.connect(deployer).approve(underwritingLocker.address, constants.MaxUint256); + await underwritingLocker.connect(deployer).createLock(voter1.address, DEPOSIT_AMOUNT, CURRENT_TIME + 4 * ONE_YEAR); + }); + it("will throw if voteForBribe for gauge with no bribe", async () => { + await expect(bribeController.connect(voter1).voteForBribe(voter1.address, 2, 10000)).to.be.revertedWith("NoBribesForSelectedGauge"); + }); + it("will throw if bribeController not set in voting contract", async () => { + await expect(bribeController.connect(voter1).voteForBribe(voter1.address, 1, 10000)).to.be.revertedWith("NotOwnerNorDelegate"); + }); + it("will throw if voteForBribe for more than unused votepower", async () => { + await voting.connect(voter1).vote(voter1.address, 2, 9000); + await registry.connect(governor).set(["bribeController"], [bribeController.address]); + await voting.connect(governor).setBribeController(); + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(1000); + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000); + await expect(bribeController.connect(voter1).voteForBribe(voter1.address, 1, 10000)).to.be.revertedWith("TotalVotePowerBPSOver10000"); + }); + it("can voteForBribe", async () => { + const tx = await bribeController.connect(voter1).voteForBribe(voter1.address, 1, 1000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter1.address, 1, 1000); + const vote = await bribeController.getVotesForVoter(voter1.address); + expect(vote[0].gaugeID).eq(1) + expect(vote[0].votePowerBPS).eq(1000) + const voteForBribe = await bribeController.getVotesForGauge(1); + expect(voteForBribe[0].voter).eq(voter1.address) + expect(voteForBribe[0].votePowerBPS).eq(1000) + }); + it("claimForBribe immediately after vote has no token transfer", async () => { + const tx = await bribeController.connect(voter1).claimBribes(); + await expect(tx).to.not.emit(bribeController, "BribeClaimed"); + }) + }); + + /******************* + STATE SUMMARY + *******************/ + /** + * Vote state: + * voter1 has lockID 1, and allocated votes + * gaugeID 1 => 1000, gaugeID 2 => 9000 + * + * Bribe state: + * gaugeID 1 => 100 of bribeToken1 and 100 and bribeToken2 provided for the current epoch + * gaugeID 2 => No bribes + */ + + describe("processBribes", () => { + before(async function () { + await underwritingLocker.connect(governor).setVotingContract() + await gaugeController.connect(governor).addTokenholder(underwritingLocker.address) + }); + it("will throw if called in same epoch as contract deployment", async () => { + await expect(bribeController.connect(governor).processBribes()).to.be.revertedWith("BribesAlreadyProcessed"); + }); + it("will throw in next epoch if gauge weights not yet updated", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await expect(bribeController.connect(governor).processBribes()).to.be.revertedWith("LastEpochPremiumsNotCharged"); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + }); + it("will throw in next epoch if premiums not yet charged", async () => { + await expect(bribeController.connect(governor).processBribes()).to.be.revertedWith("LastEpochPremiumsNotCharged"); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) + }); + it("cannot vote or remove vote in next epoch, before bribes processed", async () => { + await expect(bribeController.connect(voter1).voteForBribe(voter1.address, 1, 1000)).to.be.revertedWith("LastEpochBribesNotProcessed"); + await expect(bribeController.connect(voter1).removeVoteForBribe(voter1.address, 1)).to.be.revertedWith("LastEpochBribesNotProcessed"); + }); + it("cannot provide bribe, before bribes processed", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address], [1], 1)).to.be.revertedWith("LastEpochBribesNotProcessed"); + }); + it("anon can process bribes", async () => { + const EPOCH_START_TIMESTAMP = await bribeController.getEpochStartTimestamp(); + const tx = await bribeController.connect(anon).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + await expect(tx).to.emit(bribeController, "BribesProcessed").withArgs(EPOCH_START_TIMESTAMP); + expect(await bribeController.lastTimeBribesProcessed()).eq(EPOCH_START_TIMESTAMP); + expect(await bribeController.isBribingOpen()).eq(true); + }); + it("Mappings storing votes and provided bribes should be empty after bribes processing", async () => { + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000) + }) + }); + + describe("claimBribes", () => { + it("Should be able to see claimable bribes", async () => { + const bribes = await bribeController.getClaimableBribes(voter1.address); + expect(bribes[0].bribeToken).eq(bribeToken1.address) + expect(bribes[1].bribeToken).eq(bribeToken2.address) + expect(bribes[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes[1].bribeAmount).eq(BRIBE_AMOUNT) + }) + it("can claimBribes", async () => { + const OLD_VOTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const tx = await bribeController.connect(voter1).claimBribes(); + await expect(tx).to.emit(bribeController, "BribeClaimed").withArgs(voter1.address, bribeToken1.address, BRIBE_AMOUNT); + await expect(tx).to.emit(bribeController, "BribeClaimed").withArgs(voter1.address, bribeToken2.address, BRIBE_AMOUNT); + + const NEW_VOTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const BRIBER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_VOTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_BALANCE_BRIBE_TOKEN_1) + const BRIBER_BALANCE_BRIBE_TOKEN_2_CHANGE = NEW_VOTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_BALANCE_BRIBE_TOKEN_2) + const BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2_CHANGE = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + expect(BRIBER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT) + expect(BRIBER_BALANCE_BRIBE_TOKEN_2_CHANGE).eq(BRIBE_AMOUNT) + expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) + expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) + expect(await bribeController.getClaimableBribes(briber1.address)).deep.eq([]); + }) + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1. + * voter1 will initially vote for gaugeID 1, then remove the vote. + * We will vary the epoch length to 3 weeks here. + * We expect that all bribe tokens will be routed to the revenue router + */ + + describe("removeVoteForBribe scenario", () => { + before(async function () { + await gaugeController.connect(governor).setEpochLengthInWeeks(3); + await bribeController.connect(briber1).provideBribes([bribeToken1.address], [BRIBE_AMOUNT], 1) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1")]); + const bribes = await bribeController.getProvidedBribesForGauge(1); + expect(bribes[0].bribeToken).eq(bribeToken1.address) + expect(bribes[0].bribeAmount).eq(BRIBE_AMOUNT) + }); + it("throws if called by non voter or delegate", async () => { + await expect(bribeController.connect(briber1.address).removeVoteForBribe(voter1.address, 1)).to.be.revertedWith("NotOwnerNorDelegate"); + }) + it("throws if attempt to remove vote for gauge without bribe", async () => { + await expect(bribeController.connect(voter1.address).removeVoteForBribe(voter1.address, 2)).to.be.revertedWith("NoBribesForSelectedGauge"); + }) + it("throws if remove inexistent vote", async () => { + await expect(bribeController.connect(voter1.address).removeVoteForBribe(voter1.address, 1)).to.be.revertedWith("EnumerableMap: nonexistent key"); + }) + it("if gauge paused, cannot vote", async () => { + await gaugeController.connect(governor).pauseGauge(1); + await expect(bribeController.connect(voter1.address).voteForBribe(voter1.address, 1, 1000)).to.be.revertedWith("GaugeIDPaused"); + await gaugeController.connect(governor).unpauseGauge(1); + await bribeController.connect(voter1).voteForBribe(voter1.address, 1, 1000); + }) + it("if gauge paused, delegate can remove vote", async () => { + await gaugeController.connect(governor).pauseGauge(1); + const tx = await bribeController.connect(delegate1).removeVoteForBribe(voter1.address, 1); + await expect(tx).to.emit(bribeController, "VoteForBribeRemoved").withArgs(voter1.address, 1); + }) + it("cannot process bribes before next epoch", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await expect(bribeController.connect(governor.address).processBribes()).to.be.reverted; + }) + it("after processing bribes, all bribes should stay on the bribing contract, and paused gauge does not impact this", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + 2 * ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) + + const OLD_VOTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + await bribeController.connect(anon).processBribes({gasLimit: CUSTOM_GAS_LIMIT}) + + const NEW_VOTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const VOTER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_VOTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_BALANCE_BRIBE_TOKEN_1) + const BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1); + + expect(VOTER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(0) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(BRIBE_AMOUNT) + expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(0) + expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(1000) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000) + }) + it("rescueTokens cannot be called by non-governance", async () => { + await expect(bribeController.connect(voter1).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address)).to.be.revertedWith("!governance"); + }) + it("rescueTokens can be called", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + + const tx = await bribeController.connect(governor).rescueTokens([bribeToken1.address], revenueRouter.address) + await expect(tx).to.emit(bribeController, "TokenRescued").withArgs(bribeToken1.address, revenueRouter.address, OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(BRIBE_AMOUNT.mul(-1)) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(BRIBE_AMOUNT) + }) + it("check for reset of epoch length", async function () { + await gaugeController.connect(governor).setEpochLengthInWeeks(1) + const LAST_CHECKPOINT_TIMESTAMP = await bribeController.lastTimeBribesProcessed(); + const EPOCH_START_TIMESTAMP = await bribeController.getEpochStartTimestamp(); + if(EPOCH_START_TIMESTAMP.gt(LAST_CHECKPOINT_TIMESTAMP)) { + await gaugeController.connect(anon).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + await voting.connect(anon).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + await bribeController.connect(anon).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + } + expect(await bribeController.isBribingOpen()).eq(true); + }); + after(async function () { + await gaugeController.connect(governor).unpauseGauge(1); + }); + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * Create gaugeID 3 + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1 and 100 of bribeToken2. + * briber1 will provide bribe for gaugeID 3 with 100 of bribeToken1 and 100 of bribeToken2. + * voter2 and voter3 will create an equivalent lock to voter1 + * voter1 will voteForBribe for gaugeID 1 with 5% and gaugeID 2 with 5% + * voter2 will voteForBribe for gaugeID 1 with 50% and gaugeID 2 with 50% + * voter3 will initially mirror voter2 voteForBribes, but then remove their bribe + */ + + describe("voteForMultipleBribes and removeVotesForMultipleBribes scenario", () => { + before(async function () { + await gaugeController.connect(governor).addGauge("3", ONE_PERCENT); + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 1) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 3) + const lock = await underwritingLocker.locks(1); + await underwritingLocker.connect(deployer).createLock(voter2.address, lock.amount, lock.end); + await underwritingLocker.connect(deployer).createLock(voter3.address, lock.amount, lock.end); + expect(await voting.getVotePower(voter1.address)).eq(await voting.getVotePower(voter2.address)) + expect(await voting.getVotePower(voter1.address)).eq(await voting.getVotePower(voter3.address)) + expect(await voting.getVotePower(voter2.address)).eq(await voting.getVotePower(voter3.address)) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1"), BN.from("3")]) + const bribes1 = await bribeController.getProvidedBribesForGauge(1); + expect(bribes1[0].bribeToken).eq(bribeToken1.address) + expect(bribes1[1].bribeToken).eq(bribeToken2.address) + expect(bribes1[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes1[1].bribeAmount).eq(BRIBE_AMOUNT) + const bribes3 = await bribeController.getProvidedBribesForGauge(3); + expect(bribes3[0].bribeToken).eq(bribeToken1.address) + expect(bribes3[1].bribeToken).eq(bribeToken2.address) + expect(bribes3[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes3[1].bribeAmount).eq(BRIBE_AMOUNT) + await voting.connect(voter2).setDelegate(delegate1.address) + const lifetimeBribes1 = await bribeController.getLifetimeProvidedBribes(briber1.address); + expect(lifetimeBribes1[0].bribeToken).eq(bribeToken1.address) + expect(lifetimeBribes1[1].bribeToken).eq(bribeToken2.address) + expect(lifetimeBribes1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(4)) + expect(lifetimeBribes1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(3)) + }); + it("will throw if called by non-lock owner or delegate", async () => { + await expect(bribeController.connect(briber1).voteForMultipleBribes(voter1.address, [1, 2], [500, 500])).to.be.revertedWith("NotOwnerNorDelegate"); + await expect(bribeController.connect(briber1).removeVotesForMultipleBribes(voter1.address, [1, 2])).to.be.revertedWith("NotOwnerNorDelegate"); + }); + it("will throw if voter has no locks", async () => { + await expect(bribeController.connect(briber1).voteForMultipleBribes(briber1.address, [1, 2], [500, 500])).to.be.revertedWith("VoterHasNoLocks"); + }); + it("will throw if voteForBribe for gauge with no bribe", async () => { + await expect(bribeController.connect(voter1).voteForMultipleBribes(voter1.address, [1, 4], [500, 500])).to.be.revertedWith("NoBribesForSelectedGauge"); + }); + it("will throw if voteForBribe for more than unused votepower", async () => { + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(1000); + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000); + await expect(bribeController.connect(voter1).voteForMultipleBribes(voter1.address, [1, 3], [500, 501])).to.be.revertedWith("TotalVotePowerBPSOver10000"); + }); + it("can voteForMultipleBribes", async () => { + const tx1 = await bribeController.connect(voter1).voteForMultipleBribes(voter1.address, [1, 3], [500, 500]); + await expect(tx1).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter1.address, 1, 500); + await expect(tx1).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter1.address, 3, 500); + const tx2 = await bribeController.connect(delegate1).voteForMultipleBribes(voter2.address, [1, 3], [500, 500]); + await expect(tx2).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter2.address, 1, 500); + await expect(tx2).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter2.address, 3, 500); + const tx3 = await bribeController.connect(delegate1).voteForMultipleBribes(voter2.address, [1, 3], [5000, 5000]); + await expect(tx3).to.emit(bribeController, "VoteForBribeChanged").withArgs(voter2.address, 1, 5000, 500); + await expect(tx3).to.emit(bribeController, "VoteForBribeChanged").withArgs(voter2.address, 3, 5000, 500); + const tx4 = await bribeController.connect(voter3).voteForMultipleBribes(voter3.address, [1, 3], [5000, 5000]); + await expect(tx4).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter3.address, 1, 5000); + await expect(tx4).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter3.address, 3, 5000); + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(0) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(0) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(0) + }); + it("can removeVotesForMultipleBribes", async () => { + const tx = await bribeController.connect(voter3).removeVotesForMultipleBribes(voter3.address, [1, 3]); + await expect(tx).to.emit(bribeController, "VoteForBribeRemoved").withArgs(voter3.address, 1); + await expect(tx).to.emit(bribeController, "VoteForBribeRemoved").withArgs(voter3.address, 3); + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000) + }); + it("getVotesForGauge", async () => { + const votes1 = await bribeController.getVotesForGauge(1); + expect(votes1[0].voter).eq(voter1.address) + expect(votes1[1].voter).eq(voter2.address) + expect(votes1[0].votePowerBPS).eq(500) + expect(votes1[1].votePowerBPS).eq(5000) + const votes3 = await bribeController.getVotesForGauge(3); + expect(votes1[0].voter).eq(voter1.address) + expect(votes1[1].voter).eq(voter2.address) + expect(votes1[0].votePowerBPS).eq(500) + expect(votes1[1].votePowerBPS).eq(5000) + }) + it("getVotesForVoter", async () => { + const votes1 = await bribeController.getVotesForVoter(voter1.address); + expect(votes1[0].gaugeID).eq(1) + expect(votes1[1].gaugeID).eq(3) + expect(votes1[0].votePowerBPS).eq(500) + expect(votes1[1].votePowerBPS).eq(500) + const votes2 = await bribeController.getVotesForVoter(voter2.address); + expect(votes2[0].gaugeID).eq(1) + expect(votes2[1].gaugeID).eq(3) + expect(votes2[0].votePowerBPS).eq(5000) + expect(votes2[1].votePowerBPS).eq(5000) + expect(await bribeController.getVotesForVoter(voter3.address)).deep.eq([]) + }) + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + await bribeController.connect(governor).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(3)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter2.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForGauge(3)).deep.eq([]) + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000) + }) + it("getClaimableBribes", async () => { + const claim1 = await bribeController.getClaimableBribes(voter1.address); + expect(claim1[0].bribeToken).eq(bribeToken1.address) + expect(claim1[1].bribeToken).eq(bribeToken2.address) + expectClose(claim1[0].bribeAmount, BRIBE_AMOUNT.mul(2).mul(1).div(11), 1e3) + expectClose(claim1[1].bribeAmount, BRIBE_AMOUNT.mul(2).mul(1).div(11), 1e3) + const claim2 = await bribeController.getClaimableBribes(voter2.address); + expect(claim2[0].bribeToken).eq(bribeToken1.address) + expect(claim2[1].bribeToken).eq(bribeToken2.address) + expectClose(claim2[0].bribeAmount, BRIBE_AMOUNT.mul(2).mul(10).div(11), 1e3) + expectClose(claim2[1].bribeAmount, BRIBE_AMOUNT.mul(2).mul(10).div(11), 1e3) + expect(await bribeController.getClaimableBribes(voter3.address)).deep.eq([]) + }) + it("can claimBribes", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + const tx1 = await bribeController.connect(voter1).claimBribes(); + const tx2 = await bribeController.connect(voter2).claimBribes(); + await expect(tx1).to.emit(bribeController, "BribeClaimed"); + await expect(tx2).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expectClose(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1, BRIBE_AMOUNT.mul(2).mul(1).div(11), 1e3); + expectClose(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2, BRIBE_AMOUNT.mul(2).mul(1).div(11), 1e3); + expectClose(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1, BRIBE_AMOUNT.mul(2).mul(10).div(11), 1e3); + expectClose(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2, BRIBE_AMOUNT.mul(2).mul(10).div(11), 1e3); + expectClose(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1, BRIBE_AMOUNT.mul(-2), 1e4); + expectClose(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2, BRIBE_AMOUNT.mul(-2), 1e4); + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(await bribeController.getClaimableBribes(briber1.address)).deep.eq([]); + }) + it("rescueTokens cannot be called by non-governance", async () => { + await expect(bribeController.connect(voter1).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address)).to.be.revertedWith("!governance"); + }) + it("rescueTokens can be called", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const tx = await bribeController.connect(governor).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address) + await expect(tx).to.emit(bribeController, "TokenRescued").withArgs(bribeToken1.address, revenueRouter.address, OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1); + await expect(tx).to.emit(bribeController, "TokenRescued").withArgs(bribeToken1.address, revenueRouter.address, OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + }) + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * Create gauge4 with no bribes + * voter4 will create equivalent lock to voter3 + * voter3 and 4 has slightly more votePower than voter1 and , because voter3 did not get charged a premium in the last epoch + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1, 100 of bribeToken2 + * briber1 will provide bribe for gaugeID 2 with 200 of bribeToken1, 200 of bribeToken2 + * briber1 will provide bribe for gaugeID 3 with 100 of bribeToken1, 100 of bribeToken2 + * delegate1 will be the delegate for voter1, voter2, voter3 and voter4 + * delegate1 will vote voteForBribeForMultipleVoters 20% for gauge1, 50% for gauge2, 10% for gauge3, for voters 1, 2, 3 and 4 + * delegate1 will removeVotesForBribeForMultipleVoters for voters 3 and 4, for gauge3 only + */ + + describe("voteForBribeForMultipleVoters and removeVotesForBribeForMultipleVoters scenario", () => { + before(async function () { + const lock = await underwritingLocker.locks(3); + await underwritingLocker.connect(deployer).createLock(voter4.address, lock.amount, lock.end) + expect(await voting.getVotePower(voter4.address)).eq(await voting.getVotePower(voter3.address)) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 1) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT.mul(2), BRIBE_AMOUNT.mul(2)], 2) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 3) + await voting.connect(voter1).setDelegate(delegate1.address) + await voting.connect(voter2).setDelegate(delegate1.address) + await voting.connect(voter3).setDelegate(delegate1.address) + await voting.connect(voter4).setDelegate(delegate1.address) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1"), BN.from("2"), BN.from("3")]) + const bribes1 = await bribeController.getProvidedBribesForGauge(1); + expect(bribes1[0].bribeToken).eq(bribeToken1.address) + expect(bribes1[1].bribeToken).eq(bribeToken2.address) + expect(bribes1[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes1[1].bribeAmount).eq(BRIBE_AMOUNT) + const bribes2 = await bribeController.getProvidedBribesForGauge(2); + expect(bribes2[0].bribeToken).eq(bribeToken1.address) + expect(bribes2[1].bribeToken).eq(bribeToken2.address) + expect(bribes2[0].bribeAmount).eq(BRIBE_AMOUNT.mul(2)) + expect(bribes2[1].bribeAmount).eq(BRIBE_AMOUNT.mul(2)) + const bribes3 = await bribeController.getProvidedBribesForGauge(3); + expect(bribes3[0].bribeToken).eq(bribeToken1.address) + expect(bribes3[1].bribeToken).eq(bribeToken2.address) + expect(bribes3[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes3[1].bribeAmount).eq(BRIBE_AMOUNT) + const lifetimeBribes1 = await bribeController.getLifetimeProvidedBribes(briber1.address); + expect(lifetimeBribes1[0].bribeToken).eq(bribeToken1.address) + expect(lifetimeBribes1[1].bribeToken).eq(bribeToken2.address) + expect(lifetimeBribes1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(8)) + expect(lifetimeBribes1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(7)) + await gaugeController.connect(governor).addGauge("4", ONE_PERCENT) + }); + it("will throw if called by non-lock owner or delegate", async () => { + await expect(bribeController.connect(briber1).voteForBribeForMultipleVoters([voter1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3], [2000, 5000, 1000])).to.be.revertedWith("NotOwnerNorDelegate"); + await expect(bribeController.connect(briber1).removeVotesForBribeForMultipleVoters([voter1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3])).to.be.revertedWith("NotOwnerNorDelegate"); + }); + it("will throw if voter has no locks", async () => { + await expect(bribeController.connect(briber1).voteForBribeForMultipleVoters([briber1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3], [2000, 5000, 1000])).to.be.revertedWith("VoterHasNoLocks"); + }); + it("will throw if voteForBribe for gauge with no bribe", async () => { + await expect(bribeController.connect(delegate1).voteForBribeForMultipleVoters([voter1.address, voter2.address], [1, 4], [500, 500])).to.be.revertedWith("NoBribesForSelectedGauge"); + }); + it("will throw if voteForBribe for more than unused votepower", async () => { + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(0); + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000); + await expect(bribeController.connect(delegate1).voteForBribeForMultipleVoters([voter1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3], [2000, 5000, 1000])).to.be.revertedWith("TotalVotePowerBPSOver10000"); + }); + it("can voteForBribeForMultipleVoters", async () => { + await voting.connect(delegate1).removeVoteMultiple(voter1.address, [2]) + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(9000); + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(10000); + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(0); + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(10000); + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000); + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000); + expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(10000); + expect(await bribeController.getAvailableVotePowerBPS(voter4.address)).eq(10000); + const tx = await bribeController.connect(delegate1).voteForBribeForMultipleVoters([voter1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3], [2000, 5000, 1000]); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter1.address, 1, 2000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter2.address, 1, 2000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter3.address, 1, 2000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter4.address, 1, 2000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter1.address, 2, 5000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter2.address, 2, 5000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter3.address, 2, 5000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter4.address, 2, 5000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter1.address, 3, 1000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter2.address, 3, 1000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter3.address, 3, 1000); + await expect(tx).to.emit(bribeController, "VoteForBribeAdded").withArgs(voter4.address, 3, 1000); + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(2000) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(2000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(2000) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(2000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(2000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(2000) + expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(2000) + expect(await bribeController.getAvailableVotePowerBPS(voter4.address)).eq(2000) + }); + it("can removeVotesForMultipleBribes", async () => { + const tx = await bribeController.connect(delegate1).removeVotesForBribeForMultipleVoters([voter3.address, voter4.address], [3]); + await expect(tx).to.emit(bribeController, "VoteForBribeRemoved").withArgs(voter3.address, 3); + await expect(tx).to.emit(bribeController, "VoteForBribeRemoved").withArgs(voter4.address, 3); + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(3000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(3000) + expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(3000) + expect(await bribeController.getAvailableVotePowerBPS(voter4.address)).eq(3000) + }); + it("getVotesForGauge", async () => { + const votes1 = await bribeController.getVotesForGauge(1); + expect(votes1[0].voter).eq(voter1.address) + expect(votes1[1].voter).eq(voter2.address) + expect(votes1[2].voter).eq(voter3.address) + expect(votes1[3].voter).eq(voter4.address) + expect(votes1[0].votePowerBPS).eq(2000) + expect(votes1[1].votePowerBPS).eq(2000) + expect(votes1[2].votePowerBPS).eq(2000) + expect(votes1[3].votePowerBPS).eq(2000) + const votes2 = await bribeController.getVotesForGauge(2); + expect(votes2[0].voter).eq(voter1.address) + expect(votes2[1].voter).eq(voter2.address) + expect(votes2[2].voter).eq(voter3.address) + expect(votes2[3].voter).eq(voter4.address) + expect(votes2[0].votePowerBPS).eq(5000) + expect(votes2[1].votePowerBPS).eq(5000) + expect(votes2[2].votePowerBPS).eq(5000) + expect(votes2[3].votePowerBPS).eq(5000) + const votes3 = await bribeController.getVotesForGauge(3); + expect(votes3[0].voter).eq(voter1.address) + expect(votes3[1].voter).eq(voter2.address) + expect(votes3[0].votePowerBPS).eq(1000) + expect(votes3[1].votePowerBPS).eq(1000) + expect(votes3.length).eq(2) + }) + it("getVotesForVoter", async () => { + const votes1 = await bribeController.getVotesForVoter(voter1.address); + expect(votes1[0].gaugeID).eq(1) + expect(votes1[1].gaugeID).eq(2) + expect(votes1[2].gaugeID).eq(3) + expect(votes1[0].votePowerBPS).eq(2000) + expect(votes1[1].votePowerBPS).eq(5000) + expect(votes1[2].votePowerBPS).eq(1000) + const votes2 = await bribeController.getVotesForVoter(voter2.address); + expect(votes2[0].gaugeID).eq(1) + expect(votes2[1].gaugeID).eq(2) + expect(votes2[2].gaugeID).eq(3) + expect(votes2[0].votePowerBPS).eq(2000) + expect(votes2[1].votePowerBPS).eq(5000) + expect(votes2[2].votePowerBPS).eq(1000) + const votes3 = await bribeController.getVotesForVoter(voter3.address); + expect(votes3[0].gaugeID).eq(1) + expect(votes3[1].gaugeID).eq(2) + expect(votes3[0].votePowerBPS).eq(2000) + expect(votes3[1].votePowerBPS).eq(5000) + expect(votes3.length).eq(2) + const votes4 = await bribeController.getVotesForVoter(voter3.address); + expect(votes4[0].gaugeID).eq(1) + expect(votes4[1].gaugeID).eq(2) + expect(votes4[0].votePowerBPS).eq(2000) + expect(votes4[1].votePowerBPS).eq(5000) + expect(votes4.length).eq(2) + }) + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + await bribeController.connect(governor).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(2)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(3)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter2.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter3.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter4.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForGauge(2)).deep.eq([]) + expect(await bribeController.getVotesForGauge(3)).deep.eq([]) + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(2000) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(2000) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(3000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(3000) + expect(await bribeController.getAvailableVotePowerBPS(voter4.address)).eq(10000) + }) + it("getClaimableBribes", async () => { + const claim1 = await bribeController.getClaimableBribes(voter1.address); + expect(claim1[0].bribeToken).eq(bribeToken1.address) + expect(claim1[1].bribeToken).eq(bribeToken2.address) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(claim1[0].bribeAmount), ONE_HUNDRED_PERCENT, 1e14) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(claim1[1].bribeAmount), ONE_HUNDRED_PERCENT, 1e14) + const claim2 = await bribeController.getClaimableBribes(voter2.address); + expect(claim2[0].bribeToken).eq(bribeToken1.address) + expect(claim2[1].bribeToken).eq(bribeToken2.address) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(claim2[0].bribeAmount), ONE_HUNDRED_PERCENT, 1e14) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(claim2[1].bribeAmount), ONE_HUNDRED_PERCENT, 1e14) + const claim3 = await bribeController.getClaimableBribes(voter3.address); + expect(claim3[0].bribeToken).eq(bribeToken1.address) + expect(claim3[1].bribeToken).eq(bribeToken2.address) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(claim3[0].bribeAmount), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(claim3[1].bribeAmount), ONE_HUNDRED_PERCENT, 1e15) + const claim4 = await bribeController.getClaimableBribes(voter4.address); + expect(claim4[0].bribeToken).eq(bribeToken1.address) + expect(claim4[1].bribeToken).eq(bribeToken2.address) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(claim4[0].bribeAmount), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(claim4[1].bribeAmount), ONE_HUNDRED_PERCENT, 1e15) + }) + it("can claimBribes for voter1", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const tx = await bribeController.connect(voter1).claimBribes(); + await expect(tx).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2).eq(0) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(5).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(5).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]); + }) + it("can claimBribes for voter2", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const tx = await bribeController.connect(voter2).claimBribes(); + await expect(tx).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(5).div(16)).div(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2).eq(0) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(5).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(5).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(await bribeController.getClaimableBribes(voter2.address)).deep.eq([]); + }) + it("can claimBribes for voter3", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const tx = await bribeController.connect(voter3).claimBribes(); + await expect(tx).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2).eq(0) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(3).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(3).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(await bribeController.getClaimableBribes(voter3.address)).deep.eq([]); + }) + it("can claimBribes for voter4", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const tx = await bribeController.connect(voter4).claimBribes(); + await expect(tx).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(4).mul(3).div(16)).div(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(3).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1), ONE_HUNDRED_PERCENT, 1e15) + expectClose(ONE_HUNDRED_PERCENT.mul(BRIBE_AMOUNT.mul(-4).mul(3).div(16)).div(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2), ONE_HUNDRED_PERCENT, 1e15) + expect(await bribeController.getClaimableBribes(voter4.address)).deep.eq([]); + }) + it("voters cannot claim bribes again", async () => { + await expect(bribeController.connect(voter1).claimBribes()).to.be.revertedWith("NoClaimableBribes"); + await expect(bribeController.connect(voter2).claimBribes()).to.be.revertedWith("NoClaimableBribes"); + await expect(bribeController.connect(voter3).claimBribes()).to.be.revertedWith("NoClaimableBribes"); + await expect(bribeController.connect(voter4).claimBribes()).to.be.revertedWith("NoClaimableBribes"); + }); + after(async function () { + await bribeController.connect(governor).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address) + }); + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1, 100 of bribeToken2 + * briber1 will provide bribe for gaugeID 2 with 100 of bribeToken1, 100 of bribeToken2 + * briber1 will provide bribe for gaugeID 3 with 100 of bribeToken1, 100 of bribeToken2 + * delegate1 will vote voteForBribeForMultipleVoters 20% for gauge1, 50% for gauge2, 10% for gauge3, for voters 1, 2, 3 and 4 + * delegate1 will removeVotesForBribeForMultipleVoters so that no votes are remaining. + */ + + describe("no votes scenario", () => { + before(async function () { + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 1) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 2) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 3) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1"), BN.from("2"), BN.from("3")]) + const bribes1 = await bribeController.getProvidedBribesForGauge(1); + expect(bribes1[0].bribeToken).eq(bribeToken1.address) + expect(bribes1[1].bribeToken).eq(bribeToken2.address) + expect(bribes1[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes1[1].bribeAmount).eq(BRIBE_AMOUNT) + const bribes2 = await bribeController.getProvidedBribesForGauge(2); + expect(bribes2[0].bribeToken).eq(bribeToken1.address) + expect(bribes2[1].bribeToken).eq(bribeToken2.address) + expect(bribes2[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes2[1].bribeAmount).eq(BRIBE_AMOUNT) + const bribes3 = await bribeController.getProvidedBribesForGauge(3); + expect(bribes3[0].bribeToken).eq(bribeToken1.address) + expect(bribes3[1].bribeToken).eq(bribeToken2.address) + expect(bribes3[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes3[1].bribeAmount).eq(BRIBE_AMOUNT) + const lifetimeBribes1 = await bribeController.getLifetimeProvidedBribes(briber1.address); + expect(lifetimeBribes1[0].bribeToken).eq(bribeToken1.address) + expect(lifetimeBribes1[1].bribeToken).eq(bribeToken2.address) + expect(lifetimeBribes1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(11)) + expect(lifetimeBribes1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(10)) + }); + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + await bribeController.connect(governor).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(BRIBE_AMOUNT.mul(3)) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(BRIBE_AMOUNT.mul(3)) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(2)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(3)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter2.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter3.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter4.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForGauge(2)).deep.eq([]) + expect(await bribeController.getVotesForGauge(3)).deep.eq([]) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter4.address)).eq(10000) + expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter2.address)).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter3.address)).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter4.address)).deep.eq([]); + }) + after(async function () { + await bribeController.connect(governor).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address) + }); + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1, 100 of bribeToken2 + * briber1 will provide bribe for gaugeID 2 with 100 of bribeToken1, 100 of bribeToken2 + * briber1 will provide bribe for gaugeID 3 with 100 of bribeToken1, 100 of bribeToken2 + * There are no voteForBribes + */ + + describe("all votes removed scenario", () => { + before(async function () { + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 1) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 2) + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 3) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1"), BN.from("2"), BN.from("3")]) + const bribes1 = await bribeController.getProvidedBribesForGauge(1); + expect(bribes1[0].bribeToken).eq(bribeToken1.address) + expect(bribes1[1].bribeToken).eq(bribeToken2.address) + expect(bribes1[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes1[1].bribeAmount).eq(BRIBE_AMOUNT) + const bribes2 = await bribeController.getProvidedBribesForGauge(2); + expect(bribes2[0].bribeToken).eq(bribeToken1.address) + expect(bribes2[1].bribeToken).eq(bribeToken2.address) + expect(bribes2[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes2[1].bribeAmount).eq(BRIBE_AMOUNT) + const bribes3 = await bribeController.getProvidedBribesForGauge(3); + expect(bribes3[0].bribeToken).eq(bribeToken1.address) + expect(bribes3[1].bribeToken).eq(bribeToken2.address) + expect(bribes3[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(bribes3[1].bribeAmount).eq(BRIBE_AMOUNT) + const lifetimeBribes1 = await bribeController.getLifetimeProvidedBribes(briber1.address); + expect(lifetimeBribes1[0].bribeToken).eq(bribeToken1.address) + expect(lifetimeBribes1[1].bribeToken).eq(bribeToken2.address) + expect(lifetimeBribes1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(14)) + expect(lifetimeBribes1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(13)) + await bribeController.connect(delegate1).voteForBribeForMultipleVoters([voter1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3], [2000, 5000, 1000]); + await bribeController.connect(delegate1).removeVotesForBribeForMultipleVoters([voter1.address, voter2.address, voter3.address, voter4.address], [1, 2, 3]); + }); + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + await bribeController.connect(governor).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter4.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter4.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_3_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_3_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_4_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_4_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_3_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_4_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(BRIBE_AMOUNT.mul(3)) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(BRIBE_AMOUNT.mul(3)) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(2)).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(3)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter2.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter3.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter4.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForGauge(2)).deep.eq([]) + expect(await bribeController.getVotesForGauge(3)).deep.eq([]) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter4.address)).eq(10000) + expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter2.address)).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter3.address)).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter4.address)).deep.eq([]); + }) + after(async function () { + await bribeController.connect(governor).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address) + }); + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1, 100 of bribeToken2 + * anon will provide bribe for gaugeID 1 with 100 of bribeToken1, 100 of bribeToken2 + * voter1 will vote with all available votepower for gaugeID 1 + */ + + describe("multiple bribers scenario", () => { + const GAUGE_ID = 1; + + before(async function () { + await bribeToken1.connect(briber1).transfer(anon.address, BRIBE_AMOUNT.mul(2)); + await bribeToken1.connect(anon).approve(bribeController.address, constants.MaxUint256); + await bribeToken2.connect(briber1).transfer(anon.address, BRIBE_AMOUNT.mul(2)); + await bribeToken2.connect(anon).approve(bribeController.address, constants.MaxUint256); + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], GAUGE_ID) + await bribeController.connect(anon).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], GAUGE_ID) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1")]) + const bribes1 = await bribeController.getProvidedBribesForGauge(GAUGE_ID); + expect(bribes1[0].bribeToken).eq(bribeToken1.address) + expect(bribes1[1].bribeToken).eq(bribeToken2.address) + expect(bribes1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(2)) + expect(bribes1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(2)) + }); + it("can vote", async () => { + const votePowerBPS = await bribeController.getAvailableVotePowerBPS(voter1.address); + await bribeController.connect(voter1).voteForBribe(voter1.address, GAUGE_ID, votePowerBPS); + }) + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + await bribeController.connect(anon).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter1.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(10000) + }); + it("getClaimableBribes", async () => { + const claim1 = await bribeController.getClaimableBribes(voter1.address); + expect(claim1[0].bribeToken).eq(bribeToken1.address) + expect(claim1[1].bribeToken).eq(bribeToken2.address) + expect(claim1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(2)) + expect(claim1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(2)) + }) + it("can claimBribes", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const tx1 = await bribeController.connect(voter1).claimBribes(); + await expect(tx1).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter1.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter1.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expectClose(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1, BRIBE_AMOUNT.mul(2), 1e3); + expectClose(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2, BRIBE_AMOUNT.mul(2), 1e3); + expectClose(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1, BRIBE_AMOUNT.mul(-2), 1e4); + expectClose(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2, BRIBE_AMOUNT.mul(-2), 1e4); + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(await bribeController.getClaimableBribes(briber1.address)).deep.eq([]); + }) + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * briber1 will provide bribe for gaugeID 1 with 100 of bribeToken1, 100 of bribeToken2. + * voter2 and voter3 (who had locks created about the same time) will vote with all available votepower for gaugeID 1. + * voter2 will mutate their vote afterwards via UnderwritingLockVoting. + */ + + describe("edge case - voteForBribe mutated through UnderwritingLockVoting in the same epoch", () => { + const GAUGE_ID = 1; + + before(async function () { + await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], GAUGE_ID) + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([BN.from("1")]) + const bribes1 = await bribeController.getProvidedBribesForGauge(GAUGE_ID); + expect(bribes1[0].bribeToken).eq(bribeToken1.address); + expect(bribes1[1].bribeToken).eq(bribeToken2.address); + expect(bribes1[0].bribeAmount).eq(BRIBE_AMOUNT.mul(1)); + expect(bribes1[1].bribeAmount).eq(BRIBE_AMOUNT.mul(1)); + }); + it("non-voting contract cannot call receiveVoteNotification", async () => { + await expect(bribeController.connect(anon).receiveVoteNotification(voter1.address, GAUGE_ID, 10000)).to.be.revertedWith("NotVotingContract"); + }) + it("voteForBribe mutation made via UnderwritingLockVoting reflected in BribeController", async () => { + // Initially voter2 and voter3 will vote will all available votepower for gaugeID 1. + const votePowerBPS1 = await bribeController.getAvailableVotePowerBPS(voter2.address); + const votePowerBPS2 = await bribeController.getAvailableVotePowerBPS(voter3.address); + expect(votePowerBPS1).eq(votePowerBPS2); + await bribeController.connect(voter2).voteForBribe(voter2.address, GAUGE_ID, votePowerBPS1); + await bribeController.connect(voter3).voteForBribe(voter3.address, GAUGE_ID, votePowerBPS2); + + // Now voter2 will mutate this vote via UnderwritingLockVoting. + await voting.connect(voter2).vote(voter2.address, GAUGE_ID, votePowerBPS1.div(2)) + + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(votePowerBPS1.div(2)) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(votePowerBPS1.div(2)) + const voterVotes = await bribeController.getVotesForVoter(voter2.address); + expect(voterVotes[0].gaugeID).eq(GAUGE_ID) + expect(voterVotes[0].votePowerBPS).eq(votePowerBPS1.div(2)) + const gaugeVotes = await bribeController.getVotesForGauge(GAUGE_ID); + expect(gaugeVotes[0].voter).eq(voter2.address) + expect(gaugeVotes[0].votePowerBPS).eq(votePowerBPS1.div(2)) + expect(gaugeVotes[1].voter).eq(voter3.address) + expect(gaugeVotes[1].votePowerBPS).eq(votePowerBPS1) + }) + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}); + + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + await bribeController.connect(anon).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + expect(await bribeController.getProvidedBribesForGauge(1)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter2.address)).deep.eq([]) + expect(await bribeController.getVotesForVoter(voter3.address)).deep.eq([]) + expect(await bribeController.getVotesForGauge(1)).deep.eq([]) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(5000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getAvailableVotePowerBPS(voter3.address)).eq(10000) + }); + it("getClaimableBribes", async () => { + const claim1 = await bribeController.getClaimableBribes(voter2.address); + expect(claim1[0].bribeToken).eq(bribeToken1.address) + expect(claim1[1].bribeToken).eq(bribeToken2.address) + + const claim2 = await bribeController.getClaimableBribes(voter3.address); + expect(claim2[0].bribeToken).eq(bribeToken1.address) + expect(claim2[1].bribeToken).eq(bribeToken2.address) + + expectClose(claim1[0].bribeAmount.add(claim2[0].bribeAmount), BRIBE_AMOUNT, 1e3) + expectClose(claim1[1].bribeAmount.add(claim2[1].bribeAmount), BRIBE_AMOUNT, 1e3) + expectClose(claim2[0].bribeAmount.div(claim1[0].bribeAmount), 2, 1e14) + expectClose(claim2[1].bribeAmount.div(claim1[1].bribeAmount), 2, 1e14) + }) + it("can claimBribes", async () => { + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const tx1 = await bribeController.connect(voter2).claimBribes(); + await expect(tx1).to.emit(bribeController, "BribeClaimed"); + const tx2 = await bribeController.connect(voter3).claimBribes(); + await expect(tx2).to.emit(bribeController, "BribeClaimed"); + + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter2.address) + const NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter2.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(voter3.address) + const NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(voter3.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + const NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = await bribeToken1.balanceOf(revenueRouter.address) + + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_1_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_1_BALANCE_BRIBE_TOKEN_2) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_1.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_1) + const CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2 = NEW_VOTER_2_BALANCE_BRIBE_TOKEN_2.sub(OLD_VOTER_2_BALANCE_BRIBE_TOKEN_2) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2.sub(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + const CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2 = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2) + + expectClose(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1.add(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1), BRIBE_AMOUNT, 1e3) + expectClose(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2.add(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2), BRIBE_AMOUNT, 1e3) + expectClose(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_1.div(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_1), 2, 1e10) + expectClose(CHANGE_VOTER_2_BALANCE_BRIBE_TOKEN_2.div(CHANGE_VOTER_1_BALANCE_BRIBE_TOKEN_2), 2, 1e10) + expectClose(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1, BRIBE_AMOUNT.mul(-1), 1e4); + expectClose(CHANGE_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2, BRIBE_AMOUNT.mul(-1), 1e4); + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1).eq(0) + expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) + expect(await bribeController.getClaimableBribes(briber1.address)).deep.eq([]); + }) + }); + + /********************* + INTENTION STATEMENT + *********************/ + /** + * We will create 46 new gauges, for a total of 50 + * We will create 25 new voters with equivalent locks, who all delegate to delegate1 + * briber1 will allocate { 100 of bribeToken1, 100 of bribeToken 2 } to each of these gauges + * All new voters will allocate 2% of votePowerBPS to each gauge + */ + + describe("stress test with 25 voters and 50 gauges", () => { + const TOTAL_GAUGES = 50; + const TOTAL_VOTERS = 25; + let WALLET_ARRAY: Wallet[] = [] + let VOTER_ARRAY: string[] = [] + + before(async function () { + // Create 46 new gauges + const tx1 = await gaugeController.connect(governor).addGauge("X", ONE_PERCENT) + let nonce1 = tx1.nonce + const promises = [] + for (let i = 0; i < TOTAL_GAUGES - 5; i++) { + nonce1 += 1; + promises.push(gaugeController.connect(governor).addGauge("X", ONE_PERCENT, {nonce: nonce1})) + } + + // Create 25 new voters with equivalent locks, and all setDelegate to delegate1 + for (let i = 0; i < TOTAL_VOTERS; i++) { + WALLET_ARRAY.push(ethers.Wallet.createRandom().connect(provider)) + } + + const lock = await underwritingLocker.locks(1) + const tx2 = await underwritingLocker.connect(deployer).createLock(WALLET_ARRAY[0].address, lock.amount, lock.end); + let nonce2 = tx2.nonce + nonce2 += 1; + await deployer.sendTransaction({to: WALLET_ARRAY[0].address, value: ONE_ETHER.div(10)}) + promises.push(voting.connect(WALLET_ARRAY[0]).setDelegate(delegate1.address)); + + for (let i = 1; i < TOTAL_VOTERS; i++) { + nonce2 += 1; + promises.push(deployer.sendTransaction({to: WALLET_ARRAY[i].address, value: ONE_ETHER.div(10), nonce: nonce2})) + promises.push(voting.connect(WALLET_ARRAY[i]).setDelegate(delegate1.address)); + } + + for (let i = 1; i < TOTAL_VOTERS; i++) { + nonce2 += 1; + promises.push(underwritingLocker.connect(deployer).createLock(WALLET_ARRAY[i].address, lock.amount, lock.end, {nonce: nonce2})) + } + + // Briber1 will allocate { 100 of bribeToken1, 100 of bribeToken 2 } to each of these 50 gauges + + const tx3 = await bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], 1); + let nonce3 = tx3.nonce; + + for (let i = 2; i < TOTAL_GAUGES + 1; i++) { + nonce3 += 1; + promises.push(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [BRIBE_AMOUNT, BRIBE_AMOUNT], i, {nonce: nonce3})); + } + + await Promise.all(promises); + VOTER_ARRAY = WALLET_ARRAY.map(voter => voter.address) + + // All new voters will allocate 2% of votePowerBPS to each of the 50 gauges + const votePowerBPSArray = [] + const gaugeArray = [] + + for (let i = 1; i < TOTAL_GAUGES + 1; i++) { + votePowerBPSArray.push(10000 / TOTAL_GAUGES); + gaugeArray.push(i); + } + + const promises2 = [] + const tx4 = await bribeController.connect(delegate1).voteForMultipleBribes(VOTER_ARRAY[0], gaugeArray, votePowerBPSArray); + let nonce4 = tx4.nonce + for (let i = 1; i < TOTAL_VOTERS; i++) { + nonce4 += 1; + promises2.push(bribeController.connect(delegate1).voteForMultipleBribes(VOTER_ARRAY[i], gaugeArray, votePowerBPSArray, {nonce: nonce4})) + } + await Promise.all(promises2); + + const promises3 = [] + for (let i = 0; i < TOTAL_VOTERS; i++) { + promises3.push(bribeController.getUnusedVotePowerBPS(VOTER_ARRAY[i])) + } + const unusedVotePowerBPSArray = await Promise.all(promises3) + for (let i = 0; i < TOTAL_VOTERS; i++) { + expect(unusedVotePowerBPSArray[i]).eq(0) + } + + }); + it("processBribes will change state as expected", async () => { + const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + const EPOCH_START_TIME = await bribeController.getEpochStartTimestamp() + + { + let counter = 0; + while (true) { + counter += 1; + const tx = await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + + if ((await gaugeController.lastTimeGaugeWeightsUpdated()).lt(EPOCH_START_TIME)) { + await expect(tx).to.emit(gaugeController, "IncompleteGaugeUpdate"); + continue; + } else { + await expect(tx).to.emit(gaugeController, "GaugeWeightsUpdated").withArgs(EPOCH_START_TIME); + break; + } + } + console.log(`Required ${counter} iterations of updateGaugeWeights()`) + } + + { + let counter = 0; + while (true) { + counter += 1; + const tx = await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) + + if ((await voting.lastTimePremiumsCharged()).lt(EPOCH_START_TIME)) { + await expect(tx).to.emit(voting, "IncompletePremiumsCharge"); + continue; + } else { + await expect(tx).to.emit(voting, "AllPremiumsCharged").withArgs(EPOCH_START_TIME); + break; + } + } + console.log(`Required ${counter} iterations of chargePremiums()`) + } + + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + + { + let counter = 0; + while (true) { + counter += 1; + const tx = await bribeController.connect(anon).processBribes({gasLimit: CUSTOM_GAS_LIMIT}) + + if ((await bribeController.lastTimeBribesProcessed()).lt(EPOCH_START_TIME)) { + await expect(tx).to.emit(bribeController, "IncompleteBribesProcessing"); + continue; + } else { + await expect(tx).to.emit(bribeController, "BribesProcessed").withArgs(EPOCH_START_TIME); + break; + } + } + console.log(`Required ${counter} iterations of processBribes()`) + } + + // CHECK BRIBE TOKEN BALANCES + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) + const NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2 = await bribeToken2.balanceOf(bribeController.address) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1).eq(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1) + expect(NEW_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2).eq(OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_2) + + // CHECK MAPPINGS + expect(await bribeController.getAllGaugesWithBribe()).deep.eq([]) + + // CHECK GAUGE MAPPINGS + const gauge_promises = [] + + for (let i = 0; i < TOTAL_GAUGES; i++) { + gauge_promises.push(bribeController.getProvidedBribesForGauge(i)) + gauge_promises.push(bribeController.getVotesForGauge(i)) + } + + const GAUGE_MAPPINGS_ARRAY = await Promise.all(gauge_promises); + + for (let i = 0; i < GAUGE_MAPPINGS_ARRAY.length; i++) { + expect(GAUGE_MAPPINGS_ARRAY[i]).deep.eq([]); + } + + // CHECK VOTER MAPPINGS + + const voter_promises_1 = [] + + for (let i = 0; i < TOTAL_VOTERS; i++) { + voter_promises_1.push(bribeController.getVotesForVoter(VOTER_ARRAY[i])) + } + + const VOTER_VOTES_ARRAY = await Promise.all(voter_promises_1); + + for (let i = 0; i < VOTER_VOTES_ARRAY.length; i++) { + expect(VOTER_VOTES_ARRAY[i]).deep.eq([]); + } + + const voter_promises_2 = [] + + for (let i = 0; i < TOTAL_VOTERS; i++) { + voter_promises_2.push(bribeController.getAvailableVotePowerBPS(VOTER_ARRAY[i])) + } + + const availableVotePowerBPSArray = await Promise.all(voter_promises_2); + + for (let i = 0; i < VOTER_VOTES_ARRAY.length; i++) { + expect(availableVotePowerBPSArray[i]).eq(10000); + } + }); + it("getClaimableBribes", async () => { + const claim_promises = [] + + for (let i = 0; i < TOTAL_VOTERS; i++) { + claim_promises.push(bribeController.getClaimableBribes(VOTER_ARRAY[i])); + } + + const CLAIMS_ARRAY = await Promise.all(claim_promises); + + for (let i = 0; i < CLAIMS_ARRAY.length; i++) { + const claim = CLAIMS_ARRAY[i] + expect(claim[0].bribeToken).eq(bribeToken1.address) + expect(claim[1].bribeToken).eq(bribeToken2.address) + expectClose(claim[0].bribeAmount, BRIBE_AMOUNT.mul(TOTAL_GAUGES).div(TOTAL_VOTERS), 1e15) + expectClose(claim[1].bribeAmount, BRIBE_AMOUNT.mul(TOTAL_GAUGES).div(TOTAL_VOTERS), 1e15) + } + }) + it("claimBribes", async () => { + const claim_promises = []; + const old_balance_promises = []; + const new_balance_promises = []; + + for (let i = 0; i < TOTAL_VOTERS; i++) { + old_balance_promises.push(bribeToken1.balanceOf(VOTER_ARRAY[i])) + old_balance_promises.push(bribeToken2.balanceOf(VOTER_ARRAY[i])) + claim_promises.push(bribeController.connect(WALLET_ARRAY[i]).claimBribes()) + } + + const OLD_BALANCE_ARRAY = await Promise.all(old_balance_promises) + await Promise.all(claim_promises) + + for (let i = 0; i < TOTAL_VOTERS; i++) { + new_balance_promises.push(bribeToken1.balanceOf(VOTER_ARRAY[i])) + new_balance_promises.push(bribeToken2.balanceOf(VOTER_ARRAY[i])) + } + + const NEW_BALANCE_ARRAY = await Promise.all(new_balance_promises) + + for (let i = 0; i < NEW_BALANCE_ARRAY.length; i++) { + expectClose(NEW_BALANCE_ARRAY[i].sub(OLD_BALANCE_ARRAY[i]), BRIBE_AMOUNT.mul(TOTAL_GAUGES).div(TOTAL_VOTERS), 1e12) + } + }) + after(async function () { + await bribeController.connect(governor).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address) + }); + }); + +}); \ No newline at end of file diff --git a/test/native/GaugeController.test.ts b/test/native/GaugeController.test.ts index 666c79b1..93adbf83 100644 --- a/test/native/GaugeController.test.ts +++ b/test/native/GaugeController.test.ts @@ -31,8 +31,8 @@ const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; describe("GaugeController", function () { - const [deployer, governor, revenueRouter, voter1, updater, anon] = provider.getWallets(); - + const [deployer, governor, revenueRouter, voter1, anon] = provider.getWallets(); + /*************************** VARIABLE DECLARATIONS ***************************/ @@ -73,10 +73,9 @@ describe("GaugeController", function () { }); it("initializes properly", async function () { expect(await gaugeController.token()).eq(token.address); - expect(await gaugeController.updater()).eq(ZERO_ADDRESS); expect(await gaugeController.leverageFactor()).eq(ONE_HUNDRED_PERCENT); expect(await gaugeController.totalGauges()).eq(0); - expect(await gaugeController.lastTimeGaugeWeightsUpdated()).eq(0); + expect(await gaugeController.lastTimeGaugeWeightsUpdated()).eq(await gaugeController.getEpochStartTimestamp()); expect(await gaugeController.getGaugeWeight(0)).eq(ZERO); expect(await gaugeController.getAllGaugeWeights()).deep.eq([ZERO]); expect(await gaugeController.getNumActiveGauges()).eq(0); @@ -199,21 +198,14 @@ describe("GaugeController", function () { }); }); - describe("setUpdater", () => { - it("non governor cannot setUpdater", async () => { - await expect(gaugeController.connect(voter1).setUpdater(updater.address)).to.be.revertedWith("!governance"); - }); - it("can set updater", async () => { - let tx = await gaugeController.connect(governor).setUpdater(updater.address); - await expect(tx).to.emit(gaugeController, "UpdaterSet").withArgs(updater.address); - expect(await gaugeController.updater()).eq(updater.address) - }); - }); describe("setEpochLengthInWeeks", () => { it("non governor cannot setEpochLengthInWeeks", async () => { await expect(gaugeController.connect(voter1).setEpochLengthInWeeks(1)).to.be.revertedWith("!governance"); }); + it("cannot set epoch length to 0", async () => { + await expect(gaugeController.connect(governor).setEpochLengthInWeeks(0)).to.be.revertedWith("CannotSetEpochLengthTo0"); + }); it("can setEpochLengthInWeeks", async () => { let tx = await gaugeController.connect(governor).setEpochLengthInWeeks(2); await expect(tx).to.emit(gaugeController, "EpochLengthSet").withArgs(2); @@ -306,9 +298,6 @@ describe("GaugeController", function () { it("cannot be vote for gaugeID 0", async () => { await expect(gaugeController.connect(voter1).vote(voter1.address, 0, 1)).to.be.revertedWith("CannotVoteForGaugeID0"); }); - it("cannot be called before gauge weight updated", async () => { - await expect(gaugeController.connect(voter1).vote(voter1.address, 1, 1)).to.be.revertedWith("GaugeWeightsNotYetUpdated"); - }); }); /********************* @@ -330,8 +319,6 @@ describe("GaugeController", function () { await gaugeController.connect(governor).addVotingContract(voting.address) await registry.connect(governor).set(["underwritingLockVoting"], [voting.address]); await underwritingLocker.connect(governor).setVotingContract() - await gaugeController.connect(governor).updateGaugeWeights(); - await voting.connect(governor).chargePremiums(); await token.connect(deployer).transfer(voter1.address, ONE_ETHER.mul(1000)) await token.connect(voter1).approve(underwritingLocker.address, constants.MaxUint256) await underwritingLocker.connect(voter1).createLock(voter1.address, DEPOSIT_AMOUNT, CURRENT_TIME + 4 * ONE_YEAR) @@ -356,10 +343,13 @@ describe("GaugeController", function () { expect(await gaugeController.getVoteCount(voting.address, voter1.address)).eq(1) expect(await gaugeController.getVotersCount(voting.address)).eq(1) }); - it("updater can updateGaugeWeight in next epoch", async () => { + it("cannot update gauge in same epoch as contract deployment", async () => { + await expect(gaugeController.connect(governor).updateGaugeWeights()).to.be.revertedWith("GaugeWeightsAlreadyUpdated"); + }); + it("anon can updateGaugeWeight in next epoch", async () => { const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); - const tx = await gaugeController.connect(updater).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + const tx = await gaugeController.connect(anon).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) const EPOCH_START_TIME = await gaugeController.getEpochStartTimestamp() await expect(tx).to.emit(gaugeController, "GaugeWeightsUpdated").withArgs(EPOCH_START_TIME); }); diff --git a/test/native/UnderwritingLockVoting.test.ts b/test/native/UnderwritingLockVoting.test.ts index 50e82fcd..427e3db8 100644 --- a/test/native/UnderwritingLockVoting.test.ts +++ b/test/native/UnderwritingLockVoting.test.ts @@ -28,8 +28,8 @@ const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; describe("UnderwritingLockVoting", function () { - const [deployer, governor, revenueRouter, voter1, voter2, delegate1, updater, bribeController, anon] = provider.getWallets(); - + const [deployer, governor, revenueRouter, voter1, voter2, delegate1, bribeController, anon] = provider.getWallets(); + /*************************** VARIABLE DECLARATIONS ***************************/ @@ -89,10 +89,9 @@ describe("UnderwritingLockVoting", function () { expect(await voting.underwritingLocker()).eq(underwritingLocker.address); expect(await voting.gaugeController()).eq(gaugeController.address); expect(await voting.registry()).eq(registry.address); - expect(await voting.updater()).eq(ZERO_ADDRESS); expect(await voting.bribeController()).eq(ZERO_ADDRESS); - expect(await voting.lastTimePremiumsCharged()).eq(0); - expect(await voting.isVotingOpen()).eq(false); + expect(await voting.lastTimePremiumsCharged()).eq(await voting.lastTimePremiumsCharged()); + expect(await voting.isVotingOpen()).eq(true); expect(await gaugeController.getVoteCount(voting.address, voter1.address)).eq(0) expect(await gaugeController.getVotersCount(voting.address)).eq(0) }); @@ -117,7 +116,7 @@ describe("UnderwritingLockVoting", function () { expect(await voting.getVotes(voter1.address)).deep.eq([]) }); it("chargePremiums should revert", async function () { - await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("GaugeWeightsNotYetUpdated") + await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("LastEpochPremiumsAlreadyProcessed") }); }); @@ -221,17 +220,6 @@ describe("UnderwritingLockVoting", function () { }) }); - describe("setUpdater", () => { - it("non governor cannot setUpdater", async () => { - await expect(voting.connect(voter1).setUpdater(updater.address)).to.be.revertedWith("!governance"); - }); - it("can set updater", async () => { - const tx = await voting.connect(governor).setUpdater(updater.address); - await expect(tx).to.emit(voting, "UpdaterSet").withArgs(updater.address); - expect(await voting.updater()).eq(updater.address) - }); - }); - describe("setBribeController", () => { it("non governor cannot setBribeController", async () => { await expect(voting.connect(voter1).setBribeController()).to.be.revertedWith("!governance"); @@ -333,17 +321,6 @@ describe("UnderwritingLockVoting", function () { }); }); - describe("gaugeController.setUpdater", () => { - it("non governor cannot setUpdater", async () => { - await expect(gaugeController.connect(voter1).setUpdater(updater.address)).to.be.revertedWith("!governance"); - }); - it("can set updater", async () => { - let tx = await gaugeController.connect(governor).setUpdater(updater.address); - await expect(tx).to.emit(gaugeController, "UpdaterSet").withArgs(updater.address); - expect(await gaugeController.updater()).eq(updater.address) - }); - }); - /********************* INTENTION STATEMENT *********************/ @@ -351,53 +328,6 @@ describe("UnderwritingLockVoting", function () { describe("basic vote() scenario", () => { let LAST_RECORDED_VOTE_POWER: BN; // To transport VOTE_POWER value from one unit test to another. - - it("vote() and voteMultiple() should throw if gauge weights have not been processed", async function () { - await expect(voting.connect(voter1).vote(voter1.address, 1, 10000)).to.be.revertedWith("LastEpochPremiumsNotCharged"); - await expect(voting.connect(voter1).voteMultiple(voter1.address, [1, 2], [10000, 10000])).to.be.revertedWith("LastEpochPremiumsNotCharged"); - }); - it("updateGaugeWeights() should revert if non governor or updater", async function () { - await expect(gaugeController.connect(voter1).updateGaugeWeights()).to.be.revertedWith("NotUpdaterNorGovernance"); - }); - it("chargePremiums() should revert if non governor or updater", async function () { - await expect(voting.connect(voter1).chargePremiums()).to.be.revertedWith("NotUpdaterNorGovernance"); - }); - it("chargePremiums() should revert if gauge weights have not been updated", async function () { - await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("GaugeWeightsNotYetUpdated"); - }); - it("updateGaugeWeights() should succeed", async function () { - const EPOCH_START_TIME = await voting.getEpochStartTimestamp(); - const tx = await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); - await expect(tx).to.emit(gaugeController, "GaugeWeightsUpdated").withArgs(EPOCH_START_TIME); - expect(await gaugeController.lastTimeGaugeWeightsUpdated()).eq(EPOCH_START_TIME) - expect(await gaugeController.getVotePowerSum()).eq(0) - }); - it("updateGaugeWeights() should revert if attempted again in the same epoch", async function () { - await expect(gaugeController.connect(governor).updateGaugeWeights()).to.be.revertedWith("GaugeWeightsAlreadyUpdated"); - }); - it("isVotingOpen() should return false at this point", async function () { - expect(await voting.isVotingOpen()).eq(false); - }); - it("vote() and voteMultiple() should throw if premiums have not been charged", async function () { - await expect(voting.connect(voter1).vote(voter1.address, 1, 10000)).to.be.revertedWith("LastEpochPremiumsNotCharged"); - await expect(voting.connect(voter1).voteMultiple(voter1.address, [1, 2], [10000, 10000])).to.be.revertedWith("LastEpochPremiumsNotCharged"); - }); - it("chargePremiums() should revert before tokenholder added to gaugeController()", async function () { - await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("NoTokenholdersAdded"); - await gaugeController.connect(governor).addTokenholder(underwritingLocker.address); - }); - it("chargePremiums() should succeed", async function () { - const EPOCH_START_TIME = await voting.getEpochStartTimestamp(); - const tx = await voting.connect(governor).chargePremiums(); - await expect(tx).to.emit(voting, "AllPremiumsCharged").withArgs(EPOCH_START_TIME); - expect(await voting.lastTimePremiumsCharged()).eq(EPOCH_START_TIME) - }); - it("isVotingOpen() should return true at this point", async function () { - expect(await voting.isVotingOpen()).eq(true); - }); - it("chargePremiums() should revert if attempted again in the same epoch", async function () { - await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("LastEpochPremiumsAlreadyProcessed"); - }); it("non-owner or non-delegate cannot vote() or voteMultiple()", async function () { await expect(voting.connect(anon).vote(voter1.address, 1, 1)).to.be.revertedWith("NotOwnerNorDelegate()"); await expect(voting.connect(anon).voteMultiple(voter1.address, [1, 2], [10000, 10000])).to.be.revertedWith("NotOwnerNorDelegate"); @@ -516,35 +446,49 @@ describe("UnderwritingLockVoting", function () { expect(await gaugeController.getVoteCount(voting.address, voter1.address)).eq(1) expect(await gaugeController.getVotersCount(voting.address)).eq(1) }); - it("updateGaugeWeights() called by updater should succeed in the next epoch", async function () { + it("chargePremiums() should revert if gauge weights have not been updated", async function () { const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); - const tx = await gaugeController.connect(updater).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("GaugeWeightsNotYetUpdated"); + }); + it("updateGaugeWeights() called by anon should succeed in the next epoch", async function () { + const tx = await gaugeController.connect(anon).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); const EPOCH_START_TIME = await voting.getEpochStartTimestamp(); const VOTE_POWER = await voting.getVotePower(voter1.address); await expect(tx).to.emit(gaugeController, "GaugeWeightsUpdated").withArgs(EPOCH_START_TIME); + expect(await gaugeController.lastTimeGaugeWeightsUpdated()).eq(EPOCH_START_TIME) LAST_RECORDED_VOTE_POWER = VOTE_POWER; }); + it("updateGaugeWeights() should revert if attempted again in the same epoch", async function () { + await expect(gaugeController.connect(governor).updateGaugeWeights()).to.be.revertedWith("GaugeWeightsAlreadyUpdated"); + }); + it("isVotingOpen() should return false at this point", async function () { + expect(await voting.isVotingOpen()).eq(false); + }); it("sanity check of gauge weights", async function () { expect(await gaugeController.getGaugeWeight(1)).eq(ONE_HUNDRED_PERCENT) expect(await gaugeController.getAllGaugeWeights()).deep.eq([ZERO, ONE_HUNDRED_PERCENT]); expect(await gaugeController.getVotePowerSum()).eq(LAST_RECORDED_VOTE_POWER) - }); - it("cannot vote or voteMultiple, between processVotes() and chargePremiums() for the same epoch", async function () { + }); + it("cannot vote or voteMultiple, if premiums not charged for the last epoch", async function () { expect(await voting.isVotingOpen()).eq(false) await expect(voting.connect(voter1).vote(voter1.address, 1, 1)).to.be.revertedWith("LastEpochPremiumsNotCharged"); await expect(voting.connect(delegate1).voteMultiple(voter1.address, [1, 2], [10000, 10000])).to.be.revertedWith("LastEpochPremiumsNotCharged"); }); + it("chargePremiums() should revert before tokenholder added to gaugeController()", async function () { + await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("NoTokenholdersAdded"); + await gaugeController.connect(governor).addTokenholder(underwritingLocker.address); + }); it("chargePremiums() should revert before underwritingLocker.sol call setVotingContract()", async function () { await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("NotVotingContract"); }); - it("chargePremiums() call by updater should succeed in the next epoch, provided allowance granted by underwritingLocker", async function () { + it("chargePremiums() call by anon should succeed in the next epoch, provided allowance granted by underwritingLocker", async function () { await registry.connect(governor).set(["underwritingLockVoting"],[voting.address]); await underwritingLocker.connect(governor).setVotingContract(); const OLD_VOTER_LOCKED_AMOUNT = await getTotalLockedAmount(voter1.address) const OLD_UNDERWRITING_LOCKER_BALANCE = await token.balanceOf(underwritingLocker.address); - const tx = await voting.connect(updater).chargePremiums(); + const tx = await voting.connect(anon).chargePremiums(); const EPOCH_START_TIME = await voting.getEpochStartTimestamp(); const NEW_VOTER_LOCKED_AMOUNT = await getTotalLockedAmount(voter1.address) const NEW_UNDERWRITING_LOCKER_BALANCE = await token.balanceOf(underwritingLocker.address); @@ -553,6 +497,7 @@ describe("UnderwritingLockVoting", function () { expect(await token.balanceOf(revenueRouter.address)).eq(EXPECTED_PREMIUM); expect(NEW_VOTER_LOCKED_AMOUNT.sub(OLD_VOTER_LOCKED_AMOUNT)).eq(EXPECTED_PREMIUM.mul(-1)); expect(NEW_UNDERWRITING_LOCKER_BALANCE.sub(OLD_UNDERWRITING_LOCKER_BALANCE)).eq(EXPECTED_PREMIUM.mul(-1)); + expect(await voting.lastTimePremiumsCharged()).eq(EPOCH_START_TIME) expect(await voting.isVotingOpen()).eq(true) }); }); @@ -660,7 +605,7 @@ describe("UnderwritingLockVoting", function () { it("cannot call chargePremiums() before epoch passes", async function () { const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); - await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("LastEpochPremiumsAlreadyProcessed"); + await expect(voting.connect(governor).chargePremiums()).to.be.reverted; }); it("can updateGaugeWeights() in the next epoch", async function () { const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; @@ -740,8 +685,15 @@ describe("UnderwritingLockVoting", function () { expect(await gaugeController.lastTimeGaugeWeightsUpdated()).eq(EPOCH_START_TIME) expect(await voting.lastTimePremiumsCharged()).eq(EPOCH_START_TIME) }); - after(async function () { + it("check for reset of epoch length", async function () { await gaugeController.connect(governor).setEpochLengthInWeeks(1) + const LAST_CHECKPOINT_TIMESTAMP = await voting.lastTimePremiumsCharged(); + const EPOCH_START_TIMESTAMP = await voting.getEpochStartTimestamp(); + if(EPOCH_START_TIMESTAMP.gt(LAST_CHECKPOINT_TIMESTAMP)) { + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + await voting.connect(governor).chargePremiums(); + } + expect(await voting.isVotingOpen()).eq(true); }); }); diff --git a/test/native/UnderwritingLocker.test.ts b/test/native/UnderwritingLocker.test.ts index aebaf97e..37ff591f 100644 --- a/test/native/UnderwritingLocker.test.ts +++ b/test/native/UnderwritingLocker.test.ts @@ -1836,30 +1836,35 @@ describe("UnderwritingLocker", function () { const votingContractProxy = ethers.Wallet.createRandom().connect(provider); it("will revert if called by non voting-contract", async function () { - await expect(underwritingLocker.connect(governor).chargePremium(1, 1)).to.be.revertedWith("NotVotingContract"); + await expect(underwritingLocker.connect(governor).chargePremium(user1.address, 1)).to.be.revertedWith("NotVotingContract"); }); it("will revert if attempt to charge more premium than exists in the lock", async function () { await registry.connect(governor).set(["underwritingLockVoting"], [votingContractProxy.address]) await underwritingLocker.connect(governor).setVotingContract() expect(await underwritingLocker.votingContract()).eq(votingContractProxy.address) - await expect(underwritingLocker.connect(votingContractProxy).chargePremium(9, DEPOSIT_AMOUNT.mul(2))).to.be.reverted; + await expect(underwritingLocker.connect(votingContractProxy).chargePremium(user1.address, DEPOSIT_AMOUNT.mul(999))).to.be.reverted; }); it("premium can be charged", async function () { - const LOCK_ID = 9; + const LOCK_ID_1 = 9; + const LOCK_ID_2 = 10; const PREMIUM_AMOUNT = DEPOSIT_AMOUNT.div(2) - const oldLockState = await getLockState(LOCK_ID) + const oldLockState1 = await getLockState(LOCK_ID_1) + const oldLockState2 = await getLockState(LOCK_ID_2) await user1.sendTransaction({to: votingContractProxy.address, value: ONE_ETHER}) // Send gas money - const tx = await underwritingLocker.connect(votingContractProxy).chargePremium(9, PREMIUM_AMOUNT) - const newLockState = await getLockState(LOCK_ID) - const lockStateChange = await getLockStateChange(newLockState, oldLockState) - expect(lockStateChange.amount).eq(PREMIUM_AMOUNT.mul(-1)) + const tx = await underwritingLocker.connect(votingContractProxy).chargePremium(user1.address, PREMIUM_AMOUNT.mul(2)) + const newLockState1 = await getLockState(LOCK_ID_1) + const newLockState2 = await getLockState(LOCK_ID_2) + const lockStateChange1 = await getLockStateChange(newLockState1, oldLockState1) + const lockStateChange2 = await getLockStateChange(newLockState2, oldLockState2) + expect(lockStateChange1.amount).eq(PREMIUM_AMOUNT.mul(-1)) + expect(lockStateChange2.amount).eq(PREMIUM_AMOUNT.mul(-1)) }); it("if attempt to charge premium for non-existent lock, call will succeed but state will not change", async function () { const LOCK_ID = 1; const oldGlobalState = await getGlobalState() const oldUserState = await getUserState(user1) - const tx = await underwritingLocker.connect(votingContractProxy).chargePremium(LOCK_ID, 1) + const tx = await underwritingLocker.connect(votingContractProxy).chargePremium(user2.address, 1) expect(await underwritingLocker.exists(LOCK_ID)).eq(false) const newGlobalState = await getGlobalState() const newUserState = await getUserState(user1) diff --git a/test/utilities/artifact_importer.ts b/test/utilities/artifact_importer.ts index 42f31362..9f8033cf 100644 --- a/test/utilities/artifact_importer.ts +++ b/test/utilities/artifact_importer.ts @@ -123,6 +123,7 @@ export async function import_artifacts() { artifacts.GaugeController = await tryImport(`${artifact_dir}/native/GaugeController.sol/GaugeController.json`); artifacts.MockUnderwritingLockListener = await tryImport(`${artifact_dir}/mocks/MockUnderwritingLockListener.sol/MockUnderwritingLockListener.json`); artifacts.DepositHelper = await tryImport(`${artifact_dir}/native/DepositHelper.sol/DepositHelper.json`); + artifacts.BribeController = await tryImport(`${artifact_dir}/native/BribeController.sol/BribeController.json`); return artifacts; }