This repository has been archived by the owner on Sep 25, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f707a46
Showing
20 changed files
with
9,016 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.sol linguist-language=Solidity |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: Tests | ||
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
pull_request: | ||
|
||
jobs: | ||
unit-tests: | ||
name: Unit Tests | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions/setup-node@v1 | ||
with: | ||
node-version: 12.x | ||
- run: yarn | ||
- run: yarn lint | ||
- run: yarn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules/ | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"extension": ["ts"], | ||
"spec": "./test/**/*.spec.ts", | ||
"require": "ts-node/register", | ||
"timeout": 12000 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"semi": false, | ||
"singleQuote": true, | ||
"printWidth": 120 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ignore-scripts true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# liquidity-staker | ||
|
||
Forked from | ||
[https://github.com/Synthetixio/synthetix/tree/v2.27.2/](https://github.com/Synthetixio/synthetix/tree/v2.27.2/) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
pragma solidity ^0.5.16; | ||
|
||
contract RewardsDistributionRecipient { | ||
address public rewardsDistribution; | ||
|
||
function notifyRewardAmount(uint256 reward) external; | ||
|
||
modifier onlyRewardsDistribution() { | ||
require(msg.sender == rewardsDistribution, "Caller is not RewardsDistribution contract"); | ||
_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
pragma solidity ^0.5.16; | ||
|
||
import "openzeppelin-solidity-2.3.0/contracts/math/Math.sol"; | ||
import "openzeppelin-solidity-2.3.0/contracts/math/SafeMath.sol"; | ||
import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20Detailed.sol"; | ||
import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/SafeERC20.sol"; | ||
import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol"; | ||
|
||
// Inheritance | ||
import "./interfaces/IStakingRewards.sol"; | ||
import "./RewardsDistributionRecipient.sol"; | ||
|
||
contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard { | ||
using SafeMath for uint256; | ||
using SafeERC20 for IERC20; | ||
|
||
/* ========== STATE VARIABLES ========== */ | ||
|
||
IERC20 public rewardsToken; | ||
IERC20 public stakingToken; | ||
uint256 public periodFinish = 0; | ||
uint256 public rewardRate = 0; | ||
uint256 public rewardsDuration = 60 days; | ||
uint256 public lastUpdateTime; | ||
uint256 public rewardPerTokenStored; | ||
|
||
mapping(address => uint256) public userRewardPerTokenPaid; | ||
mapping(address => uint256) public rewards; | ||
|
||
uint256 private _totalSupply; | ||
mapping(address => uint256) private _balances; | ||
|
||
/* ========== CONSTRUCTOR ========== */ | ||
|
||
constructor( | ||
address _rewardsDistribution, | ||
address _rewardsToken, | ||
address _stakingToken | ||
) public { | ||
rewardsToken = IERC20(_rewardsToken); | ||
stakingToken = IERC20(_stakingToken); | ||
rewardsDistribution = _rewardsDistribution; | ||
} | ||
|
||
/* ========== VIEWS ========== */ | ||
|
||
function totalSupply() external view returns (uint256) { | ||
return _totalSupply; | ||
} | ||
|
||
function balanceOf(address account) external view returns (uint256) { | ||
return _balances[account]; | ||
} | ||
|
||
function lastTimeRewardApplicable() public view returns (uint256) { | ||
return Math.min(block.timestamp, periodFinish); | ||
} | ||
|
||
function rewardPerToken() public view returns (uint256) { | ||
if (_totalSupply == 0) { | ||
return rewardPerTokenStored; | ||
} | ||
return | ||
rewardPerTokenStored.add( | ||
lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply) | ||
); | ||
} | ||
|
||
function earned(address account) public view returns (uint256) { | ||
return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]); | ||
} | ||
|
||
function getRewardForDuration() external view returns (uint256) { | ||
return rewardRate.mul(rewardsDuration); | ||
} | ||
|
||
/* ========== MUTATIVE FUNCTIONS ========== */ | ||
|
||
function stakeWithPermit(uint256 amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external nonReentrant updateReward(msg.sender) { | ||
require(amount > 0, "Cannot stake 0"); | ||
_totalSupply = _totalSupply.add(amount); | ||
_balances[msg.sender] = _balances[msg.sender].add(amount); | ||
|
||
// permit | ||
IUniswapV2ERC20(address(stakingToken)).permit(msg.sender, address(this), amount, deadline, v, r, s); | ||
|
||
stakingToken.safeTransferFrom(msg.sender, address(this), amount); | ||
emit Staked(msg.sender, amount); | ||
} | ||
|
||
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) { | ||
require(amount > 0, "Cannot stake 0"); | ||
_totalSupply = _totalSupply.add(amount); | ||
_balances[msg.sender] = _balances[msg.sender].add(amount); | ||
stakingToken.safeTransferFrom(msg.sender, address(this), amount); | ||
emit Staked(msg.sender, amount); | ||
} | ||
|
||
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) { | ||
require(amount > 0, "Cannot withdraw 0"); | ||
_totalSupply = _totalSupply.sub(amount); | ||
_balances[msg.sender] = _balances[msg.sender].sub(amount); | ||
stakingToken.safeTransfer(msg.sender, amount); | ||
emit Withdrawn(msg.sender, amount); | ||
} | ||
|
||
function getReward() public nonReentrant updateReward(msg.sender) { | ||
uint256 reward = rewards[msg.sender]; | ||
if (reward > 0) { | ||
rewards[msg.sender] = 0; | ||
rewardsToken.safeTransfer(msg.sender, reward); | ||
emit RewardPaid(msg.sender, reward); | ||
} | ||
} | ||
|
||
function exit() external { | ||
withdraw(_balances[msg.sender]); | ||
getReward(); | ||
} | ||
|
||
/* ========== RESTRICTED FUNCTIONS ========== */ | ||
|
||
function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) { | ||
if (block.timestamp >= periodFinish) { | ||
rewardRate = reward.div(rewardsDuration); | ||
} else { | ||
uint256 remaining = periodFinish.sub(block.timestamp); | ||
uint256 leftover = remaining.mul(rewardRate); | ||
rewardRate = reward.add(leftover).div(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 = rewardsToken.balanceOf(address(this)); | ||
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high"); | ||
|
||
lastUpdateTime = block.timestamp; | ||
periodFinish = block.timestamp.add(rewardsDuration); | ||
emit RewardAdded(reward); | ||
} | ||
|
||
/* ========== MODIFIERS ========== */ | ||
|
||
modifier updateReward(address account) { | ||
rewardPerTokenStored = rewardPerToken(); | ||
lastUpdateTime = lastTimeRewardApplicable(); | ||
if (account != address(0)) { | ||
rewards[account] = earned(account); | ||
userRewardPerTokenPaid[account] = rewardPerTokenStored; | ||
} | ||
_; | ||
} | ||
|
||
/* ========== EVENTS ========== */ | ||
|
||
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); | ||
} | ||
|
||
interface IUniswapV2ERC20 { | ||
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
pragma solidity ^0.5.16; | ||
|
||
import 'openzeppelin-solidity-2.3.0/contracts/token/ERC20/IERC20.sol'; | ||
import 'openzeppelin-solidity-2.3.0/contracts/ownership/Ownable.sol'; | ||
|
||
import './StakingRewards.sol'; | ||
|
||
contract StakingRewardsFactory is Ownable { | ||
// immutables | ||
address public rewardsToken; | ||
uint public stakingRewardsGenesis; | ||
|
||
// the staking tokens for which the rewards contract has been deployed | ||
address[] public stakingTokens; | ||
|
||
// info about rewards for a particular staking token | ||
struct StakingRewardsInfo { | ||
address stakingRewards; | ||
uint rewardAmount; | ||
} | ||
|
||
// rewards info by staking token | ||
mapping(address => StakingRewardsInfo) public stakingRewardsInfoByStakingToken; | ||
|
||
constructor( | ||
address _rewardsToken, | ||
uint _stakingRewardsGenesis | ||
) Ownable() public { | ||
require(_stakingRewardsGenesis >= block.timestamp, 'StakingRewardsFactory::constructor: genesis too soon'); | ||
|
||
rewardsToken = _rewardsToken; | ||
stakingRewardsGenesis = _stakingRewardsGenesis; | ||
} | ||
|
||
///// permissioned functions | ||
|
||
// deploy a staking reward contract for the staking token, and store the reward amount | ||
// the reward will be distributed to the staking reward contract no sooner than the genesis | ||
function deploy(address stakingToken, uint rewardAmount) public onlyOwner { | ||
StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken]; | ||
require(info.stakingRewards == address(0), 'StakingRewardsFactory::deploy: already deployed'); | ||
|
||
info.stakingRewards = address(new StakingRewards(/*_rewardsDistribution=*/ address(this), rewardsToken, stakingToken)); | ||
info.rewardAmount = rewardAmount; | ||
stakingTokens.push(stakingToken); | ||
} | ||
|
||
///// permissionless functions | ||
|
||
// call notifyRewardAmount for all staking tokens. | ||
function notifyRewardAmounts() public { | ||
require(stakingTokens.length > 0, 'StakingRewardsFactory::notifyRewardAmounts: called before any deploys'); | ||
for (uint i = 0; i < stakingTokens.length; i++) { | ||
notifyRewardAmount(stakingTokens[i]); | ||
} | ||
} | ||
|
||
// notify reward amount for an individual staking token. | ||
// this is a fallback in case the notifyRewardAmounts costs too much gas to call for all contracts | ||
function notifyRewardAmount(address stakingToken) public { | ||
require(block.timestamp >= stakingRewardsGenesis, 'StakingRewardsFactory::notifyRewardAmount: not ready'); | ||
|
||
StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken]; | ||
require(info.stakingRewards != address(0), 'StakingRewardsFactory::notifyRewardAmount: not deployed'); | ||
|
||
if (info.rewardAmount > 0) { | ||
uint rewardAmount = info.rewardAmount; | ||
info.rewardAmount = 0; | ||
|
||
require( | ||
IERC20(rewardsToken).transfer(info.stakingRewards, rewardAmount), | ||
'StakingRewardsFactory::notifyRewardAmount: transfer failed' | ||
); | ||
StakingRewards(info.stakingRewards).notifyRewardAmount(rewardAmount); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
pragma solidity >=0.4.24; | ||
|
||
|
||
interface IStakingRewards { | ||
// Views | ||
function lastTimeRewardApplicable() external view returns (uint256); | ||
|
||
function rewardPerToken() external view returns (uint256); | ||
|
||
function earned(address account) external view returns (uint256); | ||
|
||
function getRewardForDuration() external view returns (uint256); | ||
|
||
function totalSupply() external view returns (uint256); | ||
|
||
function balanceOf(address account) external view returns (uint256); | ||
|
||
// Mutative | ||
|
||
function stake(uint256 amount) external; | ||
|
||
function withdraw(uint256 amount) external; | ||
|
||
function getReward() external; | ||
|
||
function exit() external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity =0.5.16; | ||
|
||
import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20Detailed.sol"; | ||
import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20Mintable.sol"; | ||
|
||
contract TestERC20 is ERC20Detailed, ERC20Mintable { | ||
constructor(uint amount) ERC20Detailed('Test ERC20', 'TEST', 18) public { | ||
mint(msg.sender, amount); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "@uniswap/liquidity-staker", | ||
"version": "1.0.2", | ||
"author": "Noah Zinsmeister", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Uniswap/liquidity-staker" | ||
}, | ||
"files": [ | ||
"build" | ||
], | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"precompile": "rimraf ./build/", | ||
"compile": "waffle", | ||
"pretest": "yarn compile", | ||
"test": "mocha", | ||
"lint": "prettier ./test/**/*.ts --check", | ||
"prepublishOnly": "yarn test" | ||
}, | ||
"dependencies": { | ||
"openzeppelin-solidity-2.3.0": "npm:[email protected]" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "^4.2.12", | ||
"@types/mocha": "^8.0.3", | ||
"@uniswap/v2-core": "^1.0.1", | ||
"chai": "^4.2.0", | ||
"ethereum-waffle": "^3.1.0", | ||
"ethereumjs-util": "^7.0.5", | ||
"mocha": "^8.1.3", | ||
"prettier": "^2.1.1", | ||
"rimraf": "^3.0.2", | ||
"solc": "0.5.16", | ||
"ts-node": "^9.0.0", | ||
"typescript": "^4.0.2" | ||
} | ||
} |
Oops, something went wrong.