代币奖励系统
代币奖励系统是一个用于管理和分发代币奖励的智能合约系统,支持多种奖励策略和分发机制。本教程将介绍如何实现一个灵活且安全的代币奖励系统。
功能特性
- 多代币奖励支持
- 灵活的奖励策略
- 时间锁定机制
- 批量分发功能
- 紧急提取机制
合约实现
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title TokenRewards
* @dev 代币奖励合约实现
*/
contract TokenRewards is Ownable, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// 奖励信息结构
struct RewardInfo {
IERC20 rewardToken; // 奖励代币
uint256 rewardPerBlock; // 每区块奖励
uint256 startBlock; // 开始区块
uint256 endBlock; // 结束区块
uint256 lastRewardBlock;// 最后奖励区块
uint256 accRewardPerShare; // 累计每股奖励
uint256 totalStaked; // 总质押量
}
// 用户信息结构
struct UserInfo {
uint256 amount; // 用户质押数量
uint256 rewardDebt; // 奖励债务
uint256 pendingRewards; // 待领取奖励
uint256 lastClaimBlock; // 最后领取区块
}
// 质押代币
IERC20 public stakingToken;
// 奖励代币列表
RewardInfo[] public rewardTokens;
// 用户信息映射 user => rewardIndex => UserInfo
mapping(address => mapping(uint256 => UserInfo)) public userInfo;
// 是否暂停
bool public paused;
// 最小质押时间
uint256 public minStakingTime;
// 事件
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, address indexed rewardToken, uint256 reward);
event RewardAdded(address indexed rewardToken, uint256 rewardPerBlock, uint256 startBlock, uint256 endBlock);
event RewardUpdated(uint256 indexed index, uint256 rewardPerBlock);
event EmergencyWithdrawn(address indexed user, uint256 amount);
/**
* @dev 构造函数
*/
constructor(
address _stakingToken,
uint256 _minStakingTime
) {
require(_stakingToken != address(0), "Invalid staking token");
stakingToken = IERC20(_stakingToken);
minStakingTime = _minStakingTime;
}
/**
* @dev 添加奖励代币
*/
function addReward(
address _rewardToken,
uint256 _rewardPerBlock,
uint256 _startBlock,
uint256 _duration
) external onlyOwner {
require(_rewardToken != address(0), "Invalid reward token");
require(_duration > 0, "Invalid duration");
require(_startBlock >= block.number, "Invalid start block");
uint256 endBlock = _startBlock.add(_duration);
rewardTokens.push(RewardInfo({
rewardToken: IERC20(_rewardToken),
rewardPerBlock: _rewardPerBlock,
startBlock: _startBlock,
endBlock: endBlock,
lastRewardBlock: _startBlock,
accRewardPerShare: 0,
totalStaked: 0
}));
emit RewardAdded(_rewardToken, _rewardPerBlock, _startBlock, endBlock);
}
/**
* @dev 更新奖励速率
*/
function updateRewardPerBlock(
uint256 _index,
uint256 _rewardPerBlock
) external onlyOwner {
require(_index < rewardTokens.length, "Invalid index");
updateReward(_index);
rewardTokens[_index].rewardPerBlock = _rewardPerBlock;
emit RewardUpdated(_index, _rewardPerBlock);
}
/**
* @dev 质押代币
*/
function stake(uint256 _amount) external nonReentrant {
require(!paused, "Contract paused");
require(_amount > 0, "Cannot stake 0");
// 更新所有奖励
for (uint256 i = 0; i < rewardTokens.length; i++) {
updateReward(i);
UserInfo storage user = userInfo[msg.sender][i];
if (user.amount > 0) {
uint256 pending = user.amount.mul(rewardTokens[i].accRewardPerShare).div(1e12).sub(user.rewardDebt);
if (pending > 0) {
user.pendingRewards = user.pendingRewards.add(pending);
}
}
user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount.mul(rewardTokens[i].accRewardPerShare).div(1e12);
rewardTokens[i].totalStaked = rewardTokens[i].totalStaked.add(_amount);
}
stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
emit Staked(msg.sender, _amount);
}
/**
* @dev 提取质押
*/
function withdraw(uint256 _amount) external nonReentrant {
require(_amount > 0, "Cannot withdraw 0");
for (uint256 i = 0; i < rewardTokens.length; i++) {
UserInfo storage user = userInfo[msg.sender][i];
require(user.amount >= _amount, "Withdraw amount exceeds balance");
updateReward(i);
uint256 pending = user.amount.mul(rewardTokens[i].accRewardPerShare).div(1e12).sub(user.rewardDebt);
if (pending > 0) {
user.pendingRewards = user.pendingRewards.add(pending);
}
user.amount = user.amount.sub(_amount);
user.rewardDebt = user.amount.mul(rewardTokens[i].accRewardPerShare).div(1e12);
rewardTokens[i].totalStaked = rewardTokens[i].totalStaked.sub(_amount);
}
stakingToken.safeTransfer(msg.sender, _amount);
emit Withdrawn(msg.sender, _amount);
}
/**
* @dev 领取奖励
*/
function claimReward(uint256 _index) external nonReentrant {
require(_index < rewardTokens.length, "Invalid index");
updateReward(_index);
UserInfo storage user = userInfo[msg.sender][_index];
uint256 pending = user.amount.mul(rewardTokens[_index].accRewardPerShare).div(1e12).sub(user.rewardDebt);
if (pending > 0 || user.pendingRewards > 0) {
uint256 totalReward = pending.add(user.pendingRewards);
user.pendingRewards = 0;
user.rewardDebt = user.amount.mul(rewardTokens[_index].accRewardPerShare).div(1e12);
user.lastClaimBlock = block.number;
rewardTokens[_index].rewardToken.safeTransfer(msg.sender, totalReward);
emit RewardPaid(msg.sender, address(rewardTokens[_index].rewardToken), totalReward);
}
}
/**
* @dev 批量领取奖励
*/
function claimAllRewards() external nonReentrant {
for (uint256 i = 0; i < rewardTokens.length; i++) {
updateReward(i);
UserInfo storage user = userInfo[msg.sender][i];
uint256 pending = user.amount.mul(rewardTokens[i].accRewardPerShare).div(1e12).sub(user.rewardDebt);
if (pending > 0 || user.pendingRewards > 0) {
uint256 totalReward = pending.add(user.pendingRewards);
user.pendingRewards = 0;
user.rewardDebt = user.amount.mul(rewardTokens[i].accRewardPerShare).div(1e12);
user.lastClaimBlock = block.number;
rewardTokens[i].rewardToken.safeTransfer(msg.sender, totalReward);
emit RewardPaid(msg.sender, address(rewardTokens[i].rewardToken), totalReward);
}
}
}
/**
* @dev 紧急提取
*/
function emergencyWithdraw() external nonReentrant {
for (uint256 i = 0; i < rewardTokens.length; i++) {
UserInfo storage user = userInfo[msg.sender][i];
uint256 amount = user.amount;
if (amount > 0) {
user.amount = 0;
user.rewardDebt = 0;
user.pendingRewards = 0;
rewardTokens[i].totalStaked = rewardTokens[i].totalStaked.sub(amount);
}
}
if (stakingToken.balanceOf(address(this)) > 0) {
stakingToken.safeTransfer(msg.sender, stakingToken.balanceOf(address(this)));
}
emit EmergencyWithdrawn(msg.sender, stakingToken.balanceOf(address(this)));
}
/**
* @dev 更新奖励
*/
function updateReward(uint256 _index) internal {
RewardInfo storage reward = rewardTokens[_index];
if (block.number <= reward.lastRewardBlock) {
return;
}
if (reward.totalStaked == 0) {
reward.lastRewardBlock = block.number;
return;
}
uint256 endBlock = block.number > reward.endBlock ? reward.endBlock : block.number;
uint256 blocks = endBlock.sub(reward.lastRewardBlock);
if (blocks <= 0) {
return;
}
uint256 rewards = blocks.mul(reward.rewardPerBlock);
reward.accRewardPerShare = reward.accRewardPerShare.add(rewards.mul(1e12).div(reward.totalStaked));
reward.lastRewardBlock = block.number;
}
/**
* @dev 查看待领取奖励
*/
function pendingReward(
address _user,
uint256 _index
) external view returns (uint256) {
require(_index < rewardTokens.length, "Invalid index");
RewardInfo storage reward = rewardTokens[_index];
UserInfo storage user = userInfo[_user][_index];
uint256 accRewardPerShare = reward.accRewardPerShare;
if (block.number > reward.lastRewardBlock && reward.totalStaked != 0) {
uint256 endBlock = block.number > reward.endBlock ? reward.endBlock : block.number;
uint256 blocks = endBlock.sub(reward.lastRewardBlock);
uint256 rewards = blocks.mul(reward.rewardPerBlock);
accRewardPerShare = accRewardPerShare.add(rewards.mul(1e12).div(reward.totalStaked));
}
return user.amount.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt).add(user.pendingRewards);
}
/**
* @dev 暂停/恢复合约
*/
function setPaused(bool _paused) external onlyOwner {
paused = _paused;
}
/**
* @dev 设置最小质押时间
*/
function setMinStakingTime(uint256 _minStakingTime) external onlyOwner {
minStakingTime = _minStakingTime;
}
/**
* @dev 恢复误转入的代币
*/
function recoverToken(
address _token,
uint256 _amount
) external onlyOwner {
require(_token != address(stakingToken), "Cannot recover staking token");
bool isRewardToken = false;
for (uint256 i = 0; i < rewardTokens.length; i++) {
if (_token == address(rewardTokens[i].rewardToken)) {
isRewardToken = true;
break;
}
}
require(!isRewardToken, "Cannot recover reward token");
IERC20(_token).safeTransfer(owner(), _amount);
}
}
关键概念
奖励机制
奖励系统支持:
- 多代币奖励
- 区块奖励
- 累计奖励
- 待领取奖励
质押机制
质押系统包括:
- 代币质押
- 解除质押
- 紧急提取
- 最小质押时间
计算方法
奖励计算:
- 区块计算
- 份额计算
- 累计计算
- 精度处理
安全考虑
代币安全
- 转账安全
- 余额检查
- 溢出保护
- 重入防护
权限控制
- 管理权限
- 操作限制
- 暂停机制
- 紧急处理
数据验证
- 参数检查
- 状态验证
- 地址验证
- 金额验证
异常处理
- 错误恢复
- 状态回滚
- 资金找回
- 紧急提取
最佳实践
合约设计
- 模块化结构
- 清晰接口
- 完整事件
- 状态管理
奖励管理
- 合理速率
- 定期更新
- 精确计算
- 公平分配
用户体验
- 便捷操作
- 状态查询
- 批量处理
- 及时反馈
运维管理
- 参数调整
- 状态监控
- 异常处理
- 升级规划
扩展功能
- 动态奖励率
- 奖励倍数
- 锁定期奖励
- 推荐奖励
- 团队奖励
应用场景
流动性挖矿
- LP代币奖励
- 交易挖矿
- 流动性激励
- 市场深度
生态激励
- 社区建设
- 项目贡献
- 长期持有
- 生态发展
项目运营
- 用户增长
- 市场推广
- 社区激励
- 忠诚度计划
常见问题解答(FAQ)
基础概念
Q: 什么是代币奖励系统?
A: 代币奖励系统是一种激励机制,主要特点包括:
- 多代币奖励支持
- 灵活的奖励策略
- 实时奖励计算
- 自动分配机制
- 公平分配原则
Q: 奖励系统有哪些类型?
A: 主要类型包括:
- 质押挖矿奖励
- 流动性挖矿奖励
- 交易挖矿奖励
- 生态建设奖励
- 治理参与奖励
操作相关
Q: 如何设计奖励策略?
A: 设计要点包括:
- 确定奖励代币
- 设置奖励规则
- 配置分配机制
- 制定时间计划
- 建立退出机制
Q: 如何计算奖励?
A: 计算方法包括:
- 区块奖励计算
- 权重比例计算
- 时间周期计算
- 累积奖励计算
- 精度因子处理
安全相关
Q: 奖励系统有哪些风险?
A: 主要风险包括:
- 通胀风险
- 代币价格波动
- 奖励分配不公
- 系统漏洞风险
- 攻击者套利
Q: 如何确保奖励公平?
A: 保障措施包括:
- 透明的规则设计
- 公平的分配机制
- 完善的监控系统
- 有效的防作弊机制
- 及时的问题响应