Staking

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.0;

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 transferFrom(address sender, 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);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }
    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

abstract contract Ownable is Context {
    address private _owner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    constructor() {
        _owner = _msgSender();
        emit OwnershipTransferred(address(0), _owner);
    }
    function owner() public view virtual returns (address) {
        return _owner;
    }
    modifier onlyOwner() {
        require(owner() == _msgSender(), "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 zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        return a - b;
    }
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        return a / b;
    }
}
/**
 * @title Staking
 * @notice Receives tokens from the Presale contract (onlyPresale) and keeps track of stakes & 300% APY rewards.
 *
 * Reward formula (linear accrual):
 *   reward = amount * APY_NUM * duration / (APY_DEN * YEAR)
 * where APY_NUM = 300, APY_DEN = 100, YEAR = 365 days
 */
contract Staking is Ownable {
    using SafeMath for uint256;

    IERC20 public token;
    address public presale; // only this address can call stakeFor

    uint256 public constant APY_NUM = 300; 
    uint256 public constant APY_DEN = 100;
    uint256 public constant YEAR = 365 days;

    struct StakeInfo {
        uint256 amount;        
        uint256 rewards;       
        uint256 lastUpdated;   
    }

    mapping(address => StakeInfo) public stakes;
    uint256 public totalStaked;

    event StakedFor(address indexed user, uint256 amount);
    event RewardsClaimed(address indexed user, uint256 reward);
    event Unstaked(address indexed user, uint256 principal, uint256 reward);
    event PresaleSet(address indexed presale);
    event Funded(uint256 amount);
    event EmergencyWithdrawal(address indexed to, uint256 amount);

    modifier onlyPresale() {
        require(msg.sender == presale, "Staking: caller is not presale");
        _;
    }

    constructor(address _token) {
        require(_token != address(0), "Invalid token");
        token = IERC20(_token);
    }

    function setPresale(address _presale) external onlyOwner {
        require(_presale != address(0), "Invalid presale");
        presale = _presale;
        emit PresaleSet(_presale);
    }

    /// @notice Called by presale AFTER presale has transferred tokens to this contract.
    function stakeFor(address user, uint256 amount) external onlyPresale {
        require(user != address(0), "Invalid user");
        require(amount > 0, "Amount zero");

        _updateRewards(user);

        stakes[user].amount = stakes[user].amount.add(amount);
        // lastUpdated already set in _updateRewards to block timestamp
        totalStaked = totalStaked.add(amount);

        emit StakedFor(user, amount);
    }

    /// @notice Claim only rewards (principal stays staked)
    function claimRewards() external {
        _updateRewards(msg.sender);
        uint256 reward = stakes[msg.sender].rewards;
        require(reward > 0, "No rewards");

        stakes[msg.sender].rewards = 0;

        require(token.transfer(msg.sender, reward), "Token transfer failed");
        emit RewardsClaimed(msg.sender, reward);
    }

    /// @notice Unstake principal (optionally withdraw partial). Rewards are also paid out up to now.
    function unstake(uint256 amount) external {
        require(amount > 0, "Amount zero");
        require(stakes[msg.sender].amount >= amount, "Insufficient staked");

        _updateRewards(msg.sender);

        // collect pending reward
        uint256 reward = stakes[msg.sender].rewards;
        stakes[msg.sender].rewards = 0;

        // reduce principal
        stakes[msg.sender].amount = stakes[msg.sender].amount.sub(amount);
        totalStaked = totalStaked.sub(amount);

        // transfer principal + reward
        uint256 payout = amount.add(reward);
        require(token.transfer(msg.sender, payout), "Token transfer failed");

        emit Unstaked(msg.sender, amount, reward);
    }

    /// @notice View: compute pending rewards since last update (does not modify state)
    function pendingRewards(address user) public view returns (uint256) {
        StakeInfo memory s = stakes[user];
        if (s.amount == 0) return s.rewards;

        uint256 since = block.timestamp.sub(s.lastUpdated);
        uint256 accrued = s.amount
            .mul(APY_NUM)
            .mul(since)
            .div(APY_DEN)
            .div(YEAR);
        return s.rewards.add(accrued);
    }

    /// @dev update per-user rewards to now
    function _updateRewards(address user) internal {
        if (stakes[user].lastUpdated == 0) {
            // first time being touched: set lastUpdated to now
            stakes[user].lastUpdated = block.timestamp;
            return;
        }

        uint256 since = block.timestamp.sub(stakes[user].lastUpdated);
        if (since == 0) return;

        if (stakes[user].amount > 0) {
            uint256 accrued = stakes[user].amount
                .mul(APY_NUM)
                .mul(since)
                .div(APY_DEN)
                .div(YEAR);

            stakes[user].rewards = stakes[user].rewards.add(accrued);
        }

        stakes[user].lastUpdated = block.timestamp;
    }

    /// @notice Owner can fund reward pool by transferring tokens to this contract and calling fund()
    function fund(uint256 amount) external onlyOwner {
        require(amount > 0, "Amount zero");
        require(token.transferFrom(msg.sender, address(this), amount), "TransferFrom failed");
        emit Funded(amount);
    }

    /// @notice Emergency: owner can withdraw any tokens (useful for admins)
    function emergencyWithdraw(address to, uint256 amount) external onlyOwner {
        require(to != address(0), "Zero address");
        require(token.transfer(to, amount), "Transfer failed");
        emit EmergencyWithdrawal(to, amount);
    }
}

Tags:
ERC20, DeFi, Staking|addr:0xa87ce478ca48c96d3f4d0dfd2a40c2e3dea3047b|verified:true|block:23726309|tx:0x8bdb19786452ee714373e676857175ccf345068a984cde25974e704f8c0f1c5c|first_check:1762267180

Submitted on: 2025-11-04 15:39:43

Comments

Log in to comment.

No comments yet.