Skip to content

Commit

Permalink
test coverage
Browse files Browse the repository at this point in the history
toyv0 committed Apr 19, 2024
1 parent 3e87275 commit 627b073
Showing 17 changed files with 916 additions and 190 deletions.
22 changes: 21 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -74,4 +74,24 @@ test_file_block_debug :; FOUNDRY_PROFILE=$(PROFILE) forge test $(FORK_URL) $(MAT
test_file_debug_test :; FOUNDRY_PROFILE=$(PROFILE) forge test $(FORK_URL) $(MATCH_PATH) $(MATCH_TEST) -vvv

# runs single test within file with added verbosity for failing test from a given block: "make test_file_block_debug_test FILE=Minter TEST=testUnwrap"
test_file_block_debug_test :; FOUNDRY_PROFILE=$(PROFILE) forge test $(FORK_URL) $(MATCH_PATH) $(MATCH_TEST) $(FORK_BLOCK) -vvv
test_file_block_debug_test :; FOUNDRY_PROFILE=$(PROFILE) forge test $(FORK_URL) $(MATCH_PATH) $(MATCH_TEST) $(FORK_BLOCK) -vvv

# | File | % Lines | % Statements | % Branches | % Funcs |
# |----------------------------|--------------------|--------------------|------------------|------------------|
# | src/RewardsDistributor.sol | 90.00% (126/140) | 88.44% (176/199) | 63.64% (42/66) | 80.00% (12/15) |
# | src/VotingEscrow.sol | 90.34% (449/497) | 91.06% (560/615) | 67.05% (177/264) | 82.50% (66/80) |


# | File | % Lines | % Statements | % Branches | % Funcs |
# |----------------------------|--------------------|--------------------|------------------|------------------|
# | src/Bribe.sol | 94.52% (138/146) | 95.63% (175/183) | 78.38% (58/74) | 100.00% (19/19) |
# | src/Minter.sol | 100.00% (48/48) | 100.00% (61/61) | 88.46% (23/26) | 100.00% (9/9) |
# | src/Voter.sol | 97.14% (170/175) | 96.53% (195/202) | 83.02% (88/106) | 93.33% (28/30) |
# | src/FluxToken.sol | 97.06% (66/68) | 97.40% (75/77) | 90.32% (56/62) | 100.00% (19/19) |
# | src/RevenueHandler.sol | 100.00% (94/94) | 100.00% (115/115) | 93.18% (41/44) | 100.00% (15/15) |
# | src/RewardPoolManager.sol | 100.00% (47/47) | 100.00% (55/55) | 94.12% (32/34) | 100.00% (13/13) |
# | src/BaseGauge.sol | 100.00% (13/13) | 100.00% (13/13) | 100.00% (14/14) | 80.00% (4/5) |
# | src/AlchemixGovernor.sol | 100.00% (16/16) | 100.00% (17/17) | 100.00% (12/12) | 100.00% (6/6) |
# | src/BribeFactory.sol | 100.00% (1/1) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) |
# | src/GaugeFactory.sol | 100.00% (2/2) | 100.00% (4/4) | 100.00% (0/0) | 100.00% (2/2) |
# | src/PassthroughGauge.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) |
55 changes: 18 additions & 37 deletions src/BaseGauge.sol
Original file line number Diff line number Diff line change
@@ -6,24 +6,35 @@ import "src/interfaces/IBribe.sol";
import "src/interfaces/IBaseGauge.sol";
import "src/interfaces/IVoter.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

/**
* @title Base Gauge
* @notice Implementation of functionality that various gauge types use or extend
* @notice Gauges are used to incentivize pools, they emit or passthrough reward tokens
*/
abstract contract BaseGauge is IBaseGauge {
uint256 internal constant DURATION = 2 weeks; // Rewards released over voting period
using SafeERC20 for IERC20;

/// @notice Rewards released over voting period
uint256 internal constant DURATION = 2 weeks;
uint256 internal constant BRIBE_LAG = 1 days;
uint256 internal constant MAX_REWARD_TOKENS = 16;

address public ve; // veALCX token used for gauges
address public bribe; // Address of bribe contract
address public voter; // Address of voter contract
/// @notice veALCX token used for gauges
address public ve;
/// @notice Address of bribe contract
address public bribe;
/// @notice Address of voter contract
address public voter;
/// @notice Address of admin
address public admin;
/// @notice Address of pending admin
address public pendingAdmin;
address public receiver; // Address that receives the ALCX rewards
address public rewardToken; // Address of the reward token
/// @notice Address that receives the ALCX rewards
address public receiver;
/// @notice Address of the reward token
address public rewardToken;

// Re-entrancy check
uint256 internal _unlocked = 1;
@@ -34,22 +45,6 @@ abstract contract BaseGauge is IBaseGauge {
_unlocked = 1;
}

/*
View functions
*/

/// @inheritdoc IBaseGauge
function getVotingStage(uint256 timestamp) external pure returns (VotingStage) {
uint256 modTime = timestamp % (1 weeks);
if (modTime < BRIBE_LAG) {
return VotingStage.BribesPhase;
} else if (modTime >= BRIBE_LAG && modTime < (BRIBE_LAG + DURATION)) {
return VotingStage.VotesPhase;
} else {
return VotingStage.RewardsPhase;
}
}

/*
External functions
*/
@@ -76,7 +71,7 @@ abstract contract BaseGauge is IBaseGauge {
function notifyRewardAmount(uint256 _amount) external lock {
require(msg.sender == voter, "not voter");
require(_amount > 0, "zero amount");
_safeTransferFrom(rewardToken, msg.sender, address(this), _amount);
IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), _amount);

emit NotifyReward(msg.sender, rewardToken, _amount);

@@ -87,20 +82,6 @@ abstract contract BaseGauge is IBaseGauge {
Internal functions
*/

function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}

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)
);
require(success && (data.length == 0 || abi.decode(data, (bool))));
}

