all files / contracts/staking/ xSOLACE.sol

100% Statements 28/28
100% Branches 2/2
100% Functions 11/11
100% Lines 28/28
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173                                                                                                                      34× 34× 34× 34× 34× 34×   34×                 98× 98× 96×   96×       96×               14× 14× 14× 14× 34× 34×   14×                                                                                                                                  
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.6;
 
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "./../interfaces/staking/IxsLocker.sol";
import "./../interfaces/staking/IxSOLACE.sol";
 
 
/**
 * @title xSolace Token (xSOLACE)
 * @author solace.fi
 * @notice The vote token of the Solace DAO.
 *
 * xSOLACE is the vote token of the Solace DAO. It masquerades as an ERC20 but cannot be transferred, minted, or burned, and thus has no economic value outside of voting.
 *
 * Balances are calculated based on **Locks** in [`xsLocker`](./xsLocker). The base value of a lock is its `amount` of [**SOLACE**](./../SOLACE). Its multiplier is 4x when `end` is 4 years from now, 1x when unlocked, and linearly decreasing between the two. The balance of a lock is its base value times its multiplier.
 *
 * [`balanceOf(user)`](#balanceof) is calculated as the sum of the balances of the user's locks. [`totalSupply()`](#totalsupply) is calculated as the sum of the balances of all locks. These functions should not be called on-chain as they are gas intensive.
 *
 * Voting will occur off chain.
 *
 * Note that transferring [**SOLACE**](./../SOLACE) to this contract will not give you any **xSOLACE**. You should deposit your [**SOLACE**](./../SOLACE) into [`xsLocker`](./xsLocker) via `createLock()`.
 */
// solhint-disable-next-line contract-name-camelcase
contract xSOLACE is IxSOLACE {
 
    /***************************************
    GLOBAL VARIABLES
    ***************************************/
 
    /// @notice The maximum duration of a lock in seconds.
    uint256 public constant override MAX_LOCK_DURATION = 4 * 365 days; // 4 years
    /// @notice The vote power multiplier at max lock in bps.
    uint256 public constant override MAX_LOCK_MULTIPLIER_BPS = 40000;  // 4X
    /// @notice The vote power multiplier when unlocked in bps.
    uint256 public constant override UNLOCKED_MULTIPLIER_BPS = 10000; // 1X
    // 1 bps = 1/10000
    uint256 internal constant MAX_BPS = 10000;
 
    /// @notice The [**xsLocker**](./xsLocker) contract.
    address public override xsLocker;
 
    /**
     * @notice Constructs the **xSOLACE** contract.
     * @param xsLocker_ The [**xsLocker**](./xsLocker) contract.
     */
    constructor(address xsLocker_) {
        require(xsLocker_ != address(0x0), "zero address xslocker");
        xsLocker = xsLocker_;
    }
 
    /***************************************
    VIEW FUNCTIONS
    ***************************************/
 
    /**
     * @notice Returns the user's **xSOLACE** balance.
     * @param account The account to query.
     * @return balance The user's balance.
     */
    function balanceOf(address account) external view override returns (uint256 balance) {
        IERC721Enumerable locker = IERC721Enumerable(xsLocker);
        uint256 numOfLocks = locker.balanceOf(account);
        balance = 0;
        for (uint256 i = 0; i < numOfLocks; i++) {
            uint256 xsLockID = locker.tokenOfOwnerByIndex(account, i);
            balance += balanceOfLock(xsLockID);
        }
        return balance;
    }
 
    /**
     * @notice Returns the **xSOLACE** balance of a lock.
     * @param xsLockID The lock to query.
     * @return balance The locks's balance.
     */
    function balanceOfLock(uint256 xsLockID) public view override returns (uint256 balance) {
        IxsLocker locker = IxsLocker(xsLocker);
        Lock memory lock = locker.locks(xsLockID);
        uint256 base = lock.amount * UNLOCKED_MULTIPLIER_BPS / MAX_BPS;
        // solhint-disable-next-line not-rely-on-time
        uint256 bonus = (lock.end <= block.timestamp)
            ? 0 // unlocked
            // solhint-disable-next-line not-rely-on-time
            : lock.amount * (lock.end - block.timestamp) * (MAX_LOCK_MULTIPLIER_BPS - UNLOCKED_MULTIPLIER_BPS) / (MAX_LOCK_DURATION * MAX_BPS); // locked
        return base + bonus;
    }
 
    /**
     * @notice Returns the total supply of **xSOLACE**.
     * @return supply The total supply.
     */
    function totalSupply() external view override returns (uint256 supply) {
        IERC721Enumerable locker = IERC721Enumerable(xsLocker);
        uint256 numOfLocks = locker.totalSupply();
        supply = 0;
        for (uint256 i = 0; i < numOfLocks; i++) {
            uint256 xsLockID = locker.tokenByIndex(i);
            supply += balanceOfLock(xsLockID);
        }
        return supply;
    }
 
    /**
     * @notice Returns the name of the token.
     */
    function name() external pure override returns (string memory) {
        return "xsolace";
    }
 
    /**
     * @notice Returns the symbol of the token.
     */
    function symbol() external pure override returns (string memory) {
        return "xSOLACE";
    }
 
    /**
     * @notice Returns the number of decimals used to get its user representation.
     */
    function decimals() external pure override returns (uint8) {
        return 18;
    }
 
    /**
     * @notice Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through `transferFrom`.
     */
    // solhint-disable-next-line no-unused-vars
    function allowance(address owner, address spender) external pure override returns (uint256) {
        return 0;
    }
 
    /***************************************
    MUTATOR FUNCTIONS
    ***************************************/
 
    /**
     * @notice In a normal ERC20 contract this would move `amount` tokens from the caller's account to `recipient`.
     * This version reverts because **xSOLACE** is non-transferrable.
     * @param recipient The user to send tokens to.
     * @param amount The amount of tokens to send.
     * @return success False.
     */
    // solhint-disable-next-line no-unused-vars
    function transfer(address recipient, uint256 amount) external override returns (bool success) {
        revert("xSOLACE transfer not allowed");
    }
 
    /**
     * @notice In a normal ERC20 contract this would move `amount` tokens from `sender` to `recipient` using allowance.
     * This version reverts because **xSOLACE** is non-transferrable.
     * @param recipient The user to send tokens to.
     * @param amount The amount of tokens to send.
     * @return success False.
     */
    // solhint-disable-next-line no-unused-vars
    function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool success) {
        revert("xSOLACE transfer not allowed");
    }
 
    /**
     * @notice In a normal ERC20 contract this would set `amount` as the allowance of `spender` over the caller's tokens.
     * This version reverts because **xSOLACE** is non-transferrable.
     * @param spender The user to assign allowance.
     * @param amount The amount of tokens to send.
     * @return success False.
     */
    // solhint-disable-next-line no-unused-vars
    function approve(address spender, uint256 amount) external override returns (bool success) {
        revert("xSOLACE transfer not allowed");
    }
}