S Price: $0.397457 (-13.30%)

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

    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 {
    IEqualizerGauge public gauge;
    IRouter public router;
    IERC20 public token0;
    IERC20 public token1;
    IERC20 public stakeToken;
    IERC20 public rewardToken;
    address private treasury;
    address private creatorTreasury;
    address private owner;
    bool public active = true;
    uint256 public depositFee = 1;
    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 _gauge,
        address _router,
        IERC20 _stakeToken,
        IERC20 _rewardToken,
        IERC20 _token0,
        IERC20 _token1
    ) {
        treasury = _treasury;
        vaultFee = _vaultFee;
        creatorTreasury = _creatorTreasury;
        creatorFee = _creatorFee;
        gauge = IEqualizerGauge(_gauge);
        router = IRouter(_router);
        stakeToken = _stakeToken;
        rewardToken = _rewardToken;
        owner = msg.sender;
        lpPerTicket = 1e18;
        token0 = _token0;
        token1 = _token1;

        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(gauge), 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 getRewards() internal returns (uint256) {
        gauge.getReward();
        return rewardToken.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), false);
        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),
            false,
            token0Balance,
            token1Balance,
            1,
            1,
            address(this),
            block.timestamp
        );

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

    function updateVault() internal returns (uint256) {
        uint256 totalLp = totalDeposits();

        uint256 rewardAmount = 0;
        if (totalTickets > 0 && totalLp > 0) {
            rewardAmount = getRewards();
            if (rewardAmount > 0) {
                uint256 compoundAmount = (rewardAmount * vaultFee) / 1000;
                uint256 creatorAmount = (rewardAmount * creatorFee) / 1000;

                rewardToken.transfer(creatorTreasury, creatorAmount);
                rewardToken.transfer(treasury, compoundAmount);
                rewardAmount = rewardAmount - (compoundAmount + creatorAmount);

                uint256 halfReward = rewardAmount / 2;
                uint256 token0Amount = swapLPToToken0(halfReward);
                uint256 token1Amount = swapLPToToken1(halfReward);
                require(token0Amount > 0 && token1Amount > 0, "No tokens to swap");

                uint256 stakeAmount = addLiquidity();
                require(stakeAmount > 0, "No LP tokens to stake");

                innerDeposit();
                compoundedAt = block.timestamp;
            }
        }

        return rewardAmount;
    }

    function innerDeposit() internal returns (uint256) {
        uint256 lpAmount = stakeToken.balanceOf(address(this));
        if (lpAmount > 0) {
            gauge.deposit(lpAmount);

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

        return lpPerTicket;
    }

    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 tickets = (amount * 1e18) / lpPerTicket;
        userTickets[msg.sender] += tickets;
        totalTickets += tickets;

        gauge.deposit(amount);

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

    function _withdraw(uint256 amount) internal {
        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;

        gauge.withdraw(amount);
        stakeToken.transfer(msg.sender, amount);

        emit Withdraw(msg.sender, amount);
    }

    function withdraw(uint256 amount) external nonReentrant {
        _withdraw(amount);
    }

    function withdrawAll() external {
        uint256 tickets = userTickets[msg.sender];
        uint256 userLp = (tickets * lpPerTicket) / 1e18;
        _withdraw(userLp);
    }

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

        uint256 lpAmount = stakeToken.balanceOf(address(this));
        if (lpAmount > 0) {
            gauge.deposit(lpAmount);

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

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

    function totalDeposits() public view returns (uint256) {
        return gauge.balanceOf(address(this));
    }

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

interface IEqualizerGauge {
    function deposit(uint256 _value) external;
    function withdraw(uint256 amount) external;
    function getReward() external;
    function balanceOf(address account) external view returns (uint256);
}

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

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

Context size (optional):