Contract Diff Checker

Contract Name:
StakingWithOptionalReferral

Contract Source Code:

File 1 of 1 : StakingWithOptionalReferral

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

// Address library from OpenZeppelin
library Address {
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return _functionCallWithValue(target, data, 0, errorMessage);
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        return _functionCallWithValue(target, data, value, errorMessage);
    }

    function _functionCallWithValue(
        address target,
        bytes memory data,
        uint256 weiValue,
        string memory errorMessage
    ) private returns (bytes memory) {
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: weiValue}(data);
        if (success) {
            return returndata;
        } else {
            if (returndata.length > 0) {
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// SafeERC20 from OpenZeppelin
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// Context from OpenZeppelin
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// ReentrancyGuard from OpenZeppelin
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;
    }
}

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

/// @title StakingWithOptionalReferral - A staking contract for SANIC tokens with optional referrals and robust security.
contract StakingWithOptionalReferral is ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public sanicToken; // Token to be staked
    address public feeRecipient; // Address to receive fees
    address public owner; // Contract owner

    uint256 public dailyRewardRate = 8; // 8% daily rewards
    uint256 public depositFee = 5; // 5% deposit fee
    uint256 public referralFee = 12; // 12% referral bonus
    uint256 public claimCooldown = 1 minutes; // Cooldown period for claiming rewards
    uint256 public penaltyCooldown = 24 hours; // Time window for penalty enforcement
    uint256 public penaltyRate = 50; // 50% penalty on rewards for early claims

    struct User {
        uint256 depositedAmount;
        uint256 lastClaimTime;
        uint256 lastWithdrawalTime;
    }

    mapping(address => User) public users;
    mapping(address => address) public referrers;
    mapping(address => bool) public hasBeenReferred;

    uint256 public totalDeposits;

    event Deposit(address indexed user, uint256 amount, uint256 netAmount, address indexed referrer);
    event ClaimRewards(address indexed user, uint256 reward, uint256 penalty);
    event Compound(address indexed user, uint256 compoundedAmount);
    event ReferralBonusPaid(address indexed referrer, uint256 bonus);
    event TokenUpdated(address indexed oldToken, address indexed newToken);
    event FeeRecipientUpdated(address indexed oldRecipient, address indexed newRecipient);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not authorized");
        _;
    }

    constructor(address _sanicToken, address _feeRecipient) {
        require(_sanicToken != address(0), "Invalid token address");
        require(_feeRecipient != address(0), "Invalid fee recipient address");

        sanicToken = IERC20(_sanicToken);
        feeRecipient = _feeRecipient;
        owner = msg.sender;
    }

    function deposit(uint256 _amount, address _referrer) external nonReentrant {
        require(_amount > 0, "Deposit amount must be greater than zero");

        uint256 allowance = sanicToken.allowance(msg.sender, address(this));
        require(allowance >= _amount, "Token allowance too low");

        sanicToken.safeTransferFrom(msg.sender, address(this), _amount);

        uint256 fee = (_amount * depositFee) / 100;
        uint256 netAmount = _amount - fee;

        sanicToken.safeTransfer(feeRecipient, fee);

        if (_referrer != address(0) && _referrer != msg.sender) {
            require(!hasBeenReferred[msg.sender], "User has already been referred");
            uint256 referralBonus = (_amount * referralFee) / 100;
            sanicToken.safeTransfer(_referrer, referralBonus);
            referrers[msg.sender] = _referrer;
            hasBeenReferred[msg.sender] = true;

            emit ReferralBonusPaid(_referrer, referralBonus);
        }

        User storage user = users[msg.sender];
        user.depositedAmount += netAmount;
        user.lastClaimTime = block.timestamp;
        user.lastWithdrawalTime = 0; // Reset withdrawal time on deposit
        totalDeposits += netAmount;

        emit Deposit(msg.sender, _amount, netAmount, _referrer);
    }

    function claimRewards() external nonReentrant {
        User storage user = users[msg.sender];
        require(user.depositedAmount > 0, "No deposits found");
        require(block.timestamp >= user.lastClaimTime + claimCooldown, "Claim cooldown active");

        uint256 rewards = calculateRewards(msg.sender);
        require(rewards > 0, "No rewards available");

        uint256 penalty = 0;
        if (block.timestamp < user.lastWithdrawalTime + penaltyCooldown) {
            penalty = (rewards * penaltyRate) / 100;
        }

        uint256 netRewards = rewards - penalty;
        user.lastClaimTime = block.timestamp;
        user.lastWithdrawalTime = block.timestamp;

        user.depositedAmount -= netRewards; // Deduct claimed rewards from deposit
        sanicToken.safeTransfer(msg.sender, netRewards);

        if (penalty > 0) {
            sanicToken.safeTransfer(feeRecipient, penalty);
        }

        emit ClaimRewards(msg.sender, netRewards, penalty);
    }

    function calculateRewards(address _user) public view returns (uint256) {
        User memory user = users[_user];
        uint256 elapsedTime = block.timestamp - user.lastClaimTime;
        uint256 dailyRewards = (user.depositedAmount * dailyRewardRate) / 100;
        return (dailyRewards * elapsedTime) / 1 days;
    }

    function compound() external nonReentrant {
        User storage user = users[msg.sender];
        require(user.depositedAmount > 0, "No deposits found");
        require(block.timestamp >= user.lastClaimTime + claimCooldown, "Claim cooldown active");

        uint256 rewards = calculateRewards(msg.sender);
        require(rewards > 0, "No rewards available");

        user.depositedAmount += rewards; // Add rewards to deposit
        user.lastClaimTime = block.timestamp;

        emit Compound(msg.sender, rewards);
    }

    function getTotalDeposited() external view returns (uint256) {
        return totalDeposits;
    }

    function updateToken(address _newToken) external onlyOwner {
        require(_newToken != address(0), "Invalid token address");
        address oldToken = address(sanicToken);
        sanicToken = IERC20(_newToken);
        emit TokenUpdated(oldToken, _newToken);
    }

    function updateFeeRecipient(address _newRecipient) external onlyOwner {
        require(_newRecipient != address(0), "Invalid fee recipient address");
        address oldRecipient = feeRecipient;
        feeRecipient = _newRecipient;
        emit FeeRecipientUpdated(oldRecipient, _newRecipient);
    }
}

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

Context size (optional):