BuxJuicyCarrotStaking

Description:

Decentralized Finance (DeFi) protocol contract providing Liquidity, Staking, Factory functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/BuxJuicyCarrotStaking.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.24;\r
\r
/**\r
 * @title QuarterLockStaking\r
 * @notice Fixed 3-month staking with a default 20% APY (i.e., 5% per 3 months).\r
 *         - Emergency withdraw anytime with a 10% penalty (no rewards). Penalty stays in the pool.\r
 *         - Claim principal + rewards only after 3 months.\r
 *         - Adding more resets the 3-month timer and snapshots the then-current rate. Any partial,\r
 *           not-yet-matured reward progress is forfeited by design (explicit).\r
 *         - Owner can change APY (in basis points). Existing stakes keep their snapshot rate.\r
 *\r
 * SECURITY NOTES:\r
 *  - Uses ReentrancyGuard and SafeERC20.\r
 *  - One active stake per user to keep logic simple and predictable.\r
 *  - Rewards are paid from the same ERC20 token, funded into this contract by the owner.\r
 *  - Contract tracks liabilities (principal + snapshotted rewards) and only allows owner to\r
 *    recover surplus above these liabilities, preventing accidental underfunding.\r
 */\r
\r
interface IERC20 {\r
    function totalSupply() external view returns (uint256);\r
    function balanceOf(address) external view returns (uint256);\r
    function allowance(address owner, address spender) external view returns (uint256);\r
    function approve(address spender, uint256 value) external returns (bool);\r
    function transfer(address to, uint256 value) external returns (bool);\r
    function transferFrom(address from,address to,uint256 value) external returns (bool);\r
    event Transfer(address indexed from, address indexed to, uint256 value);\r
    event Approval(address indexed owner, address indexed spender, uint256 value);\r
}\r
\r
library SafeERC20 {\r
    function safeTransfer(IERC20 token, address to, uint256 value) internal {\r
        bool ok = token.transfer(to, value);\r
        require(ok, "SafeERC20: transfer failed");\r
    }\r
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\r
        bool ok = token.transferFrom(from, to, value);\r
        require(ok, "SafeERC20: transferFrom failed");\r
    }\r
    function safeApprove(IERC20 token, address spender, uint256 value) internal {\r
        bool ok = token.approve(spender, value);\r
        require(ok, "SafeERC20: approve failed");\r
    }\r
}\r
\r
abstract contract ReentrancyGuard {\r
    uint256 private _status;\r
    constructor() { _status = 1; }\r
    modifier nonReentrant() {\r
        require(_status == 1, "ReentrancyGuard: reentrant");\r
        _status = 2;\r
        _;\r
        _status = 1;\r
    }\r
}\r
\r
abstract contract Ownable {\r
    address public owner;\r
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\r
    constructor() { owner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); }\r
    modifier onlyOwner() { require(msg.sender == owner, "Ownable: not owner"); _; }\r
    function transferOwnership(address newOwner) external onlyOwner {\r
        require(newOwner != address(0), "Ownable: zero");\r
        emit OwnershipTransferred(owner, newOwner);\r
        owner = newOwner;\r
    }\r
}\r
\r
contract BuxJuicyCarrotStaking is Ownable, ReentrancyGuard {\r
    using SafeERC20 for IERC20;\r
\r
    // --- Config ---\r
    uint256 public constant LOCK_DURATION = 90 days;          // ~3 months\r
    uint16  public constant PENALTY_BPS   = 1000;             // 10% penalty on emergency withdraw\r
    uint16  public constant MAX_APY_BPS   = 10_000;           // 100% APY hard cap for safety\r
\r
    IERC20 public immutable stakingToken;\r
\r
    // APY in basis points (e.g., 2000 = 20% APY). 3-month period rate is APY/4 (integer division).\r
    uint16 public apyBps = 2000; // default 20% APY - 5% per 3 months\r
\r
    struct Stake {\r
        uint256 amount;          // principal\r
        uint64  startTime;       // stake start time\r
        uint16  quarterRateBps;  // snapshot of 3-month rate at (re)start time: apyBps/4\r
    }\r
\r
    mapping(address => Stake) public stakes;\r
\r
    // --- Accounting (surplus protection) ---\r
    // Tracks the sum of all user principals and the sum of all snapshotted full-cycle rewards.\r
    uint256 public totalPrincipal;\r
    uint256 public totalRewardsLiability;\r
\r
    // --- Events ---\r
    event Deposited(address indexed user, uint256 amount, uint256 newTotal, uint16 quarterRateBps, uint256 newMaturity);\r
    event Claimed(address indexed user, uint256 principal, uint256 reward);\r
    event EmergencyWithdraw(address indexed user, uint256 withdrawn, uint256 penaltyKept);\r
    event ApyChanged(uint16 oldApyBps, uint16 newApyBps);\r
    event Funded(address indexed from, uint256 amount);\r
    event Recovered(address indexed to, uint256 amount);\r
\r
    // --- Errors ---\r
    error ZeroAmount();\r
    error NotMature();\r
    error NothingStaked();\r
    error InsufficientPool();\r
    error NoSurplus();\r
\r
    constructor(IERC20 token_) {\r
        require(address(token_) != address(0), "token=0");\r
        stakingToken = token_;\r
    }\r
\r
    // --- Owner Functions ---\r
\r
    /// @notice Set APY in basis points (e.g., 2000 = 20%). Existing stakes keep their snapshot rate.\r
    function setApyBps(uint16 newApyBps) external onlyOwner {\r
        require(newApyBps <= MAX_APY_BPS, "APY too high");\r
        uint16 old = apyBps;\r
        apyBps = newApyBps;\r
        emit ApyChanged(old, newApyBps);\r
    }\r
\r
    /// @notice Fund the contract with reward/principal liquidity.\r
    function fund(uint256 amount) external onlyOwner {\r
        if (amount == 0) revert ZeroAmount();\r
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);\r
        emit Funded(msg.sender, amount);\r
    }\r
