diff --git a/src/hardhat/contracts/Staking/FraxUnifiedFarmTemplate_V2.sol b/src/hardhat/contracts/Staking/FraxUnifiedFarmTemplate_V2.sol index 7c663467..b1228e9d 100755 --- a/src/hardhat/contracts/Staking/FraxUnifiedFarmTemplate_V2.sol +++ b/src/hardhat/contracts/Staking/FraxUnifiedFarmTemplate_V2.sol @@ -62,6 +62,7 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { error NotOwnerOrTknMgr(); error NotEnoughRewardTokensAvailable(address); error TooManyStakes(); + error NotARewardToken(); /* ========== STATE VARIABLES ========== */ @@ -70,13 +71,16 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { // Frax related address internal constant frax_address = 0x853d955aCEf822Db058eb8505911ED77F175b99e; - uint256 public fraxPerLPStored; // fraxPerLPToken is a public view function, although doesn't show the stored value + /// @notice fraxPerLPToken is a public view function, although doesn't show the stored value + uint256 public fraxPerLPStored; // Constant for various precisions uint256 internal constant MULTIPLIER_PRECISION = 1e18; // Time tracking + /// @notice Ending timestamp for the current period uint256 public periodFinish; + /// @notice Timestamp of the last update - when this period started uint256 public lastUpdateTime; // Lock time and multiplier settings @@ -96,13 +100,15 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { mapping(address => mapping(address => bool)) internal proxy_allowed_stakers; // Reward addresses, gauge addresses, reward rates, and reward managers - mapping(address => address) public rewardManagers; // token addr -> manager addr + /// @notice token addr -> manager addr + mapping(address => address) public rewardManagers; address[] internal rewardTokens; address[] internal gaugeControllers; address[] internal rewardDistributors; uint256[] internal rewardRatesManual; mapping(address => bool) internal isRewardToken; - mapping(address => uint256) public rewardTokenAddrToIdx; // token addr -> token index + /// @notice token addr -> token index + mapping(address => uint256) public rewardTokenAddrToIdx; // Reward period uint256 public constant rewardsDuration = 604800; // 7 * 86400 (7 days) @@ -122,11 +128,13 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { uint256 internal _total_combined_weight; mapping(address => uint256) internal _locked_liquidity; mapping(address => uint256) internal _combined_weights; - mapping(address => uint256) public proxy_lp_balances; // Keeps track of LP balances proxy-wide. Needed to make sure the proxy boost is kept in line + /// @notice Keeps track of LP balances proxy-wide. Needed to make sure the proxy boost is kept in line + mapping(address => uint256) public proxy_lp_balances; - // Stakers set which proxy(s) they want to use - mapping(address => address) public staker_designated_proxies; // Keep public so users can see on the frontend if they have a proxy + /// @notice Stakers set which proxy(s) they want to use + /// @dev Keep public so users can see on the frontend if they have a proxy + mapping(address => address) public staker_designated_proxies; // Admin booleans for emergencies and overrides bool public stakesUnlocked; // Release locked stakes in case of emergency @@ -211,7 +219,10 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { // ------ REWARD RELATED ------ - // See if the caller_addr is a manager for the reward token + /// @notice Checks if the caller is a manager for the reward token + /// @param caller_addr The address of the caller + /// @param reward_token_addr The address of the reward token + /// @return bool True if the caller is a manager for the reward token function isTokenManagerFor(address caller_addr, address reward_token_addr) public view returns (bool){ if (!isRewardToken[reward_token_addr]) return false; else if (caller_addr == address(0) || reward_token_addr == address(0)) return false; @@ -220,7 +231,8 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { return false; } - // All the reward tokens + /// @notice Get's all the reward tokens this contract handles + /// @return rewardTokens_ The reward tokens array function getAllRewardTokens() external view returns (address[] memory) { return rewardTokens; } @@ -230,6 +242,9 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { return Math.min(block.timestamp, periodFinish); } + /// @notice The amount of reward tokens being paid out per second this period + /// @param token_idx The index of the reward token + /// @return rwd_rate The reward rate function rewardRates(uint256 token_idx) public view returns (uint256 rwd_rate) { // address gauge_controller_address = gaugeControllers[token_idx]; if (gaugeControllers[token_idx] != address(0)) { @@ -243,7 +258,8 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } - // Amount of reward tokens per LP token / liquidity unit + /// @notice The rate of reward tokens earned per liquidity unit + /// @return newRewardsPerTokenStored The new rewards per token stored array function rewardsPerToken() public view returns (uint256[] memory newRewardsPerTokenStored) { if (_total_liquidity_locked == 0 || _total_combined_weight == 0) { return rewardsPerTokenStored; @@ -259,9 +275,10 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } - // Amount of reward tokens an account has earned / accrued - // Note: In the edge-case of one of the account's stake expiring since the last claim, this will - // return a slightly inflated number + /// @notice The amount of reward tokens an account has earned / accrued + /// @dev In the edge-case of one of the account's stake expiring since the last claim, this will + /// @param account The account to check + /// @return new_earned Array of reward token amounts earned by the account function earned(address account) public view returns (uint256[] memory new_earned) { uint256[] memory reward_arr = rewardsPerToken(); new_earned = new uint256[](rewardTokens.length); @@ -277,7 +294,8 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } - // Total reward tokens emitted in the given period + /// @notice The total reward tokens emitted in the given period + /// @return rewards_per_duration_arr Array of reward token amounts emitted in the current period function getRewardForDuration() external view returns (uint256[] memory rewards_per_duration_arr) { rewards_per_duration_arr = new uint256[](rewardRatesManual.length); @@ -289,28 +307,36 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { // ------ LIQUIDITY AND WEIGHTS ------ - // User locked liquidity / LP tokens + /// @notice The farm's total locked liquidity / LP tokens + /// @return The total locked liquidity function totalLiquidityLocked() external view returns (uint256) { return _total_liquidity_locked; } - // Total locked liquidity / LP tokens + /// @notice A user's locked liquidity / LP tokens + /// @param account The address of the account + /// @return The locked liquidity function lockedLiquidityOf(address account) external view returns (uint256) { return _locked_liquidity[account]; } - // Total combined weight + /// @notice The farm's total combined weight of all users + /// @return The total combined weight function totalCombinedWeight() external view returns (uint256) { return _total_combined_weight; } - // Total 'balance' used for calculating the percent of the pool the account owns - // Takes into account the locked stake time multiplier and veFXS multiplier + /// @notice Total 'balance' used for calculating the percent of the pool the account owns + /// @notice Takes into account the locked stake time multiplier and veFXS multiplier + /// @param account The address of the account + /// @return The combined weight function combinedWeightOf(address account) external view returns (uint256) { return _combined_weights[account]; } - // Calculated the combined weight for an account + /// @notice Calculates the combined weight for an account + /// @notice Must be overriden by the child contract + /// @dev account The address of the account function calcCurCombinedWeight(address) public virtual view returns ( uint256, @@ -322,14 +348,10 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } // ------ LOCK RELATED ------ - // Multiplier amount, given the length of the lock + /// @notice Reads the lock boost multiplier for a given duration + /// @param secs The duration of the lock in seconds + /// @return The multiplier amount function lockMultiplier(uint256 secs) public view returns (uint256) { - // return Math.min( - // lock_max_multiplier, - // uint256(MULTIPLIER_PRECISION) + ( - // (secs * (lock_max_multiplier - MULTIPLIER_PRECISION)) / lock_time_for_max_multiplier - // ) - // ) ; return Math.min( lock_max_multiplier, (secs * lock_max_multiplier) / lock_time_for_max_multiplier @@ -338,34 +360,52 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { // ------ FRAX RELATED ------ + /// @notice The amount of FRAX denominated value being boosted that an address has staked + /// @param account The address to check + /// @return The amount of FRAX value boosted function userStakedFrax(address account) public view returns (uint256) { return (fraxPerLPStored * _locked_liquidity[account]) / MULTIPLIER_PRECISION; } + /// @notice The amount of FRAX denominated value being boosted that a proxy address has staked + /// @param proxy_address The address to check + /// @return The amount of FRAX value boosted function proxyStakedFrax(address proxy_address) public view returns (uint256) { return (fraxPerLPStored * proxy_lp_balances[proxy_address]) / MULTIPLIER_PRECISION; } - // Max LP that can get max veFXS boosted for a given address at its current veFXS balance + /// @notice The maximum LP that can get max veFXS boosted for a given address at its current veFXS balance + /// @param account The address to check + /// @return The maximum LP that can get max veFXS boosted for a given address at its current veFXS balance function maxLPForMaxBoost(address account) external view returns (uint256) { return (veFXS.balanceOf(account) * MULTIPLIER_PRECISION * MULTIPLIER_PRECISION) / (vefxs_per_frax_for_max_boost * fraxPerLPStored); } - // Meant to be overridden + /// @notice Must be overriden to return the current FRAX per LP token + /// @return The current number of FRAX per LP token function fraxPerLPToken() public virtual view returns (uint256) { revert NeedsFPLPTLogic(); } // ------ veFXS RELATED ------ + /// @notice The minimum veFXS required to get max boost for a given address + /// @param account The address to check + /// @return The minimum veFXS required to get max boost function minVeFXSForMaxBoost(address account) public view returns (uint256) { return (userStakedFrax(account) * vefxs_per_frax_for_max_boost) / MULTIPLIER_PRECISION; } + /// @notice The minimum veFXS required to get max boost for a given proxy + /// @param proxy_address The proxy address + /// @return The minimum veFXS required to get max boost function minVeFXSForMaxBoostProxy(address proxy_address) public view returns (uint256) { return (proxyStakedFrax(proxy_address) * vefxs_per_frax_for_max_boost) / MULTIPLIER_PRECISION; } + /// @notice Looks up a staker's proxy + /// @param addr The address to check + /// @return the_proxy The proxy address, or address(0) function getProxyFor(address addr) public view returns (address){ if (valid_vefxs_proxies[addr]) { // If addr itself is a proxy, return that. @@ -378,6 +418,9 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } + /// @notice The multiplier for a given account, based on veFXS + /// @param account The account to check + /// @return vefxs_multiplier The multiplier boost for the account function veFXSMultiplier(address account) public view returns (uint256 vefxs_multiplier) { // Use either the user's or their proxy's veFXS balance // uint256 vefxs_bal_to_use = 0; @@ -415,10 +458,8 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { /* =============== MUTATIVE FUNCTIONS =============== */ - - // Proxy can allow a staker to use their veFXS balance (the staker will have to reciprocally toggle them too) - // Must come before stakerSetVeFXSProxy - // CALLED BY PROXY + /// @notice Toggle whether a staker can use the proxy's veFXS balance to boost yields + /// @notice Proxy must call this first, then the staker must call stakerSetVeFXSProxy function proxyToggleStaker(address staker_address) external { if(!valid_vefxs_proxies[msg.sender]) revert InvalidProxy(); @@ -433,8 +474,8 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } - // Staker can allow a veFXS proxy (the proxy will have to toggle them first) - // CALLED BY STAKER + /// @notice After proxy toggles staker to true, staker must call and confirm this + /// @param proxy_address The address of the veFXS proxy function stakerSetVeFXSProxy(address proxy_address) external { if(!valid_vefxs_proxies[proxy_address]) revert InvalidProxy(); if(!proxy_allowed_stakers[proxy_address][msg.sender]) revert ProxyHasNotApprovedYou(); @@ -528,8 +569,10 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { // ------ REWARDS CLAIMING ------ + /// @notice A function that can be overridden to add extra logic to the getReward function + /// @param destination_address The address to send the rewards to function getRewardExtraLogic(address destination_address) public nonReentrant { - if(rewardsCollectionPaused == true) revert RewardsCollectionPaused(); + if(rewardsCollectionPaused) revert RewardsCollectionPaused(); return _getRewardExtraLogic(msg.sender, destination_address); } @@ -539,16 +582,26 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } /// @notice A function that can be overridden to add extra logic to the pre-transfer process to process curve LP rewards + /// @dev param0: from The sender address of the transfer + /// @dev param1: to The recipient address of the transfer + /// @dev Override in children function preTransferProcess(address, address) public virtual { revert NeedsPreTransferProcessLogic(); } // Two different getReward functions are needed because of delegateCall and msg.sender issues // For backwards-compatibility + /// @notice Claims rewards to destination address + /// @param destination_address The address to send the rewards to + /// @return rewards_before The rewards available before the claim function getReward(address destination_address) external nonReentrant returns (uint256[] memory) { return _getReward(msg.sender, destination_address, true); } + /// @notice Claims rewards to destination address & wether to do extra logic + /// @param destination_address The address to send the rewards to + /// @param claim_extra_too Whether to do extra logic + /// @return rewards_before The rewards available before the claim function getReward2(address destination_address, bool claim_extra_too) external nonReentrant returns (uint256[] memory) { return _getReward(msg.sender, destination_address, claim_extra_too); } @@ -563,7 +616,7 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { lastRewardClaimTime[rewardee] = block.timestamp; // Make sure rewards collection isn't paused - if(rewardsCollectionPaused == true) revert RewardsCollectionPaused(); + if(rewardsCollectionPaused) revert RewardsCollectionPaused(); // Update the rewards array and distribute rewards rewards_before = new uint256[](rewardTokens.length); @@ -661,6 +714,8 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { lastUpdateTime = lastTimeRewardApplicable(); } + /// @notice Updates the gauge weights, if applicable + /// @param force_update If true, will update the weights even if the time hasn't elapsed function sync_gauge_weights(bool force_update) public { // Loop through the gauge controllers for (uint256 i; i < gaugeControllers.length; i++){ @@ -678,6 +733,7 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } + /// @notice Updates gauge weights, fraxPerLP, pulls in new rewards or updates rewards function sync() public { // Sync the gauge weight, if applicable sync_gauge_weights(false); @@ -700,6 +756,11 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { // ------ PAUSES ------ + /// @notice Owner or governance can pause/unpause staking, withdrawals, rewards collection, and collectRewardsOnWithdrawal + /// @param _stakingPaused Whether staking is paused + /// @param _withdrawalsPaused Whether withdrawals are paused + /// @param _rewardsCollectionPaused Whether rewards collection is paused + /// @param _collectRewardsOnWithdrawalPaused Whether collectRewardsOnWithdrawal is paused function setPauses( bool _stakingPaused, bool _withdrawalsPaused, @@ -714,16 +775,20 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { /* ========== RESTRICTED FUNCTIONS - Owner or timelock only ========== */ + /// @notice Owner or governance can unlock stakes - irreversible! function unlockStakes() external onlyByOwnGov { stakesUnlocked = !stakesUnlocked; } - // Adds a valid veFXS proxy address + /// @notice Owner or governance sets whether an address is a valid veFXS proxy + /// @param _proxy_addr The address to set function toggleValidVeFXSProxy(address _proxy_addr) external onlyByOwnGov { valid_vefxs_proxies[_proxy_addr] = !valid_vefxs_proxies[_proxy_addr]; } - // Added to support recovering LP Rewards and other mistaken tokens from other systems to be distributed to holders + /// @notice Allows owner to recover any ERC20 or token manager to recover their reward token. + /// @param tokenAddress The address of the token to recover + /// @param tokenAmount The amount of the token to recover function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyTknMgrs(tokenAddress) { // Check if the desired token is a reward token bool isRewTkn = isRewardToken[tokenAddress]; @@ -744,6 +809,15 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } } + /// @notice Sets multiple variables at once + /// @param _misc_vars The variables to set: + /// [0]: uint256 _lock_max_multiplier, + /// [1] uint256 _vefxs_max_multiplier, + /// [2] uint256 _vefxs_per_frax_for_max_boost, + /// [3] uint256 _vefxs_boost_scale_factor, + /// [4] uint256 _lock_time_for_max_multiplier, + /// [5] uint256 _lock_time_min + /// [6] uint256 _max_stake_limit (must be at greater or equal to old value) function setMiscVariables( uint256[7] memory _misc_vars // [0]: uint256 _lock_max_multiplier, @@ -776,19 +850,27 @@ contract FraxUnifiedFarmTemplate_V2 is OwnedV2, ReentrancyGuardV2 { } // The owner or the reward token managers can set reward rates + /// @notice Allows owner or reward token managers to set the reward rate for a given reward token + /// @param reward_token_address The address of the reward token + /// @param _new_rate The new reward rate (token amount divided by reward period duration) + /// @param _gauge_controller_address The address of the gauge controller for this reward token + /// @param _rewards_distributor_address The address of the rewards distributor for this reward token function setRewardVars( address reward_token_address, uint256 _new_rate, address _gauge_controller_address, address _rewards_distributor_address ) external onlyTknMgrs(reward_token_address) { - require(isRewardToken[reward_token_address], 'Not a reward token'); + if (!isRewardToken[reward_token_address]) revert NotARewardToken(); rewardRatesManual[rewardTokenAddrToIdx[reward_token_address]] = _new_rate; gaugeControllers[rewardTokenAddrToIdx[reward_token_address]] = _gauge_controller_address; rewardDistributors[rewardTokenAddrToIdx[reward_token_address]] = _rewards_distributor_address; } // The owner or the reward token managers can change managers + /// @notice Allows owner or reward token managers to change the reward manager for a given reward token + /// @param reward_token_address The address of the reward token + /// @param new_manager_address The new reward manager address function changeTokenManager(address reward_token_address, address new_manager_address) external onlyTknMgrs(reward_token_address) { rewardManagers[reward_token_address] = new_manager_address; } diff --git a/src/hardhat/contracts/Staking/FraxUnifiedFarm_ERC20_V2.sol b/src/hardhat/contracts/Staking/FraxUnifiedFarm_ERC20_V2.sol index 6d6db832..5226287f 100644 --- a/src/hardhat/contracts/Staking/FraxUnifiedFarm_ERC20_V2.sol +++ b/src/hardhat/contracts/Staking/FraxUnifiedFarm_ERC20_V2.sol @@ -119,7 +119,7 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { uint256 start_timestamp; uint256 liquidity; uint256 ending_timestamp; - uint256 lock_multiplier; // 6 decimals of precision. 1x = 1000000 + uint256 lock_multiplier; } @@ -179,6 +179,8 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // ------ FRAX RELATED ------ + /// @notice Get the amount of FRAX 'inside' an LP token + /// @dev Override if needing to do something special function fraxPerLPToken() public virtual view override returns (uint256) { // Get the amount of FRAX 'inside' of the lp tokens uint256 frax_per_lp_token; @@ -274,6 +276,10 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // ------ LIQUIDITY AND WEIGHTS ------ + /// @notice Calculates the current lock multiplier for a given account's locked stake + /// @param account The account to check + /// @param stake_idx The index of the stake to check + /// @return midpoint_lock_multiplier The midpoint of the user's stake's lock multiplier function calcCurrLockMultiplier(address account, uint256 stake_idx) public view returns (uint256 midpoint_lock_multiplier) { // Get the stake LockedStake memory thisStake = lockedStakes[account][stake_idx]; @@ -329,6 +335,11 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { } // Calculate the combined weight for an account + /// @notice Calculates the combined weight for an account + /// @param account The account to check + /// @return old_combined_weight The account's old combined weight + /// @return new_vefxs_multiplier The account's new veFXS multiplier + /// @return new_combined_weight The account's new combined weight function calcCurCombinedWeight(address account) public override view returns ( uint256 old_combined_weight, @@ -374,22 +385,34 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // ------ LOCK RELATED ------ - // All the locked stakes for a given account + /// @notice Returns the locked stakes for a given account + /// @param account The address of the account + /// @return The array of locked stakes for a given account function lockedStakesOf(address account) external view returns (LockedStake[] memory) { return lockedStakes[account]; } - // Returns the length of the locked stakes for a given account + /// @notice Returns the length of the locked stakes for a given account + /// @param account The address of the account + /// @return The length of the locked stakes for a given account function lockedStakesOfLength(address account) external view returns (uint256) { return lockedStakes[account].length; } + /// @notice Returns the locked stake at a given index + /// @param staker The address of the staker + /// @param locked_stake_index The index of the locked stake + /// @return locked_stake The locked stake struct function getLockedStake(address staker, uint256 locked_stake_index) public view returns (LockedStake memory locked_stake) { return(lockedStakes[staker][locked_stake_index]); } /// @notice Returns the liquidity and ending timestamp of a locked stake + /// @param staker The address of the staker + /// @param locked_stake_index The index of the locked stake + /// @return The liquidity of the locked stake + /// @return The ending timestamp of the locked stake function getStakeLiquidityAndEnding(address staker, uint256 locked_stake_index) external view returns (uint256,uint256) { return( lockedStakes[staker][locked_stake_index].liquidity, @@ -400,6 +423,11 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { /* =============== MUTATIVE FUNCTIONS =============== */ // ------ STAKING ------ + + /// @notice Searches for previous used, but currently zeroed out stake positions within the array + /// @param staker The address of the staker + /// @return The index of the unused stake position + /// @return Whether or not an unused stake position was found function _findUnusedStakeIndex(address staker) internal view returns (uint256,bool) { uint256 i; while (i < lockedStakes[staker].length) { @@ -411,14 +439,33 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { return (i,false); } + /// @notice Updates a stake for a given staker + /// @param staker The address of the staker + /// @param index The index of the stake + /// @param start_timestamp The timestamp of when the stake started + /// @param liquidity The amount of liquidity to stake + /// @param ending_timestamp The timestamp of when the stake ends + /// @param lock_multiplier The multiplier of the stake function _updateStake(address staker, uint256 index, uint256 start_timestamp, uint256 liquidity, uint256 ending_timestamp, uint256 lock_multiplier) internal { lockedStakes[staker][index] = LockedStake(start_timestamp, liquidity, ending_timestamp, lock_multiplier); } - function _createNewStake(address staker, uint256 start_timestamp, uint256 liquidity, uint256 ending_timestamp, uint256 lock_multiplier) internal { + /// @notice Creates a new stake for a given staker + /// @param staker The address of the staker + /// @param start_timestamp The timestamp of when the stake started + /// @param liquidity The amount of liquidity to stake + /// @param ending_timestamp The timestamp of when the stake ends + /// @param lock_multiplier The multiplier of the stake + /// @return The index of the new stake + function _createNewStake(address staker, uint256 start_timestamp, uint256 liquidity, uint256 ending_timestamp, uint256 lock_multiplier) internal returns(uint256) { lockedStakes[staker].push(LockedStake(start_timestamp, liquidity, ending_timestamp, lock_multiplier)); + return lockedStakes[staker].length - 1; } + /// @notice Update's the global & proxy liquidity amounts, and checkpoint's user's rewards/balances + /// @param staker_address The address of the staker + /// @param amt The amount of liquidity to add or remove + /// @param is_add Whether to add or remove liquidity function _updateLiqAmts(address staker_address, uint256 amt, bool is_add) internal { // Get the proxy address address the_proxy = staker_designated_proxies[staker_address]; @@ -443,7 +490,7 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { } // Need to call to update the combined weights - updateRewardAndBalance(staker_address, false); + _updateRewardAndBalance(staker_address, false); } // // Add additional LPs to an existing locked stake @@ -532,12 +579,13 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { /// @param source_address The address of the source of the liquidity. /// @param liquidity The amount of liquidity to stake. /// @param secs The number of seconds to lock the liquidity for. + /// @param useTargetStakeIndex If true, alter certain parameters of an existing stake. If false, create a new stake. + /// @param targetIndex The index of the stake to alter, if applicable. function _manageStake( address staker_address, address source_address, uint256 liquidity, uint256 secs, - // uint256 start_timestamp, bool useTargetStakeIndex, uint256 targetIndex ) internal updateRewardAndBalanceMdf(staker_address, true) returns (uint256 stakeIndex) { @@ -555,19 +603,15 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // AND if there's enough stake space, create a new one, otherwise check for zeroed out stakes if (!useTargetStakeIndex && lockedStakes[staker_address].length < max_locked_stakes) { // Create the locked stake - _createNewStake( + stakeIndex = _createNewStake( staker_address, block.timestamp, liquidity, block.timestamp + secs, lockMultiplier(secs) ); - - // set the return value to the index of the new stake - stakeIndex = lockedStakes[staker_address].length - 1; // check that the number of this address' stakes are not over the limit - // if (lockedStakes[staker_address].length > max_locked_stakes) revert TooManyStakes(); } else if (!useTargetStakeIndex && lockedStakes[staker_address].length == max_locked_stakes) { // look for unused stakes that were previously used but zeroed out by withdrawals or transfers (uint256 index, bool success) = _findUnusedStakeIndex(staker_address); @@ -613,19 +657,18 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // set the return value to the index of the stake we altered stakeIndex = targetIndex; - - // no need to check the number of stakes here because we are not creating a new stake } // if altering balances of a stake, update the liquidities + // liquidity can be 0 if we are only extending the lock duration if (liquidity > 0) { // Update liquidities if we are creating a new stake or locking additional + // this also runs `_updateRewardAndBalance` for the staker _updateLiqAmts(staker_address, liquidity, true); + } else { + // otherwise, only update rewards and balances + _updateRewardAndBalance(msg.sender, false); } - - // Whether updating durations, creating new stake, or locking additional, update the rewards & balances - updateRewardAndBalance(msg.sender, false); - emit StakeLocked(staker_address, liquidity, secs, lockedStakes[staker_address].length - 1, source_address); return stakeIndex; @@ -633,9 +676,13 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // ------ WITHDRAWING ------ - // Two different withdrawLocked functions are needed because of delegateCall and msg.sender issues (important for proxies) + /// @notice Withdraws a locked stake. + /// @notice This function is only callable by the staker. + /// @param theArrayIndex The index of the stake in the staker's array of stakes. + /// @param destination_address The address to send the withdrawn liquidity to. + /// @return The amount of liquidity withdrawn. function withdrawLocked(uint256 theArrayIndex, address destination_address) nonReentrant external returns (uint256) { - if (withdrawalsPaused == true) revert WithdrawalsPaused(); + if (withdrawalsPaused) revert WithdrawalsPaused(); return _withdrawLocked(msg.sender, destination_address, theArrayIndex); } @@ -687,6 +734,9 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // Approve `spender` to transfer `lockedStake` on behalf of `owner` /// @notice Used to increase allowance when it is at zero /// @dev separating this from the `increaseAllowance` function to avoid the allowance exploit + /// @param spender The address of the account that is allowed to transfer the tokens + /// @param lockedStakeIndex The index of the locked stake + /// @param amount The amount of tokens to be approved for transfer function setAllowance(address spender, uint256 lockedStakeIndex, uint256 amount) external { if(spenderAllowance[msg.sender][lockedStakeIndex][spender] > 0) revert CannotBeZero(); spenderAllowance[msg.sender][lockedStakeIndex][spender] = amount; @@ -695,6 +745,9 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { /// @notice Used to increase allowance when it is not at zero /// @dev separating this from the `setAllowance` function to avoid the allowance exploit + /// @param spender The address of the account that is allowed to transfer the tokens + /// @param lockedStakeIndex The index of the locked stake + /// @param amount The amount of tokens to be approved for transfer function increaseAllowance(address spender, uint256 lockedStakeIndex, uint256 amount) external { if (spenderAllowance[msg.sender][lockedStakeIndex][spender] == 0) revert AllowanceIsZero(); spenderAllowance[msg.sender][lockedStakeIndex][spender] += amount; @@ -702,6 +755,8 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { } /// @notice Revoke approval for a single lockedStake + /// @param spender The address of the account that is allowed to transfer the tokens + /// @param lockedStakeIndex The index of the locked stake function removeAllowance(address spender, uint256 lockedStakeIndex) external { spenderAllowance[msg.sender][lockedStakeIndex][spender] = 0; emit Approval(msg.sender, spender, lockedStakeIndex, 0); @@ -709,6 +764,8 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // Approve or revoke `spender` to transfer any/all locks on behalf of the owner /// @notice Set's `spender` approval for all locks: true=approve, false=remove approval + /// @param spender The address of the account that is allowed to transfer the tokens + /// @param approved The approval status function setApprovalForAll(address spender, bool approved) external { spenderApprovalForAllLocks[msg.sender][spender] = approved; emit ApprovalForAll(msg.sender, spender, approved); @@ -764,9 +821,6 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { bool use_receiver_lock_index, uint256 receiver_lock_index ) external nonReentrant returns (uint256,uint256) { - // check approvals NOTE not needed as _spendAllowance does this also - // if (!isApproved(sender_address, sender_lock_index, transfer_amount)) revert TransferLockNotAllowed(msg.sender, sender_lock_index); - /// if spender is not approved for all, spend allowance, otherwise, carry on if(!spenderApprovalForAllLocks[sender_address][msg.sender]) { // adjust the allowance down & performs checks @@ -845,28 +899,20 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { _updateLiqAmts(addrs[0], transfer_amount, false); /** if use_receiver_lock_index is true & the incoming stake wouldn't extend the receiver's stake - * & the index is valid - * & has liquidity - * & is still locked, update the stake & ending timestamp (longer of the two) - * else, create a new lockedStake - * note using nested if checks to reduce gas costs slightly + * - update the liquidity by transfer_amount + * else: + * - If max stakes is reached: + * - look for an unused stake to update with new values + * - or if none available, revert + * - Otherwise, user has available stake capacity, so create a new lockedStake */ if ( use_receiver_lock_index && senderStake.ending_timestamp <= lockedStakes[addrs[1]][receiver_lock_index].ending_timestamp ) { - // Get the stake and its index - LockedStake memory receiverStake = getLockedStake(addrs[1], receiver_lock_index); - - if (receiver_lock_index < lockedStakes[addrs[1]].length) { - if (receiverStake.liquidity > 0) { - if (receiverStake.ending_timestamp > block.timestamp) { - // Update the existing staker's stake liquidity - lockedStakes[addrs[1]][receiver_lock_index].liquidity += transfer_amount; - } - } - } + // Adjust the locked stake's liquidity by transfer_amount + lockedStakes[addrs[1]][receiver_lock_index].liquidity += transfer_amount; } else { // if receiver would have too many stakes to create a new one, look for zeroed out stakes if (lockedStakes[addrs[1]].length == max_locked_stakes) { @@ -892,16 +938,13 @@ contract FraxUnifiedFarm_ERC20_V2 is FraxUnifiedFarmTemplate_V2 { // otherwise, create a new locked stake } else { // create the new lockedStake - _createNewStake( + receiver_lock_index = _createNewStake( addrs[1], senderStake.start_timestamp, transfer_amount, senderStake.ending_timestamp, senderStake.lock_multiplier ); - - // update the return value of the locked index - receiver_lock_index = lockedStakes[addrs[1]].length - 1; } }