Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit Fixes QS-11 #77

Merged
merged 8 commits into from
Jun 26, 2024
43 changes: 29 additions & 14 deletions contracts/Farm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
event PoolSubscribed(uint256 indexed depositId, uint8 fundId);
event FarmStartTimeUpdated(uint256 newStartTime);
event CooldownPeriodUpdated(uint256 newCooldownPeriod);
event RewardRateUpdated(address indexed rwdToken, uint256[] newRewardRate);
event RewardRateUpdated(address indexed rwdToken, uint128[] newRewardRate);
event RewardAdded(address rwdToken, uint256 amount);
event FarmClosed();
event RecoveredERC20(address token, uint256 amount);
Expand Down Expand Up @@ -115,9 +115,8 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
revert ZeroAmount();
}
_validateFarmOpen();
if (rewardData[_rwdToken].tknManager == address(0)) {
revert InvalidRewardToken();
}
_validateRewardToken(_rwdToken);

updateFarmRewardData();
IERC20(_rwdToken).safeTransferFrom(msg.sender, address(this), _amount);
emit RewardAdded(_rwdToken, _amount);
Expand Down Expand Up @@ -159,7 +158,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
uint256 numRewards = rewardTokens.length;
for (uint8 iRwd; iRwd < numRewards;) {
_recoverRewardFunds(rewardTokens[iRwd], type(uint256).max);
_setRewardRate(rewardTokens[iRwd], new uint256[](rewardFunds.length));
_setRewardRate(rewardTokens[iRwd], new uint128[](rewardFunds.length));
unchecked {
++iRwd;
}
Expand Down Expand Up @@ -187,7 +186,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
/// @notice Function to update reward params for a fund.
/// @param _rwdToken The reward token's address.
/// @param _newRewardRates The new reward rate for the fund (includes the precision).
function setRewardRate(address _rwdToken, uint256[] memory _newRewardRates) external {
function setRewardRate(address _rwdToken, uint128[] memory _newRewardRates) external {
_validateFarmOpen();
_validateTokenManager(_rwdToken);
updateFarmRewardData();
Expand Down Expand Up @@ -225,6 +224,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
rewards = new uint256[][](numDepositSubs);

uint256 time = _getRewardAccrualTimeElapsed();
uint256[] memory accumulatedRewards = new uint256[](numRewards);

// Update the two reward funds.
for (uint8 iSub; iSub < numDepositSubs;) {
Expand All @@ -233,7 +233,8 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
uint8 fundId = sub.fundId;
for (uint8 iRwd; iRwd < numRewards;) {
if (funds[fundId].totalLiquidity != 0 && isFarmActive()) {
uint256 accRewards = _getAccRewards(iRwd, fundId, time);
uint256 accRewards = _getAccRewards(iRwd, fundId, time, accumulatedRewards[iRwd]); // accumulatedRewards is sent to consider the already accrued rewards.
accumulatedRewards[iRwd] += accRewards;
// update the accRewardPerShare for delta time.
funds[fundId].accRewardPerShare[iRwd] += (accRewards * PREC) / funds[fundId].totalLiquidity;
}
Expand Down Expand Up @@ -286,6 +287,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
/// @param _rwdToken The reward token's address.
/// @return The reward rates for the reward token (uint256[]).
function getRewardRates(address _rwdToken) external view returns (uint256[] memory) {
_validateRewardToken(_rwdToken);
uint256 numFunds = rewardFunds.length;
uint256[] memory rates = new uint256[](numFunds);
uint8 id = rewardData[_rwdToken].id;
Expand Down Expand Up @@ -333,7 +335,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
if (fund.totalLiquidity != 0) {
for (uint8 iRwd; iRwd < numRewards;) {
// Get the accrued rewards for the time.
uint256 accRewards = _getAccRewards(iRwd, iFund, time);
uint256 accRewards = _getAccRewards(iRwd, iFund, time, 0); // _alreadyAccRewardBal is 0.
rewardData[rewardTokens[iRwd]].accRewardBal += accRewards;
fund.accRewardPerShare[iRwd] += (accRewards * PREC) / fund.totalLiquidity;

Expand Down Expand Up @@ -402,9 +404,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
function getRewardBalance(address _rwdToken) public view returns (uint256) {
RewardData memory rwdData = rewardData[_rwdToken];

if (rwdData.tknManager == address(0)) {
revert InvalidRewardToken();
}
_validateRewardToken(_rwdToken);

uint256 numFunds = rewardFunds.length;
uint256 rewardsAcc = rwdData.accRewardBal;
Expand Down Expand Up @@ -634,7 +634,7 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
/// @notice Function to update reward params for a fund.
/// @param _rwdToken The reward token's address.
/// @param _newRewardRates The new reward rate for the fund (includes the precision).
function _setRewardRate(address _rwdToken, uint256[] memory _newRewardRates) internal {
function _setRewardRate(address _rwdToken, uint128[] memory _newRewardRates) internal {
uint8 id = rewardData[_rwdToken].id;
uint256 numFunds = rewardFunds.length;
if (_newRewardRates.length != numFunds) {
Expand Down Expand Up @@ -736,14 +736,21 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
/// @param _rwdId Id of the reward token.
/// @param _fundId Id of the reward fund.
/// @param _time Time interval for the reward computation.
function _getAccRewards(uint8 _rwdId, uint8 _fundId, uint256 _time) internal view returns (uint256) {
/// @param _alreadyAccRewardBal Already accrued reward balance.
/// @dev `_alreadyAccRewardBal` is useful when this function called from `computeRewards` function.
/// As `computeReward` is a view function and it doesn't update the `accRewardBal` in the `rewardData`.
function _getAccRewards(uint8 _rwdId, uint8 _fundId, uint256 _time, uint256 _alreadyAccRewardBal)
internal
view
returns (uint256)
{
uint256 rewardsPerSec = rewardFunds[_fundId].rewardsPerSec[_rwdId];
if (rewardsPerSec == 0) {
return 0;
}
address rwdToken = rewardTokens[_rwdId];
uint256 rwdSupply = IERC20(rwdToken).balanceOf(address(this));
uint256 rwdAccrued = rewardData[rwdToken].accRewardBal;
uint256 rwdAccrued = rewardData[rwdToken].accRewardBal + _alreadyAccRewardBal;

uint256 rwdBal = 0;
// Calculate the available reward funds in the farm.
Expand Down Expand Up @@ -805,6 +812,14 @@ abstract contract Farm is FarmStorage, Ownable, ReentrancyGuard, Initializable,
}
}

/// @notice Validate the reward token is valid.
/// @param _rwdToken Address of reward token.
function _validateRewardToken(address _rwdToken) internal view {
if (rewardData[_rwdToken].tknManager == address(0)) {
revert InvalidRewardToken();
}
}

/// @notice Get the time elapsed since the last reward accrual.
/// @return time The time elapsed since the last reward accrual.
function _getRewardAccrualTimeElapsed() internal view virtual returns (uint256) {
Expand Down
8 changes: 8 additions & 0 deletions contracts/FarmDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ abstract contract FarmDeployer is Ownable, ReentrancyGuard {

// Custom Errors
error InvalidAddress();
error NewFarmImplementationSameAsOld();

/// @notice Constructor.
/// @param _farmRegistry Address of the Demeter Farm Registry.
Expand All @@ -62,7 +63,14 @@ abstract contract FarmDeployer is Ownable, ReentrancyGuard {
/// @notice Update farm implementation's address.
/// @dev Only callable by the owner.
/// @param _newFarmImplementation New farm implementation's address.
/// @dev Ensure that `_newFarmId` is correct for the new farm implementation.
function updateFarmImplementation(address _newFarmImplementation, string calldata _newFarmId) external onlyOwner {
_validateNonZeroAddr(_newFarmImplementation);

if (farmImplementation == _newFarmImplementation) {
revert NewFarmImplementationSameAsOld();
}

farmId = _newFarmId;
farmImplementation = _newFarmImplementation;

Expand Down
6 changes: 6 additions & 0 deletions contracts/FarmRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ contract FarmRegistry is OwnableUpgradeable {

// Custom Errors.
error DeployerNotRegistered();
error FarmAlreadyRegistered();
error DeployerAlreadyRegistered();
error InvalidDeployerId();
error PrivilegeSameAsDesired();
Expand Down Expand Up @@ -77,9 +78,14 @@ contract FarmRegistry is OwnableUpgradeable {
/// @param _farm Address of the created farm contract
/// @param _creator Address of the farm creator.
function registerFarm(address _farm, address _creator) external {
_validateNonZeroAddr(_farm);
if (!deployerRegistered[msg.sender]) {
revert DeployerNotRegistered();
}
if (farmRegistered[_farm]) {
YashP16 marked this conversation as resolved.
Show resolved Hide resolved
revert FarmAlreadyRegistered();
}

farms.push(_farm);
farmRegistered[_farm] = true;
emit FarmRegistered(_farm, _creator, msg.sender);
Expand Down
4 changes: 4 additions & 0 deletions contracts/features/OperableDeposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ abstract contract OperableDeposit is Farm {

// Custom Errors.
error DecreaseDepositNotPermitted();
error InsufficientLiquidity();

/// @notice Update subscription data of a deposit for increase in liquidity.
/// @param _depositId Unique deposit id for the deposit.
Expand Down Expand Up @@ -120,6 +121,9 @@ abstract contract OperableDeposit is Farm {
if (_amount == 0) {
revert CannotWithdrawZeroAmount();
}
if (_amount > userDeposit.liquidity) {
revert InsufficientLiquidity();
}

if (userDeposit.expiryDate != 0 || userDeposit.cooldownPeriod != 0) {
revert DecreaseDepositNotPermitted();
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IFarm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {RewardData, RewardFund} from "./DataTypes.sol";
interface IFarm {
function updateRewardData(address _rwdToken, address _newTknManager) external;

function setRewardRate(address _rwdToken, uint256[] memory _newRwdRates) external;
function setRewardRate(address _rwdToken, uint128[] memory _newRewardRates) external;

function recoverRewardFunds(address _rwdToken, uint256 _amount) external;

Expand Down
26 changes: 14 additions & 12 deletions contracts/rewarder/Rewarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IOracle} from "../interfaces/IOracle.sol";
import {IFarm} from "../interfaces/IFarm.sol";
import {IRewarderFactory} from "../interfaces/IRewarderFactory.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

/// @title Rewarder contract of Demeter Protocol.
/// @author Sperax Foundation.
Expand All @@ -49,8 +50,8 @@ contract Rewarder is Ownable, Initializable, ReentrancyGuard {
// nonLockupRewardPer - Reward percentage allocation for no lockup fund (rest goes to lockup fund).
struct FarmRewardConfig {
uint256 apr;
uint256 rewardRate;
uint256 maxRewardRate;
uint128 rewardRate;
uint128 maxRewardRate;
uint256[] baseAssetIndexes;
uint256 nonLockupRewardPer; // 5e3 = 50%.
}
Expand All @@ -62,7 +63,7 @@ contract Rewarder is Ownable, Initializable, ReentrancyGuard {
// nonLockupRewardPer - Reward percentage allocation for no lockup fund (rest goes to lockup fund).
struct FarmRewardConfigInput {
uint256 apr;
uint256 maxRewardRate;
uint128 maxRewardRate;
address[] baseTokens;
uint256 nonLockupRewardPer; // 5e3 = 50%.
}
Expand Down Expand Up @@ -272,7 +273,7 @@ contract Rewarder is Ownable, Initializable, ReentrancyGuard {

function _calibrateReward(address _farm) private returns (uint256 rewardsToSend) {
FarmRewardConfig memory farmRewardConfig = farmRewardConfigs[_farm];
uint256 rewardRate;
uint128 rewardRate;
if (farmRewardConfig.apr != 0) {
(address[] memory assets, uint256[] memory amounts) = _getTokenAmounts(_farm);
// Calculating total USD value for all the assets.
Expand All @@ -298,9 +299,10 @@ contract Rewarder is Ownable, Initializable, ReentrancyGuard {
// For token with lower decimals the calculation of rewardRate might not be accurate because of precision loss in truncation.
// rewardValuePerSecond = (APR * totalValue / 100) / 365 days.
// rewardRate = rewardValuePerSecond * pricePrecision / price.
rewardRate = (farmRewardConfig.apr * totalValue * priceData.precision)
/ (APR_PRECISION * DENOMINATOR * ONE_YEAR * priceData.price);

rewardRate = SafeCast.toUint128(
(farmRewardConfig.apr * totalValue * priceData.precision)
/ (APR_PRECISION * DENOMINATOR * ONE_YEAR * priceData.price)
);
if (rewardRate > farmRewardConfig.maxRewardRate) {
rewardRate = farmRewardConfig.maxRewardRate;
}
Expand Down Expand Up @@ -333,15 +335,15 @@ contract Rewarder is Ownable, Initializable, ReentrancyGuard {
/// @param _farm Address of the farm.
/// @param _rwdRate Reward per second to be emitted.
/// @param _nonLockupRewardPer Reward percentage to be allocated to no lockup fund.
function _setRewardRate(address _farm, uint256 _rwdRate, uint256 _nonLockupRewardPer) private {
uint256[] memory _newRewardRates;
function _setRewardRate(address _farm, uint128 _rwdRate, uint256 _nonLockupRewardPer) private {
uint128[] memory _newRewardRates;
if (IFarm(_farm).cooldownPeriod() == 0) {
_newRewardRates = new uint256[](1);
_newRewardRates = new uint128[](1);
_newRewardRates[0] = _rwdRate;
IFarm(_farm).setRewardRate(REWARD_TOKEN, _newRewardRates);
} else {
_newRewardRates = new uint256[](2);
uint256 commonFundShare = (_rwdRate * _nonLockupRewardPer) / MAX_PERCENTAGE;
_newRewardRates = new uint128[](2);
uint128 commonFundShare = SafeCast.toUint128((_rwdRate * _nonLockupRewardPer) / MAX_PERCENTAGE);
_newRewardRates[0] = commonFundShare;
_newRewardRates[1] = _rwdRate - commonFundShare;
IFarm(_farm).setRewardRate(REWARD_TOKEN, _newRewardRates);
Expand Down
Loading
Loading