diff --git a/contracts/Distributor.sol b/contracts/Distributor.sol index c9e9c96..7885ad1 100644 --- a/contracts/Distributor.sol +++ b/contracts/Distributor.sol @@ -9,11 +9,6 @@ import { IDistributor } from "./interfaces/IDistributor.sol"; abstract contract Distributor is IDistributor, Ownable { address internal _distributor; - event DistributorChanged(address newDistributor); - - error NotDistributor(); - error ZeroDistributorAddress(); - modifier onlyDistributor { if (msg.sender != _distributor) revert NotDistributor(); _; diff --git a/contracts/FarmingLib.sol b/contracts/FarmingLib.sol index 16ecc6b..bc35583 100644 --- a/contracts/FarmingLib.sol +++ b/contracts/FarmingLib.sol @@ -25,7 +25,7 @@ library FarmingLib { } /** - * @notice Creates a new Info struct. + * @notice Creates a new FarmingLib.Info struct. * @param getTotalSupply The function to get the total supply. * @param data The data struct for storage. * @return info The created Info struct. @@ -40,8 +40,8 @@ library FarmingLib { } /** - * @notice Retrieves the Data struct from an Info struct. - * @param self The Info struct. + * @notice Retrieves the FarmingLib.Data struct from an FarmingLib.Info struct. + * @param self The Info struct to retrieve data from storage. * @return data The retrieved Data struct. */ function getData(Info memory self) internal pure returns(Data storage data) { @@ -52,11 +52,11 @@ library FarmingLib { } /** - * @notice Begins farming for a specified period. - * @param self The Info struct. - * @param amount The amount to farm. - * @param period The farming period. - * @return reward The farming reward. + * @notice Updates farming info with new amount and specified period. + * @param self The FarmingLib.Info struct to retrieve data from storage. + * @param amount A new amount to farm. + * @param period A new farming period. + * @return reward Updated farming reward. */ function startFarming(Info memory self, uint256 amount, uint256 period) internal returns(uint256 reward) { Data storage data = self.getData(); @@ -76,22 +76,22 @@ library FarmingLib { } /** - * @notice Gets the farmed amount for an account. - * @param self The Info struct. - * @param account The account to check. - * @param balance The account balance. - * @return result The farmed amount. + * @notice Gets the amount of farmed reward tokens for an account. + * @param self The FarmingLib.Info struct to retrieve data from storage. + * @param account The address of the account to check. + * @param balance The farmable token balance of the account. + * @return result The number of tokens farmed. */ function farmed(Info memory self, address account, uint256 balance) internal view returns(uint256) { return self.getData().userInfo.farmed(account, balance, _farmedPerToken(self)); } /** - * @notice Claims the farmed amount for an account. - * @param self The Info struct. - * @param account The account to claim for. - * @param balance The account balance. - * @return amount The claimed amount. + * @notice Claims the farmed reward tokens for an account. + * @param self The FarmingLib.Info struct to retrieve data from storage. + * @param account The address of the account to claim for. + * @param balance The account balance of farmable tokens. + * @return amount The claimed amount of reward tokens. */ function claim(Info memory self, address account, uint256 balance) internal returns(uint256 amount) { Data storage data = self.getData(); @@ -104,10 +104,10 @@ library FarmingLib { } /** - * @notice Updates the balances of two accounts. - * @param self The Info struct. - * @param from The account to transfer from. - * @param to The account to transfer to. + * @notice Updates the farmable token balances of two accounts. + * @param self The FarmingLib.Info struct to retrieve data from storage. + * @param from The address of the account to transfer from. + * @param to The address of the account to transfer to. * @param amount The amount to transfer. */ function updateBalances(Info memory self, address from, address to, uint256 amount) internal { diff --git a/contracts/FarmingPlugin.sol b/contracts/FarmingPlugin.sol index a4aac7d..b9805dc 100644 --- a/contracts/FarmingPlugin.sol +++ b/contracts/FarmingPlugin.sol @@ -13,16 +13,17 @@ import { IFarmingPlugin } from "./interfaces/IFarmingPlugin.sol"; import { Distributor } from "./Distributor.sol"; import { FarmingLib, FarmAccounting } from "./FarmingLib.sol"; +/** + * @title Implementation of the {IFarmingPlugin} interface. + * @notice This contract only accounts for the balances of users + * who added it as a plugin to the farmable token. + */ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { using SafeERC20 for IERC20; using FarmingLib for FarmingLib.Info; using FarmAccounting for FarmAccounting.Info; using Address for address payable; - error ZeroFarmableTokenAddress(); - error ZeroRewardsTokenAddress(); - error InsufficientFunds(); - IERC20 public immutable rewardsToken; uint256 private _totalSupply; @@ -37,20 +38,32 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { emit FarmCreated(address(farmableToken_), address(rewardsToken_)); } + /** + * @notice See {IFarmingPlugin-farmInfo} + */ function farmInfo() public view returns(FarmAccounting.Info memory) { return _farm.farmInfo; } + /** + * @notice See {IFarmingPlugin-totalSupply} + */ function totalSupply() public view returns(uint256) { return _totalSupply; } + /** + * @notice See {IFarmingPlugin-startFarming} + */ function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { uint256 reward = _makeInfo().startFarming(amount, period); emit RewardUpdated(reward, period); rewardsToken.safeTransferFrom(msg.sender, address(this), amount); } + /** + * @notice See {IFarmingPlugin-stopFarming} + */ function stopFarming() public virtual onlyDistributor { uint256 leftover = _makeInfo().stopFarming(); emit RewardUpdated(0, 0); @@ -59,11 +72,17 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { } } + /** + * @notice See {IFarmingPlugin-farmed} + */ function farmed(address account) public view virtual returns(uint256) { uint256 balance = IERC20Plugins(token).pluginBalanceOf(address(this), account); return _makeInfo().farmed(account, balance); } + /** + * @notice See {IFarmingPlugin-claim} + */ function claim() public virtual { uint256 pluginBalance = IERC20Plugins(token).pluginBalanceOf(address(this), msg.sender); uint256 amount = _makeInfo().claim(msg.sender, pluginBalance); @@ -86,6 +105,9 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { } } + /** + * @notice See {IFarmingPlugin-rescueFunds} + */ function rescueFunds(IERC20 token_, uint256 amount) public virtual onlyDistributor { if(token_ == IERC20(address(0))) { payable(_distributor).sendValue(amount); diff --git a/contracts/FarmingPool.sol b/contracts/FarmingPool.sol index 4c0db93..2ac18ad 100644 --- a/contracts/FarmingPool.sol +++ b/contracts/FarmingPool.sol @@ -12,18 +12,16 @@ import { IFarmingPool } from "./interfaces/IFarmingPool.sol"; import { Distributor } from "./Distributor.sol"; import { FarmAccounting, FarmingLib } from "./FarmingLib.sol"; +/** + * @title Implementation of the {IFarmingPool} interface. + * @notice This contract accounts for the balance of the farmable token's deposits through + * its own balance as it is inherited from ERC20. + */ contract FarmingPool is IFarmingPool, Distributor, ERC20 { using SafeERC20 for IERC20; using Address for address payable; using FarmingLib for FarmingLib.Info; - error SameStakingAndRewardsTokens(); - error ZeroStakingTokenAddress(); - error ZeroRewardsTokenAddress(); - error AccessDenied(); - error InsufficientFunds(); - error MaxBalanceExceeded(); - uint256 internal constant _MAX_BALANCE = 1e32; IERC20 public immutable stakingToken; @@ -44,20 +42,32 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { rewardsToken = rewardsToken_; } + /** + * @notice See {IERC20Metadata-decimals} + */ function decimals() public view virtual override returns (uint8) { return IERC20Metadata(address(stakingToken)).decimals(); } + /** + * @notice See {IFarmingPool-farmInfo} + */ function farmInfo() public view returns(FarmAccounting.Info memory) { return _farm.farmInfo; } + /** + * @notice See {IFarmingPool-startFarming} + */ function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { uint256 reward = _makeInfo().startFarming(amount, period); emit RewardUpdated(reward, period); rewardsToken.safeTransferFrom(msg.sender, address(this), amount); } + /** + * @notice See {IFarmingPool-stopFarming} + */ function stopFarming() public virtual onlyDistributor { uint256 leftover = _makeInfo().stopFarming(); emit RewardUpdated(0, 0); @@ -66,21 +76,33 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { } } + /** + * @notice See {IFarmingPool-farmed} + */ function farmed(address account) public view virtual returns (uint256) { return _makeInfo().farmed(account, balanceOf(account)); } + /** + * @notice See {IFarmingPool-deposit} + */ function deposit(uint256 amount) public virtual { _mint(msg.sender, amount); if (balanceOf(msg.sender) > _MAX_BALANCE) revert MaxBalanceExceeded(); stakingToken.safeTransferFrom(msg.sender, address(this), amount); } + /** + * @notice See {IFarmingPool-withdraw} + */ function withdraw(uint256 amount) public virtual { _burn(msg.sender, amount); stakingToken.safeTransfer(msg.sender, amount); } + /** + * @notice See {IFarmingPool-claim} + */ function claim() public virtual { uint256 amount = _makeInfo().claim(msg.sender, balanceOf(msg.sender)); if (amount > 0) { @@ -92,11 +114,17 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { reward.safeTransfer(to, amount); } + /** + * @notice See {IFarmingPool-exit} + */ function exit() public virtual { withdraw(balanceOf(msg.sender)); claim(); } + /** + * @notice See {IFarmingPool-rescueFunds} + */ function rescueFunds(IERC20 token, uint256 amount) public virtual onlyDistributor { if (token == IERC20(address(0))) { payable(_distributor).sendValue(amount); diff --git a/contracts/MultiFarmingPlugin.sol b/contracts/MultiFarmingPlugin.sol index 0b3a24d..13a3f1a 100644 --- a/contracts/MultiFarmingPlugin.sol +++ b/contracts/MultiFarmingPlugin.sol @@ -14,6 +14,11 @@ import { IMultiFarmingPlugin } from "./interfaces/IMultiFarmingPlugin.sol"; import { Distributor } from "./Distributor.sol"; import { FarmAccounting, FarmingLib } from "./FarmingLib.sol"; +/** + * @title Implementation of the {IMultiFarmingPlugin} interface. + * @notice This contract only accounts for the balances of users + * who added it as a plugin to the farmable token. + */ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { using SafeERC20 for IERC20; using FarmingLib for FarmingLib.Info; @@ -21,14 +26,6 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { using AddressSet for AddressSet.Data; using AddressArray for AddressArray.Data; - error ZeroFarmableTokenAddress(); - error ZeroRewardsTokenAddress(); - error RewardsTokenAlreadyAdded(); - error RewardsTokensLimitTooHigh(uint256); - error RewardsTokensLimitReached(); - error RewardsTokenNotFound(); - error InsufficientFunds(); - uint256 public immutable rewardsTokensLimit; uint256 private _totalSupply; @@ -42,18 +39,30 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { rewardsTokensLimit = rewardsTokensLimit_; } + /** + * @notice See {IMultiFarmingPlugin-rewardsTokens} + */ function rewardsTokens() external view returns(address[] memory) { return _rewardsTokens.items.get(); } + /** + * @notice See {IMultiFarmingPlugin-farmInfo} + */ function farmInfo(IERC20 rewardsToken) public view returns(FarmAccounting.Info memory) { return _farms[rewardsToken].farmInfo; } + /** + * @notice See {IMultiFarmingPlugin-totalSupply} + */ function totalSupply() public view returns(uint256) { return _totalSupply; } + /** + * @notice See {IMultiFarmingPlugin-addRewardsToken} + */ function addRewardsToken(address rewardsToken) public virtual onlyOwner { if (rewardsToken == address(0)) revert ZeroRewardsTokenAddress(); if (_rewardsTokens.length() == rewardsTokensLimit) revert RewardsTokensLimitReached(); @@ -61,6 +70,9 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { emit FarmCreated(address(token), rewardsToken); } + /** + * @notice See {IMultiFarmingPlugin-startFarming} + */ function startFarming(IERC20 rewardsToken, uint256 amount, uint256 period) public virtual onlyDistributor { if (!_rewardsTokens.contains(address(rewardsToken))) revert RewardsTokenNotFound(); @@ -69,6 +81,9 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { rewardsToken.safeTransferFrom(msg.sender, address(this), amount); } + /** + * @notice See {IMultiFarmingPlugin-stopFarming} + */ function stopFarming(IERC20 rewardsToken) public virtual onlyDistributor { if (!_rewardsTokens.contains(address(rewardsToken))) revert RewardsTokenNotFound(); @@ -79,16 +94,26 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { } } + + /** + * @notice See {IMultiFarmingPlugin-farmed} + */ function farmed(IERC20 rewardsToken, address account) public view virtual returns(uint256) { uint256 balance = IERC20Plugins(token).pluginBalanceOf(address(this), account); return _makeInfo(rewardsToken).farmed(account, balance); } + /** + * @notice See {IMultiFarmingPlugin-claim} + */ function claim(IERC20 rewardsToken) public virtual { uint256 pluginBalance = IERC20Plugins(token).pluginBalanceOf(address(this), msg.sender); _claim(rewardsToken, msg.sender, pluginBalance); } + /** + * @notice See {IMultiFarmingPlugin-claim} + */ function claim() public virtual { uint256 pluginBalance = IERC20Plugins(token).pluginBalanceOf(address(this), msg.sender); address[] memory tokens = _rewardsTokens.items.get(); @@ -127,6 +152,9 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { } } + /** + * @notice See {IMultiFarmingPlugin-rescueFunds} + */ function rescueFunds(IERC20 token_, uint256 amount) public virtual onlyDistributor { if(token_ == IERC20(address(0))) { payable(_distributor).sendValue(amount); diff --git a/contracts/accounting/FarmAccounting.sol b/contracts/accounting/FarmAccounting.sol index 78e2a6e..2fc9211 100644 --- a/contracts/accounting/FarmAccounting.sol +++ b/contracts/accounting/FarmAccounting.sol @@ -5,10 +5,6 @@ pragma solidity ^0.8.0; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; library FarmAccounting { - error ZeroDuration(); - error DurationTooLarge(); - error AmountTooLarge(); - struct Info { uint40 finished; uint32 duration; @@ -19,6 +15,10 @@ library FarmAccounting { uint256 internal constant _MAX_REWARD_AMOUNT = 1e32; // 108 bits uint256 internal constant _SCALE = 1e18; // 60 bits + error ZeroDuration(); + error DurationTooLarge(); + error AmountTooLarge(); + /// @dev Requires extra 18 decimals for precision, result fits in 168 bits function farmedSinceCheckpointScaled(Info storage info, uint256 checkpoint) internal view returns(uint256 amount) { unchecked { diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 4490069..5a65775 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -3,6 +3,12 @@ pragma solidity ^0.8.0; interface IDistributor { + // Emitted when the distributor is changed + event DistributorChanged(address newDistributor); + + error NotDistributor(); + error ZeroDistributorAddress(); + /** * @notice Sets the entity that can manage the farming */ diff --git a/contracts/interfaces/IFarmingPlugin.sol b/contracts/interfaces/IFarmingPlugin.sol index 2bbb9af..23758bf 100644 --- a/contracts/interfaces/IFarmingPlugin.sol +++ b/contracts/interfaces/IFarmingPlugin.sol @@ -4,22 +4,58 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IPlugin } from "@1inch/token-plugins/contracts/interfaces/IPlugin.sol"; + import { FarmAccounting } from "../accounting/FarmAccounting.sol"; +/** + * @title Interface of a plugin for farming reward tokens. + */ interface IFarmingPlugin is IPlugin { + // Emitted in constructor when the plugin is set up. event FarmCreated(address token, address reward); + // Emitted when farming parameters are updated. event RewardUpdated(uint256 reward, uint256 duration); + error ZeroFarmableTokenAddress(); + error ZeroRewardsTokenAddress(); + error InsufficientFunds(); + // View functions + /** + * @notice Returns the number of farmable tokens counted by this plugin. + */ function totalSupply() external view returns(uint256); + /** + * @notice Gets information about the current farm: finished, duration, reward and balance. + */ function farmInfo() external view returns(FarmAccounting.Info memory); + /** + * @notice Gets the amount of farmed reward tokens for the account. + * @param account The address of the account to check. + */ function farmed(address account) external view returns(uint256); // User functions + /** + * @notice Claims the farmed reward tokens for the caller. + */ function claim() external; // Distributor functions + /** + * @notice Begins farming for the specified period. + * @param amount The amount to farm. + * @param period The farming period. + */ function startFarming(uint256 amount, uint256 period) external; + /** + * @notice Stops farming immediately and refunds unspent rewards. + */ function stopFarming() external; - function rescueFunds(IERC20 token, uint256 amount) external; + /** + * @notice Retrieves tokens that accidentally appeared on the contract. + * @param token_ The address of the token to be rescued. + * @param amount The number of tokens to rescue. + */ + function rescueFunds(IERC20 token_, uint256 amount) external; } diff --git a/contracts/interfaces/IFarmingPool.sol b/contracts/interfaces/IFarmingPool.sol index e4ac90d..0cb7efa 100644 --- a/contracts/interfaces/IFarmingPool.sol +++ b/contracts/interfaces/IFarmingPool.sol @@ -3,23 +3,69 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + import { FarmAccounting } from "../accounting/FarmAccounting.sol"; +/** + * @title Interface of a pool for farming reward tokens, required for farming tokens that don't support plugins. + */ interface IFarmingPool is IERC20 { + // Emitted when farming parameters are updated. event RewardUpdated(uint256 reward, uint256 duration); + error SameStakingAndRewardsTokens(); + error ZeroStakingTokenAddress(); + error ZeroRewardsTokenAddress(); + error AccessDenied(); + error InsufficientFunds(); + error MaxBalanceExceeded(); + // View functions + /** + * @notice Gets information about the current farm: finished, duration, reward and balance. + */ function farmInfo() external view returns(FarmAccounting.Info memory); + /** + * @notice Gets the amount of farmed reward tokens for the account. + * @param account The address of the account to check. + */ function farmed(address account) external view returns(uint256); // User functions + /** + * @notice Stakes the farmable tokens and mints its own tokens in return. + * @param amount The amount of tokens to stake. + */ function deposit(uint256 amount) external; + /** + * @notice Burns the contract tokens and returns the farmable tokens. + * @param amount The amount of tokens to withdraw. + */ function withdraw(uint256 amount) external; + /** + * @notice Claims the farmed reward tokens for the caller. + */ function claim() external; + /** + * @notice Claims the farmed reward tokens for the caller and withdraws the staked tokens. + */ function exit() external; // Distributor functions + /** + * @notice Begins farming for the specified period. + * @param amount The amount to farm. + * @param period The farming period. + */ function startFarming(uint256 amount, uint256 period) external; + /** + * @notice Stops farming immediately and refunds unspent rewards. + */ function stopFarming() external; + /** + * @notice Retrieves tokens that accidentally appeared on the contract. + * @param token The address of the token to be rescued. + * @param amount The number of tokens to rescue. + */ function rescueFunds(IERC20 token, uint256 amount) external; } diff --git a/contracts/interfaces/IMultiFarmingPlugin.sol b/contracts/interfaces/IMultiFarmingPlugin.sol index 38d6a55..5d60e58 100644 --- a/contracts/interfaces/IMultiFarmingPlugin.sol +++ b/contracts/interfaces/IMultiFarmingPlugin.sol @@ -4,23 +4,74 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IPlugin } from "@1inch/token-plugins/contracts/interfaces/IPlugin.sol"; + import { FarmAccounting } from "../accounting/FarmAccounting.sol"; +/** + * @title Interface of a plugin for farming multiple reward tokens. + */ interface IMultiFarmingPlugin is IPlugin { + // Emitted when a new reward token is added. event FarmCreated(address token, address reward); + // Emitted when farming parameters are updated. event RewardUpdated(address token, uint256 reward, uint256 duration); + error ZeroFarmableTokenAddress(); + error ZeroRewardsTokenAddress(); + error RewardsTokenAlreadyAdded(); + error RewardsTokensLimitTooHigh(uint256); + error RewardsTokensLimitReached(); + error RewardsTokenNotFound(); + error InsufficientFunds(); + // View functions + /** + * @notice Returns the number of farmable tokens counted by this plugin. + */ function totalSupply() external view returns(uint256); - function farmInfo(IERC20 rewardsToken) external view returns(FarmAccounting.Info memory); + /** + * @notice Gets information about the current farm for the selected reward token. + * @param rewardsToken The address of the reward token. + */ + function farmInfo(IERC20 rewardsToken) external view returns(FarmAccounting.Info memory);/** + * @notice Gets the amount of selected reward tokens farmed for the account. + * @param rewardsToken The address of the reward token. + * @param account The address of the account to check. + */ function farmed(IERC20 rewardsToken, address account) external view returns(uint256); + /** + * Gets all reward tokens that are supported by this plugin. + */ + function rewardsTokens() external view returns(address[] memory); // User functions + /** + * @notice Claims the selected farmed reward tokens for the caller. + * @param rewardsToken The address of the reward token. + */ function claim(IERC20 rewardsToken) external; + /** + * @notice Claims for the caller all farmed reward tokens supported by the plugin. + */ function claim() external; // Distributor functions + /** + * @notice Begins farming for the selected reward token for the specified period. + * @param rewardsToken The address of the reward token. + * @param amount The amount to farm. + * @param period The farming period. + */ function startFarming(IERC20 rewardsToken, uint256 amount, uint256 period) external; + /** + * @notice Stops farming for the selected reward token immediately and refunds unspent rewards. + * @param rewardsToken The address of the reward token. + */ function stopFarming(IERC20 rewardsToken) external; - function rescueFunds(IERC20 token, uint256 amount) external; + /** + * @notice Retrieves tokens that accidentally appeared on the contract. + * @param token_ The address of the token to be rescued. + * @param amount The number of tokens to rescue. + */ + function rescueFunds(IERC20 token_, uint256 amount) external; }