S Price: $0.808179 (+0.78%)

Contract Diff Checker

Contract Name:
Vault

Contract Source Code:

File 1 of 1 : Vault

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

abstract contract ReentrancyGuard {
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    receive() external payable {}

    fallback() external payable {}

    function _nonReentrantBefore() private {
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        _status = NOT_ENTERED;
    }

    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

contract Vault is ReentrancyGuard {
    ISnakePool public pool;
    IRouter public router;
    IOracle public gsnakeOracle;
    IERC20 public token0;
    IERC20 public token1;
    IERC20 public stakeToken;
    IERC20 public rewardToken;
    IERC20 public wrappedSonic;
    address private treasury;
    address private creatorTreasury;
    address private owner;
    bool public active = true;
    bool public emergencyWithdrawn = false;
    uint256 public depositFee = 2;
    uint256 public vaultFee;
    uint256 public creatorFee;
    uint256 private lpPerTicket;
    uint256 public totalTickets;
    uint256 public compoundedAt;

    mapping(address => uint256) public userTickets; // Number of tickets per user

    event Deposit(address indexed user, uint256 amount, uint256 tickets);
    event Withdraw(address indexed user, uint256 amount);
    event Compound(uint256 rewardAmount, uint256 lpAmount);
    event ActiveStatusChanged(bool newStatus);

    constructor(
        address _treasury,
        uint256 _vaultFee,
        address _creatorTreasury,
        uint256 _creatorFee,
        address _pool,
        address _router,
        address _gsnakeOracle,
        IERC20 _stakeToken,
        IERC20 _rewardToken,
        IERC20 _token0,
        IERC20 _token1
    ) {
        treasury = _treasury;
        vaultFee = _vaultFee;
        creatorTreasury = _creatorTreasury;
        creatorFee = _creatorFee;
        pool = ISnakePool(_pool);
        router = IRouter(_router);
        gsnakeOracle = IOracle(_gsnakeOracle);
        stakeToken = _stakeToken;
        rewardToken = _rewardToken;
        owner = msg.sender;
        lpPerTicket = 1e18;
        token0 = _token0;
        token1 = _token1;
        wrappedSonic = IERC20(0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38);

        require(_vaultFee + _creatorFee <= 100, "Invalid fee distribution");

        rewardToken.approve(address(router), type(uint256).max);
        token0.approve(address(router), type(uint256).max);
        token1.approve(address(router), type(uint256).max);
        stakeToken.approve(address(pool), type(uint256).max);
    }

    modifier whenActive() {
        require(active, "Vault is not active");
        _;
    }

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

    function toggleActive() external onlyOwner {
        active = !active;
        emit ActiveStatusChanged(active);
    }

    function _swapRewardToSonic(uint256 rewardAmountIn) internal returns (uint256 sonicReceived) {
        IRouter.route[] memory path = new IRouter.route[](1);
        path[0] = IRouter.route(address(rewardToken), address(wrappedSonic), false);

        uint[] memory amounts = router.swapExactTokensForTokens(
            rewardAmountIn,
            1,
            path,
            address(this),
            block.timestamp
        );
        uint256 wrappedSonicReceived = amounts[amounts.length - 1];

        IWETH(address(wrappedSonic)).withdraw(wrappedSonicReceived);

        sonicReceived = wrappedSonicReceived;
    }

    function _harvest() internal returns (uint256) {
        require(address(this).balance > 0, "No funds to harvest");
        require(pool.pendingShareAndPendingRewards(0, address(this)) > pool.minClaimThreshold(), "No rewards to harvest");

        uint256 balanceBefore = address(this).balance;
        pool.harvest{value: balanceBefore}(0);
        uint256 balanceAfter = address(this).balance;

        return balanceBefore - balanceAfter;
    }

    function _getRewards() internal returns (uint256 feePaid, uint256 rewards) {
        uint256 amountSonicToPay = _harvest();

        feePaid = amountSonicToPay;
        rewards = rewardToken.balanceOf(address(this));
    }

    function _swapToken0ForToken1(uint256 amount) internal returns (uint256 token1Received) {
        require(amount > 0, "No token0 to swap");
        IRouter.route[] memory path = new IRouter.route[](1);
        path[0] = IRouter.route(address(token0), address(token1), true);
        router.swapExactTokensForTokens(
            amount,
            1,
            path,
            address(this),
            block.timestamp
        );
        token1Received = token1.balanceOf(address(this));
    }

    function _swapToken1ForToken0(uint256 amount) internal returns (uint256 token0Received) {
        require(amount > 0, "No token1 to swap");
        IRouter.route[] memory path = new IRouter.route[](1);
        path[0] = IRouter.route(address(token1), address(token0), true);
        router.swapExactTokensForTokens(
            amount,
            1,
            path,
            address(this),
            block.timestamp
        );
        token0Received = token0.balanceOf(address(this));
    }

    function _swapLPToToken0(uint256 rewardAmount) internal returns (uint256) {
        require(rewardAmount > 0, "No token to swap");

        IRouter.route[] memory path = new IRouter.route[](1);
        path[0] = IRouter.route(address(rewardToken), address(token0), false);
        router.swapExactTokensForTokens(
            rewardAmount,
            1,
            path,
            address(this),
            block.timestamp
        );

        return token0.balanceOf(address(this));
    }

    function _swapLPToToken1(uint256 rewardAmount) internal returns (uint256) {
        require(rewardAmount > 0, "No token to swap");

        IRouter.route[] memory path = new IRouter.route[](2);
        path[0] = IRouter.route(address(rewardToken), address(token0), false);
        path[1] = IRouter.route(address(token0), address(token1), true);
        router.swapExactTokensForTokens(
            rewardAmount,
            1,
            path,
            address(this),
            block.timestamp
        );

        return token1.balanceOf(address(this));
    }

    function _addLiquidity() internal returns (uint256) {
        uint256 token0Balance = IERC20(token0).balanceOf(address(this));
        uint256 token1Balance = IERC20(token1).balanceOf(address(this));

        // add liquidity
        require(token0Balance > 0, "Insufficient balance for Token0 to provide liquidity");
        require(token1Balance > 0, "Insufficient balance for Token1 to provide liquidity");
        router.addLiquidity(
            address(token0),
            address(token1),
            true,
            token0Balance,
            token1Balance,
            1,
            1,
            address(this),
            block.timestamp
        );

        return IERC20(stakeToken).balanceOf(address(this));
    }

    function _recoverSonicFee(uint256 feePaid) internal returns (uint256, uint256) {
        uint256 requiredSonic = (feePaid * 150) / 100;

        IRouter.route[] memory path = new IRouter.route[](1);
        path[0] = IRouter.route(address(rewardToken), address(wrappedSonic), false);
        uint256[] memory amountsIn = router.getAmountsIn(requiredSonic, path);
        uint256 rewardTokensForSonic = amountsIn[0];

        uint256 sonicReceived = _swapRewardToSonic(rewardTokensForSonic);
        require(sonicReceived >= requiredSonic, "Insufficient sonic received from swap");

        return (sonicReceived, rewardTokensForSonic);
    }

    function addLeftoverLiquidity() external onlyOwner nonReentrant {
        uint256 token0Bal = token0.balanceOf(address(this));
        uint256 token1Bal = token1.balanceOf(address(this));
        require(token0Bal > 0 && token1Bal > 0, "No leftovers to add liquidity");

        (uint256 optimalToken0, uint256 optimalToken1, ) = router.quoteAddLiquidity(
            address(token0),
            address(token1),
            true,
            token0Bal,
            token1Bal
        );

        if (token0Bal > optimalToken0) {
            uint256 excessToken0 = token0Bal - optimalToken0;
            _swapToken0ForToken1(excessToken0);
        } else if (token1Bal > optimalToken1) {
            uint256 excessToken1 = token1Bal - optimalToken1;
            _swapToken1ForToken0(excessToken1);
        }

        uint256 stakeAmount = _addLiquidity();
        require(stakeAmount > 0, "No LP tokens received");

        _innerDeposit();
    }

    function _updateVault() internal returns (uint256) {
        uint256 totalLp = totalDeposits();
        uint256 netRewardTokens = 0;

        if (totalTickets > 0 && totalLp > 0) {
            uint256 rewardAmount = 0;
            uint256 feePaid = 0;
            if (pool.pendingShareAndPendingRewards(0, address(this)) > pool.minClaimThreshold()) {
                (feePaid, rewardAmount) = _getRewards();
            }
            if (rewardAmount > 0) {
                (, uint256 rewardTokensForSonic) = _recoverSonicFee(feePaid);

                uint256 remainingReward = rewardAmount - rewardTokensForSonic;
                uint256 compoundAmount = (remainingReward * vaultFee) / 1000;
                uint256 creatorAmount = (remainingReward * creatorFee) / 1000;
                rewardToken.transfer(creatorTreasury, creatorAmount);
                rewardToken.transfer(treasury, compoundAmount);
                remainingReward = remainingReward - (compoundAmount + creatorAmount);

                (uint112 reserve0, uint112 reserve1, ) = IPair(address(stakeToken)).getReserves();

                uint256 rewardForToken0 = (remainingReward * uint256(reserve0)) / (uint256(reserve0) + uint256(reserve1));
                uint256 rewardForToken1 = remainingReward - rewardForToken0;

                uint256 token0Amount = _swapLPToToken0(rewardForToken0);
                uint256 token1Amount = _swapLPToToken1(rewardForToken1);

                (uint256 optimalToken0, uint256 optimalToken1, ) = router.quoteAddLiquidity(
                    address(token0),
                    address(token1),
                    true,
                    token0Amount,
                    token1Amount
                );

                if (token0Amount > optimalToken0) {
                    uint256 excessToken0 = token0Amount - optimalToken0;
                    _swapToken0ForToken1(excessToken0);
                } else if (token1Amount > optimalToken1) {
                    uint256 excessToken1 = token1Amount - optimalToken1;
                    _swapToken1ForToken0(excessToken1);
                }

                uint256 stakeAmount = _addLiquidity();
                require(stakeAmount > 0, "No LP tokens received");

                _innerDeposit();
                compoundedAt = block.timestamp;

                netRewardTokens = remainingReward;
            }
        }
        return netRewardTokens;
    }

    function _innerDeposit() internal returns (uint256) {
        uint256 lpAmount = stakeToken.balanceOf(address(this));
        if (lpAmount > 0) {
            pool.deposit(0, lpAmount);

            uint256 totalDeps = totalDeposits();
            lpPerTicket = (totalDeps * 1e18) / totalTickets;
        }

        return lpPerTicket;
    }

    function _withdraw(uint256 amount) internal {
        if (totalTickets != 0) {
            _updateVault();
        }

        uint256 tickets = userTickets[msg.sender];
        require(tickets > 0, "No tickets to withdraw");

        uint256 userLp = (tickets * lpPerTicket) / 1e18;
        require(userLp >= amount, "Insufficient balance");

        uint256 withdrawTickets = (amount * 1e18) / lpPerTicket;
        userTickets[msg.sender] -= withdrawTickets;
        totalTickets -= withdrawTickets;

        pool.withdraw(0, amount);
        stakeToken.transfer(msg.sender, amount);

        emit Withdraw(msg.sender, amount);
    }

    function deposit(uint256 amount) external nonReentrant whenActive {
        if (totalTickets != 0) {
            _updateVault();
        }

        stakeToken.transferFrom(msg.sender, address(this), amount);

        uint256 fee = (amount * depositFee) / 1000;
        stakeToken.transfer(treasury, fee);
        amount -= fee;

        (uint256 amountBefore,) = pool.userInfo(0, address(this));
        pool.deposit(0, amount);
        (uint256 amountAfter ,) = pool.userInfo(0, address(this));

        uint256 depositedAmount = amountAfter - amountBefore;
        uint256 tickets = (depositedAmount * 1e18) / lpPerTicket;
        userTickets[msg.sender] += tickets;
        totalTickets += tickets;

        emit Deposit(msg.sender, depositedAmount, tickets);
    }

    function withdraw(uint256 amount) external nonReentrant {
        require(!emergencyWithdrawn, "Use userEmergencyWithdraw instead");
        _withdraw(amount);
    }

    function withdrawAll() external nonReentrant {
        require(!emergencyWithdrawn, "Use userEmergencyWithdraw instead");
        uint256 tickets = userTickets[msg.sender];
        uint256 userLp = (tickets * lpPerTicket) / 1e18;
        _withdraw(userLp);
    }

    function userEmergencyWithdraw() external nonReentrant {
        require(emergencyWithdrawn, "Emergency withdraw not enabled");
        uint256 tickets = userTickets[msg.sender];
        if (tickets == 0) return;

        uint256 userLp = (tickets * lpPerTicket) / 1e18;
        userTickets[msg.sender] = 0;
        totalTickets -= tickets;

        stakeToken.transfer(msg.sender, userLp);

        emit Withdraw(msg.sender, userLp);
    }

    function emergencyWithdraw() external onlyOwner nonReentrant {
        pool.emergencyWithdraw(0);
        emergencyWithdrawn = true;
        active = false;

        emit ActiveStatusChanged(active);
    }

    function compound() external whenActive {
        uint256 rewards = _updateVault();
        require(rewards > 0, "No rewards to compound");

        emit Compound(rewardToken.balanceOf(address(this)), rewards);
    }

    function totalDeposits() public view returns (uint256) {
        (uint256 amount,) = pool.userInfo(0, address(this));
        return amount;
    }

    function lpForUser(address _user) external view returns (uint256) {
        uint256 tickets = userTickets[_user];
        if (tickets == 0) return 0;

        uint256 _lpPerTicket = lpPerTicket;
        uint256 totalLp = totalDeposits();
        if (totalTickets > 0 && totalLp > 0) {
            uint256 rewardAmount = rewardToken.balanceOf(address(this));
            if (rewardAmount > 0) {
                _lpPerTicket += (rewardAmount * 1e18) / totalTickets;
            }
        }

        return (tickets * _lpPerTicket) / 1e18;
    }

    function addFunds() external payable onlyOwner {
        require(msg.value > 0, "No funds sent");
    }

    function takeFunds(uint256 amount) external onlyOwner nonReentrant {
        require(address(this).balance >= amount, "Insufficient balance");

        (bool sent, ) = payable(owner).call{value: amount}("");
        require(sent, "Transfer failed");
    }

    function takeAllFunds() external onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        require(balance > 0, "No funds available");

        (bool sent, ) = payable(owner).call{value: balance}("");
        require(sent, "Transfer failed");
    }
}

