Skip to content
This repository has been archived by the owner on May 5, 2023. It is now read-only.

Feat/farm #1

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ report/

# Node.js deps
node_modules/

# remappings
remappings.txt
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
1 change: 1 addition & 0 deletions lib/solmate
Submodule solmate added at 2001af
206 changes: 144 additions & 62 deletions src/Farm.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
pragma solidity ^0.8.14;

import {GemAbstract} from "dss-interfaces/ERC/GemAbstract.sol";
import {SafeMath} from "./utils/SafeMath.sol";
import {ReentrancyGuard} from "./utils/ReentrancyGuard.sol";

contract Farm is ReentrancyGuard {
using SafeMath for uint256;

contract Farm {
GemAbstract public immutable rewardGem;
GemAbstract public immutable gem;

Expand All @@ -15,17 +11,16 @@ contract Farm is ReentrancyGuard {
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;

uint256 public live;
uint256 public periodFinish = 0;
uint256 public rewardRate = 0;
uint256 public rewardsDuration = 7 days;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint public lastPauseTime;
bool public paused;
address public rewardsDistribution;

uint256 private _totalSupply;
mapping(address => uint256) private _balances;
uint256 private _totalSupply;

/**
* @notice `usr` was granted admin access.
Expand All @@ -38,12 +33,30 @@ contract Farm is ReentrancyGuard {
* @param usr The user address.
*/
event Deny(address indexed usr);
event PauseChanged(bool isPaused);
/**
* @notice A contract parameter was updated.
* @param what The changed parameter name. Currently the supported values are: "rewardDuration".
* @param data The new value of the parameter.
*/
event File(bytes32 indexed what, uint256 data);
/**
* @notice A contract parameter was updated.
* @param what The changed parameter name. Currently the supported values are: "rewardsDistribution".
* @param data The new value of the parameter.
*/
event File(bytes32 indexed what, address data);
/**
* @notice Recover ERC20 token `amt` to `usr`.
* @param token The token address.
* @param usr The destination address.
* @param amt The amount of `token` flushed out.
*/
event Yank(address indexed token, address indexed usr, uint256 amt);

event RewardAdded(uint256 reward);
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event RewardsDurationUpdated(uint256 newDuration);
event Recovered(address token, uint256 amt, address to);

/**
Expand All @@ -54,13 +67,8 @@ contract Farm is ReentrancyGuard {
_;
}

modifier notPaused() {
require(!paused, "Farm/is-paused");
_;
}

modifier onlyRewardsDistribution() {
require(msg.sender == rewardsDistribution, "Farm/not-rewards-distribution");
modifier isLive() {
require(live == 1, "Farm/not-live");
_;
}

Expand All @@ -74,15 +82,20 @@ contract Farm is ReentrancyGuard {
_;
}

constructor(address _rewardsDistribution, address _rewardGem, address _gem) public {
constructor(address _rewardsDistribution, address _rewardGem, address _gem) {
rewardGem = GemAbstract(_rewardGem);
gem = GemAbstract(_gem);
rewardsDistribution = _rewardsDistribution;

live = 1;
wards[msg.sender] = 1;
emit Rely(msg.sender);
}

/*//////////////////////////////////
Authorization
//////////////////////////////////*/

/**
* @notice Grants `usr` admin access to this contract.
* @param usr The user address.
Expand All @@ -101,39 +114,59 @@ contract Farm is ReentrancyGuard {
emit Deny(usr);
}

function setRewardsDuration(uint256 _rewardsDuration) external auth {
require(block.timestamp > periodFinish, "Farm/period-no-finished");
rewardsDuration = _rewardsDuration;
emit RewardsDurationUpdated(rewardsDuration);
}
/*//////////////////////////////////
Administration
//////////////////////////////////*/

function setRewardsDistribution(address _rewardsDistribution) external auth {
rewardsDistribution = _rewardsDistribution;
/**
* @notice Updates a contract parameter.
* @dev Reward duration can be updated only when previouse distribution is done
* @param what The changed parameter name. `rewardDuration`
* @param data The new value of the parameter.
*/
function file(bytes32 what, uint256 data) external auth {
if (what == "rewardDuration") {
require(block.timestamp > periodFinish, "Farm/period-no-finished");
rewardsDuration = data;
} else {
revert("Farm/unrecognised-param");
}

emit File(what, data);
}

/**
* @notice Change the paused state of the contract
* @dev Only the contract owner may call this.
* @notice Updates a contract parameter.
* @param what The changed parameter name. `rewardDistribution`
* @param data The new value of the parameter.
*/
function setPaused(bool _paused) external auth {
// Ensure we're actually changing the state before we do anything
if (_paused == paused) {
return;
function file(bytes32 what, address data) external auth {
if (what == "rewardDistribution") {
rewardsDistribution = data;
} else {
revert("Farm/unrecognised-param");
}

// Set our paused state.
paused = _paused;
emit File(what, data);
}

// If applicable, set the last pause time.
if (paused) {
lastPauseTime = block.timestamp;
}
/**
* @notice Cage farm
*/
function cage() external auth {
live = 0;
}

// Let everyone know that our pause state has changed.
emit PauseChanged(paused);
/**
* @notice Escape from cage
*/
function escape() external auth {
live = 1;
}

/* ========== VIEWS ========== */
/*//////////////////////////////////
View
//////////////////////////////////*/

function totalSupply() external view returns (uint256) {
return _totalSupply;
Expand All @@ -152,41 +185,49 @@ contract Farm is ReentrancyGuard {
return rewardPerTokenStored;
}
return
rewardPerTokenStored.add(
lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
_add(
rewardPerTokenStored,
_div(_mul(_sub(lastTimeRewardApplicable(), lastUpdateTime), rewardRate * 1e18), _totalSupply)
);
}

function earned(address account) public view returns (uint256) {
return
_balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(
_add(
_div(_mul(_balances[account], _sub(rewardPerToken(), userRewardPerTokenPaid[account])), 1e18),
rewards[account]
);
}

function getRewardForDuration() external view returns (uint256) {
return rewardRate.mul(rewardsDuration);
return _mul(rewardRate, rewardsDuration);
}

/* ========== MUTATIVE FUNCTIONS ========== */
/*//////////////////////////////////
Operations
//////////////////////////////////*/

function stake(uint256 amount) external nonReentrant notPaused updateReward(msg.sender) {
function stake(uint256 amount) external isLive updateReward(msg.sender) {
require(amount > 0, "Farm/invalid-amount");
_totalSupply = _totalSupply.add(amount);
_balances[msg.sender] = _balances[msg.sender].add(amount);

_totalSupply = _add(_totalSupply, amount);
_balances[msg.sender] = _add(_balances[msg.sender], amount);
gem.transferFrom(msg.sender, address(this), amount);

emit Staked(msg.sender, amount);
}

function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
function withdraw(uint256 amount) public updateReward(msg.sender) {
require(amount > 0, "Farm/invalid-amount");
_totalSupply = _totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);

_totalSupply = _sub(_totalSupply, amount);
_balances[msg.sender] = _sub(_balances[msg.sender], amount);
gem.transfer(msg.sender, amount);

emit Withdrawn(msg.sender, amount);
}

function getReward() public nonReentrant updateReward(msg.sender) {
function getReward() public updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
Expand All @@ -200,30 +241,71 @@ contract Farm is ReentrancyGuard {
getReward();
}

function recoverERC20(address token, uint256 amt, address to) external auth {
/**
* @notice Flushes out `amt` of `token` sitting in this contract to `usr` address.
* @dev Can only be called by the admin.
* @param token Token address.
* @param amt Token amount.
* @param usr Destination address.
*/
function yank(address token, uint256 amt, address usr) external auth {
require(token != address(gem), "Farm/gem-not-allowed");
GemAbstract(token).transfer(to, amt);
emit Recovered(token, amt, to);

GemAbstract(token).transfer(usr, amt);

emit Yank(token, usr, amt);
}

function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {
function notifyRewardAmount(uint256 reward) external updateReward(address(0)) {
require(wards[msg.sender] == 1 || msg.sender == rewardsDistribution, "Farm/not-authorized");

if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
rewardRate = _div(reward, rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration);
uint256 remaining = _sub(periodFinish, block.timestamp);
uint256 leftover = _mul(remaining, rewardRate);
rewardRate = _div(_add(reward, leftover), rewardsDuration);
}

// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardGem.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), "Farm/invalid-reward");
require(rewardRate <= _div(balance, rewardsDuration), "Farm/invalid-reward");

lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
periodFinish = _add(block.timestamp, rewardsDuration);

emit RewardAdded(reward);
}

/*//////////////////////////////////
Math
//////////////////////////////////*/

function _add(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
require((z = x + y) >= x, "Math/add-overflow");
}
}

function _sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
require((z = x - y) <= x, "Math/sub-overflow");
}
}

function _mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
require(y == 0 || (z = x * y) / y == x, "Math/mul-overflow");
}
}

function _div(uint x, uint y) internal pure returns (uint z) {
unchecked {
require(y > 0, "Math/divide-by-zero");
return x / y;
}
}
}
Loading