/**
* @notice Override function to implement passthrough logic
* @param _amount Amount of rewards
30 changes: 9 additions & 21 deletions src/Bribe.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// SPDX-License-Identifier: GPL-3
pragma solidity ^0.8.15;

import "lib/forge-std/src/console2.sol";
import "src/interfaces/IBribe.sol";
import "src/interfaces/IBaseGauge.sol";
import "src/interfaces/IVoter.sol";
import "src/interfaces/IVotingEscrow.sol";
import "src/libraries/Math.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

/**
* @title Bribe
* @notice Implementation of bribe contract to be used with gauges
*/
contract Bribe is IBribe {
using SafeERC20 for IERC20;

/// @notice Rewards released over voting period
uint256 internal constant DURATION = 2 weeks;
/// @notice Duration of time when bribes are accepted
@@ -107,7 +110,7 @@ contract Bribe is IBribe {

/// @inheritdoc IBribe
function notifyRewardAmount(address token, uint256 amount) external lock {
require(amount > 0);
require(amount > 0, "reward amount must be greater than 0");

// If the token has been whitelisted by the voter contract, add it to the rewards list
require(IVoter(voter).isWhitelisted(token), "bribe tokens must be whitelisted");
@@ -117,7 +120,8 @@ contract Bribe is IBribe {
uint256 adjustedTstamp = getEpochStart(block.timestamp);
uint256 epochRewards = tokenRewardsPerEpoch[token][adjustedTstamp];

_safeTransferFrom(token, msg.sender, address(this), amount);
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

tokenRewardsPerEpoch[token][adjustedTstamp] = epochRewards + amount;
periodFinish[token] = adjustedTstamp + DURATION;

@@ -126,7 +130,7 @@ contract Bribe is IBribe {

/// @inheritdoc IBribe
function addRewardToken(address token) external {
require(msg.sender == gauge);
require(msg.sender == gauge, "not being set by a gauge");
_addRewardToken(token);
}

@@ -135,7 +139,6 @@ contract Bribe is IBribe {
require(msg.sender == voter, "Only voter can execute");
require(IVoter(voter).isWhitelisted(newToken), "New token must be whitelisted");
require(rewards[oldTokenIndex] == oldToken, "Old token mismatch");
require(newToken != address(0), "New token cannot be zero address");

// Check that the newToken does not already exist in the rewards array
for (uint256 i = 0; i < rewards.length; i++) {
@@ -193,7 +196,6 @@ contract Bribe is IBribe {
if (votingCheckpoints[nCheckpoints - 1].timestamp < timestamp) {
return (nCheckpoints - 1);
}

// Check implicit zero balance
if (votingCheckpoints[0].timestamp > timestamp) {
return 0;
@@ -291,7 +293,7 @@ contract Bribe is IBribe {

_writeCheckpoint(tokenId, balanceOf[tokenId]);

_safeTransfer(tokens[i], _owner, _reward);
IERC20(tokens[i]).safeTransfer(_owner, _reward);

emit ClaimRewards(_owner, tokens[i], _reward);
}
@@ -384,18 +386,4 @@ contract Bribe is IBribe {
function _bribeStart(uint256 timestamp) internal pure returns (uint256) {
return timestamp - (timestamp % (DURATION));
}

function _safeTransfer(address token, address to, uint256 value) internal {
require(token.code.length > 0);
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))));
}

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)
);
require(success && (data.length == 0 || abi.decode(data, (bool))));
}
}
4 changes: 2 additions & 2 deletions src/FluxToken.sol
Original file line number Diff line number Diff line change
@@ -213,7 +213,7 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken {

// Given an amount of eth, calculate how much FLUX it would earn in a year if it were deposited into veALCX
function getClaimableFlux(uint256 _amount, address _nft) public view returns (uint256 claimableFlux) {
uint256 bpt = _calculateBPT(_amount);
uint256 bpt = calculateBPT(_amount);

uint256 veMul = IVotingEscrow(veALCX).MULTIPLIER();
uint256 veMax = IVotingEscrow(veALCX).MAXTIME();
@@ -229,7 +229,7 @@ contract FluxToken is ERC20("Flux", "FLUX"), IFluxToken {
}
}

function _calculateBPT(uint256 _amount) public view returns (uint256 bptOut) {
function calculateBPT(uint256 _amount) public view returns (uint256 bptOut) {
bptOut = _amount * bptMultiplier;
}
}
4 changes: 2 additions & 2 deletions src/RewardsDistributor.sol
Original file line number Diff line number Diff line change
@@ -138,7 +138,7 @@ contract RewardsDistributor is IRewardsDistributor, ReentrancyGuard {

/// @inheritdoc IRewardsDistributor
function checkpointToken() external {
assert(msg.sender == depositor);
require(msg.sender == depositor, "only depositor");
_checkpointToken();
}

@@ -211,7 +211,7 @@ contract RewardsDistributor is IRewardsDistributor, ReentrancyGuard {

/// @dev Once off event on contract initialize
function setDepositor(address _depositor) external {
require(msg.sender == depositor);
require(msg.sender == depositor, "only depositor");
depositor = _depositor;
emit DepositorUpdated(_depositor);
}
38 changes: 11 additions & 27 deletions src/Voter.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3
pragma solidity ^0.8.15;

import "lib/forge-std/src/console2.sol";
import "src/interfaces/IBribeFactory.sol";
import "src/interfaces/IBribe.sol";
import "src/interfaces/IBaseGauge.sol";
@@ -28,7 +28,7 @@ contract Voter is IVoter {
address public immutable bribefactory;

uint256 internal constant BPS = 10_000;
uint256 internal constant MAX_BOOST = 10000;
uint256 internal constant MAX_BOOST = 10_000;
uint256 internal constant MIN_BOOST = 0;
/// @notice Rewards are released over this duration (2 weeks)
uint256 internal constant DURATION = 2 weeks;
@@ -167,7 +167,7 @@ contract Voter is IVoter {

/// @inheritdoc IVoter
function swapReward(address gaugeAddress, uint256 tokenIndex, address oldToken, address newToken) external {
require(msg.sender == admin);
require(msg.sender == admin, "only admin can swap reward tokens");
IBribe(bribes[gaugeAddress]).swapOutRewardToken(tokenIndex, oldToken, newToken);
}

@@ -231,8 +231,8 @@ contract Voter is IVoter {
uint256[] calldata _weights,
uint256 _boost
) external onlyNewEpoch(_tokenId) {
require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId));
require(_poolVote.length == _weights.length);
require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId), "not approved or owner");
require(_poolVote.length == _weights.length, "pool vote and weights mismatch");
require(_poolVote.length > 0, "no pools voted for");
require(_poolVote.length <= pools.length, "invalid pools");
require(
@@ -251,6 +251,7 @@ contract Voter is IVoter {
/// @inheritdoc IVoter
function whitelist(address _token) public {
require(msg.sender == admin, "not admin");
require(_token != address(0), "cannot be zero address");
_whitelist(_token);
}

@@ -262,8 +263,8 @@ contract Voter is IVoter {

/// @inheritdoc IVoter
function createGauge(address _pool, GaugeType _gaugeType) external returns (address) {
require(msg.sender == admin, "not admin");
require(gauges[_pool] == address(0x0), "exists");
require(msg.sender == admin, "only admin creates gauges");

address _bribe = IBribeFactory(bribefactory).createBribe();

@@ -327,23 +328,6 @@ contract Voter is IVoter {
}
}

/// @inheritdoc IVoter
function updateForRange(uint256 start, uint256 end) public {
for (uint256 i = start; i < end; i++) {
_updateFor(gauges[pools[i]]);
}
}

/// @inheritdoc IVoter
function updateAll() external {
updateForRange(0, pools.length);
}

/// @inheritdoc IVoter
function updateGauge(address _gauge) external {
_updateFor(_gauge);
}

/// @inheritdoc IVoter
function claimBribes(address[] memory _bribes, address[][] memory _tokens, uint256 _tokenId) external {
require(IVotingEscrow(veALCX).isApprovedOrOwner(msg.sender, _tokenId));
@@ -446,8 +430,8 @@ contract Voter is IVoter {
require(isAlive[_gauge], "cannot vote for dead gauge");

uint256 _poolWeight = (_weights[i] * totalPower) / _totalVoteWeight;
require(votes[_tokenId][_pool] == 0);
require(_poolWeight != 0);
require(votes[_tokenId][_pool] == 0, "already voted for pool");
require(_poolWeight != 0, "cannot vote with zero weight");
_updateFor(_gauge);

poolVote[_tokenId].push(_pool);
@@ -471,13 +455,13 @@ contract Voter is IVoter {
}

function _whitelist(address _token) internal {
require(!isWhitelisted[_token]);
require(!isWhitelisted[_token], "token already whitelisted");
isWhitelisted[_token] = true;
emit Whitelisted(msg.sender, _token);
}

function _removeFromWhitelist(address _token) internal {
require(isWhitelisted[_token]);
require(isWhitelisted[_token], "token not whitelisted");
isWhitelisted[_token] = false;
emit RemovedFromWhitelist(msg.sender, _token);
}
29 changes: 16 additions & 13 deletions src/VotingEscrow.sol
Original file line number Diff line number Diff line change
@@ -501,13 +501,13 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
function approve(address _approved, uint256 _tokenId) public {
address owner = idToOwner[_tokenId];
// Throws if `_tokenId` is not a valid token
require(owner != address(0));
require(owner != address(0), "owner not found");
// Throws if `_approved` is the current owner
require(_approved != owner, "Approved is already owner");
// Check requirements
bool senderIsOwner = (owner == msg.sender);
bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender];
require(senderIsOwner || senderIsApprovedForAll);
require(senderIsOwner || senderIsApprovedForAll, "sender is not owner or approved");
// Set the approval
idToApprovals[_tokenId] = _approved;
emit Approval(owner, _approved, _tokenId);
@@ -523,7 +523,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
*/
function setApprovalForAll(address _operator, bool _approved) external {
// Throws if `_operator` is the `msg.sender`
require(_operator != msg.sender);
require(_operator != msg.sender, "operator cannot be sender");
ownerToOperators[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
@@ -533,7 +533,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
* @param delegatee The address to delegate votes to
*/
function delegate(address delegatee) public {
if (delegatee == address(0)) delegatee = msg.sender;
require(delegatee != address(0), "cannot delegate to zero address");
return _delegate(msg.sender, delegatee);
}

@@ -571,13 +571,13 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {

/// @inheritdoc IVotingEscrow
function voting(uint256 _tokenId) external {
require(msg.sender == voter);
require(msg.sender == voter, "not voter");
voted[_tokenId] = true;
}

/// @inheritdoc IVotingEscrow
function abstain(uint256 _tokenId) external {
require(msg.sender == voter);
require(msg.sender == voter, "not voter");
voted[_tokenId] = false;
}

@@ -618,8 +618,8 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
function merge(uint256 _from, uint256 _to) external {
require(!voted[_from], "voting in progress for token");
require(_from != _to, "must be different tokens");
require(_isApprovedOrOwner(msg.sender, _from));
require(_isApprovedOrOwner(msg.sender, _to));
require(_isApprovedOrOwner(msg.sender, _from), "not approved or owner");
require(_isApprovedOrOwner(msg.sender, _to), "not approved or owner");

LockedBalance memory _locked0 = locked[_from];
LockedBalance memory _locked1 = locked[_to];
@@ -657,8 +657,8 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {

/// @inheritdoc IVotingEscrow
function updateLock(uint256 _tokenId) external {
require(isMaxLocked(_tokenId), "not max locked");
require(msg.sender == voter, "not voter");
require(isMaxLocked(_tokenId), "not max locked");

locked[_tokenId].end = ((block.timestamp + MAXTIME) / WEEK) * WEEK;
}
@@ -712,7 +712,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
* @param _maxLockEnabled Is max lock being enabled
*/
function updateUnlockTime(uint256 _tokenId, uint256 _lockDuration, bool _maxLockEnabled) external nonreentrant {
require(_isApprovedOrOwner(msg.sender, _tokenId));
require(_isApprovedOrOwner(msg.sender, _tokenId), "not approved or owner");

LockedBalance memory _locked = locked[_tokenId];

@@ -739,7 +739,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
* @dev Only possible if the lock has expired
*/
function withdraw(uint256 _tokenId) public nonreentrant {
require(_isApprovedOrOwner(msg.sender, _tokenId));
require(_isApprovedOrOwner(msg.sender, _tokenId), "not approved or owner");
require(!voted[_tokenId], "voting in progress for token");

LockedBalance memory _locked = locked[_tokenId];
@@ -757,7 +757,10 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {
_checkpoint(_tokenId, _locked, LockedBalance(0, 0, false, 0));

// Withdraws BPT from reward pool
require(IRewardPoolManager(rewardPoolManager).withdrawFromRewardPool(value));
require(
IRewardPoolManager(rewardPoolManager).withdrawFromRewardPool(value),
"withdraw from reward pool failed"
);

require(IERC20(BPT).transfer(ownerOf(_tokenId), value));

@@ -773,7 +776,7 @@ contract VotingEscrow is IERC721, IERC721Metadata, IVotes, IVotingEscrow {

/// @inheritdoc IVotingEscrow
function startCooldown(uint256 _tokenId) external {
require(_isApprovedOrOwner(msg.sender, _tokenId));
require(_isApprovedOrOwner(msg.sender, _tokenId), "not approved or owner");

LockedBalance memory _locked = locked[_tokenId];

9 changes: 5 additions & 4 deletions src/gauges/PassthroughGauge.sol
Original file line number Diff line number Diff line change
@@ -13,6 +13,8 @@ import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
* @dev If custom distribution logic is necessary create additional contract
*/
contract PassthroughGauge is BaseGauge {
using SafeERC20 for IERC20;

constructor(address _receiver, address _bribe, address _ve, address _voter) {
receiver = _receiver;
bribe = _bribe;
@@ -35,10 +37,9 @@ contract PassthroughGauge is BaseGauge {
* @param _amount Amount of rewards
*/
function _passthroughRewards(uint256 _amount) internal override {
uint256 rewardBalance = IERC20(rewardToken).balanceOf(address(this));
require(rewardBalance >= _amount, "insufficient rewards");

_safeTransfer(rewardToken, receiver, _amount);
// Gauge will always have _amount
// It is transfered in BaseGauge.notifyRewardAmount
IERC20(rewardToken).safeTransfer(receiver, _amount);

emit Passthrough(msg.sender, rewardToken, _amount, receiver);
}
6 changes: 0 additions & 6 deletions src/interfaces/IBaseGauge.sol
Original file line number Diff line number Diff line change
@@ -54,12 +54,6 @@ interface IBaseGauge {

function acceptAdmin() external;

/**
* @notice Get the current voting stage
* @param timestamp The timestamp to check the voting stage
*/
function getVotingStage(uint256 timestamp) external pure returns (VotingStage);

/**
* @notice Distribute the appropriate rewards to a gauge
* @dev This function may require different implementation depending on destination
18 changes: 0 additions & 18 deletions src/interfaces/IVoter.sol
Original file line number Diff line number Diff line change
@@ -156,24 +156,6 @@ interface IVoter {
*/
function updateFor(address[] memory _gauges) external;

/**
* @notice Update a list of gauges given a range of pool indexes
* @param start start index
* @param end end index
*/
function updateForRange(uint256 start, uint256 end) external;

/**
* @notice Update all gauges in the pools array
*/
function updateAll() external;

/**
* @notice Update the gauge for a specific pool
* @param _gauge Address of the gauge to update
*/
function updateGauge(address _gauge) external;

/**
* @notice Claim the bribes for a given token id
* @param _bribes Array of bribe addresses to claim from
24 changes: 24 additions & 0 deletions src/test/AlchemixGovernor.t.sol
Original file line number Diff line number Diff line change
@@ -381,4 +381,28 @@ contract AlchemixGovernorTest is BaseTest {

assertFalse(voter.isWhitelisted(usdc));
}

function testAdminFunctions() public {
address admin = governor.admin();

hevm.expectRevert(abi.encodePacked("not admin"));
governor.setAdmin(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
governor.setVotingDelay(20);

hevm.expectRevert(abi.encodePacked("not admin"));
governor.setVotingPeriod(20);

hevm.prank(admin);
governor.setAdmin(devmsig);

hevm.prank(admin);
hevm.expectRevert(abi.encodePacked("not pending admin"));
governor.acceptAdmin();

hevm.prank(devmsig);
governor.acceptAdmin();
assertEq(governor.admin(), devmsig, "admin should be updated");
}
}
143 changes: 143 additions & 0 deletions src/test/FluxToken.t.sol
Original file line number Diff line number Diff line change
@@ -8,6 +8,80 @@ contract FluxTokenTest is BaseTest {
setupContracts(block.timestamp);
}

function testAdminFunctionErrors() external {
address admin = flux.admin();

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setAdmin(devmsig);

hevm.prank(admin);
hevm.expectRevert(abi.encodePacked("not pending admin"));
flux.acceptAdmin();

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setVoter(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setVeALCX(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setAlchemechNFT(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setPatronNFT(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setNftMultiplier(1);

hevm.expectRevert(abi.encodePacked("not admin"));
flux.setBptMultiplier(1);
}

function testUpdateToZero() external {
address admin = flux.admin();
address minter = flux.minter();

hevm.prank(admin);
flux.setAdmin(devmsig);

hevm.startPrank(devmsig);
flux.acceptAdmin();

hevm.expectRevert(abi.encodePacked("FluxToken: voter cannot be zero address"));
flux.setVoter(address(0));

hevm.expectRevert(abi.encodePacked("FluxToken: veALCX cannot be zero address"));
flux.setVeALCX(address(0));

hevm.expectRevert(abi.encodePacked("FluxToken: alchemechNFT cannot be zero address"));
flux.setAlchemechNFT(address(0));

hevm.expectRevert(abi.encodePacked("FluxToken: patronNFT cannot be zero address"));
flux.setPatronNFT(address(0));

hevm.expectRevert(abi.encodePacked("FluxToken: nftMultiplier cannot be zero"));
flux.setNftMultiplier(0);

hevm.expectRevert(abi.encodePacked("FluxToken: bptMultiplier cannot be zero"));
flux.setBptMultiplier(0);

hevm.stopPrank();

hevm.prank(minter);
hevm.expectRevert(abi.encodePacked("FluxToken: minter cannot be zero address"));
flux.setMinter(address(0));
}

function testSetMultipliersLimit() external {
hevm.startPrank(admin);

hevm.expectRevert(abi.encodePacked("FluxToken: nftMultiplier cannot be greater than BPS"));
flux.setNftMultiplier(BPS + 1);

hevm.expectRevert(abi.encodePacked("FluxToken: bptMultiplier cannot be greater than BPS"));
flux.setBptMultiplier(BPS + 1);
}

function testMintFluxFromNFT() external {
uint256 tokenId = 4;
address ownerOfPatronNFT = IAlEthNFT(patronNFT).ownerOf(tokenId);
@@ -36,6 +110,13 @@ contract FluxTokenTest is BaseTest {
assertEq(flux.balanceOf(ownerOfAlchemechNFT), alchemechTotal, "owner should have alchemech flux");
}

function testCalculateBPT() external {
uint256 amount = 1000;
uint256 bptCalculation = flux.calculateBPT(amount);

assertEq(amount * flux.bptMultiplier(), bptCalculation, "should calculate BPT correctly");
}

function testMintFluxFromNFTErrors() external {
uint256 tokenId = 4;
address ownerOfPatronNFT = IAlEthNFT(patronNFT).ownerOf(tokenId);
@@ -107,4 +188,66 @@ contract FluxTokenTest is BaseTest {

assertEq(unclaimedFluxEnd, amountToRagequit, "should have all unclaimed flux");
}

function testFluxActions() external {
address bribeAddress = voter.bribes(address(sushiGauge));

uint256 tokenId1 = createVeAlcx(admin, TOKEN_1, veALCX.MAXTIME(), false);
uint256 tokenId2 = createVeAlcx(beef, TOKEN_1, veALCX.MAXTIME(), false);

uint256 amount1 = veALCX.claimableFlux(tokenId1);
uint256 amount2 = veALCX.claimableFlux(tokenId2);

uint256 totalAmount = amount1 + amount2;

uint256 unclaimedFlux1Start = flux.getUnclaimedFlux(tokenId1);
uint256 unclaimedFlux2Start = flux.getUnclaimedFlux(tokenId2);

assertEq(unclaimedFlux1Start, 0, "should start with no unclaimed flux");
assertEq(unclaimedFlux2Start, 0, "should start with no unclaimed flux");

address[] memory pools = new address[](1);
pools[0] = sushiPoolAddress;
uint256[] memory weights = new uint256[](1);
weights[0] = 5000;

address[] memory bribes = new address[](1);
bribes[0] = address(bribeAddress);
address[][] memory tokens = new address[][](2);
tokens[0] = new address[](1);
tokens[0][0] = bal;

hevm.prank(admin);
voter.vote(tokenId1, pools, weights, 0);

hevm.prank(beef);
voter.vote(tokenId2, pools, weights, 0);

// Fast forward epochs
hevm.warp(newEpoch());

voter.distribute();

hevm.expectRevert(abi.encodePacked("not voter"));
flux.updateFlux(tokenId1, amount1);

hevm.expectRevert(abi.encodePacked("not voter"));
flux.accrueFlux(tokenId1);

hevm.prank(address(voter));
hevm.expectRevert(abi.encodePacked("not enough flux"));
flux.updateFlux(tokenId1, TOKEN_100K);

hevm.expectRevert(abi.encodePacked("not veALCX"));
flux.mergeFlux(tokenId1, tokenId2);

hevm.prank(address(veALCX));
flux.mergeFlux(tokenId1, tokenId2);

uint256 unclaimedFlux1End = flux.getUnclaimedFlux(tokenId1);
uint256 unclaimedFlux2End = flux.getUnclaimedFlux(tokenId2);

assertEq(unclaimedFlux1End, 0, "should have no unclaimed flux");
assertEq(unclaimedFlux2End, totalAmount, "should have all unclaimed flux");
}
}
19 changes: 19 additions & 0 deletions src/test/Minter.t.sol
Original file line number Diff line number Diff line change
@@ -295,6 +295,11 @@ contract MinterTest is BaseTest {

hevm.expectRevert(abi.encodePacked("insufficient balance to compound"));
distributor.claim(tokenId, true);

hevm.stopPrank();

hevm.expectRevert(abi.encodePacked("not approved"));
distributor.claim(tokenId, true);
}

// Compound claiming should revert if user doesn't provide enough weth
@@ -320,6 +325,9 @@ contract MinterTest is BaseTest {
hevm.expectRevert(abi.encodePacked("insufficient balance to compound"));
distributor.claim(tokenId, true);

hevm.expectRevert(abi.encodePacked("Value must be 0 if not compounding"));
distributor.claim{ value: 1 ether }(tokenId, false);

distributor.claim{ value: 100 ether }(tokenId, true);
assertGt(admin.balance, 0);
assertGt(100 ether, admin.balance);
@@ -332,12 +340,19 @@ contract MinterTest is BaseTest {
hevm.expectRevert(abi.encodePacked("not initializer"));
minter.initialize();

hevm.prank(address(0));
hevm.expectRevert(abi.encodePacked("already initialized"));
minter.initialize();

hevm.expectRevert(abi.encodePacked("not admin"));
minter.setAdmin(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
minter.setVeAlcxEmissionsRate(1000);

hevm.expectRevert(abi.encodePacked("not voter"));
minter.updatePeriod();

hevm.prank(admin);
minter.setAdmin(devmsig);

@@ -347,6 +362,10 @@ contract MinterTest is BaseTest {
hevm.startPrank(devmsig);

minter.acceptAdmin();

hevm.expectRevert(abi.encodePacked("cannot be greater than 100%"));
minter.setVeAlcxEmissionsRate(10_000 * 2);

minter.setVeAlcxEmissionsRate(1000);

hevm.stopPrank();
38 changes: 38 additions & 0 deletions src/test/RevenueHandler.t.sol
Original file line number Diff line number Diff line change
@@ -312,6 +312,22 @@ contract RevenueHandlerTest is BaseTest {
assertEq(balAfter, claimable, "should be equal to amount claimed");
}

function testNotEnoughRevenueToClaim() external {
uint256 revAmt = 1000e18;
uint256 tokenId = _setupClaimableNonAlchemicRevenue(revAmt, bal);
uint256 balBefore = IERC20(bal).balanceOf(address(this));

assertEq(balBefore, 0, "should have no bal before claiming");

uint256 claimable = revenueHandler.claimable(tokenId, bal);

hevm.prank(address(revenueHandler));
IERC20(bal).transfer(admin, claimable);

hevm.expectRevert(abi.encodePacked("Not enough revenue to claim"));
revenueHandler.claim(tokenId, bal, address(0), claimable, address(this));
}

function testClaimNonApprovedRevenue() external {
uint256 revAmt = 1000e18;
uint256 tokenId = _setupClaimableNonAlchemicRevenue(revAmt, aura);
@@ -606,4 +622,26 @@ contract RevenueHandlerTest is BaseTest {
assertEq(revenueHandler.treasuryPct(), newPct);
}
}

function testEnableRevenueToken() external {
address revenueToken = revenueHandler.revenueTokens(0);

hevm.expectRevert(abi.encodePacked("Token enabled"));
revenueHandler.enableRevenueToken(revenueToken);

revenueHandler.disableRevenueToken(revenueToken);

hevm.expectRevert(abi.encodePacked("Token disabled"));
revenueHandler.disableRevenueToken(revenueToken);

revenueHandler.enableRevenueToken(revenueToken);
}

function testSetTreasury() external {
hevm.expectRevert(abi.encodePacked("treasury cannot be 0x0"));
revenueHandler.setTreasury(address(0));

revenueHandler.setTreasury(admin);
assertEq(revenueHandler.treasury(), admin, "treasury should be admin");
}
}
167 changes: 167 additions & 0 deletions src/test/RewardPoolManager.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import "./BaseTest.sol";

contract RewardPoolManagerTest is BaseTest {
function setUp() public {
setupContracts(block.timestamp);
}

function testAdminFunctions() public {
address admin = rewardPoolManager.admin();

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setAdmin(devmsig);

hevm.prank(admin);
hevm.expectRevert(abi.encodePacked("not pending admin"));
rewardPoolManager.acceptAdmin();

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setTreasury(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setRewardPool(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setPoolToken(devmsig);

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setVeALCX(devmsig);

hevm.prank(admin);
rewardPoolManager.setAdmin(devmsig);

hevm.startPrank(devmsig);
rewardPoolManager.acceptAdmin();

rewardPoolManager.setTreasury(devmsig);

rewardPoolManager.setRewardPool(devmsig);

rewardPoolManager.setPoolToken(address(usdc));

rewardPoolManager.setVeALCX(address(minter));

hevm.stopPrank();
}

function testDepositIntoRewardPoolError() public {
hevm.expectRevert(abi.encodePacked("must be veALCX"));
rewardPoolManager.depositIntoRewardPool(TOKEN_1);
}

function testWithdrawFromRewardPool() public {
hevm.expectRevert(abi.encodePacked("must be veALCX"));
rewardPoolManager.withdrawFromRewardPool(1000);
}

// Test depositing, withdrawing from a rewardPool (Aura pool)
function testRewardPool() public {
// Reward pool should be set
assertEq(rewardPool, rewardPoolManager.rewardPool());

deal(bpt, address(rewardPoolManager), TOKEN_1);

// Initial amount of bal and aura rewards earned
uint256 rewardBalanceBefore1 = IERC20(bal).balanceOf(admin);
uint256 rewardBalanceBefore2 = IERC20(aura).balanceOf(admin);
assertEq(rewardBalanceBefore1, 0, "rewardBalanceBefore1 should be 0");
assertEq(rewardBalanceBefore2, 0, "rewardBalanceBefore2 should be 0");

// Initial BPT balance of rewardPoolManager
uint256 amount = IERC20(bpt).balanceOf(address(rewardPoolManager));
assertEq(amount, TOKEN_1);

// Deposit BPT balance into rewardPool
hevm.prank(address(veALCX));
rewardPoolManager.depositIntoRewardPool(amount);

uint256 amountAfterDeposit = IERC20(bpt).balanceOf(address(rewardPoolManager));
assertEq(amountAfterDeposit, 0, "full balance should be deposited");

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.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.claimRewardPoolRewards();

hevm.prank(admin);
rewardPoolManager.claimRewardPoolRewards();
uint256 rewardBalanceAfter1 = IERC20(bal).balanceOf(address(admin));
uint256 rewardBalanceAfter2 = IERC20(aura).balanceOf(address(admin));

// After claiming rewards admin bal balance should increase
assertGt(rewardBalanceAfter1, rewardBalanceBefore1, "should accumulate bal rewards");
assertGt(rewardBalanceAfter2, rewardBalanceBefore2, "should accumulate aura rewards");

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 rewardPoolManager admin can update rewardPool
hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setRewardPool(sushiPoolAddress);

hevm.prank(admin);
rewardPoolManager.setRewardPool(sushiPoolAddress);

// Reward pool should update
assertEq(sushiPoolAddress, rewardPoolManager.rewardPool(), "rewardPool not updated");
}

function testUpdatingRewardPoolTokens() public {
address admin = rewardPoolManager.admin();

address[] memory tokens = new address[](2);
tokens[0] = dai;
tokens[1] = usdt;

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.swapOutRewardPoolToken(0, bal, usdc);

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.addRewardPoolTokens(tokens);

hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.addRewardPoolToken(dai);

hevm.startPrank(admin);

hevm.expectRevert(abi.encodePacked("incorrect token"));
rewardPoolManager.swapOutRewardPoolToken(0, dai, usdc);

rewardPoolManager.swapOutRewardPoolToken(0, bal, usdc);
assertEq(rewardPoolManager.rewardPoolTokens(0), usdc, "rewardPoolTokens[0] should be usdc");

rewardPoolManager.addRewardPoolTokens(tokens);
assertEq(rewardPoolManager.rewardPoolTokens(2), dai, "rewardPoolTokens[2] should be dai");
assertEq(rewardPoolManager.rewardPoolTokens(3), usdt, "rewardPoolTokens[3] should be usdt");
}

function testMaxRewardPoolTokens() public {
address[] memory tokens = new address[](8);
tokens[0] = dai;
tokens[1] = usdt;
tokens[2] = usdc;
tokens[3] = bpt;
tokens[4] = time;
tokens[5] = aleth;
tokens[6] = alusd3crv;
tokens[7] = alusd;

hevm.prank(admin);
rewardPoolManager.addRewardPoolTokens(tokens);

hevm.prank(admin);
hevm.expectRevert(abi.encodePacked("too many reward pool tokens"));
rewardPoolManager.addRewardPoolToken(beef);
}
}
348 changes: 346 additions & 2 deletions src/test/Voting.t.sol

Large diffs are not rendered by default.

152 changes: 95 additions & 57 deletions src/test/VotingEscrow.t.sol
Original file line number Diff line number Diff line change
@@ -45,63 +45,6 @@ contract VotingEscrowTest is BaseTest {
hevm.stopPrank();
}

// Test depositing, withdrawing from a rewardPool (Aura pool)
function testRewardPool() public {
// Reward pool should be set
assertEq(rewardPool, rewardPoolManager.rewardPool());

deal(bpt, address(rewardPoolManager), TOKEN_1);

// Initial amount of bal and aura rewards earned
uint256 rewardBalanceBefore1 = IERC20(bal).balanceOf(admin);
uint256 rewardBalanceBefore2 = IERC20(aura).balanceOf(admin);
assertEq(rewardBalanceBefore1, 0, "rewardBalanceBefore1 should be 0");
assertEq(rewardBalanceBefore2, 0, "rewardBalanceBefore2 should be 0");

// Initial BPT balance of rewardPoolManager
uint256 amount = IERC20(bpt).balanceOf(address(rewardPoolManager));
assertEq(amount, TOKEN_1);

// Deposit BPT balance into rewardPool
hevm.prank(address(veALCX));
rewardPoolManager.depositIntoRewardPool(amount);

uint256 amountAfterDeposit = IERC20(bpt).balanceOf(address(rewardPoolManager));
assertEq(amountAfterDeposit, 0, "full balance should be deposited");

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);
rewardPoolManager.claimRewardPoolRewards();
uint256 rewardBalanceAfter1 = IERC20(bal).balanceOf(address(admin));
uint256 rewardBalanceAfter2 = IERC20(aura).balanceOf(address(admin));

// After claiming rewards admin bal balance should increase
assertGt(rewardBalanceAfter1, rewardBalanceBefore1, "should accumulate bal rewards");
assertGt(rewardBalanceAfter2, rewardBalanceBefore2, "should accumulate aura rewards");

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 rewardPoolManager admin can update rewardPool
hevm.expectRevert(abi.encodePacked("not admin"));
rewardPoolManager.setRewardPool(sushiPoolAddress);

hevm.prank(admin);
rewardPoolManager.setRewardPool(sushiPoolAddress);

// Reward pool should update
assertEq(sushiPoolAddress, rewardPoolManager.rewardPool(), "rewardPool not updated");
}

function testUpdateLockDuration() public {
hevm.startPrank(admin);

@@ -376,6 +319,15 @@ contract VotingEscrowTest is BaseTest {
assertGt(unclaimedFlux3, unclaimedFlux2, "unclaimed flux should be greater for active voter");
}

function testOnlyDepositorFunctions() public {
// Distributor should be set
hevm.expectRevert(abi.encodePacked("only depositor"));
distributor.setDepositor(beef);

hevm.expectRevert(abi.encodePacked("only depositor"));
distributor.checkpointToken();
}

// Voting should not impact amount of ALCX rewards earned
function testRewardsClaiming() public {
uint256 tokenId1 = createVeAlcx(admin, TOKEN_1, MAXTIME, false);
@@ -468,6 +420,11 @@ contract VotingEscrowTest is BaseTest {
function testMergeTokens() public {
uint256 tokenId1 = createVeAlcx(admin, TOKEN_1, MAXTIME, false);
uint256 tokenId2 = createVeAlcx(admin, TOKEN_100K, MAXTIME / 2, false);
uint256 tokenId3 = createVeAlcx(beef, TOKEN_100K, MAXTIME / 2, false);

hevm.prank(beef);
hevm.expectRevert(abi.encodePacked("not approved or owner"));
veALCX.merge(tokenId1, tokenId2);

hevm.startPrank(admin);

@@ -500,6 +457,9 @@ contract VotingEscrowTest is BaseTest {
hevm.expectRevert(abi.encodePacked("must be different tokens"));
veALCX.merge(tokenId1, tokenId1);

hevm.expectRevert(abi.encodePacked("not approved or owner"));
veALCX.merge(tokenId1, tokenId3);

veALCX.merge(tokenId1, tokenId2);

uint256 unclaimedFluxAfter1 = flux.getUnclaimedFlux(tokenId1);
@@ -552,6 +512,12 @@ contract VotingEscrowTest is BaseTest {
// Fast forward to lock end of tokenId2
hevm.warp(block.timestamp + THREE_WEEKS);

hevm.expectRevert(abi.encodePacked("not approved or owner"));
veALCX.withdraw(tokenId1);

hevm.expectRevert(abi.encodePacked("not approved or owner"));
veALCX.startCooldown(tokenId1);

hevm.startPrank(admin);

// Should not be able to withdraw BPT
@@ -655,6 +621,9 @@ contract VotingEscrowTest is BaseTest {
hevm.prank(address(veALCX));
flux.mint(admin, ragequitAmount);

hevm.expectRevert(abi.encodePacked("not approved or owner"));
veALCX.updateUnlockTime(tokenId, 1 days, true);

hevm.startPrank(admin);
flux.approve(address(veALCX), ragequitAmount);
veALCX.startCooldown(tokenId);
@@ -977,4 +946,73 @@ contract VotingEscrowTest is BaseTest {
uint256 newVotingPowerBeef = veALCX.getVotes(beef);
assertEq(newVotingPowerBeef, balanceOfTokens, "incorrect voting power");
}

function testAdminFunctions() public {
hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setTreasury(beef);

hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setVoter(beef);

hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setRewardsDistributor(beef);

hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setfluxMultiplier(0);

hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setRewardPoolManager(beef);

hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setAdmin(beef);

hevm.expectRevert(abi.encodePacked("not admin"));
veALCX.setfluxPerVeALCX(0);

hevm.expectRevert(abi.encodePacked("not voter"));
veALCX.voting(0);

hevm.expectRevert(abi.encodePacked("not voter"));
veALCX.abstain(0);

hevm.prank(admin);
veALCX.setAdmin(beef);

hevm.expectRevert(abi.encodePacked("not pending admin"));
veALCX.acceptAdmin();

hevm.prank(admin);
hevm.expectRevert(abi.encodePacked("treasury cannot be 0x0"));
veALCX.setTreasury(address(0));

hevm.prank(admin);
hevm.expectRevert(abi.encodePacked("fluxMultiplier must be greater than 0"));
veALCX.setfluxMultiplier(0);

hevm.prank(admin);
veALCX.setTreasury(beef);
assertEq(veALCX.treasury(), beef, "incorrect treasury");

hevm.expectRevert(abi.encodePacked("owner not found"));
veALCX.approve(admin, 100);

hevm.expectRevert(abi.encodePacked("cannot delegate to zero address"));
veALCX.delegate(address(0));

hevm.prank(beef);
veALCX.acceptAdmin();

hevm.startPrank(beef);

veALCX.setfluxPerVeALCX(10);
assertEq(veALCX.fluxPerVeALCX(), 10, "incorrect flux per veALCX");

veALCX.setfluxMultiplier(10);
assertEq(veALCX.fluxMultiplier(), 10, "incorrect flux multiplier");

veALCX.setClaimFee(1000);
assertEq(veALCX.claimFeeBps(), 1000, "incorrect claim fee");

hevm.stopPrank();
}
}

0 comments on commit 627b073

Please sign in to comment.