\r
    /// @notice Recover *only* the surplus (balance above total liabilities).\r
    function recover(uint256 amount, address to) external onlyOwner {\r
        require(to != address(0), "to=0");\r
        uint256 minRequired = totalPrincipal + totalRewardsLiability;\r
        uint256 bal = stakingToken.balanceOf(address(this));\r
        uint256 available = (bal > minRequired) ? (bal - minRequired) : 0;\r
        if (available == 0 || amount > available) revert NoSurplus();\r
        stakingToken.safeTransfer(to, amount);\r
        emit Recovered(to, amount);\r
    }\r
\r
    // --- User Actions ---\r
\r
    /**\r
     * @notice Deposit tokens to start/reset a 3-month stake.\r
     * @dev Adding more resets the timer and snapshots the *current* 3-month rate (apyBps/4).\r
     *      Any partial, not-yet-matured reward from the previous period is forfeited by design.\r
     */\r
    function deposit(uint256 amount) external nonReentrant {\r
        if (amount == 0) revert ZeroAmount();\r
\r
        Stake storage s = stakes[msg.sender];\r
\r
        // Remove old reward liability if there was an active stake\r
        if (s.amount != 0) {\r
            totalRewardsLiability -= _userLiability(s);\r
        }\r
\r
        // Pull tokens in\r
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);\r
\r
        // Track principal delta\r
        totalPrincipal += amount;\r
\r
        // Increase principal and reset timer/rate snapshot\r
        s.amount += amount;\r
        s.startTime = uint64(block.timestamp);\r
        s.quarterRateBps = _currentQuarterRateBps();\r
\r
        // Add new reward liability for full cycle at snapshotted rate\r
        totalRewardsLiability += _userLiability(s);\r
\r
        emit Deposited(\r
            msg.sender,\r
            amount,\r
            s.amount,\r
            s.quarterRateBps,\r
            uint256(s.startTime) + LOCK_DURATION\r
        );\r
    }\r
\r
    /// @notice Claim principal + fixed 3-month reward after maturity.\r
    function claim() external nonReentrant {\r
        Stake storage s = stakes[msg.sender];\r
        if (s.amount == 0) revert NothingStaked();\r
        if (!_isMature(s.startTime)) revert NotMature();\r
\r
        uint256 principal = s.amount;\r
        uint256 reward = _userLiability(s);\r
        uint256 totalOut = principal + reward;\r
\r
        // Ensure pool has enough\r
        if (stakingToken.balanceOf(address(this)) < totalOut) revert InsufficientPool();\r
\r
        // Adjust totals before zeroing\r
        totalPrincipal -= principal;\r
        totalRewardsLiability -= reward;\r
\r
        // Reset stake before external call\r
        s.amount = 0;\r
        s.startTime = 0;\r
        s.quarterRateBps = 0;\r
\r
        stakingToken.safeTransfer(msg.sender, totalOut);\r
        emit Claimed(msg.sender, principal, reward);\r
    }\r
