DogenariiStaking

Description:

Decentralized Finance (DeFi) protocol contract providing Staking functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

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);
}

abstract contract Ownable {
    address private _owner;

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

    constructor() {
        _owner = msg.sender;
        emit OwnershipTransferred(address(0), 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 {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

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

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;
    }
}

contract DogenariiStaking is Ownable, ReentrancyGuard {
    IERC20 public immutable stakingToken;
    bool public emergencyWithdrawEnabled;
    uint256 public totalPrincipalStaked;
    bool public paused;

    struct PoolInfo {
        uint256 apy;
        uint256 unlockTime; 
        uint256 totalStaked;
        bool isActive;
    }

    struct StakeInfo {
        uint256 amount;
        uint256 startTime;
        uint256 apy;
        uint256 rewardDebt; 
        uint256 stakerIndex;
    }
    
    PoolInfo[] public poolInfo;
    mapping(uint256 => mapping(address => StakeInfo)) public userStakes;
    mapping(uint256 => address[]) public stakersInPool;

    uint256 public constant BASIS_POINTS = 100;
    uint256 public constant DAYS_IN_YEAR = 365;
    uint256 public constant PRECISION_FACTOR = 1e18;

    event PoolAdded(uint256 indexed pid, uint256 apy, uint256 unlockTime);
    event PoolUpdated(uint256 indexed pid, uint256 apy, uint256 unlockTime, bool isActive);
    event Staked(address indexed user, uint256 indexed pid, uint256 amount);
    event Unstaked(address indexed user, uint256 indexed pid, uint256 amount, uint256 reward);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event RewardsDeposited(address indexed funder, uint256 amount);
    event EmergencyTokenRecovery(address indexed token, address indexed to, uint256 amount);
    event EmergencyNativeRecovery(address indexed to, uint256 amount);
    
    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }

    constructor(address _stakingTokenAddress) {
        stakingToken = IERC20(_stakingTokenAddress);
        emergencyWithdrawEnabled = false;
    }
    
    function setPaused(bool _paused) external onlyOwner {
        paused = _paused;
    }

    function addPool(uint256 _apy, uint256 _unlockTime) external onlyOwner {
        require(_apy > 0 && _apy <= 50, "APY must be 1-50%");
        require(_unlockTime > block.timestamp, "Unlock time must be in the future");
        require(_unlockTime <= block.timestamp + 730 days, "Unlock time max 2 years");

        poolInfo.push(PoolInfo({
            apy: _apy,
            unlockTime: _unlockTime,
            totalStaked: 0,
            isActive: true
        }));
        emit PoolAdded(poolInfo.length - 1, _apy, _unlockTime);
    }

    function updatePool(uint256 _pid, uint256 _apy, uint256 _unlockTime, bool _isActive) external onlyOwner {
        require(_pid < poolInfo.length, "Pool does not exist");
        require(_apy > 0 && _apy <= 50, "APY must be 1-50%");
        require(_unlockTime > block.timestamp, "Unlock time must be in the future");
        require(_unlockTime <= block.timestamp + 730 days, "Unlock time max 2 years");
        
        poolInfo[_pid].apy = _apy;
        poolInfo[_pid].unlockTime = _unlockTime;
        poolInfo[_pid].isActive = _isActive;
        emit PoolUpdated(_pid, _apy, _unlockTime, _isActive);
    }
    
    function setEmergencyWithdraw(bool _enabled) external onlyOwner {
        emergencyWithdrawEnabled = _enabled;
    }

    function depositRewards(uint256 _amount) external onlyOwner {
        require(_amount > 0, "Amount must be greater than zero");
        stakingToken.transferFrom(msg.sender, address(this), _amount);
        emit RewardsDeposited(msg.sender, _amount);
    }

    function recoverTokens(address _tokenAddress, uint256 _amount) external onlyOwner {
        require(_tokenAddress != address(stakingToken), "Cannot recover the staking token");
        IERC20(_tokenAddress).transfer(owner(), _amount);
        emit EmergencyTokenRecovery(_tokenAddress, owner(), _amount);
    }

    function recoverNativeETH() external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "No native balance to recover");
        (bool success, ) = payable(owner()).call{value: balance}("");
        require(success, "Native transfer failed");
        emit EmergencyNativeRecovery(owner(), balance);
    }
    
    // Public User Functions

    function stake(uint256 _pid, uint256 _amount) external nonReentrant whenNotPaused {
        require(_pid < poolInfo.length, "Pool does not exist");

        PoolInfo storage pool = poolInfo[_pid];
        require(block.timestamp < pool.unlockTime, "Pool has already unlocked");

        require(pool.isActive, "Pool is not active");
        require(_amount > 0, "Cannot stake zero tokens");

        StakeInfo storage userStake = userStakes[_pid][msg.sender];

        if (userStake.amount > 0) {
            uint256 pendingRewards = calculateRewards(_pid, msg.sender);
            userStake.rewardDebt += pendingRewards;
        } else {
            userStake.stakerIndex = stakersInPool[_pid].length;
            stakersInPool[_pid].push(msg.sender);
        }

        pool.totalStaked += _amount;
        totalPrincipalStaked += _amount;

        userStake.amount += _amount;
        userStake.startTime = block.timestamp;
        userStake.apy = pool.apy;
        
        stakingToken.transferFrom(msg.sender, address(this), _amount);
        emit Staked(msg.sender, _pid, _amount);
    }

    function unstake(uint256 _pid) external nonReentrant whenNotPaused {
        _unstake(_pid, false);
    }

    function emergencyWithdraw(uint256 _pid) external nonReentrant whenNotPaused {
        require(emergencyWithdrawEnabled, "Emergency withdraw is not enabled");
        _unstake(_pid, true);
    }

    // --- Internal Logic ---

    function _unstake(uint256 _pid, bool _isEmergency) private {
        require(_pid < poolInfo.length, "Pool does not exist");
        PoolInfo storage pool = poolInfo[_pid];
        StakeInfo storage userStake = userStakes[_pid][msg.sender];
        address stakerAddress = msg.sender;

        require(userStake.amount > 0, "You have no active stake in this pool");

        uint256 principal = userStake.amount;
        uint256 reward = 0;

        if (_isEmergency) {
            emit EmergencyWithdraw(stakerAddress, _pid, principal);
        } else {
            require(block.timestamp >= pool.unlockTime, "Pool is still locked");
            reward = userStake.rewardDebt + calculateRewards(_pid, stakerAddress);
            emit Unstaked(stakerAddress, _pid, principal, reward);
        }

        pool.totalStaked -= principal;
        totalPrincipalStaked -= principal;

        uint256 indexToRemove = userStake.stakerIndex;
        address lastStaker = stakersInPool[_pid][stakersInPool[_pid].length - 1];
        if (stakerAddress != lastStaker) {
            stakersInPool[_pid][indexToRemove] = lastStaker;
            userStakes[_pid][lastStaker].stakerIndex = indexToRemove;
        }
        stakersInPool[_pid].pop();

        delete userStakes[_pid][stakerAddress];
        
        uint256 totalWithdrawal = principal + reward;
        require(stakingToken.balanceOf(address(this)) >= totalWithdrawal, "Insufficient funds in contract");
        stakingToken.transfer(stakerAddress, totalWithdrawal);
    }
    
    // --- View Functions ---

    function getAvailableRewards() public view returns (uint256) {
        uint256 contractBalance = stakingToken.balanceOf(address(this));
        return contractBalance <= totalPrincipalStaked ? 0 : contractBalance - totalPrincipalStaked;
    }

    function getTotalRewardLiability() external view returns (uint256[] memory rewardsPerPool, uint256 grandTotalReward) {
        uint256 numPools = poolInfo.length;
        rewardsPerPool = new uint256[](numPools);

        for (uint256 pid = 0; pid < numPools; pid++) {
            uint256 poolTotalReward;
            PoolInfo memory pool = poolInfo[pid];
            address[] memory stakers = stakersInPool[pid];
            
            for (uint256 i = 0; i < stakers.length; i++) {
                StakeInfo memory userStake = userStakes[pid][stakers[i]];
                if (pool.unlockTime > userStake.startTime) {
                    uint256 totalStakingDurationInSeconds = pool.unlockTime - userStake.startTime;
                    uint256 totalStakingDurationInDays = totalStakingDurationInSeconds / 1 days;
                    uint256 fullReward = (userStake.amount * userStake.apy * totalStakingDurationInDays) / (BASIS_POINTS * DAYS_IN_YEAR);
                    poolTotalReward += userStake.rewardDebt + fullReward;
                }
            }
            rewardsPerPool[pid] = poolTotalReward;
            grandTotalReward += poolTotalReward;
        }
        return (rewardsPerPool, grandTotalReward);
    }

    function calculateRewards(uint256 _pid, address _user) public view returns (uint256) {
        StakeInfo memory userStake = userStakes[_pid][_user];
        if (userStake.amount == 0) {
            return 0;
        }
        
        PoolInfo memory pool = poolInfo[_pid];
        uint256 endTime = block.timestamp > pool.unlockTime ? pool.unlockTime : block.timestamp;

        if (userStake.startTime >= endTime) {
            return 0;
        }

        uint256 stakingDurationInSeconds = endTime - userStake.startTime;
        uint256 stakingDurationInDays = stakingDurationInSeconds / 1 days;

        return (userStake.amount * userStake.apy * stakingDurationInDays) / (BASIS_POINTS * DAYS_IN_YEAR);
    }

    function getPoolUnlockTime(uint256 _pid) external view returns (uint256) {
        require(_pid < poolInfo.length, "Pool does not exist");
        return poolInfo[_pid].unlockTime;
    }
    
    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }
}

Tags:
ERC20, DeFi, Staking|addr:0x0f729235eb5ec117068dd533e4258b9cb5f86a0f|verified:true|block:23382802|tx:0x7055f0c4c38fdf955b5173676303661c46ca798be21fb92e4a6b31bbc7ea77ee|first_check:1758115718

Submitted on: 2025-09-17 15:28:40

Comments

Log in to comment.

No comments yet.