Token Liquidity Mining
Liquidity mining is an important incentive mechanism in the DeFi ecosystem, used to attract users to provide liquidity. This tutorial will explain how to implement a complete liquidity mining system.
Features
- LP token staking mining
- Multi-pool support
- Dynamic reward distribution
- Reinvestment mechanism
- Emergency withdrawal function
Contract Implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
contract LiquidityMining is Ownable, ReentrancyGuard, Pausable {
// Pool information
struct Pool {
IERC20 lpToken; // LP token address
IERC20 rewardToken; // Reward token address
uint256 lastRewardBlock;// Last block where rewards were updated
uint256 accRewardPerShare; // Accumulated reward per share
uint256 allocPoint; // Allocation weight
uint256 totalStaked; // Total staked amount
}
// User information in pool
struct UserInfo {
uint256 amount; // Staked amount
uint256 rewardDebt; // Reward debt
uint256 pendingRewards; // Pending rewards
}
// Pool list
Pool[] public pools;
// User information mapping poolId => user => info
mapping(uint256 => mapping(address => UserInfo)) public userInfo;
// Reward per block
uint256 public rewardPerBlock;
// Total allocation points
uint256 public totalAllocPoint;
// Start mining block
uint256 public startBlock;
// End mining block
uint256 public endBlock;
// Events
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
event RewardPaid(address indexed user, uint256 indexed pid, uint256 amount);
event PoolAdded(uint256 indexed pid, address lpToken, address rewardToken, uint256 allocPoint);
event PoolUpdated(uint256 indexed pid, uint256 allocPoint);
constructor(
uint256 _rewardPerBlock,
uint256 _startBlock,
uint256 _endBlock
) {
rewardPerBlock = _rewardPerBlock;
startBlock = _startBlock;
endBlock = _endBlock;
}
// Add new mining pool
function addPool(
IERC20 _lpToken,
IERC20 _rewardToken,
uint256 _allocPoint,
bool _withUpdate
) external onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
totalAllocPoint += _allocPoint;
pools.push(Pool({
lpToken: _lpToken,
rewardToken: _rewardToken,
lastRewardBlock: lastRewardBlock,
accRewardPerShare: 0,
allocPoint: _allocPoint,
totalStaked: 0
}));
emit PoolAdded(pools.length - 1, address(_lpToken), address(_rewardToken), _allocPoint);
}
// Update pool allocation points
function setPoolAllocPoint(
uint256 _pid,
uint256 _allocPoint,
bool _withUpdate
) external onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
totalAllocPoint = totalAllocPoint - pools[_pid].allocPoint + _allocPoint;
pools[_pid].allocPoint = _allocPoint;
emit PoolUpdated(_pid, _allocPoint);
}
// Update all pools
function massUpdatePools() public {
uint256 length = pools.length;
for (uint256 pid = 0; pid < length; ++pid) {
updatePool(pid);
}
}
// Update single pool
function updatePool(uint256 _pid) public {
Pool storage pool = pools[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
if (pool.totalStaked == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 reward = multiplier * rewardPerBlock * pool.allocPoint / totalAllocPoint;
pool.accRewardPerShare += reward * 1e12 / pool.totalStaked;
pool.lastRewardBlock = block.number;
}
// Stake LP tokens
function deposit(uint256 _pid, uint256 _amount) external nonReentrant whenNotPaused {
Pool storage pool = pools[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
if (user.amount > 0) {
uint256 pending = user.amount * pool.accRewardPerShare / 1e12 - user.rewardDebt;
if (pending > 0) {
user.pendingRewards += pending;
}
}
if (_amount > 0) {
pool.lpToken.transferFrom(msg.sender, address(this), _amount);
user.amount += _amount;
pool.totalStaked += _amount;
}
user.rewardDebt = user.amount * pool.accRewardPerShare / 1e12;
emit Deposit(msg.sender, _pid, _amount);
}
// Withdraw LP tokens
function withdraw(uint256 _pid, uint256 _amount) external nonReentrant {
Pool storage pool = pools[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount >= _amount, "withdraw: not enough");
updatePool(_pid);
uint256 pending = user.amount * pool.accRewardPerShare / 1e12 - user.rewardDebt;
if (pending > 0) {
user.pendingRewards += pending;
}
if (_amount > 0) {
user.amount -= _amount;
pool.totalStaked -= _amount;
pool.lpToken.transfer(msg.sender, _amount);
}
user.rewardDebt = user.amount * pool.accRewardPerShare / 1e12;
emit Withdraw(msg.sender, _pid, _amount);
}
// Claim rewards
function harvest(uint256 _pid) external nonReentrant {
Pool storage pool = pools[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
updatePool(_pid);
uint256 pending = user.amount * pool.accRewardPerShare / 1e12 - user.rewardDebt;
if (pending > 0 || user.pendingRewards > 0) {
uint256 totalReward = pending + user.pendingRewards;
user.pendingRewards = 0;
pool.rewardToken.transfer(msg.sender, totalReward);
emit RewardPaid(msg.sender, _pid, totalReward);
}
user.rewardDebt = user.amount * pool.accRewardPerShare / 1e12;
}
// Emergency withdrawal
function emergencyWithdraw(uint256 _pid) external nonReentrant {
Pool storage pool = pools[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
uint256 amount = user.amount;
user.amount = 0;
user.rewardDebt = 0;
user.pendingRewards = 0;
pool.totalStaked -= amount;
pool.lpToken.transfer(msg.sender, amount);
emit EmergencyWithdraw(msg.sender, _pid, amount);
}
// Calculate block reward multiplier
function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
if (_to <= endBlock) {
return _to - _from;
} else if (_from >= endBlock) {
return 0;
} else {
return endBlock - _from;
}
}
// View pending rewards
function pendingReward(uint256 _pid, address _user) external view returns (uint256) {
Pool storage pool = pools[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accRewardPerShare = pool.accRewardPerShare;
if (block.number > pool.lastRewardBlock && pool.totalStaked != 0) {
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 reward = multiplier * rewardPerBlock * pool.allocPoint / totalAllocPoint;
accRewardPerShare += reward * 1e12 / pool.totalStaked;
}
return user.amount * accRewardPerShare / 1e12 - user.rewardDebt + user.pendingRewards;
}
// Pause/Unpause (admin function)
function setPaused(bool _paused) external onlyOwner {
if (_paused) {
_pause();
} else {
_unpause();
}
}
}
Key Concepts
Pool Management
The pool system supports:
- Multiple pools running in parallel
- Dynamic weight allocation
- Independent reward tokens
- Flexible parameter adjustment
Reward Calculation
The reward mechanism includes:
- Block reward distribution
- Weight ratio calculation
- Accumulated reward update
- User share calculation
Staking Operations
Staking functions support:
- LP token deposit and withdrawal
- Real-time reward calculation
- Reinvestment operations
- Emergency withdrawal
Security Considerations
Fund Security
- Balance check
- Transfer verification
- Overflow protection
Permission Control
- Admin function restrictions
- Pause mechanism
- Parameter validation
Reentrancy Protection
- Use ReentrancyGuard
- State update order
- External call protection
Exception Handling
- Emergency withdrawal
- State recovery
- Error handling
Best Practices
Pool Configuration
- Reasonable reward rate
- Appropriate weight allocation
- Regular parameter adjustments
Revenue Management
- Reward token reserve
- Inflation control
- Sustainable planning
User Experience
- Simple operation process
- Clear revenue display
- Timely state updates
System Maintenance
- Regular state check
- Monitor abnormal situations
- Address issues promptly
Extended Features
- Reward multiplier
- Lock period setting
- Automatic reinvestment
- Recommendation rewards
- Governance rights
Summary
Liquidity mining systems are important components of DeFi projects. Through this tutorial, you can:
- Implement a complete mining mechanism
- Establish a secure reward system
- Optimize user participation experience
- Ensure system sustainability
Frequently Asked Questions (FAQ)
Basic Concepts
Q: What is liquidity mining?
A: Liquidity mining is a DeFi incentive mechanism, characterized by:
- Providing liquidity for rewards
- Multiple pools running in parallel
- Dynamic reward distribution
- Automatic reinvestment mechanism
- Flexible withdrawal mechanism
Q: What types of liquidity mining are there?
A: The main types include:
- Single-token mining
- Trading pair mining
- Index fund mining
- Stablecoin mining
- Synthetic asset mining
Operational Related
Q: How to participate in liquidity mining?
A: The steps include:
- Preparing LP tokens
- Selecting mining pools
- Staking LP tokens
- Waiting for revenue generation
- Claiming rewards periodically
Q: How to manage mining revenue?
A: Management methods include:
- Monitoring yield
- Calculating investment returns
- Choosing reinvestment timing
- Adjusting staking strategies
- Optimizing pool selection
Security Related
Q: What are the risks of liquidity mining?
A: The main risks include:
- Impermanent loss risk
- Smart contract risk
- Token price volatility
- Yield rate changes
- Liquidity pool squeezes
Q: How to reduce mining risks?
A: Prevention measures include:
- Diversifying investment pools
- Regularly checking yields
- Setting stop-loss strategies
- Keeping an eye on project progress
- Adjusting positions in a timely manner