\r
    /**\r
     * @notice Emergency withdraw principal at any time with a 10% penalty. No rewards.\r
     * @dev Penalty stays in the pool (contract balance) to benefit remaining stakers.\r
     */\r
    function emergencyWithdraw() external nonReentrant {\r
        Stake storage s = stakes[msg.sender];\r
        if (s.amount == 0) revert NothingStaked();\r
\r
        uint256 principal = s.amount;\r
        uint256 penalty = (principal * PENALTY_BPS) / 10_000;\r
        uint256 payout = principal - penalty;\r
\r
        // Adjust totals (remove principal and the snapshotted reward liability)\r
        totalPrincipal -= principal;\r
        totalRewardsLiability -= _userLiability(s);\r
\r
        // Reset before transfer\r
        s.amount = 0;\r
        s.startTime = 0;\r
        s.quarterRateBps = 0;\r
\r
        stakingToken.safeTransfer(msg.sender, payout);\r
        // penalty remains in contract balance\r
        emit EmergencyWithdraw(msg.sender, payout, penalty);\r
    }\r
\r
    // --- Views ---\r
\r
    /// @notice Returns current 3-month rate in bps based on APY (APY/4).\r
    function currentQuarterRateBps() external view returns (uint16) {\r
        return _currentQuarterRateBps();\r
    }\r
\r
    /// @notice Returns the maturity timestamp for the caller's current stake (0 if none).\r
    function maturityOf(address user) external view returns (uint256) {\r
        Stake memory s = stakes[user];\r
        if (s.amount == 0) return 0;\r
        return uint256(s.startTime) + LOCK_DURATION;\r
    }\r
\r
    /// @notice Returns pending reward if already mature; otherwise 0.\r
    function pendingReward(address user) external view returns (uint256) {\r
        Stake memory s = stakes[user];\r
        if (s.amount == 0) return 0;\r
        if (!_isMature(s.startTime)) return 0;\r
        return _userLiability(s);\r
    }\r
\r
    /// @notice Helper to see if a stake is mature.\r
    function isMature(address user) external view returns (bool) {\r
        Stake memory s = stakes[user];\r
        if (s.amount == 0) return false;\r
        return _isMature(s.startTime);\r
    }\r
\r
    /// @notice Total liabilities = total principals + total snapshotted rewards for active stakes.\r
    function totalLiabilities() public view returns (uint256) {\r
        return totalPrincipal + totalRewardsLiability;\r
    }\r
\r
    /// @notice Current token balance held by the contract.\r
    function poolBalance() public view returns (uint256) {\r
        return stakingToken.balanceOf(address(this));\r
    }\r
\r
    /// @notice Surplus tokens available for safe recovery.\r
    function surplus() external view returns (uint256) {\r
        uint256 bal = stakingToken.balanceOf(address(this));\r
        uint256 liab = totalLiabilities();\r
        return bal > liab ? bal - liab : 0;\r
    }\r
\r
    // --- Internals ---\r
\r
    function _isMature(uint64 start) internal view returns (bool) {\r
        if (start == 0) return false;\r
        return block.timestamp >= uint256(start) + LOCK_DURATION;\r
    }\r
\r
    function _currentQuarterRateBps() internal view returns (uint16) {\r
        // Integer division by 4; e.g. 2000 APY -> 500 bps per quarter (5%)\r
        return uint16(apyBps / 4);\r
    }\r
\r
    function _userLiability(Stake memory s) internal pure returns (uint256) {\r
        // Full-cycle reward at the user's snapshotted quarter rate.\r
        return (s.amount * s.quarterRateBps) / 10_000;\r
    }\r
}\r
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 500
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC20, DeFi, Liquidity, Staking, Factory|addr:0x0a95103b38848483d24c776819ee81906dde76a1|verified:true|block:23483278|tx:0xa4701464d27b9307c3cb2c2cd0a56cb73b80a2fe974ca57def8ab145e81f5973|first_check:1759332380

Submitted on: 2025-10-01 17:26:21

Comments

Log in to comment.

No comments yet.