APE Price: $0.44 (+2.05%)

Contract Diff Checker

Contract Name:
LPStaking

Contract Source Code:

File 1 of 1 : LPStaking

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// IERC20 Interface
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// Ownable Contract
abstract contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor() {
        _transferOwnership(msg.sender);
    }

    function owner() public view virtual returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(owner() == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// ReentrancyGuard Contract
abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

// Pausable Contract
abstract contract Pausable {
    event Paused(address account);
    event Unpaused(address account);

    bool private _paused;

    constructor() {
        _paused = false;
    }

    function paused() public view virtual returns (bool) {
        return _paused;
    }

    modifier whenNotPaused() {
        require(!paused(), "Pausable: paused");
        _;
    }

    modifier whenPaused() {
        require(paused(), "Pausable: not paused");
        _;
    }

    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(msg.sender);
    }

    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(msg.sender);
    }
}

/**
 * @title LPStaking Contract
 * @notice This contract handles staking of LP tokens with dual rewards and anti-abuse features
 */
contract LPStaking is Ownable, ReentrancyGuard, Pausable {
    // ============ Events ============
    event Staked(address indexed user, uint256 amount);
    event Unstaked(address indexed user, uint256 amount);
    event RewardsClaimed(address indexed user, uint256 amount, uint256 secondaryAmount);
    event MinimumStakeAmountUpdated(uint256 oldAmount, uint256 newAmount);
    event LockPeriodUpdated(uint256 oldPeriod, uint256 newPeriod);
    event DynamicRewardRateUpdated(uint256 oldRate, uint256 newRate);
    event BonusMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier);
    event BlacklistStatusUpdated(address indexed user, bool status);
    event EmergencyWithdraw(address indexed user, uint256 amount);
    event TimeTierUpdated(uint256 tierId, uint256 oldPeriod, uint256 newPeriod);
    event MaxRewardRateUpdated(uint256 oldRate, uint256 newRate);
    event MaxBonusMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier);
    event TierThresholdUpdated(uint256 tierId, uint256 oldAmount, uint256 newAmount);

    // ============ Constants ============
    uint256 public MAX_REWARD_RATE = 10000; // 100%
    uint256 public MAX_BONUS_MULTIPLIER = 1000; // 10x
    uint256 public TIER1_THRESHOLD = 1000e18;    // 1,000 tokens
    uint256 public TIER2_THRESHOLD = 10000e18;   // 10,000 tokens
    uint256 public TIER3_THRESHOLD = 100000e18;  // 100,000 tokens
    uint256 public TIME_TIER1 = 7 days;    // 1 week
    uint256 public TIME_TIER2 = 14 days;   // 2 weeks
    uint256 public TIME_TIER3 = 30 days;   // 1 month

    // ============ Token Configuration ============
    IERC20 public dexLPToken;
    IERC20 public HACCoinReward;

    // ============ Reward Configuration ============
    uint256 public baseRewardRate; // Base reward rate
    uint256 public dynamicRewardRate; // Additional dynamic reward during high-traffic periods
    uint256 public bonusMultiplier; // Bonus multiplier for early or large contributions

    // ============ Staking Configuration ============
    uint256 public minimumStakeAmount;
    uint256 public lockPeriod;

    // ============ User Data ============
    mapping(address => uint256) public stakedAmount;
    mapping(address => uint256) public stakingStartTime;
    mapping(address => uint256) public lastClaimTime;
    mapping(address => bool) public blacklisted;

    // ============ Constructor ============
    constructor(
        address _dexLPTokenAddress,      // <-- dex LP token address
        address _HACCoinContractAddress,  // <-- HACCoin token address
        uint256 _baseRewardRate,
        uint256 _bonusMultiplier,
        uint256 _minimumStakeAmount,
        uint256 _lockPeriod
    ) {
        require(_dexLPTokenAddress != address(0), "Zero address not allowed");
        require(_HACCoinContractAddress != address(0), "Zero address not allowed");
        require(_baseRewardRate <= MAX_REWARD_RATE, "Rate too high");
        dexLPToken = IERC20(_dexLPTokenAddress);
        HACCoinReward = IERC20(_HACCoinContractAddress);
        baseRewardRate = _baseRewardRate;
        bonusMultiplier = _bonusMultiplier;
        minimumStakeAmount = _minimumStakeAmount;
        lockPeriod = _lockPeriod;
    }

    // ============ Modifiers ============
    modifier notBlacklisted() {
        require(!blacklisted[msg.sender], "Account is blacklisted");
        _;
    }

    // ============ Core Staking Functions ============
    function stake(uint256 amount) external nonReentrant whenNotPaused notBlacklisted {
        require(amount >= minimumStakeAmount, "Below minimum stake amount");
        require(amount > 0, "Cannot stake 0");

        // Transfer LP tokens to this contract
        dexLPToken.transferFrom(msg.sender, address(this), amount);

        // If they already have staked tokens, update their rewards checkpoint
        if (stakedAmount[msg.sender] > 0) {
            lastClaimTime[msg.sender] = block.timestamp;
        } else {
            // For first-time stakers, set initial claim time
            lastClaimTime[msg.sender] = block.timestamp;
        }

        // Update staking amount and time
        stakedAmount[msg.sender] += amount;
        stakingStartTime[msg.sender] = block.timestamp;

        emit Staked(msg.sender, amount);
    }

    function unstake(uint256 amount) external nonReentrant whenNotPaused notBlacklisted {
        require(block.timestamp >= stakingStartTime[msg.sender] + lockPeriod, "Still in lock period");
        require(stakedAmount[msg.sender] >= amount, "Insufficient staked balance");

        // Calculate and distribute pending rewards before unstaking
        uint256 pendingReward = calculateReward(msg.sender);
        if (pendingReward > 0) {
            distributeRewards(msg.sender, pendingReward);
            lastClaimTime[msg.sender] = block.timestamp;
        }

        // Update staking amount and transfer tokens back
        stakedAmount[msg.sender] -= amount;
        require(dexLPToken.transfer(msg.sender, amount), "Transfer failed");

        emit Unstaked(msg.sender, amount);
    }

    function claimRewards() external nonReentrant whenNotPaused notBlacklisted {
        uint256 pendingReward = calculateReward(msg.sender);
        require(pendingReward > 0, "No rewards to claim");
        
        // Update last claim time before distribution
        lastClaimTime[msg.sender] = block.timestamp;
        
        // Calculate and distribute rewards
        uint256 secondaryReward = pendingReward / 10;
        distributeRewards(msg.sender, pendingReward);
        
        emit RewardsClaimed(msg.sender, pendingReward, secondaryReward);
    }

    // ============ View Functions ============
    function calculateReward(address user) public view returns (uint256) {
        if (stakedAmount[user] == 0) return 0;
        
        uint256 duration = block.timestamp - lastClaimTime[user];
        uint256 dynamicRate = baseRewardRate + dynamicRewardRate;
        uint256 amountMultiplier = getAmountMultiplier(stakedAmount[user]);
        uint256 totalStakingDuration = block.timestamp - stakingStartTime[user];
        uint256 timeMultiplier = getTimeMultiplier(totalStakingDuration);
        
        uint256 reward = (stakedAmount[user] * dynamicRate * duration) / 1e18;
        reward = reward * amountMultiplier * timeMultiplier / 10000;
        reward = reward * bonusMultiplier / 100;
        
        return reward;
    }

    function getTotalStaked() external view returns (uint256) {
        return dexLPToken.balanceOf(address(this));
    }

    function getUserStakeInfo(address user) external view returns (
        uint256 staked,
        uint256 startTime,
        uint256 pendingRewards,
        bool isBlacklisted
    ) {
        return (
            stakedAmount[user],
            stakingStartTime[user],
            calculateReward(user),
            blacklisted[user]
        );
    }

    function getLPTokenAllowance(address user) external view returns (uint256) {
        return dexLPToken.allowance(user, address(this));
    }

    // ============ Internal Functions ============
    function distributeRewards(address user, uint256 rewardAmount) public {
        require(HACCoinReward.balanceOf(address(this)) >= rewardAmount, "Insufficient rewards");
        uint256 secondaryReward = rewardAmount / 10;
        require(address(this).balance >= secondaryReward, "Insufficient secondary rewards");
        
        require(HACCoinReward.transfer(user, rewardAmount), "Reward transfer failed");
        (bool success, ) = payable(user).call{value: secondaryReward}("");
        require(success, "Native token transfer failed");
    }

    // ============ Admin Functions ============
    // Pause/Unpause
    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    // Rate Management
    function setDynamicRewardRate(uint256 rate) external onlyOwner {
        require(rate <= MAX_REWARD_RATE, "Rate too high");
        uint256 oldRate = dynamicRewardRate;
        dynamicRewardRate = rate;
        emit DynamicRewardRateUpdated(oldRate, rate);
    }

    function setBonusMultiplier(uint256 multiplier) external onlyOwner {
        require(multiplier > 0 && multiplier <= MAX_BONUS_MULTIPLIER, "Invalid multiplier");
        uint256 oldMultiplier = bonusMultiplier;
        bonusMultiplier = multiplier;
        emit BonusMultiplierUpdated(oldMultiplier, multiplier);
    }

    // Configuration Management
    function setMinimumStakeAmount(uint256 amount) external onlyOwner {
        uint256 oldAmount = minimumStakeAmount;
        minimumStakeAmount = amount;
        emit MinimumStakeAmountUpdated(oldAmount, amount);
    }

    function setLockPeriod(uint256 period) external onlyOwner {
        uint256 oldPeriod = lockPeriod;
        lockPeriod = period;
        emit LockPeriodUpdated(oldPeriod, period);
    }

    // Access Control
    function setBlacklist(address user, bool status) external onlyOwner {
        require(user != address(0), "Invalid address");
        blacklisted[user] = status;
        emit BlacklistStatusUpdated(user, status);
    }

    function batchSetBlacklist(address[] calldata users, bool status) external onlyOwner {
        for(uint256 i = 0; i < users.length; i++) {
            require(users[i] != address(0), "Invalid address");
            blacklisted[users[i]] = status;
            emit BlacklistStatusUpdated(users[i], status);
        }
    }

    // ============ Emergency Functions ============
    function emergencyWithdraw() external nonReentrant {
        uint256 amount = stakedAmount[msg.sender];
        require(amount > 0, "No staked tokens");
        
        stakedAmount[msg.sender] = 0;
        stakingStartTime[msg.sender] = 0;
        dexLPToken.transfer(msg.sender, amount);
        
        emit EmergencyWithdraw(msg.sender, amount);
    }

    function emergencyRewardWithdraw() external onlyOwner {
        uint256 rewardBalance = HACCoinReward.balanceOf(address(this));
        uint256 nativeBalance = address(this).balance;
        
        if (rewardBalance > 0) {
            HACCoinReward.transfer(owner(), rewardBalance);
        }
        if (nativeBalance > 0) {
            (bool success, ) = payable(owner()).call{value: nativeBalance}("");
            require(success, "Native token transfer failed");
        }
    }

    function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
        require(tokenAddress != address(dexLPToken), "Cannot recover staking token");
        IERC20(tokenAddress).transfer(owner(), tokenAmount);
    }

    function getRewardBalances() external view returns (uint256 newTokenBalance, uint256 nativeBalance) {
        return (
            HACCoinReward.balanceOf(address(this)),
            address(this).balance
        );
    }

    // ============ Helper Functions ============
    function getAmountMultiplier(uint256 amount) public view returns (uint256) {
        if (amount >= TIER3_THRESHOLD) {
            return 150;    // 1.5x multiplier
        } else if (amount >= TIER2_THRESHOLD) {
            return 125;    // 1.25x multiplier
        } else if (amount >= TIER1_THRESHOLD) {
            return 110;    // 1.1x multiplier
        }
        return 100;       // 1x multiplier (base)
    }

    function getTimeMultiplier(uint256 duration) public view returns (uint256) {
        if (duration >= TIME_TIER3) {
            return 150;    // 1.5x multiplier
        } else if (duration >= TIME_TIER2) {
            return 125;    // 1.25x multiplier
        } else if (duration >= TIME_TIER1) {
            return 110;    // 1.1x multiplier
        }
        return 100;       // 1x multiplier (base)
    }

    function getUserMultipliers(address user) external view returns (
        uint256 amountMultiplier,
        uint256 timeMultiplier,
        uint256 totalMultiplier
    ) {
        uint256 duration = block.timestamp - stakingStartTime[user];
        amountMultiplier = getAmountMultiplier(stakedAmount[user]);
        timeMultiplier = getTimeMultiplier(duration);
        totalMultiplier = (amountMultiplier * timeMultiplier * bonusMultiplier) / 1000; // Final multiplier in basis points
        return (amountMultiplier, timeMultiplier, totalMultiplier);
    }

    // ============ Time Tier Functions ============
    function setTimeTier1(uint256 newPeriod) external onlyOwner {
        require(newPeriod < TIME_TIER2, "TIME_TIER1 must be less than TIME_TIER2");
        require(newPeriod > 0, "Period cannot be zero");
        uint256 oldPeriod = TIME_TIER1;
        TIME_TIER1 = newPeriod;
        emit TimeTierUpdated(1, oldPeriod, newPeriod);
    }

    function setTimeTier2(uint256 newPeriod) external onlyOwner {
        require(newPeriod > TIME_TIER1, "TIME_TIER2 must be greater than TIME_TIER1");
        require(newPeriod < TIME_TIER3, "TIME_TIER2 must be less than TIME_TIER3");
        uint256 oldPeriod = TIME_TIER2;
        TIME_TIER2 = newPeriod;
        emit TimeTierUpdated(2, oldPeriod, newPeriod);
    }

    function setTimeTier3(uint256 newPeriod) external onlyOwner {
        require(newPeriod > TIME_TIER2, "TIME_TIER3 must be greater than TIME_TIER2");
        uint256 oldPeriod = TIME_TIER3;
        TIME_TIER3 = newPeriod;
        emit TimeTierUpdated(3, oldPeriod, newPeriod);
    }

    function getTimeTiers() external view returns (uint256, uint256, uint256) {
        return (TIME_TIER1, TIME_TIER2, TIME_TIER3);
    }

    // ============ Setter Functions ============
    function setMaxRewardRate(uint256 newRate) external onlyOwner {
        uint256 oldRate = MAX_REWARD_RATE;
        MAX_REWARD_RATE = newRate;
        emit MaxRewardRateUpdated(oldRate, newRate);
    }

    function setMaxBonusMultiplier(uint256 newMultiplier) external onlyOwner {
        uint256 oldMultiplier = MAX_BONUS_MULTIPLIER;
        MAX_BONUS_MULTIPLIER = newMultiplier;
        emit MaxBonusMultiplierUpdated(oldMultiplier, newMultiplier);
    }

    function setTierThreshold(uint256 tierId, uint256 newAmount) external onlyOwner {
        require(tierId >= 1 && tierId <= 3, "Invalid tier");
        uint256 oldAmount;
        
        if (tierId == 1) {
            oldAmount = TIER1_THRESHOLD;
            require(newAmount < TIER2_THRESHOLD, "Must be less than TIER2");
            TIER1_THRESHOLD = newAmount;
        } else if (tierId == 2) {
            oldAmount = TIER2_THRESHOLD;
            require(newAmount > TIER1_THRESHOLD && newAmount < TIER3_THRESHOLD, "Must be between TIER1 and TIER3");
            TIER2_THRESHOLD = newAmount;
        } else {
            oldAmount = TIER3_THRESHOLD;
            require(newAmount > TIER2_THRESHOLD, "Must be greater than TIER2");
            TIER3_THRESHOLD = newAmount;
        }
        
        emit TierThresholdUpdated(tierId, oldAmount, newAmount);
    }

    struct RewardBreakdown {
        uint256 accumulatedRewards;      // Total pending rewards
        uint256 rewardsPerDay;           // Current rate of HACCoin rewards per day
        uint256 secondaryRewards;        // Accumulated TOPIA rewards
        uint256 timeMultiplier;          // Current time multiplier (e.g., 110 = 1.1x)
        uint256 amountMultiplier;        // Current amount multiplier
        uint256 effectiveAPR;            // Effective APR based on current multipliers
        uint256 stakeDuration;           // How long they've been staking
        uint256 nextMultiplierThreshold; // Time until next multiplier tier
    }

    function getDetailedRewards(address user) external view returns (RewardBreakdown memory) {
        RewardBreakdown memory breakdown;
        
        if (stakedAmount[user] == 0) return breakdown;

        // Calculate base numbers
        uint256 totalStakingDuration = block.timestamp - stakingStartTime[user];
        uint256 dynamicRate = baseRewardRate + dynamicRewardRate;
        
        // Get multipliers
        breakdown.timeMultiplier = getTimeMultiplier(totalStakingDuration);
        breakdown.amountMultiplier = getAmountMultiplier(stakedAmount[user]);
        
        // Calculate accumulated rewards
        breakdown.accumulatedRewards = calculateReward(user);
        breakdown.secondaryRewards = breakdown.accumulatedRewards / 10; // TOPIA rewards
        
        // Calculate daily rate
        uint256 dailyBase = (stakedAmount[user] * dynamicRate * 86400) / 1e18; // 86400 seconds in a day
        breakdown.rewardsPerDay = dailyBase * breakdown.timeMultiplier * breakdown.amountMultiplier * bonusMultiplier / 1000000;
        
        // Calculate effective APR
        breakdown.effectiveAPR = (breakdown.rewardsPerDay * 365 * 100) / stakedAmount[user];
        
        // Staking duration info
        breakdown.stakeDuration = totalStakingDuration;
        
        // Calculate time until next multiplier
        if (totalStakingDuration < TIME_TIER1) {
            breakdown.nextMultiplierThreshold = TIME_TIER1 - totalStakingDuration;
        } else if (totalStakingDuration < TIME_TIER2) {
            breakdown.nextMultiplierThreshold = TIME_TIER2 - totalStakingDuration;
        } else if (totalStakingDuration < TIME_TIER3) {
            breakdown.nextMultiplierThreshold = TIME_TIER3 - totalStakingDuration;
        } else {
            breakdown.nextMultiplierThreshold = 0; // Max tier reached
        }
        return breakdown;
    }

    // Add receive() function to accept native tokens
    receive() external payable {}
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):