Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Remove discounted fee calculation logic from Deployers and Farm factory updates #22

Merged
merged 8 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ Demeter automates the fundamental aspects of launching and managing decentralize

- **Engineering support to launch and manage the farm** - The Audited Demeter Farm Factory contract will generate the pool and farm contracts for the Demeter user.
- **Marketing support to make the community aware of the new farm** - Protocols that launch their farm through Demeter benefit from being whitelisted on the Demeter active farms dashboard. This exclusive list features all of the farms that are actively distributing rewards that were deployed with Demeter. Farmers will regularly look to this dashboard for new projects and become users of these protocols.
- **Financial support through SPA incentives** - When paired against USDs or SPA, Demeter farms will receive fixed SPA emissions from the SPA gauge, boosting the farm APR. Fee for launching farms will also be discounted for these farms.

### Launching Demeter Farm

1. Approve USDs spend
1. Approve fee token to spend
1. Input pool parameters and farm parameters and execute a transaction to create farm and pay the farm creation fee
1. If pool does not exist then the transaction reverts.
1. If the user does not have enough USDs for fee payment then the transaction reverts. The fee is 100 USDs. If one of the pool tokens is SPA or USDs, then a discounted fee of 50 USDs is applicable.
1. If the user does not have enough fee tokens for fee payment then the transaction reverts. The fee is 100 USDs on Arbitrum.
1. After creation of the farm contract, reward token managers can update reward related parameters.
1. After creation of the farm contract, the farm admin can manage some attributes of the farm like start date, cooldown period, close/pause farm etc.

Expand All @@ -45,7 +44,7 @@ Demeter automates the fundamental aspects of launching and managing decentralize

### Fee

Demeter will charge a flat $100 fee to launch the farm. When paired against USDs or SPA, the fee will discounted, only $50. The fee collected belongs to the SPA stakers and can be transferred directly to the wallet address where all Sperax protocol fees are collected. Fees have to be paid in USDs in the beginning, more payment methods can be added in future. Fee amount can be changed in future through governance.
Demeter will charge a flat $100 fee to launch the farm. The fee collected belongs to the SPA stakers and can be transferred directly to the wallet address where all Sperax protocol fees are collected. Fees have to be paid in USDs on Arbitrum in the beginning, more payment methods can be added in future. Fee amount and fee token can be changed in future through governance.

### Farm Management

Expand Down
73 changes: 20 additions & 53 deletions contracts/BaseFarmDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeE
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IFarmFactory} from "./interfaces/IFarmFactory.sol";