interface ISnakePool {
    function deposit(uint _pid, uint _amount) external;
    function withdraw(uint _pid, uint _amount) external;
    function emergencyWithdraw(uint _pid) external;
    function harvest(uint _pid) payable external;
    function harvestAll() payable external;

    function userInfo(uint256 _pid, address _user) external view returns (uint256 amount, uint256 rewardDebt);
    function poolInfo(uint256 _pid) external view returns (ISnakePool.PoolInfo memory);
    function pendingRewards(uint256 _pid, address _user) external view returns (uint256);
    function pendingShareAndPendingRewards(uint _pid, address _user) external view returns (uint256);
    function minClaimThreshold() external view returns (uint256);
    function pegStabilityModuleFeeEnabled() external view returns (bool);
    function pegStabilityModuleFee() external view returns (uint256);

    enum GaugeDex {
        NONE,
        SHADOW,
        SWAPX
    }

    struct GaugeInfo {
        bool isGauge;   // If this is a gauge
        address gauge;  // The gauge
        GaugeDex gaugeDex; // Dex of the gauge
    }

    struct PoolInfo {
        IERC20 token; // Address of LP token contract.
        uint256 depFee; // deposit fee that is applied to created pool.
        uint256 allocPoint; // How many allocation points assigned to this pool. GSNAKEs to distribute per block.
        uint256 lastRewardTime; // Last time that GSNAKEs distribution occurs.
        uint256 accGsnakePerShare; // Accumulated GSNAKEs per share, times 1e18. See below.
        bool isStarted; // if lastRewardTime has passed
        GaugeInfo gaugeInfo; // Gauge info (does this pool have a gauge and where is it)
        uint256 poolGsnakePerSec; // rewards per second for pool (acts as allocPoint)
    }
}

