Smart Staking Liquidity Pool Model Solidity, Web3
👤 Sharing: AI
```solidity
pragma solidity ^0.8.0;
// SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title Smart Staking Liquidity Pool
* @dev This contract implements a basic staking pool with liquidity provision.
* Users can deposit LP tokens (assumed ERC20) into the pool to earn rewards.
* The rewards are distributed based on the amount of LP tokens staked and the time staked.
* The contract also includes basic governance features like setting rewards per block.
* This contract requires an ERC20 compatible LP token to be already deployed.
*/
contract SmartStakingLiquidityPool is Ownable {
using SafeMath for uint256;
// --- Structs ---
/**
* @dev Represents a user's staking information.
*/
struct UserInfo {
uint256 amount; // Amount of LP tokens staked.
uint256 rewardDebt; // Accumulated reward debt.
uint256 stakedTime; // Timestamp when the user staked.
}
// --- State Variables ---
IERC20 public lpToken; // The LP token contract.
IERC20 public rewardToken; // The reward token contract.
uint256 public rewardPerBlock; // Reward tokens distributed per block.
uint256 public lastRewardBlock; // Last block when rewards were distributed.
uint256 public accRewardPerShare; // Accumulated rewards per share of LP tokens.
mapping(address => UserInfo) public userInfo; // Mapping from user address to user info.
uint256 public totalStaked; // Total LP tokens staked in the pool.
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event RewardPerBlockUpdated(uint256 newRewardPerBlock);
// --- Constructor ---
/**
* @param _lpToken Address of the LP token contract.
* @param _rewardToken Address of the reward token contract.
* @param _rewardPerBlock Initial reward tokens distributed per block.
*/
constructor(
address _lpToken,
address _rewardToken,
uint256 _rewardPerBlock
) {
lpToken = IERC20(_lpToken);
rewardToken = IERC20(_rewardToken);
rewardPerBlock = _rewardPerBlock;
lastRewardBlock = block.number;
}
// --- Modifiers ---
/**
* @dev Modifier to ensure sufficient balance of reward tokens in the contract.
*/
modifier hasSufficientReward() {
require(rewardToken.balanceOf(address(this)) >= (block.number - lastRewardBlock) * rewardPerBlock, "Insufficient reward balance.");
_;
}
// --- Core Functions ---
/**
* @dev Updates the accumulated rewards per share. This is crucial for fair reward distribution.
*/
function updatePool() internal {
if (block.number <= lastRewardBlock) {
return;
}
if (totalStaked == 0) {
lastRewardBlock = block.number;
return;
}
uint256 multiplier = block.number.sub(lastRewardBlock);
uint256 reward = multiplier.mul(rewardPerBlock);
accRewardPerShare = accRewardPerShare.add(reward.mul(1e12).div(totalStaked)); // use 1e12 for better precision
lastRewardBlock = block.number;
}
/**
* @dev Calculates the pending rewards for a user.
* @param _user Address of the user.
* @return The amount of reward tokens the user is entitled to.
*/
function pendingReward(address _user) public view returns (uint256) {
UserInfo storage user = userInfo[_user];
uint256 accReward = accRewardPerShare;
uint256 stakedAmount = user.amount;
if (block.number > lastRewardBlock && totalStaked != 0) {
uint256 multiplier = block.number.sub(lastRewardBlock);
uint256 reward = multiplier.mul(rewardPerBlock);
accReward = accRewardPerShare.add(reward.mul(1e12).div(totalStaked));
}
return stakedAmount.mul(accReward).div(1e12).sub(user.rewardDebt); // use 1e12 precision again
}
/**
* @dev Deposits LP tokens into the pool.
* @param _amount Amount of LP tokens to deposit.
*/
function deposit(uint256 _amount) external hasSufficientReward {
require(_amount > 0, "Cannot deposit zero amount.");
updatePool();
UserInfo storage user = userInfo[msg.sender];
uint256 pending = pendingReward(msg.sender);
if (pending > 0) {
safeRewardTransfer(msg.sender, pending);
emit RewardPaid(msg.sender, pending);
}
if (user.amount > 0) {
user.rewardDebt = user.amount.mul(accRewardPerShare).div(1e12); // use 1e12 precision
}
lpToken.transferFrom(msg.sender, address(this), _amount);
user.amount = user.amount.add(_amount);
totalStaked = totalStaked.add(_amount);
user.stakedTime = block.timestamp;
user.rewardDebt = user.amount.mul(accRewardPerShare).div(1e12); // update reward debt for new amount
emit Deposit(msg.sender, _amount);
}
/**
* @dev Withdraws LP tokens from the pool.
* @param _amount Amount of LP tokens to withdraw.
*/
function withdraw(uint256 _amount) external hasSufficientReward {
require(_amount > 0, "Cannot withdraw zero amount.");
updatePool();
UserInfo storage user = userInfo[msg.sender];
require(user.amount >= _amount, "Insufficient staked balance.");
uint256 pending = pendingReward(msg.sender);
if (pending > 0) {
safeRewardTransfer(msg.sender, pending);
emit RewardPaid(msg.sender, pending);
}
user.rewardDebt = user.amount.mul(accRewardPerShare).div(1e12); // update reward debt before withdraw
user.amount = user.amount.sub(_amount);
totalStaked = totalStaked.sub(_amount);
lpToken.transfer(msg.sender, _amount);
user.rewardDebt = user.amount.mul(accRewardPerShare).div(1e12); // update again if some amount remains
emit Withdraw(msg.sender, _amount);
}
/**
* @dev Withdraws all LP tokens from the pool. Convenience function.
*/
function withdrawAll() external {
withdraw(userInfo[msg.sender].amount);
}
/**
* @dev Emergency withdraw function. Withdraws LP tokens without rewarding. Useful in case of an exploit.
*/
function emergencyWithdraw() external {
UserInfo storage user = userInfo[msg.sender];
uint256 amount = user.amount;
user.amount = 0;
user.rewardDebt = 0;
totalStaked = totalStaked.sub(amount);
lpToken.transfer(msg.sender, amount);
emit Withdraw(msg.sender, amount);
}
/**
* @dev Safe transfer of reward tokens to the user.
* @param _to Address to transfer the tokens to.
* @param _amount Amount of tokens to transfer.
*/
function safeRewardTransfer(address _to, uint256 _amount) internal {
uint256 rewardBalance = rewardToken.balanceOf(address(this));
if (_amount > rewardBalance) {
rewardToken.transfer(_to, rewardBalance);
} else {
rewardToken.transfer(_to, _amount);
}
}
// --- Governance Functions ---
/**
* @dev Sets the reward tokens distributed per block.
* @param _rewardPerBlock New reward tokens distributed per block.
*/
function setRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner {
updatePool();
rewardPerBlock = _rewardPerBlock;
emit RewardPerBlockUpdated(_rewardPerBlock);
}
/**
* @dev Adds reward tokens to the contract. Must be called by the owner.
* @param _amount Amount of reward tokens to add.
*/
function addReward(uint256 _amount) external onlyOwner {
rewardToken.transferFrom(msg.sender, address(this), _amount);
}
/**
* @dev Allows the owner to withdraw any remaining reward tokens from the contract.
*/
function withdrawRemainingReward() external onlyOwner {
uint256 balance = rewardToken.balanceOf(address(this));
rewardToken.transfer(owner(), balance);
}
}
```
**Explanation:**
1. **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version. `^0.8.0` means any compiler version from 0.8.0 up to (but not including) 0.9.0.
2. **`import "@openzeppelin/contracts/token/ERC20/IERC20.sol";`**: Imports the `IERC20` interface from OpenZeppelin, which defines the standard functions for interacting with ERC20 tokens.
3. **`import "@openzeppelin/contracts/access/Ownable.sol";`**: Imports the `Ownable` contract from OpenZeppelin, providing a basic access control mechanism where only the owner can call certain functions.
4. **`import "@openzeppelin/contracts/utils/math/SafeMath.sol";`**: Imports the `SafeMath` library from OpenZeppelin for performing safe arithmetic operations, preventing integer overflows and underflows. While Solidity 0.8.0 has built-in overflow protection, this is still included for clarity and potential compatibility with older compiler versions.
5. **`contract SmartStakingLiquidityPool is Ownable { ... }`**: Defines the main contract, inheriting from `Ownable`.
6. **`using SafeMath for uint256;`**: Enables the `SafeMath` library's functions to be used directly on `uint256` variables.
7. **`struct UserInfo { ... }`**: Defines a struct to store user-specific information:
* `amount`: The amount of LP tokens the user has staked.
* `rewardDebt`: A crucial variable for tracking the user's accumulated reward debt. This prevents rounding errors from affecting reward distribution. It represents the user's share of rewards *already* accounted for.
* `stakedTime`: The timestamp when the user staked (or last deposited).
8. **State Variables:**
* `IERC20 public lpToken;`: The address of the LP token (the token being staked). It's declared as `IERC20` so the contract can interact with it using the ERC20 interface.
* `IERC20 public rewardToken;`: The address of the reward token (the token users receive for staking).
* `uint256 public rewardPerBlock;`: The amount of reward tokens distributed per block.
* `uint256 public lastRewardBlock;`: The block number of the last reward distribution.
* `uint256 public accRewardPerShare;`: Accumulated reward per share of LP tokens staked. This is the core mechanism for distributing rewards fairly. It represents the total reward distributed divided by the total LP tokens staked.
* `mapping(address => UserInfo) public userInfo;`: A mapping that stores the `UserInfo` for each user.
* `uint256 public totalStaked;`: The total amount of LP tokens staked in the pool.
9. **Events:** `Deposit`, `Withdraw`, `RewardPaid`, and `RewardPerBlockUpdated` events are emitted to log important actions.
10. **`constructor(address _lpToken, address _rewardToken, uint256 _rewardPerBlock) { ... }`**: The constructor initializes the contract:
* Sets the `lpToken`, `rewardToken`, and `rewardPerBlock` variables.
* Sets the `lastRewardBlock` to the current block number.
11. **`modifier hasSufficientReward() { ... }`**: A modifier that checks if the contract has enough reward tokens to distribute based on the time elapsed since the last reward distribution and the `rewardPerBlock`. Prevents reward distribution if the contract is out of reward tokens.
12. **`updatePool() internal { ... }`**: This is the heart of the reward distribution logic. It updates the `accRewardPerShare` variable, which is used to calculate individual user rewards.
* It checks if any rewards have been issued since the last update and adjusts accordingly.
* It's called at the beginning of `deposit()` and `withdraw()` to ensure that the reward pool is up-to-date before any user actions.
* It uses `1e12` for increased precision when calculating the accumulated rewards per share. This helps to minimize rounding errors.
13. **`pendingReward(address _user) public view returns (uint256) { ... }`**: Calculates the pending rewards for a given user *without* actually distributing them. This is a `view` function, meaning it doesn't modify the contract's state.
14. **`deposit(uint256 _amount) external hasSufficientReward { ... }`**: Allows users to deposit LP tokens into the pool:
* Requires the deposit amount to be greater than 0.
* Calls `updatePool()` to update the reward state.
* Calculates and transfers any pending rewards to the user.
* Transfers the deposited LP tokens from the user to the contract.
* Updates the user's `amount`, `stakedTime`, and `rewardDebt`.
* Increments `totalStaked`.
15. **`withdraw(uint256 _amount) external hasSufficientReward { ... }`**: Allows users to withdraw LP tokens from the pool:
* Requires the withdraw amount to be greater than 0.
* Calls `updatePool()` to update the reward state.
* Calculates and transfers any pending rewards to the user.
* Transfers the withdrawn LP tokens from the contract to the user.
* Updates the user's `amount` and `rewardDebt`.
* Decrements `totalStaked`.
16. **`withdrawAll() external { ... }`**: A convenience function to withdraw all of a user's staked tokens.
17. **`emergencyWithdraw() external { ... }`**: A function designed to allow users to withdraw their LP tokens quickly in case of an emergency or exploit. It does *not* distribute any rewards.
18. **`safeRewardTransfer(address _to, uint256 _amount) internal { ... }`**: A helper function to safely transfer reward tokens to a user. It checks if the contract has enough reward tokens and only transfers what's available. Important to prevent the contract from failing if it doesn't have enough rewards.
19. **`setRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner { ... }`**: Allows the owner to set the `rewardPerBlock`.
20. **`addReward(uint256 _amount) external onlyOwner { ... }`**: Allows the owner to add more reward tokens to the contract.
21. **`withdrawRemainingReward() external onlyOwner { ... }`**: Allows the owner to withdraw any remaining reward tokens from the contract.
**Key Concepts:**
* **Staking Pool:** A smart contract that allows users to lock up their tokens (LP tokens in this case) in exchange for rewards.
* **Liquidity Pool (Implied):** This contract is designed to work *with* a liquidity pool. The `lpToken` represents tokens received for providing liquidity to another contract (e.g., a decentralized exchange like Uniswap or PancakeSwap). The staking contract rewards users for providing liquidity elsewhere.
* **Reward Distribution:** The core logic of this contract is the fair distribution of reward tokens. The `accRewardPerShare` and `rewardDebt` variables are crucial for accurately tracking and distributing rewards.
* **Governance:** The `Ownable` contract provides basic governance, allowing only the owner to modify certain parameters like the `rewardPerBlock`.
* **Security:** The use of `SafeMath` (or Solidity 0.8+ overflow protection) and the `safeRewardTransfer` function are important for security. The `emergencyWithdraw` function provides a way for users to withdraw their funds quickly in case of an issue.
* **Precision:** The use of `1e12` (or any large number) for precision in the `accRewardPerShare` calculation is a common technique to reduce rounding errors in Solidity. This is especially important when dealing with small amounts of tokens.
**How to Use (Conceptual):**
1. **Deploy LP Token:** Deploy an ERC20 compatible LP token contract. This token represents a user's share in a liquidity pool (e.g., Uniswap v2 or PancakeSwap).
2. **Deploy Reward Token:** Deploy another ERC20 token contract. This is the token that will be given as a reward for staking LP tokens.
3. **Deploy Staking Contract:** Deploy the `SmartStakingLiquidityPool` contract, providing the addresses of the LP token and reward token contracts, and the initial reward per block.
4. **Add Reward Tokens:** The owner needs to transfer reward tokens to the `SmartStakingLiquidityPool` contract using the `addReward` function.
5. **Users Deposit:** Users deposit their LP tokens into the staking contract using the `deposit` function. They must first approve the staking contract to spend their LP tokens using the `lpToken.approve(address(stakingContract), amount)` function.
6. **Rewards Accumulate:** Rewards accumulate based on the `rewardPerBlock` and the amount of LP tokens staked.
7. **Users Withdraw:** Users can withdraw their LP tokens and claim their accumulated rewards using the `withdraw` function.
8. **Owner Updates Rewards:** The owner can update the `rewardPerBlock` as needed.
**Important Considerations:**
* **Auditing:** This contract should be thoroughly audited before being deployed to a production environment.
* **Front-Running:** Be aware of potential front-running attacks, especially when setting the `rewardPerBlock`. Consider using a more complex governance mechanism to mitigate this risk.
* **Reentrancy:** While the OpenZeppelin contracts used are generally reentrancy safe, always consider the possibility of reentrancy vulnerabilities when interacting with external contracts.
* **Gas Costs:** Reward distribution can be gas-intensive. Consider optimizing the contract for gas efficiency. The use of `1e12` helps but other optimizations may be needed. Potentially batch operations.
* **Immutability:** Once deployed, the contract's core logic is immutable. Carefully consider the design and ensure it meets your requirements before deployment.
This example provides a solid foundation for a smart staking liquidity pool. You can extend it with more features, such as:
* Tiered rewards based on staking duration.
* Lockup periods for staked tokens.
* Governance features like voting on reward parameters.
* Fee structures for withdrawals.
* Integration with oracles for dynamically adjusting rewards.
👁️ Viewed: 9
Comments