/// @title BaseFarmDeployer contract of Demeter Protocol
/// @notice Exposes base functionalities which will be useful in every deployer
/// @author Sperax Foundation
abstract contract BaseFarmDeployer is Ownable {
using SafeERC20 for IERC20;

Expand All @@ -13,84 +16,48 @@ abstract contract BaseFarmDeployer is Ownable {
address public immutable FACTORY;
// Stores the address of farmImplementation.
address public farmImplementation;
uint256 public discountedFee;

event FarmCreated(address farm, address creator, address indexed admin);
event FeeCollected(address indexed creator, address token, uint256 amount, bool indexed claimable);
event FeeCollected(address indexed creator, address token, uint256 amount);
event FarmImplementationUpdated(address newFarmImplementation);
event DiscountedFeeUpdated(uint256 oldDiscountedFee, uint256 newDiscountedFee);

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

constructor(address _factory) {
_isNonZeroAddr(_factory);
FACTORY = _factory;
}

/// @notice Update farm implementation's address
/// @dev only callable by owner
/// @param _newFarmImplementation New farm implementation's address
function updateFarmImplementation(address _newFarmImplementation) external onlyOwner {
farmImplementation = _newFarmImplementation;
emit FarmImplementationUpdated(_newFarmImplementation);
}

/// @notice An external function to update discountOnSpaUSDsFarms
/// @param _discountedFee New desired discount on Spa/ USDs farms
/// @dev _discountedFee cannot be more than 100
function updateDiscountedFee(uint256 _discountedFee) external onlyOwner {
emit DiscountedFeeUpdated(discountedFee, _discountedFee);
discountedFee = _discountedFee;
}

/// @notice A public view function to calculate fees
/// @param _tokenA address of token A
/// @param _tokenB address of token B
/// @notice Order does not matter
/// @return Fees to be paid in feeToken set in FarmFactory (mostly USDs)
function calculateFees(address _tokenA, address _tokenB) external view returns (address, address, uint256, bool) {
_isNonZeroAddr(_tokenA);
_isNonZeroAddr(_tokenB);
if (_tokenA == _tokenB) {
revert InvalidTokenPair();
}
return _calculateFees(_tokenA, _tokenB);
/// @notice A public view function to get fees from Farm Factory
/// @return feeReceiver of feeToken in feeAmount
function getFees(address _deployerAccount)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark this function as external

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved 06b706b

external
view
returns (address feeReceiver, address feeToken, uint256 feeAmount)
{
(feeReceiver, feeToken, feeAmount) = IFarmFactory(FACTORY).getFeeParams(_deployerAccount);
}

/// @notice Collect fee and transfer it to feeReceiver.
/// @dev Function fetches all the fee params from sfarmFactory.
function _collectFee(address _tokenA, address _tokenB) internal virtual {
(address feeReceiver, address feeToken, uint256 feeAmount, bool claimable) = _calculateFees(_tokenA, _tokenB);
/// @dev Function fetches all the fee params from farmFactory.
function _collectFee() internal virtual {
// Here msg.sender would be the deployer/creator of the farm which will be checked in privileged deployer list
(address feeReceiver, address feeToken, uint256 feeAmount) = IFarmFactory(FACTORY).getFeeParams(msg.sender);
if (feeAmount != 0) {
IERC20(feeToken).safeTransferFrom(msg.sender, feeReceiver, feeAmount);
emit FeeCollected(msg.sender, feeToken, feeAmount, claimable);
emit FeeCollected(msg.sender, feeToken, feeAmount);
}
}

/// @notice An internal function to calculate fees
/// @notice and return feeReceiver, feeToken, feeAmount and claimable
function _calculateFees(address _tokenA, address _tokenB) internal view returns (address, address, uint256, bool) {
(address feeReceiver, address feeToken, uint256 feeAmount) = IFarmFactory(FACTORY).getFeeParams();
if (IFarmFactory(FACTORY).isPrivilegedDeployer(msg.sender)) {
// No fees for privileged deployers
feeAmount = 0;
return (feeReceiver, feeToken, feeAmount, false);
}
if (_checkToken(_tokenA) || _checkToken(_tokenB)) {
// DiscountedFee if either of the token is SPA or USDs
// This fees is claimable
return (feeReceiver, feeToken, discountedFee, true);
} else {
// No discount because neither of the token is SPA or USDs
return (feeReceiver, feeToken, feeAmount, false);
}
}

/// @notice Check if a token is either SPA | USDs.
/// @param _token Address of the desired token.
function _checkToken(address _token) internal pure returns (bool) {
return _token == SPA || _token == USDS;
}

/// @notice Validate address
function _isNonZeroAddr(address _addr) internal pure {
if (_addr == address(0)) {
Expand Down
26 changes: 15 additions & 11 deletions contracts/FarmFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pragma solidity 0.8.16;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

/// @title Farm Factory contract of Demeter Protocol
/// @notice This contract tracks fee details, privileged deployers, deployed farms and farm deployers
/// @author Sperax Foundation
contract FarmFactory is OwnableUpgradeable {
address public feeReceiver;
address public feeToken;
Expand All @@ -30,8 +33,7 @@ contract FarmFactory is OwnableUpgradeable {
mapping(address => bool) public isPrivilegedDeployer;

event FarmRegistered(address indexed farm, address indexed creator, address indexed deployer);
event FarmDeployerRegistered(address deployer);
event FarmDeployerRemoved(address deployer);
event FarmDeployerUpdated(address deployer, bool registered);
event FeeParamsUpdated(address receiver, address token, uint256 amount);
event PrivilegeUpdated(address deployer, bool privilege);

Expand All @@ -40,7 +42,6 @@ contract FarmFactory is OwnableUpgradeable {
error DeployerAlreadyRegistered();
error InvalidDeployerId();
error PrivilegeSameAsDesired();
error FeeCannotBeZero();
error InvalidAddress();

// Disable initialization for the implementation contract
Expand All @@ -51,6 +52,7 @@ contract FarmFactory is OwnableUpgradeable {
/// @notice constructor
/// @param _feeToken The fee token for farm creation.
/// @param _feeAmount The fee amount to be paid by the creator.
/// @param _feeReceiver Receiver of the fees
function initialize(address _feeReceiver, address _feeToken, uint256 _feeAmount) external initializer {
OwnableUpgradeable.__Ownable_init();
updateFeeParams(_feeReceiver, _feeToken, _feeAmount);
Expand Down Expand Up @@ -78,7 +80,7 @@ contract FarmFactory is OwnableUpgradeable {
}
deployerList.push(_deployer);
deployerRegistered[_deployer] = true;
emit FarmDeployerRegistered(_deployer);
emit FarmDeployerUpdated(_deployer, true);
}

/// @notice Remove an existing deployer from factory
Expand All @@ -93,7 +95,7 @@ contract FarmFactory is OwnableUpgradeable {
deployerList[_id] = deployerList[numDeployer - 1];
deployerList.pop();

emit FarmDeployerRemoved(deployer);
emit FarmDeployerUpdated(deployer, false);
}

/// @notice A function to add/ remove privileged deployer
Expand All @@ -120,9 +122,14 @@ contract FarmFactory is OwnableUpgradeable {
return farms;
}

/// @notice Get all the fee parameters for creating farm.
/// @return Returns FeeReceiver, feeToken address and feeTokenAmt.
function getFeeParams() external view returns (address, address, uint256) {
/// @notice Get all the fee parameters for creating farm
/// @param _deployerAccount The account creating the farm
/// @return Returns FeeReceiver, feeToken address and feeTokenAmt
/// @dev It returns fee amount as 0 if deployer account is privileged
function getFeeParams(address _deployerAccount) external view returns (address, address, uint256) {
if (isPrivilegedDeployer[_deployerAccount]) {
return (feeReceiver, feeToken, 0);
}
return (feeReceiver, feeToken, feeAmount);
}

Expand All @@ -133,9 +140,6 @@ contract FarmFactory is OwnableUpgradeable {
function updateFeeParams(address _receiver, address _feeToken, uint256 _amount) public onlyOwner {
_isNonZeroAddr(_receiver);
_isNonZeroAddr(_feeToken);
if (_amount == 0) {
revert FeeCannotBeZero();
}
feeReceiver = _receiver;
feeToken = _feeToken;
feeAmount = _amount;
Expand Down
3 changes: 1 addition & 2 deletions contracts/camelot/Demeter_CamelotFarm_Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ contract Demeter_CamelotFarm_Deployer is BaseFarmDeployer, ReentrancyGuard {
constructor(address _factory, address _protocolFactory) BaseFarmDeployer(_factory) {
_isNonZeroAddr(_protocolFactory);
PROTOCOL_FACTORY = _protocolFactory;
discountedFee = 50e18; // 50 USDs
farmImplementation = address(new Demeter_CamelotFarm());
}

Expand All @@ -66,7 +65,7 @@ contract Demeter_CamelotFarm_Deployer is BaseFarmDeployer, ReentrancyGuard {
farmInstance.transferOwnership(_data.farmAdmin);
address farm = address(farmInstance);
// Calculate and collect fee if required
_collectFee(_data.camelotPoolData.tokenA, _data.camelotPoolData.tokenB);
_collectFee();
emit FarmCreated(farm, msg.sender, _data.farmAdmin);
IFarmFactory(FACTORY).registerFarm(farm, msg.sender);
return farm;
Expand Down
52 changes: 1 addition & 51 deletions contracts/e20-farms/balancer/Demeter_BalancerFarm_Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ contract Demeter_BalancerFarm_Deployer is BaseFarmDeployer, ReentrancyGuard {

BALANCER_VAULT = _balancerVault;
DEPLOYER_NAME = _deployerName;
discountedFee = 50e18; // 50 USDs
farmImplementation = address(new Demeter_BalancerFarm());
}

Expand All @@ -76,11 +75,9 @@ contract Demeter_BalancerFarm_Deployer is BaseFarmDeployer, ReentrancyGuard {
_isNonZeroAddr(_data.farmAdmin);

address pairPool = validatePool(_data.poolId);
IERC20[] memory tokens;
(tokens,,) = IBalancerVault(BALANCER_VAULT).getPoolTokens(_data.poolId);

// Calculate and collect fee if required
_collectFee(tokens);
_collectFee();

Demeter_BalancerFarm farmInstance = Demeter_BalancerFarm(Clones.clone(farmImplementation));
farmInstance.initialize(_data.farmStartTime, _data.cooldownPeriod, pairPool, _data.rewardData);
Expand All @@ -91,57 +88,10 @@ contract Demeter_BalancerFarm_Deployer is BaseFarmDeployer, ReentrancyGuard {
return farm;
}

/// @notice An external function to calculate fees when pool tokens are in array.
/// @param _tokens Array of addresses of tokens
/// @return feeReceiver's address, feeToken's address, feeAmount, boolean claimable
function calculateFees(IERC20[] memory _tokens) external view returns (address, address, uint256, bool) {
return _calculateFees(_tokens);
}

/// @notice A function to validate Balancer pool
/// @param _poolId bytes32 Id of the pool
function validatePool(bytes32 _poolId) public view returns (address pool) {
(pool,) = IBalancerVault(BALANCER_VAULT).getPool(_poolId);
_isNonZeroAddr(pool);
}

/// @notice An internal function to calculate fees when tokens are passed as an array
/// @param _tokens Array of token addresses
/// @return feeReceiver's address, feeToken's address, feeAmount, boolean claimable
function _calculateFees(IERC20[] memory _tokens) internal view returns (address, address, uint256, bool) {
uint8 tokensLen = uint8(_tokens.length);

if (tokensLen == 0) {
revert InvalidTokens();
}

(address feeReceiver, address feeToken, uint256 feeAmount) = IFarmFactory(FACTORY).getFeeParams();
if (IFarmFactory(FACTORY).isPrivilegedDeployer(msg.sender)) {
// No fees for privileged deployers
feeAmount = 0;
return (feeReceiver, feeToken, feeAmount, false);
}
for (uint8 i; i < tokensLen;) {
if (_checkToken(address(_tokens[i]))) {
// DiscountedFee if either of the token is SPA or USDs
// This fees is claimable
return (feeReceiver, feeToken, discountedFee, true);
}
unchecked {
++i;
}
}
// No discount because neither of the token is SPA or USDs
return (feeReceiver, feeToken, feeAmount, false);
}

/// @notice A function to collect the fees
/// @param _tokens Array of token addresses
function _collectFee(IERC20[] memory _tokens) private {
(address feeReceiver, address feeToken, uint256 feeAmount, bool claimable) = _calculateFees(_tokens);
if (feeAmount > 0) {
IERC20(feeToken).safeTransferFrom(msg.sender, feeReceiver, feeAmount);
emit FeeCollected(msg.sender, feeToken, feeAmount, claimable);
}
}
}
3 changes: 1 addition & 2 deletions contracts/e20-farms/uniswapV2/Demeter_UniV2FarmDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ contract Demeter_UniV2FarmDeployer is BaseFarmDeployer, ReentrancyGuard {
_isNonZeroAddr(_protocolFactory);
PROTOCOL_FACTORY = _protocolFactory;
DEPLOYER_NAME = _deployerName;
discountedFee = 100e18; // 100 USDs
farmImplementation = address(new Demeter_E20_farm());
}

Expand All @@ -72,7 +71,7 @@ contract Demeter_UniV2FarmDeployer is BaseFarmDeployer, ReentrancyGuard {
farmInstance.transferOwnership(_data.farmAdmin);
address farm = address(farmInstance);
// Calculate and collect fee if required
_collectFee(_data.camelotPoolData.tokenA, _data.camelotPoolData.tokenB);
_collectFee();
IFarmFactory(FACTORY).registerFarm(farm, msg.sender);
emit FarmCreated(farm, msg.sender, _data.farmAdmin);
return farm;
Expand Down
5 changes: 4 additions & 1 deletion contracts/interfaces/IFarmFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ interface IFarmFactory {

function registerFarmDeployer(address _deployer) external;

function getFeeParams() external view returns (address feeFeceiver, address feeToken, uint256 feeAmount);
function getFeeParams(address _deployerAccount)
external
view
returns (address feeFeceiver, address feeToken, uint256 feeAmount);

function isPrivilegedDeployer(address _user) external view returns (bool);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ contract Demeter_UniV3FarmDeployer is BaseFarmDeployer, ReentrancyGuard {
string public constant DEPLOYER_NAME = "Demeter_SushiV3FarmDeployer_v3";

constructor(address _factory) BaseFarmDeployer(_factory) {
discountedFee = 50e18; // 50 USDs
farmImplementation = address(new Demeter_SushiV3Farm());
}

Expand All @@ -55,7 +54,7 @@ contract Demeter_UniV3FarmDeployer is BaseFarmDeployer, ReentrancyGuard {
farmInstance.transferOwnership(_data.farmAdmin);
address farm = address(farmInstance);
// Calculate and collect fee if required
_collectFee(_data.uniswapPoolData.tokenA, _data.uniswapPoolData.tokenB);
_collectFee();
emit FarmCreated(farm, msg.sender, _data.farmAdmin);
IFarmFactory(FACTORY).registerFarm(farm, msg.sender);
return farm;
Expand Down
3 changes: 1 addition & 2 deletions contracts/uniswapV3/uniswap/Demeter_UniV3FarmDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ contract Demeter_UniV3FarmDeployer is BaseFarmDeployer, ReentrancyGuard {
string public constant DEPLOYER_NAME = "Demeter_UniV3FarmDeployer_v3";

constructor(address _factory) BaseFarmDeployer(_factory) {
discountedFee = 50e18; // 50 USDs
farmImplementation = address(new Demeter_UniV3Farm());
}

Expand All @@ -55,7 +54,7 @@ contract Demeter_UniV3FarmDeployer is BaseFarmDeployer, ReentrancyGuard {
farmInstance.transferOwnership(_data.farmAdmin);
address farm = address(farmInstance);
// Calculate and collect fee if required
_collectFee(_data.uniswapPoolData.tokenA, _data.uniswapPoolData.tokenB);
_collectFee();
emit FarmCreated(farm, msg.sender, _data.farmAdmin);
IFarmFactory(FACTORY).registerFarm(farm, msg.sender);
return farm;
Expand Down
Loading