From 35800824785fa34b54b98c9191df6d5cad7463fa Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Fri, 26 Aug 2022 17:47:33 +1000 Subject: [PATCH 01/24] first bribe contract commit --- .../interfaces/native/IBribeController.sol | 217 ++++++++ contracts/native/BribeController.sol | 463 ++++++++++++++++++ test/native/BribeController.test.ts | 370 ++++++++++++++ test/utilities/artifact_importer.ts | 1 + 4 files changed, 1051 insertions(+) create mode 100644 contracts/interfaces/native/IBribeController.sol create mode 100644 contracts/native/BribeController.sol create mode 100644 test/native/BribeController.test.ts diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol new file mode 100644 index 0000000..e0aebc2 --- /dev/null +++ b/contracts/interfaces/native/IBribeController.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.6; + +import "./GaugeStructs.sol"; + +interface IBribeController { + /*************************************** + STRUCTS + ***************************************/ + + struct Bribe { + address bribeToken; + uint256 bribeAmount; + } + + /*************************************** + 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 unwhitelisted bribe token. + error CannotBribeWithNonWhitelistedToken(); + + /// @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 distributeBribes() is called by neither governance nor updater, or governance is locked. + error NotUpdaterNorGovernance(); + + /// @notice Thrown if distributeBribes() is called after bribes have already been successfully distributed in the current epoch. + error BribesAlreadyDistributed(); + + /// @notice Thrown when distributeBribes is attempted before the last epoch's votes have been successfully processed through gaugeController.updateGaugeWeights(). + error GaugeWeightsNotYetUpdated(); + + /*************************************** + EVENTS + ***************************************/ + + /// @notice Emitted when bribe is provided. + event BribeProvided(address indexed briber, uint256 indexed gaugeID, address indexed bribeToken, uint256 bribeAmount); + + /// @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 the Updater is set. + event UpdaterSet(address indexed updater); + + /// @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 distributeBribes() does an incomplete update, and will need to be run again until completion. + event IncompleteBribeDistribution(); + + /// @notice Emitted when bribes distributed for an epoch. + event BribesDistributed(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 Updater address. + function updater() external view returns (address); + + /// @notice End timestamp for last epoch that bribes were distributed for all stored votes. + function lastTimeBribesDistributed() 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 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 bribes which have been provided for the current epoch for a given gauge. + * @param gaugeID_ GaugeID to query for. + * @return bribes Array of provided bribes. + */ + function getProvidedBribesForCurrentEpoch(uint256 gaugeID_) external view returns (Bribe[] memory bribes); + + /** + * @notice Get bribes which have been provided for the next epoch for a given gauge. + * @param gaugeID_ GaugeID to query for. + * @return bribes Array of provided bribes. + */ + function getProvidedBribesForNextEpoch(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); + + /*************************************** + BRIBER FUNCTIONS + ***************************************/ + + /** + * @notice Offer bribes. + * @param bribeTokens_ Array of bribe token addresses. + * @param bribeAmounts_ Array of bribe token amounts. + * @param gaugeID_ Gauge ID to bribe for. + */ + function offerBribes( + 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 + */ + function voteForBribe(address voter_, uint256 gaugeID_) external; + /** + * @notice Claim bribes. + */ + function claimBribes() external; + + /*************************************** + GOVERNANCE FUNCTIONS + ***************************************/ + + /** + * @notice Sets the [`Registry`](./Registry) contract address. + * @dev Requires 'uwe', 'revenueRouter' 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 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 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 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`. + * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. + */ + function processBribes() external; +} \ No newline at end of file diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol new file mode 100644 index 0000000..8e7acf9 --- /dev/null +++ b/contracts/native/BribeController.sol @@ -0,0 +1,463 @@ +// 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"; +import "hardhat/console.sol"; + +// ? PROVIDE WITHDRAW BRIBE FUNCTION? + +contract BribeController is + IBribeController, + ReentrancyGuard, + Governable + { + using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableMapS for EnumerableMapS.AddressToUintMap; + + /*************************************** + 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 Updater address. + /// @dev Second address that can call updateGaugeWeights (in addition to governance). + address public override updater; + + /// @notice End timestamp for last epoch that bribes were distributed for all stored votes. + uint256 public override lastTimeBribesDistributed; + + /*************************************** + GLOBAL INTERNAL VARIABLES + ***************************************/ + + uint256 constant internal WEEK = 604800; + + /// @notice epochEndTimeStamp => gaugeID => bribeToken => totalBribeAmount. + mapping(uint256 => mapping(uint256 => EnumerableMapS.AddressToUintMap)) internal _providedBribesA; + + /// @notice gaugeID => bribeToken => bribeAmount. + mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _providedBribes; + + /// @notice voter => Vote (gaugeID => votePowerBPS). + mapping(address => EnumerableMapS.UintToUintMap) internal _votes; + + /// @notice voter => bribeToken => claimableBribeAmount. + mapping(address => EnumerableMapS.AddressToUintMap) internal _claimableBribes; + + /// @notice briber => bribeToken => claimableBribeAmount. + mapping(address => EnumerableMapS.AddressToUintMap) internal _lifetimeProvidedBribes; + + /// @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(); + lastTimeBribesDistributed = _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 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 + ***************************************/ + + /** + * @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 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(); + bribes = new Bribe[](length); + for (uint256 i = 0; i < length; i++) { + (address bribeToken, uint256 bribeAmount) = _claimableBribes[voter_].at(i); + bribes[i] = Bribe(bribeToken, bribeAmount); + } + return bribes; + } + + /** + * @notice Get bribes which have been provided for the current epoch for a given gauge. + * @param gaugeID_ GaugeID to query for. + * @return bribes Array of provided bribes. + */ + function getProvidedBribesForCurrentEpoch(uint256 gaugeID_) external view override returns (Bribe[] memory bribes) { + uint256 epochStartTimeStamp = _getEpochStartTimestamp(); + uint256 length = _providedBribesA[epochStartTimeStamp][gaugeID_].length(); + bribes = new Bribe[](length); + for (uint256 i = 0; i < length; i++) { + (address bribeToken, uint256 bribeAmount) = _providedBribesA[epochStartTimeStamp][gaugeID_].at(i); + bribes[i] = Bribe(bribeToken, bribeAmount); + } + return bribes; + } + + /** + * @notice Get bribes which have been provided for the next epoch for a given gauge. + * @param gaugeID_ GaugeID to query for. + * @return bribes Array of provided bribes. + */ + function getProvidedBribesForNextEpoch(uint256 gaugeID_) external view override returns (Bribe[] memory bribes) { + uint256 epochEndTimeStamp = _getEpochEndTimestamp(); + uint256 length = _providedBribesA[epochEndTimeStamp][gaugeID_].length(); + bribes = new Bribe[](length); + for (uint256 i = 0; i < length; i++) { + (address bribeToken, uint256 bribeAmount) = _providedBribesA[epochEndTimeStamp][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; + } + + /*************************************** + INTERNAL MUTATOR FUNCTIONS + ***************************************/ + + /** + * @notice Sets registry and related contract addresses. + * @dev Requires 'uwe', 'revenueRouter' 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); + } + + /*************************************** + BRIBER FUNCTIONS + ***************************************/ + + /** + * @notice Offer bribes. + * @param bribeTokens_ Array of bribe token addresses. + * @param bribeAmounts_ Array of bribe token amounts. + * @param gaugeID_ Gauge ID to bribe for. + */ + function offerBribes( + address[] calldata bribeTokens_, + uint256[] calldata bribeAmounts_, + uint256 gaugeID_ + ) external override nonReentrant { + // CHECKS + if (bribeTokens_.length != bribeAmounts_.length) revert ArrayArgumentsLengthMismatch(); + if (!IGaugeController(gaugeController).isGaugeActive(gaugeID_)) revert CannotBribeForInactiveGauge(); + + uint256 length = _bribeTokenWhitelist.length(); + for (uint256 i = 0; i < length; i++) { + if (!_bribeTokenWhitelist.contains(bribeTokens_[i])) revert CannotBribeWithNonWhitelistedToken(); + } + + // INTERNAL STATE MUTATIONS + 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 + */ + function voteForBribe(address voter_, uint256 gaugeID_) external override nonReentrant { + // CHECKS + if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); + if (_providedBribes[gaugeID_].length() == 0) revert NoBribesForSelectedGauge(); + + + + // uint256 length = _claimableBribes[msg.sender].length(); + // if (length == 0) return; + + // while (_claimableBribes[msg.sender].length() != 0) { + // (address bribeToken, uint256 bribeAmount) = _claimableBribes[msg.sender].at(0); + // _claimableBribes[msg.sender].remove(bribeToken); + // SafeERC20.safeTransfer(IERC20(bribeToken), msg.sender, bribeAmount); + // emit BribeClaimed(msg.sender, bribeToken, bribeAmount); + // } + } + + // 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) return; + + while (_claimableBribes[msg.sender].length() != 0) { + (address bribeToken, uint256 bribeAmount) = _claimableBribes[msg.sender].at(0); + _claimableBribes[msg.sender].remove(bribeToken); + SafeERC20.safeTransfer(IERC20(bribeToken), msg.sender, bribeAmount); + emit BribeClaimed(msg.sender, bribeToken, bribeAmount); + } + } + + /*************************************** + GOVERNANCE FUNCTIONS + ***************************************/ + + /** + * @notice Sets the [`Registry`](./Registry) contract address. + * @dev Requires 'uwe', 'revenueRouter' 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 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 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_); + } + + // IDEAL FLOW is gaugeController.updateGaugeWeights() => BribeController.distributeBribes() => UnderwritingLockVoting.chargePremiums() + // We don't have an onchain mechanism to enforce this flow in the current implementation. + // However to calculate bribes accurately, we need individual votes, individual vote power, and vote power for each gauge from the same state. And this can only be guaranteed in with the flow above. + + // Very cumbersome to enforce this flow on-chain, should re-architect as being completed in one function in an stateless off-chain node. + + /** + * @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`. + * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. + */ + function processBribes() external override { + // CHECKS + if (!_isUpdaterOrGovernance()) revert NotUpdaterNorGovernance(); + uint256 currentEpochStartTime = _getEpochStartTimestamp(); + // Require gauge weights to have been updated for this epoch => ensure state we are querying from is < 1 WEEK old. + if(IGaugeController(gaugeController).lastTimeGaugeWeightsUpdated() != currentEpochStartTime) revert GaugeWeightsNotYetUpdated(); + if (lastTimeBribesDistributed >= currentEpochStartTime) revert BribesAlreadyDistributed(); + + // GET REQUIRED EXTERNAL DATA + uint256 votePowerSum = IGaugeController(gaugeController).getVotePowerSum(); + uint256[] memory gaugeVotePower = IGaugeController(gaugeController).getAllGaugeWeights(); + + for (uint256 i = 0; i < gaugeVotePower.length; i++) { + // Convert from gaugeWeight to votePower. + // Reassign variable instead of using new variable to save stack space. + gaugeVotePower[i] = gaugeVotePower[i] * votePowerSum / 1e18; + } + + // INTERNAL STATE MUTATIONS + // ITERATE THROUGH EPOCHS, FROM `lastTimeBribesDistributed + WEEK` TO `currentEpochStartTime` + while (lastTimeBribesDistributed < currentEpochStartTime) { + lastTimeBribesDistributed += WEEK; + + // ITERATE THROUGH VOTERS + address[] memory voters = IGaugeController(gaugeController).getVoters(votingContract); + for(uint256 i = _updateInfo._votersIndex == type(uint88).max ? 0 : _updateInfo._votersIndex ; i < voters.length; i++) { + + // ITERATE THROUGH VOTES + GaugeStructs.Vote[] memory votes = IGaugeController(gaugeController).getVotes(votingContract, voters[i]); + + for(uint256 j = _updateInfo._votesIndex == type(uint88).max || i != _updateInfo._votersIndex ? 0 : _updateInfo._votesIndex; j < votes.length; j++) { + + if (gaugeVotePower[votes[j].gaugeID] > 0) { + // ITERATE THROUGH BRIBE TOKENS + for(uint256 k = _updateInfo._votingContractsIndex == type(uint80).max || j != _updateInfo._votesIndex ? 0 : _updateInfo._votingContractsIndex; k < _providedBribesA[lastTimeBribesDistributed][votes[j].gaugeID].length(); k++) { + // CHECKPOINT + if (gasleft() < 30000) {return _saveUpdateState(k, i, j);} + + (address bribeToken, uint256 totalBribeAmount) = _providedBribesA[lastTimeBribesDistributed][votes[j].gaugeID].at(k); + uint256 proportionalBribeAmount = totalBribeAmount * IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voters[i]) / gaugeVotePower[votes[j].gaugeID]; + (,uint256 runningBribeTotalForVoter) = _claimableBribes[voters[i]].tryGet(bribeToken); + // STATE CHANGE 1 - ADD TO CLAIMABLE BRIBES OF USER + _claimableBribes[voters[i]].set(bribeToken, runningBribeTotalForVoter + proportionalBribeAmount); + } + } + } + } + + emit BribesDistributed(lastTimeBribesDistributed); + } + + _clearUpdateInfo(); + } + + /*************************************** + distributeBribes() HELPER FUNCTIONS + ***************************************/ + + /** + * @notice Save state of updating gauge weights to _updateInfo. + * @param votingContractsIndex_ Current index of _votingContracts. + * @param votersIndex_ Current index of _voters[votingContractsIndex_]. + * @param votesIndex_ Current index of _votes[votingContractsIndex_][votersIndex_] + */ + function _saveUpdateState(uint256 votingContractsIndex_, uint256 votersIndex_, uint256 votesIndex_) internal { + assembly { + let updateInfo + updateInfo := or(updateInfo, shr(176, shl(176, votingContractsIndex_))) // [0:80] => votingContractsIndex_ + updateInfo := or(updateInfo, shr(88, shl(168, votersIndex_))) // [80:168] => votersIndex_ + updateInfo := or(updateInfo, shl(168, votesIndex_)) // [168:256] => votesIndex_ + sstore(_updateInfo.slot, updateInfo) + } + emit IncompleteBribeDistribution(); + } + + /// @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) + } + } +} \ No newline at end of file diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts new file mode 100644 index 0000000..3718dea --- /dev/null +++ b/test/native/BribeController.test.ts @@ -0,0 +1,370 @@ +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(1000); +const SCALE_FACTOR = ONE_ETHER; +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, updater, 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.votingContract()).eq(voting.address); + expect(await bribeController.gaugeController()).eq(gaugeController.address); + expect(await bribeController.registry()).eq(registry.address); + expect(await bribeController.updater()).eq(ZERO_ADDRESS); + expect(await bribeController.lastTimeBribesDistributed()).eq(await bribeController.getEpochStartTimestamp()); + expect(await bribeController.getBribeTokenWhitelist()).deep.eq([]); + expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]); + expect(await bribeController.getProvidedBribesForCurrentEpoch(0)).deep.eq([]); + expect(await bribeController.getLifetimeProvidedBribes(briber1.address)).deep.eq([]); + }); + 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 gaugeController.getEpochStartTimestamp()).eq(EXPECTED_EPOCH_START_TIME) + }); + it("getEpochEndTimestamp() == getEpochStartTimestamp() + ONE_WEEK ", async function () { + expect(await gaugeController.getEpochEndTimestamp()).eq((await gaugeController.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("setUpdater", () => { + it("non governor cannot setUpdater", async () => { + await expect(bribeController.connect(voter1).setUpdater(updater.address)).to.be.revertedWith("!governance"); + }); + it("can set updater", async () => { + let tx = await bribeController.connect(governor).setUpdater(updater.address); + await expect(tx).to.emit(bribeController, "UpdaterSet").withArgs(updater.address); + expect(await bribeController.updater()).eq(updater.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 1K of bribeToken1 and 1K of bribeToken2 as a bribe over 1 epoch for gauge 1 + + describe("provideBribes", () => { + it("will throw if bribeToken and bribeAmount arrays mismatched", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1], 1, 1)).to.be.revertedWith("ArrayArgumentsLengthMismatch"); + }); + it("will throw if bribe for 0 epochs", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1, 1], 1, 0)).to.be.revertedWith("CannotBribeFor0Epochs"); + }); + it("will throw if bribe for inactive gauge", async () => { + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1, 1], 0, 1)).to.be.revertedWith("CannotBribeForInactiveGauge"); + await gaugeController.connect(governor).addGauge("1", ONE_PERCENT); + }); + it("will throw if bribe with non-whitelisted token", async () => { + await expect(bribeController.connect(briber1).provideBribes([token.address, bribeToken2.address], [1, 1], 1, 1)).to.be.revertedWith("CannotBribeWithNonWhitelistedToken"); + }); + it("can provide bribe", async () => { + await bribeToken1.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(10000)); + await bribeToken2.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(10000)); + 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], 1, 1) + await expect(tx).to.emit(bribeController, "BribeProvided").withArgs(briber1.address, 1, bribeToken1.address, BRIBE_AMOUNT, 1); + await expect(tx).to.emit(bribeController, "BribeProvided").withArgs(briber1.address, 1, bribeToken2.address, BRIBE_AMOUNT, 1); + + 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) + + expect(await bribeController.getProvidedBribesForCurrentEpoch(1)).deep.eq([]); + const providedBribes = await bribeController.getProvidedBribesForNextEpoch(1); + expect(providedBribes[0].bribeAmount).eq(BRIBE_AMOUNT) + expect(providedBribes[1].bribeAmount).eq(BRIBE_AMOUNT) + expect(providedBribes[0].bribeToken).eq(bribeToken1.address) + expect(providedBribes[1].bribeToken).eq(bribeToken2.address) + }); + it("claimBribes does not revert, but does not do anything", async () => { + const tx = await bribeController.connect(voter1).claimBribes(); + await expect(tx).to.not.emit(bribeController, "BribeClaimed") + }); + }); + + /******************* + STATE SUMMARY + *******************/ + /** + * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the current epoch + */ + + /********************* + INTENTION STATEMENT + *********************/ + // voter1 will create lockID 1, and vote with 10000 votePowerBPS for gaugeID 1 + + describe("distributeBribes", () => { + it("will throw if called by non-governor or non-updater", async () => { + await expect(bribeController.connect(briber1).distributeBribes()).to.be.revertedWith("NotUpdaterNorGovernance"); + }); + it("will throw if gauge weights not yet updated", async () => { + await expect(bribeController.connect(updater).distributeBribes()).to.be.revertedWith("GaugeWeightsNotYetUpdated"); + }); + it("will throw if called in the first epoch of contract deployment", async () => { + await gaugeController.connect(governor).updateGaugeWeights() + await gaugeController.connect(governor).addVotingContract(voting.address) + await underwritingLocker.connect(governor).setVotingContract() + await gaugeController.connect(governor).addTokenholder(underwritingLocker.address) + await voting.connect(governor).chargePremiums() + await expect(bribeController.connect(updater).distributeBribes()).to.be.revertedWith("BribesAlreadyDistributed"); + }); + it("can distributeBribe", async () => { + // CREATE LOCK AND VOTE + 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); + await voting.connect(voter1).vote(voter1.address, 1, 10000); + + await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + const newEpochStartTimestamp = await bribeController.getEpochStartTimestamp() + await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + const tx = await bribeController.connect(governor).distributeBribes({gasLimit: CUSTOM_GAS_LIMIT}) + await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) + await expect(tx).to.emit(bribeController, "BribesDistributed").withArgs(newEpochStartTimestamp); + }) + }); + + /******************* + STATE SUMMARY + *******************/ + /** + * voter1 => lockID1, voted for gaugeID 1 with 10000 votePowerBPS + * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the epoch just past + */ + + describe("claimBribes", () => { + it("getClaimableBribes", 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([]); + }) + }); + +}); \ No newline at end of file diff --git a/test/utilities/artifact_importer.ts b/test/utilities/artifact_importer.ts index 42f3136..9f8033c 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; } From 3e48bfc48b72883de2a2bd9647a3464be39243bb Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Sat, 27 Aug 2022 05:24:26 +1000 Subject: [PATCH 02/24] added voteForBribe functions to BribeController.sol --- .../interfaces/native/IBribeController.sol | 75 ++++- contracts/native/BribeController.sol | 258 ++++++++++++++---- contracts/native/UnderwritingLockVoting.sol | 2 +- 3 files changed, 267 insertions(+), 68 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index e0aebc2..7da4352 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -36,6 +36,9 @@ interface IBribeController { /// @notice Thrown when voteForBribe() attempted by a non-owner or non-delegate. error NotOwnerNorDelegate(); + /// @notice Thrown when attempting to remove vote that does not exist. + error CannotRemoveNonExistentVote(); + /// @notice Thrown when voteForBribe() attempted for gauge without bribe. error NoBribesForSelectedGauge(); @@ -55,6 +58,15 @@ interface IBribeController { /// @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); @@ -111,6 +123,13 @@ interface IBribeController { */ 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 list of whitelisted bribe tokens. * @return whitelist @@ -125,18 +144,18 @@ interface IBribeController { function getClaimableBribes(address voter_) external view returns (Bribe[] memory bribes); /** - * @notice Get bribes which have been provided for the current epoch for a given gauge. - * @param gaugeID_ GaugeID to query for. - * @return bribes Array of provided bribes. + * @notice Get all gaugeIDs with bribe/s offered in the present epoch. + * @return gauges Array of gaugeIDs with current bribe. */ - function getProvidedBribesForCurrentEpoch(uint256 gaugeID_) external view returns (Bribe[] memory bribes); + function getAllGaugesWithBribe() external view returns (uint256[] memory gauges); /** - * @notice Get bribes which have been provided for the next epoch for a given gauge. + * @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 getProvidedBribesForNextEpoch(uint256 gaugeID_) external view returns (Bribe[] memory 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. @@ -168,8 +187,50 @@ interface IBribeController { * @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_) external; + 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 removeVoteForMultipleBribes(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. */ diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 8e7acf9..fe565ae 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -13,8 +13,6 @@ import "./../utils/EnumerableMapS.sol"; import "./../utils/Governable.sol"; import "hardhat/console.sol"; -// ? PROVIDE WITHDRAW BRIBE FUNCTION? - contract BribeController is IBribeController, ReentrancyGuard, @@ -22,6 +20,7 @@ contract BribeController is { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableMapS for EnumerableMapS.AddressToUintMap; + using EnumerableMapS for EnumerableMapS.UintToUintMap; /*************************************** GLOBAL PUBLIC VARIABLES @@ -49,12 +48,12 @@ contract BribeController is uint256 constant internal WEEK = 604800; - /// @notice epochEndTimeStamp => gaugeID => bribeToken => totalBribeAmount. - mapping(uint256 => mapping(uint256 => EnumerableMapS.AddressToUintMap)) internal _providedBribesA; - /// @notice gaugeID => bribeToken => bribeAmount. mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _providedBribes; + /// @notice voters. + EnumerableSet.AddressSet internal _voters; + /// @notice voter => Vote (gaugeID => votePowerBPS). mapping(address => EnumerableMapS.UintToUintMap) internal _votes; @@ -115,6 +114,15 @@ contract BribeController is return ( !this.governanceIsLocked() && ( msg.sender == updater || msg.sender == this.governance() )); } + /** + * @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_)); + } + /*************************************** EXTERNAL VIEW FUNCTIONS ***************************************/ @@ -135,6 +143,15 @@ contract BribeController is 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 list of whitelisted bribe tokens. * @return whitelist @@ -163,32 +180,48 @@ contract BribeController is } /** - * @notice Get bribes which have been provided for the current epoch for a given gauge. - * @param gaugeID_ GaugeID to query for. - * @return bribes Array of provided bribes. + * @notice Get all gaugeIDs with bribe/s offered in the present epoch. + * @dev We use a convoluted implementation here to avoid introducing an extra state variable into the contract just for the sake of this function. + * @dev We accept a worse than optimal space/time complexity here because we aren't paying a penalty for that in an external view function. + * @return gauges Array of gaugeIDs with current bribe. */ - function getProvidedBribesForCurrentEpoch(uint256 gaugeID_) external view override returns (Bribe[] memory bribes) { - uint256 epochStartTimeStamp = _getEpochStartTimestamp(); - uint256 length = _providedBribesA[epochStartTimeStamp][gaugeID_].length(); - bribes = new Bribe[](length); - for (uint256 i = 0; i < length; i++) { - (address bribeToken, uint256 bribeAmount) = _providedBribesA[epochStartTimeStamp][gaugeID_].at(i); - bribes[i] = Bribe(bribeToken, bribeAmount); + function getAllGaugesWithBribe() external view override returns (uint256[] memory gauges) { + // Get count of gauges with bribes, so that we know required length of gauges array. + // Cannot use dynamic array in memory in Solidity. + uint256 countGaugesWithBribes = 0; + uint256 totalGauges = IGaugeController(gaugeController).totalGauges(); + for (uint256 i = 0; i < totalGauges; i++) { + if(_providedBribes[i + 1].length() > 0) countGaugesWithBribes += 1; + } + + // Create gauges array. + gauges = new uint256[](countGaugesWithBribes); + uint256 gauges_index = 0; + uint256 gaugeID = 0; + + // Use nested loop to fill in gauges array. + while (gauges_index < countGaugesWithBribes) { + while (true) { + gaugeID += 1; + if(_providedBribes[gaugeID].length() > 0) { + gauges[gauges_index] = gaugeID; + break; + } + } + gauges_index += 1; } - return bribes; } /** - * @notice Get bribes which have been provided for the next epoch for a given gauge. + * @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 getProvidedBribesForNextEpoch(uint256 gaugeID_) external view override returns (Bribe[] memory bribes) { - uint256 epochEndTimeStamp = _getEpochEndTimestamp(); - uint256 length = _providedBribesA[epochEndTimeStamp][gaugeID_].length(); + 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) = _providedBribesA[epochEndTimeStamp][gaugeID_].at(i); + (address bribeToken, uint256 bribeAmount) = _providedBribes[gaugeID_].at(i); bribes[i] = Bribe(bribeToken, bribeAmount); } return bribes; @@ -233,6 +266,60 @@ contract BribeController is 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 _removeVoteForBribe(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_); + } + + /** + * @notice Add, change or remove vote for bribe. + * Can only be called by the voter or their delegate. + * @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. + */ + function _voteForBribe(address voter_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_) internal nonReentrant { + // CHECKS + if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); + if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); + + 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 + IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS); + (bool votePresent, uint256 oldVotePowerBPS) = _votes[voter_].tryGet(gaugeID); + + // If remove vote + if (votePowerBPS == 0) { + if (!votePresent) revert CannotRemoveNonExistentVote(); + _votes[voter_].remove(gaugeID); + if (_votes[voter_].length() == 0) _voters.remove(voter_); + emit VoteForBribeRemoved(voter_, gaugeID); + } else { + _voters.add(voter_); + _votes[voter_].set(gaugeID, votePowerBPS); + + // Change vote + if(votePresent) { + emit VoteForBribeChanged(voter_, gaugeID, votePowerBPS, oldVotePowerBPS); + // Add vote + } else { + emit VoteForBribeAdded(voter_, gaugeID, votePowerBPS); + } + } + } + } + /*************************************** BRIBER FUNCTIONS ***************************************/ @@ -286,23 +373,74 @@ contract BribeController is * @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_) external override nonReentrant { - // CHECKS - if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); - if (_providedBribes[gaugeID_].length() == 0) revert NoBribesForSelectedGauge(); + function voteForBribe(address voter_, uint256 gaugeID_, uint256 votePowerBPS_) external override { + uint256[] memory gaugeIDs_ = new uint256[](1); + uint256[] memory votePowerBPSs_ = new uint256[](1); + gaugeIDs_[0] = gaugeID_; + votePowerBPSs_[0] = votePowerBPS_; + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_); + } + + /** + * @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 { + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_); + } + /** + * @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 { + uint256 length = voters_.length; + for (uint256 i = 0; i < length; i++) { + _voteForBribe(voters_[i], gaugeIDs_, votePowerBPSs_); + } + } + /** + * @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 { + _removeVoteForBribe(voter_, gaugeID_); + } - // uint256 length = _claimableBribes[msg.sender].length(); - // if (length == 0) return; + /** + * @notice Remove multiple votes for bribes. + * @param voter_ address of voter. + * @param gaugeIDs_ Array of gaugeIDs to remove votes for + */ + function removeVoteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external override { + uint256[] memory votePowerBPSs_ = new uint256[](gaugeIDs_.length); + for(uint256 i = 0; i < gaugeIDs_.length; i++) {votePowerBPSs_[i] = 0;} + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_); + } - // while (_claimableBribes[msg.sender].length() != 0) { - // (address bribeToken, uint256 bribeAmount) = _claimableBribes[msg.sender].at(0); - // _claimableBribes[msg.sender].remove(bribeToken); - // SafeERC20.safeTransfer(IERC20(bribeToken), msg.sender, bribeAmount); - // emit BribeClaimed(msg.sender, bribeToken, bribeAmount); - // } + /** + * @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 { + 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_); + } } // Should delegate also be able to claim bribes for user? @@ -396,38 +534,38 @@ contract BribeController is } // INTERNAL STATE MUTATIONS - // ITERATE THROUGH EPOCHS, FROM `lastTimeBribesDistributed + WEEK` TO `currentEpochStartTime` - while (lastTimeBribesDistributed < currentEpochStartTime) { - lastTimeBribesDistributed += WEEK; - - // ITERATE THROUGH VOTERS - address[] memory voters = IGaugeController(gaugeController).getVoters(votingContract); - for(uint256 i = _updateInfo._votersIndex == type(uint88).max ? 0 : _updateInfo._votersIndex ; i < voters.length; i++) { - - // ITERATE THROUGH VOTES - GaugeStructs.Vote[] memory votes = IGaugeController(gaugeController).getVotes(votingContract, voters[i]); - - for(uint256 j = _updateInfo._votesIndex == type(uint88).max || i != _updateInfo._votersIndex ? 0 : _updateInfo._votesIndex; j < votes.length; j++) { - - if (gaugeVotePower[votes[j].gaugeID] > 0) { - // ITERATE THROUGH BRIBE TOKENS - for(uint256 k = _updateInfo._votingContractsIndex == type(uint80).max || j != _updateInfo._votesIndex ? 0 : _updateInfo._votingContractsIndex; k < _providedBribesA[lastTimeBribesDistributed][votes[j].gaugeID].length(); k++) { - // CHECKPOINT - if (gasleft() < 30000) {return _saveUpdateState(k, i, j);} - - (address bribeToken, uint256 totalBribeAmount) = _providedBribesA[lastTimeBribesDistributed][votes[j].gaugeID].at(k); - uint256 proportionalBribeAmount = totalBribeAmount * IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voters[i]) / gaugeVotePower[votes[j].gaugeID]; - (,uint256 runningBribeTotalForVoter) = _claimableBribes[voters[i]].tryGet(bribeToken); - // STATE CHANGE 1 - ADD TO CLAIMABLE BRIBES OF USER - _claimableBribes[voters[i]].set(bribeToken, runningBribeTotalForVoter + proportionalBribeAmount); - } + + // ITERATE THROUGH VOTERS + while (_voters.length() > 0) { + address voter = _voters.at(0); + + // ITERATE THROUGH VOTES + while(_votes[voter].length() > 0) { + (uint256 gaugeID, uint256 votePowerBPS) = _votes[voter].at(0); + + // ITERATE THROUGH BRIBE TOKENS + if (gaugeVotePower[gaugeID] > 0) { + uint256 numBribeTokensForGauge = _providedBribes[gaugeID].length(); + for(uint256 i = _updateInfo._votingContractsIndex == type(uint80).max ? 0 : _updateInfo._votingContractsIndex; i < numBribeTokensForGauge; i++) { + // CHECKPOINT + if (gasleft() < 30000) {return _saveUpdateState(i, type(uint88).max, type(uint88).max);} + + (address bribeToken, uint256 totalBribeAmount) = _providedBribes[gaugeID].at(i); + uint256 proportionalBribeAmount = totalBribeAmount * IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) / gaugeVotePower[gaugeID]; + (,uint256 runningBribeTotalForVoter) = _claimableBribes[voter].tryGet(bribeToken); + // STATE CHANGE 1 - ADD TO CLAIMABLE BRIBES OF USER + _claimableBribes[voter].set(bribeToken, runningBribeTotalForVoter + proportionalBribeAmount); } + + // Cleanup both _votes and _voters[voter] enumerable collections. + _removeVoteForBribe(voter, gaugeID); } } - - emit BribesDistributed(lastTimeBribesDistributed); } + // If leftover bribes? + + emit BribesDistributed(currentEpochStartTime); _clearUpdateInfo(); } diff --git a/contracts/native/UnderwritingLockVoting.sol b/contracts/native/UnderwritingLockVoting.sol index 94526ec..a0fffd4 100644 --- a/contracts/native/UnderwritingLockVoting.sol +++ b/contracts/native/UnderwritingLockVoting.sol @@ -331,7 +331,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(); From 3a313eef7086f51dcbf27976bab5cc63ff381277 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Sat, 27 Aug 2022 19:48:00 +1000 Subject: [PATCH 03/24] added processBribes implementation, added ability to vote on contract deployment of UnderwritingLockVoting --- .../interfaces/native/IBribeController.sol | 35 ++- contracts/native/BribeController.sol | 230 +++++++++++------- contracts/native/GaugeController.sol | 1 + contracts/native/UnderwritingLockVoting.sol | 1 + 4 files changed, 175 insertions(+), 92 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index 7da4352..3fae2fc 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -13,6 +13,11 @@ interface IBribeController { uint256 bribeAmount; } + struct VoteForGauge { + address voter; + uint256 votePowerBPS; + } + /*************************************** CUSTOM ERRORS ***************************************/ @@ -42,14 +47,17 @@ interface IBribeController { /// @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 when distributeBribes() is called by neither governance nor updater, or governance is locked. error NotUpdaterNorGovernance(); /// @notice Thrown if distributeBribes() is called after bribes have already been successfully distributed in the current epoch. error BribesAlreadyDistributed(); - /// @notice Thrown when distributeBribes is attempted before the last epoch's votes have been successfully processed through gaugeController.updateGaugeWeights(). - error GaugeWeightsNotYetUpdated(); + /// @notice Thrown when distributeBribes is attempted before the last epoch's premiums have been successfully charged through underwritingLockVoting.chargePremiums(). + error LastEpochPremiumsNotCharged(); /*************************************** EVENTS @@ -83,7 +91,7 @@ interface IBribeController { event BribeTokenRemoved(address indexed bribeToken); /// @notice Emitted when distributeBribes() does an incomplete update, and will need to be run again until completion. - event IncompleteBribeDistribution(); + event IncompleteBribeProcessing(); /// @notice Emitted when bribes distributed for an epoch. event BribesDistributed(uint256 indexed epochEndTimestamp); @@ -101,11 +109,14 @@ interface IBribeController { /// @notice Address of UnderwritingLockVoting.sol function votingContract() external view returns (address); + /// @notice Address of revenue router. + function revenueRouter() external view returns (address); + /// @notice Updater address. function updater() external view returns (address); - /// @notice End timestamp for last epoch that bribes were distributed for all stored votes. - function lastTimeBribesDistributed() external view returns (uint256); + /// @notice End timestamp for last epoch that bribes were processed for all stored votes. + function lastTimeBribesProcessed() external view returns (uint256); /*************************************** EXTERNAL VIEW FUNCTIONS @@ -163,6 +174,20 @@ interface IBribeController { */ 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); + /*************************************** BRIBER FUNCTIONS ***************************************/ diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index fe565ae..439e194 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -19,6 +19,7 @@ contract BribeController is Governable { using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableSet for EnumerableSet.UintSet; using EnumerableMapS for EnumerableMapS.AddressToUintMap; using EnumerableMapS for EnumerableMapS.UintToUintMap; @@ -35,33 +36,36 @@ contract BribeController is /// @notice UnderwriterLockVoting.sol address address public override votingContract; + /// @notice Revenue router address + address public override revenueRouter; + /// @notice Updater address. /// @dev Second address that can call updateGaugeWeights (in addition to governance). address public override updater; - /// @notice End timestamp for last epoch that bribes were distributed for all stored votes. - uint256 public override lastTimeBribesDistributed; + /// @notice End timestamp for last epoch that bribes were processed for all stored votes. + uint256 public override lastTimeBribesProcessed; /*************************************** GLOBAL INTERNAL VARIABLES ***************************************/ - uint256 constant internal WEEK = 604800; - /// @notice gaugeID => bribeToken => bribeAmount. mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _providedBribes; - /// @notice voters. - EnumerableSet.AddressSet internal _voters; - - /// @notice voter => Vote (gaugeID => votePowerBPS). - mapping(address => EnumerableMapS.UintToUintMap) internal _votes; + /// @notice briber => bribeToken => lifetimeOfferedBribeAmount. + mapping(address => EnumerableMapS.AddressToUintMap) internal _lifetimeProvidedBribes; /// @notice voter => bribeToken => claimableBribeAmount. mapping(address => EnumerableMapS.AddressToUintMap) internal _claimableBribes; - /// @notice briber => bribeToken => claimableBribeAmount. - mapping(address => EnumerableMapS.AddressToUintMap) internal _lifetimeProvidedBribes; + /// @notice gaugeID => total vote power + /// @dev We use this to i.) Store an enumerable collection of gauges with bribes, + /// @dev and ii.) Store total vote power chaisng bribes for each gauge. + EnumerableMapS.UintToUintMap internal _gaugeToTotalVotePower; + + /// @notice gaugeID => voter => votePowerBPS. + mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _votes; /// @notice whitelist of tokens that can be accepted as bribes EnumerableSet.AddressSet internal _bribeTokenWhitelist; @@ -83,7 +87,7 @@ contract BribeController is { _setRegistry(registry_); _clearUpdateInfo(); - lastTimeBribesDistributed = _getEpochStartTimestamp(); + lastTimeBribesProcessed = _getEpochStartTimestamp(); } /*************************************** @@ -181,34 +185,14 @@ contract BribeController is /** * @notice Get all gaugeIDs with bribe/s offered in the present epoch. - * @dev We use a convoluted implementation here to avoid introducing an extra state variable into the contract just for the sake of this function. - * @dev We accept a worse than optimal space/time complexity here because we aren't paying a penalty for that in an external view function. * @return gauges Array of gaugeIDs with current bribe. */ function getAllGaugesWithBribe() external view override returns (uint256[] memory gauges) { - // Get count of gauges with bribes, so that we know required length of gauges array. - // Cannot use dynamic array in memory in Solidity. - uint256 countGaugesWithBribes = 0; - uint256 totalGauges = IGaugeController(gaugeController).totalGauges(); - for (uint256 i = 0; i < totalGauges; i++) { - if(_providedBribes[i + 1].length() > 0) countGaugesWithBribes += 1; - } - - // Create gauges array. - gauges = new uint256[](countGaugesWithBribes); - uint256 gauges_index = 0; - uint256 gaugeID = 0; - - // Use nested loop to fill in gauges array. - while (gauges_index < countGaugesWithBribes) { - while (true) { - gaugeID += 1; - if(_providedBribes[gaugeID].length() > 0) { - gauges[gauges_index] = gaugeID; - break; - } - } - gauges_index += 1; + uint256 length = _gaugeToTotalVotePower.length(); + gauges = new uint256[](length); + for (uint256 i = 0; i < length; i++) { + (uint256 gaugeID,) = _gaugeToTotalVotePower.at(i); + gauges[i] = gaugeID; } } @@ -242,6 +226,59 @@ contract BribeController is 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); + } + } + /*************************************** INTERNAL MUTATOR FUNCTIONS ***************************************/ @@ -263,6 +300,10 @@ contract BribeController is (, address underwritingLockVoting) = reg.tryGet("underwritingLockVoting"); if(underwritingLockVoting == address(0x0)) revert ZeroAddressInput("underwritingLockVoting"); votingContract = underwritingLockVoting; + // set revenueRouter + (, address revenueRouterAddr) = reg.tryGet("revenueRouter"); + if(revenueRouterAddr == address(0x0)) revert ZeroAddressInput("revenueRouter"); + revenueRouter = revenueRouterAddr; emit RegistrySet(_registry); } @@ -290,6 +331,7 @@ contract BribeController is // CHECKS if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); + if ( _getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); for(uint256 i = 0; i < gaugeIDs_.length; i++) { uint256 gaugeID = gaugeIDs_[i]; @@ -297,17 +339,17 @@ contract BribeController is if (_providedBribes[gaugeID].length() == 0) revert NoBribesForSelectedGauge(); // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS); - (bool votePresent, uint256 oldVotePowerBPS) = _votes[voter_].tryGet(gaugeID); + (bool votePresent, uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); // If remove vote if (votePowerBPS == 0) { if (!votePresent) revert CannotRemoveNonExistentVote(); - _votes[voter_].remove(gaugeID); - if (_votes[voter_].length() == 0) _voters.remove(voter_); + _votes[gaugeID].remove(voter_); + if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); emit VoteForBribeRemoved(voter_, gaugeID); } else { - _voters.add(voter_); - _votes[voter_].set(gaugeID, votePowerBPS); + _gaugeToTotalVotePower.set(gaugeID, 0); + _votes[gaugeID].set(voter_, votePowerBPS); // Change vote if(votePresent) { @@ -338,6 +380,7 @@ contract BribeController is // CHECKS if (bribeTokens_.length != bribeAmounts_.length) revert ArrayArgumentsLengthMismatch(); if (!IGaugeController(gaugeController).isGaugeActive(gaugeID_)) revert CannotBribeForInactiveGauge(); + if ( _getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); uint256 length = _bribeTokenWhitelist.length(); for (uint256 i = 0; i < length; i++) { @@ -504,15 +547,16 @@ contract BribeController is emit BribeTokenRemoved(bribeToken_); } - // IDEAL FLOW is gaugeController.updateGaugeWeights() => BribeController.distributeBribes() => UnderwritingLockVoting.chargePremiums() - // We don't have an onchain mechanism to enforce this flow in the current implementation. - // However to calculate bribes accurately, we need individual votes, individual vote power, and vote power for each gauge from the same state. And this can only be guaranteed in with the flow above. + // To get bribe allocated to a vote, need all votes allocated to the gauge to calculate total votepower allocated to that gauge - // Very cumbersome to enforce this flow on-chain, should re-architect as being completed in one function in an stateless off-chain node. + // Need two iterations + // 1.) Iterate to find total votepower to each gaugeID + // 2.) Iterate to allocate bribes to each voter, also cleanup of _providedBribes, _voters and _votes enumerable collections + // Leftover bribes to revenueRouter /** * @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`. + * @dev Designed to be called in a while-loop with custom gas limit of 6M until `lastTimeBribesProcessed == epochStartTimestamp`. * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. */ function processBribes() external override { @@ -520,50 +564,62 @@ contract BribeController is if (!_isUpdaterOrGovernance()) revert NotUpdaterNorGovernance(); uint256 currentEpochStartTime = _getEpochStartTimestamp(); // Require gauge weights to have been updated for this epoch => ensure state we are querying from is < 1 WEEK old. - if(IGaugeController(gaugeController).lastTimeGaugeWeightsUpdated() != currentEpochStartTime) revert GaugeWeightsNotYetUpdated(); - if (lastTimeBribesDistributed >= currentEpochStartTime) revert BribesAlreadyDistributed(); - - // GET REQUIRED EXTERNAL DATA - uint256 votePowerSum = IGaugeController(gaugeController).getVotePowerSum(); - uint256[] memory gaugeVotePower = IGaugeController(gaugeController).getAllGaugeWeights(); - - for (uint256 i = 0; i < gaugeVotePower.length; i++) { - // Convert from gaugeWeight to votePower. - // Reassign variable instead of using new variable to save stack space. - gaugeVotePower[i] = gaugeVotePower[i] * votePowerSum / 1e18; + if(IUnderwritingLockVoting(votingContract).lastTimePremiumsCharged() != currentEpochStartTime) revert LastEpochPremiumsNotCharged(); + if (lastTimeBribesProcessed >= currentEpochStartTime) revert BribesAlreadyDistributed(); + + // LOOP 1 - GET TOTAL VOTE POWER FOR EACH GAUGE + // Block-scope to avoid stack too deep error + { + uint256 numGauges = _gaugeToTotalVotePower.length(); + // Iterate by gauge + for (uint256 i = 0; i < numGauges; i++) { + // Iterate by vote + (uint256 gaugeID, uint256 runningVotePowerSum) = _gaugeToTotalVotePower.at(i); + uint256 numVotes = _votes[gaugeID].length(); + + for (uint256 j = 0; j < numVotes; j++) { + (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(j); + uint256 votePower = IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter); + // State mutation 1 + _gaugeToTotalVotePower.set(gaugeID, runningVotePowerSum + (votePower * votePowerBPS) / 10000); + } + } } - // INTERNAL STATE MUTATIONS - - // ITERATE THROUGH VOTERS - while (_voters.length() > 0) { - address voter = _voters.at(0); - - // ITERATE THROUGH VOTES - while(_votes[voter].length() > 0) { - (uint256 gaugeID, uint256 votePowerBPS) = _votes[voter].at(0); - - // ITERATE THROUGH BRIBE TOKENS - if (gaugeVotePower[gaugeID] > 0) { - uint256 numBribeTokensForGauge = _providedBribes[gaugeID].length(); - for(uint256 i = _updateInfo._votingContractsIndex == type(uint80).max ? 0 : _updateInfo._votingContractsIndex; i < numBribeTokensForGauge; i++) { - // CHECKPOINT - if (gasleft() < 30000) {return _saveUpdateState(i, type(uint88).max, type(uint88).max);} - - (address bribeToken, uint256 totalBribeAmount) = _providedBribes[gaugeID].at(i); - uint256 proportionalBribeAmount = totalBribeAmount * IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) / gaugeVotePower[gaugeID]; - (,uint256 runningBribeTotalForVoter) = _claimableBribes[voter].tryGet(bribeToken); - // STATE CHANGE 1 - ADD TO CLAIMABLE BRIBES OF USER - _claimableBribes[voter].set(bribeToken, runningBribeTotalForVoter + proportionalBribeAmount); - } - - // Cleanup both _votes and _voters[voter] enumerable collections. - _removeVoteForBribe(voter, gaugeID); + // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS + // _gaugeToTotalVotePower, _votes, _voters, _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 + while(_votes[gaugeID].length() > 0) { + (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(0); + uint256 bribeProportion = (IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) * votePowerBPS / 10000) * 1e18 / votePowerSum; + + // Iterate by bribeToken + uint256 numBribeTokens = _providedBribes[gaugeID].length(); + for (uint256 k = 0; k < numBribeTokens; k++) { + // State mutation 2 + (address bribeToken, uint256 totalBribeAmount) = _providedBribes[gaugeID].at(k); + (, uint256 runningClaimableAmount) = _claimableBribes[voter].tryGet(bribeToken); + uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; + _providedBribes[gaugeID].set(bribeToken, totalBribeAmount - bribeAmount); // Should not underflow as integers round down in Solidity. + _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); } + // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. + _removeVoteForBribe(voter, gaugeID); } - } - // If leftover bribes? + // Cleanup _providedBribes enumerable collection. + // Send leftover bribes to revenueRouter. + while(_providedBribes[gaugeID].length() > 0) { + (address bribeToken, uint256 remainingBribeAmount) = _providedBribes[gaugeID].at(0); + SafeERC20.safeTransfer(IERC20(bribeToken), revenueRouter, remainingBribeAmount); + _providedBribes[gaugeID].remove(bribeToken); + } + } + } emit BribesDistributed(currentEpochStartTime); _clearUpdateInfo(); @@ -587,7 +643,7 @@ contract BribeController is updateInfo := or(updateInfo, shl(168, votesIndex_)) // [168:256] => votesIndex_ sstore(_updateInfo.slot, updateInfo) } - emit IncompleteBribeDistribution(); + emit IncompleteBribeProcessing(); } /// @notice Reset _updateInfo to starting state. diff --git a/contracts/native/GaugeController.sol b/contracts/native/GaugeController.sol index a7bedfd..b495bf7 100644 --- a/contracts/native/GaugeController.sol +++ b/contracts/native/GaugeController.sol @@ -111,6 +111,7 @@ contract GaugeController is _gauges.push(GaugeStructs.Gauge(false, 0, "")); _clearUpdateInfo(); _epochLength = WEEK; + lastTimeGaugeWeightsUpdated = _getEpochStartTimestamp(); } /*************************************** diff --git a/contracts/native/UnderwritingLockVoting.sol b/contracts/native/UnderwritingLockVoting.sol index a0fffd4..4f6daf7 100644 --- a/contracts/native/UnderwritingLockVoting.sol +++ b/contracts/native/UnderwritingLockVoting.sol @@ -111,6 +111,7 @@ contract UnderwritingLockVoting is // Initialize as non-zero storage slots. _totalPremiumDue = type(uint256).max; _clearUpdateInfo(); + lastTimePremiumsCharged = _getEpochStartTimestamp(); } /*************************************** From 12bb106fadeedf695815f576a2897fe145cbbb3c Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Sun, 28 Aug 2022 04:16:32 +1000 Subject: [PATCH 04/24] fixed gaugecontroller and underwritinglockvoting tests for enabling voting on deployment --- test/native/GaugeController.test.ts | 10 ++- test/native/UnderwritingLockVoting.test.ts | 80 ++++++++-------------- 2 files changed, 31 insertions(+), 59 deletions(-) diff --git a/test/native/GaugeController.test.ts b/test/native/GaugeController.test.ts index a44b2f5..b530ea5 100644 --- a/test/native/GaugeController.test.ts +++ b/test/native/GaugeController.test.ts @@ -76,7 +76,7 @@ describe("GaugeController", function () { 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); @@ -306,9 +306,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 +327,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,6 +351,9 @@ describe("GaugeController", function () { expect(await gaugeController.getVoteCount(voting.address, voter1.address)).eq(1) expect(await gaugeController.getVotersCount(voting.address)).eq(1) }); + it("cannot update gauge in same epoch as contract deployment", async () => { + await expect(gaugeController.connect(governor).updateGaugeWeights()).to.be.revertedWith("GaugeWeightsAlreadyUpdated"); + }); it("updater can updateGaugeWeight in next epoch", async () => { const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); diff --git a/test/native/UnderwritingLockVoting.test.ts b/test/native/UnderwritingLockVoting.test.ts index 64c6ce1..44ed9b9 100644 --- a/test/native/UnderwritingLockVoting.test.ts +++ b/test/native/UnderwritingLockVoting.test.ts @@ -91,8 +91,8 @@ describe("UnderwritingLockVoting", function () { 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 +117,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") }); }); @@ -351,53 +351,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,28 +469,48 @@ 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]); + await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("GaugeWeightsNotYetUpdated"); + }); + it("updateGaugeWeights() should revert if non governor or updater", async function () { + await expect(gaugeController.connect(voter1).updateGaugeWeights()).to.be.revertedWith("NotUpdaterNorGovernance"); + }); + it("updateGaugeWeights() called by updater should succeed in the next epoch", async function () { const tx = await gaugeController.connect(updater).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() should revert if non governor or updater", async function () { + await expect(voting.connect(voter1).chargePremiums()).to.be.revertedWith("NotUpdaterNorGovernance"); + }); it("chargePremiums() call by updater 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(); @@ -553,6 +526,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) }); }); From 7f6980cfebe3acd1ae3426f4a677423786b38a87 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Sun, 28 Aug 2022 04:31:23 +1000 Subject: [PATCH 05/24] changed struct element name for UpdateInfo --- contracts/interfaces/native/GaugeStructs.sol | 9 +++------ contracts/native/BribeController.sol | 3 +-- contracts/native/GaugeController.sol | 8 ++++---- contracts/native/UnderwritingLockVoting.sol | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/contracts/interfaces/native/GaugeStructs.sol b/contracts/interfaces/native/GaugeStructs.sol index 17fa080..be10038 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/native/BribeController.sol b/contracts/native/BribeController.sol index 439e194..1c63d4b 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -19,7 +19,6 @@ contract BribeController is Governable { using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.UintSet; using EnumerableMapS for EnumerableMapS.AddressToUintMap; using EnumerableMapS for EnumerableMapS.UintToUintMap; @@ -587,7 +586,7 @@ contract BribeController is } // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS - // _gaugeToTotalVotePower, _votes, _voters, _votes and _providedBribes enumerable collections should be empty at the end. + // _gaugeToTotalVotePower, _votes and _providedBribes enumerable collections should be empty at the end. { // Iterate by gauge while (_gaugeToTotalVotePower.length() > 0) { diff --git a/contracts/native/GaugeController.sol b/contracts/native/GaugeController.sol index b495bf7..10d6c4a 100644 --- a/contracts/native/GaugeController.sol +++ b/contracts/native/GaugeController.sol @@ -575,19 +575,19 @@ contract GaugeController is */ 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(); @@ -601,7 +601,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 4f6daf7..d21f66f 100644 --- a/contracts/native/UnderwritingLockVoting.sol +++ b/contracts/native/UnderwritingLockVoting.sol @@ -494,7 +494,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])) { @@ -508,7 +508,7 @@ contract UnderwritingLockVoting is // 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++) { + for(uint256 j = _updateInfo.index3 == type(uint88).max || i != _updateInfo.index2 ? 0 : _updateInfo.index3; j < numLocks; j++) { if (gasleft() < 20000) {return _saveUpdateState(0, i, j);} // Split premium amongst each lock equally. IUnderwritingLocker(underwritingLocker).chargePremium(lockIDs[j], premium / numLocks); From fa26ab8b4085ef505a93b6e81369bd71c46c2a4f Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Sun, 28 Aug 2022 06:32:25 +1000 Subject: [PATCH 06/24] complete bribe tests up to voteForBribe --- .../interfaces/native/IBribeController.sol | 31 ++- contracts/native/BribeController.sol | 77 +++--- test/native/BribeController.test.ts | 219 +++++++++++------- 3 files changed, 209 insertions(+), 118 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index 3fae2fc..0ac02b0 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -35,6 +35,9 @@ interface IBribeController { /// @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(); @@ -50,13 +53,13 @@ interface IBribeController { /// @notice Thrown when offerBribe() or voteForBribe() attempted before last epoch bribes are processed. error LastEpochBribesNotProcessed(); - /// @notice Thrown when distributeBribes() is called by neither governance nor updater, or governance is locked. + /// @notice Thrown when processBribes() is called by neither governance nor updater, or governance is locked. error NotUpdaterNorGovernance(); - /// @notice Thrown if distributeBribes() is called after bribes have already been successfully distributed in the current epoch. - error BribesAlreadyDistributed(); + /// @notice Thrown if processBribes() is called after bribes have already been successfully processed in the current epoch. + error BribesAlreadyProcessed(); - /// @notice Thrown when distributeBribes is attempted before the last epoch's premiums have been successfully charged through underwritingLockVoting.chargePremiums(). + /// @notice Thrown when processBribes is attempted before the last epoch's premiums have been successfully charged through underwritingLockVoting.chargePremiums(). error LastEpochPremiumsNotCharged(); /*************************************** @@ -90,11 +93,11 @@ interface IBribeController { /// @notice Emitted when bribe token removed from whitelist. event BribeTokenRemoved(address indexed bribeToken); - /// @notice Emitted when distributeBribes() does an incomplete update, and will need to be run again until completion. - event IncompleteBribeProcessing(); + /// @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 BribesDistributed(uint256 indexed epochEndTimestamp); + event BribesProcessed(uint256 indexed epochEndTimestamp); /*************************************** GLOBAL VARIABLES @@ -188,17 +191,23 @@ interface IBribeController { */ 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 Offer bribes. + * @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 offerBribes( + function provideBribes( address[] calldata bribeTokens_, uint256[] calldata bribeAmounts_, uint256 gaugeID_ @@ -294,6 +303,10 @@ interface IBribeController { */ function removeBribeToken(address bribeToken_) external; + /*************************************** + UPDATER FUNCTIONS + ***************************************/ + /** * @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`. diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 1c63d4b..5e8bd26 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -278,6 +278,17 @@ contract BribeController is } } + /** + * @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 ***************************************/ @@ -328,9 +339,9 @@ contract BribeController is */ function _voteForBribe(address voter_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_) internal nonReentrant { // CHECKS - if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); if ( _getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); + if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); for(uint256 i = 0; i < gaugeIDs_.length; i++) { uint256 gaugeID = gaugeIDs_[i]; @@ -347,9 +358,8 @@ contract BribeController is if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); emit VoteForBribeRemoved(voter_, gaugeID); } else { - _gaugeToTotalVotePower.set(gaugeID, 0); _votes[gaugeID].set(voter_, votePowerBPS); - + // Change vote if(votePresent) { emit VoteForBribeChanged(voter_, gaugeID, votePowerBPS, oldVotePowerBPS); @@ -366,20 +376,24 @@ contract BribeController is ***************************************/ /** - * @notice Offer bribes. + * @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 offerBribes( + function provideBribes( address[] calldata bribeTokens_, uint256[] calldata bribeAmounts_, uint256 gaugeID_ ) external override nonReentrant { // CHECKS if (bribeTokens_.length != bribeAmounts_.length) revert ArrayArgumentsLengthMismatch(); - if (!IGaugeController(gaugeController).isGaugeActive(gaugeID_)) revert CannotBribeForInactiveGauge(); - if ( _getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); + if (_getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); + try IGaugeController(gaugeController).isGaugeActive(gaugeID_) returns (bool gaugeActive) { + if (!gaugeActive) revert CannotBribeForInactiveGauge(); + } catch { + revert CannotBribeForNonExistentGauge(); + } uint256 length = _bribeTokenWhitelist.length(); for (uint256 i = 0; i < length; i++) { @@ -387,6 +401,9 @@ contract BribeController is } // INTERNAL STATE MUTATIONS + (bool gaugePresent,) = _gaugeToTotalVotePower.tryGet(gaugeID_); + if(!gaugePresent) _gaugeToTotalVotePower.set(gaugeID_, 1); // Do not set to 0 to avoid SSTORE penalty for 0 slot in processBribes(). + for (uint256 i = 0; i < length; i++) { (,uint256 previousBribeSum) = _providedBribes[gaugeID_].tryGet(bribeTokens_[i]); _providedBribes[gaugeID_].set(bribeTokens_[i], previousBribeSum + bribeAmounts_[i]); @@ -546,12 +563,9 @@ contract BribeController is emit BribeTokenRemoved(bribeToken_); } - // To get bribe allocated to a vote, need all votes allocated to the gauge to calculate total votepower allocated to that gauge - - // Need two iterations - // 1.) Iterate to find total votepower to each gaugeID - // 2.) Iterate to allocate bribes to each voter, also cleanup of _providedBribes, _voters and _votes enumerable collections - // Leftover bribes to revenueRouter + /*************************************** + UPDATER FUNCTIONS + ***************************************/ /** * @notice Processes bribes, and makes bribes claimable by eligible voters. @@ -562,21 +576,23 @@ contract BribeController is // CHECKS if (!_isUpdaterOrGovernance()) revert NotUpdaterNorGovernance(); 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 (lastTimeBribesProcessed >= currentEpochStartTime) revert BribesAlreadyDistributed(); - // LOOP 1 - GET TOTAL VOTE POWER FOR EACH GAUGE + // 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 = 0; i < numGauges; i++) { + for (uint256 i = _updateInfo.index1 == type(uint80).max ? 0 : _updateInfo.index1; i < numGauges; i++) { // Iterate by vote (uint256 gaugeID, uint256 runningVotePowerSum) = _gaugeToTotalVotePower.at(i); uint256 numVotes = _votes[gaugeID].length(); - for (uint256 j = 0; j < numVotes; j++) { + 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);} (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(j); uint256 votePower = IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter); // State mutation 1 @@ -594,15 +610,18 @@ contract BribeController is // Iterate by vote while(_votes[gaugeID].length() > 0) { (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(0); - uint256 bribeProportion = (IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) * votePowerBPS / 10000) * 1e18 / votePowerSum; + // `votePowerSum - 1` to nullify initiating _gaugeToTotalVotePower values at 1 rather than 0. + uint256 bribeProportion = (IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) * votePowerBPS / 10000) * 1e18 / (votePowerSum - 1); // Iterate by bribeToken uint256 numBribeTokens = _providedBribes[gaugeID].length(); for (uint256 k = 0; k < numBribeTokens; k++) { - // State mutation 2 + // Checkpoint 2 + if (gasleft() < 30000) {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); uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; + // State mutation 2 _providedBribes[gaugeID].set(bribeToken, totalBribeAmount - bribeAmount); // Should not underflow as integers round down in Solidity. _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); } @@ -620,7 +639,7 @@ contract BribeController is } } - emit BribesDistributed(currentEpochStartTime); + emit BribesProcessed(currentEpochStartTime); _clearUpdateInfo(); } @@ -629,20 +648,20 @@ contract BribeController is ***************************************/ /** - * @notice Save state of updating gauge weights to _updateInfo. - * @param votingContractsIndex_ Current index of _votingContracts. - * @param votersIndex_ Current index of _voters[votingContractsIndex_]. - * @param votesIndex_ Current index of _votes[votingContractsIndex_][votersIndex_] + * @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 votingContractsIndex_, uint256 votersIndex_, uint256 votesIndex_) internal { + function _saveUpdateState(uint256 loop1GaugeIndex_, uint256 loop1VoteIndex_, uint256 loop2BribeTokenIndex_) internal { assembly { let updateInfo - updateInfo := or(updateInfo, shr(176, shl(176, votingContractsIndex_))) // [0:80] => votingContractsIndex_ - updateInfo := or(updateInfo, shr(88, shl(168, votersIndex_))) // [80:168] => votersIndex_ - updateInfo := or(updateInfo, shl(168, votesIndex_)) // [168:256] => votesIndex_ + 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 IncompleteBribeProcessing(); + emit IncompleteBribesProcessing(); } /// @notice Reset _updateInfo to starting state. diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 3718dea..13ce524 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -29,7 +29,7 @@ const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; describe("BribeController", function () { - const [deployer, governor, revenueRouter, voter1, voter2, updater, briber1, anon] = provider.getWallets(); + const [deployer, governor, revenueRouter, voter1, voter2, delegate1, updater, briber1, anon] = provider.getWallets(); /*************************** VARIABLE DECLARATIONS @@ -94,15 +94,21 @@ describe("BribeController", function () { await expectDeployed(bribeController.address); }); it("initializes properly", async function () { - expect(await bribeController.votingContract()).eq(voting.address); - expect(await bribeController.gaugeController()).eq(gaugeController.address); expect(await bribeController.registry()).eq(registry.address); + expect(await bribeController.gaugeController()).eq(gaugeController.address); + expect(await bribeController.revenueRouter()).eq(revenueRouter.address); + expect(await bribeController.votingContract()).eq(voting.address); expect(await bribeController.updater()).eq(ZERO_ADDRESS); - expect(await bribeController.lastTimeBribesDistributed()).eq(await bribeController.getEpochStartTimestamp()); + 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.getProvidedBribesForCurrentEpoch(0)).deep.eq([]); + expect(await bribeController.getUnusedVotePowerBPS(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; @@ -145,6 +151,7 @@ describe("BribeController", function () { let registry2: Registry; const RANDOM_ADDRESS_1 = ethers.Wallet.createRandom().connect(provider).address; const RANDOM_ADDRESS_2 = ethers.Wallet.createRandom().connect(provider).address; + const RANDOM_ADDRESS_3 = ethers.Wallet.createRandom().connect(provider).address; before(async function () { registry2 = (await deployContract(deployer, artifacts.Registry, [governor.address])) as Registry; @@ -163,6 +170,10 @@ describe("BribeController", function () { await expect(bribeController.connect(governor).setRegistry(registry2.address)).to.be.revertedWith('ZeroAddressInput("underwritingLockVoting")'); await registry2.connect(governor).set(["underwritingLockVoting"], [RANDOM_ADDRESS_2]); }) + it("reverts if zero address revenueRouter in Registry", async function () { + await expect(bribeController.connect(governor).setRegistry(registry2.address)).to.be.revertedWith('ZeroAddressInput("revenueRouter")'); + await registry2.connect(governor).set(["revenueRouter"], [RANDOM_ADDRESS_3]); + }) it("sets registry", async function () { const tx = await bribeController.connect(governor).setRegistry(registry2.address); await expect(tx).to.emit(bribeController, "RegistrySet").withArgs(registry2.address); @@ -171,6 +182,7 @@ describe("BribeController", function () { expect(await bribeController.registry()).eq(registry2.address); expect(await bribeController.gaugeController()).eq(RANDOM_ADDRESS_1); expect(await bribeController.votingContract()).eq(RANDOM_ADDRESS_2); + expect(await bribeController.revenueRouter()).eq(RANDOM_ADDRESS_3); }); after(async function () { await bribeController.connect(governor).setRegistry(registry.address); @@ -220,23 +232,26 @@ describe("BribeController", function () { /********************* INTENTION STATEMENT *********************/ - // briber1 will provide 1K of bribeToken1 and 1K of bribeToken2 as a bribe over 1 epoch for gauge 1 + // briber1 will provide 1K of bribeToken1 and 1K of bribeToken2 as a bribe for gauge 1 + // create gauge2 - describe("provideBribes", () => { + describe("provideBribe", () => { it("will throw if bribeToken and bribeAmount arrays mismatched", async () => { - await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1], 1, 1)).to.be.revertedWith("ArrayArgumentsLengthMismatch"); + await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1], 1)).to.be.revertedWith("ArrayArgumentsLengthMismatch"); }); - it("will throw if bribe for 0 epochs", async () => { - await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1, 1], 1, 0)).to.be.revertedWith("CannotBribeFor0Epochs"); + 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 inactive gauge", async () => { - await expect(bribeController.connect(briber1).provideBribes([bribeToken1.address, bribeToken2.address], [1, 1], 0, 1)).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 with non-whitelisted token", async () => { - await expect(bribeController.connect(briber1).provideBribes([token.address, bribeToken2.address], [1, 1], 1, 1)).to.be.revertedWith("CannotBribeWithNonWhitelistedToken"); + 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(10000)); await bribeToken2.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(10000)); await bribeToken1.connect(briber1).approve(bribeController.address, constants.MaxUint256); @@ -246,9 +261,9 @@ describe("BribeController", function () { 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], 1, 1) - await expect(tx).to.emit(bribeController, "BribeProvided").withArgs(briber1.address, 1, bribeToken1.address, BRIBE_AMOUNT, 1); - await expect(tx).to.emit(bribeController, "BribeProvided").withArgs(briber1.address, 1, bribeToken2.address, BRIBE_AMOUNT, 1); + 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) @@ -258,7 +273,6 @@ describe("BribeController", function () { 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) @@ -270,12 +284,13 @@ describe("BribeController", function () { expect(lifetimeBribes[0].bribeToken).eq(bribeToken1.address) expect(lifetimeBribes[1].bribeToken).eq(bribeToken2.address) - expect(await bribeController.getProvidedBribesForCurrentEpoch(1)).deep.eq([]); - const providedBribes = await bribeController.getProvidedBribesForNextEpoch(1); - expect(providedBribes[0].bribeAmount).eq(BRIBE_AMOUNT) - expect(providedBribes[1].bribeAmount).eq(BRIBE_AMOUNT) - expect(providedBribes[0].bribeToken).eq(bribeToken1.address) - expect(providedBribes[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 does not revert, but does not do anything", async () => { const tx = await bribeController.connect(voter1).claimBribes(); @@ -288,44 +303,88 @@ describe("BribeController", function () { *******************/ /** * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the current epoch + * gaugeID 2 => No bribes */ /********************* INTENTION STATEMENT *********************/ - // voter1 will create lockID 1, and vote with 10000 votePowerBPS for gaugeID 1 + // voter1 will create lockID 1, and voteForBribe with 1000 votePowerBPS for gaugeID 1 - describe("distributeBribes", () => { - it("will throw if called by non-governor or non-updater", async () => { - await expect(bribeController.connect(briber1).distributeBribes()).to.be.revertedWith("NotUpdaterNorGovernance"); - }); - it("will throw if gauge weights not yet updated", async () => { - await expect(bribeController.connect(updater).distributeBribes()).to.be.revertedWith("GaugeWeightsNotYetUpdated"); - }); - it("will throw if called in the first epoch of contract deployment", async () => { - await gaugeController.connect(governor).updateGaugeWeights() + describe("voteForBribe", () => { + before(async function () { await gaugeController.connect(governor).addVotingContract(voting.address) - await underwritingLocker.connect(governor).setVotingContract() - await gaugeController.connect(governor).addTokenholder(underwritingLocker.address) - await voting.connect(governor).chargePremiums() - await expect(bribeController.connect(updater).distributeBribes()).to.be.revertedWith("BribesAlreadyDistributed"); }); - it("can distributeBribe", async () => { - // CREATE LOCK AND VOTE + 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); - await voting.connect(voter1).vote(voter1.address, 1, 10000); - - await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); - const newEpochStartTimestamp = await bribeController.getEpochStartTimestamp() - await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) - const tx = await bribeController.connect(governor).distributeBribes({gasLimit: CUSTOM_GAS_LIMIT}) - await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) - await expect(tx).to.emit(bribeController, "BribesDistributed").withArgs(newEpochStartTimestamp); - }) + }); + 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(); + 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) + }); }); + + // describe("processBribes", () => { + // before(async function () { + // const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + // await underwritingLocker.connect(governor).setVotingContract() + // await gaugeController.connect(governor).addTokenholder(underwritingLocker.address) + // await token.connect(deployer).approve(underwritingLocker.address, constants.MaxUint256); + // await underwritingLocker.connect(deployer).createLock(voter1.address, DEPOSIT_AMOUNT, CURRENT_TIME + 4 * ONE_YEAR); + // await voting.connect(voter1).vote(voter1.address, 1, 10000); + // }); + // it("will throw if called by non-governor or non-updater", async () => { + // await expect(bribeController.connect(briber1).processBribes()).to.be.revertedWith("NotUpdaterNorGovernance"); + // }); + // 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 gaugeController.connect(governor).updateGaugeWeights() + // await voting.connect(governor).chargePremiums() + // await expect(bribeController.connect(updater).distributeBribes()).to.be.revertedWith("BribesAlreadyDistributed"); + // }); + // it("can distributeBribe", async () => { + // // CREATE LOCK AND VOTE + // const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; + + + // await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); + // const newEpochStartTimestamp = await bribeController.getEpochStartTimestamp() + // await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) + // const tx = await bribeController.connect(governor).distributeBribes({gasLimit: CUSTOM_GAS_LIMIT}) + // await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) + // await expect(tx).to.emit(bribeController, "BribesDistributed").withArgs(newEpochStartTimestamp); + // }) + // }); + /******************* STATE SUMMARY *******************/ @@ -334,37 +393,37 @@ describe("BribeController", function () { * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the epoch just past */ - describe("claimBribes", () => { - it("getClaimableBribes", 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); + // describe("claimBribes", () => { + // it("getClaimableBribes", 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([]); - }) - }); + // 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([]); + // }) + // }); }); \ No newline at end of file From 76346a2054cf71f0ab1cfd532ca746a7d31c15a5 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Mon, 29 Aug 2022 04:07:48 +1000 Subject: [PATCH 07/24] completed removeVoteForBribe tests --- .../interfaces/native/IGaugeController.sol | 3 + contracts/native/BribeController.sol | 65 +++-- contracts/native/GaugeController.sol | 1 + test/native/BribeController.test.ts | 234 ++++++++++++------ test/native/GaugeController.test.ts | 3 + 5 files changed, 209 insertions(+), 97 deletions(-) diff --git a/contracts/interfaces/native/IGaugeController.sol b/contracts/interfaces/native/IGaugeController.sol index a42b484..eb82cb2 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(); diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 5e8bd26..e657a0b 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -19,6 +19,7 @@ contract BribeController is Governable { using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableSet for EnumerableSet.UintSet; using EnumerableMapS for EnumerableMapS.AddressToUintMap; using EnumerableMapS for EnumerableMapS.UintToUintMap; @@ -59,10 +60,11 @@ contract BribeController is mapping(address => EnumerableMapS.AddressToUintMap) internal _claimableBribes; /// @notice gaugeID => total vote power - /// @dev We use this to i.) Store an enumerable collection of gauges with bribes, - /// @dev and ii.) Store total vote power chaisng bribes for each gauge. 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; @@ -187,11 +189,10 @@ contract BribeController is * @return gauges Array of gaugeIDs with current bribe. */ function getAllGaugesWithBribe() external view override returns (uint256[] memory gauges) { - uint256 length = _gaugeToTotalVotePower.length(); + uint256 length = _gaugesWithBribes.length(); gauges = new uint256[](length); for (uint256 i = 0; i < length; i++) { - (uint256 gaugeID,) = _gaugeToTotalVotePower.at(i); - gauges[i] = gaugeID; + gauges[i] = _gaugesWithBribes.at(i); } } @@ -322,12 +323,12 @@ contract BribeController is * @param voter_ address of voter. * @param gaugeID_ The ID of the gauge to remove vote for. */ - function _removeVoteForBribe(address voter_, uint256 gaugeID_) internal { + 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_); + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, true); } /** @@ -336,12 +337,17 @@ contract BribeController is * @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_) internal nonReentrant { + function _voteForBribe(address voter_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_, bool isInternalCall_) internal nonReentrant { // CHECKS if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); - if ( _getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); - if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); + + // 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(); + } for(uint256 i = 0; i < gaugeIDs_.length; i++) { uint256 gaugeID = gaugeIDs_[i]; @@ -350,7 +356,6 @@ contract BribeController is // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS); (bool votePresent, uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); - // If remove vote if (votePowerBPS == 0) { if (!votePresent) revert CannotRemoveNonExistentVote(); @@ -358,6 +363,7 @@ contract BribeController is if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); emit VoteForBribeRemoved(voter_, gaugeID); } else { + _gaugeToTotalVotePower.set(gaugeID, 1); // Do not set to 0 to avoid SSTORE penalty for 0 slot in processBribes(). _votes[gaugeID].set(voter_, votePowerBPS); // Change vote @@ -388,21 +394,19 @@ contract BribeController is ) external override nonReentrant { // CHECKS if (bribeTokens_.length != bribeAmounts_.length) revert ArrayArgumentsLengthMismatch(); - if (_getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); try IGaugeController(gaugeController).isGaugeActive(gaugeID_) returns (bool gaugeActive) { if (!gaugeActive) revert CannotBribeForInactiveGauge(); } catch { revert CannotBribeForNonExistentGauge(); } - uint256 length = _bribeTokenWhitelist.length(); + uint256 length = bribeTokens_.length; for (uint256 i = 0; i < length; i++) { if (!_bribeTokenWhitelist.contains(bribeTokens_[i])) revert CannotBribeWithNonWhitelistedToken(); } - + // INTERNAL STATE MUTATIONS - (bool gaugePresent,) = _gaugeToTotalVotePower.tryGet(gaugeID_); - if(!gaugePresent) _gaugeToTotalVotePower.set(gaugeID_, 1); // Do not set to 0 to avoid SSTORE penalty for 0 slot in processBribes(). + _gaugesWithBribes.add(gaugeID_); for (uint256 i = 0; i < length; i++) { (,uint256 previousBribeSum) = _providedBribes[gaugeID_].tryGet(bribeTokens_[i]); @@ -439,7 +443,7 @@ contract BribeController is uint256[] memory votePowerBPSs_ = new uint256[](1); gaugeIDs_[0] = gaugeID_; votePowerBPSs_[0] = votePowerBPS_; - _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_); + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); } /** @@ -449,7 +453,7 @@ contract BribeController is * @param votePowerBPSs_ Array of corresponding vote power BPS values. */ function voteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_, uint256[] calldata votePowerBPSs_) external override { - _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_); + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); } /** @@ -462,7 +466,7 @@ contract BribeController is function voteForBribeForMultipleVoters(address[] calldata voters_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_) external override { uint256 length = voters_.length; for (uint256 i = 0; i < length; i++) { - _voteForBribe(voters_[i], gaugeIDs_, votePowerBPSs_); + _voteForBribe(voters_[i], gaugeIDs_, votePowerBPSs_, false); } } @@ -472,7 +476,11 @@ contract BribeController is * @param gaugeID_ The ID of the gauge to remove vote for. */ function removeVoteForBribe(address voter_, uint256 gaugeID_) external override { - _removeVoteForBribe(voter_, gaugeID_); + uint256[] memory gaugeIDs_ = new uint256[](1); + uint256[] memory votePowerBPSs_ = new uint256[](1); + gaugeIDs_[0] = gaugeID_; + votePowerBPSs_[0] = 0; + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); } /** @@ -483,7 +491,7 @@ contract BribeController is function removeVoteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external override { uint256[] memory votePowerBPSs_ = new uint256[](gaugeIDs_.length); for(uint256 i = 0; i < gaugeIDs_.length; i++) {votePowerBPSs_[i] = 0;} - _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_); + _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); } /** @@ -498,7 +506,7 @@ contract BribeController is 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_); + _voteForBribe(voters_[i], gaugeIDs_, votePowerBPSs_, false); } } @@ -626,19 +634,24 @@ contract BribeController is _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); } // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. - _removeVoteForBribe(voter, gaugeID); + _removeVoteForBribeInternal(voter, gaugeID); } + } + } - // Cleanup _providedBribes enumerable collection. - // Send leftover bribes to revenueRouter. + // Cleanup _gaugesWithBribes and _providedBribes enumerable collections. + // Send leftover bribes to revenueRouter. + while(_gaugesWithBribes.length() > 0) { + uint256 gaugeID = _gaugesWithBribes.at(0); while(_providedBribes[gaugeID].length() > 0) { (address bribeToken, uint256 remainingBribeAmount) = _providedBribes[gaugeID].at(0); SafeERC20.safeTransfer(IERC20(bribeToken), revenueRouter, remainingBribeAmount); _providedBribes[gaugeID].remove(bribeToken); } - } + _gaugesWithBribes.remove(gaugeID); } + lastTimeBribesProcessed = currentEpochStartTime; emit BribesProcessed(currentEpochStartTime); _clearUpdateInfo(); } diff --git a/contracts/native/GaugeController.sol b/contracts/native/GaugeController.sol index 10d6c4a..4f0b2d4 100644 --- a/contracts/native/GaugeController.sol +++ b/contracts/native/GaugeController.sol @@ -528,6 +528,7 @@ contract GaugeController is * @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_); } diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 13ce524..caadb35 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -22,7 +22,7 @@ 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(1000); +const BRIBE_AMOUNT = ONE_ETHER.mul(100); const SCALE_FACTOR = ONE_ETHER; const ONE_PERCENT = ONE_ETHER.div(100); const ONE_HUNDRED_PERCENT = ONE_ETHER; @@ -232,7 +232,7 @@ describe("BribeController", function () { /********************* INTENTION STATEMENT *********************/ - // briber1 will provide 1K of bribeToken1 and 1K of bribeToken2 as a bribe for gauge 1 + // briber1 will provide 100 of bribeToken1 and 100 of bribeToken2 as a bribe for gauge 1 // create gauge2 describe("provideBribe", () => { @@ -302,14 +302,18 @@ describe("BribeController", function () { STATE SUMMARY *******************/ /** - * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the current epoch + * 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 voteForBribe with 1000 votePowerBPS for gaugeID 1 + /** + * voter1 will create lockID 1, and make allocate votePowerBPS: + * gaugeID 1 => 1000 + * gaugeID 2 => 9000 + */ describe("voteForBribe", () => { before(async function () { @@ -334,6 +338,7 @@ describe("BribeController", function () { 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); await expect(bribeController.connect(voter1).voteForBribe(voter1.address, 1, 10000)).to.be.revertedWith("TotalVotePowerBPSOver10000"); }); it("can voteForBribe", async () => { @@ -348,42 +353,160 @@ describe("BribeController", function () { }); }); + /******************* + 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 by non-governor or non-updater", async () => { + await expect(bribeController.connect(briber1).processBribes()).to.be.revertedWith("NotUpdaterNorGovernance"); + }); + 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("updater can process bribes", async () => { + const EPOCH_START_TIMESTAMP = await bribeController.getEpochStartTimestamp(); + const tx = await bribeController.connect(governor).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(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("can remove vote", async () => { + await bribeController.connect(voter1).voteForBribe(voter1.address, 1, 1000); + const tx = await bribeController.connect(voter1).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.revertedWith("BribesAlreadyProcessed"); + }) + it("after processing bribes, all bribes should be distributed to revenue router", 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) + const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) + await bribeController.connect(updater).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 NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.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); + const REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) + + expect(VOTER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(0) + expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) + expect(REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT) + expect(await bribeToken1.balanceOf(revenueRouter.address)).eq(BRIBE_AMOUNT) + }) + after(async function () { + await gaugeController.connect(governor).setEpochLengthInWeeks(1); + }); + }); + - // describe("processBribes", () => { - // before(async function () { - // const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; - // await underwritingLocker.connect(governor).setVotingContract() - // await gaugeController.connect(governor).addTokenholder(underwritingLocker.address) - // await token.connect(deployer).approve(underwritingLocker.address, constants.MaxUint256); - // await underwritingLocker.connect(deployer).createLock(voter1.address, DEPOSIT_AMOUNT, CURRENT_TIME + 4 * ONE_YEAR); - // await voting.connect(voter1).vote(voter1.address, 1, 10000); - // }); - // it("will throw if called by non-governor or non-updater", async () => { - // await expect(bribeController.connect(briber1).processBribes()).to.be.revertedWith("NotUpdaterNorGovernance"); - // }); - // 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 gaugeController.connect(governor).updateGaugeWeights() - // await voting.connect(governor).chargePremiums() - // await expect(bribeController.connect(updater).distributeBribes()).to.be.revertedWith("BribesAlreadyDistributed"); - // }); - // it("can distributeBribe", async () => { - // // CREATE LOCK AND VOTE - // const CURRENT_TIME = (await provider.getBlock('latest')).timestamp; - - - // await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); - // const newEpochStartTimestamp = await bribeController.getEpochStartTimestamp() - // await gaugeController.connect(governor).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}) - // const tx = await bribeController.connect(governor).distributeBribes({gasLimit: CUSTOM_GAS_LIMIT}) - // await voting.connect(governor).chargePremiums({gasLimit: CUSTOM_GAS_LIMIT}) - // await expect(tx).to.emit(bribeController, "BribesDistributed").withArgs(newEpochStartTimestamp); - // }) - // }); /******************* STATE SUMMARY @@ -393,37 +516,6 @@ describe("BribeController", function () { * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the epoch just past */ - // describe("claimBribes", () => { - // it("getClaimableBribes", 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([]); - // }) - // }); + }); \ No newline at end of file diff --git a/test/native/GaugeController.test.ts b/test/native/GaugeController.test.ts index b530ea5..6ae39d8 100644 --- a/test/native/GaugeController.test.ts +++ b/test/native/GaugeController.test.ts @@ -214,6 +214,9 @@ describe("GaugeController", function () { 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); From a23dd1d95f137c6fc1793dc04e1fbf20efc3cd5e Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Mon, 29 Aug 2022 04:12:54 +1000 Subject: [PATCH 08/24] completed removeVoteForBribe tests --- test/native/BribeController.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index caadb35..f94a51a 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -468,8 +468,14 @@ describe("BribeController", function () { it("throws if remove inexistent vote", async () => { await expect(bribeController.connect(voter1.address).removeVoteForBribe(voter1.address, 1)).to.be.revertedWith("EnumerableMap: nonexistent key"); }) - it("can remove vote", async () => { + 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, can remove vote", async () => { + await gaugeController.connect(governor).pauseGauge(1); const tx = await bribeController.connect(voter1).removeVoteForBribe(voter1.address, 1); await expect(tx).to.emit(bribeController, "VoteForBribeRemoved").withArgs(voter1.address, 1); }) @@ -503,6 +509,7 @@ describe("BribeController", function () { }) after(async function () { await gaugeController.connect(governor).setEpochLengthInWeeks(1); + await gaugeController.connect(governor).unpauseGauge(1); }); }); From b86de7ce7eff9d45018813fbae1d3530955319ca Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Tue, 30 Aug 2022 02:17:24 +1000 Subject: [PATCH 09/24] did voteForMultipleBribes and removeVoteForMultipleBribes scenario for bribe tests --- .../interfaces/native/IBribeController.sol | 13 +- contracts/native/BribeController.sol | 49 ++- test/native/BribeController.test.ts | 279 +++++++++++++++++- 3 files changed, 323 insertions(+), 18 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index 0ac02b0..a642194 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -92,6 +92,9 @@ interface IBribeController { /// @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(); @@ -254,7 +257,7 @@ interface IBribeController { * @param voter_ address of voter. * @param gaugeIDs_ Array of gaugeIDs to remove votes for */ - function removeVoteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external; + function removeVotesForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external; /** * @notice Remove gauge votes for multiple voters. @@ -303,6 +306,14 @@ interface IBribeController { */ 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 FUNCTIONS ***************************************/ diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index e657a0b..6050031 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -488,7 +488,7 @@ contract BribeController is * @param voter_ address of voter. * @param gaugeIDs_ Array of gaugeIDs to remove votes for */ - function removeVoteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external override { + function removeVotesForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external override { uint256[] memory votePowerBPSs_ = new uint256[](gaugeIDs_.length); for(uint256 i = 0; i < gaugeIDs_.length; i++) {votePowerBPSs_[i] = 0;} _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); @@ -571,6 +571,22 @@ contract BribeController is emit BribeTokenRemoved(bribeToken_); } + /** + * @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 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 FUNCTIONS ***************************************/ @@ -588,6 +604,27 @@ contract BribeController is // 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 + // => send provided bribes to revenueRouter + // => early return + if (_gaugeToTotalVotePower.length() == 0) { + while(_gaugesWithBribes.length() > 0) { + uint256 gaugeID = _gaugesWithBribes.at(0); + while(_providedBribes[gaugeID].length() > 0) { + (address bribeToken, uint256 remainingBribeAmount) = _providedBribes[gaugeID].at(0); + SafeERC20.safeTransfer(IERC20(bribeToken), revenueRouter, remainingBribeAmount); + _providedBribes[gaugeID].remove(bribeToken); + } + _gaugesWithBribes.remove(gaugeID); + } + + lastTimeBribesProcessed = currentEpochStartTime; + emit BribesProcessed(currentEpochStartTime); + _clearUpdateInfo(); + return; + } + // LOOP 1 - GET TOTAL VOTE POWER CHASING BRIBES FOR EACH GAUGE // Block-scope to avoid stack too deep error { @@ -595,12 +632,13 @@ contract BribeController is // Iterate by gauge for (uint256 i = _updateInfo.index1 == type(uint80).max ? 0 : _updateInfo.index1; i < numGauges; i++) { // Iterate by vote - (uint256 gaugeID, uint256 runningVotePowerSum) = _gaugeToTotalVotePower.at(i); + (uint256 gaugeID,) = _gaugeToTotalVotePower.at(i); uint256 numVotes = _votes[gaugeID].length(); 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 @@ -615,6 +653,7 @@ contract BribeController is // Iterate by gauge while (_gaugeToTotalVotePower.length() > 0) { (uint256 gaugeID, uint256 votePowerSum) = _gaugeToTotalVotePower.at(0); + // Iterate by vote while(_votes[gaugeID].length() > 0) { (address voter, uint256 votePowerBPS) = _votes[gaugeID].at(0); @@ -630,7 +669,6 @@ contract BribeController is (, uint256 runningClaimableAmount) = _claimableBribes[voter].tryGet(bribeToken); uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; // State mutation 2 - _providedBribes[gaugeID].set(bribeToken, totalBribeAmount - bribeAmount); // Should not underflow as integers round down in Solidity. _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); } // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. @@ -640,12 +678,11 @@ contract BribeController is } // Cleanup _gaugesWithBribes and _providedBribes enumerable collections. - // Send leftover bribes to revenueRouter. + // No leftover bribes to send to the revenueRouter in this case. while(_gaugesWithBribes.length() > 0) { uint256 gaugeID = _gaugesWithBribes.at(0); while(_providedBribes[gaugeID].length() > 0) { - (address bribeToken, uint256 remainingBribeAmount) = _providedBribes[gaugeID].at(0); - SafeERC20.safeTransfer(IERC20(bribeToken), revenueRouter, remainingBribeAmount); + (address bribeToken,) = _providedBribes[gaugeID].at(0); _providedBribes[gaugeID].remove(bribeToken); } _gaugesWithBribes.remove(gaugeID); diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index f94a51a..7c2b985 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -29,7 +29,7 @@ const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; describe("BribeController", function () { - const [deployer, governor, revenueRouter, voter1, voter2, delegate1, updater, briber1, anon] = provider.getWallets(); + const [deployer, governor, revenueRouter, voter1, voter2, voter3, delegate1, updater, briber1, anon] = provider.getWallets(); /*************************** VARIABLE DECLARATIONS @@ -318,6 +318,7 @@ describe("BribeController", function () { 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"); @@ -474,9 +475,9 @@ describe("BribeController", function () { await gaugeController.connect(governor).unpauseGauge(1); await bribeController.connect(voter1).voteForBribe(voter1.address, 1, 1000); }) - it("if gauge paused, can remove vote", async () => { + it("if gauge paused, delegate can remove vote", async () => { await gaugeController.connect(governor).pauseGauge(1); - const tx = await bribeController.connect(voter1).removeVoteForBribe(voter1.address, 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 () => { @@ -484,7 +485,7 @@ describe("BribeController", function () { await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); await expect(bribeController.connect(governor.address).processBribes()).to.be.revertedWith("BribesAlreadyProcessed"); }) - it("after processing bribes, all bribes should be distributed to revenue router", async () => { + it("after processing bribes, all bribes should be distributed to revenue router, 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}) @@ -506,6 +507,11 @@ describe("BribeController", function () { expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) expect(REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT) expect(await bribeToken1.balanceOf(revenueRouter.address)).eq(BRIBE_AMOUNT) + 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) }) after(async function () { await gaugeController.connect(governor).setEpochLengthInWeeks(1); @@ -513,16 +519,267 @@ describe("BribeController", function () { }); }); - - - /******************* - STATE SUMMARY - *******************/ + /********************* + INTENTION STATEMENT + *********************/ /** - * voter1 => lockID1, voted for gaugeID 1 with 10000 votePowerBPS - * gaugeID 1 => 1K of bribeToken1 and 1K and bribeToken2 provided for the epoch just past + * 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 removeVoteForMultipleBribes 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 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"); + }); + 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); + 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.getUnusedVotePowerBPS(voter2.address)).eq(0) + expect(await bribeController.getUnusedVotePowerBPS(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) + }); + 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(1000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.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("claimTokens cannot be called by non-governance", async () => { + await expect(bribeController.connect(voter1).rescueTokens([bribeToken1.address, bribeToken2.address], revenueRouter.address)).to.be.revertedWith("!governance"); + }) + it("claimTokens 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_1).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) + }) + }); }); \ No newline at end of file From 7c33f3c9d15b8d73f4ecc6ffb24cdd53a15b7c00 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Tue, 30 Aug 2022 19:34:27 +1000 Subject: [PATCH 10/24] completed unit tests, however need to further analyse processbribes for gas efficiency --- .../interfaces/native/IBribeController.sol | 8 +- contracts/native/BribeController.sol | 75 +- contracts/native/GaugeController.sol | 1 - test/native/BribeController.test.ts | 1011 ++++++++++++++++- 4 files changed, 1027 insertions(+), 68 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index a642194..c6f2574 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -41,6 +41,9 @@ interface IBribeController { /// @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 voteForBribe() attempted by a non-owner or non-delegate. error NotOwnerNorDelegate(); @@ -115,9 +118,6 @@ interface IBribeController { /// @notice Address of UnderwritingLockVoting.sol function votingContract() external view returns (address); - /// @notice Address of revenue router. - function revenueRouter() external view returns (address); - /// @notice Updater address. function updater() external view returns (address); @@ -279,7 +279,7 @@ interface IBribeController { /** * @notice Sets the [`Registry`](./Registry) contract address. - * @dev Requires 'uwe', 'revenueRouter' and 'underwritingLocker' addresses to be set in the Registry. + * @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. */ diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 6050031..44ec679 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -36,9 +36,6 @@ contract BribeController is /// @notice UnderwriterLockVoting.sol address address public override votingContract; - /// @notice Revenue router address - address public override revenueRouter; - /// @notice Updater address. /// @dev Second address that can call updateGaugeWeights (in addition to governance). address public override updater; @@ -296,7 +293,7 @@ contract BribeController is /** * @notice Sets registry and related contract addresses. - * @dev Requires 'uwe', 'revenueRouter' and 'underwritingLocker' addresses to be set in the Registry. + * @dev Requires 'uwe' and 'underwritingLocker' addresses to be set in the Registry. * @param _registry The registry address to set. */ function _setRegistry(address _registry) internal { @@ -311,10 +308,6 @@ contract BribeController is (, address underwritingLockVoting) = reg.tryGet("underwritingLockVoting"); if(underwritingLockVoting == address(0x0)) revert ZeroAddressInput("underwritingLockVoting"); votingContract = underwritingLockVoting; - // set revenueRouter - (, address revenueRouterAddr) = reg.tryGet("revenueRouter"); - if(revenueRouterAddr == address(0x0)) revert ZeroAddressInput("revenueRouter"); - revenueRouter = revenueRouterAddr; emit RegistrySet(_registry); } @@ -516,7 +509,7 @@ contract BribeController is */ function claimBribes() external override nonReentrant { uint256 length = _claimableBribes[msg.sender].length(); - if (length == 0) return; + if (length == 0) revert NoClaimableBribes(); while (_claimableBribes[msg.sender].length() != 0) { (address bribeToken, uint256 bribeAmount) = _claimableBribes[msg.sender].at(0); @@ -532,7 +525,7 @@ contract BribeController is /** * @notice Sets the [`Registry`](./Registry) contract address. - * @dev Requires 'uwe', 'revenueRouter' and 'underwritingLocker' addresses to be set in the Registry. + * @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. */ @@ -572,7 +565,7 @@ contract BribeController is } /** - * @notice Rescues misplaced and remaining bribes (from Solidity rounding down). + * @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. @@ -606,25 +599,14 @@ contract BribeController is // If no votes to process // => early cleanup of _gaugesWithBribes and _providedBribes mappings - // => send provided bribes to revenueRouter + // => bribes stay custodied on bribing contract // => early return if (_gaugeToTotalVotePower.length() == 0) { - while(_gaugesWithBribes.length() > 0) { - uint256 gaugeID = _gaugesWithBribes.at(0); - while(_providedBribes[gaugeID].length() > 0) { - (address bribeToken, uint256 remainingBribeAmount) = _providedBribes[gaugeID].at(0); - SafeERC20.safeTransfer(IERC20(bribeToken), revenueRouter, remainingBribeAmount); - _providedBribes[gaugeID].remove(bribeToken); - } - _gaugesWithBribes.remove(gaugeID); - } - - lastTimeBribesProcessed = currentEpochStartTime; - emit BribesProcessed(currentEpochStartTime); - _clearUpdateInfo(); - return; + return _concludeProcessBribes(currentEpochStartTime); } + console.log("===Loaded state: 1: %s, 2: %s, 3: %s", _updateInfo.index1, _updateInfo.index2, _updateInfo.index3); + // LOOP 1 - GET TOTAL VOTE POWER CHASING BRIBES FOR EACH GAUGE // Block-scope to avoid stack too deep error { @@ -637,16 +619,20 @@ contract BribeController is for (uint256 j = _updateInfo.index2 == type(uint88).max || i != _updateInfo.index1 ? 0 : _updateInfo.index2; j < numVotes; j++) { // Checkpoint 1 + // console.log("LOOP 1 START: ", gasleft()); 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); + // console.log("LOOP 1 END: ", gasleft()); } } } + console.log("B"); + // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS // _gaugeToTotalVotePower, _votes and _providedBribes enumerable collections should be empty at the end. { @@ -663,13 +649,16 @@ contract BribeController is // Iterate by bribeToken uint256 numBribeTokens = _providedBribes[gaugeID].length(); for (uint256 k = 0; k < numBribeTokens; k++) { + // console.log("LOOP 2 START: ", gasleft()); // Checkpoint 2 - if (gasleft() < 30000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k);} + 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); uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; // State mutation 2 _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); + // console.log("LOOP 2 END: ", gasleft()); + if (gasleft() < 100000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k + 1);} } // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. _removeVoteForBribeInternal(voter, gaugeID); @@ -678,19 +667,7 @@ contract BribeController is } // Cleanup _gaugesWithBribes and _providedBribes enumerable collections. - // No leftover bribes to send to the revenueRouter in this case. - while(_gaugesWithBribes.length() > 0) { - uint256 gaugeID = _gaugesWithBribes.at(0); - while(_providedBribes[gaugeID].length() > 0) { - (address bribeToken,) = _providedBribes[gaugeID].at(0); - _providedBribes[gaugeID].remove(bribeToken); - } - _gaugesWithBribes.remove(gaugeID); - } - - lastTimeBribesProcessed = currentEpochStartTime; - emit BribesProcessed(currentEpochStartTime); - _clearUpdateInfo(); + _concludeProcessBribes(currentEpochStartTime); } /*************************************** @@ -704,6 +681,7 @@ contract BribeController is * @param loop2BribeTokenIndex_ Current index of _providedBribes[gaugeID] in loop 2. */ function _saveUpdateState(uint256 loop1GaugeIndex_, uint256 loop1VoteIndex_, uint256 loop2BribeTokenIndex_) internal { + console.log("===SAVEUPDATESTATE: 1: %s, 2: %s, 3: %s", loop1GaugeIndex_, loop1VoteIndex_, loop2BribeTokenIndex_); assembly { let updateInfo updateInfo := or(updateInfo, shr(176, shl(176, loop1GaugeIndex_))) // [0:80] => votingContractsIndex_ @@ -722,4 +700,21 @@ contract BribeController is 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) { + (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 4f0b2d4..f0b1ecc 100644 --- a/contracts/native/GaugeController.sol +++ b/contracts/native/GaugeController.sol @@ -107,7 +107,6 @@ contract GaugeController is token = token_; leverageFactor = 1e18; // Default 1x leverage. // Pre-fill slot 0 of _gauges, ensure gaugeID 1 maps to _gauges[1] - GaugeStructs.Gauge memory newGauge = GaugeStructs.Gauge(false, 0, ""); _gauges.push(GaugeStructs.Gauge(false, 0, "")); _clearUpdateInfo(); _epochLength = WEEK; diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 7c2b985..593c5af 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -29,7 +29,7 @@ const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; describe("BribeController", function () { - const [deployer, governor, revenueRouter, voter1, voter2, voter3, delegate1, updater, briber1, anon] = provider.getWallets(); + const [deployer, governor, revenueRouter, voter1, voter2, voter3, voter4, delegate1, updater, briber1] = provider.getWallets(); /*************************** VARIABLE DECLARATIONS @@ -96,7 +96,6 @@ describe("BribeController", function () { it("initializes properly", async function () { expect(await bribeController.registry()).eq(registry.address); expect(await bribeController.gaugeController()).eq(gaugeController.address); - expect(await bribeController.revenueRouter()).eq(revenueRouter.address); expect(await bribeController.votingContract()).eq(voting.address); expect(await bribeController.updater()).eq(ZERO_ADDRESS); expect(await bribeController.lastTimeBribesProcessed()).eq(await bribeController.getEpochStartTimestamp()); @@ -151,7 +150,6 @@ describe("BribeController", function () { let registry2: Registry; const RANDOM_ADDRESS_1 = ethers.Wallet.createRandom().connect(provider).address; const RANDOM_ADDRESS_2 = ethers.Wallet.createRandom().connect(provider).address; - const RANDOM_ADDRESS_3 = ethers.Wallet.createRandom().connect(provider).address; before(async function () { registry2 = (await deployContract(deployer, artifacts.Registry, [governor.address])) as Registry; @@ -170,10 +168,6 @@ describe("BribeController", function () { await expect(bribeController.connect(governor).setRegistry(registry2.address)).to.be.revertedWith('ZeroAddressInput("underwritingLockVoting")'); await registry2.connect(governor).set(["underwritingLockVoting"], [RANDOM_ADDRESS_2]); }) - it("reverts if zero address revenueRouter in Registry", async function () { - await expect(bribeController.connect(governor).setRegistry(registry2.address)).to.be.revertedWith('ZeroAddressInput("revenueRouter")'); - await registry2.connect(governor).set(["revenueRouter"], [RANDOM_ADDRESS_3]); - }) it("sets registry", async function () { const tx = await bribeController.connect(governor).setRegistry(registry2.address); await expect(tx).to.emit(bribeController, "RegistrySet").withArgs(registry2.address); @@ -182,7 +176,6 @@ describe("BribeController", function () { expect(await bribeController.registry()).eq(registry2.address); expect(await bribeController.gaugeController()).eq(RANDOM_ADDRESS_1); expect(await bribeController.votingContract()).eq(RANDOM_ADDRESS_2); - expect(await bribeController.revenueRouter()).eq(RANDOM_ADDRESS_3); }); after(async function () { await bribeController.connect(governor).setRegistry(registry.address); @@ -252,8 +245,8 @@ describe("BribeController", function () { }); it("can provide bribe", async () => { const GAUGE_ID = BN.from("1") - await bribeToken1.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(10000)); - await bribeToken2.connect(deployer).transfer(briber1.address, ONE_ETHER.mul(10000)); + 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); @@ -292,9 +285,8 @@ describe("BribeController", function () { expect(await bribeController.getAllGaugesWithBribe()).deep.eq([GAUGE_ID]); }); - it("claimBribes does not revert, but does not do anything", async () => { - const tx = await bribeController.connect(voter1).claimBribes(); - await expect(tx).to.not.emit(bribeController, "BribeClaimed") + it("claimBribes throws if no bribes to claim", async () => { + await expect(bribeController.connect(voter1).claimBribes()).to.be.revertedWith("NoClaimableBribes"); }); }); @@ -485,7 +477,7 @@ describe("BribeController", function () { await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); await expect(bribeController.connect(governor.address).processBribes()).to.be.revertedWith("BribesAlreadyProcessed"); }) - it("after processing bribes, all bribes should be distributed to revenue router, and paused gauge does not impact this", async () => { + 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}) @@ -493,26 +485,46 @@ describe("BribeController", function () { 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) - const OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.address) await bribeController.connect(updater).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 NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(revenueRouter.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); - const REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1_CHANGE = NEW_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1.sub(OLD_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1) expect(VOTER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(0) - expect(BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT.mul(-1)) - expect(REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_1_CHANGE).eq(BRIBE_AMOUNT) - expect(await bribeToken1.balanceOf(revenueRouter.address)).eq(BRIBE_AMOUNT) + 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) }) + 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) + }) after(async function () { await gaugeController.connect(governor).setEpochLengthInWeeks(1); await gaugeController.connect(governor).unpauseGauge(1); @@ -532,7 +544,7 @@ describe("BribeController", function () { * voter3 will initially mirror voter2 voteForBribes, but then remove their bribe */ - describe("voteForMultipleBribes and removeVoteForMultipleBribes scenario", () => { + 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) @@ -542,6 +554,7 @@ describe("BribeController", function () { 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) @@ -562,6 +575,7 @@ describe("BribeController", function () { }); 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"); @@ -682,6 +696,7 @@ describe("BribeController", function () { expect(await bribeController.getVotesForGauge(3)).deep.eq([]) expect(await bribeController.getUnusedVotePowerBPS(voter1.address)).eq(1000) expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) }) it("getClaimableBribes", async () => { const claim1 = await bribeController.getClaimableBribes(voter1.address); @@ -738,10 +753,10 @@ describe("BribeController", function () { expect(CHANGE_REVENUE_ROUTER_BALANCE_BRIBE_TOKEN_2).eq(0) expect(await bribeController.getClaimableBribes(briber1.address)).deep.eq([]); }) - it("claimTokens cannot be called by non-governance", async () => { + 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("claimTokens can be called", async () => { + 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) @@ -782,4 +797,954 @@ describe("BribeController", function () { }) }); + /********************* + 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(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(10000); + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000); + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000); + expect(await bribeController.getUnusedVotePowerBPS(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.getUnusedVotePowerBPS(voter2.address)).eq(2000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(2000) + expect(await bribeController.getUnusedVotePowerBPS(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.getUnusedVotePowerBPS(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(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(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.getUnusedVotePowerBPS(voter1.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(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.getUnusedVotePowerBPS(voter1.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) + expect(await bribeController.getUnusedVotePowerBPS(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 + *********************/ + /** + * 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 = 25; + const TOTAL_VOTERS = 50; + 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 lastTimePremiumsCharged()`) + } + + 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(updater).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.getUnusedVotePowerBPS(VOTER_ARRAY[i])) + } + + const unusedVotePowerBPSArray = await Promise.all(voter_promises_2); + + for (let i = 0; i < VOTER_VOTES_ARRAY.length; i++) { + expect(unusedVotePowerBPSArray[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 From f4e9b4751029993a3b7ec41bfb67b986e01127e4 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Tue, 30 Aug 2022 20:13:55 +1000 Subject: [PATCH 11/24] fixed wrong saveupdatestate index bug --- .../interfaces/native/IBribeController.sol | 3 --- contracts/native/BribeController.sol | 19 +++++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index c6f2574..6f3b33d 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -47,9 +47,6 @@ interface IBribeController { /// @notice Thrown when voteForBribe() attempted by a non-owner or non-delegate. error NotOwnerNorDelegate(); - /// @notice Thrown when attempting to remove vote that does not exist. - error CannotRemoveNonExistentVote(); - /// @notice Thrown when voteForBribe() attempted for gauge without bribe. error NoBribesForSelectedGauge(); diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 44ec679..75ad8ed 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -347,11 +347,12 @@ contract BribeController is uint256 votePowerBPS = votePowerBPSs_[i]; if (_providedBribes[gaugeID].length() == 0) revert NoBribesForSelectedGauge(); // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS + uint256 gas3 = gasleft(); IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS); - (bool votePresent, uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); + console.log("IUnderwritingLockVoting.vote: %s", gas3 - gasleft()); + (,uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); // If remove vote if (votePowerBPS == 0) { - if (!votePresent) revert CannotRemoveNonExistentVote(); _votes[gaugeID].remove(voter_); if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); emit VoteForBribeRemoved(voter_, gaugeID); @@ -360,7 +361,7 @@ contract BribeController is _votes[gaugeID].set(voter_, votePowerBPS); // Change vote - if(votePresent) { + if(oldVotePowerBPS > 0) { emit VoteForBribeChanged(voter_, gaugeID, votePowerBPS, oldVotePowerBPS); // Add vote } else { @@ -619,20 +620,16 @@ contract BribeController is for (uint256 j = _updateInfo.index2 == type(uint88).max || i != _updateInfo.index1 ? 0 : _updateInfo.index2; j < numVotes; j++) { // Checkpoint 1 - // console.log("LOOP 1 START: ", gasleft()); 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); - // console.log("LOOP 1 END: ", gasleft()); } } } - console.log("B"); - // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS // _gaugeToTotalVotePower, _votes and _providedBribes enumerable collections should be empty at the end. { @@ -649,7 +646,7 @@ contract BribeController is // Iterate by bribeToken uint256 numBribeTokens = _providedBribes[gaugeID].length(); for (uint256 k = 0; k < numBribeTokens; k++) { - // console.log("LOOP 2 START: ", gasleft()); + uint256 gas1 = gasleft(); // Checkpoint 2 if (gasleft() < 120000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k);} (address bribeToken, uint256 totalBribeAmount) = _providedBribes[gaugeID].at(k); @@ -657,11 +654,13 @@ contract BribeController is uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; // State mutation 2 _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); - // console.log("LOOP 2 END: ", gasleft()); - if (gasleft() < 100000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k + 1);} + console.log("LOOP 2 GAS COST: ", gas1 - gasleft()); } // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. + uint256 gas2 = gasleft(); + if (gasleft() < 110000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, 0);} _removeVoteForBribeInternal(voter, gaugeID); + console.log("_removeVoteForBribeInternal: %s", gas2 - gasleft()); } } } From 30eac5a410716b4d3954b0a5259d1286f210ee89 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Mon, 5 Sep 2022 13:30:48 +1000 Subject: [PATCH 12/24] removed updater role in bribe, voting and lock contracts --- .../interfaces/native/IBribeController.sol | 19 +------ .../interfaces/native/IGaugeController.sol | 17 ------- .../native/IUnderwritingLockVoting.sol | 17 ------- contracts/native/BribeController.sol | 36 +++---------- contracts/native/GaugeController.sol | 32 +++--------- contracts/native/UnderwritingLockVoting.sol | 40 ++++----------- test/native/BribeController.test.ts | 37 ++++++-------- test/native/GaugeController.test.ts | 17 ++----- test/native/UnderwritingLockVoting.test.ts | 50 ++++++------------- 9 files changed, 57 insertions(+), 208 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index 6f3b33d..8c24de2 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -53,9 +53,6 @@ interface IBribeController { /// @notice Thrown when offerBribe() or voteForBribe() attempted before last epoch bribes are processed. error LastEpochBribesNotProcessed(); - /// @notice Thrown when processBribes() is called by neither governance nor updater, or governance is locked. - error NotUpdaterNorGovernance(); - /// @notice Thrown if processBribes() is called after bribes have already been successfully processed in the current epoch. error BribesAlreadyProcessed(); @@ -84,9 +81,6 @@ interface IBribeController { /// @notice Emitted when registry set. event RegistrySet(address indexed registry); - /// @notice Emitted when the Updater is set. - event UpdaterSet(address indexed updater); - /// @notice Emitted when bribe token added to whitelist. event BribeTokenAdded(address indexed bribeToken); @@ -115,9 +109,6 @@ interface IBribeController { /// @notice Address of UnderwritingLockVoting.sol function votingContract() external view returns (address); - /// @notice Updater address. - function updater() external view returns (address); - /// @notice End timestamp for last epoch that bribes were processed for all stored votes. function lastTimeBribesProcessed() external view returns (uint256); @@ -282,13 +273,6 @@ interface IBribeController { */ 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 Adds token to whitelist of accepted bribe tokens. * Can only be called by the current [**governor**](/docs/protocol/governance). @@ -312,13 +296,12 @@ interface IBribeController { function rescueTokens(address[] memory tokens_, address receiver_) external; /*************************************** - UPDATER FUNCTIONS + 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`. - * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. */ function processBribes() external; } \ No newline at end of file diff --git a/contracts/interfaces/native/IGaugeController.sol b/contracts/interfaces/native/IGaugeController.sol index eb82cb2..1f84ad1 100644 --- a/contracts/interfaces/native/IGaugeController.sol +++ b/contracts/interfaces/native/IGaugeController.sol @@ -72,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 ***************************************/ @@ -106,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); @@ -128,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); @@ -326,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). @@ -366,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 843be05..0a6a9ed 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/native/BribeController.sol b/contracts/native/BribeController.sol index 75ad8ed..13621c5 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -36,10 +36,6 @@ contract BribeController is /// @notice UnderwriterLockVoting.sol address address public override votingContract; - /// @notice Updater address. - /// @dev Second address that can call updateGaugeWeights (in addition to governance). - address public override updater; - /// @notice End timestamp for last epoch that bribes were processed for all stored votes. uint256 public override lastTimeBribesProcessed; @@ -108,14 +104,6 @@ contract BribeController is return IGaugeController(gaugeController).getEpochEndTimestamp(); } - /** - * @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() )); - } - /** * @notice Get unused votePowerBPS for a voter. * @param voter_ The address of the voter to query for. @@ -338,7 +326,7 @@ contract BribeController is // ENABLE INTERNAL CALL TO SKIP CHECKS (which otherwise block processBribes) if (!isInternalCall_) { - if (_getEpochStartTimestamp() != lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); + if (_getEpochStartTimestamp() > lastTimeBribesProcessed) revert LastEpochBribesNotProcessed(); if (voter_ != msg.sender && IUnderwritingLockVoting(votingContract).delegateOf(voter_) != msg.sender) revert NotOwnerNorDelegate(); } @@ -534,16 +522,6 @@ contract BribeController 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 Adds token to whitelist of accepted bribe tokens. * Can only be called by the current [**governor**](/docs/protocol/governance). @@ -581,22 +559,20 @@ contract BribeController is } } - /*************************************** - UPDATER FUNCTIONS - ***************************************/ + /******************************************** + 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`. - * Can only be called by the current [**governor**](/docs/protocol/governance) or the updater role. */ function processBribes() external override { // CHECKS - if (!_isUpdaterOrGovernance()) revert NotUpdaterNorGovernance(); 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(IUnderwritingLockVoting(votingContract).lastTimePremiumsCharged() < currentEpochStartTime) revert LastEpochPremiumsNotCharged(); // If no votes to process // => early cleanup of _gaugesWithBribes and _providedBribes mappings @@ -670,7 +646,7 @@ contract BribeController is } /*************************************** - distributeBribes() HELPER FUNCTIONS + processBribes() HELPER FUNCTIONS ***************************************/ /** diff --git a/contracts/native/GaugeController.sol b/contracts/native/GaugeController.sol index f0b1ecc..0641e86 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; @@ -178,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 ***************************************/ @@ -415,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 @@ -511,18 +499,10 @@ 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. */ @@ -568,13 +548,15 @@ 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.index3 == type(uint88).max ) {_resetVotePowerOfGaugeMapping();} // If first call for epoch, reset _votePowerOfGauge uint256 epochStartTime = _getEpochStartTimestamp(); if (lastTimeGaugeWeightsUpdated >= epochStartTime) revert GaugeWeightsAlreadyUpdated(); diff --git a/contracts/native/UnderwritingLockVoting.sol b/contracts/native/UnderwritingLockVoting.sol index d21f66f..a48dc14 100644 --- a/contracts/native/UnderwritingLockVoting.sol +++ b/contracts/native/UnderwritingLockVoting.sol @@ -63,10 +63,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; @@ -156,14 +152,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 ***************************************/ @@ -285,7 +273,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(); @@ -453,16 +441,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. @@ -475,17 +453,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(); @@ -530,9 +510,9 @@ contract UnderwritingLockVoting is emit AllPremiumsCharged(epochStartTimestamp); } - /*************************************** - updateGaugeWeights() HELPER FUNCTIONS - ***************************************/ + /*********************************** + chargePremiums() HELPER FUNCTIONS + ***********************************/ /** * @notice Save state of charging premium to _updateInfo diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 593c5af..4744e85 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -29,7 +29,7 @@ const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; describe("BribeController", function () { - const [deployer, governor, revenueRouter, voter1, voter2, voter3, voter4, delegate1, updater, briber1] = provider.getWallets(); + const [deployer, governor, revenueRouter, voter1, voter2, voter3, voter4, delegate1, briber1, anon] = provider.getWallets(); /*************************** VARIABLE DECLARATIONS @@ -97,7 +97,6 @@ describe("BribeController", 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.updater()).eq(ZERO_ADDRESS); expect(await bribeController.lastTimeBribesProcessed()).eq(await bribeController.getEpochStartTimestamp()); expect(await bribeController.getBribeTokenWhitelist()).deep.eq([]); expect(await bribeController.getClaimableBribes(voter1.address)).deep.eq([]); @@ -182,17 +181,6 @@ describe("BribeController", function () { }); }); - describe("setUpdater", () => { - it("non governor cannot setUpdater", async () => { - await expect(bribeController.connect(voter1).setUpdater(updater.address)).to.be.revertedWith("!governance"); - }); - it("can set updater", async () => { - let tx = await bribeController.connect(governor).setUpdater(updater.address); - await expect(tx).to.emit(bribeController, "UpdaterSet").withArgs(updater.address); - expect(await bribeController.updater()).eq(updater.address) - }); - }); - describe("addBribeToken", () => { it("non governor cannot add new bribe token", async () => { await expect(bribeController.connect(voter1).addBribeToken(bribeToken1.address)).to.be.revertedWith("!governance"); @@ -364,9 +352,6 @@ describe("BribeController", function () { await underwritingLocker.connect(governor).setVotingContract() await gaugeController.connect(governor).addTokenholder(underwritingLocker.address) }); - it("will throw if called by non-governor or non-updater", async () => { - await expect(bribeController.connect(briber1).processBribes()).to.be.revertedWith("NotUpdaterNorGovernance"); - }); it("will throw if called in same epoch as contract deployment", async () => { await expect(bribeController.connect(governor).processBribes()).to.be.revertedWith("BribesAlreadyProcessed"); }); @@ -384,9 +369,9 @@ describe("BribeController", function () { 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("updater can process bribes", async () => { + it("anon can process bribes", async () => { const EPOCH_START_TIMESTAMP = await bribeController.getEpochStartTimestamp(); - const tx = await bribeController.connect(governor).processBribes({gasLimit: CUSTOM_GAS_LIMIT}); + 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); @@ -485,7 +470,7 @@ describe("BribeController", function () { 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(updater).processBribes({gasLimit: CUSTOM_GAS_LIMIT}) + 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) @@ -525,8 +510,18 @@ describe("BribeController", function () { 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).setEpochLengthInWeeks(1); await gaugeController.connect(governor).unpauseGauge(1); }); }); @@ -1638,7 +1633,7 @@ describe("BribeController", function () { let counter = 0; while (true) { counter += 1; - const tx = await bribeController.connect(updater).processBribes({gasLimit: CUSTOM_GAS_LIMIT}) + 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"); diff --git a/test/native/GaugeController.test.ts b/test/native/GaugeController.test.ts index 6ae39d8..88f6bd2 100644 --- a/test/native/GaugeController.test.ts +++ b/test/native/GaugeController.test.ts @@ -31,7 +31,7 @@ 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,7 +73,6 @@ 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(await gaugeController.getEpochStartTimestamp()); @@ -199,16 +198,6 @@ 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 () => { @@ -357,10 +346,10 @@ describe("GaugeController", function () { it("cannot update gauge in same epoch as contract deployment", async () => { await expect(gaugeController.connect(governor).updateGaugeWeights()).to.be.revertedWith("GaugeWeightsAlreadyUpdated"); }); - it("updater can updateGaugeWeight in next epoch", async () => { + 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 44ed9b9..e7e3018 100644 --- a/test/native/UnderwritingLockVoting.test.ts +++ b/test/native/UnderwritingLockVoting.test.ts @@ -28,7 +28,7 @@ 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,7 +89,6 @@ 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(await voting.lastTimePremiumsCharged()); expect(await voting.isVotingOpen()).eq(true); @@ -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 *********************/ @@ -474,11 +451,8 @@ describe("UnderwritingLockVoting", function () { await provider.send("evm_mine", [CURRENT_TIME + ONE_WEEK]); await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("GaugeWeightsNotYetUpdated"); }); - it("updateGaugeWeights() should revert if non governor or updater", async function () { - await expect(gaugeController.connect(voter1).updateGaugeWeights()).to.be.revertedWith("NotUpdaterNorGovernance"); - }); - it("updateGaugeWeights() called by updater should succeed in the next epoch", async function () { - const tx = await gaugeController.connect(updater).updateGaugeWeights({gasLimit: CUSTOM_GAS_LIMIT}); + 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); @@ -508,16 +482,13 @@ describe("UnderwritingLockVoting", function () { it("chargePremiums() should revert before underwritingLocker.sol call setVotingContract()", async function () { await expect(voting.connect(governor).chargePremiums()).to.be.revertedWith("NotVotingContract"); }); - it("chargePremiums() should revert if non governor or updater", async function () { - await expect(voting.connect(voter1).chargePremiums()).to.be.revertedWith("NotUpdaterNorGovernance"); - }); - 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); @@ -634,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; @@ -714,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); }); }); From 7252bd17a964d741d1d199910989b73e1db70d8c Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Mon, 5 Sep 2022 15:41:54 +1000 Subject: [PATCH 13/24] changed implementation for UnderwritingLocker.chargePremium --- .../interfaces/native/IUnderwritingLocker.sol | 7 +++--- contracts/native/UnderwritingLockVoting.sol | 13 +---------- contracts/native/UnderwritingLocker.sol | 12 ++++++---- test/native/UnderwritingLocker.test.ts | 23 +++++++++++-------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/contracts/interfaces/native/IUnderwritingLocker.sol b/contracts/interfaces/native/IUnderwritingLocker.sol index d989771..481bf64 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/native/UnderwritingLockVoting.sol b/contracts/native/UnderwritingLockVoting.sol index a48dc14..b4bde52 100644 --- a/contracts/native/UnderwritingLockVoting.sol +++ b/contracts/native/UnderwritingLockVoting.sol @@ -482,18 +482,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.index3 == type(uint88).max || i != _updateInfo.index2 ? 0 : _updateInfo.index3; 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; } diff --git a/contracts/native/UnderwritingLocker.sol b/contracts/native/UnderwritingLocker.sol index 3ef8bf3..78b89d8 100644 --- a/contracts/native/UnderwritingLocker.sol +++ b/contracts/native/UnderwritingLocker.sol @@ -707,14 +707,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/test/native/UnderwritingLocker.test.ts b/test/native/UnderwritingLocker.test.ts index a676603..3cd4d35 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) From 109278caba83c1827ce13950bd13553c1e436dcd Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Tue, 6 Sep 2022 23:39:53 +1000 Subject: [PATCH 14/24] fixed processbug bribes on larger sample test --- contracts/native/BribeController.sol | 27 +++++++++++++++++---------- test/native/BribeController.test.ts | 4 ++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 13621c5..d28aa01 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -61,6 +61,11 @@ contract BribeController is /// @notice gaugeID => voter => votePowerBPS. mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _votes; + /// @notice Mirror of votes. + /// @dev _votes will be cleaned up in processBribes(), _votesMirror will not be. + /// @dev This will enable _voteForBribe to remove previous epoch's voteForBribes. + mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _votesMirror; + /// @notice whitelist of tokens that can be accepted as bribes EnumerableSet.AddressSet internal _bribeTokenWhitelist; @@ -337,7 +342,6 @@ contract BribeController is // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS uint256 gas3 = gasleft(); IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS); - console.log("IUnderwritingLockVoting.vote: %s", gas3 - gasleft()); (,uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); // If remove vote if (votePowerBPS == 0) { @@ -582,11 +586,10 @@ contract BribeController is return _concludeProcessBribes(currentEpochStartTime); } - console.log("===Loaded state: 1: %s, 2: %s, 3: %s", _updateInfo.index1, _updateInfo.index2, _updateInfo.index3); - // LOOP 1 - GET TOTAL VOTE POWER CHASING BRIBES FOR EACH GAUGE // Block-scope to avoid stack too deep error { + uint256 gas0 = gasleft(); uint256 numGauges = _gaugeToTotalVotePower.length(); // Iterate by gauge for (uint256 i = _updateInfo.index1 == type(uint80).max ? 0 : _updateInfo.index1; i < numGauges; i++) { @@ -609,6 +612,7 @@ contract BribeController is // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS // _gaugeToTotalVotePower, _votes and _providedBribes enumerable collections should be empty at the end. { + uint256 gas0 = gasleft(); // Iterate by gauge while (_gaugeToTotalVotePower.length() > 0) { (uint256 gaugeID, uint256 votePowerSum) = _gaugeToTotalVotePower.at(0); @@ -621,28 +625,31 @@ contract BribeController is // Iterate by bribeToken uint256 numBribeTokens = _providedBribes[gaugeID].length(); - for (uint256 k = 0; k < numBribeTokens; k++) { + for (uint256 k = _updateInfo.index3 == type(uint88).max ? 0 : _updateInfo.index3; k < numBribeTokens; k++) { uint256 gas1 = gasleft(); // Checkpoint 2 - if (gasleft() < 120000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k);} + 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); uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; // State mutation 2 _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); - console.log("LOOP 2 GAS COST: ", gas1 - gasleft()); + // console.log("LOOP 2 D - GAS COST PER BRIBE TOKEN: ", gas1 - gasleft()); } + if (_updateInfo.index3 != 0) {_updateInfo.index3 = type(uint88).max;} // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. uint256 gas2 = gasleft(); - if (gasleft() < 110000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, 0);} + if (gasleft() < 110000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, type(uint88).max - 1);} _removeVoteForBribeInternal(voter, gaugeID); - console.log("_removeVoteForBribeInternal: %s", gas2 - gasleft()); } } + console.log("===BLOCK 2 GAS: %s", gas0 - gasleft()); } // Cleanup _gaugesWithBribes and _providedBribes enumerable collections. - _concludeProcessBribes(currentEpochStartTime); + return _concludeProcessBribes(currentEpochStartTime); } /*************************************** @@ -656,7 +663,6 @@ contract BribeController is * @param loop2BribeTokenIndex_ Current index of _providedBribes[gaugeID] in loop 2. */ function _saveUpdateState(uint256 loop1GaugeIndex_, uint256 loop1VoteIndex_, uint256 loop2BribeTokenIndex_) internal { - console.log("===SAVEUPDATESTATE: 1: %s, 2: %s, 3: %s", loop1GaugeIndex_, loop1VoteIndex_, loop2BribeTokenIndex_); assembly { let updateInfo updateInfo := or(updateInfo, shr(176, shl(176, loop1GaugeIndex_))) // [0:80] => votingContractsIndex_ @@ -682,6 +688,7 @@ contract BribeController is 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); } diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 4744e85..75cd79f 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -1508,8 +1508,8 @@ describe("BribeController", function () { */ describe("stress test with 25 voters and 50 gauges", () => { - const TOTAL_GAUGES = 25; - const TOTAL_VOTERS = 50; + const TOTAL_GAUGES = 50; + const TOTAL_VOTERS = 25; let WALLET_ARRAY: Wallet[] = [] let VOTER_ARRAY: string[] = [] From 19933bc440645b495ad0e24aa518b4130ba891ba Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Wed, 7 Sep 2022 00:09:35 +1000 Subject: [PATCH 15/24] added pre-initialize claimablebribes optimization to bribecontroller --- contracts/native/BribeController.sol | 25 +++++++++++++++++++++++-- test/native/BribeController.test.ts | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index d28aa01..a71c0e4 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -166,9 +166,15 @@ contract BribeController is */ function getClaimableBribes(address voter_) external view override returns (Bribe[] memory bribes) { uint256 length = _claimableBribes[voter_].length(); - bribes = new Bribe[](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; @@ -357,12 +363,26 @@ contract BribeController is 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() + * @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 ***************************************/ @@ -503,10 +523,10 @@ contract BribeController is 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); } @@ -633,6 +653,7 @@ contract BribeController is } (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); diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 75cd79f..c7193f5 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -1623,7 +1623,7 @@ describe("BribeController", function () { break; } } - console.log(`Required ${counter} iterations of lastTimePremiumsCharged()`) + console.log(`Required ${counter} iterations of chargePremiums()`) } const OLD_BRIBING_CONTROLLER_BALANCE_BRIBE_TOKEN_1 = await bribeToken1.balanceOf(bribeController.address) From 450e023794723501ab387611ad51562ab403259d Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Fri, 9 Sep 2022 18:05:42 +1000 Subject: [PATCH 16/24] added 'shift removevote work from processbribes to voter action' optimization --- .../interfaces/native/IBribeController.sol | 6 ++ contracts/native/BribeController.sol | 68 +++++++++++++---- test/native/BribeController.test.ts | 73 +++++++++++++------ 3 files changed, 108 insertions(+), 39 deletions(-) diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index 8c24de2..81375ef 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -135,6 +135,12 @@ interface IBribeController { */ 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 diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index a71c0e4..cd07ad2 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -61,10 +61,10 @@ contract BribeController is /// @notice gaugeID => voter => votePowerBPS. mapping(uint256 => EnumerableMapS.AddressToUintMap) internal _votes; - /// @notice Mirror of votes. - /// @dev _votes will be cleaned up in processBribes(), _votesMirror will not be. + /// @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(uint256 => EnumerableMapS.AddressToUintMap) internal _votesMirror; + mapping(address => EnumerableMapS.UintToUintMap) internal _votesMirror; /// @notice whitelist of tokens that can be accepted as bribes EnumerableSet.AddressSet internal _bribeTokenWhitelist; @@ -118,6 +118,25 @@ contract BribeController is 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 ***************************************/ @@ -147,6 +166,15 @@ contract BribeController is 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 @@ -339,6 +367,18 @@ contract BribeController is 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); + if (gaugeID != 0) {IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, 0);} + } + } + } } for(uint256 i = 0; i < gaugeIDs_.length; i++) { @@ -346,18 +386,19 @@ contract BribeController is uint256 votePowerBPS = votePowerBPSs_[i]; if (_providedBribes[gaugeID].length() == 0) revert NoBribesForSelectedGauge(); // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS - uint256 gas3 = gasleft(); - IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS); - (,uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); + (bool success, 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_); if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); emit VoteForBribeRemoved(voter_, gaugeID); } 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); @@ -372,6 +413,7 @@ contract BribeController is /** * @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. */ @@ -596,15 +638,13 @@ contract BribeController is 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 (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); - } + 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 @@ -632,7 +672,6 @@ contract BribeController is // LOOP 2 - DO ACCOUNTING FOR _claimableBribes AND _providedBribes MAPPINGS // _gaugeToTotalVotePower, _votes and _providedBribes enumerable collections should be empty at the end. { - uint256 gas0 = gasleft(); // Iterate by gauge while (_gaugeToTotalVotePower.length() > 0) { (uint256 gaugeID, uint256 votePowerSum) = _gaugeToTotalVotePower.at(0); @@ -646,7 +685,6 @@ contract BribeController is // Iterate by bribeToken uint256 numBribeTokens = _providedBribes[gaugeID].length(); for (uint256 k = _updateInfo.index3 == type(uint88).max ? 0 : _updateInfo.index3; k < numBribeTokens; k++) { - uint256 gas1 = gasleft(); // Checkpoint 2 if (gasleft() < 120000) { return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, k); @@ -657,16 +695,14 @@ contract BribeController is uint256 bribeAmount = totalBribeAmount * bribeProportion / 1e18; // State mutation 2 _claimableBribes[voter].set(bribeToken, runningClaimableAmount + bribeAmount); - // console.log("LOOP 2 D - GAS COST PER BRIBE TOKEN: ", gas1 - gasleft()); } if (_updateInfo.index3 != 0) {_updateInfo.index3 = type(uint88).max;} // Cleanup _votes, _gaugeToTotalVotePower enumerable collections. - uint256 gas2 = gasleft(); if (gasleft() < 110000) {return _saveUpdateState(type(uint80).max - 1, type(uint88).max - 1, type(uint88).max - 1);} + uint256 gas1 = gasleft(); _removeVoteForBribeInternal(voter, gaugeID); } } - console.log("===BLOCK 2 GAS: %s", gas0 - gasleft()); } // Cleanup _gaugesWithBribes and _providedBribes enumerable collections. diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index c7193f5..6126254 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -101,6 +101,7 @@ describe("BribeController", function () { 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([]); @@ -320,6 +321,7 @@ describe("BribeController", function () { 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 () => { @@ -381,7 +383,8 @@ describe("BribeController", function () { 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.getUnusedVotePowerBPS(voter1.address)).eq(0) + expect(await bribeController.getAvailableVotePowerBPS(voter1.address)).eq(1000) }) }); @@ -460,7 +463,7 @@ describe("BribeController", function () { 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.revertedWith("BribesAlreadyProcessed"); + 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; @@ -486,6 +489,7 @@ describe("BribeController", function () { 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"); @@ -580,6 +584,7 @@ describe("BribeController", function () { }); 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 () => { @@ -596,14 +601,18 @@ describe("BribeController", function () { 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); @@ -689,9 +698,12 @@ describe("BribeController", function () { 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(1000) - expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) + 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); @@ -786,7 +798,7 @@ describe("BribeController", function () { 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_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) }) @@ -853,15 +865,20 @@ describe("BribeController", function () { 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(1000); + 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(10000); - expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000); + 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); @@ -876,16 +893,22 @@ describe("BribeController", function () { 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); @@ -1012,10 +1035,14 @@ describe("BribeController", function () { 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(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(10000) + 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); @@ -1357,10 +1384,10 @@ describe("BribeController", function () { 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(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(10000) + 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([]); @@ -1483,10 +1510,10 @@ describe("BribeController", function () { 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(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter2.address)).eq(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter3.address)).eq(10000) - expect(await bribeController.getUnusedVotePowerBPS(voter4.address)).eq(10000) + 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([]); @@ -1686,13 +1713,13 @@ describe("BribeController", function () { const voter_promises_2 = [] for (let i = 0; i < TOTAL_VOTERS; i++) { - voter_promises_2.push(bribeController.getUnusedVotePowerBPS(VOTER_ARRAY[i])) + voter_promises_2.push(bribeController.getAvailableVotePowerBPS(VOTER_ARRAY[i])) } - const unusedVotePowerBPSArray = await Promise.all(voter_promises_2); + const availableVotePowerBPSArray = await Promise.all(voter_promises_2); for (let i = 0; i < VOTER_VOTES_ARRAY.length; i++) { - expect(unusedVotePowerBPSArray[i]).eq(10000); + expect(availableVotePowerBPSArray[i]).eq(10000); } }); it("getClaimableBribes", async () => { From 9befab92fc9c000c957f19e85c5e00926b9a68e9 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Fri, 9 Sep 2022 20:29:18 +1000 Subject: [PATCH 17/24] did hardhat-coverage and slither tests for BribeController.sol --- contracts/native/BribeController.sol | 3 +-- test/native/BribeController.test.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index cd07ad2..f81f33e 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -649,7 +649,6 @@ contract BribeController is // LOOP 1 - GET TOTAL VOTE POWER CHASING BRIBES FOR EACH GAUGE // Block-scope to avoid stack too deep error { - uint256 gas0 = gasleft(); uint256 numGauges = _gaugeToTotalVotePower.length(); // Iterate by gauge for (uint256 i = _updateInfo.index1 == type(uint80).max ? 0 : _updateInfo.index1; i < numGauges; i++) { @@ -680,7 +679,7 @@ contract BribeController is 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 = (IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) * votePowerBPS / 10000) * 1e18 / (votePowerSum - 1); + uint256 bribeProportion = 1e18 * (IUnderwritingLockVoting(votingContract).getLastProcessedVotePowerOf(voter) * votePowerBPS / 10000) / (votePowerSum - 1); // Iterate by bribeToken uint256 numBribeTokens = _providedBribes[gaugeID].length(); diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 6126254..0739c32 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -23,7 +23,6 @@ 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 SCALE_FACTOR = ONE_ETHER; const ONE_PERCENT = ONE_ETHER.div(100); const ONE_HUNDRED_PERCENT = ONE_ETHER; const CUSTOM_GAS_LIMIT = 6000000; @@ -112,10 +111,10 @@ describe("BribeController", function () { 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 gaugeController.getEpochStartTimestamp()).eq(EXPECTED_EPOCH_START_TIME) + expect(await bribeController.getEpochStartTimestamp()).eq(EXPECTED_EPOCH_START_TIME) }); it("getEpochEndTimestamp() == getEpochStartTimestamp() + ONE_WEEK ", async function () { - expect(await gaugeController.getEpochEndTimestamp()).eq((await gaugeController.getEpochStartTimestamp()).add(ONE_WEEK)) + expect(await bribeController.getEpochEndTimestamp()).eq((await bribeController.getEpochStartTimestamp()).add(ONE_WEEK)) }); }); @@ -334,6 +333,10 @@ describe("BribeController", function () { 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"); + }) }); /******************* From ef8c5045f2ed649b9b2095f2fa155326a2408846 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Tue, 13 Sep 2022 00:00:04 +1000 Subject: [PATCH 18/24] minor style changes to bribecontroller --- contracts/native/BribeController.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index f81f33e..b54011d 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -125,8 +125,9 @@ contract BribeController is */ function _getAvailableVotePowerBPS(address voter_) internal view returns (uint256 availableVotePowerBPS) { (,uint256 epochEndTimestamp) = _votesMirror[voter_].tryGet(0); - if (epochEndTimestamp == _getEpochEndTimestamp()) {return _getUnusedVotePowerBPS(voter_);} - else { + if (epochEndTimestamp == _getEpochEndTimestamp()) { + return _getUnusedVotePowerBPS(voter_); + } else { uint256 length = _votesMirror[voter_].length(); uint256 staleVotePowerBPS = 0; for (uint256 i = 0; i < length; i++) { @@ -386,7 +387,7 @@ contract BribeController is uint256 votePowerBPS = votePowerBPSs_[i]; if (_providedBribes[gaugeID].length() == 0) revert NoBribesForSelectedGauge(); // USE CHECKS IN EXTERNAL CALLS BEFORE FURTHER INTERNAL STATE MUTATIONS - (bool success, uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); + (, uint256 oldVotePowerBPS) = _votes[gaugeID].tryGet(voter_); if(!isInternalCall_) {IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, votePowerBPS);} // If remove vote if (votePowerBPS == 0) { From 4884b1ce439648bfdf159e84ba6d92b9b1d273f7 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Thu, 15 Sep 2022 13:33:30 +1000 Subject: [PATCH 19/24] added bribe process lock on providing bribes --- contracts/native/BribeController.sol | 4 +++- test/native/BribeController.test.ts | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index b54011d..0486936 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -376,7 +376,8 @@ contract BribeController is while(_votesMirror[voter_].length() > 0) { (uint256 gaugeID, uint256 votePowerBPS) = _votesMirror[voter_].at(0); _votesMirror[voter_].remove(gaugeID); - if (gaugeID != 0) {IUnderwritingLockVoting(votingContract).vote(voter_, gaugeID, 0);} + // '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 {}} } } } @@ -442,6 +443,7 @@ contract BribeController is 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(); diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 0739c32..1fef439 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -374,6 +374,9 @@ describe("BribeController", function () { 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}); From b15f446f88252135d30e5ef11693f7fcae4c7327 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Mon, 19 Sep 2022 14:47:08 +1000 Subject: [PATCH 20/24] removed nonreentrant modifier and event emission for _removeVoteForBribeInternal call --- contracts/native/BribeController.sol | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/contracts/native/BribeController.sol b/contracts/native/BribeController.sol index 0486936..248a820 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -355,12 +355,13 @@ contract BribeController is /** * @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 nonReentrant { + function _voteForBribe(address voter_, uint256[] memory gaugeIDs_, uint256[] memory votePowerBPSs_, bool isInternalCall_) internal { // CHECKS if (gaugeIDs_.length != votePowerBPSs_.length) revert ArrayArgumentsLengthMismatch(); @@ -393,9 +394,9 @@ contract BribeController is // If remove vote if (votePowerBPS == 0) { if(!isInternalCall_) _votesMirror[voter_].remove(gaugeID); - _votes[gaugeID].remove(voter_); - if (_votes[gaugeID].length() == 0) _gaugeToTotalVotePower.remove(gaugeID); - emit VoteForBribeRemoved(voter_, 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); @@ -489,7 +490,7 @@ contract BribeController is * @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 { + 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_; @@ -503,7 +504,7 @@ contract BribeController is * @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 { + function voteForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_, uint256[] calldata votePowerBPSs_) external override nonReentrant { _voteForBribe(voter_, gaugeIDs_, votePowerBPSs_, false); } @@ -514,7 +515,7 @@ contract BribeController is * @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 { + 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); @@ -526,7 +527,7 @@ contract BribeController is * @param voter_ address of voter. * @param gaugeID_ The ID of the gauge to remove vote for. */ - function removeVoteForBribe(address voter_, uint256 gaugeID_) external override { + function removeVoteForBribe(address voter_, uint256 gaugeID_) external override nonReentrant { uint256[] memory gaugeIDs_ = new uint256[](1); uint256[] memory votePowerBPSs_ = new uint256[](1); gaugeIDs_[0] = gaugeID_; @@ -539,7 +540,7 @@ contract BribeController is * @param voter_ address of voter. * @param gaugeIDs_ Array of gaugeIDs to remove votes for */ - function removeVotesForMultipleBribes(address voter_, uint256[] calldata gaugeIDs_) external override { + 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); @@ -552,7 +553,7 @@ contract BribeController is * @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 { + 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;} @@ -658,7 +659,8 @@ contract BribeController is // 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);} @@ -678,7 +680,7 @@ contract BribeController is while (_gaugeToTotalVotePower.length() > 0) { (uint256 gaugeID, uint256 votePowerSum) = _gaugeToTotalVotePower.at(0); - // Iterate by vote + // 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. @@ -701,8 +703,7 @@ contract BribeController is 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);} - uint256 gas1 = gasleft(); - _removeVoteForBribeInternal(voter, gaugeID); + _removeVoteForBribeInternal(voter, gaugeID); // 20-30K gas per call } } } From 0df2cd8b86b4a7482e61ed8fafcdce07dbf7096f Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Thu, 22 Sep 2022 13:28:06 +1000 Subject: [PATCH 21/24] added multiple briber unit test --- test/native/BribeController.test.ts | 116 ++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 1fef439..a60a082 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -1530,6 +1530,122 @@ describe("BribeController", function () { }); }); + /********************* + 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 *********************/ From 64f2f1039600ed0349d6d72676413bc3bb0fb445 Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Thu, 29 Sep 2022 04:32:28 +1000 Subject: [PATCH 22/24] Fix edge case where voteForBribe mutated via votingContract, and mutation not correctly reflected in bribeController --- .../interfaces/native/IBribeController.sol | 8 +- contracts/interfaces/native/IVoteListener.sol | 17 ++ contracts/native/BribeController.sol | 22 +++ contracts/native/UnderwritingLockVoting.sol | 26 +++ test/native/BribeController.test.ts | 155 ++++++++++++++++++ 5 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 contracts/interfaces/native/IVoteListener.sol diff --git a/contracts/interfaces/native/IBribeController.sol b/contracts/interfaces/native/IBribeController.sol index 81375ef..36c8f3c 100644 --- a/contracts/interfaces/native/IBribeController.sol +++ b/contracts/interfaces/native/IBribeController.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.6; import "./GaugeStructs.sol"; +import "./IVoteListener.sol"; -interface IBribeController { +interface IBribeController is IVoteListener { /*************************************** STRUCTS ***************************************/ @@ -44,6 +45,9 @@ interface IBribeController { /// @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(); @@ -266,7 +270,7 @@ interface IBribeController { * @notice Claim bribes. */ function claimBribes() external; - + /*************************************** GOVERNANCE FUNCTIONS ***************************************/ diff --git a/contracts/interfaces/native/IVoteListener.sol b/contracts/interfaces/native/IVoteListener.sol new file mode 100644 index 0000000..b9816d4 --- /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 index a23fdfc..e003157 100644 --- a/contracts/native/BribeController.sol +++ b/contracts/native/BribeController.sol @@ -577,6 +577,28 @@ contract BribeController is } } + /*************************************** + 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 ***************************************/ diff --git a/contracts/native/UnderwritingLockVoting.sol b/contracts/native/UnderwritingLockVoting.sol index 8b3ddad..34d6eeb 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 @@ -300,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 ***************************************/ diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index a60a082..17b6019 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -1645,6 +1645,161 @@ describe("BribeController", function () { }) }); + /********************* + 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("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 From a4366f35f3259e7bb02cf1d1ccf81e0159da6ebf Mon Sep 17 00:00:00 2001 From: kyzooghost Date: Thu, 29 Sep 2022 14:55:55 +1000 Subject: [PATCH 23/24] added unit test for access control of new receiveVoteNotification function --- test/native/BribeController.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/native/BribeController.test.ts b/test/native/BribeController.test.ts index 17b6019..aa7da3b 100644 --- a/test/native/BribeController.test.ts +++ b/test/native/BribeController.test.ts @@ -1666,6 +1666,9 @@ describe("BribeController", function () { 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); From 0aef58299beda253dce7faca49d1c5e572f3f4bf Mon Sep 17 00:00:00 2001 From: Andrew Leonard Date: Fri, 30 Sep 2022 17:26:27 -0700 Subject: [PATCH 24/24] deploy and use on goerli --- scripts/goerli/deploy-native-locker.ts | 82 ++-- scripts/goerli/use-native.ts | 553 +++++++++++++++++++------ 2 files changed, 473 insertions(+), 162 deletions(-) diff --git a/scripts/goerli/deploy-native-locker.ts b/scripts/goerli/deploy-native-locker.ts index 42ac9bf..649e9bc 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 76f4161..6b3b94c 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());