From ed90acdf4010dc52182318cd8dada58fbc8bc67f Mon Sep 17 00:00:00 2001 From: byshape Date: Tue, 26 Sep 2023 23:18:04 +0100 Subject: [PATCH 1/3] Renamed start/stop farming library functions --- contracts/FarmingLib.sol | 83 ++++++++--------- contracts/FarmingPlugin.sol | 88 +++++++++---------- contracts/FarmingPool.sol | 70 +++++++-------- contracts/MultiFarmingPlugin.sol | 78 ++++++++-------- .../{FarmAccounting.sol => Farming.sol} | 36 ++++---- .../{UserAccounting.sol => Rewards.sol} | 48 +++++----- contracts/interfaces/IFarmingPlugin.sol | 4 +- contracts/interfaces/IFarmingPool.sol | 4 +- contracts/interfaces/IMultiFarmingPlugin.sol | 4 +- 9 files changed, 208 insertions(+), 207 deletions(-) rename contracts/accounting/{FarmAccounting.sol => Farming.sol} (91%) rename contracts/accounting/{UserAccounting.sol => Rewards.sol} (94%) diff --git a/contracts/FarmingLib.sol b/contracts/FarmingLib.sol index 16ecc6b..11e1654 100644 --- a/contracts/FarmingLib.sol +++ b/contracts/FarmingLib.sol @@ -2,20 +2,21 @@ pragma solidity ^0.8.0; -import { FarmAccounting } from "./accounting/FarmAccounting.sol"; -import { UserAccounting } from "./accounting/UserAccounting.sol"; +import { Farming } from "./accounting/Farming.sol"; +import { Rewards } from "./accounting/Rewards.sol"; /// @title FarmingLib -/// @dev A library for farming logic, using FarmAccounting and UserAccounting. +/// @dev A library for farming logic, using Farming and Rewards. library FarmingLib { - using FarmAccounting for FarmAccounting.Info; - using UserAccounting for UserAccounting.Info; + using Farming for Farming.Info; + using Rewards for Rewards.Info; using FarmingLib for FarmingLib.Info; - /// @dev Struct containing farm and user detailed info for farming operations. See {FarmAccounting.Info} and {UserAccounting.Info} for. + // TODO: check all docs and update + /// @dev Struct containing farm and user detailed info for farming operations. See {Farming.Info} and {Rewards.Info} for. struct Data { - FarmAccounting.Info farmInfo; - UserAccounting.Info userInfo; + Farming.Info farmingInfo; + Rewards.Info rewardsInfo; } /// @dev Struct containing the total supply function and a data slot for EVM storage. @@ -58,10 +59,10 @@ library FarmingLib { * @param period The farming period. * @return reward The farming reward. */ - function startFarming(Info memory self, uint256 amount, uint256 period) internal returns(uint256 reward) { + function updateFarmData(Info memory self, uint256 amount, uint256 period) internal returns(uint256 reward) { Data storage data = self.getData(); - data.userInfo.updateFarmedPerToken(_farmedPerToken(self)); - reward = data.farmInfo.startFarming(amount, period); + data.rewardsInfo.updateFarmedPerToken(_farmedPerToken(self)); + reward = data.farmingInfo.update(amount, period); } /** @@ -69,21 +70,10 @@ library FarmingLib { * @param self The FarmingLib.Info struct to retrieve data from storage. * @return leftover Amount of reward tokens remaining after farming. */ - function stopFarming(Info memory self) internal returns(uint256 leftover) { + function cancelFarming(Info memory self) internal returns(uint256 leftover) { Data storage data = self.getData(); - data.userInfo.updateFarmedPerToken(_farmedPerToken(self)); - leftover = data.farmInfo.stopFarming(); - } - - /** - * @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. - */ - function farmed(Info memory self, address account, uint256 balance) internal view returns(uint256) { - return self.getData().userInfo.farmed(account, balance, _farmedPerToken(self)); + data.rewardsInfo.updateFarmedPerToken(_farmedPerToken(self)); + leftover = data.farmingInfo.cancel(); } /** @@ -96,10 +86,10 @@ library FarmingLib { function claim(Info memory self, address account, uint256 balance) internal returns(uint256 amount) { Data storage data = self.getData(); uint256 fpt = _farmedPerToken(self); - amount = data.userInfo.farmed(account, balance, fpt); + amount = data.rewardsInfo.farmed(account, balance, fpt); if (amount > 0) { - data.userInfo.eraseFarmed(account, balance, fpt); - data.farmInfo.claim(amount); + data.rewardsInfo.eraseFarmed(account, balance, fpt); + data.farmingInfo.claim(amount); } } @@ -111,25 +101,25 @@ library FarmingLib { * @param amount The amount to transfer. */ function updateBalances(Info memory self, address from, address to, uint256 amount) internal { - self.getData().userInfo.updateBalances(from, to, amount, _farmedPerToken(self)); + self.getData().rewardsInfo.updateBalances(from, to, amount, _farmedPerToken(self)); } - function _farmedPerToken(Info memory self) private view returns (uint256) { - return self.getData().userInfo.farmedPerToken(_infoToContext(self), _lazyGetSupply, _lazyGetFarmed); - } - - // UserAccounting bindings - - function _lazyGetSupply(bytes32 context) private view returns(uint256) { - Info memory self = _contextToInfo(context); - return self.getTotalSupply(); + /** + * @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. + */ + function farmed(Info memory self, address account, uint256 balance) internal view returns(uint256) { + return self.getData().rewardsInfo.farmed(account, balance, _farmedPerToken(self)); } - function _lazyGetFarmed(bytes32 context, uint256 checkpoint) private view returns(uint256) { - Info memory self = _contextToInfo(context); - return self.getData().farmInfo.farmedSinceCheckpointScaled(checkpoint); + function _farmedPerToken(Info memory self) private view returns (uint256) { + return self.getData().rewardsInfo.farmedPerToken(_infoToContext(self), _lazyGetSupply, _lazyGetFarmed); } + // --- Rewards bindings section start --- function _contextToInfo(bytes32 context) private pure returns(Info memory self) { assembly ("memory-safe") { // solhint-disable-line no-inline-assembly self := context @@ -141,4 +131,15 @@ library FarmingLib { context := self } } + + function _lazyGetSupply(bytes32 context) private view returns(uint256) { + Info memory self = _contextToInfo(context); + return self.getTotalSupply(); + } + + function _lazyGetFarmed(bytes32 context, uint256 checkpoint) private view returns(uint256) { + Info memory self = _contextToInfo(context); + return self.getData().farmingInfo.farmedSinceCheckpointScaled(checkpoint); + } + // --- Rewards bindings section end --- } diff --git a/contracts/FarmingPlugin.sol b/contracts/FarmingPlugin.sol index fc3cbe9..8124ab5 100644 --- a/contracts/FarmingPlugin.sol +++ b/contracts/FarmingPlugin.sol @@ -10,26 +10,26 @@ import { Plugin } from "@1inch/token-plugins/contracts/Plugin.sol"; import { IERC20Plugins } from "@1inch/token-plugins/contracts/interfaces/IERC20Plugins.sol"; import { IFarmingPlugin } from "./interfaces/IFarmingPlugin.sol"; -import { FarmingLib, FarmAccounting } from "./FarmingLib.sol"; +import { FarmingLib, Farming } from "./FarmingLib.sol"; contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { using SafeERC20 for IERC20; using FarmingLib for FarmingLib.Info; - using FarmAccounting for FarmAccounting.Info; + using Farming for Farming.Info; using Address for address payable; - error ZeroFarmableTokenAddress(); - error ZeroRewardsTokenAddress(); - error ZeroDistributorAddress(); - error SameDistributor(); - error InsufficientFunds(); - IERC20 public immutable rewardsToken; address private _distributor; uint256 private _totalSupply; FarmingLib.Data private _farm; + error ZeroFarmableTokenAddress(); + error ZeroRewardsTokenAddress(); + error ZeroDistributorAddress(); + error SameDistributor(); + error InsufficientFunds(); + modifier onlyDistributor { if (msg.sender != _distributor) revert AccessDenied(); _; @@ -44,43 +44,26 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { emit FarmCreated(address(farmableToken_), address(rewardsToken_)); } - function farmInfo() public view returns(FarmAccounting.Info memory) { - return _farm.farmInfo; - } - - function totalSupply() public view returns(uint256) { - return _totalSupply; - } - - function distributor() public view returns(address) { - return _distributor; - } - - function setDistributor(address distributor_) public virtual onlyOwner { - if (distributor_ == address(0)) revert ZeroDistributorAddress(); - address oldDistributor = _distributor; - if (distributor_ == oldDistributor) revert SameDistributor(); - emit DistributorChanged(oldDistributor, distributor_); - _distributor = distributor_; - } - function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { - uint256 reward = _makeInfo().startFarming(amount, period); + uint256 reward = _makeInfo().updateFarmData(amount, period); emit RewardUpdated(reward, period); rewardsToken.safeTransferFrom(msg.sender, address(this), amount); } function stopFarming() public virtual onlyDistributor { - uint256 leftover = _makeInfo().stopFarming(); + uint256 leftover = _makeInfo().cancelFarming(); emit RewardUpdated(0, 0); if (leftover > 0) { rewardsToken.safeTransfer(msg.sender, leftover); } } - function farmed(address account) public view virtual returns(uint256) { - uint256 balance = IERC20Plugins(token).pluginBalanceOf(address(this), account); - return _makeInfo().farmed(account, balance); + function setDistributor(address distributor_) public virtual onlyOwner { + if (distributor_ == address(0)) revert ZeroDistributorAddress(); + address oldDistributor = _distributor; + if (distributor_ == oldDistributor) revert SameDistributor(); + emit DistributorChanged(oldDistributor, distributor_); + _distributor = distributor_; } function claim() public virtual { @@ -91,6 +74,34 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { } } + function rescueFunds(IERC20 token_, uint256 amount) public virtual onlyDistributor { + if(token_ == IERC20(address(0))) { + payable(_distributor).sendValue(amount); + } else { + if (token_ == rewardsToken) { + if (rewardsToken.balanceOf(address(this)) < _farm.farmingInfo.balance + amount) revert InsufficientFunds(); + } + token_.safeTransfer(_distributor, amount); + } + } + + function farmInfo() public view returns(Farming.Info memory) { + return _farm.farmingInfo; + } + + function totalSupply() public view returns(uint256) { + return _totalSupply; + } + + function distributor() public view returns(address) { + return _distributor; + } + + function farmed(address account) public view virtual returns(uint256) { + uint256 balance = IERC20Plugins(token).pluginBalanceOf(address(this), account); + return _makeInfo().farmed(account, balance); + } + function _transferReward(IERC20 reward, address to, uint256 amount) internal virtual { reward.safeTransfer(to, amount); } @@ -105,17 +116,6 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { } } - function rescueFunds(IERC20 token_, uint256 amount) public virtual onlyDistributor { - if(token_ == IERC20(address(0))) { - payable(_distributor).sendValue(amount); - } else { - if (token_ == rewardsToken) { - if (rewardsToken.balanceOf(address(this)) < _farm.farmInfo.balance + amount) revert InsufficientFunds(); - } - token_.safeTransfer(_distributor, amount); - } - } - function _makeInfo() private view returns(FarmingLib.Info memory) { return FarmingLib.makeInfo(totalSupply, _farm); } diff --git a/contracts/FarmingPool.sol b/contracts/FarmingPool.sol index 23299d5..3e9e021 100644 --- a/contracts/FarmingPool.sol +++ b/contracts/FarmingPool.sol @@ -9,13 +9,21 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; import { IFarmingPool } from "./interfaces/IFarmingPool.sol"; -import { FarmAccounting, FarmingLib } from "./FarmingLib.sol"; +import { Farming, FarmingLib } from "./FarmingLib.sol"; contract FarmingPool is IFarmingPool, Ownable, ERC20 { using SafeERC20 for IERC20; using Address for address payable; using FarmingLib for FarmingLib.Info; + uint256 internal constant _MAX_BALANCE = 1e32; + + IERC20 public immutable stakingToken; + IERC20 public immutable rewardsToken; + + address private _distributor; + FarmingLib.Data private _farm; + error SameStakingAndRewardsTokens(); error ZeroStakingTokenAddress(); error ZeroRewardsTokenAddress(); @@ -25,14 +33,6 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { error InsufficientFunds(); error MaxBalanceExceeded(); - uint256 internal constant _MAX_BALANCE = 1e32; - - IERC20 public immutable stakingToken; - IERC20 public immutable rewardsToken; - - address private _distributor; - FarmingLib.Data private _farm; - modifier onlyDistributor { if (msg.sender != _distributor) revert AccessDenied(); _; @@ -51,18 +51,6 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { rewardsToken = rewardsToken_; } - function decimals() public view virtual override returns (uint8) { - return IERC20Metadata(address(stakingToken)).decimals(); - } - - function farmInfo() public view returns(FarmAccounting.Info memory) { - return _farm.farmInfo; - } - - function distributor() public view virtual returns (address) { - return _distributor; - } - function setDistributor(address distributor_) public virtual onlyOwner { if (distributor_ == address(0)) revert ZeroDistributorAddress(); address oldDistributor = _distributor; @@ -72,23 +60,19 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { } function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { - uint256 reward = _makeInfo().startFarming(amount, period); + uint256 reward = _makeInfo().updateFarmData(amount, period); emit RewardUpdated(reward, period); rewardsToken.safeTransferFrom(msg.sender, address(this), amount); } function stopFarming() public virtual onlyDistributor { - uint256 leftover = _makeInfo().stopFarming(); + uint256 leftover = _makeInfo().cancelFarming(); emit RewardUpdated(0, 0); if (leftover > 0) { rewardsToken.safeTransfer(msg.sender, leftover); } } - function farmed(address account) public view virtual returns (uint256) { - return _makeInfo().farmed(account, balanceOf(account)); - } - function deposit(uint256 amount) public virtual { _mint(msg.sender, amount); if (balanceOf(msg.sender) > _MAX_BALANCE) revert MaxBalanceExceeded(); @@ -107,10 +91,6 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { } } - function _transferReward(IERC20 reward, address to, uint256 amount) internal virtual { - reward.safeTransfer(to, amount); - } - function exit() public virtual { withdraw(balanceOf(msg.sender)); claim(); @@ -123,19 +103,34 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { if (token == stakingToken) { if (stakingToken.balanceOf(address(this)) < totalSupply() + amount) revert InsufficientFunds(); } else if (token == rewardsToken) { - if (rewardsToken.balanceOf(address(this)) < _farm.farmInfo.balance + amount) revert InsufficientFunds(); + if (rewardsToken.balanceOf(address(this)) < _farm.farmingInfo.balance + amount) revert InsufficientFunds(); } token.safeTransfer(_distributor, amount); } } - function _makeInfo() private view returns(FarmingLib.Info memory) { - return FarmingLib.makeInfo(totalSupply, _farm); + function decimals() public view virtual override returns (uint8) { + return IERC20Metadata(address(stakingToken)).decimals(); } - // ERC20 overrides + function farmInfo() public view returns(Farming.Info memory) { + return _farm.farmingInfo; + } + function distributor() public view virtual returns (address) { + return _distributor; + } + + function farmed(address account) public view virtual returns (uint256) { + return _makeInfo().farmed(account, balanceOf(account)); + } + + function _transferReward(IERC20 reward, address to, uint256 amount) internal virtual { + reward.safeTransfer(to, amount); + } + + // --- ERC20 overrides section start --- function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override { super._beforeTokenTransfer(from, to, amount); @@ -143,4 +138,9 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { _makeInfo().updateBalances(from, to, amount); } } + // --- ERC20 overrides section end --- + + function _makeInfo() private view returns(FarmingLib.Info memory) { + return FarmingLib.makeInfo(totalSupply, _farm); + } } diff --git a/contracts/MultiFarmingPlugin.sol b/contracts/MultiFarmingPlugin.sol index bbcab6f..ad1ed65 100644 --- a/contracts/MultiFarmingPlugin.sol +++ b/contracts/MultiFarmingPlugin.sol @@ -11,7 +11,7 @@ import { AddressArray, AddressSet } from "@1inch/solidity-utils/contracts/librar import { IERC20Plugins } from "@1inch/token-plugins/contracts/interfaces/IERC20Plugins.sol"; import { IMultiFarmingPlugin } from "./interfaces/IMultiFarmingPlugin.sol"; -import { FarmAccounting, FarmingLib } from "./FarmingLib.sol"; +import { Farming, FarmingLib } from "./FarmingLib.sol"; contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { using SafeERC20 for IERC20; @@ -20,6 +20,13 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { using AddressSet for AddressSet.Data; using AddressArray for AddressArray.Data; + uint256 public immutable rewardsTokensLimit; + + address private _distributor; + uint256 private _totalSupply; + mapping(IERC20 => FarmingLib.Data) private _farms; + AddressSet.Data private _rewardsTokens; + error ZeroFarmableTokenAddress(); error ZeroRewardsTokenAddress(); error ZeroDistributorAddress(); @@ -30,13 +37,6 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { error RewardsTokenNotFound(); error InsufficientFunds(); - uint256 public immutable rewardsTokensLimit; - - address private _distributor; - uint256 private _totalSupply; - mapping(IERC20 => FarmingLib.Data) private _farms; - AddressSet.Data private _rewardsTokens; - modifier onlyDistributor { if (msg.sender != _distributor) revert AccessDenied(); _; @@ -53,18 +53,6 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { return _rewardsTokens.items.get(); } - function farmInfo(IERC20 rewardsToken) public view returns(FarmAccounting.Info memory) { - return _farms[rewardsToken].farmInfo; - } - - function totalSupply() public view returns(uint256) { - return _totalSupply; - } - - function distributor() public view returns(address) { - return _distributor; - } - function setDistributor(address distributor_) public virtual onlyOwner { if (distributor_ == address(0)) revert ZeroDistributorAddress(); address oldDistributor = _distributor; @@ -83,7 +71,7 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { function startFarming(IERC20 rewardsToken, uint256 amount, uint256 period) public virtual onlyDistributor { if (!_rewardsTokens.contains(address(rewardsToken))) revert RewardsTokenNotFound(); - uint256 reward = _makeInfo(rewardsToken).startFarming(amount, period); + uint256 reward = _makeInfo(rewardsToken).updateFarmData(amount, period); emit RewardUpdated(address(rewardsToken), reward, period); rewardsToken.safeTransferFrom(msg.sender, address(this), amount); } @@ -91,18 +79,13 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { function stopFarming(IERC20 rewardsToken) public virtual onlyDistributor { if (!_rewardsTokens.contains(address(rewardsToken))) revert RewardsTokenNotFound(); - uint256 leftover = _makeInfo(rewardsToken).stopFarming(); + uint256 leftover = _makeInfo(rewardsToken).cancelFarming(); emit RewardUpdated(address(rewardsToken), 0, 0); if (leftover > 0) { rewardsToken.safeTransfer(msg.sender, leftover); } } - 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); - } - function claim(IERC20 rewardsToken) public virtual { uint256 pluginBalance = IERC20Plugins(token).pluginBalanceOf(address(this), msg.sender); _claim(rewardsToken, msg.sender, pluginBalance); @@ -119,13 +102,34 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { } } - function _claim(IERC20 rewardsToken, address account, uint256 pluginBalance) private { - uint256 amount = _makeInfo(rewardsToken).claim(account, pluginBalance); - if (amount > 0) { - _transferReward(rewardsToken, account, amount); + function rescueFunds(IERC20 token_, uint256 amount) public virtual onlyDistributor { + if(token_ == IERC20(address(0))) { + payable(_distributor).sendValue(amount); + } else { + if (_rewardsTokens.contains(address(token_))) { + if (token_.balanceOf(address(this)) < _farms[token_].farmingInfo.balance + amount) revert InsufficientFunds(); + } + token_.safeTransfer(_distributor, amount); } } + function farmInfo(IERC20 rewardsToken) public view returns(Farming.Info memory) { + return _farms[rewardsToken].farmingInfo; + } + + function totalSupply() public view returns(uint256) { + return _totalSupply; + } + + function distributor() public view returns(address) { + return _distributor; + } + + 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); + } + function _transferReward(IERC20 reward, address to, uint256 amount) internal virtual { reward.safeTransfer(to, amount); } @@ -146,14 +150,10 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { } } - function rescueFunds(IERC20 token_, uint256 amount) public virtual onlyDistributor { - if(token_ == IERC20(address(0))) { - payable(_distributor).sendValue(amount); - } else { - if (_rewardsTokens.contains(address(token_))) { - if (token_.balanceOf(address(this)) < _farms[token_].farmInfo.balance + amount) revert InsufficientFunds(); - } - token_.safeTransfer(_distributor, amount); + function _claim(IERC20 rewardsToken, address account, uint256 pluginBalance) private { + uint256 amount = _makeInfo(rewardsToken).claim(account, pluginBalance); + if (amount > 0) { + _transferReward(rewardsToken, account, amount); } } diff --git a/contracts/accounting/FarmAccounting.sol b/contracts/accounting/Farming.sol similarity index 91% rename from contracts/accounting/FarmAccounting.sol rename to contracts/accounting/Farming.sol index 78e2a6e..026cbd7 100644 --- a/contracts/accounting/FarmAccounting.sol +++ b/contracts/accounting/Farming.sol @@ -4,11 +4,7 @@ pragma solidity ^0.8.0; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -library FarmAccounting { - error ZeroDuration(); - error DurationTooLarge(); - error AmountTooLarge(); - +library Farming { struct Info { uint40 finished; uint32 duration; @@ -19,19 +15,11 @@ library FarmAccounting { uint256 internal constant _MAX_REWARD_AMOUNT = 1e32; // 108 bits uint256 internal constant _SCALE = 1e18; // 60 bits - /// @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 { - (uint40 finished, uint32 duration, uint184 reward) = (info.finished, info.duration, info.reward); - if (duration > 0) { - uint256 elapsed = Math.min(block.timestamp, finished) - Math.min(checkpoint, finished); - // size of (type(uint32).max * _MAX_REWARD_AMOUNT * _SCALE) is less than 200 bits, so there is no overflow - return elapsed * reward * _SCALE / duration; - } - } - } + error ZeroDuration(); + error DurationTooLarge(); + error AmountTooLarge(); - function startFarming(Info storage info, uint256 amount, uint256 period) internal returns(uint256) { + function update(Info storage info, uint256 amount, uint256 period) internal returns(uint256) { if (period == 0) revert ZeroDuration(); if (period > type(uint32).max) revert DurationTooLarge(); @@ -52,7 +40,7 @@ library FarmAccounting { return amount; } - function stopFarming(Info storage info) internal returns(uint256 leftover) { + function cancel(Info storage info) internal returns(uint256 leftover) { leftover = info.reward - farmedSinceCheckpointScaled(info, info.finished - info.duration) / _SCALE; (info.finished, info.duration, info.reward, info.balance) = ( uint40(block.timestamp), @@ -65,4 +53,16 @@ library FarmAccounting { function claim(Info storage info, uint256 amount) internal { info.balance -= amount; } + + /// @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 { + (uint40 finished, uint32 duration, uint184 reward) = (info.finished, info.duration, info.reward); + if (duration > 0) { + uint256 elapsed = Math.min(block.timestamp, finished) - Math.min(checkpoint, finished); + // size of (type(uint32).max * _MAX_REWARD_AMOUNT * _SCALE) is less than 200 bits, so there is no overflow + return elapsed * reward * _SCALE / duration; + } + } + } } diff --git a/contracts/accounting/UserAccounting.sol b/contracts/accounting/Rewards.sol similarity index 94% rename from contracts/accounting/UserAccounting.sol rename to contracts/accounting/Rewards.sol index 735596a..627ccf4 100644 --- a/contracts/accounting/UserAccounting.sol +++ b/contracts/accounting/Rewards.sol @@ -2,37 +2,15 @@ pragma solidity ^0.8.0; -import { FarmAccounting } from "./FarmAccounting.sol"; +import { Farming } from "./Farming.sol"; -library UserAccounting { +library Rewards { struct Info { uint40 checkpoint; uint216 farmedPerTokenStored; mapping(address => int256) corrections; } - function farmedPerToken( - Info storage info, - bytes32 context, - function(bytes32) internal view returns(uint256) lazyGetSupply, - function(bytes32, uint256) internal view returns(uint256) lazyGetFarmed - ) internal view returns(uint256) { - (uint256 checkpoint, uint256 fpt) = (info.checkpoint, info.farmedPerTokenStored); - if (block.timestamp != checkpoint) { - uint256 supply = lazyGetSupply(context); - if (supply > 0) { - // fpt increases by 168 bit / supply - unchecked { fpt += lazyGetFarmed(context, checkpoint) / supply; } - } - } - return fpt; - } - - function farmed(Info storage info, address account, uint256 balance, uint256 fpt) internal view returns(uint256) { - // balance * fpt is less than 168 bit - return uint256(int256(balance * fpt) - info.corrections[account]) / FarmAccounting._SCALE; - } - function eraseFarmed(Info storage info, address account, uint256 balance, uint256 fpt) internal { // balance * fpt is less than 168 bit info.corrections[account] = int256(balance * fpt); @@ -60,4 +38,26 @@ library UserAccounting { } } } + + function farmedPerToken( + Info storage info, + bytes32 context, + function(bytes32) internal view returns(uint256) lazyGetSupply, + function(bytes32, uint256) internal view returns(uint256) lazyGetFarmed + ) internal view returns(uint256) { + (uint256 checkpoint, uint256 fpt) = (info.checkpoint, info.farmedPerTokenStored); + if (block.timestamp != checkpoint) { + uint256 supply = lazyGetSupply(context); + if (supply > 0) { + // fpt increases by 168 bit / supply + unchecked { fpt += lazyGetFarmed(context, checkpoint) / supply; } + } + } + return fpt; + } + + function farmed(Info storage info, address account, uint256 balance, uint256 fpt) internal view returns(uint256) { + // balance * fpt is less than 168 bit + return uint256(int256(balance * fpt) - info.corrections[account]) / Farming._SCALE; + } } diff --git a/contracts/interfaces/IFarmingPlugin.sol b/contracts/interfaces/IFarmingPlugin.sol index a02ba45..0a8ee8b 100644 --- a/contracts/interfaces/IFarmingPlugin.sol +++ b/contracts/interfaces/IFarmingPlugin.sol @@ -4,7 +4,7 @@ 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"; +import { Farming } from "../accounting/Farming.sol"; interface IFarmingPlugin is IPlugin { event FarmCreated(address token, address reward); @@ -14,7 +14,7 @@ interface IFarmingPlugin is IPlugin { // View functions function totalSupply() external view returns(uint256); function distributor() external view returns(address); - function farmInfo() external view returns(FarmAccounting.Info memory); + function farmInfo() external view returns(Farming.Info memory); function farmed(address account) external view returns(uint256); // User functions diff --git a/contracts/interfaces/IFarmingPool.sol b/contracts/interfaces/IFarmingPool.sol index 180649c..64e0546 100644 --- a/contracts/interfaces/IFarmingPool.sol +++ b/contracts/interfaces/IFarmingPool.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { FarmAccounting } from "../accounting/FarmAccounting.sol"; +import { Farming } from "../accounting/Farming.sol"; interface IFarmingPool is IERC20 { event DistributorChanged(address oldDistributor, address newDistributor); @@ -11,7 +11,7 @@ interface IFarmingPool is IERC20 { // View functions function distributor() external view returns(address); - function farmInfo() external view returns(FarmAccounting.Info memory); + function farmInfo() external view returns(Farming.Info memory); function farmed(address account) external view returns(uint256); // User functions diff --git a/contracts/interfaces/IMultiFarmingPlugin.sol b/contracts/interfaces/IMultiFarmingPlugin.sol index b718b75..402172f 100644 --- a/contracts/interfaces/IMultiFarmingPlugin.sol +++ b/contracts/interfaces/IMultiFarmingPlugin.sol @@ -4,7 +4,7 @@ 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"; +import { Farming } from "../accounting/Farming.sol"; interface IMultiFarmingPlugin is IPlugin { event FarmCreated(address token, address reward); @@ -14,7 +14,7 @@ interface IMultiFarmingPlugin is IPlugin { // View functions function totalSupply() external view returns(uint256); function distributor() external view returns(address); - function farmInfo(IERC20 rewardsToken) external view returns(FarmAccounting.Info memory); + function farmInfo(IERC20 rewardsToken) external view returns(Farming.Info memory); function farmed(IERC20 rewardsToken, address account) external view returns(uint256); // User functions From fa430c6f49a6b825d54fa8f6d7d794f4fa43c378 Mon Sep 17 00:00:00 2001 From: byshape Date: Wed, 27 Sep 2023 12:16:52 +0100 Subject: [PATCH 2/3] Created abstract `Distributor` contract --- contracts/Distributor.sol | 34 +++++++++++++++ contracts/FarmingPlugin.sol | 43 +++++------------- contracts/FarmingPool.sol | 38 ++++------------ contracts/MultiFarmingPlugin.sol | 46 ++++++-------------- contracts/interfaces/IDistributor.sol | 9 ++++ contracts/interfaces/IFarmingPlugin.sol | 15 +++---- contracts/interfaces/IFarmingPool.sol | 14 +++--- contracts/interfaces/IMultiFarmingPlugin.sol | 15 +++---- test/FarmingPlugin.js | 6 +-- test/FarmingPool.js | 6 +-- test/MultiFarmingPlugin.js | 4 +- 11 files changed, 101 insertions(+), 129 deletions(-) create mode 100644 contracts/Distributor.sol create mode 100644 contracts/interfaces/IDistributor.sol diff --git a/contracts/Distributor.sol b/contracts/Distributor.sol new file mode 100644 index 0000000..d2ba71d --- /dev/null +++ b/contracts/Distributor.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { IDistributor } from "./interfaces/IDistributor.sol"; + +abstract contract Distributor is IDistributor, Ownable { + address internal _distributor; + + event DistributorChanged(address oldDistributor, address newDistributor); + + error NotDistributor(); + error SameDistributor(); + error ZeroDistributorAddress(); + + modifier onlyDistributor { + if (msg.sender != _distributor) revert NotDistributor(); + _; + } + + function setDistributor(address distributor_) public virtual onlyOwner { + if (distributor_ == address(0)) revert ZeroDistributorAddress(); + address oldDistributor = _distributor; + if (distributor_ == oldDistributor) revert SameDistributor(); + emit DistributorChanged(oldDistributor, distributor_); + _distributor = distributor_; + } + + function distributor() public view virtual returns (address) { + return _distributor; + } +} diff --git a/contracts/FarmingPlugin.sol b/contracts/FarmingPlugin.sol index 8124ab5..d46e424 100644 --- a/contracts/FarmingPlugin.sol +++ b/contracts/FarmingPlugin.sol @@ -3,37 +3,31 @@ pragma solidity ^0.8.0; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; -import { Plugin } from "@1inch/token-plugins/contracts/Plugin.sol"; import { IERC20Plugins } from "@1inch/token-plugins/contracts/interfaces/IERC20Plugins.sol"; +import { Plugin } from "@1inch/token-plugins/contracts/Plugin.sol"; -import { IFarmingPlugin } from "./interfaces/IFarmingPlugin.sol"; +import { Distributor } from "./Distributor.sol"; import { FarmingLib, Farming } from "./FarmingLib.sol"; +import { IFarmingPlugin } from "./interfaces/IFarmingPlugin.sol"; -contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { - using SafeERC20 for IERC20; - using FarmingLib for FarmingLib.Info; - using Farming for Farming.Info; +contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { using Address for address payable; + using Farming for Farming.Info; + using FarmingLib for FarmingLib.Info; + using SafeERC20 for IERC20; IERC20 public immutable rewardsToken; - address private _distributor; - uint256 private _totalSupply; FarmingLib.Data private _farm; - + uint256 private _totalSupply; + + error InsufficientFunds(); error ZeroFarmableTokenAddress(); error ZeroRewardsTokenAddress(); - error ZeroDistributorAddress(); - error SameDistributor(); - error InsufficientFunds(); - - modifier onlyDistributor { - if (msg.sender != _distributor) revert AccessDenied(); - _; - } constructor(IERC20Plugins farmableToken_, IERC20 rewardsToken_) Plugin(farmableToken_) @@ -57,15 +51,6 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { rewardsToken.safeTransfer(msg.sender, leftover); } } - - function setDistributor(address distributor_) public virtual onlyOwner { - if (distributor_ == address(0)) revert ZeroDistributorAddress(); - address oldDistributor = _distributor; - if (distributor_ == oldDistributor) revert SameDistributor(); - emit DistributorChanged(oldDistributor, distributor_); - _distributor = distributor_; - } - function claim() public virtual { uint256 pluginBalance = IERC20Plugins(token).pluginBalanceOf(address(this), msg.sender); uint256 amount = _makeInfo().claim(msg.sender, pluginBalance); @@ -93,10 +78,6 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Ownable { return _totalSupply; } - function distributor() public view returns(address) { - return _distributor; - } - function farmed(address account) public view virtual returns(uint256) { uint256 balance = IERC20Plugins(token).pluginBalanceOf(address(this), account); return _makeInfo().farmed(account, balance); diff --git a/contracts/FarmingPool.sol b/contracts/FarmingPool.sol index 3e9e021..ab77cb8 100644 --- a/contracts/FarmingPool.sol +++ b/contracts/FarmingPool.sol @@ -2,42 +2,34 @@ pragma solidity ^0.8.0; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { IERC20, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; -import { IFarmingPool } from "./interfaces/IFarmingPool.sol"; +import { Distributor } from "./Distributor.sol"; import { Farming, FarmingLib } from "./FarmingLib.sol"; +import { IFarmingPool } from "./interfaces/IFarmingPool.sol"; -contract FarmingPool is IFarmingPool, Ownable, ERC20 { - using SafeERC20 for IERC20; +contract FarmingPool is IFarmingPool, Distributor, ERC20 { using Address for address payable; using FarmingLib for FarmingLib.Info; + using SafeERC20 for IERC20; uint256 internal constant _MAX_BALANCE = 1e32; - IERC20 public immutable stakingToken; IERC20 public immutable rewardsToken; + IERC20 public immutable stakingToken; - address private _distributor; FarmingLib.Data private _farm; + error InsufficientFunds(); + error MaxBalanceExceeded(); error SameStakingAndRewardsTokens(); error ZeroStakingTokenAddress(); error ZeroRewardsTokenAddress(); - error ZeroDistributorAddress(); - error SameDistributor(); - error AccessDenied(); - error InsufficientFunds(); - error MaxBalanceExceeded(); - - modifier onlyDistributor { - if (msg.sender != _distributor) revert AccessDenied(); - _; - } - + constructor(IERC20Metadata stakingToken_, IERC20 rewardsToken_) ERC20( string(abi.encodePacked("Farming of ", stakingToken_.name())), @@ -51,14 +43,6 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { rewardsToken = rewardsToken_; } - function setDistributor(address distributor_) public virtual onlyOwner { - if (distributor_ == address(0)) revert ZeroDistributorAddress(); - address oldDistributor = _distributor; - if (distributor_ == oldDistributor) revert SameDistributor(); - emit DistributorChanged(oldDistributor, distributor_); - _distributor = distributor_; - } - function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { uint256 reward = _makeInfo().updateFarmData(amount, period); emit RewardUpdated(reward, period); @@ -118,10 +102,6 @@ contract FarmingPool is IFarmingPool, Ownable, ERC20 { return _farm.farmingInfo; } - function distributor() public view virtual returns (address) { - return _distributor; - } - function farmed(address account) public view virtual returns (uint256) { return _makeInfo().farmed(account, balanceOf(account)); } diff --git a/contracts/MultiFarmingPlugin.sol b/contracts/MultiFarmingPlugin.sol index ad1ed65..966ecff 100644 --- a/contracts/MultiFarmingPlugin.sol +++ b/contracts/MultiFarmingPlugin.sol @@ -2,45 +2,39 @@ pragma solidity ^0.8.0; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import { Plugin } from "@1inch/token-plugins/contracts/Plugin.sol"; -import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; + import { AddressArray, AddressSet } from "@1inch/solidity-utils/contracts/libraries/AddressSet.sol"; +import { SafeERC20 } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; import { IERC20Plugins } from "@1inch/token-plugins/contracts/interfaces/IERC20Plugins.sol"; +import { Plugin } from "@1inch/token-plugins/contracts/Plugin.sol"; -import { IMultiFarmingPlugin } from "./interfaces/IMultiFarmingPlugin.sol"; +import { Distributor } from "./Distributor.sol"; import { Farming, FarmingLib } from "./FarmingLib.sol"; +import { IMultiFarmingPlugin } from "./interfaces/IMultiFarmingPlugin.sol"; -contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { - using SafeERC20 for IERC20; - using FarmingLib for FarmingLib.Info; +contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { using Address for address payable; - using AddressSet for AddressSet.Data; using AddressArray for AddressArray.Data; - + using AddressSet for AddressSet.Data; + using FarmingLib for FarmingLib.Info; + using SafeERC20 for IERC20; + uint256 public immutable rewardsTokensLimit; - address private _distributor; - uint256 private _totalSupply; mapping(IERC20 => FarmingLib.Data) private _farms; AddressSet.Data private _rewardsTokens; + uint256 private _totalSupply; + error InsufficientFunds(); error ZeroFarmableTokenAddress(); error ZeroRewardsTokenAddress(); - error ZeroDistributorAddress(); - error SameDistributor(); error RewardsTokenAlreadyAdded(); + error RewardsTokenNotFound(); error RewardsTokensLimitTooHigh(uint256); error RewardsTokensLimitReached(); - error RewardsTokenNotFound(); - error InsufficientFunds(); - - modifier onlyDistributor { - if (msg.sender != _distributor) revert AccessDenied(); - _; - } constructor(IERC20Plugins farmableToken_, uint256 rewardsTokensLimit_) Plugin(farmableToken_) { if (rewardsTokensLimit_ > 5) revert RewardsTokensLimitTooHigh(rewardsTokensLimit_); @@ -53,14 +47,6 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { return _rewardsTokens.items.get(); } - function setDistributor(address distributor_) public virtual onlyOwner { - if (distributor_ == address(0)) revert ZeroDistributorAddress(); - address oldDistributor = _distributor; - if (distributor_ == oldDistributor) revert SameDistributor(); - emit DistributorChanged(oldDistributor, distributor_); - _distributor = distributor_; - } - function addRewardsToken(address rewardsToken) public virtual onlyOwner { if (rewardsToken == address(0)) revert ZeroRewardsTokenAddress(); if (_rewardsTokens.length() == rewardsTokensLimit) revert RewardsTokensLimitReached(); @@ -121,10 +107,6 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Ownable { return _totalSupply; } - function distributor() public view returns(address) { - return _distributor; - } - 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); diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol new file mode 100644 index 0000000..9fa825c --- /dev/null +++ b/contracts/interfaces/IDistributor.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IDistributor { + function setDistributor(address distributor_) external; + + function distributor() external view returns(address); +} diff --git a/contracts/interfaces/IFarmingPlugin.sol b/contracts/interfaces/IFarmingPlugin.sol index 0a8ee8b..37a8d48 100644 --- a/contracts/interfaces/IFarmingPlugin.sol +++ b/contracts/interfaces/IFarmingPlugin.sol @@ -8,22 +8,17 @@ import { Farming } from "../accounting/Farming.sol"; interface IFarmingPlugin is IPlugin { event FarmCreated(address token, address reward); - event DistributorChanged(address oldDistributor, address newDistributor); event RewardUpdated(uint256 reward, uint256 duration); - // View functions - function totalSupply() external view returns(uint256); - function distributor() external view returns(address); - function farmInfo() external view returns(Farming.Info memory); - function farmed(address account) external view returns(uint256); - // User functions function claim() external; - // Owner functions - function setDistributor(address distributor_) external; - // Distributor functions function startFarming(uint256 amount, uint256 period) external; function rescueFunds(IERC20 token, uint256 amount) external; + + // View functions + function totalSupply() external view returns(uint256); + function farmInfo() external view returns(Farming.Info memory); + function farmed(address account) external view returns(uint256); } diff --git a/contracts/interfaces/IFarmingPool.sol b/contracts/interfaces/IFarmingPool.sol index 64e0546..dca9510 100644 --- a/contracts/interfaces/IFarmingPool.sol +++ b/contracts/interfaces/IFarmingPool.sol @@ -6,24 +6,20 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Farming } from "../accounting/Farming.sol"; interface IFarmingPool is IERC20 { - event DistributorChanged(address oldDistributor, address newDistributor); event RewardUpdated(uint256 reward, uint256 duration); - // View functions - function distributor() external view returns(address); - function farmInfo() external view returns(Farming.Info memory); - function farmed(address account) external view returns(uint256); - // User functions function deposit(uint256 amount) external; function withdraw(uint256 amount) external; function claim() external; function exit() external; - // Owner functions - function setDistributor(address distributor_) external; - // Distributor functions function startFarming(uint256 amount, uint256 period) external; + function stopFarming() external; function rescueFunds(IERC20 token, uint256 amount) external; + + // View functions + function farmInfo() external view returns(Farming.Info memory); + function farmed(address account) external view returns(uint256); } diff --git a/contracts/interfaces/IMultiFarmingPlugin.sol b/contracts/interfaces/IMultiFarmingPlugin.sol index 402172f..1044735 100644 --- a/contracts/interfaces/IMultiFarmingPlugin.sol +++ b/contracts/interfaces/IMultiFarmingPlugin.sol @@ -8,23 +8,18 @@ import { Farming } from "../accounting/Farming.sol"; interface IMultiFarmingPlugin is IPlugin { event FarmCreated(address token, address reward); - event DistributorChanged(address oldDistributor, address newDistributor); event RewardUpdated(address token, uint256 reward, uint256 duration); - // View functions - function totalSupply() external view returns(uint256); - function distributor() external view returns(address); - function farmInfo(IERC20 rewardsToken) external view returns(Farming.Info memory); - function farmed(IERC20 rewardsToken, address account) external view returns(uint256); - // User functions function claim(IERC20 rewardsToken) external; function claim() external; - // Owner functions - function setDistributor(address distributor_) external; - // Distributor functions function startFarming(IERC20 rewardsToken, uint256 amount, uint256 period) external; function rescueFunds(IERC20 token, uint256 amount) external; + + // View functions + function totalSupply() external view returns(uint256); + function farmInfo(IERC20 rewardsToken) external view returns(Farming.Info memory); + function farmed(IERC20 rewardsToken, address account) external view returns(uint256); } diff --git a/test/FarmingPlugin.js b/test/FarmingPlugin.js index 9606ea0..a6b2dd6 100644 --- a/test/FarmingPlugin.js +++ b/test/FarmingPlugin.js @@ -62,7 +62,7 @@ describe('FarmingPlugin', function () { const { farm } = await loadFixture(initContracts); await expect( farm.connect(wallet2).startFarming(1000, 60 * 60 * 24), - ).to.be.revertedWithCustomError(farm, 'AccessDenied'); + ).to.be.revertedWithCustomError(farm, 'NotDistributor'); }); /* @@ -243,7 +243,7 @@ describe('FarmingPlugin', function () { expect(wallet2).to.not.equal(distributor); await expect( farm.connect(wallet2).stopFarming(), - ).to.be.revertedWithCustomError(farm, 'AccessDenied'); + ).to.be.revertedWithCustomError(farm, 'NotDistributor'); }); /* @@ -371,7 +371,7 @@ describe('FarmingPlugin', function () { expect(wallet2).to.not.equal(distributor); await expect( farm.connect(wallet2).rescueFunds(gift, '1000'), - ).to.be.revertedWithCustomError(farm, 'AccessDenied'); + ).to.be.revertedWithCustomError(farm, 'NotDistributor'); }); /* diff --git a/test/FarmingPool.js b/test/FarmingPool.js index 064bdef..051718d 100644 --- a/test/FarmingPool.js +++ b/test/FarmingPool.js @@ -52,7 +52,7 @@ describe('FarmingPool', function () { const { farm } = await loadFixture(initContracts); await expect( farm.connect(wallet2).startFarming(1000, 60 * 60 * 24), - ).to.be.revertedWithCustomError(farm, 'AccessDenied'); + ).to.be.revertedWithCustomError(farm, 'NotDistributor'); }); }); @@ -515,7 +515,7 @@ describe('FarmingPool', function () { expect(wallet2.address).to.not.equal(distributor); await expect( farm.connect(wallet2).stopFarming(), - ).to.be.revertedWithCustomError(farm, 'AccessDenied'); + ).to.be.revertedWithCustomError(farm, 'NotDistributor'); }); it('should transfer tokens from farm to wallet', async function () { @@ -582,7 +582,7 @@ describe('FarmingPool', function () { expect(wallet2.address).to.not.equal(distributor); await expect( farm.connect(wallet2).rescueFunds(gift, '1000'), - ).to.be.revertedWithCustomError(farm, 'AccessDenied'); + ).to.be.revertedWithCustomError(farm, 'NotDistributor'); }); it('should thrown with not enough balance for staking token', async function () { diff --git a/test/MultiFarmingPlugin.js b/test/MultiFarmingPlugin.js index 77d188e..0c0761a 100644 --- a/test/MultiFarmingPlugin.js +++ b/test/MultiFarmingPlugin.js @@ -104,7 +104,7 @@ describe('MultiFarmingPlugin', function () { expect(wallet2).to.not.equal(distributor); await expect( multiFarm.connect(wallet2).stopFarming(gift), - ).to.be.revertedWithCustomError(multiFarm, 'AccessDenied'); + ).to.be.revertedWithCustomError(multiFarm, 'NotDistributor'); }); it('should be reverted because of an invalid reward token', async function () { @@ -168,7 +168,7 @@ describe('MultiFarmingPlugin', function () { expect(wallet2).to.not.equal(distributor); await expect( multiFarm.connect(wallet2).rescueFunds(gift, '1000'), - ).to.be.revertedWithCustomError(multiFarm, 'AccessDenied'); + ).to.be.revertedWithCustomError(multiFarm, 'NotDistributor'); }); it('should thrown with insufficient funds', async function () { From ad36f5561731c7bb05e8ed82e5f6e5ba4984609e Mon Sep 17 00:00:00 2001 From: byshape Date: Wed, 27 Sep 2023 19:53:09 +0100 Subject: [PATCH 3/3] Added docs --- README.md | 12 +++-- contracts/FarmingLib.sol | 47 ++++++++++---------- contracts/FarmingPlugin.sol | 27 +++++++++++ contracts/FarmingPool.sol | 35 +++++++++++++++ contracts/MultiFarmingPlugin.sol | 39 +++++++++++++++- contracts/interfaces/IFarmingPlugin.sol | 31 ++++++++++++- contracts/interfaces/IFarmingPool.sol | 36 +++++++++++++++ contracts/interfaces/IMultiFarmingPlugin.sol | 43 +++++++++++++++++- 8 files changed, 239 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b63893e..84a97b0 100644 --- a/README.md +++ b/README.md @@ -113,22 +113,28 @@ contract AMMPoolToken is ERC20Plugins { Storage access: -- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/FarmAccounting.sol#L14-L16) for farming params, updated only on farming restarting: +- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/Farming.sol#L9-L11) for farming params, updated when farming is restarted or stopped: ```solidity uint40 public finished; uint32 public duration; uint184 public reward; ``` + +- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/Farming.sol#L9-L12) for total amount of rewards that haven't been claimed yet, updated when farming is restarted, stopped or when the user claims rewards: + + ```solidity + uint256 balance; + ``` -- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/UserAccounting.sol#L7-L8) for farming state, updated only on changing number of farming tokens: +- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/Rewards.sol#L9-L10) for farming state, updated only on changing number of farming tokens: ```solidity uint40 public checkpoint; uint216 public farmedPerTokenStored; ``` -- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/UserAccounting.sol#L9) per each farmer, updated on deposits/withdrawals (kudos to [@snjax](https://github.com/snjax)): +- [1 storage slot](https://github.com/1inch/farming/blob/master/contracts/accounting/Rewards.sol#L11) per each farmer, updated on deposits/withdrawals (kudos to [@snjax](https://github.com/snjax)): ```solidity mapping(address => int256) public corrections; diff --git a/contracts/FarmingLib.sol b/contracts/FarmingLib.sol index 11e1654..e352a13 100644 --- a/contracts/FarmingLib.sol +++ b/contracts/FarmingLib.sol @@ -12,8 +12,7 @@ library FarmingLib { using Rewards for Rewards.Info; using FarmingLib for FarmingLib.Info; - // TODO: check all docs and update - /// @dev Struct containing farm and user detailed info for farming operations. See {Farming.Info} and {Rewards.Info} for. + /// @dev Struct containing farming and rewards detailed info for farming operations. See {Farming.Info} and {Rewards.Info}. struct Data { Farming.Info farmingInfo; Rewards.Info rewardsInfo; @@ -26,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. @@ -41,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) { @@ -53,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 updateFarmData(Info memory self, uint256 amount, uint256 period) internal returns(uint256 reward) { Data storage data = self.getData(); @@ -77,11 +76,11 @@ library FarmingLib { } /** - * @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(); @@ -94,10 +93,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 { @@ -105,11 +104,11 @@ 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().rewardsInfo.farmed(account, balance, _farmedPerToken(self)); diff --git a/contracts/FarmingPlugin.sol b/contracts/FarmingPlugin.sol index d46e424..8e9f8f5 100644 --- a/contracts/FarmingPlugin.sol +++ b/contracts/FarmingPlugin.sol @@ -14,6 +14,11 @@ import { Distributor } from "./Distributor.sol"; import { FarmingLib, Farming } from "./FarmingLib.sol"; import { IFarmingPlugin } from "./interfaces/IFarmingPlugin.sol"; +/** + * @title Plugin for farming reward tokens. + * @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 Address for address payable; using Farming for Farming.Info; @@ -38,12 +43,18 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { emit FarmCreated(address(farmableToken_), address(rewardsToken_)); } + /** + * @notice See {IFarmingPlugin-startFarming} + */ function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { uint256 reward = _makeInfo().updateFarmData(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().cancelFarming(); emit RewardUpdated(0, 0); @@ -51,6 +62,10 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { rewardsToken.safeTransfer(msg.sender, leftover); } } + + /** + * @notice See {IFarmingPlugin-claim} + */ function claim() public virtual { uint256 pluginBalance = IERC20Plugins(token).pluginBalanceOf(address(this), msg.sender); uint256 amount = _makeInfo().claim(msg.sender, pluginBalance); @@ -59,6 +74,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); @@ -70,14 +88,23 @@ contract FarmingPlugin is Plugin, IFarmingPlugin, Distributor { } } + /** + * @notice See {IFarmingPlugin-farmInfo} + */ function farmInfo() public view returns(Farming.Info memory) { return _farm.farmingInfo; } + /** + * @notice See {IFarmingPlugin-totalSupply} + */ function totalSupply() public view returns(uint256) { return _totalSupply; } + /** + * @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); diff --git a/contracts/FarmingPool.sol b/contracts/FarmingPool.sol index ab77cb8..e864767 100644 --- a/contracts/FarmingPool.sol +++ b/contracts/FarmingPool.sol @@ -12,6 +12,11 @@ import { Distributor } from "./Distributor.sol"; import { Farming, FarmingLib } from "./FarmingLib.sol"; import { IFarmingPool } from "./interfaces/IFarmingPool.sol"; +/** + * @title Contract for farming reward tokens, required for farming tokens that don't support plugins. + * @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 Address for address payable; using FarmingLib for FarmingLib.Info; @@ -43,12 +48,18 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { rewardsToken = rewardsToken_; } + /** + * @notice See {IFarmingPool-startFarming} + */ function startFarming(uint256 amount, uint256 period) public virtual onlyDistributor { uint256 reward = _makeInfo().updateFarmData(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().cancelFarming(); emit RewardUpdated(0, 0); @@ -57,17 +68,26 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { } } + /** + * @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) { @@ -75,11 +95,17 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { } } + /** + * @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); @@ -94,14 +120,23 @@ contract FarmingPool is IFarmingPool, Distributor, ERC20 { } } + /** + * @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(Farming.Info memory) { return _farm.farmingInfo; } + /** + * @notice See {IFarmingPool-farmed} + */ function farmed(address account) public view virtual returns (uint256) { return _makeInfo().farmed(account, balanceOf(account)); } diff --git a/contracts/MultiFarmingPlugin.sol b/contracts/MultiFarmingPlugin.sol index 966ecff..9ec61f1 100644 --- a/contracts/MultiFarmingPlugin.sol +++ b/contracts/MultiFarmingPlugin.sol @@ -15,6 +15,11 @@ import { Distributor } from "./Distributor.sol"; import { Farming, FarmingLib } from "./FarmingLib.sol"; import { IMultiFarmingPlugin } from "./interfaces/IMultiFarmingPlugin.sol"; +/** + * @title Plugin for farming multiple reward tokens. + * @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 Address for address payable; using AddressArray for AddressArray.Data; @@ -29,12 +34,12 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { uint256 private _totalSupply; error InsufficientFunds(); - error ZeroFarmableTokenAddress(); - error ZeroRewardsTokenAddress(); error RewardsTokenAlreadyAdded(); error RewardsTokenNotFound(); error RewardsTokensLimitTooHigh(uint256); error RewardsTokensLimitReached(); + error ZeroFarmableTokenAddress(); + error ZeroRewardsTokenAddress(); constructor(IERC20Plugins farmableToken_, uint256 rewardsTokensLimit_) Plugin(farmableToken_) { if (rewardsTokensLimit_ > 5) revert RewardsTokensLimitTooHigh(rewardsTokensLimit_); @@ -43,10 +48,16 @@ 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-addRewardsToken} + */ function addRewardsToken(address rewardsToken) public virtual onlyOwner { if (rewardsToken == address(0)) revert ZeroRewardsTokenAddress(); if (_rewardsTokens.length() == rewardsTokensLimit) revert RewardsTokensLimitReached(); @@ -54,6 +65,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(); @@ -62,6 +76,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(); @@ -72,11 +89,17 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { } } + /** + * @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(); @@ -88,6 +111,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); @@ -99,14 +125,23 @@ contract MultiFarmingPlugin is Plugin, IMultiFarmingPlugin, Distributor { } } + /** + * @notice See {IMultiFarmingPlugin-farmInfo} + */ function farmInfo(IERC20 rewardsToken) public view returns(Farming.Info memory) { return _farms[rewardsToken].farmingInfo; } + /** + * @notice See {IMultiFarmingPlugin-totalSupply} + */ function totalSupply() public view returns(uint256) { return _totalSupply; } + /** + * @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); diff --git a/contracts/interfaces/IFarmingPlugin.sol b/contracts/interfaces/IFarmingPlugin.sol index 37a8d48..624560d 100644 --- a/contracts/interfaces/IFarmingPlugin.sol +++ b/contracts/interfaces/IFarmingPlugin.sol @@ -7,18 +7,47 @@ import { IPlugin } from "@1inch/token-plugins/contracts/interfaces/IPlugin.sol"; import { Farming } from "../accounting/Farming.sol"; 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); // 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; - function rescueFunds(IERC20 token, uint256 amount) 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; // View functions + /** + * Returns the number of farmable tokens counted by this plugin. + */ function totalSupply() external view returns(uint256); + /** + * @notice Gets information about the current farm. + */ function farmInfo() external view returns(Farming.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); } diff --git a/contracts/interfaces/IFarmingPool.sol b/contracts/interfaces/IFarmingPool.sol index dca9510..174fcd6 100644 --- a/contracts/interfaces/IFarmingPool.sol +++ b/contracts/interfaces/IFarmingPool.sol @@ -6,20 +6,56 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Farming } from "../accounting/Farming.sol"; interface IFarmingPool is IERC20 { + // Emitted when farming parameters are updated. event RewardUpdated(uint256 reward, uint256 duration); // 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; // View functions + /** + * @notice Gets information about the current farm. + */ function farmInfo() external view returns(Farming.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); } diff --git a/contracts/interfaces/IMultiFarmingPlugin.sol b/contracts/interfaces/IMultiFarmingPlugin.sol index 1044735..b19c768 100644 --- a/contracts/interfaces/IMultiFarmingPlugin.sol +++ b/contracts/interfaces/IMultiFarmingPlugin.sol @@ -7,19 +7,60 @@ import { IPlugin } from "@1inch/token-plugins/contracts/interfaces/IPlugin.sol"; import { Farming } from "../accounting/Farming.sol"; 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); // 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; - function rescueFunds(IERC20 token, uint256 amount) 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; + /** + * @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; // View functions + /** + * Returns the number of farmable tokens counted by this plugin. + */ function totalSupply() external view returns(uint256); + /** + * @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(Farming.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); }