质押收益
- 质押收益介绍
- Defi质押的操作流程
- 合约实现
质押收益介绍
Defi:单币质押、流动性质押、借贷等
PoS质押:以太坊通过质押ETH来选择验证人,参与新的区块产生的验证
其它:GameFi、NFT质押
常见Defi质押介绍
单币质押:
平台:AAVE、Compound、LaunchPad(Staking)
原理:用户存入一种代币,获取利息或质押收益
举例:相当于去中心化银行存款,在LaunchPad中存入C2N代币,获取C2N代币奖励
流动性质押:
平台:Uniswap、PancakeSwap
原理:用户提供两种代币组成的交易对,换取流动性代币
举例:在Uniswap中提供交易币对,获取交易币对的LP Token,通过LP token获得奖励或参与其它活动
借贷质押:
平台:AAVE、Compound
原理:用户存入抵押资产,借出另一种资产,同时获取利息或治理代币利息
举例:在AAVE中存入ETH赚取利息,同时获取AAVE代币奖励
质押机制和流程
- 两种IERC20:质押代币、奖励代币(可以是同种代币)
- 奖励机制设置
- 用户质押代币
合约实现**(理解变量含义、安全性)**
- 合约变量
- 构造函数(质押代币、奖励代币、管理员)
- 管理员modifier
- 设置持续时间、设置奖励速率(notifyRewardAmount)
- 更新收益modifier
- 质押、撤回、获取收益函数
- 状态查询函数
设置奖励金额和分配速率
function notifyRewardAmount(uint256 _amount) external onlyOwner updateReward(address(0)) {
if (block.timestamp > finishAt) {
rewardRate = _amount / duration;
} else {
uint256 remainingRewards = rewardRate * (finishAt - block.timestamp);
rewardRate = (remainingRewards + _amount) / duration;
}
require(rewardRate > 0, "reward rate = 0");
require(rewardRate * duration <= rewardsToken.balanceOf(address(this)), "reward amount > balance");
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
}
- 当当前时间超过上一个奖励周期结束时间(
finishAt)时,创建一个全新的奖励周期 - 当当前时间还在上一个奖励周期内时,将剩余奖励与新奖励合并计算新的分配速率
- 更新奖励结束时间(
finishAt)为当前时间加上持续时间(duration)
- 当前时间:1600,finishAt:1500,amount:1000,duration:1000
- rewardRate:1
- 当前时间:2000,finishAt:2600,amount:1000,duration:1000
- remainingRewards:600
- rewardRate:1.6
在关键操作前更新用户奖励状态
modifier updateReward(address _account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (_account != address(0)) {
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
_;
}
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) return rewardPerTokenStored;
return rewardPerTokenStored + (rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18) / totalSupply;
}
function lastTimeRewardApplicable() public view returns (uint256) {
return _min(block.timestamp, finishAt);
}
时间轴: t0 ---- t1 (用户A质押) ---- t2 (用户B质押) ---- t3 (现在) —— t4(用户A质押)
全局 rewardPerTokenStored 发展: t0: 0 t1: 1.2 t2: 1.8 t3: 2.5
t4: 2.8
用户A:
- 质押时(t1) userRewardPerTokenPaid[A] = 1.2
- earned[A] = 质押金额 × (2.5 - 1.2) + rewards[A]
用户B:
- 质押时(t2) userRewardPerTokenPaid[B] = 1.8
- earned[B] = 质押金额 × (2.5 - 1.8) + rewards[B]
计算用户当前可领取的奖励总额
function earned(address _account) public view returns (uint256) {
return (balanceOf[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e18 + rewards[_account];
}
质押时,会按照当前用户的质押数量,先以当前质押的数额,将奖励存入用户的rewards中去,然后重置用户的奖励计算参数(因为质押的总金额变了,每个质押代币对应的奖励数额也变了)
每次的质押、撤回,都会重新计算用户的rewards[account]
课程代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/interfaces/IERC20.sol";
contract StakingRewards {
IERC20 public immutable stakingToken;
IERC20 public immutable rewardsToken;
address public owner;
uint256 public duration;
uint256 public finishAt;
uint256 public updatedAt;
uint256 public rewardRate;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
modifier updateReward(address _account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (_account != address(0)) {
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
_;
}
constructor(address _stakingToken, address _rewardsToken) {
owner = msg.sender;
stakingToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardsToken);
}
function setRewardsDuration(uint256 _duration) external onlyOwner {
require(finishAt < block.timestamp, "reward duration not finished");
duration = _duration;
}
function notifyRewardAmount(uint256 _amount)
external
onlyOwner
updateReward(address(0))
{
if (block.timestamp > finishAt) {
rewardRate = _amount / duration;
} else {
uint256 remainingRewards = rewardRate *
(finishAt - block.timestamp);
rewardRate = (remainingRewards + _amount) / duration;
}
require(rewardRate > 0, "reward rate = 0");
require(
rewardRate * duration <= rewardsToken.balanceOf(address(this)),
"reward amount > balance"
);
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
}
function stake(uint256 _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
stakingToken.transferFrom(msg.sender, address(this), _amount);
balanceOf[msg.sender] += _amount;
totalSupply += _amount;
}
function withdraw(uint256 _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
balanceOf[msg.sender] -= _amount;
totalSupply -= _amount;
stakingToken.transfer(msg.sender, _amount);
}
function lastTimeRewardApplicable() public view returns (uint256) {
return _min(block.timestamp, finishAt);
}
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored +
((rewardRate * (lastTimeRewardApplicable() - updatedAt)) * 1e18) /
totalSupply;
}
function earned(address _account) public view returns (uint256) {
return
(balanceOf[_account] *
(rewardPerToken() - userRewardPerTokenPaid[_account])) /
1e18 +
rewards[_account];
}
function getReward() external updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.transfer(msg.sender, reward);
}
}
function _min(uint256 x, uint256 y) private pure returns (uint256) {
return x <= y ? x : y;
}
}