Skip to content

Commit

Permalink
feat: minor changes in GaugeV3 and TumblerV3
Browse files Browse the repository at this point in the history
- `IRateKeeper` now declares the signature of `addToken`
- `TumblerV3` accepts pool as constructor argument instead of the quota keeper for consistency
  • Loading branch information
lekhovitsky committed Dec 17, 2024
1 parent 725c7f3 commit 60af9e7
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 32 deletions.
2 changes: 0 additions & 2 deletions contracts/interfaces/ITumblerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ interface ITumblerV3 is IRateKeeper, IACLTrait, ITumblerV3Events {

function getRates(address[] calldata tokens) external view returns (uint16[] memory);

function addToken(address token, uint16 rate) external;

function setRate(address token, uint16 rate) external;

function updateRates() external;
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/base/IRateKeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ interface IRateKeeper is IVersion {
/// @notice Pool rates are provided for
function pool() external view returns (address);

/// @notice Adds token to the rate keeper
/// @dev Must add token to the quota keeper in case it's not already there
function addToken(address token) external;

/// @notice Whether token is added to the rate keeper
function isTokenAdded(address token) external view returns (bool);

Expand Down
17 changes: 17 additions & 0 deletions contracts/pool/GaugeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,29 @@ contract GaugeV3 is IGaugeV3, ACLTrait, SanityCheckTrait {
/// @param token Address of the token to add
/// @param minRate The minimal interest rate paid on token's quotas
/// @param maxRate The maximal interest rate paid on token's quotas
/// @dev Exists for backward compatibility with older gauges
function addQuotaToken(address token, uint16 minRate, uint16 maxRate)
external
override
nonZeroAddress(token) // U:[GA-4]
configuratorOnly // U:[GA-3]
{
_addToken(token, minRate, maxRate);
}

/// @notice Adds a new quoted token to the gauge with default initial rate params
/// If token is not added to the quota keeper, adds it there as well
/// @param token Address of the token to add
function addToken(address token)
external
override
nonZeroAddress(token) // U:[GA-4]
configuratorOnly // U:[GA-3]
{
_addToken(token, 1, 1);
}

function _addToken(address token, uint16 minRate, uint16 maxRate) internal {
if (isTokenAdded(token) || token == IPoolV3(pool).underlyingToken()) {
revert TokenNotAllowedException(); // U:[GA-4]
}
Expand Down
23 changes: 10 additions & 13 deletions contracts/pool/TumblerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,13 @@ contract TumblerV3 is ITumblerV3, ACLTrait, SanityCheckTrait {
mapping(address => uint16) internal _rates;

/// @notice Constructor
/// @param poolQuotaKeeper_ Quota keeper of the pool whose rates to set by this contract
/// @param pool_ Pool whose rates to set by this contract
/// @param epochLength_ Epoch length in seconds
/// @custom:tests U:[TU-1]
constructor(address poolQuotaKeeper_, uint256 epochLength_)
ACLTrait(ACLTrait(IPoolQuotaKeeperV3(poolQuotaKeeper_).pool()).acl())
{
pool = IPoolQuotaKeeperV3(poolQuotaKeeper_).pool();
underlying = IPoolQuotaKeeperV3(poolQuotaKeeper_).underlying();
poolQuotaKeeper = poolQuotaKeeper_;
constructor(address pool_, uint256 epochLength_) ACLTrait(ACLTrait(pool_).acl()) {
pool = pool_;
poolQuotaKeeper = IPoolV3(pool_).poolQuotaKeeper();
underlying = IPoolQuotaKeeperV3(poolQuotaKeeper).underlying();
epochLength = epochLength_;
}

Expand Down Expand Up @@ -88,25 +86,25 @@ contract TumblerV3 is ITumblerV3, ACLTrait, SanityCheckTrait {
}
}

/// @notice Adds `token` to the set of supported tokens and to the quota keeper unless it's already there,
/// sets its rate to `rate`
/// @dev Reverts if `token` is zero address, pool's underlying or is already added, or if `rate` is zero
/// @notice Adds `token` to the set of supported tokens and to the quota keeper unless it's already there
/// @dev Reverts if `token` is zero address, pool's underlying or is already added
/// @custom:tests U:[TU-2]
function addToken(address token, uint16 rate) external override configuratorOnly nonZeroAddress(token) {
function addToken(address token) external override configuratorOnly nonZeroAddress(token) {
if (token == underlying || !_tokensSet.add(token)) revert TokenNotAllowedException();
if (!IPoolQuotaKeeperV3(poolQuotaKeeper).isQuotedToken(token)) {
IPoolQuotaKeeperV3(poolQuotaKeeper).addQuotaToken(token);
}
emit AddToken(token);

_setRate(token, rate);
_setRate(token, 1);
}

/// @dev Sets `token`'s rate to `rate`
/// @dev Reverts if `token` is not added or `rate` is zero
/// @custom:tests U:[TU-3]
function setRate(address token, uint16 rate) external override configuratorOnly {
if (!_tokensSet.contains(token)) revert TokenIsNotQuotedException();
if (rate == 0) revert IncorrectParameterException();
_setRate(token, rate);
}

Expand All @@ -119,7 +117,6 @@ contract TumblerV3 is ITumblerV3, ACLTrait, SanityCheckTrait {

/// @dev `setRate` implementation
function _setRate(address token, uint16 rate) internal {
if (rate == 0) revert IncorrectParameterException();
if (_rates[token] == rate) return;
_rates[token] = rate;
emit SetRate(token, rate);
Expand Down
9 changes: 6 additions & 3 deletions contracts/test/integration/governance/QuotaRates.int.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ contract QuotaRatesIntegrationTest is Test {
quotaKeeper = new PoolQuotaKeeperV3(address(pool));
pool.setPoolQuotaKeeper(address(quotaKeeper));

tumbler = new TumblerV3(address(quotaKeeper), 1 days);
tumbler = new TumblerV3(address(pool), 1 days);
quotaKeeper.setGauge(address(tumbler));
}

/// @notice I:[QR-1]: `TumblerV3` allows to change rates in `PoolQuotaKeeperV3`
function test_I_QR_01_tumbler_allows_to_change_rates_in_poolQuotaKeeper() public {
tumbler.addToken(address(token1), 4200);
tumbler.addToken(address(token2), 12000);
tumbler.addToken(address(token1));
tumbler.addToken(address(token2));

tumbler.setRate(address(token1), 4200);
tumbler.setRate(address(token2), 12000);

address[] memory tokens = new address[](2);
tokens[0] = address(token1);
Expand Down
9 changes: 9 additions & 0 deletions contracts/test/unit/pool/GaugeV3.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ contract GauageV3UnitTest is TestHelper, IGaugeV3Events {

/// @dev U:[GA-3]: configuratorOnly functions reverts if called by non-configurator
function test_U_GA_03_configuratorOnly_functions_reverts_if_called_by_non_configurator() public {
vm.expectRevert(CallerNotConfiguratorException.selector);
gauge.addToken(DUMB_ADDRESS);

vm.expectRevert(CallerNotConfiguratorException.selector);
gauge.addQuotaToken(DUMB_ADDRESS, 0, 0);

Expand All @@ -103,6 +106,12 @@ contract GauageV3UnitTest is TestHelper, IGaugeV3Events {
address token = DUMB_ADDRESS;
vm.startPrank(CONFIGURATOR);

vm.expectRevert(ZeroAddressException.selector);
gauge.addToken(address(0));

vm.expectRevert(TokenNotAllowedException.selector);
gauge.addToken(underlying);

vm.expectRevert(ZeroAddressException.selector);
gauge.addQuotaToken(address(0), 0, 0);

Expand Down
24 changes: 10 additions & 14 deletions contracts/test/unit/pool/TumblerV3.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {
poolQuotaKeeper.set_lastQuotaRateUpdate(uint40(block.timestamp));
pool.setPoolQuotaKeeper(address(poolQuotaKeeper));

tumbler = new TumblerV3(address(poolQuotaKeeper), 1 days);
tumbler = new TumblerV3(address(pool), 1 days);
}

/// @notice U:[TU-1]: Constructor works as expected
Expand All @@ -62,15 +62,11 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {

// addToken reverts if token is zero address
vm.expectRevert(ZeroAddressException.selector);
tumbler.addToken(address(0), 0);
tumbler.addToken(address(0));

// addToken reverts if token is underlying
vm.expectRevert(TokenNotAllowedException.selector);
tumbler.addToken(underlying, 0);

// addToken reverts on zero rate
vm.expectRevert(IncorrectParameterException.selector);
tumbler.addToken(token1, 0);
tumbler.addToken(underlying);

// addToken properly adds token to both rate and quota keeper and sets rate
poolQuotaKeeper.set_isQuotedToken(false);
Expand All @@ -81,9 +77,9 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {
emit AddToken(token1);

vm.expectEmit(true, true, true, true);
emit SetRate(token1, 4200);
emit SetRate(token1, 1);

tumbler.addToken(token1, 4200);
tumbler.addToken(token1);

assertTrue(tumbler.isTokenAdded(token1), "token1 is not added");

Expand All @@ -93,7 +89,7 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {

// addToken reverts if token is already added
vm.expectRevert(TokenNotAllowedException.selector);
tumbler.addToken(token1, 4200);
tumbler.addToken(token1);

// addToken adds token to tumbler but skips quota keeper if token is already there
poolQuotaKeeper.set_isQuotedToken(true);
Expand All @@ -106,7 +102,7 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {
vm.expectEmit(true, true, true, true);
emit SetRate(token2, 1);

tumbler.addToken(token2, 1);
tumbler.addToken(token2);

assertTrue(tumbler.isTokenAdded(token2), "token2 is not added");

Expand All @@ -125,7 +121,7 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {
vm.expectRevert(TokenIsNotQuotedException.selector);
tumbler.setRate(token1, 0);

tumbler.addToken(token1, 1);
tumbler.addToken(token1);

// setRate reverts on zero rate
vm.expectRevert(IncorrectParameterException.selector);
Expand Down Expand Up @@ -159,8 +155,8 @@ contract TumblerV3UnitTest is Test, ITumblerV3Events {
vm.clearMockedCalls();
vm.warp(block.timestamp + 1 days);

tumbler.addToken(token1, 4200);
tumbler.addToken(token2, 12000);
tumbler.addToken(token1);
tumbler.addToken(token2);

// updates rates in quota keeper
vm.expectCall(address(poolQuotaKeeper), abi.encodeCall(poolQuotaKeeper.updateRates, ()));
Expand Down

0 comments on commit 60af9e7

Please sign in to comment.