diff --git a/src/BaseGauge.sol b/src/BaseGauge.sol index 8d0f365..3599394 100644 --- a/src/BaseGauge.sol +++ b/src/BaseGauge.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.15; import "src/libraries/Math.sol"; import "src/interfaces/IBribe.sol"; -import "src/interfaces/IGaugeFactory.sol"; import "src/interfaces/IBaseGauge.sol"; import "src/interfaces/IVoter.sol"; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; @@ -39,7 +38,7 @@ abstract contract BaseGauge is IBaseGauge { View functions */ - function getVotingStage(uint256 timestamp) public pure returns (VotingStage) { + function getVotingStage(uint256 timestamp) external pure returns (VotingStage) { uint256 modTime = timestamp % (1 weeks); if (modTime < BRIBE_LAG) { return VotingStage.BribesPhase; @@ -100,12 +99,6 @@ abstract contract BaseGauge is IBaseGauge { require(success && (data.length == 0 || abi.decode(data, (bool)))); } - function _safeApprove(address token, address spender, uint256 value) internal { - require(token.code.length > 0); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value)); - require(success && (data.length == 0 || abi.decode(data, (bool)))); - } - /** * @notice Override function to implement passthrough logic * @param _amount Amount of rewards diff --git a/src/Bribe.sol b/src/Bribe.sol index 2c4d160..6ecffde 100644 --- a/src/Bribe.sol +++ b/src/Bribe.sol @@ -21,8 +21,8 @@ contract Bribe is IBribe { uint256 public supplyNumCheckpoints; uint256 public totalSupply; - address public veALCX; - address public voter; + address public immutable veALCX; + address public immutable voter; address public gauge; // Address of the gauge that the bribes are for address[] public rewards; @@ -250,7 +250,8 @@ contract Bribe is IBribe { function getRewardForOwner(uint256 tokenId, address[] memory tokens) external lock { require(msg.sender == voter, "not voter"); address _owner = IVotingEscrow(veALCX).ownerOf(tokenId); - for (uint256 i = 0; i < tokens.length; i++) { + uint256 length = tokens.length; + for (uint256 i = 0; i < length; i++) { uint256 _reward = earned(tokens[i], tokenId); require(_reward > 0, "no rewards to claim"); diff --git a/src/CurveEthPoolAdapter.sol b/src/CurveEthPoolAdapter.sol index afd3fc6..b5e72ab 100644 --- a/src/CurveEthPoolAdapter.sol +++ b/src/CurveEthPoolAdapter.sol @@ -8,18 +8,14 @@ import "src/libraries/TokenUtils.sol"; import "src/interfaces/IWETH9.sol"; contract CurveEthPoolAdapter is IPoolAdapter { - address public override pool; + address public immutable override pool; mapping(address => int128) public tokenIds; bool public isMetapool; - address public weth; + address public immutable weth; - constructor( - address _pool, - address[] memory _tokens, - address _weth - ) { + constructor(address _pool, address[] memory _tokens, address _weth) { weth = _weth; pool = _pool; for (uint256 i; i < _tokens.length; i++) { diff --git a/src/FluxToken.sol b/src/FluxToken.sol index 4d4714a..0267c0e 100644 --- a/src/FluxToken.sol +++ b/src/FluxToken.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.15; import "src/interfaces/IFluxToken.sol"; +import "src/interfaces/IVotingEscrow.sol"; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -14,10 +15,19 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken { /// @dev The address which enables the minting of tokens. address public minter; + address public voter; + address public veALCX; + address public admin; // the timelock executor + address public pendingAdmin; // the timelock executor + + mapping(uint256 => uint256) public unclaimedFlux; // tokenId => amount of unclaimed flux constructor(address _minter) { require(_minter != address(0), "FluxToken: minter cannot be zero address"); minter = _minter; + voter = _minter; + veALCX = _minter; + admin = _minter; } /// @dev Modifier which checks that the caller has the minter role. @@ -26,6 +36,33 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken { _; } + /// @inheritdoc IFluxToken + function setAdmin(address _admin) external { + require(msg.sender == admin, "not admin"); + pendingAdmin = _admin; + } + + /// @inheritdoc IFluxToken + function acceptAdmin() external { + require(msg.sender == pendingAdmin, "not pending admin"); + admin = pendingAdmin; + emit AdminUpdated(pendingAdmin); + } + + /// @inheritdoc IFluxToken + function setVoter(address _voter) external { + require(msg.sender == admin, "not admin"); + require(_voter != address(0), "FluxToken: voter cannot be zero address"); + voter = _voter; + } + + /// @inheritdoc IFluxToken + function setVeALCX(address _veALCX) external { + require(msg.sender == admin, "not admin"); + require(_veALCX != address(0), "FluxToken: veALCX cannot be zero address"); + veALCX = _veALCX; + } + /// @inheritdoc IFluxToken function setMinter(address _minter) external onlyMinter { require(_minter != address(0), "FluxToken: minter cannot be zero address"); @@ -44,4 +81,44 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken { _approve(_account, msg.sender, newAllowance); _burn(_account, _amount); } + + /// @inheritdoc IFluxToken + function getUnclaimedFlux(uint256 _tokenId) external view returns (uint256) { + return unclaimedFlux[_tokenId]; + } + + /// @inheritdoc IFluxToken + function mergeFlux(uint256 _fromTokenId, uint256 _toTokenId) external { + require(msg.sender == veALCX, "not veALCX"); + + unclaimedFlux[_toTokenId] += unclaimedFlux[_fromTokenId]; + unclaimedFlux[_fromTokenId] = 0; + } + + /// @inheritdoc IFluxToken + function accrueFlux(uint256 _tokenId) external { + require(msg.sender == voter, "not voter"); + uint256 amount = IVotingEscrow(veALCX).claimableFlux(_tokenId); + unclaimedFlux[_tokenId] += amount; + } + + /// @inheritdoc IFluxToken + function updateFlux(uint256 _tokenId, uint256 _amount) external { + require(msg.sender == voter, "not voter"); + require(_amount <= unclaimedFlux[_tokenId], "not enough flux"); + unclaimedFlux[_tokenId] -= _amount; + } + + /// @inheritdoc IFluxToken + function claimFlux(uint256 _tokenId, uint256 _amount) external { + require(unclaimedFlux[_tokenId] >= _amount, "amount greater than unclaimed balance"); + + if (msg.sender != veALCX) { + require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId), "not approved"); + } + + unclaimedFlux[_tokenId] -= _amount; + + _mint(IVotingEscrow(veALCX).ownerOf(_tokenId), _amount); + } } diff --git a/src/Minter.sol b/src/Minter.sol index d38b280..b880116 100644 --- a/src/Minter.sol +++ b/src/Minter.sol @@ -21,9 +21,9 @@ contract Minter is IMinter { using SafeMath for uint256; // Allows minting once per epoch (epoch = 1 week, reset every Thursday 00:00 UTC) - uint256 public constant WEEK = 1 weeks; + uint256 public immutable WEEK = 1 weeks; + uint256 public immutable BPS = 10_000; uint256 public constant TAIL_EMISSIONS_RATE = 2194e18; // Tail emissions rate - uint256 public constant BPS = 10_000; uint256 public epochEmissions; uint256 public activePeriod; @@ -39,9 +39,7 @@ contract Minter is IMinter { address public initializer; address public treasury; - bool public initialized; - - IAlchemixToken public alcx; + IAlchemixToken public immutable alcx; IVoter public immutable voter; IVotingEscrow public immutable ve; IRewardsDistributor public immutable rewardsDistributor; @@ -87,11 +85,9 @@ contract Minter is IMinter { } function initialize() external { - require(msg.sender != address(0), "cannot be zero address"); - require(initialized == false, "already initialized"); require(initializer == msg.sender, "not initializer"); + require(msg.sender != address(0), "already initialized"); initializer = address(0); - initialized = true; } /* @@ -173,7 +169,7 @@ contract Minter is IMinter { revenueHandler.checkpoint(); - emit Mint(msg.sender, epochEmissions, circulatingEmissionsSupply()); + emit Mint(msg.sender, epochEmissions, supply); } return period; } diff --git a/src/RevenueHandler.sol b/src/RevenueHandler.sol index 9bddafb..a891d71 100644 --- a/src/RevenueHandler.sol +++ b/src/RevenueHandler.sol @@ -50,7 +50,7 @@ contract RevenueHandler is IRevenueHandler, Ownable { uint256 internal constant WEEK = 1 weeks; uint256 internal constant BPS = 10_000; - address public veALCX; + address public immutable veALCX; address[] public revenueTokens; mapping(address => bool) public alchemicTokens; // token => is alchemic-token (true/false) mapping(address => RevenueTokenConfig) public revenueTokenConfigs; // token => RevenueTokenConfig @@ -83,7 +83,8 @@ contract RevenueHandler is IRevenueHandler, Ownable { /// @inheritdoc IRevenueHandler function addRevenueToken(address revenueToken) external override onlyOwner { - for (uint256 i = 0; i < revenueTokens.length; i++) { + uint256 length = revenueTokens.length; + for (uint256 i = 0; i < length; i++) { if (revenueTokens[i] == revenueToken) { revert("revenue token already exists"); } @@ -94,9 +95,10 @@ contract RevenueHandler is IRevenueHandler, Ownable { /// @inheritdoc IRevenueHandler function removeRevenueToken(address revenueToken) external override onlyOwner { - for (uint256 i = 0; i < revenueTokens.length; i++) { + uint256 length = revenueTokens.length; + for (uint256 i = 0; i < length; i++) { if (revenueTokens[i] == revenueToken) { - revenueTokens[i] = revenueTokens[revenueTokens.length - 1]; + revenueTokens[i] = revenueTokens[length - 1]; revenueTokens.pop(); emit RevenueTokenTokenRemoved(revenueToken); return; @@ -211,41 +213,40 @@ contract RevenueHandler is IRevenueHandler, Ownable { if (block.timestamp >= currentEpoch + WEEK /* && initializer == address(0) */) { currentEpoch = (block.timestamp / WEEK) * WEEK; - for (uint256 i = 0; i < revenueTokens.length; i++) { + uint256 length = revenueTokens.length; + for (uint256 i = 0; i < length; i++) { // These will be zero if the revenue token is not an alchemic-token uint256 treasuryAmt = 0; uint256 amountReceived = 0; address token = revenueTokens[i]; + RevenueTokenConfig memory tokenConfig = revenueTokenConfigs[token]; + // If a revenue token is disabled, skip it. - if (revenueTokenConfigs[token].disabled) continue; + if (tokenConfig.disabled) continue; + + uint256 thisBalance = IERC20(token).balanceOf(address(this)); // If poolAdapter is set, the revenue token is an alchemic-token - if (revenueTokenConfigs[token].poolAdapter != address(0)) { + if (tokenConfig.poolAdapter != address(0)) { // Treasury only receives revenue if the token is an alchemic-token - treasuryAmt = (IERC20(token).balanceOf(address(this)) * treasuryPct) / BPS; + treasuryAmt = (thisBalance * treasuryPct) / BPS; IERC20(token).safeTransfer(treasury, treasuryAmt); // Only melt if there is an alchemic-token to melt to amountReceived = _melt(token); // Update amount of alchemic-token revenue received for this epoch - epochRevenues[currentEpoch][revenueTokenConfigs[token].debtToken] += amountReceived; + epochRevenues[currentEpoch][tokenConfig.debtToken] += amountReceived; } else { // If the revenue token doesn't have a poolAdapter, it is not an alchemic-token - amountReceived = IERC20(token).balanceOf(address(this)); + amountReceived = thisBalance; // Update amount of non-alchemic-token revenue received for this epoch epochRevenues[currentEpoch][token] += amountReceived; } - emit RevenueRealized( - currentEpoch, - token, - revenueTokenConfigs[revenueTokens[i]].debtToken, - amountReceived, - treasuryAmt - ); + emit RevenueRealized(currentEpoch, token, tokenConfig.debtToken, amountReceived, treasuryAmt); } } } diff --git a/src/RewardPoolManager.sol b/src/RewardPoolManager.sol new file mode 100644 index 0000000..b8bfbd1 --- /dev/null +++ b/src/RewardPoolManager.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-3 +pragma solidity ^0.8.15; + +import "src/interfaces/IRewardPoolManager.sol"; +import "src/interfaces/aura/IRewardPool4626.sol"; +import "src/interfaces/aura/IRewardStaking.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract RewardPoolManager is IRewardPoolManager { + using SafeERC20 for IERC20; + + uint256 internal constant MAX_REWARD_POOL_TOKENS = 10; + + address public admin; + address public pendingAdmin; + address public veALCX; + address public rewardPool; // destination for poolToken + address public treasury; + address public poolToken; // BPT + + address[] public rewardPoolTokens; + + mapping(address => bool) public isRewardPoolToken; + + constructor(address _admin, address _veALCX, address _poolToken, address _rewardPool, address _treasury) { + admin = _admin; + veALCX = _veALCX; + poolToken = _poolToken; + rewardPool = _rewardPool; + treasury = _treasury; + } + + /// @inheritdoc IRewardPoolManager + function setAdmin(address _admin) external { + require(msg.sender == admin, "not admin"); + pendingAdmin = _admin; + } + + /// @inheritdoc IRewardPoolManager + function acceptAdmin() external { + require(msg.sender == pendingAdmin, "not pending admin"); + admin = pendingAdmin; + emit AdminUpdated(pendingAdmin); + } + + /// @inheritdoc IRewardPoolManager + function setTreasury(address _treasury) external { + require(msg.sender == admin, "not admin"); + treasury = _treasury; + emit TreasuryUpdated(_treasury); + } + + /// @inheritdoc IRewardPoolManager + function setRewardPool(address _rewardPool) external { + require(msg.sender == admin, "not admin"); + rewardPool = _rewardPool; + emit RewardPoolUpdated(_rewardPool); + } + + /// @inheritdoc IRewardPoolManager + function setPoolToken(address _poolToken) external { + require(msg.sender == admin, "not admin"); + poolToken = _poolToken; + emit PoolTokenUpdated(_poolToken); + } + + /// @inheritdoc IRewardPoolManager + function setVeALCX(address _veALCX) external { + require(msg.sender == admin, "not admin"); + veALCX = _veALCX; + emit VeALCXUpdated(_veALCX); + } + + /// @inheritdoc IRewardPoolManager + function depositIntoRewardPool(uint256 _amount) external returns (bool) { + require(msg.sender == veALCX, "must be veALCX"); + + IERC20(poolToken).approve(rewardPool, _amount); + IRewardPool4626(rewardPool).deposit(_amount, address(this)); + return true; + } + + /// @inheritdoc IRewardPoolManager + function withdrawFromRewardPool(uint256 _amount) external returns (bool) { + require(msg.sender == veALCX, "must be veALCX"); + + IRewardPool4626(rewardPool).withdraw(_amount, veALCX, address(this)); + return true; + } + + /// @inheritdoc IRewardPoolManager + function claimRewardPoolRewards() external { + require(msg.sender == admin, "not admin"); + IRewardStaking(rewardPool).getReward(address(this), false); + uint256[] memory rewardPoolAmounts = new uint256[](rewardPoolTokens.length); + for (uint256 i = 0; i < rewardPoolTokens.length; i++) { + rewardPoolAmounts[i] = IERC20(rewardPoolTokens[i]).balanceOf(address(this)); + if (rewardPoolAmounts[i] > 0) { + IERC20(rewardPoolTokens[i]).safeTransfer(treasury, rewardPoolAmounts[i]); + emit ClaimRewardPoolRewards(msg.sender, rewardPoolTokens[i], rewardPoolAmounts[i]); + } + } + } + + /// @inheritdoc IRewardPoolManager + function addRewardPoolToken(address _token) external { + require(msg.sender == admin, "not admin"); + _addRewardPoolToken(_token); + } + + /// @inheritdoc IRewardPoolManager + function addRewardPoolTokens(address[] calldata _tokens) external { + require(msg.sender == admin, "not admin"); + for (uint256 i = 0; i < _tokens.length; i++) { + _addRewardPoolToken(_tokens[i]); + } + } + + /// @inheritdoc IRewardPoolManager + function swapOutRewardPoolToken(uint256 i, address oldToken, address newToken) external { + require(msg.sender == admin, "not admin"); + require(rewardPoolTokens[i] == oldToken, "incorrect token"); + require(newToken != address(0)); + + isRewardPoolToken[oldToken] = false; + isRewardPoolToken[newToken] = true; + rewardPoolTokens[i] = newToken; + } + + /* + Internal functions + */ + + function _addRewardPoolToken(address token) internal { + if (!isRewardPoolToken[token] && token != address(0)) { + require(rewardPoolTokens.length < MAX_REWARD_POOL_TOKENS, "too many reward pool tokens"); + + isRewardPoolToken[token] = true; + rewardPoolTokens.push(token); + } + } +} diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index fea3db0..542aa37 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -23,9 +23,9 @@ import { WeightedMath } from "src/interfaces/balancer/WeightedMath.sol"; contract RewardsDistributor is IRewardsDistributor, ReentrancyGuard { using SafeERC20 for IERC20; - uint256 public constant WEEK = 1 weeks; - address public constant BURN_ADDRESS = address(0); - uint256 public constant BPS = 10_000; + uint256 public immutable WEEK = 1 weeks; + address public immutable BURN_ADDRESS = address(0); + uint256 public immutable BPS = 10_000; bytes32 public balancerPoolId; @@ -34,7 +34,7 @@ contract RewardsDistributor is IRewardsDistributor, ReentrancyGuard { uint256 public lastTokenTime; uint256 public tokenLastBalance; - address public votingEscrow; + address public immutable votingEscrow; address public rewardsToken; address public lockedToken; address public depositor; @@ -45,9 +45,9 @@ contract RewardsDistributor is IRewardsDistributor, ReentrancyGuard { mapping(uint256 => uint256) public timeCursorOf; mapping(uint256 => uint256) public userEpochOf; - IWETH9 public WETH; - IVault public balancerVault; - IBasePool public balancerPool; + IWETH9 public immutable WETH; + IVault public immutable balancerVault; + IBasePool public immutable balancerPool; AggregatorV3Interface public priceFeed; IAsset[] public poolAssets = new IAsset[](2); diff --git a/src/Voter.sol b/src/Voter.sol index b63e241..16c586b 100644 --- a/src/Voter.sol +++ b/src/Voter.sol @@ -93,13 +93,13 @@ contract Voter is IVoter { /// @inheritdoc IVoter function maxVotingPower(uint256 _tokenId) public view returns (uint256) { - return - IVotingEscrow(veALCX).balanceOfToken(_tokenId) + - ((IVotingEscrow(veALCX).balanceOfToken(_tokenId) * boostMultiplier) / BPS); + uint256 balance = IVotingEscrow(veALCX).balanceOfToken(_tokenId); + + return balance + ((balance * boostMultiplier) / BPS); } /// @inheritdoc IVoter - function maxFluxBoost(uint256 _tokenId) public view returns (uint256) { + function maxFluxBoost(uint256 _tokenId) external view returns (uint256) { return (IVotingEscrow(veALCX).balanceOfToken(_tokenId) * boostMultiplier) / BPS; } @@ -107,7 +107,7 @@ contract Voter is IVoter { return pools.length; } - function getPoolVote(uint256 _tokenId) public view returns (address[] memory) { + function getPoolVote(uint256 _tokenId) external view returns (address[] memory) { return poolVote[_tokenId]; } @@ -164,7 +164,7 @@ contract Voter is IVoter { lastVoted[_tokenId] = block.timestamp; _reset(_tokenId); IVotingEscrow(veALCX).abstain(_tokenId); - IVotingEscrow(veALCX).accrueFlux(_tokenId, IVotingEscrow(veALCX).claimableFlux(_tokenId)); + IFluxToken(FLUX).accrueFlux(_tokenId); } /// @inheritdoc IVoter @@ -177,7 +177,7 @@ contract Voter is IVoter { } require( - IVotingEscrow(veALCX).claimableFlux(_tokenId) + IVotingEscrow(veALCX).unclaimedFlux(_tokenId) >= _boost, + IVotingEscrow(veALCX).claimableFlux(_tokenId) + IFluxToken(FLUX).getUnclaimedFlux(_tokenId) >= _boost, "insufficient FLUX to boost" ); require( @@ -220,7 +220,7 @@ contract Voter is IVoter { require(_poolVote.length == _weights.length); require(_poolVote.length <= pools.length, "invalid pools"); require( - IVotingEscrow(veALCX).claimableFlux(_tokenId) + IVotingEscrow(veALCX).unclaimedFlux(_tokenId) >= _boost, + IVotingEscrow(veALCX).claimableFlux(_tokenId) + IFluxToken(FLUX).getUnclaimedFlux(_tokenId) >= _boost, "insufficient FLUX to boost" ); require( @@ -232,11 +232,18 @@ contract Voter is IVoter { _vote(_tokenId, _poolVote, _weights, _boost); } + /// @inheritdoc IVoter function whitelist(address _token) public { require(msg.sender == admin, "not admin"); _whitelist(_token); } + /// @inheritdoc IVoter + function removeFromWhitelist(address _token) external { + require(msg.sender == admin, "not admin"); + _removeFromWhitelist(_token); + } + /// @inheritdoc IVoter function createGauge(address _pool, GaugeType _gaugeType) external returns (address) { require(gauges[_pool] == address(0x0), "exists"); @@ -249,8 +256,8 @@ contract Voter is IVoter { if (_gaugeType == IVoter.GaugeType.Curve) { _gauge = IGaugeFactory(gaugefactory).createCurveGauge(_bribe, veALCX); - } - if (_gaugeType == IVoter.GaugeType.Passthrough) { + } else { + // _gaugeType == IVoter.GaugeType.Passthrough _gauge = IGaugeFactory(gaugefactory).createPassthroughGauge(_pool, _bribe, veALCX); } @@ -341,14 +348,8 @@ contract Voter is IVoter { /// @inheritdoc IVoter function distribute(address _gauge) public lock { - require(isAlive[_gauge], "cannot distribute to a dead gauge"); - + _distribute(_gauge); IMinter(minter).updatePeriod(); - _updateFor(_gauge); - uint256 _claimable = claimable[_gauge]; - IBaseGauge(_gauge).notifyRewardAmount(_claimable); - - emit DistributeReward(msg.sender, _gauge, _claimable); } function distribute() external { @@ -357,20 +358,32 @@ contract Voter is IVoter { function distribute(uint256 start, uint256 finish) public { for (uint256 x = start; x < finish; x++) { - distribute(gauges[pools[x]]); + _distribute(gauges[pools[x]]); } + IMinter(minter).updatePeriod(); } function distribute(address[] memory _gauges) external { for (uint256 x = 0; x < _gauges.length; x++) { - distribute(_gauges[x]); + _distribute(_gauges[x]); } + IMinter(minter).updatePeriod(); } /* Internal functions */ + function _distribute(address _gauge) internal { + require(isAlive[_gauge], "cannot distribute to a dead gauge"); + + _updateFor(_gauge); + uint256 _claimable = claimable[_gauge]; + IBaseGauge(_gauge).notifyRewardAmount(_claimable); + + emit DistributeReward(msg.sender, _gauge, _claimable); + } + function _reset(uint256 _tokenId) internal { address[] storage _poolVote = poolVote[_tokenId]; uint256 _poolVoteCnt = _poolVote.length; @@ -384,10 +397,10 @@ contract Voter is IVoter { _updateFor(gauges[_pool]); weights[_pool] -= _votes; votes[_tokenId][_pool] -= _votes; - if (_votes > 0) { - IBribe(bribes[gauges[_pool]]).withdraw(uint256(_votes), _tokenId); - _totalWeight += _votes; - } + + IBribe(bribes[gauges[_pool]]).withdraw(uint256(_votes), _tokenId); + _totalWeight += _votes; + emit Abstained(msg.sender, _pool, _tokenId, _votes); } } @@ -402,21 +415,21 @@ contract Voter is IVoter { uint256 _poolCnt = _poolVote.length; uint256 _totalVoteWeight = 0; uint256 _totalWeight = 0; - uint256 _usedWeight = 0; for (uint256 i = 0; i < _poolCnt; i++) { _totalVoteWeight += _weights[i]; } + IFluxToken(FLUX).accrueFlux(_tokenId); + uint256 totalPower = (IVotingEscrow(veALCX).balanceOfToken(_tokenId) + _boost); + for (uint256 i = 0; i < _poolCnt; i++) { address _pool = _poolVote[i]; address _gauge = gauges[_pool]; require(isAlive[_gauge], "cannot vote for dead gauge"); - IVotingEscrow(veALCX).accrueFlux(_tokenId, IVotingEscrow(veALCX).claimableFlux(_tokenId)); - uint256 _poolWeight = (_weights[i] * (IVotingEscrow(veALCX).balanceOfToken(_tokenId) + _boost)) / - _totalVoteWeight; + uint256 _poolWeight = (_weights[i] * totalPower) / _totalVoteWeight; require(votes[_tokenId][_pool] == 0); require(_poolWeight != 0); _updateFor(_gauge); @@ -426,19 +439,18 @@ contract Voter is IVoter { weights[_pool] += _poolWeight; votes[_tokenId][_pool] += _poolWeight; IBribe(bribes[_gauge]).deposit(uint256(_poolWeight), _tokenId); - _usedWeight += _poolWeight; _totalWeight += _poolWeight; emit Voted(msg.sender, _pool, _tokenId, _poolWeight); } - if (_usedWeight > 0) IVotingEscrow(veALCX).voting(_tokenId); + if (_totalWeight > 0) IVotingEscrow(veALCX).voting(_tokenId); totalWeight += uint256(_totalWeight); - usedWeights[_tokenId] = uint256(_usedWeight); + usedWeights[_tokenId] = uint256(_totalWeight); lastVoted[_tokenId] = block.timestamp; // Update flux balance of token if boost was used if (_boost > 0) { - IVotingEscrow(veALCX).updateFlux(_tokenId, _boost); + IFluxToken(FLUX).updateFlux(_tokenId, _boost); } } @@ -448,6 +460,12 @@ contract Voter is IVoter { emit Whitelisted(msg.sender, _token); } + function _removeFromWhitelist(address _token) internal { + require(isWhitelisted[_token]); + isWhitelisted[_token] = false; + emit RemovedFromWhitelist(msg.sender, _token); + } + function _updateFor(address _gauge) internal { require(isGauge[_gauge], "invalid gauge"); @@ -468,7 +486,6 @@ contract Voter is IVoter { } function _safeTransferFrom(address token, address from, address to, uint256 value) internal { - require(token.code.length > 0); (bool success, bytes memory data) = token.call( abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value) ); diff --git a/src/VotingEscrow.sol b/src/VotingEscrow.sol index 50e313b..72a12ea 100644 --- a/src/VotingEscrow.sol +++ b/src/VotingEscrow.sol @@ -4,8 +4,7 @@ pragma solidity ^0.8.15; import "src/interfaces/IVotingEscrow.sol"; import "src/interfaces/IFluxToken.sol"; import "src/interfaces/IRewardsDistributor.sol"; -import "src/interfaces/aura/IRewardPool4626.sol"; -import "src/interfaces/aura/IRewardStaking.sol"; +import "src/interfaces/IRewardPoolManager.sol"; import "src/libraries/Base64.sol"; import "openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import "openzeppelin-contracts/contracts/governance/utils/IVotes.sol"; @@ -35,7 +34,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { } struct LockedBalance { - int256 amount; + uint256 amount; uint256 end; bool maxLockEnabled; uint256 cooldown; @@ -54,33 +53,15 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { string public constant name = "veALCX"; string public constant symbol = "veALCX"; string public constant version = "1.0.0"; - uint8 public constant decimals = 18; - - /// @notice The EIP-712 typehash for the contract's domain - bytes32 public constant DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); - - /// @notice The EIP-712 typehash for the delegation struct used by the contract - bytes32 public constant DELEGATION_TYPEHASH = - keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); - - /// @dev ERC165 interface ID of ERC165 - bytes4 internal constant ERC165_INTERFACE_ID = 0x01ffc9a7; - - /// @dev ERC165 interface ID of ERC721 - bytes4 internal constant ERC721_INTERFACE_ID = 0x80ac58cd; - - /// @dev ERC165 interface ID of ERC721Metadata - bytes4 internal constant ERC721_METADATA_INTERFACE_ID = 0x5b5e139f; + uint8 public immutable decimals = 18; uint256 public constant EPOCH = 1 weeks; uint256 public constant MAX_DELEGATES = 1024; // avoid too much gas - uint256 internal constant WEEK = 1 weeks; + uint256 internal immutable WEEK = 1 weeks; + uint256 internal immutable BPS = 10_000; uint256 internal constant MAXTIME = 365 days; uint256 internal constant MULTIPLIER = 1 ether; - uint256 internal constant MAX_REWARD_POOL_TOKENS = 10; - uint256 internal constant BPS = 10_000; int256 internal constant iMAXTIME = 365 days; int256 internal constant iMULTIPLIER = 1 ether; @@ -88,10 +69,10 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { /// @dev Current count of token uint256 internal tokenId; - address public ALCX; - address public FLUX; - address public BPT; - address public rewardPool; // destination for BPT + address public immutable ALCX; + address public immutable FLUX; + address public immutable BPT; + address public rewardPoolManager; // destination for BPT address public admin; // the timelock executor address public pendingAdmin; // the timelock executor address public voter; @@ -104,9 +85,6 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { uint256 public fluxPerVeALCX; // Percent of veALCX power needed in flux in order to unlock early uint256 public epoch; - address[] public rewardPoolTokens; - - mapping(uint256 => uint256) public unclaimedFlux; // tokenId => amount of unclaimed flux mapping(uint256 => LockedBalance) public locked; mapping(uint256 => uint256) public ownershipChange; mapping(uint256 => Point) public pointHistory; // epoch -> unsigned point @@ -136,9 +114,6 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { /// @dev Mapping from owner address to mapping of operator addresses. mapping(address => mapping(address => bool)) internal ownerToOperators; - /// @dev Mapping of interface id to bool about whether or not it's supported - mapping(bytes4 => bool) internal supportedInterfaces; - /// @notice A record of each accounts delegate mapping(address => address) private _delegates; @@ -167,12 +142,10 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { event RewardsDistributorUpdated(address distributor); event FluxMultiplierUpdated(uint256 fluxMultiplier); event FluxPerVeALCXUpdated(uint256 fluxPerVeALCX); - event RewardPoolUpdated(address newRewardPool); event Withdraw(address indexed provider, uint256 tokenId, uint256 value, uint256 ts); event Supply(uint256 prevSupply, uint256 supply); event Ragequit(address indexed provider, uint256 tokenId, uint256 ts); event CooldownStarted(address indexed provider, uint256 tokenId, uint256 ts); - event ClaimRewardPoolRewards(address indexed claimer, address rewardToken, uint256 rewardAmount); event TreasuryUpdated(address indexed newTreasury); /// @dev reentrancy guard @@ -192,14 +165,14 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { * @param _alcx `ALCX` token address * @param _flux `FLUX` token address */ - constructor(address _bpt, address _alcx, address _flux, address _rewardPool, address _treasury) { + constructor(address _bpt, address _alcx, address _flux, address _treasury) { BPT = _bpt; ALCX = _alcx; FLUX = _flux; - rewardPool = _rewardPool; treasury = _treasury; voter = msg.sender; + rewardPoolManager = msg.sender; admin = msg.sender; distributor = msg.sender; fluxPerVeALCX = 5000; // 5000 bps = 50% @@ -207,10 +180,6 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { pointHistory[0].blk = block.number; pointHistory[0].ts = block.timestamp; - - supportedInterfaces[ERC165_INTERFACE_ID] = true; - supportedInterfaces[ERC721_INTERFACE_ID] = true; - supportedInterfaces[ERC721_METADATA_INTERFACE_ID] = true; } /* @@ -227,14 +196,8 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { return checkpoints[_address][lastCheckpoint].tokenIds; } - /** - * @notice Interface identification is specified in ERC-165. - * @param _interfaceID Id of the interface - * @return bool Boolean result of provided interface - */ - function supportsInterface(bytes4 _interfaceID) external view returns (bool) { - return supportedInterfaces[_interfaceID]; - } + // Not supported + function supportsInterface(bytes4 _interfaceID) external view returns (bool) {} /** * @notice Get the most recently recorded rate of voting power decrease for `_tokenId` @@ -357,9 +320,10 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { if (nCheckpoints == 0) { return 0; } - uint256[] storage _tokenIds = checkpoints[account][nCheckpoints - 1].tokenIds; + uint256[] memory _tokenIds = checkpoints[account][nCheckpoints - 1].tokenIds; uint256 votes = 0; - for (uint256 i = 0; i < _tokenIds.length; i++) { + uint256 tokenIdCount = _tokenIds.length; + for (uint256 i = 0; i < tokenIdCount; i++) { uint256 tId = _tokenIds[i]; votes = votes + _balanceOfTokenAt(tId, block.timestamp); } @@ -388,7 +352,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { // Binary search to find the index of the checkpoint that is the closest to the requested timestamp while (upper > lower) { uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow - Checkpoint storage cp = checkpoints[account][center]; + Checkpoint memory cp = checkpoints[account][center]; if (cp.timestamp == timestamp) { return center; } else if (cp.timestamp < timestamp) { @@ -415,7 +379,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { } // Sum votes - uint256[] storage _tokenIds = checkpoints[account][_checkIndex].tokenIds; + uint256[] memory _tokenIds = checkpoints[account][_checkIndex].tokenIds; for (uint256 i = 0; i < _tokenIds.length; i++) { uint256 tId = _tokenIds[i]; // Use the provided input timestamp here to get the right decay @@ -445,13 +409,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { function tokenURI(uint256 _tokenId) external view returns (string memory) { require(idToOwner[_tokenId] != address(0), "Query for nonexistent token"); LockedBalance memory _locked = locked[_tokenId]; - return - _tokenURI( - _tokenId, - _balanceOfTokenAt(_tokenId, block.timestamp), - _locked.end, - uint256(int256(_locked.amount)) - ); + return _tokenURI(_tokenId, _balanceOfTokenAt(_tokenId, block.timestamp), _locked.end, _locked.amount); } function balanceOfToken(uint256 _tokenId) external view returns (uint256) { @@ -616,7 +574,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { // Throws if `_approved` is the current owner require(_approved != owner, "Approved is already owner"); // Check requirements - bool senderIsOwner = (idToOwner[_tokenId] == msg.sender); + bool senderIsOwner = (owner == msg.sender); bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender]; require(senderIsOwner || senderIsApprovedForAll); // Set the approval @@ -648,18 +606,8 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { return _delegate(msg.sender, delegatee); } - function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) public { - bytes32 domainSeparator = keccak256( - abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), keccak256(bytes(version)), block.chainid, address(this)) - ); - bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)); - bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - address signatory = ecrecover(digest, v, r, s); - require(signatory != address(0), "VotingEscrow::delegateBySig: invalid signature"); - require(nonce == nonces[signatory]++, "VotingEscrow::delegateBySig: invalid nonce"); - require(block.timestamp <= expiry, "VotingEscrow::delegateBySig: signature expired"); - return _delegate(signatory, delegatee); - } + // Not supported + function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) public {} function setVoter(address _voter) external { require(msg.sender == admin, "not admin"); @@ -668,11 +616,16 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { } function setRewardsDistributor(address _distributor) external { - require(msg.sender == distributor, "not distributor"); + require(msg.sender == distributor, "not admin"); distributor = _distributor; emit RewardsDistributorUpdated(_distributor); } + function setRewardPoolManager(address _rewardPoolManager) external { + require(msg.sender == rewardPoolManager, "not admin"); + rewardPoolManager = _rewardPoolManager; + } + function voting(uint256 _tokenId) external { require(msg.sender == voter); voted[_tokenId] = true; @@ -743,9 +696,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { // If max lock is enabled retain the max lock _locked1.maxLockEnabled = _locked0.maxLockEnabled ? _locked0.maxLockEnabled : _locked1.maxLockEnabled; - // Consolidate unclaimed flux - unclaimedFlux[_to] += unclaimedFlux[_from]; - unclaimedFlux[_from] = 0; + IFluxToken(FLUX).mergeFlux(_from, _to); // If max lock is enabled end is the max lock time, otherwise it is the greater of the two end times uint256 end = _locked1.maxLockEnabled @@ -853,7 +804,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { require(_locked.cooldown > 0, "Cooldown period has not started"); require(block.timestamp >= _locked.cooldown, "Cooldown period in progress"); - uint256 value = uint256(int256(_locked.amount)); + uint256 value = _locked.amount; locked[_tokenId] = LockedBalance(0, 0, false, 0); uint256 supplyBefore = supply; @@ -865,13 +816,13 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { _checkpoint(_tokenId, _locked, LockedBalance(0, 0, false, 0)); // Withdraws BPT from reward pool - require(_withdrawFromRewardPool(value)); + require(IRewardPoolManager(rewardPoolManager).withdrawFromRewardPool(value)); require(IERC20(BPT).transfer(ownerOf(_tokenId), value)); // Claim any unclaimed ALCX rewards and FLUX IRewardsDistributor(distributor).claim(_tokenId, false); - _claimFlux(_tokenId, unclaimedFlux[_tokenId]); + IFluxToken(FLUX).claimFlux(_tokenId, IFluxToken(FLUX).getUnclaimedFlux(_tokenId)); // Burn the token _burn(_tokenId); @@ -880,37 +831,6 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { emit Supply(supplyBefore, supplyBefore - value); } - /** - * @notice Accrue unclaimed flux for a given veALCX - * @param _tokenId ID of the token flux is being accrued to - * @param _amount Amount of flux being accrued - */ - function accrueFlux(uint256 _tokenId, uint256 _amount) external { - require(msg.sender == voter, "not voter"); - unclaimedFlux[_tokenId] += _amount; - } - - /** - * @notice Update unclaimed flux balance for a given veALCX - * @param _tokenId ID of the token flux is being updated for - * @param _amount Amount of flux being used - */ - function updateFlux(uint256 _tokenId, uint256 _amount) external { - require(msg.sender == voter, "not voter"); - require(_amount <= unclaimedFlux[_tokenId], "not enough flux"); - unclaimedFlux[_tokenId] -= _amount; - } - - /** - * @notice Claim unclaimed flux for a given veALCX - * @param _tokenId ID of the token flux is being accrued to - * @param _amount Amount of flux being claimed - * @dev flux can only be claimed after accrual - */ - function claimFlux(uint256 _tokenId, uint256 _amount) external { - _claimFlux(_tokenId, _amount); - } - /** * @notice Starts the cooldown for `_tokenId` * @param _tokenId ID of the token to start cooldown for @@ -944,84 +864,10 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { emit CooldownStarted(msg.sender, _tokenId, _locked.cooldown); } - /** - * @notice Deposit amount into rewardPool - * @dev Can only be called by governance - */ - function depositIntoRewardPool(uint256 _amount) external { - require(msg.sender == admin, "not admin"); - _depositIntoRewardPool(_amount); - } - - /** - * @notice Withdraw amount from rewardPool - * @dev Can only be called by governance - */ - function withdrawFromRewardPool(uint256 _amount) external { - require(msg.sender == admin, "not admin"); - _withdrawFromRewardPool(_amount); - } - - /** - * @notice Update the address of the rewardPool - */ - function updateRewardPool(address _newRewardPool) external { - require(msg.sender == admin, "not admin"); - rewardPool = _newRewardPool; - emit RewardPoolUpdated(_newRewardPool); - } - - /** - * @notice Claim rewards from the rewardPool - */ - function claimRewardPoolRewards() external { - require(msg.sender == admin, "not admin"); - IRewardStaking(rewardPool).getReward(address(this), false); - uint256[] memory rewardPoolAmounts = new uint256[](rewardPoolTokens.length); - for (uint256 i = 0; i < rewardPoolTokens.length; i++) { - rewardPoolAmounts[i] = IERC20(rewardPoolTokens[i]).balanceOf(address(this)); - if (rewardPoolAmounts[i] > 0) { - IERC20(rewardPoolTokens[i]).safeTransfer(treasury, rewardPoolAmounts[i]); - emit ClaimRewardPoolRewards(msg.sender, rewardPoolTokens[i], rewardPoolAmounts[i]); - } - } - } - - function addRewardPoolToken(address _token) external { - require(msg.sender == admin, "not admin"); - _addRewardPoolToken(_token); - } - - function addRewardPoolTokens(address[] calldata _tokens) external { - require(msg.sender == admin, "not admin"); - for (uint256 i = 0; i < _tokens.length; i++) { - _addRewardPoolToken(_tokens[i]); - } - } - - function swapOutRewardPoolToken(uint256 i, address oldToken, address newToken) external { - require(msg.sender == admin, "not admin"); - require(rewardPoolTokens[i] == oldToken, "incorrect token"); - require(newToken != address(0)); - - isRewardPoolToken[oldToken] = false; - isRewardPoolToken[newToken] = true; - rewardPoolTokens[i] = newToken; - } - /* Internal functions */ - function _addRewardPoolToken(address token) internal { - if (!isRewardPoolToken[token] && token != address(0)) { - require(rewardPoolTokens.length < MAX_REWARD_POOL_TOKENS, "too many reward pool tokens"); - - isRewardPoolToken[token] = true; - rewardPoolTokens.push(token); - } - } - /** * @notice Returns the number of tokens owned by `_owner`. * @param _owner Address for whom to query the balance. @@ -1032,16 +878,6 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { return ownerToTokenCount[_owner]; } - function _claimFlux(uint256 _tokenId, uint256 _amount) internal { - require(_isApprovedOrOwner(msg.sender, _tokenId)); - require(unclaimedFlux[_tokenId] >= _amount, "amount greater than unclaimed balance"); - - unclaimedFlux[_tokenId] -= _amount; - - // FLUX is minted to the veALCX owner's address - IFluxToken(FLUX).mint(ownerOf(_tokenId), _amount); - } - /** * @notice Returns whether the given spender can transfer a given token ID * @param _spender address of the spender to query @@ -1379,9 +1215,9 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { */ function _calculatePoint(LockedBalance memory _locked, uint256 _time) internal pure returns (Point memory point) { if (_locked.end > _time && _locked.amount > 0) { - point.slope = _locked.maxLockEnabled ? int256(0) : (_locked.amount * iMULTIPLIER) / iMAXTIME; + point.slope = _locked.maxLockEnabled ? int256(0) : (int256(_locked.amount) * iMULTIPLIER) / iMAXTIME; point.bias = _locked.maxLockEnabled - ? ((_locked.amount * iMULTIPLIER) / iMAXTIME) * (int256(_locked.end - _time)) + ? ((int256(_locked.amount) * iMULTIPLIER) / iMAXTIME) * (int256(_locked.end - _time)) : (point.slope * (int256(_locked.end - _time))); } } @@ -1552,7 +1388,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { _locked.maxLockEnabled ); // Adding to existing lock, or if a lock is expired - creating a new one - _locked.amount += int256(_value); + _locked.amount += _value; _locked.maxLockEnabled = _maxLockEnabled; @@ -1570,9 +1406,12 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { address from = msg.sender; if (_value != 0 && depositType != DepositType.MERGE_TYPE) { - require(IERC20(BPT).transferFrom(from, address(this), _value)); + require(IERC20(BPT).transferFrom(from, rewardPoolManager, _value)); // Deposits BPT into reward pool - require(_depositIntoRewardPool(_value), "Deposit into reward pool failed"); + require( + IRewardPoolManager(rewardPoolManager).depositIntoRewardPool(_value), + "Deposit into reward pool failed" + ); } emit Deposit(from, _tokenId, _value, _locked.end, _locked.maxLockEnabled, depositType, block.timestamp); @@ -1597,8 +1436,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { ? ((block.timestamp + MAXTIME) / WEEK) * WEEK : ((block.timestamp + _lockDuration) / WEEK) * WEEK; - require(_value > 0); // dev: need non-zero value - require(unlockTime > block.timestamp, "Can only lock until time in the future"); + require(_value > 0, "Cannot lock 0 value"); require(unlockTime <= block.timestamp + MAXTIME, "Voting lock can be 1 year max"); require(unlockTime >= block.timestamp + EPOCH, "Voting lock must be 1 epoch"); @@ -1611,25 +1449,6 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes { return _tokenId; } - /** - * @notice Deposit amount into rewardPool - * @param _amount Amount to deposit - */ - function _depositIntoRewardPool(uint256 _amount) internal returns (bool) { - IERC20(BPT).approve(rewardPool, _amount); - IRewardPool4626(rewardPool).deposit(_amount, address(this)); - return true; - } - - /** - * @notice Withdraw amount from rewardPool - * @param _amount Amount to withdraw - */ - function _withdrawFromRewardPool(uint256 _amount) internal returns (bool) { - IRewardPool4626(rewardPool).withdraw(_amount, address(this), address(this)); - return true; - } - // The following ERC20/minime-compatible methods are not real balanceOf and supply! // They measure the weights for the purpose of voting, so they don't represent // real coins. diff --git a/src/base/ErrorMessages.sol b/src/base/ErrorMessages.sol deleted file mode 100644 index 393988b..0000000 --- a/src/base/ErrorMessages.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.4; - -/// @notice An error used to indicate that an argument passed to a function is illegal or -/// inappropriate. -/// -/// @param message The error message. -error IllegalArgument(string message); - -/// @notice An error used to indicate that a function has encountered an unrecoverable state. -/// -/// @param message The error message. -error IllegalState(string message); - -/// @notice An error used to indicate that an operation is unsupported. -/// -/// @param message The error message. -error UnsupportedOperation(string message); - -/// @notice An error used to indicate that a message sender tried to execute a privileged function. -/// -/// @param message The error message. -error Unauthorized(string message); \ No newline at end of file diff --git a/src/gauges/CurveGauge.sol b/src/gauges/CurveGauge.sol index aae4331..c1d5ec7 100644 --- a/src/gauges/CurveGauge.sol +++ b/src/gauges/CurveGauge.sol @@ -99,8 +99,6 @@ contract CurveGauge is BaseGauge { */ function _passthroughRewards(uint256 _amount) internal override { require(initialized, "gauge must me initialized"); - require(_amount > 0, "insufficient amount"); - require(msg.sender == voter, "not voter"); require(proposalUpdated == true, "proposal must be updated"); // Reset proposal flag diff --git a/src/gauges/PassthroughGauge.sol b/src/gauges/PassthroughGauge.sol index f21e58a..77dfeac 100644 --- a/src/gauges/PassthroughGauge.sol +++ b/src/gauges/PassthroughGauge.sol @@ -35,9 +35,6 @@ contract PassthroughGauge is BaseGauge { * @param _amount Amount of rewards */ function _passthroughRewards(uint256 _amount) internal override { - require(_amount > 0, "insufficient amount"); - require(msg.sender == voter, "not voter"); - uint256 rewardBalance = IERC20(rewardToken).balanceOf(address(this)); require(rewardBalance >= _amount, "insufficient rewards"); diff --git a/src/governance/L2Governor.sol b/src/governance/L2Governor.sol index 0799bef..88cea96 100644 --- a/src/governance/L2Governor.sol +++ b/src/governance/L2Governor.sol @@ -225,7 +225,7 @@ abstract contract L2Governor is Context, ERC165, EIP712, IGovernor, IERC721Recei /** * @dev Public accessor to check the eta of a queued proposal */ - function proposalEta(uint256 proposalId) public view virtual returns (uint256) { + function proposalEta(uint256 proposalId) external view virtual returns (uint256) { uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]); return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value } @@ -309,7 +309,8 @@ abstract contract L2Governor is Context, ERC165, EIP712, IGovernor, IERC721Recei "Governor: veALCX power below proposal threshold" ); - uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)), chainId); + bytes32 descriptionHash = keccak256(bytes(description)); + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash, chainId); require(targets.length == values.length, "Governor: invalid proposal length"); require(targets.length == calldatas.length, "Governor: invalid proposal length"); @@ -318,32 +319,25 @@ abstract contract L2Governor is Context, ERC165, EIP712, IGovernor, IERC721Recei ProposalCore storage proposal = _proposals[proposalId]; require(proposal.voteStart.isUnset(), "Governor: proposal already exists"); - uint64 start = block.timestamp.toUint64() + votingDelay.toUint64(); - uint64 deadline = start + votingPeriod.toUint64(); uint256 executionDelay = votingDelay + votingPeriod + _timelock.executionDelay(); - proposal.voteStart.setDeadline(start); - proposal.voteEnd.setDeadline(deadline); + proposal.voteStart.setDeadline(block.timestamp.toUint64() + votingDelay.toUint64()); + proposal.voteEnd.setDeadline(proposal.voteStart.getDeadline() + votingPeriod.toUint64()); - _timelockIds[proposalId] = _timelock.hashOperationBatch( - targets, - values, - calldatas, - 0, - keccak256(bytes(description)), - chainId - ); - _timelock.scheduleBatch(targets, values, calldatas, 0, keccak256(bytes(description)), chainId, executionDelay); + // creates the identifier of an operation containing a batch of transactions + _timelockIds[proposalId] = keccak256(abi.encode(targets, values, calldatas, 0, descriptionHash, chainId)); + + _timelock.scheduleBatch(targets, values, calldatas, 0, descriptionHash, chainId, executionDelay); emit ProposalCreated( proposalId, _msgSender(), targets, values, - new string[](targets.length), + new string[](0), calldatas, - start, - deadline, + proposal.voteStart.getDeadline(), + proposal.voteEnd.getDeadline(), chainId ); diff --git a/src/interfaces/IFluxToken.sol b/src/interfaces/IFluxToken.sol index 15157ea..e372114 100644 --- a/src/interfaces/IFluxToken.sol +++ b/src/interfaces/IFluxToken.sol @@ -4,6 +4,35 @@ pragma solidity ^0.8.15; import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; interface IFluxToken is IERC20 { + event AdminUpdated(address admin); + + /** + * @notice Set the address responsible for administration + * @param _admin Address that enables the administration of FLUX + * @dev This function reverts if the caller does not have the admin role. + */ + function setAdmin(address _admin) external; + + /** + * @notice Accept the address responsible for administration + * @dev This function reverts if the caller does not have the pendingAdmin role. + */ + function acceptAdmin() external; + + /** + * @notice Set the voting contract address + * @param _voter Voter contract address + * @dev This function reverts if the caller does not have the admin role. + */ + function setVoter(address _voter) external; + + /** + * @notice Set the veALCX contract address + * @param _veALCX veALCX contract address + * @dev This function reverts if the caller does not have the admin role. + */ + function setVeALCX(address _veALCX) external; + /** * @notice Set the address responsible for minting * @param _minter Address that enables the minting of FLUX @@ -25,4 +54,38 @@ interface IFluxToken is IERC20 { * @param _amount The amount of tokens to burn. */ function burnFrom(address _account, uint256 _amount) external; + + /** + * @notice Get the amount of unclaimed flux for a given veALCX tokenId + * @param _tokenId ID of the token to get unclaimed flux for + * @return Amount of unclaimed flux + */ + function getUnclaimedFlux(uint256 _tokenId) external view returns (uint256); + + /** + * @notice Merge the unclaimed flux from one token into another + * @param _fromTokenId The token to merge from + * @param _toTokenId The token to merge to + */ + function mergeFlux(uint256 _fromTokenId, uint256 _toTokenId) external; + + /** + * @notice Accrue unclaimed flux for a given veALCX + * @param _tokenId ID of the token flux is being accrued to + */ + function accrueFlux(uint256 _tokenId) external; + + /** + * @notice Update unclaimed flux balance for a given veALCX + * @param _tokenId ID of the token flux is being updated for + * @param _amount Amount of flux being used + */ + function updateFlux(uint256 _tokenId, uint256 _amount) external; + + /** + * @notice Claim unclaimed flux for a given token + * @param _tokenId ID of the token flux is being claimed for + * @param _amount Amount of flux being claimed + */ + function claimFlux(uint256 _tokenId, uint256 _amount) external; } diff --git a/src/interfaces/IRewardPoolManager.sol b/src/interfaces/IRewardPoolManager.sol new file mode 100644 index 0000000..39911c0 --- /dev/null +++ b/src/interfaces/IRewardPoolManager.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3 +pragma solidity ^0.8.15; + +interface IRewardPoolManager { + event TreasuryUpdated(address indexed newTreasury); + event AdminUpdated(address admin); + event RewardPoolUpdated(address newRewardPool); + event PoolTokenUpdated(address newPoolToken); + event VeALCXUpdated(address newVeALCX); + event ClaimRewardPoolRewards(address indexed claimer, address rewardToken, uint256 rewardAmount); + + /** + * @notice Set the address responsible for administration + * @param _admin Address that enables the administration of FLUX + * @dev This function reverts if the caller does not have the admin role. + */ + function setAdmin(address _admin) external; + + /** + * @notice Accept the address responsible for administration + * @dev This function reverts if the caller does not have the pendingAdmin role. + */ + function acceptAdmin() external; + + /** + * @notice Set the treasury address + * @param _treasury Treasury address + * @dev This function reverts if the caller does not have the admin role. + */ + function setTreasury(address _treasury) external; + + /** + * @notice Set the rewardPool address + * @param _newRewardPool RewardPool address + * @dev This function reverts if the caller does not have the admin role. + */ + function setRewardPool(address _newRewardPool) external; + + /** + * @notice Set the poolToken address + * @param _newPoolToken PoolToken address + * @dev This function reverts if the caller does not have the admin role. + */ + function setPoolToken(address _newPoolToken) external; + + /** + * @notice Set the veALCX address + * @param _newVeALCX veALCX address + * @dev This function reverts if the caller does not have the admin role. + */ + function setVeALCX(address _newVeALCX) external; + + /** + * @notice Deposit amount into rewardPool + * @param _amount Amount to deposit + */ + function depositIntoRewardPool(uint256 _amount) external returns (bool); + + /** + * @notice Withdraw amount from rewardPool + * @param _amount Amount to withdraw + */ + function withdrawFromRewardPool(uint256 _amount) external returns (bool); + + /** + * @notice Claim rewards from the rewardPool + */ + function claimRewardPoolRewards() external; + + /** + * @notice Add a rewardPoolToken + * @param _token Address of the token to add + */ + function addRewardPoolToken(address _token) external; + + /** + * @notice Add multiple rewardPoolTokens + * @param _tokens Addresses of the tokens to add + */ + function addRewardPoolTokens(address[] calldata _tokens) external; + + /** + * @notice Swap a rewardPoolToken + * @param i Index of the token to swap + * @param oldToken Address of the token to remove + * @param newToken Address of the token to add + */ + function swapOutRewardPoolToken(uint256 i, address oldToken, address newToken) external; +} diff --git a/src/interfaces/IVoter.sol b/src/interfaces/IVoter.sol index c1ecfbb..07019d2 100644 --- a/src/interfaces/IVoter.sol +++ b/src/interfaces/IVoter.sol @@ -23,6 +23,7 @@ interface IVoter { event Voted(address indexed voter, address indexed pool, uint256 tokenId, uint256 weight); event Withdraw(address indexed account, address indexed gauge, uint256 tokenId, uint256 amount); event Whitelisted(address indexed whitelister, address indexed token); + event RemovedFromWhitelist(address indexed whitelister, address indexed token); function veALCX() external view returns (address); @@ -42,6 +43,12 @@ interface IVoter { */ function whitelist(address _token) external; + /** + * @notice Remove a token from the whitelist + * @param _token address of the token + */ + function removeFromWhitelist(address _token) external; + /** * @notice Get the maximum voting power a given veALCX can have by using FLUX * @param _tokenId ID of the token diff --git a/src/interfaces/IVotingEscrow.sol b/src/interfaces/IVotingEscrow.sol index bd5a713..03e3aae 100644 --- a/src/interfaces/IVotingEscrow.sol +++ b/src/interfaces/IVotingEscrow.sol @@ -140,35 +140,6 @@ interface IVotingEscrow { */ function claimableFlux(uint256 tokenId) external view returns (uint256); - /** - * @notice Total amount of flux accrued - * @param tokenId ID of the token - * @return uint256 Total amount of flux accrued - */ - function unclaimedFlux(uint256 tokenId) external view returns (uint256); - - /** - * @notice Accrue unclaimed flux for a given veALCX - * @param tokenId ID of the token flux is being accrued to - * @param amount Amount of flux being accrued - */ - function accrueFlux(uint256 tokenId, uint256 amount) external; - - /** - * @notice Update unclaimed flux balance for a given veALCX - * @param tokenId ID of the token flux is being updated for - * @param amount Amount of flux being used - */ - function updateFlux(uint256 tokenId, uint256 amount) external; - - /** - * @notice Claim unclaimed flux for a given veALCX - * @param tokenId ID of the token flux is being accrued to - * @param amount Amount of flux being claimed - * @dev flux can be claimed after accrual - */ - function claimFlux(uint256 tokenId, uint256 amount) external; - /** * @notice Starts the cooldown for `_tokenId` * @param tokenId ID of the token to start cooldown for diff --git a/src/test/BaseTest.sol b/src/test/BaseTest.sol index 1009bc9..40ac6ae 100644 --- a/src/test/BaseTest.sol +++ b/src/test/BaseTest.sol @@ -10,6 +10,7 @@ import "src/AlchemixGovernor.sol"; import "src/FluxToken.sol"; import "src/Voter.sol"; import "src/Minter.sol"; +import "src/RewardPoolManager.sol"; import "src/RewardsDistributor.sol"; import "src/RevenueHandler.sol"; import "src/Bribe.sol"; @@ -107,6 +108,7 @@ contract BaseTest is DSTestPlus { BribeFactory public bribeFactory; RewardsDistributor public distributor; Minter public minter; + RewardPoolManager public rewardPoolManager; RevenueHandler public revenueHandler; TimelockExecutor public timelockExecutor; AlchemixGovernor public governor; @@ -129,17 +131,18 @@ contract BaseTest is DSTestPlus { hevm.startPrank(admin); - veALCX = new VotingEscrow(bpt, address(alcx), address(flux), address(rewardPool), admin); + veALCX = new VotingEscrow(bpt, address(alcx), address(flux), admin); + + rewardPoolManager = new RewardPoolManager(admin, address(veALCX), bpt, rewardPool, admin); veALCX.setVoter(admin); veALCX.setRewardsDistributor(admin); - veALCX.addRewardPoolToken(bal); - veALCX.addRewardPoolToken(aura); + veALCX.setRewardPoolManager(address(rewardPoolManager)); + rewardPoolManager.addRewardPoolToken(bal); + rewardPoolManager.addRewardPoolToken(aura); IERC20(bpt).approve(address(veALCX), type(uint256).max); - FluxToken(flux).setMinter(address(veALCX)); - timeGauge = new StakingRewards(address(this), address(alcx), time); revenueHandler = new RevenueHandler(address(veALCX), admin, 0); gaugeFactory = new GaugeFactory(); @@ -160,6 +163,10 @@ contract BaseTest is DSTestPlus { veALCX.setVoter(address(voter)); veALCX.setRewardsDistributor(address(distributor)); + flux.setMinter(address(veALCX)); + flux.setVeALCX(address(veALCX)); + flux.setVoter(address(voter)); + IMinter.InitializationParams memory params = IMinter.InitializationParams( address(alcx), address(voter), diff --git a/src/test/Minter.t.sol b/src/test/Minter.t.sol index d5c5570..e9fc4bb 100644 --- a/src/test/Minter.t.sol +++ b/src/test/Minter.t.sol @@ -242,7 +242,7 @@ contract MinterTest is BaseTest { hevm.startPrank(admin); // Initial amount of BPT locked in a veALCX position - (int256 initLockedAmount, , , ) = veALCX.locked(tokenId); + (uint256 initLockedAmount, , , ) = veALCX.locked(tokenId); minter.updatePeriod(); @@ -267,7 +267,7 @@ contract MinterTest is BaseTest { uint256 wethBalanceAfter = weth.balanceOf(admin); // Updated amount of BPT locked - (int256 nextLockedAmount, , , ) = veALCX.locked(tokenId); + (uint256 nextLockedAmount, , , ) = veALCX.locked(tokenId); // BPT locked should be higher after compounding assertGt(nextLockedAmount, initLockedAmount, "error compounding"); @@ -352,9 +352,8 @@ contract MinterTest is BaseTest { // Test admin controlled functions function testAdminFunctions() public { assertEq(minter.initializer(), address(0), "minter not initialized"); - assertEq(minter.initialized(), true, "minter not initialized"); - hevm.expectRevert(abi.encodePacked("already initialized")); + hevm.expectRevert(abi.encodePacked("not initializer")); minter.initialize(); hevm.expectRevert(abi.encodePacked("not admin")); diff --git a/src/test/Voting.t.sol b/src/test/Voting.t.sol index 9768ac7..e775da4 100644 --- a/src/test/Voting.t.sol +++ b/src/test/Voting.t.sol @@ -219,14 +219,14 @@ contract VotingTest is BaseTest { hevm.startPrank(admin); uint256 claimedBalance = flux.balanceOf(admin); - uint256 unclaimedBalance = veALCX.unclaimedFlux(tokenId); + uint256 unclaimedBalance = flux.getUnclaimedFlux(tokenId); assertEq(claimedBalance, 0); assertEq(unclaimedBalance, 0); voter.reset(tokenId); - unclaimedBalance = veALCX.unclaimedFlux(tokenId); + unclaimedBalance = flux.getUnclaimedFlux(tokenId); // Claimed balance is equal to the amount able to be claimed assertEq(unclaimedBalance, veALCX.claimableFlux(tokenId)); @@ -239,7 +239,7 @@ contract VotingTest is BaseTest { unclaimedBalance += veALCX.claimableFlux(tokenId); // The unclaimed balance should equal the total amount of unclaimed flux - assertEq(unclaimedBalance, veALCX.unclaimedFlux(tokenId)); + assertEq(unclaimedBalance, flux.getUnclaimedFlux(tokenId)); hevm.stopPrank(); } @@ -247,31 +247,31 @@ contract VotingTest is BaseTest { // veALCX holder should be able to mint flux they have accrued function testMintFlux() public { hevm.expectRevert(abi.encodePacked("FluxToken: only minter")); - FluxToken(flux).setMinter(address(admin)); + flux.setMinter(address(admin)); uint256 tokenId = createVeAlcx(admin, TOKEN_1, MAXTIME, false); hevm.startPrank(admin); uint256 claimedBalance = flux.balanceOf(admin); - uint256 unclaimedBalance = veALCX.unclaimedFlux(tokenId); + uint256 unclaimedBalance = flux.getUnclaimedFlux(tokenId); - assertEq(claimedBalance, 0); - assertEq(unclaimedBalance, 0); + assertEq(claimedBalance, 0, "claimed balance should be 0"); + assertEq(unclaimedBalance, 0, "unclaimed balance should be 0"); hevm.expectRevert(abi.encodePacked("amount greater than unclaimed balance")); - veALCX.claimFlux(tokenId, TOKEN_1); + flux.claimFlux(tokenId, TOKEN_1); voter.reset(tokenId); - claimedBalance = veALCX.unclaimedFlux(tokenId); + claimedBalance = flux.getUnclaimedFlux(tokenId); - veALCX.claimFlux(tokenId, claimedBalance); + flux.claimFlux(tokenId, claimedBalance); - unclaimedBalance = veALCX.unclaimedFlux(tokenId); + unclaimedBalance = flux.getUnclaimedFlux(tokenId); - assertEq(unclaimedBalance, 0); - assertEq(flux.balanceOf(admin), claimedBalance); + assertEq(unclaimedBalance, 0, "unclaimed balance should reset"); + assertEq(flux.balanceOf(admin), claimedBalance, "admin flux balance should increase"); hevm.stopPrank(); } @@ -301,7 +301,7 @@ contract VotingTest is BaseTest { uint256 votingWeight = veALCX.balanceOfToken(tokenId); uint256 maxFluxAmount = voter.maxFluxBoost(tokenId); - uint256 fluxAccessable = veALCX.claimableFlux(tokenId) + veALCX.unclaimedFlux(tokenId); + uint256 fluxAccessable = veALCX.claimableFlux(tokenId) + flux.getUnclaimedFlux(tokenId); // Max boost amount should be the voting weight plus the boost multiplier assertEq(voter.maxVotingPower(tokenId), votingWeight + maxFluxAmount); diff --git a/src/test/VotingEscrow.t.sol b/src/test/VotingEscrow.t.sol index ff0b527..f2d2a19 100644 --- a/src/test/VotingEscrow.t.sol +++ b/src/test/VotingEscrow.t.sol @@ -48,9 +48,9 @@ contract VotingEscrowTest is BaseTest { // Test depositing, withdrawing from a rewardPool (Aura pool) function testRewardPool() public { // Reward pool should be set - assertEq(rewardPool, veALCX.rewardPool()); + assertEq(rewardPool, rewardPoolManager.rewardPool()); - deal(bpt, address(veALCX), TOKEN_1); + deal(bpt, address(rewardPoolManager), TOKEN_1); // Initial amount of bal and aura rewards earned uint256 rewardBalanceBefore1 = IERC20(bal).balanceOf(admin); @@ -58,25 +58,25 @@ contract VotingEscrowTest is BaseTest { assertEq(rewardBalanceBefore1, 0, "rewardBalanceBefore1 should be 0"); assertEq(rewardBalanceBefore2, 0, "rewardBalanceBefore2 should be 0"); - // Initial BPT balance of veALCX - uint256 amount = IERC20(bpt).balanceOf(address(veALCX)); + // Initial BPT balance of rewardPoolManager + uint256 amount = IERC20(bpt).balanceOf(address(rewardPoolManager)); assertEq(amount, TOKEN_1); // Deposit BPT balance into rewardPool - hevm.prank(admin); - veALCX.depositIntoRewardPool(amount); + hevm.prank(address(veALCX)); + rewardPoolManager.depositIntoRewardPool(amount); - uint256 amountAfterDeposit = IERC20(bpt).balanceOf(address(veALCX)); + uint256 amountAfterDeposit = IERC20(bpt).balanceOf(address(rewardPoolManager)); assertEq(amountAfterDeposit, 0, "full balance should be deposited"); - uint256 rewardPoolBalance = IRewardPool4626(rewardPool).balanceOf(address(veALCX)); + uint256 rewardPoolBalance = IRewardPool4626(rewardPool).balanceOf(address(rewardPoolManager)); assertEq(rewardPoolBalance, amount, "rewardPool balance should equal amount deposited"); // Fast forward to accumulate rewards hevm.warp(block.timestamp + 2 weeks); hevm.prank(admin); - veALCX.claimRewardPoolRewards(); + rewardPoolManager.claimRewardPoolRewards(); uint256 rewardBalanceAfter1 = IERC20(bal).balanceOf(address(admin)); uint256 rewardBalanceAfter2 = IERC20(aura).balanceOf(address(admin)); @@ -84,22 +84,22 @@ contract VotingEscrowTest is BaseTest { assertGt(rewardBalanceAfter1, rewardBalanceBefore1, "should accumulate bal rewards"); assertGt(rewardBalanceAfter2, rewardBalanceBefore2, "should accumulate aura rewards"); - hevm.prank(admin); - veALCX.withdrawFromRewardPool(amount); + hevm.prank(address(veALCX)); + rewardPoolManager.withdrawFromRewardPool(amount); // veALCX BPT balance should equal original amount after withdrawing from rewardPool uint256 amountAfterWithdraw = IERC20(bpt).balanceOf(address(veALCX)); assertEq(amountAfterWithdraw, amount, "should equal original amount"); - // Only veALCX admin can update rewardPool + // Only rewardPoolManager admin can update rewardPool hevm.expectRevert(abi.encodePacked("not admin")); - veALCX.updateRewardPool(sushiPoolAddress); + rewardPoolManager.setRewardPool(sushiPoolAddress); hevm.prank(admin); - veALCX.updateRewardPool(sushiPoolAddress); + rewardPoolManager.setRewardPool(sushiPoolAddress); // Reward pool should update - assertEq(sushiPoolAddress, veALCX.rewardPool(), "rewardPool not updated"); + assertEq(sushiPoolAddress, rewardPoolManager.rewardPool(), "rewardPool not updated"); } function testUpdateLockDuration() public { @@ -314,7 +314,7 @@ contract VotingEscrowTest is BaseTest { minter.updatePeriod(); uint256 unclaimedAlcx = distributor.claimable(tokenId); - uint256 unclaimedFlux = veALCX.unclaimedFlux(tokenId); + uint256 unclaimedFlux = flux.getUnclaimedFlux(tokenId); // Start cooldown once lock is expired veALCX.startCooldown(tokenId); @@ -358,8 +358,8 @@ contract VotingEscrowTest is BaseTest { hevm.prank(holder); voter.reset(tokenId3); - uint256 unclaimedFlux1 = veALCX.unclaimedFlux(tokenId1); - uint256 unclaimedFlux2 = veALCX.unclaimedFlux(tokenId2); + uint256 unclaimedFlux1 = flux.getUnclaimedFlux(tokenId1); + uint256 unclaimedFlux2 = flux.getUnclaimedFlux(tokenId2); assertGt(unclaimedFlux1, unclaimedFlux2, "unclaimed flux should be greater for longer lock"); @@ -370,8 +370,8 @@ contract VotingEscrowTest is BaseTest { hevm.prank(holder); voter.reset(tokenId3); - unclaimedFlux2 = veALCX.unclaimedFlux(tokenId2); - uint256 unclaimedFlux3 = veALCX.unclaimedFlux(tokenId3); + unclaimedFlux2 = flux.getUnclaimedFlux(tokenId2); + uint256 unclaimedFlux3 = flux.getUnclaimedFlux(tokenId3); assertGt(unclaimedFlux3, unclaimedFlux2, "unclaimed flux should be greater for active voter"); } @@ -430,23 +430,6 @@ contract VotingEscrowTest is BaseTest { hevm.stopPrank(); } - // Check support of supported interfaces - function testSupportedInterfaces() public { - bytes4 ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 ERC721_INTERFACE_ID = 0x80ac58cd; - bytes4 ERC721_METADATA_INTERFACE_ID = 0x5b5e139f; - - assertTrue(veALCX.supportsInterface(ERC165_INTERFACE_ID)); - assertTrue(veALCX.supportsInterface(ERC721_INTERFACE_ID)); - assertTrue(veALCX.supportsInterface(ERC721_METADATA_INTERFACE_ID)); - } - - // Check support of unsupported interfaces - function testUnsupportedInterfaces() public { - bytes4 ERC721_FAKE = 0x780e9d61; - assertFalse(veALCX.supportsInterface(ERC721_FAKE)); - } - // Check approving another address of veALCX function testApprovedOrOwner() public { uint256 tokenId = createVeAlcx(admin, TOKEN_1, MAXTIME, false); @@ -511,16 +494,16 @@ contract VotingEscrowTest is BaseTest { voter.reset(tokenId1); voter.reset(tokenId2); - uint256 unclaimedFluxBefore1 = veALCX.unclaimedFlux(tokenId1); - uint256 unclaimedFluxBefore2 = veALCX.unclaimedFlux(tokenId2); + uint256 unclaimedFluxBefore1 = flux.getUnclaimedFlux(tokenId1); + uint256 unclaimedFluxBefore2 = flux.getUnclaimedFlux(tokenId2); hevm.expectRevert(abi.encodePacked("must be different tokens")); veALCX.merge(tokenId1, tokenId1); veALCX.merge(tokenId1, tokenId2); - uint256 unclaimedFluxAfter1 = veALCX.unclaimedFlux(tokenId1); - uint256 unclaimedFluxAfter2 = veALCX.unclaimedFlux(tokenId2); + uint256 unclaimedFluxAfter1 = flux.getUnclaimedFlux(tokenId1); + uint256 unclaimedFluxAfter2 = flux.getUnclaimedFlux(tokenId2); // After merge unclaimed flux should consolidate into one token assertEq(unclaimedFluxAfter2, unclaimedFluxBefore1 + unclaimedFluxBefore2, "unclaimed flux not consolidated"); @@ -683,7 +666,7 @@ contract VotingEscrowTest is BaseTest { uint256 tokenId = veALCX.createLock(TOKEN_1, MAXTIME, true); uint256 claimedBalance = flux.balanceOf(admin); - uint256 unclaimedBalance = veALCX.unclaimedFlux(tokenId); + uint256 unclaimedBalance = flux.getUnclaimedFlux(tokenId); uint256 ragequitAmount = veALCX.amountToRagequit(tokenId); assertEq(claimedBalance, 0); @@ -691,7 +674,7 @@ contract VotingEscrowTest is BaseTest { voter.reset(tokenId); - unclaimedBalance = veALCX.unclaimedFlux(tokenId); + unclaimedBalance = flux.getUnclaimedFlux(tokenId); // Flux accrued over one epoch should align with the fluxMultiplier and epoch length uint256 fluxCalc = unclaimedBalance * veALCX.fluxMultiplier() * veALCX.EPOCH();