creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "contracts/SbeeStaking.sol": {
        "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.0;\n\nimport {ERC20} from \"solmate/src/tokens/ERC20.sol\";\nimport {SafeTransferLib} from \"solmate/src/utils/SafeTransferLib.sol\";\n\ncontract SbeeStaking {\n    using SafeTransferLib for ERC20;\n    using Cast for uint256;\n\n    event Staked(address user, uint256 amount);\n    event Unstaked(address user, uint256 amount);\n    event Claimed(address user, uint256 amount);\n    event RewardsPerTokenUpdated(uint256 accumulated);\n    event UserRewardsUpdated(address user, uint256 rewards, uint256 checkpoint);\n\n    struct RewardsPerToken {\n        uint128 accumulated;\n        uint128 lastUpdated;\n    }\n\n    struct UserRewards {\n        uint128 accumulated;\n        uint128 checkpoint;\n        uint256 totalEarned;\n    }\n\n    ERC20 public immutable stakingToken;\n    uint256 public totalStaked;\n    uint256 public totalStakers;\n    mapping(address => uint256) public userStake;\n\n    uint256 public immutable rewardsRate;\n    uint256 public immutable stakingStart;\n    uint256 public immutable stakingEnd;\n\n    RewardsPerToken public rewardsPerToken;\n    mapping(address => UserRewards) public accumulatedRewards;\n\n    constructor(\n        ERC20 _stakingToken,\n        uint256 _stakingStart,\n        uint256 _stakingEnd,\n        uint256 totalRewards\n    ) {\n        stakingToken = _stakingToken;\n        stakingStart = _stakingStart;\n        stakingEnd = _stakingEnd;\n        rewardsRate = totalRewards / (_stakingEnd - _stakingStart);\n        rewardsPerToken.lastUpdated = _stakingStart.u128();\n    }\n\n    function _calculateRewardsPerToken(\n        RewardsPerToken memory rewardsPerTokenIn\n    ) internal view returns (RewardsPerToken memory) {\n        RewardsPerToken memory rewardsPerTokenOut = RewardsPerToken(\n            rewardsPerTokenIn.accumulated,\n            rewardsPerTokenIn.lastUpdated\n        );\n        uint256 _totalStaked = totalStaked;\n\n        if (block.timestamp < stakingStart) return rewardsPerTokenOut;\n\n        uint256 updateTime = block.timestamp < stakingEnd\n            ? block.timestamp\n            : stakingEnd;\n        uint256 elapsed = updateTime - rewardsPerTokenIn.lastUpdated;\n\n        if (elapsed == 0) return rewardsPerTokenOut;\n        rewardsPerTokenOut.lastUpdated = updateTime.u128();\n\n        if (totalStaked == 0) return rewardsPerTokenOut;\n\n        rewardsPerTokenOut.accumulated = (rewardsPerTokenIn.accumulated +\n            (1e18 * elapsed * rewardsRate) /\n            _totalStaked).u128();\n        return rewardsPerTokenOut;\n    }\n\n    function _calculateUserRewards(\n        uint256 _staked,\n        uint256 earlierCheckpoint,\n        uint256 latterCheckpoint\n    ) internal pure returns (uint256) {\n        return (_staked * (latterCheckpoint - earlierCheckpoint)) / 1e18;\n    }\n\n    function _updateRewardsPerToken()\n        internal\n        returns (RewardsPerToken memory)\n    {\n        RewardsPerToken memory rewardsPerTokenIn = rewardsPerToken;\n        RewardsPerToken memory rewardsPerTokenOut = _calculateRewardsPerToken(\n            rewardsPerTokenIn\n        );\n\n        if (rewardsPerTokenIn.lastUpdated == rewardsPerTokenOut.lastUpdated)\n            return rewardsPerTokenOut;\n\n        rewardsPerToken = rewardsPerTokenOut;\n        emit RewardsPerTokenUpdated(rewardsPerTokenOut.accumulated);\n\n        return rewardsPerTokenOut;\n    }\n\n    function _updateUserRewards(\n        address user\n    ) internal returns (UserRewards memory) {\n        RewardsPerToken memory _rewardsPerToken = _updateRewardsPerToken();\n        UserRewards memory _userRewards = accumulatedRewards[user];\n\n        if (_userRewards.checkpoint == _rewardsPerToken.lastUpdated)\n            return _userRewards;\n\n        uint256 earnedSinceLastUpdate = _calculateUserRewards(\n            userStake[user],\n            _userRewards.checkpoint,\n            _rewardsPerToken.accumulated\n        );\n        _userRewards.accumulated += earnedSinceLastUpdate.u128();\n        _userRewards.totalEarned += earnedSinceLastUpdate.u128();\n        _userRewards.checkpoint = _rewardsPerToken.accumulated;\n\n        accumulatedRewards[user] = _userRewards;\n        emit UserRewardsUpdated(\n            user,\n            _userRewards.accumulated,\n            _userRewards.checkpoint\n        );\n\n        return _userRewards;\n    }\n\n    function _stake(address user, uint256 amount) internal {\n        _updateUserRewards(user);\n        if (userStake[user] == 0) {\n            totalStakers++;\n        }\n        totalStaked += amount;\n        userStake[user] += amount;\n        stakingToken.safeTransferFrom(user, address(this), amount);\n        emit Staked(user, amount);\n    }\n\n    function _unstake(address user, uint256 amount) internal {\n        require(userStake[user] >= amount, \"Insufficient staked amount\");\n        _updateUserRewards(user);\n        if (userStake[user] == amount) {\n            totalStakers--;\n        }\n        totalStaked -= amount;\n        userStake[user] -= amount;\n        stakingToken.safeTransfer(user, amount);\n        emit Unstaked(user, amount);\n    }\n\n    function _claim(address user, uint256 amount) internal {\n        uint256 rewardsAvailable = _updateUserRewards(msg.sender).accumulated;\n\n        accumulatedRewards[user].accumulated = (rewardsAvailable - amount)\n            .u128();\n\n        stakingToken.safeTransfer(user, amount);\n        emit Claimed(user, amount);\n    }\n\n    function stake(uint256 amount) public virtual {\n        _stake(msg.sender, amount);\n    }\n\n    function unstake(uint256 amount) public virtual {\n        _unstake(msg.sender, amount);\n    }\n\n    function claim() public virtual returns (uint256) {\n        uint256 claimed = _updateUserRewards(msg.sender).accumulated;\n        _claim(msg.sender, claimed);\n        return claimed;\n    }\n\n    function currentRewardsPerToken() public view returns (uint256) {\n        return _calculateRewardsPerToken(rewardsPerToken).accumulated;\n    }\n\n    function currentUserRewards(address user) public view returns (uint256) {\n        UserRewards memory accumulatedRewards_ = accumulatedRewards[user];\n        RewardsPerToken memory rewardsPerToken_ = _calculateRewardsPerToken(\n            rewardsPerToken\n        );\n        return\n            accumulatedRewards_.accumulated +\n            _calculateUserRewards(\n                userStake[user],\n                accumulatedRewards_.checkpoint,\n                rewardsPerToken_.accumulated\n            );\n    }\n}\n\nlibrary Cast {\n    function u128(uint256 x) internal pure returns (uint128 y) {\n        require(x <= type(uint128).max, \"Cast overflow\");\n        y = uint128(x);\n    }\n}\n"
      },
      "solmate/src/tokens/ERC20.sol": {
        "content": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\n/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)\n/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)\n/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.\nabstract contract ERC20 {\n    /*//////////////////////////////////////////////////////////////\n                                 EVENTS\n    //////////////////////////////////////////////////////////////*/\n\n    event Transfer(address indexed from, address indexed to, uint256 amount);\n\n    event Approval(address indexed owner, address indexed spender, uint256 amount);\n\n    /*//////////////////////////////////////////////////////////////\n                            METADATA STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n    string public name;\n\n    string public symbol;\n\n    uint8 public immutable decimals;\n\n    /*//////////////////////////////////////////////////////////////\n                              ERC20 STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n    uint256 public totalSupply;\n\n    mapping(address => uint256) public balanceOf;\n\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    /*//////////////////////////////////////////////////////////////\n                            EIP-2612 STORAGE\n    //////////////////////////////////////////////////////////////*/\n\n    uint256 internal immutable INITIAL_CHAIN_ID;\n\n    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;\n\n    mapping(address => uint256) public nonces;\n\n    /*//////////////////////////////////////////////////////////////\n                               CONSTRUCTOR\n    //////////////////////////////////////////////////////////////*/\n\n    constructor(\n        string memory _name,\n        string memory _symbol,\n        uint8 _decimals\n    ) {\n        name = _name;\n        symbol = _symbol;\n        decimals = _decimals;\n\n        INITIAL_CHAIN_ID = block.chainid;\n        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();\n    }\n\n    /*//////////////////////////////////////////////////////////////\n                               ERC20 LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n    function approve(address spender, uint256 amount) public virtual returns (bool) {\n        allowance[msg.sender][spender] = amount;\n\n        emit Approval(msg.sender, spender, amount);\n\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) public virtual returns (bool) {\n        balanceOf[msg.sender] -= amount;\n\n        // Cannot overflow because the sum of all user\n        // balances can't exceed the max uint256 value.\n        unchecked {\n            balanceOf[to] += amount;\n        }\n\n        emit Transfer(msg.sender, to, amount);\n\n        return true;\n    }\n\n    function transferFrom(\n        address from,\n        address to,\n        uint256 amount\n    ) public virtual returns (bool) {\n        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n        balanceOf[from] -= amount;\n\n        // Cannot overflow because the sum of all user\n        // balances can't exceed the max uint256 value.\n        unchecked {\n            balanceOf[to] += amount;\n        }\n\n        emit Transfer(from, to, amount);\n\n        return true;\n    }\n\n    /*//////////////////////////////////////////////////////////////\n                             EIP-2612 LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n    function permit(\n        address owner,\n        address spender,\n        uint256 value,\n        uint256 deadline,\n        uint8 v,\n        bytes32 r,\n        bytes32 s\n    ) public virtual {\n        require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n        // Unchecked because the only math done is incrementing\n        // the owner's nonce which cannot realistically overflow.\n        unchecked {\n            address recoveredAddress = ecrecover(\n                keccak256(\n                    abi.encodePacked(\n                        \"\\x19\\x01\",\n                        DOMAIN_SEPARATOR(),\n                        keccak256(\n                            abi.encode(\n                                keccak256(\n                                    \"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\"\n                                ),\n                                owner,\n                                spender,\n                                value,\n                                nonces[owner]++,\n                                deadline\n                            )\n                        )\n                    )\n                ),\n                v,\n                r,\n                s\n            );\n\n            require(recoveredAddress != address(0) && recoveredAddress == owner, \"INVALID_SIGNER\");\n\n            allowance[recoveredAddress][spender] = value;\n        }\n\n        emit Approval(owner, spender, value);\n    }\n\n    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();\n    }\n\n    function computeDomainSeparator() internal view virtual returns (bytes32) {\n        return\n            keccak256(\n                abi.encode(\n                    keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n                    keccak256(bytes(name)),\n                    keccak256(\"1\"),\n                    block.chainid,\n                    address(this)\n                )\n            );\n    }\n\n    /*//////////////////////////////////////////////////////////////\n                        INTERNAL MINT/BURN LOGIC\n    //////////////////////////////////////////////////////////////*/\n\n    function _mint(address to, uint256 amount) internal virtual {\n        totalSupply += amount;\n\n        // Cannot overflow because the sum of all user\n        // balances can't exceed the max uint256 value.\n        unchecked {\n            balanceOf[to] += amount;\n        }\n\n        emit Transfer(address(0), to, amount);\n    }\n\n    function _burn(address from, uint256 amount) internal virtual {\n        balanceOf[from] -= amount;\n\n        // Cannot underflow because a user's balance\n        // will never be larger than the total supply.\n        unchecked {\n            totalSupply -= amount;\n        }\n\n        emit Transfer(from, address(0), amount);\n    }\n}\n"
      },
      "solmate/src/utils/SafeTransferLib.sol": {
        "content": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\nimport {ERC20} from \"../tokens/ERC20.sol\";\n\n/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)\n/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.\n/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.\nlibrary SafeTransferLib {\n    /*//////////////////////////////////////////////////////////////\n                             ETH OPERATIONS\n    //////////////////////////////////////////////////////////////*/\n\n    function safeTransferETH(address to, uint256 amount) internal {\n        bool success;\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            // Transfer the ETH and store if it succeeded or not.\n            success := call(gas(), to, amount, 0, 0, 0, 0)\n        }\n\n        require(success, \"ETH_TRANSFER_FAILED\");\n    }\n\n    /*//////////////////////////////////////////////////////////////\n                            ERC20 OPERATIONS\n    //////////////////////////////////////////////////////////////*/\n\n    function safeTransferFrom(\n        ERC20 token,\n        address from,\n        address to,\n        uint256 amount\n    ) internal {\n        bool success;\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            // Get a pointer to some free memory.\n            let freeMemoryPointer := mload(0x40)\n\n            // Write the abi-encoded calldata into memory, beginning with the function selector.\n            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\n            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the \"from\" argument.\n            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the \"to\" argument.\n            mstore(add(freeMemoryPointer, 68), amount) // Append the \"amount\" argument. Masking not required as it's a full 32 byte type.\n\n            success := and(\n                // Set success to whether the call reverted, if not we check it either\n                // returned exactly 1 (can't just be non-zero data), or had no return data.\n                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\n                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n                // Counterintuitively, this call must be positioned second to the or() call in the\n                // surrounding and() call or else returndatasize() will be zero during the computation.\n                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)\n            )\n        }\n\n        require(success, \"TRANSFER_FROM_FAILED\");\n    }\n\n    function safeTransfer(\n        ERC20 token,\n        address to,\n        uint256 amount\n    ) internal {\n        bool success;\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            // Get a pointer to some free memory.\n            let freeMemoryPointer := mload(0x40)\n\n            // Write the abi-encoded calldata into memory, beginning with the function selector.\n            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the \"to\" argument.\n            mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument. Masking not required as it's a full 32 byte type.\n\n            success := and(\n                // Set success to whether the call reverted, if not we check it either\n                // returned exactly 1 (can't just be non-zero data), or had no return data.\n                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n                // Counterintuitively, this call must be positioned second to the or() call in the\n                // surrounding and() call or else returndatasize() will be zero during the computation.\n                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n            )\n        }\n\n        require(success, \"TRANSFER_FAILED\");\n    }\n\n    function safeApprove(\n        ERC20 token,\n        address to,\n        uint256 amount\n    ) internal {\n        bool success;\n\n        /// @solidity memory-safe-assembly\n        assembly {\n            // Get a pointer to some free memory.\n            let freeMemoryPointer := mload(0x40)\n\n            // Write the abi-encoded calldata into memory, beginning with the function selector.\n            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)\n            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the \"to\" argument.\n            mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument. Masking not required as it's a full 32 byte type.\n\n            success := and(\n                // Set success to whether the call reverted, if not we check it either\n                // returned exactly 1 (can't just be non-zero data), or had no return data.\n                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n                // Counterintuitively, this call must be positioned second to the or() call in the\n                // surrounding and() call or else returndatasize() will be zero during the computation.\n                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n            )\n        }\n\n        require(success, \"APPROVE_FAILED\");\n    }\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": false,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "libraries": {}
    }
  }}