interface IOracle {
    function update() external;
    function consult(address _token, uint256 _amountIn) external view returns (uint256 amountOut);
    function twap(address _token, uint256 _amountIn) external view returns (uint256 _amountOut);
}

interface IRouter {
    struct route {
        address from;
        address to;
        bool stable;
    }

    function addLiquidity(
        address tokenA,
        address tokenB,
        bool stable,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (
        uint amountA,
        uint amountB,
        uint liquidity
    );

    function getAmountsIn(
        uint256 amountOut,
        route[] memory routes
    ) external returns (
        uint256[] memory amounts
    );

    function quoteAddLiquidity(
        address tokenA,
        address tokenB,
        bool stable,
        uint256 amountADesired,
        uint256 amountBDesired
    ) external view returns (
        uint256 amountA,
        uint256 amountB,
        uint256 liquidity
    );

    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        route[] calldata routes,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        route[] memory routes,
        address to,
        uint256 deadline
    ) external;
}

interface IERC20 {
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, 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 from,
        address to,
        uint256 amount
    ) external returns (bool);
}

interface IWETH is IERC20 {
    function withdraw(uint256 wad) external;
}

interface IPair {
    function getReserves()
    external
    view
    returns (
        uint112 _reserve0,
        uint112 _reserve1,
        uint32 _blockTimestampLast
    );
}

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

Context size (optional):