代币质押挖矿系统
1. 系统概述
代币质押挖矿系统是一个基于 Solidity 实现的去中心化质押奖励平台,允许用户通过质押特定代币来获得奖励代币。系统实现了公平的奖励分配机制和灵活的质押管理功能。
1.1 主要特点
- 灵活质押:支持任意数量的代币质押
- 实时奖励:基于区块计算奖励
- 公平分配:按质押比例分配奖励
- 随时提取:支持随时解除质押
- 奖励累积:自动累积未领取的奖励
- 精确计算:使用高精度计算避免误差
2. 合约实现
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title TokenStaking
* @dev 代币质押挖矿合约
*/
contract TokenStaking is Ownable, ReentrancyGuard {
using SafeMath for uint256;
// 状态变量
IERC20 public stakeToken; // 质押代币
IERC20 public rewardToken; // 奖励代币
uint256 public rewardPerBlock; // 每区块奖励
uint256 public lastRewardBlock; // 上次奖励区块
uint256 public accRewardPerShare; // 每股累计奖励
uint256 public totalStaked; // 总质押量
uint256 public constant PRECISION = 1e12; // 精度因子
// 质押信息
struct StakeInfo {
uint256 amount; // 质押数量
uint256 startTime; // 开始时间
uint256 rewardDebt; // 奖励债务
uint256 pendingRewards; // 待领取奖励
uint256 lastClaimTime; // 上次领取时间
}
// 用户质押信息
mapping(address => StakeInfo) public stakeInfos;
// 事件
event Stake(address indexed user, uint256 amount);
event Unstake(address indexed user, uint256 amount);
event ClaimReward(address indexed user, uint256 amount);
event EmergencyWithdraw(address indexed user, uint256 amount);
event RewardRateUpdated(uint256 newRate);
/**
* @dev 构造函数
*/
constructor(
IERC20 _stakeToken,
IERC20 _rewardToken,
uint256 _rewardPerBlock
) {
stakeToken = _stakeToken;
rewardToken = _rewardToken;
rewardPerBlock = _rewardPerBlock;
lastRewardBlock = block.number;
}
/**
* @dev 质押代币
*/
function stake(uint256 _amount) external nonReentrant {
require(_amount > 0, "Cannot stake 0");
updatePool();
StakeInfo storage info = stakeInfos[msg.sender];
if (info.amount > 0) {
uint256 pending = info.amount.mul(accRewardPerShare).div(PRECISION).sub(info.rewardDebt);
info.pendingRewards = info.pendingRewards.add(pending);
}
stakeToken.transferFrom(msg.sender, address(this), _amount);
info.amount = info.amount.add(_amount);
info.startTime = block.timestamp;
info.rewardDebt = info.amount.mul(accRewardPerShare).div(PRECISION);
totalStaked = totalStaked.add(_amount);
emit Stake(msg.sender, _amount);
}
/**
* @dev 解除质押
*/
function unstake(uint256 _amount) external nonReentrant {
StakeInfo storage info = stakeInfos[msg.sender];
require(info.amount >= _amount, "Insufficient stake");
updatePool();
uint256 pending = info.amount.mul(accRewardPerShare).div(PRECISION).sub(info.rewardDebt);
info.pendingRewards = info.pendingRewards.add(pending);
info.amount = info.amount.sub(_amount);
info.rewardDebt = info.amount.mul(accRewardPerShare).div(PRECISION);
totalStaked = totalStaked.sub(_amount);
stakeToken.transfer(msg.sender, _amount);
emit Unstake(msg.sender, _amount);
}
/**
* @dev 领取奖励
*/
function claimReward() external nonReentrant {
updatePool();
StakeInfo storage info = stakeInfos[msg.sender];
uint256 pending = info.amount.mul(accRewardPerShare).div(PRECISION).sub(info.rewardDebt);
uint256 totalReward = info.pendingRewards.add(pending);
require(totalReward > 0, "No reward to claim");
info.pendingRewards = 0;
info.rewardDebt = info.amount.mul(accRewardPerShare).div(PRECISION);
info.lastClaimTime = block.timestamp;
rewardToken.transfer(msg.sender, totalReward);
emit ClaimReward(msg.sender, totalReward);
}
/**
* @dev 更新奖励池
*/
function updatePool() public {
if (block.number <= lastRewardBlock) {
return;
}
if (totalStaked == 0) {
lastRewardBlock = block.number;
return;
}
uint256 blocksSinceLastReward = block.number.sub(lastRewardBlock);
uint256 rewards = blocksSinceLastReward.mul(rewardPerBlock);
accRewardPerShare = accRewardPerShare.add(rewards.mul(PRECISION).div(totalStaked));
lastRewardBlock = block.number;
}
/**
* @dev 查询待领取奖励
*/
function pendingReward(address _user) external view returns (uint256) {
StakeInfo storage info = stakeInfos[_user];
uint256 _accRewardPerShare = accRewardPerShare;
if (block.number > lastRewardBlock && totalStaked > 0) {
uint256 blocksSinceLastReward = block.number.sub(lastRewardBlock);
uint256 rewards = blocksSinceLastReward.mul(rewardPerBlock);
_accRewardPerShare = _accRewardPerShare.add(rewards.mul(PRECISION).div(totalStaked));
}
uint256 pending = info.amount.mul(_accRewardPerShare).div(PRECISION).sub(info.rewardDebt);
return info.pendingRewards.add(pending);
}
/**
* @dev 紧急提取
*/
function emergencyWithdraw() external nonReentrant {
StakeInfo storage info = stakeInfos[msg.sender];
require(info.amount > 0, "No stake to withdraw");
uint256 amount = info.amount;
info.amount = 0;
info.rewardDebt = 0;
info.pendingRewards = 0;
totalStaked = totalStaked.sub(amount);
stakeToken.transfer(msg.sender, amount);
emit EmergencyWithdraw(msg.sender, amount);
}
/**
* @dev 更新每区块奖励
*/
function setRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner {
updatePool();
rewardPerBlock = _rewardPerBlock;
emit RewardRateUpdated(_rewardPerBlock);
}
/**
* @dev 获取用户质押信息
*/
function getStakeInfo(address _user) external view returns (
uint256 amount,
uint256 startTime,
uint256 pendingRewards,
uint256 lastClaimTime
) {
StakeInfo storage info = stakeInfos[_user];
return (
info.amount,
info.startTime,
info.pendingRewards,
info.lastClaimTime
);
}
/**
* @dev 获取质押统计信息
*/
function getStakeStats() external view returns (
uint256 totalStakedAmount,
uint256 rewardRate,
uint256 lastRewardTime,
uint256 accumulatedRewards
) {
return (
totalStaked,
rewardPerBlock,
lastRewardBlock,
accRewardPerShare
);
}
/**
* @dev 批量获取用户质押信息
*/
function batchGetStakeInfo(address[] calldata _users) external view returns (
uint256[] memory amounts,
uint256[] memory startTimes,
uint256[] memory pendingRewards,
uint256[] memory lastClaimTimes
) {
amounts = new uint256[](_users.length);
startTimes = new uint256[](_users.length);
pendingRewards = new uint256[](_users.length);
lastClaimTimes = new uint256[](_users.length);
for (uint256 i = 0; i < _users.length; i++) {
StakeInfo storage info = stakeInfos[_users[i]];
amounts[i] = info.amount;
startTimes[i] = info.startTime;
pendingRewards[i] = info.pendingRewards;
lastClaimTimes[i] = info.lastClaimTime;
}
return (amounts, startTimes, pendingRewards, lastClaimTimes);
}
/**
* @dev 紧急暂停/恢复
*/
bool public paused;
function setPaused(bool _paused) external onlyOwner {
paused = _paused;
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
}
3. 功能说明
3.1 质押代币
solidity
function stake(uint256 _amount) external
- 验证质押数量
- 更新奖励池
- 转入质押代币
- 更新用户质押信息
- 计算并发放待领取奖励
3.2 解除质押
solidity
function unstake(uint256 _amount) external
- 验证解除数量
- 更新奖励池
- 计算并发放奖励
- 更新质押信息
- 转出质押代币
3.3 领取奖励
solidity
function claimReward() external
- 更新奖励池
- 计算待领取奖励
- 更新奖励债务
- 转出奖励代币
4. 奖励计算机制
4.1 待领取奖励查询
solidity
function pendingReward(address _user) external view returns (uint256)
计算公式:
pending = (质押数量 * 每股累计奖励) / 1e12 - 奖励债务
4.2 奖励池更新
solidity
function updatePool() public
更新逻辑:
- 计算区块间隔
- 计算总奖励
- 更新每股累计奖励
- 更新最后奖励区块
5. 安全机制
5.1 数值计算保护
- 使用 SafeMath 库
- 高精度计算(1e12)
- 防止数值溢出
- 严格的数量验证
5.2 访问控制
- 权限管理
- 重入保护
- 暂停机制
- 紧急提取
5.3 状态管理
- 完整的事件记录
- 状态同步检查
- 异常处理机制
- 数据完整性验证
6. 使用示例
6.1 质押代币
javascript
const amount = ethers.utils.parseEther("100");
await stakeToken.approve(staking.address, amount);
await staking.stake(amount);
6.2 查询奖励
javascript
const reward = await staking.pendingReward(userAddress);
console.log("待领取奖励:", ethers.utils.formatEther(reward));
6.3 解除质押
javascript
const amount = ethers.utils.parseEther("50");
await staking.unstake(amount);
7. 总结
该代币质押挖矿系统实现了一个完整的质押奖励机制,包括:
- 灵活的质押和解除机制
- 公平的奖励分配系统
- 实时的奖励计算
- 安全的状态管理
- 完善的事件通知
系统通过精心设计的奖励计算机制和安全措施,确保了质押过程的公平性和安全性,为用户提供了可靠的质押挖矿服务。
常见问题解答(FAQ)
基础概念
Q: 什么是代币质押?
A: 代币质押是一种锁定代币以获得奖励的机制,主要特点包括:
- 代币锁定管理
- 奖励自动计算
- 实时收益分配
- 灵活提取机制
- 公平分配机制
Q: 质押系统有哪些核心功能?
A: 主要功能包括:
- 代币质押/解押
- 奖励计算分配
- 收益实时领取
- 紧急提取机制
- 状态查询功能
操作相关
Q: 如何参与质押?
A: 参与步骤包括:
- 准备足够代币
- 授权合约使用
- 选择质押数量
- 执行质押操作
- 等待奖励生成
Q: 如何计算质押收益?
A: 计算方法包括:
- 区块奖励计算
- 质押比例计算
- 时间周期计算
- 累积奖励计算
- 精度因子处理
安全相关
Q: 质押系统有哪些风险?
A: 主要风险包括:
- 智能合约风险
- 代币价格波动
- 收益率变化
- 锁定期限制
- 紧急情况处理
Q: 如何确保质押安全?
A: 安全措施包括:
- 多重验证机制
- 权限控制系统
- 紧急暂停功能
- 完整的日志记录
- 实时监控预警