LiquidusFeeEstimation.sol

Contract used to get the users LIQ holdings and calculate fees.

Smart Contract Code

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

import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

interface IStaking {
    struct UserInfo {
        uint256 amount;
        int256 rewardDebt;
        uint256 lastDepositedAt;
    }
    function userInfo(address account) view external returns (UserInfo memory);
    function lpToken() view external returns (address);
}

interface ILIQNFTs {
    function tokensOfHolder(address _holder) view external returns (uint256[] memory);
}

contract LiquidusFeeEstimation is Ownable, Pausable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    event AdminTokenRecovery(address tokenRecovered, uint256 amount);
    event feesChanged(uint256 _feeBronze, uint256 _feeSilver, uint256 _feeGold, uint256 _feeTitan);

    // Name of contract
    string private name;

    // Fee tier thresholds
    uint256 public silver;
    uint256 public gold;
    uint256 public titan;

    // Fee rates per tier
    uint256 public feeBronze;
    uint256 public feeSilver;
    uint256 public feeGold;
    uint256 public feeTitan;

    // Boost percentages per tier (display only)
    uint8 public percentBronze;
    uint8 public percentSilver;
    uint8 public percentGold;
    uint8 public percentTitan;

    // NFT
    bool public useNFT;
    address public NFTcontract;
    mapping(uint256 => uint256) tierOfCollection;

    // fee discount valid contracts (contracts with timelocks only!)
    address[] public lpStakingContracts;
    address[] public tokenStakingContracts;
    mapping(address => bool) public isLpStakingContracts;
    mapping(address => bool) public isTokenStakingContracts;

    address public LIQToken = 0xc7981767f644C7F8e483DAbDc413e8a371b83079; //LIQ token address

    constructor(string memory _name) {
        name = _name;
    }

    function userFee (address user) external view returns (uint256) {
        uint256 holdings = 0;
        uint256 NFTholdings = 0;

        if (useNFT) {
            uint256[] memory tokens = ILIQNFTs(NFTcontract).tokensOfHolder(user);
            uint256 i;
            for (i = 0; i < tokens.length; i++){
                uint256 collection = tokens[i].div(1e6);
                if (tierOfCollection[collection] > NFTholdings){
                    NFTholdings = tierOfCollection[collection];
                }
            }
        }
        
        // Calculate holdings in lp staking pool
        for (uint256 i = 0; i < lpStakingContracts.length; i++) {
            IStaking.UserInfo memory infor = IStaking(lpStakingContracts[i]).userInfo(user);
            if (infor.amount > 0) {
                holdings = holdings.add(infor.amount.mul(getTokenPerLP(IStaking(lpStakingContracts[i]).lpToken())).div(1e18));
            }
        }

        // Calculate holdings in single staking pool
        for (uint256 i = 0; i < tokenStakingContracts.length; i++) {
            IStaking.UserInfo memory infor = IStaking(tokenStakingContracts[i]).userInfo(user);
            holdings = holdings.add(infor.amount);
        }

        if (NFTholdings > holdings) {
            holdings = NFTholdings;
        }

        // Return fee based on holdings
        if (holdings.div(1e18) >= titan){
            return feeTitan;
        } else if (holdings.div(1e18) >= gold && holdings.div(1e18) < titan){
            return feeGold;
        } else if (holdings.div(1e18) >= silver && holdings.div(1e18) < gold){
            return feeSilver;
        } else if (holdings.div(1e18) < silver) {
            return feeBronze;
        }

    }

    function userHoldings (address user) external view returns (uint256) {
        uint256 holdings = 0;
        
        // Calculate holdings in lp staking pool
        for (uint256 i = 0; i < lpStakingContracts.length; i++) {
            IStaking.UserInfo memory infor = IStaking(lpStakingContracts[i]).userInfo(user);
            if (infor.amount > 0) {
                holdings = holdings.add(infor.amount.mul(getTokenPerLP(IStaking(lpStakingContracts[i]).lpToken())).div(1e18));
            }
        }

        // Calculate holdings in single staking pool
        for (uint256 i = 0; i < tokenStakingContracts.length; i++) {
            IStaking.UserInfo memory infor = IStaking(tokenStakingContracts[i]).userInfo(user);
            holdings = holdings.add(infor.amount);
        }

        return holdings;
       
    }

    function setFees(uint256 _feeBronze, uint256 _feeSilver, uint256 _feeGold, uint256 _feeTitan) public onlyOwner {
        require(_feeBronze <= 500, "Error - Fee must be less than 5%");
        require(_feeSilver <= 500, "Error - Fee must be less than 5%");
        require(_feeGold <= 500, "Error - Fee must be less than 5%");
        require(_feeTitan <= 500, "Error - Fee must be less than 5%");
        feeBronze = _feeBronze;
        feeSilver = _feeSilver;
        feeGold = _feeGold;
        feeTitan = _feeTitan;
        emit feesChanged(_feeBronze, _feeSilver, _feeGold, _feeTitan);
    }

    function setTierThresholds(uint256 _silver, uint256 _gold, uint256 _titan) public onlyOwner {
        silver = _silver;
        gold = _gold;
        titan = _titan;
    }

    function setLIQToken(address _LIQToken) public onlyOwner {
        LIQToken = _LIQToken;
    }

    function setBoostPercentage(uint8 _percentBronze, uint8 _percentSilver, uint8 _percentGold, uint8 _percentTitan) public onlyOwner {
        percentBronze = _percentBronze;
        percentSilver = _percentSilver;
        percentGold = _percentGold;
        percentTitan = _percentTitan;
    }

    function setNFT(address _NFTcontract, bool _useNFT) public onlyOwner {
        require(isContract(_NFTcontract), "Must be contract");
        NFTcontract = _NFTcontract;
        useNFT = _useNFT;
    }

    function setCollectionHoldings(uint256 collection, uint256 holdings) public onlyOwner {
        tierOfCollection[collection] = holdings.mul(1e18);
    }

    //Checker for lpStaking and single staking arrays. Source: openzeppelin address library
    function isContract(address _addr) internal view returns (bool){
        uint32 size;
        assembly {
            size := extcodesize(_addr)
        }
        return (size > 0);
    }

    function recoverWrongTokens(address _tokenAddress, uint256 _tokenAmount) external onlyOwner {
        IERC20(_tokenAddress).safeTransfer(address(msg.sender), _tokenAmount);
        emit AdminTokenRecovery(_tokenAddress, _tokenAmount);
    }

    function getTokenPerLP(address lpContractAddress) public view returns(uint256) {
        uint256 totalLPSupply = IERC20(lpContractAddress).totalSupply();
        uint256 totalLiqInLP = IERC20(LIQToken).balanceOf(lpContractAddress);
        totalLiqInLP = totalLiqInLP.mul(1e18);
        return totalLiqInLP.div(totalLPSupply);
    }

    function addLpStakingContract(address _stakingAddress) public onlyOwner {
        require(isLpStakingContracts[_stakingAddress] == false, "Already in staking list");
        require(isContract(_stakingAddress), "Must be contract");
        isLpStakingContracts[_stakingAddress] = true;
        lpStakingContracts.push(_stakingAddress);
    }

    function removeLpStakingContract(address _stakingAddress) public onlyOwner {
        require(isLpStakingContracts[_stakingAddress] == true, "Not in staking list");
        require(isContract(_stakingAddress), "Must be contract");
        isLpStakingContracts[_stakingAddress] = false;
        for (uint256 i = 0; i < lpStakingContracts.length; i++) {
            if (lpStakingContracts[i] == _stakingAddress) {
                lpStakingContracts[i] = lpStakingContracts[lpStakingContracts.length - 1];
                lpStakingContracts.pop();
                break;
            }
        }
    }

    function addTokenStakingContract(address _stakingAddress) public onlyOwner {
        require(isTokenStakingContracts[_stakingAddress] == false, "Already in staking list");
        require(isContract(_stakingAddress), "Must be contract");
        isTokenStakingContracts[_stakingAddress] = true;
        tokenStakingContracts.push(_stakingAddress);
    }

    function removeTokenStakingContract(address _stakingAddress) public onlyOwner {
        require(isTokenStakingContracts[_stakingAddress] == true, "Not in staking list");
        require(isContract(_stakingAddress), "Must be contract");
        isTokenStakingContracts[_stakingAddress] = false;
        for (uint256 i = 0; i < tokenStakingContracts.length; i++) {
            if (tokenStakingContracts[i] == _stakingAddress) {
                tokenStakingContracts[i] = tokenStakingContracts[tokenStakingContracts.length - 1];
                tokenStakingContracts.pop();
                break;
            }
        }
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    receive() external payable {}
}

Last updated