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

Simple liquidity rewards #645

Merged
merged 41 commits into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
df98c9b
Pull the Synthetix Unipool contract from mainnet
mhluongo Dec 14, 2020
16350f4
Paramaterize the Synethetix Unipool for LP rewards
mhluongo Dec 14, 2020
e646258
Importing OpenZeppelin Math.sol
dimpar Dec 15, 2020
2bc0029
Importing OpenZeppelin SafeMath.sol
dimpar Dec 15, 2020
268dd19
Importing OpenZeppelin Ownable.sol
dimpar Dec 15, 2020
0b00d43
Remove unused Context contract
dimpar Dec 15, 2020
cb6048f
Importing OpenZeppelin SafeERC20.sol
dimpar Dec 15, 2020
015d450
Changing the function order
dimpar Dec 15, 2020
f47a37f
Linting only
dimpar Dec 15, 2020
82327a0
Cleanup comments
dimpar Dec 15, 2020
08bb479
Adding receiveApproval() for KEEP tokens
dimpar Dec 16, 2020
9af85f7
Adding basic tests for token allocations
dimpar Dec 16, 2020
2f93ad3
Aligning name for a Uni pair token
dimpar Dec 17, 2020
9696385
Adding testing flow for earning the rewards and claiming them
dimpar Dec 17, 2020
31c5939
Renaming 'wrapped' token to 'wrappedToken'
dimpar Dec 18, 2020
85819dc
Renaming rewardToken to keepToken
dimpar Dec 18, 2020
70e7328
Adding a comment regarding the original source of contracts
dimpar Dec 18, 2020
1d96dfc
Combining constants
dimpar Dec 18, 2020
f49f7c8
Renaming user's 'wallet' to 'staker' which indicate his address
dimpar Dec 18, 2020
044bcab
Owner account[0] -> rewardDistribution account[5] to align with the c…
dimpar Dec 18, 2020
0344580
Linting only
dimpar Dec 18, 2020
321aad2
Call explicitly approveAndCall to show what is being tested
dimpar Dec 18, 2020
fc61d96
Simplifying balance check
dimpar Dec 18, 2020
9bee62e
Adding additional info to the test description
dimpar Dec 18, 2020
0e2e07c
Deploying contracts from dedicated accounts to test more realistic sc…
dimpar Dec 18, 2020
b148edc
Transferring KEEP rewards in notifyRewardAmount
dimpar Dec 18, 2020
dc0f47c
Changing test description 'allocate' -> 'stake'
dimpar Dec 21, 2020
09d89f3
Removing unnecessary assertions
dimpar Dec 21, 2020
7beae72
Checking additional states in LP rewards contract
dimpar Dec 21, 2020
4e8d014
Using web3 conversion to wei instead of multiplying by 10^18
dimpar Dec 21, 2020
d00548e
Refactoring mintAndApproveWrappedTokens for reusability
dimpar Dec 21, 2020
b0b2079
Merge remote-tracking branch 'origin' into liquidity-rewards
dimpar Dec 21, 2020
b0165bd
Explaining calculation of reward per token
dimpar Dec 21, 2020
8f1fdac
Revert "Using web3 conversion to wei instead of multiplying by 10^18"
dimpar Dec 21, 2020
264f253
Cleaning up
dimpar Dec 21, 2020
fe46b3a
Removing accidental code
dimpar Dec 21, 2020
d1853ea
Linting only
dimpar Dec 21, 2020
17048ba
Removing typos ballance -> balance
dimpar Dec 21, 2020
cca746d
Fixing assignment to const value
dimpar Dec 21, 2020
91b3bf3
Removing unnecessary approval before KEEP transfer
dimpar Dec 21, 2020
c9b317b
Linting only
dimpar Dec 21, 2020
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
207 changes: 207 additions & 0 deletions solidity/contracts/LPRewards.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
____ __ __ __ _
/ __/__ __ ___ / /_ / / ___ / /_ (_)__ __
_\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ /
/___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\
/___/

