// Contract: BNSG
// Target function: function delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) public
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

abstract contract Token {
    
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool);

    function balanceOf(address account) external virtual view returns (uint);
    
}

contract BNSG {
    /// @notice EIP-20 token name for this token
    string public constant name = "BNS Governance";

    /// @notice EIP-20 token symbol for this token
    string public constant symbol = "BNSG";

    address public bnsdAdd;
    address public bnsAdd;
    address public admin;

    uint96 public bnsToBNSG; // how many satoshi of BNS makes one BNSG
    uint96 public bnsdToBNSG; // how many satoshi of BNSD makes one BNSG

    // Formula to calculate rates above, Ex - BNS rate - 0.06$, BNSG rate - 1$
    // bnsToBNSG = (BNSG rate in USD) * (10 ** (bnsDecimals))/(BNS rate in USD) = (1e8/ 0.06) = 1666666666

    /// @notice EIP-20 token decimals for this token
    uint8 public constant decimals = 18;

    /// @notice Total number of tokens in circulation
    uint96 public constant maxTotalSupply = 10000000e18; // 10 million BNSG

    uint96 public totalSupply; // Starts with 0

    /// @notice Allowance amounts on behalf of others
    mapping (address => mapping (address => uint96)) internal allowances;

    /// @notice Official record of token balances for each account
    mapping (address => uint96) internal balances;

    /// @notice A record of each accounts delegate
    mapping (address => address) public delegates;

    /// @notice A checkpoint for marking number of votes from a given block
    struct Checkpoint {
        uint32 fromBlock;
        uint96 votes;
    }

    /// @notice A record of votes checkpoints for each account, by index
    mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;

    /// @notice The number of checkpoints for each account
    mapping (address => uint32) public numCheckpoints;

    /// @notice The EIP-712 typehash for the contract's domain
    bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

    /// @notice The EIP-712 typehash for the delegation struct used by the contract
    bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

    /// @notice A record of states for signing / validating signatures
    mapping (address => uint) public nonces;

    bool public rateSet;

    /// @notice An event thats emitted when an account changes its delegate
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    /// @notice An event thats emitted when a delegate account's vote balance changes
    event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance);

    /// @notice The standard EIP-20 transfer event
    event Transfer(address indexed from, address indexed to, uint256 amount);

    /// @notice The standard EIP-20 approval event
    event Approval(address indexed owner, address indexed spender, uint256 amount);


    constructor() public {
        admin = msg.sender;
        _mint(admin, 805712895369472282718529); // Amount of token minted in version 1, will be airdropped to users
    }

    modifier _adminOnly() {
        require(msg.sender == admin);
        _;
    }


    /**
     * @notice Add bns and bnsd addresses
     */
    function setAddresses(address _bns, address _bnsd) external _adminOnly returns (bool) {
        bnsAdd = _bns;
        bnsdAdd = _bnsd;
        return true;
    }

    /**
     * @notice Add rates for bns to bnsg and bnsd to bnsg
     */
    function setTokenRates(uint96 _bnsRate, uint96 _bnsdRate) external _adminOnly returns (bool) {
        bnsToBNSG = _bnsRate;
        bnsdToBNSG = _bnsdRate;
        if(!rateSet){
            rateSet = true;
        }
        return true;
    }

    /**
     * @notice Mint `BNSG` by buring BNS token from msg.sender based on current rates on contract
     * @param amountToMint The number of BNSG tokens to be minted 
     * @return Whether or not the minting succeeded
     */
    function mintBNSGWithBNS(uint96 amountToMint) external returns (bool) {
        require(rateSet, "BNSG::mint: rate not yet set");
        require(amountToMint >= 1e18, "BNSG::mint: min mint amount 1");
        uint96 _bnsNeeded = mul96(div96(amountToMint, 1e18, "BNSG::mint: div failed"),bnsToBNSG, "BNSG::mint: mul failed");
        require(Token(bnsAdd).balanceOf(msg.sender) >= _bnsNeeded, "BNSG::mint: insufficient BNS");
        require(Token(bnsAdd).transferFrom(msg.sender, address(1), _bnsNeeded), "BNSG::mint: burn BNS failed");
        _mint(msg.sender, amountToMint);
        _moveDelegates(delegates[address(0)], delegates[msg.sender], amountToMint);
        return true;
    }

    /**
     * @notice Mint `BNSG` by buring BNSD token from msg.sender based on current rates on contract
     * @param amountToMint The number of BNSG tokens to be minted 
     * @return Whether or not the minting succeeded
     */
    function mintBNSGWithBNSD(uint96 amountToMint) external returns (bool) {
        require(rateSet, "BNSG::mint: rate not yet set");
        require(amountToMint >= 1e18, "BNSG::mint: min mint amount 1");
        uint96 _bnsdNeeded = mul96(div96(amountToMint, 1e18, "BNSG::mint: div failed"),bnsdToBNSG, "BNSG::mint: mul failed");
        require(Token(bnsdAdd).balanceOf(msg.sender) >= _bnsdNeeded, "BNSG::mint: insufficient BNSD");
        require(Token(bnsdAdd).transferFrom(msg.sender, address(1), _bnsdNeeded), "BNSG::mint: burn BNSD failed");
        _mint(msg.sender, amountToMint);
        _moveDelegates(delegates[address(0)], delegates[msg.sender], amountToMint);
        return true;
    }


    function _mint(address account, uint96 amount) internal virtual {
        require(account != address(0), "BNSG: mint to the zero address");
        totalSupply =  add96(totalSupply, amount, "BNSG: mint amount overflow");
        require(totalSupply <= maxTotalSupply, "BNSG: crosses total supply possible");
        balances[account] = add96(balances[account], amount, "BNSG::_mint: transfer amount overflows");
        emit Transfer(address(0), account, amount);
    }


    /**
     * @notice Get the number of tokens `spender` is approved to spend on behalf of `account`
     * @param account The address of the account holding the funds
     * @param spender The address of the account spending the funds
     * @return The number of tokens approved
     */
    function allowance(address account, address spender) external view returns (uint) {
        return allowances[account][spender];
    }

    /**
     * @notice Approve `spender` to transfer up to `amount` from `src`
     * @dev This will overwrite the approval amount for `spender`
     *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
     * @param spender The address of the account which may transfer tokens
     * @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
     * @return Whether or not the approval succeeded
     */
    function approve(address spender, uint rawAmount) external returns (bool) {
        uint96 amount;
        if (rawAmount == uint(-1)) {
            amount = uint96(-1);
        } else {
            amount = safe96(rawAmount, "BNSG::approve: amount exceeds 96 bits");
        }

        allowances[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);
        return true;
    }

    /**
     * @notice Get the number of tokens held by the `account`
     * @param account The address of the account to get the balance of
     * @return The number of tokens held
     */
    function balanceOf(address account) external view returns (uint) {
        return balances[account];
    }

    /**
     * @notice Transfer `amount` tokens from `msg.sender` to `dst`
     * @param dst The address of the destination account
     * @param rawAmount The number of tokens to transfer
     * @return Whether or not the transfer succeeded
     */
    function transfer(address dst, uint rawAmount) external returns (bool) {
        uint96 amount = safe96(rawAmount, "BNSG::transfer: amount exceeds 96 bits");
        _transferTokens(msg.sender, dst, amount);
        return true;
    }

    /**
     * @notice Transfer `amount` tokens from `src` to `dst`
     * @param src The address of the source account
     * @param dst The address of the destination account
     * @param rawAmount The number of tokens to transfer
     * @return Whether or not the transfer succeeded
     */
    function transferFrom(address src, address dst, uint rawAmount) external returns (bool) {
        address spender = msg.sender;
        uint96 spenderAllowance = allowances[src][spender];
        uint96 amount = safe96(rawAmount, "BNSG::approve: amount exceeds 96 bits");

        if (spender != src && spenderAllowance != uint96(-1)) {
            uint96 newAllowance = sub96(spenderAllowance, amount, "BNSG::transferFrom: transfer amount exceeds spender allowance");
            allowances[src][spender] = newAllowance;

            emit Approval(src, spender, newAllowance);
        }

        _transferTokens(src, dst, amount);
        return true;
    }

    /**
     * @notice Delegate votes from `msg.sender` to `delegatee`
     * @param delegatee The address to delegate votes to
     */
    function delegate(address delegatee) public {
        return _delegate(msg.sender, delegatee);
    }