Skip to content
Chain 4441 · LiteForge TestnetHead#0
LitVM·Scan
Hard Money Web3 Explorer
Verified Contract
0x47ac007000F24AD4758eaA6Fcd1dAC020143C16a
Pin to watchlist
scan to copy
zkLTC Balance
0
hard money native
Transactions
32
Token transfers
26
Validations
0
blocks produced
Hard Money Score
55
/100
MIXED
share of activity in native zkLTC vs. tokens
native
32
tokens
26
Contract name
LiquidityProvider
Compiler
v0.8.20+commit.a1b79de6
Optimization
disabled
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

interface ILPToken is IERC20 {
    function token0() external view returns (address);
    function token1() external view returns (address);
}

contract LiquidityProvider is Ownable {
    // Info of each user.
    struct UserInfo {
        uint256 amount;
        uint256 rewardDebt;
        uint256 pendingRewards;
    }

    // Info of each pool.
    struct PoolInfo {
        address lpToken;
        uint256 allocPoint;
        uint256 lastRewardBlock;
        uint256 accTokenPerShare;
        uint256 totalLocked;
    }

    // The reward token
    address public rewardToken;
    
    // Rewards per block
    uint256 public rewardPerBlock = 1e18; // 1 token per block
    
    // Bonus mulitplier for early miners
    uint256 public constant BONUS_MULTIPLIER = 1;
    
    // Max emission rate
    uint256 public constant MAX_EMISSION = 10e18;
    
    // Info of each pool
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    // Total allocation points
    uint256 public totalAllocPoint = 0;
    // The block number when mining starts
    uint256 public startBlock;
    // The block number when mining ends
    uint256 public endBlock;
    
    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 PoolAdded(uint256 pid, address lpToken, uint256 allocPoint);
    event SetPool(uint256 pid, uint256 allocPoint);
    event UpdateEmissionRate(uint256 rewardPerBlock);

    constructor(address _rewardToken, address _owner) Ownable() {
        _transferOwnership(_owner);
        rewardToken = _rewardToken;
        startBlock = block.number;
        endBlock = startBlock + 5256000; // ~1 year with 2s block time
    }

    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    function addPool(uint256 _allocPoint, address _lpToken, bool _withUpdate) external onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint + _allocPoint;
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardBlock: lastRewardBlock,
            accTokenPerShare: 0,
            totalLocked: 0
        }));
        emit PoolAdded(poolInfo.length - 1, _lpToken, _allocPoint);
    }

    function setPool(uint256 _pid, uint256 _allocPoint, bool _withUpdate) external onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        totalAllocPoint = totalAllocPoint - poolInfo[_pid].allocPoint + _allocPoint;
        poolInfo[_pid].allocPoint = _allocPoint;
        emit SetPool(_pid, _allocPoint);
    }

    function setRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner {
        require(_rewardPerBlock <= MAX_EMISSION, "Too high");
        rewardPerBlock = _rewardPerBlock;
        emit UpdateEmissionRate(_rewardPerBlock);
    }

    function pendingReward(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accTokenPerShare = pool.accTokenPerShare;
        uint256 lpSupply = pool.totalLocked;
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
            uint256 tokenReward = multiplier * rewardPerBlock * pool.allocPoint / totalAllocPoint;
            accTokenPerShare = accTokenPerShare + tokenReward * 1e12 / lpSupply;
        }
        return user.amount * accTokenPerShare / 1e12 - user.rewardDebt + user.pendingRewards;
    }

    function deposit(uint256 _pid, uint256 _amount) external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        
        updatePool(_pid);
        
        if (user.amount > 0) {
            uint256 pending = user.amount * pool.accTokenPerShare / 1e12 - user.rewardDebt + user.pendingRewards;
            user.pendingRewards = pending;
        }
        
        if (_amount > 0) {
            IERC20(pool.lpToken).transferFrom(msg.sender, address(this), _amount);
            user.amount = user.amount + _amount;
            pool.totalLocked = pool.totalLocked + _amount;
        }
        
        user.rewardDebt = user.amount * pool.accTokenPerShare / 1e12;
        emit Deposit(msg.sender, _pid, _amount);
    }

    function withdraw(uint256 _pid, uint256 _amount) external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        
        require(user.amount >= _amount, "Withdraw amount exceeds balance");
        
        updatePool(_pid);
        
        uint256 pending = user.amount * pool.accTokenPerShare / 1e12 - user.rewardDebt + user.pendingRewards;
        user.pendingRewards = 0;
        
        if (pending > 0) {
            IERC20(rewardToken).transfer(msg.sender, pending);
        }
        
        if (_amount > 0) {
            user.amount = user.amount - _amount;
            pool.totalLocked = pool.totalLocked - _amount;
            IERC20(pool.lpToken).transfer(msg.sender, _amount);
        }
        
        user.rewardDebt = user.amount * pool.accTokenPerShare / 1e12;
        emit Withdraw(msg.sender, _pid, _amount);
    }

    function emergencyWithdraw(uint256 _pid) external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        
        uint256 amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;
        user.pendingRewards = 0;
        pool.totalLocked = pool.totalLocked - amount;
        
        IERC20(pool.lpToken).transfer(msg.sender, amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }

    function getMultiplier(uint256 from, uint256 to) public pure returns (uint256) {
        return to - from;
    }

    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = pool.totalLocked;
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
        uint256 tokenReward = multiplier * rewardPerBlock * pool.allocPoint / totalAllocPoint;
        pool.accTokenPerShare = pool.accTokenPerShare + tokenReward * 1e12 / lpSupply;
        pool.lastRewardBlock = block.number;
    }

    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    function rescueTokens(address token, address to, uint256 amount) external onlyOwner {
        require(to != address(0), "Invalid recipient");
        IERC20(token).transfer(to, amount);
    }
}