* Synthetix: Unipool.sol
*
* Docs: https://docs.synthetix.io/
*
*
* MIT License
* ===========
*
* Copyright (c) 2020 Synthetix
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/

/// These contracts reward users for adding liquidity to Uniswap https://uniswap.org/
/// These contracts were obtained from Synthetix and added some minor changes.
/// You can find the original contracts here:
/// https://etherscan.io/address/0x48d7f315fedcad332f68aafa017c7c158bc54760#code

pragma solidity 0.5.17;
pdyraga marked this conversation as resolved.
Show resolved Hide resolved

import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";

contract IRewardDistributionRecipient is Ownable {
address rewardDistribution;

function notifyRewardAmount(uint256 reward) external;

modifier onlyRewardDistribution() {
require(
msg.sender == rewardDistribution,
"Caller is not reward distribution"
);
_;
}

function setRewardDistribution(address _rewardDistribution)
external
onlyOwner
{
rewardDistribution = _rewardDistribution;
}
}

contract LPTokenWrapper {
using SafeMath for uint256;
using SafeERC20 for IERC20;

uint256 private _totalSupply;
mapping(address => uint256) private _balances;
nkuba marked this conversation as resolved.
Show resolved Hide resolved

IERC20 public wrappedToken; // Pairs: KEEP/ETH, TBTC/ETH, KEEP/TBTC

constructor(IERC20 _wrappedToken) public {
wrappedToken = _wrappedToken;
}

function totalSupply() public view returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

function stake(uint256 amount) public {
_totalSupply = _totalSupply.add(amount);
_balances[msg.sender] = _balances[msg.sender].add(amount);
wrappedToken.safeTransferFrom(msg.sender, address(this), amount);
}

function withdraw(uint256 amount) public {
_totalSupply = _totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
wrappedToken.safeTransfer(msg.sender, amount);
}
}

contract LPRewards is LPTokenWrapper, IRewardDistributionRecipient {
Copy link
Member

Choose a reason for hiding this comment

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

We need to define a dedicated contract for each of the three wrapped tokens. This will simplify usage in the dashboard.

Copy link
Contributor

Choose a reason for hiding this comment

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

IERC20 public keepToken;
uint256 public constant DURATION = 7 days;
nkuba marked this conversation as resolved.
Show resolved Hide resolved

uint256 public periodFinish = 0;
uint256 public rewardRate = 0;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;

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);

constructor(IERC20 _keepToken, IERC20 _wrappedToken)
public
LPTokenWrapper(_wrappedToken)
{
keepToken = _keepToken;
}

modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}

function exit() external {
withdraw(balanceOf(msg.sender));
getReward();
}

function notifyRewardAmount(uint256 reward)
nkuba marked this conversation as resolved.
Show resolved Hide resolved
external
onlyRewardDistribution
updateReward(address(0))
{
keepToken.safeTransferFrom(msg.sender, address(this), reward);

if (block.timestamp >= periodFinish) {
rewardRate = reward.div(DURATION);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(DURATION);
}
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(DURATION);
emit RewardAdded(reward);
}

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
balanceOf(account)
.mul(rewardPerToken().sub(userRewardPerTokenPaid[account]))
.div(1e18)
nkuba marked this conversation as resolved.
Show resolved Hide resolved
.add(rewards[account]);
}

// stake visibility is public as overriding LPTokenWrapper's stake() function
function stake(uint256 amount) public updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
super.stake(amount);
emit Staked(msg.sender, amount);
}

function withdraw(uint256 amount) public updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
super.withdraw(amount);
emit Withdrawn(msg.sender, amount);
}

function getReward() public updateReward(msg.sender) {
uint256 reward = earned(msg.sender);
if (reward > 0) {
rewards[msg.sender] = 0;
keepToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
}
Loading