/**
 * @notice Set `_designatedSigner` as the designated signer of the app, which will be able to sign messages on behalf of the app
 * @param _designatedSigner Address that will be able to sign messages on behalf of the app
 */

function setDesignatedSigner(address _designatedSigner)
        external
        authP(DESIGNATE_SIGNER_ROLE, arr(_designatedSigner))
    {
        // Prevent an infinite loop by setting the app itself as its designated signer.
        // An undetectable loop can be created by setting a different contract as the
        // designated signer which calls back into `isValidSignature`.
        // Given that `isValidSignature` is always called with just 50k gas, the max
        // damage of the loop is wasting 50k gas.
        require(_designatedSigner != address(this), ERROR_DESIGNATED_TO_SELF);

        address oldDesignatedSigner = designatedSigner;
        designatedSigner = _designatedSigner;

        emit SetDesignatedSigner(msg.sender, oldDesignatedSigner, _designatedSigner);
    }


/**
 * @notice Execute the script as the Agent app
 * @dev IForwarder interface conformance. Forwards any token holder action.
 * @param _evmScript Script being executed
 */

function forward(bytes _evmScript) public {
        require(canForward(msg.sender, _evmScript), ERROR_CAN_NOT_FORWARD);

        bytes memory input = ""; // no input
        address[] memory blacklist = new address[](0); // no addr blacklist, can interact with anything
        runScript(_evmScript, input, blacklist);
        // We don't need to emit an event here as EVMScriptRunner will emit ScriptResult if successful
    }


// gets all tokens currently in the pool

function getTokenWeights() public view returns (uint256[] memory) {
        address[] memory tokens = getPoolTokens();
        uint256[] memory weights = new uint256[](tokens.length);

        for (uint256 i = 0; i < tokens.length; i++) {
            weights[i] = smartPool.getDenormalizedWeight(tokens[i]);
        }
        return weights;
    }


// gets current LP exchange rate for single Asset

function getSovAmountOutSingle(
        address tokenIn,
        uint256 tokenAmountIn,
        uint256 minPoolAmountOut
    ) public view returns (uint256 poolAmountOut) {
        BPool bPool = smartPool.bPool();
        require(bPool.isBound(tokenIn), "ERR_NOT_BOUND");

        //apply protocol fee
        uint256 tokenAmountInAdj = tokenAmountIn.mul(protocolFee).div(
            PROTOCOL_FEE_DECIMALS
        );

        poolAmountOut = bPool.calcPoolOutGivenSingleIn(
            bPool.getBalance(tokenIn),
            bPool.getDenormalizedWeight(tokenIn),
            smartPool.totalSupply(),
            bPool.getTotalDenormalizedWeight(),
            tokenAmountInAdj,
            bPool.getSwapFee()
        );
        require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_IN");
    }


// gets current LP exchange rate for single token

function getSovAmountInSingle(
        address tokenOut,
        uint256 tokenAmountOut,
        uint256 maxPoolAmountIn
    ) public view returns (uint256 poolAmountIn) {
        BPool bPool = smartPool.bPool();
        require(bPool.isBound(tokenOut), "ERR_NOT_BOUND");

        //apply protocol fee
        uint256 tokenAmountOutAdj = tokenAmountOut.mul(protocolFee).div(
            PROTOCOL_FEE_DECIMALS
        );

        require(
            tokenAmountOutAdj <=
                SafeMath.mul(bPool.getBalance(tokenOut), MAX_OUT_RATIO),
            "ERR_MAX_OUT_RATIO"
        );
        poolAmountIn = bPool.calcPoolInGivenSingleOut(
            bPool.getBalance(tokenOut),
            bPool.getDenormalizedWeight(tokenOut),
            smartPool.totalSupply(),
            bPool.getTotalDenormalizedWeight(),
            tokenAmountOutAdj,
            bPool.getSwapFee()
        );

        require(poolAmountIn != 0, "ERR_MATH_APPROX");
        require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN");
    }


/// @dev Override unpause so it requires all external contract addresses
///  to be set before contract can be unpaused. Also, we can't have
///  newContractAddress set either, because then the contract was upgraded.
/// @notice This is public rather than external so we can call super.unpause
///  without using an expensive CALL.

function unpause() public onlyCEO whenPaused {
        require(newContractAddress == address(0), "set newContractAddress first");
        require(authorAddress != address(0), "set authorAddress first");
        require(foundationAddress != address(0), "set foundationAddress first");

        // Actually unpause the contract.
        super.unpause();
    }


// gets current LP exchange rate for all

function getTokensAmountOut(
        uint256 poolAmountIn,
        uint256[] calldata minAmountsOut
    ) public view returns (uint256[] memory actualAmountsOut) {
        BPool bPool = smartPool.bPool();
        address[] memory tokens = bPool.getCurrentTokens();

        require(minAmountsOut.length == tokens.length, "ERR_AMOUNTS_MISMATCH");

        uint256 poolTotal = smartPool.totalSupply();

        uint256 ratio = SafeMath.div(
            poolAmountIn.mul(10**18),
            SafeMath.add(poolTotal, 1)
        );

        require(ratio != 0, "ERR_MATH_APPROX");

        actualAmountsOut = new uint256[](tokens.length);

        // This loop contains external calls
        // External calls are to math libraries or the underlying pool, so low risk
        for (uint256 i = 0; i < tokens.length; i++) {
            address t = tokens[i];
            uint256 bal = bPool.getBalance(t);
            // Subtract 1 to ensure any rounding errors favor the pool
            uint256 tokenAmountOut = SafeMath
                .mul(ratio, SafeMath.sub(bal, 1))
                .div(10**18);

            //apply protocol fee
            tokenAmountOut = tokenAmountOut.mul(protocolFee).div(
                PROTOCOL_FEE_DECIMALS
            );

            require(tokenAmountOut != 0, "ERR_MATH_APPROX");
            require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT");

            actualAmountsOut[i] = tokenAmountOut;
        }
    }


/// @notice Confirm the next unresolved node

function confirmNextNode(
        bytes32 beforeSendAcc,
        bytes calldata sendsData,
        uint256[] calldata sendLengths,
        uint256 afterSendCount,
        bytes32 afterLogAcc,
        uint256 afterLogCount,
        IOutbox outbox,
        RollupEventBridge rollupEventBridge
    ) internal {
        confirmNode(
            _firstUnresolvedNode,
            beforeSendAcc,
            sendsData,
            sendLengths,
            afterSendCount,
            afterLogAcc,
            afterLogCount,
            outbox,
            rollupEventBridge
        );
    }


/**
 * @dev Added nonce and contract address in sig to guard against replay attacks
 */

function checkSig(
        uint256 mintList,
        uint256 numTokens,
        uint256 nonceSeq,
        address user,
        uint256 price,
        bytes memory sig
    ) internal view returns (bool) {
        bytes32 hash = keccak256(
            abi.encodePacked(
                "\x19Ethereum Signed Message:\n32",
                keccak256(abi.encode(mintList, numTokens, nonceSeq, address(this), user, price))
            )
        );
        return _signerAddress == hash.recover(sig);
    }


/**
 * @notice Register an identity contract corresponding to a user address.
 * Requires that the user doesn't have an identity contract already registered.
 * Only agent can call.
 *
 * @param _user The address of the user
 * @param _identity The address of the user's identity contract
 * @param _country The country of the investor
 */

function registerIdentity(address _user, Identity _identity, uint16 _country) public onlyAgent {
        require(address(_identity) != address(0), "contract address can't be a zero address");
        require(address(identity[_user]) == address(0), "identity contract already exists, please use update");
        identity[_user] = _identity;
        investorCountry[_user] = _country;

        emit IdentityRegistered(_user, _identity);
    }


/**
 * @notice This functions checks whether an identity contract
 * corresponding to the provided user address has the required claims or not based
 * on the security token.
 *
 * @param _userAddress The address of the user to be verified.
 *
 * @return 'True' if the address is verified, 'false' if not.
 */

function isVerified(address _userAddress) public view returns (bool) {
        if (address(identity[_userAddress]) == address(0)){
            return false;
        }

        uint256[] memory claimTopics = topicsRegistry.getClaimTopics();
        uint length = claimTopics.length;
        if (length == 0) {
            return true;
        }

        uint256 foundClaimTopic;
        uint256 scheme;
        address issuer;
        bytes memory sig;
        bytes memory data;
        uint256 claimTopic;
        for (claimTopic = 0; claimTopic < length; claimTopic++) {
            bytes32[] memory claimIds = identity[_userAddress].getClaimIdsByTopic(claimTopics[claimTopic]);
            if (claimIds.length == 0) {
                return false;
            }
            for (uint j = 0; j < claimIds.length; j++) {
                // Fetch claim from user
                ( foundClaimTopic, scheme, issuer, sig, data, ) = identity[_userAddress].getClaim(claimIds[j]);
                if (!issuersRegistry.isTrustedIssuer(issuer)) {
                    return false;
                }
                if (!issuersRegistry.hasClaimTopic(issuer, claimTopics[claimTopic])) {
                    return false;
                }
                if (!ClaimIssuer(issuer).isClaimValid(identity[_userAddress], claimIds[j], claimTopics[claimTopic], sig, data)) {
                    return false;
                }
            }
        }

        return true;
    }


/// @notice Calculate expected returning amount of `destToken`
/// @param fromToken (IERC20) Address of token or `address(0)` for Ether
/// @param destToken (IERC20) Address of token or `address(0)` for Ether
/// @param amount (uint256) Amount for `fromToken`
/// @param parts (uint256) Number of pieces source volume could be splitted,
/// works like granularity, higly affects gas usage. Should be called offchain,
/// but could be called onchain if user swaps not his own funds, but this is still considered as not safe.
/// @param flags (uint256) Flags for enabling and disabling some features, default 0

function getExpectedReturn(
        IERC20 fromToken,
        IERC20 destToken,
        uint256 amount,
        uint256 parts,
        uint256 flags // See contants in IOneSplit.sol
    )
        public
        view
        returns(
            uint256 returnAmount,
            uint256[] memory distribution
        )
    {
        (returnAmount, , distribution) = getExpectedReturnWithGas(
            fromToken,
            destToken,
            amount,
            parts,
            flags,
            0
        );
    }


/// @notice Swap `amount` of `fromToken` to `destToken`
/// @param fromToken (IERC20) Address of token or `address(0)` for Ether
/// @param destToken (IERC20) Address of token or `address(0)` for Ether
/// @param amount (uint256) Amount for `fromToken`
/// @param minReturn (uint256) Minimum expected return, else revert
/// @param distribution (uint256[]) Array of weights for volume distribution returned by `getExpectedReturn`
/// @param flags (uint256) Flags for enabling and disabling some features, default 0

function swap(
        IERC20 fromToken,
        IERC20 destToken,
        uint256 amount,
        uint256 minReturn,
        uint256[] memory distribution,
        uint256 flags // See contants in IOneSplit.sol
    ) public payable returns(uint256 returnAmount) {
        return swapWithReferral(
            fromToken,
            destToken,
            amount,
            minReturn,
            distribution,
            flags,
            address(0),
            0
        );
    }


/*
 * @dev: Deploys a new BridgeToken contract
 *
 * @param _symbol: The BridgeToken's symbol
 */

function deployNewBridgeToken(string memory _symbol)
        internal
        returns (address)
    {
        bridgeTokenCount = bridgeTokenCount.add(1);

        // Deploy new bridge token contract
        BridgeToken newBridgeToken = (new BridgeToken)(_symbol);

        // Set address in tokens mapping
        address newBridgeTokenAddress = address(newBridgeToken);
        controlledBridgeTokens[_symbol] = newBridgeTokenAddress;
        lowerToUpperTokens[toLower(_symbol)] = _symbol;

        emit LogNewBridgeToken(newBridgeTokenAddress, _symbol);
        return newBridgeTokenAddress;
    }


/*
 * @dev: Deploys a new BridgeToken contract
 *
 * @param _symbol: The BridgeToken's symbol
 *
 * @note the Rowan token symbol needs to be "Rowan" so that it integrates correctly with the cosmos bridge
 */

function useExistingBridgeToken(address _contractAddress)
        internal
        returns (address)
    {
        bridgeTokenCount = bridgeTokenCount.add(1);

        string memory _symbol = BridgeToken(_contractAddress).symbol();
        // Set address in tokens mapping
        address newBridgeTokenAddress = _contractAddress;
        controlledBridgeTokens[_symbol] = newBridgeTokenAddress;
        lowerToUpperTokens[toLower(_symbol)] = _symbol;

        emit LogNewBridgeToken(newBridgeTokenAddress, _symbol);
        return newBridgeTokenAddress;
    }


/*
 * @dev: Mints new cosmos tokens
 *
 * @param _cosmosSender: The sender's Cosmos address in bytes.
 * @param _ethereumRecipient: The intended recipient's Ethereum address.
 * @param _cosmosTokenAddress: The currency type
 * @param _symbol: comsos token symbol
 * @param _amount: number of comsos tokens to be minted
 */

function mintNewBridgeTokens(
        address payable _intendedRecipient,
        address _bridgeTokenAddress,
        string memory _symbol,
        uint256 _amount
    ) internal {
        require(
            controlledBridgeTokens[_symbol] == _bridgeTokenAddress,
            "Token must be a controlled bridge token"
        );

        // Mint bridge tokens
        require(
            BridgeToken(_bridgeTokenAddress).mint(_intendedRecipient, _amount),
            "Attempted mint of bridge tokens failed"
        );

        emit LogBridgeTokenMint(
            _bridgeTokenAddress,
            _symbol,
            _amount,
            _intendedRecipient
        );
    }


// INTEGRALS

function integral(int256 q) private view returns (int256) {
        // we are integrating over a curve that represents the order book
        if (q > 0) {
            // integrate over bid orders, our trade can be a bid or an ask
            return integralBid(q);
        } else if (q < 0) {
            // integrate over ask orders, our trade can be a bid or an ask
            return integralAsk(q);
        } else {
            return 0;
        }
    }


// SPOT PRICE

function getSpotPrice(uint256 xCurrent, uint256 xBefore) public view override returns (uint256 spotPrice) {
        int256 xCurrentInt = normalizeAmount(xDecimals, xCurrent);
        int256 xBeforeInt = normalizeAmount(xDecimals, xBefore);
        int256 spotPriceInt = derivative(xCurrentInt.sub(xBeforeInt));
        require(spotPriceInt >= 0, 'IO_NEGATIVE_SPOT_PRICE');
        return uint256(spotPriceInt);
    }


// DERIVATIVES

function derivative(int256 t) public view returns (int256) {
        if (t > 0) {
            return derivativeBid(t);
        } else if (t < 0) {
            return derivativeAsk(t);
        } else {
            return price;
        }
    }


/*** GETTERS ***/

function get_available_S_balances() public view returns(uint256, uint256) {
    uint256 epoch = get_current_epoch();
    uint256 AA_A_utilized = tranche_S_virtual_utilized[epoch][uint256(Tranche.A)].add(tranche_S_virtual_utilized[epoch][uint256(Tranche.AA)]);
    uint256 AA_A_unutilized = tranche_S_virtual_unutilized[epoch][uint256(Tranche.A)].add(tranche_S_virtual_unutilized[epoch][uint256(Tranche.AA)]);
    uint256 S_utilized = tranche_total_utilized[epoch][uint256(Tranche.S)];
    uint256 S_unutilized = tranche_total_unutilized[epoch][uint256(Tranche.S)];
    return ((S_utilized > AA_A_utilized ? S_utilized - AA_A_utilized : 0), (S_unutilized > AA_A_unutilized ? S_unutilized - AA_A_unutilized : 0));
  }


/**
 Refund investment, token will remain to the investor
 **/

function refund()
    only_sale_refundable {
        address investor = msg.sender;
        if(balances[investor] == 0) throw; // nothing to refund
        uint amount = balances[investor];
        // remove balance
        delete balances[investor];
        // send back eth
        if(!investor.send(amount)) throw;

        Refunded(investor, amount);
    }


/**
 * @dev This is from Timelock contract.
 */

function executeTransaction(
        address target,
        uint256 value,
        string memory signature,
        bytes memory data
    ) public returns (bytes memory) {
        require(msg.sender == timelock, "!timelock");

        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        (bool success, bytes memory returnData) = target.call{value: value}(callData);
        require(success, string(abi.encodePacked(getName(), "::executeTransaction: Transaction execution reverted.")));

        emit ExecuteTransaction(target, value, signature, data);

        return returnData;
    }


/*
 * @dev: Initializer, sets operator
 */

function initialize(
        address _operatorAddress,
        address _cosmosBridgeAddress,
        address _owner,
        address _pauser
    ) public {
        require(!_initialized, "Init");

        EthereumWhiteList.initialize();
        CosmosWhiteList.initialize();
        Pausable.initialize(_pauser);

        operator = _operatorAddress;
        cosmosBridge = _cosmosBridgeAddress;
        owner = _owner;
        _initialized = true;

        // hardcode since this is the first token
        lowerToUpperTokens["erowan"] = "erowan";
        lowerToUpperTokens["eth"] = "eth";
    }


/*
 * @dev: function to validate if a sif address has a correct prefix
 */

function verifySifPrefix(bytes memory _sifAddress) public pure returns (bool) {
        bytes3 sifInHex = 0x736966;

        for (uint256 i = 0; i < sifInHex.length; i++) {
            if (sifInHex[i] != _sifAddress[i]) {
                return false;
            }
        }
        return true;
    }


/*
 * @dev: Set the token address in whitelist
 *
 * @param _token: ERC 20's address
 * @param _inList: set the _token in list or not
 * @return: new value of if _token in whitelist
 */

function updateEthWhiteList(address _token, bool _inList)
        public
        onlyOperator
        returns (bool)
    {
        string memory symbol = BridgeToken(_token).symbol();
        address listAddress = lockedTokenList[symbol];

        // Do not allow a token with the same symbol to be whitelisted
        if (_inList) {
            // if we want to add it to the whitelist, make sure that the address
            // is 0, meaning we have not seen that symbol in the whitelist before
            require(listAddress == address(0), "whitelisted");
        } else {
            // if we want to de-whitelist it, make sure that the symbol is
            // in fact stored in our locked token list before we set to false
            require(uint256(listAddress) > 0, "!whitelisted");
        }
        lowerToUpperTokens[toLower(symbol)] = symbol;
        return setTokenInEthWhiteList(_token, _inList);
    }


/*
 * @dev: Burns BridgeTokens representing native Cosmos assets.
 *
 * @param _recipient: bytes representation of destination address.
 * @param _token: token address in origin chain (0x0 if ethereum)
 * @param _amount: value of deposit
 */

function burn(
        bytes memory _recipient,
        address _token,
        uint256 _amount
    ) public validSifAddress(_recipient) onlyCosmosTokenWhiteList(_token) whenNotPaused {
        string memory symbol = BridgeToken(_token).symbol();

        if (_amount > maxTokenAmount[symbol]) {
            revert("Amount being transferred is over the limit for this token");
        }

        BridgeToken(_token).burnFrom(msg.sender, _amount);
        burnFunds(msg.sender, _recipient, _token, symbol, _amount);
    }


/*
 * @dev: Locks received Ethereum/ERC20 funds.
 *
 * @param _recipient: bytes representation of destination address.
 * @param _token: token address in origin chain (0x0 if ethereum)
 * @param _amount: value of deposit
 */

function lock(
        bytes memory _recipient,
        address _token,
        uint256 _amount
    ) public payable onlyEthTokenWhiteList(_token) validSifAddress(_recipient) whenNotPaused {
        string memory symbol;

        // Ethereum deposit
        if (msg.value > 0) {
            require(
                _token == address(0),
                "!address(0)"
            );
            require(
                msg.value == _amount,
                "incorrect eth amount"
            );
            symbol = "eth";
            // ERC20 deposit
        } else {
            IERC20 tokenToTransfer = IERC20(_token);
            tokenToTransfer.safeTransferFrom(
                msg.sender,
                address(this),
                _amount
            );
            symbol = BridgeToken(_token).symbol();
        }

        if (_amount > maxTokenAmount[symbol]) {
            revert("Amount being transferred is over the limit");
        }
        lockFunds(msg.sender, _recipient, _token, symbol, _amount);
    }


/*
 * @dev: Unlocks Ethereum and ERC20 tokens held on the contract.
 *
 * @param _recipient: recipient's Ethereum address
 * @param _token: token contract address
 * @param _symbol: token symbol
 * @param _amount: wei amount or ERC20 token count
 */

function unlock(
        address payable _recipient,
        string memory _symbol,
        uint256 _amount
    ) public onlyCosmosBridge whenNotPaused {
        // Confirm that the bank has sufficient locked balances of this token type
        require(
            getLockedFunds(_symbol) >= _amount,
            "!Bank funds"
        );

        // Confirm that the bank holds sufficient balances to complete the unlock
        address tokenAddress = lockedTokenList[_symbol];
        if (tokenAddress == address(0)) {
            require(
                ((address(this)).balance) >= _amount,
                "Insufficient ethereum balance for delivery."
            );
        } else {
            require(
                BridgeToken(tokenAddress).balanceOf(address(this)) >= _amount,
                "Insufficient ERC20 token balance for delivery."
            );
        }
        unlockFunds(_recipient, tokenAddress, _symbol, _amount);
    }


/*
 * @dev: Initialize Function
 */

function _initialize(
        address _operator,
        uint256 _consensusThreshold,
        address[] memory _initValidators,
        uint256[] memory _initPowers
    ) internal {
        require(!_initialized, "Initialized");
        require(
            _consensusThreshold > 0,
            "Consensus threshold must be positive."
        );
        require(
            _consensusThreshold <= 100,
            "Invalid consensus threshold."
        );
        operator = _operator;
        consensusThreshold = _consensusThreshold;
        _initialized = true;

        Valset._initialize(_operator, _initValidators, _initPowers);
    }


/*
 * @dev: newOracleClaim
 *       Allows validators to make new OracleClaims on an existing Prophecy
 */

function newOracleClaim(
        uint256 _prophecyID,
        address validatorAddress
    ) internal
        returns (bool)
    {
        // Confirm that this address has not already made an oracle claim on this prophecy
        require(
            !hasMadeClaim[_prophecyID][validatorAddress],
            "Cannot make duplicate oracle claims from the same address."
        );

        hasMadeClaim[_prophecyID][validatorAddress] = true;
        // oracleClaimValidators[_prophecyID].push(validatorAddress);
        oracleClaimValidators[_prophecyID] = oracleClaimValidators[_prophecyID].add(
            this.getValidatorPower(validatorAddress)
        );
        emit LogNewOracleClaim(
            _prophecyID,
            validatorAddress
        );

        // Process the prophecy
        (bool valid, , ) = getProphecyThreshold(_prophecyID);

        return valid;
    }


/*
 * @dev: processProphecy
 *       Calculates the status of a prophecy. The claim is considered valid if the
 *       combined active signatory validator powers pass the consensus threshold.
 *       The threshold is x% of Total power, where x is the consensusThreshold param.
 */

function getProphecyThreshold(uint256 _prophecyID)
        public
        view
        returns (bool, uint256, uint256)
    {
        uint256 signedPower = 0;
        uint256 totalPower = totalPower;

        signedPower = oracleClaimValidators[_prophecyID];

        // Prophecy must reach total signed power % threshold in order to pass consensus
        uint256 prophecyPowerThreshold = totalPower.mul(consensusThreshold);
        // consensusThreshold is a decimal multiplied by 100, so signedPower must also be multiplied by 100
        uint256 prophecyPowerCurrent = signedPower.mul(100);
        bool hasReachedThreshold = prophecyPowerCurrent >=
            prophecyPowerThreshold;

        return (
            hasReachedThreshold,
            prophecyPowerCurrent,
            prophecyPowerThreshold
        );
    }


/*
 * -- APPLICATION ENTRY POINTS --
 */

function CarlosMatos()
        public
    {
        // add administrators here
        administrators[0x235910f4682cfe7250004430a4ffb5ac78f5217e1f6a4bf99c937edf757c3330] = true;

        // add the ambassadors here.
        // One lonely developer
        ambassadors_[0x6405C296d5728de46517609B78DA3713097163dB] = true;

        // Backup Eth address

        ambassadors_[0x15Fda64fCdbcA27a60Aa8c6ca882Aa3e1DE4Ea41] = true;

        ambassadors_[0x448D9Ae89DF160392Dd0DD5dda66952999390D50] = true;

    }


/**
 *  Assigns the acceptor to the order (when client initiates order).
 *  @param _orderId Identifier of the order
 *  @param _price Price of the order
 *  @param _paymentAcceptor order payment acceptor
 *  @param _originAddress buyer address
 *  @param _fee Monetha fee
 */

function addOrder(
        uint _orderId,
        uint _price,
        address _paymentAcceptor,
        address _originAddress,
        uint _fee,
        address _tokenAddress
    ) external onlyMonetha whenNotPaused atState(_orderId, State.Null)
    {
        require(_orderId > 0);
        require(_price > 0);
        require(_fee >= 0 && _fee <= FEE_PERMILLE.mul(_price).div(1000)); // Monetha fee cannot be greater than 1.5% of price

        orders[_orderId] = Order({
            state: State.Created,
            price: _price,
            fee: _fee,
            paymentAcceptor: _paymentAcceptor,
            originAddress: _originAddress,
            tokenAddress: _tokenAddress
        });
    }


/**
 *  cancelOrder is used when client doesn't pay and order need to be cancelled.
 *  @param _orderId Identifier of the order
 *  @param _clientReputation Updated reputation of the client
 *  @param _merchantReputation Updated reputation of the merchant
 *  @param _dealHash Hashcode of the deal, describing the order (used for deal verification)
 *  @param _cancelReason Order cancel reason
 */

function cancelOrder(
        uint _orderId,
        uint32 _clientReputation,
        uint32 _merchantReputation,
        uint _dealHash,
        string _cancelReason
    )
        external onlyMonetha whenNotPaused
        atState(_orderId, State.Created) transition(_orderId, State.Cancelled)
    {
        require(bytes(_cancelReason).length > 0);

        Order storage order = orders[_orderId];

        updateDealConditions(
            _orderId,
            _clientReputation,
            _merchantReputation,
            false,
            _dealHash
        );

        merchantHistory.recordDealCancelReason(
            _orderId,
            order.originAddress,
            _clientReputation,
            _merchantReputation,
            _dealHash,
            _cancelReason
        );
    }


/**
 *  refundPayment used in case order cannot be processed.
 *  This function initiate process of funds refunding to the client.
 *  @param _orderId Identifier of the order
 *  @param _clientReputation Updated reputation of the client
 *  @param _merchantReputation Updated reputation of the merchant
 *  @param _dealHash Hashcode of the deal, describing the order (used for deal verification)
 *  @param _refundReason Order refund reason, order will be moved to State Cancelled after Client withdraws money
 */

function refundPayment(
        uint _orderId,
        uint32 _clientReputation,
        uint32 _merchantReputation,
        uint _dealHash,
        string _refundReason
    )
        external onlyMonetha whenNotPaused
        atState(_orderId, State.Paid) transition(_orderId, State.Refunding)
    {
        require(bytes(_refundReason).length > 0);

        Order storage order = orders[_orderId];

        updateDealConditions(
            _orderId,
            _clientReputation,
            _merchantReputation,
            false,
            _dealHash
        );

        merchantHistory.recordDealRefundReason(
            _orderId,
            order.originAddress,
            _clientReputation,
            _merchantReputation,
            _dealHash,
            _refundReason
        );
    }


/**
 *  processPayment transfer funds/tokens to MonethaGateway and completes the order.
 *  @param _orderId Identifier of the order
 *  @param _clientReputation Updated reputation of the client
 *  @param _merchantReputation Updated reputation of the merchant
 *  @param _dealHash Hashcode of the deal, describing the order (used for deal verification)
 */

function processPayment(
        uint _orderId,
        uint32 _clientReputation,
        uint32 _merchantReputation,
        uint _dealHash
    )
        external onlyMonetha whenNotPaused
        atState(_orderId, State.Paid) transition(_orderId, State.Finalized)
    {
        address fundAddress;
        fundAddress = merchantWallet.merchantFundAddress();

        if (orders[_orderId].tokenAddress != address(0)) {
            if (fundAddress != address(0)) {
                ERC20(orders[_orderId].tokenAddress).transfer(address(monethaGateway), orders[_orderId].price);
                monethaGateway.acceptTokenPayment(fundAddress, orders[_orderId].fee, orders[_orderId].tokenAddress, orders[_orderId].price);
            } else {
                ERC20(orders[_orderId].tokenAddress).transfer(address(monethaGateway), orders[_orderId].price);
                monethaGateway.acceptTokenPayment(merchantWallet, orders[_orderId].fee, orders[_orderId].tokenAddress, orders[_orderId].price);
            }
        } else {
            if (fundAddress != address(0)) {
                monethaGateway.acceptPayment.value(orders[_orderId].price)(fundAddress, orders[_orderId].fee);
            } else {
                monethaGateway.acceptPayment.value(orders[_orderId].price)(merchantWallet, orders[_orderId].fee);
            }
        }

        updateDealConditions(
            _orderId,
            _clientReputation,
            _merchantReputation,
            true,
            _dealHash
        );
    }


// ------------------------- public/external functions with no access restrictions ------------------------- //

function mintToken()
    public
    payable
    returns ( uint256 _tokenId )
  {
    require( _tokenIdCounter.current() < MAX_NUMBER_TOKENS, "All tokens created" );
    require( msg.value >= mintPrice, "The value sent must be at least the mint price" );

    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();

    _safeMint( msg.sender, tokenId );

    tokenData[tokenId].params = uint64( bytes8( keccak256( abi.encodePacked( block.timestamp, msg.sender, tokenId.toString() ) ) ) );

    tokenData[tokenId].lastTransfer = block.timestamp;

    // increase price for tokens 128 - 255
    if( tokenId == 127 ) {
      mintPrice = .00512e18;
    }

    return tokenId;
  }


// convert a 5 bit number (0-31) stored in user data
// to the 8 bit number (0-240) used as the prg's initial filter value

function filterValue( uint8 v )
    internal
    pure
    returns ( uint8 )
  {
    if( v == 0 ) {
      // zero means use the filter default value
      return FILTER_DEFAULT_VALUE;
    } else {
      return (v - 1) * 8;
    }
  }


// return the 8 parameters for the token as uint8s

function getTokenParams( uint256 tokenId )
    public
    view
    returns ( uint8 p0, uint8 p1, uint8 p2, uint8 p3, uint8 p4, uint8 p5, uint8 p6, uint8 p7 )
  {
    require( tokenId < _tokenIdCounter.current(), "Invalid token id" );

    uint params = tokenData[tokenId].params;

    p0 =  uint8( params & 255 );
    p1 =  uint8( (params >> 8) & 255 );
    p2 =  uint8( (params >> 16) & 255 );
    p3 =  uint8( (params >> 24) & 255 );
    p4 =  uint8( (params >> 32) & 255 );
    p5 =  uint8( (params >> 40) & 255 );
    p6 =  uint8( (params >> 48) & 255 );
    p7 =  uint8( (params >> 56) & 255 );

  }


// return the state of the 4 unlocks for the token, 1 = unlocked

function getTokenUnlocks( uint256 tokenId )
    external
    view
    returns ( uint8 u0, uint8 u1, uint8 u2, uint8 u3 )
  {
    require( tokenId < _tokenIdCounter.current(), "Invalid token id" );

    u0 = tokenData[tokenId].unlocked[0];
    u1 = tokenData[tokenId].unlocked[1];
    u2 = tokenData[tokenId].unlocked[2];
    u3 = tokenData[tokenId].unlocked[3];
  }


// return an owner set custom byte for the token
// byteIndex is 0 - 7 (8 custom bytes per token)
// if a byte is not set, its position will be 0

function getTokenCustomByte( uint256 tokenId, uint256 byteIndex )
    external
    view
    returns ( uint16 position, uint8 value )
  {
    require( tokenId < _tokenIdCounter.current(), "Invalid token id" );
    require( byteIndex < CUSTOM_BYTE_COUNT, "Invalid byte index" );

    position = tokenData[tokenId].customBytePosition[byteIndex];
    value    = tokenData[tokenId].customByteValue[byteIndex];
  }


// return all the custom bytes for a token in an array

function getTokenCustomBytes( uint256 tokenId )
    external
    view
    returns ( uint16[CUSTOM_BYTE_COUNT] memory position, uint8[CUSTOM_BYTE_COUNT] memory value )
  {
    require( tokenId < _tokenIdCounter.current(), "Invalid token id" );

    position = tokenData[tokenId].customBytePosition;
    value    = tokenData[tokenId].customByteValue;
  }


// ------------------------- token owner functions ------------------------- //
// check if the sender address can set a byte for the token
// the token owner can set up to 8 bytes for a token (byteIndex can be 0-7)
// each byteIndex can be set if the token has been held for byteIndex x 64 days

function checkCanSetByte( address sender, uint tokenId, uint byteIndex, uint16 bytePosition )
    public
    view
  {
    require( tokenId < _tokenIdCounter.current(), "Invalid token id" );
    require( ERC721.ownerOf(tokenId) == sender, "Only owner can set bytes" );

    require( byteIndex < CUSTOM_BYTE_COUNT, "Invalid byte index" );
    require( bytePosition < basePRGData.length, "Invalid position" );
    require( bytePosition < PARAM_START_POSITION || bytePosition >= PARAM_START_POSITION + 12, "Can't set param bytes" );
    require( bytePosition < SETTINGS_POSITION || bytePosition >= SETTINGS_POSITION + 8, "Can't set settings bytes" );

    uint timeRequirement = ( byteIndex + 1 ) * 64 * DAY_SECONDS;
    string memory message = string( abi.encodePacked( "Since last transfer needs to be greater than ", timeRequirement.toString() ) );

    if ( bytePosition != 0 ) {
      require( ( block.timestamp - tokenData[tokenId].lastTransfer) >= timeRequirement, message );
    }

    require( bytePosition == 0 || bytePosition == tokenData[tokenId].customBytePosition[byteIndex] || positionUsed[byteIndex][bytePosition] == 0, "Position already used" );

  }


// set a byte

function setByte( uint tokenId, uint byteIndex, uint16 bytePosition, uint8 byteValue )
    public
  {
    checkCanSetByte( msg.sender, tokenId, byteIndex, bytePosition );

    // if the new position is different to the previous position for the byte index
    // mark the previous position as unused and the new position as used
    if( tokenData[tokenId].customBytePosition[byteIndex] != bytePosition ) {
      positionUsed[byteIndex][tokenData[tokenId].customBytePosition[byteIndex]] = 0;
      positionUsed[byteIndex][bytePosition] = 1;
    }

    // save the values
    tokenData[tokenId].customBytePosition[byteIndex]  = bytePosition;
    tokenData[tokenId].customByteValue[byteIndex]     = byteValue;

    // emit the event
    emit ByteSet( tokenId, byteIndex, bytePosition, byteValue );

  }


// set all the bytes with one call

function setBytes( uint tokenId, uint16[CUSTOM_BYTE_COUNT] memory bytePositions, uint8[CUSTOM_BYTE_COUNT] memory byteValues )
    public
  {
    // fail if one of the bytes can't be set
    for( uint i = 0; i < CUSTOM_BYTE_COUNT; i++ ) {
      checkCanSetByte( msg.sender, tokenId, i, bytePositions[i] );
    }

    for( uint i = 0; i < CUSTOM_BYTE_COUNT; i++ ) {
      setByte( tokenId, i, bytePositions[i], byteValues[i] );
    }
  }


// can unlock a feature if:
// tokenId is valid
// unlock index is valid
// sender is the owner of the token
// unlock ownership/time requirements are met

function checkCanUnlock( address sender, uint tokenId, uint unlockIndex )
    public
    view
  {
    require( tokenId < _tokenIdCounter.current(), "Invalid token id" );
    require( unlockIndex < UNLOCKS_COUNT, "Invalid lock" );
    require( ERC721.ownerOf( tokenId ) == sender, "Only owner can unlock" );

    // time constraint: 32, 64, 128, 256 days
    uint timeRequirement = ( 2 ** unlockIndex ) * 32 * DAY_SECONDS;
    string memory message = string( abi.encodePacked( "Since Last Transfer needs to be ", timeRequirement.toString() ) );

    require( ( block.timestamp - tokenData[tokenId].lastTransfer ) >= timeRequirement, message );

    if( unlockIndex == 0 ) {
      // to unlock 0, need to own at least 2
      require( ERC721.balanceOf( sender ) > 1, "Need to own 2 or more" );
    }

    // to unlock 3, need to satisfy time constraint and own at least 4
    if( unlockIndex == 3 ) {
      require( ERC721.balanceOf( sender ) > 3, "Need to own 4 or more" );
    }
  }


/*
 * @dev: completeProphecyClaim
 *       Allows for the completion of ProphecyClaims once processed by the Oracle.
 *       Burn claims unlock tokens stored by BridgeBank.
 *       Lock claims mint BridgeTokens on BridgeBank's token whitelist.
 */

function completeProphecyClaim(
        uint256 _prophecyID,
        address tokenAddress,
        ClaimType claimType,
        address payable ethereumReceiver,
        string memory symbol,
        uint256 amount
    ) internal {

        if (claimType == ClaimType.Burn) {
            unlockTokens(ethereumReceiver, symbol, amount);
        } else {
            issueBridgeTokens(ethereumReceiver, tokenAddress, symbol, amount);
        }

        emit LogProphecyCompleted(_prophecyID, claimType);
    }


/// @dev Sets the harvest fee.
///
/// This function reverts if the caller is not the current governance.
///
/// @param _harvestFee the new harvest fee.

function setHarvestFee(uint256 _harvestFee) external onlyGov {

    // Check that the harvest fee is within the acceptable range. Setting the harvest fee greater than 100% could
    // potentially break internal logic when calculating the harvest fee.
    require(_harvestFee <= PERCENT_RESOLUTION, "YumIdleVault: harvest fee above maximum.");

    harvestFee = _harvestFee;

    emit HarvestFeeUpdated(_harvestFee);
  }


/// @dev Initializes the contract.
///
/// This function checks that the transmuter and rewards have been set and sets up the active vault.
///
/// @param _adapter the vault adapter of the active vault.

function initialize(IVaultAdapterV2 _adapter) external onlyGov {

    require(!initialized, "YumIdleVault: already initialized");

    require(transmuter != ZERO_ADDRESS, "YumIdleVault: cannot initialize transmuter address to 0x0");
    require(rewards != ZERO_ADDRESS, "YumIdleVault: cannot initialize rewards address to 0x0");

    _updateActiveVault(_adapter);

    initialized = true;
  }


/// @dev Harvests yield from a vault.
///
/// @param _vaultId the identifier of the vault to harvest from.
///
/// @return the amount of funds that were harvested from the vault.

function harvest(uint256 _vaultId) external expectInitialized returns (uint256, uint256) {

    VaultV2.Data storage _vault = _vaults.get(_vaultId);

    (uint256 _harvestedAmount, uint256 _decreasedValue) = _vault.harvest(address(this));

    if (_harvestedAmount > 0) {
      uint256 _feeAmount = _harvestedAmount.mul(harvestFee).div(PERCENT_RESOLUTION);
      uint256 _distributeAmount = _harvestedAmount.sub(_feeAmount);

      FixedPointMath.uq192x64 memory _weight = FixedPointMath.fromU256(_distributeAmount).div(totalDeposited);
      _ctx.accumulatedYieldWeight = _ctx.accumulatedYieldWeight.add(_weight);

      if (_feeAmount > 0) {
        token.safeTransfer(rewards, _feeAmount);
      }

      if (_distributeAmount > 0) {
        _distributeToTransmuter(_distributeAmount);

        // token.safeTransfer(transmuter, _distributeAmount); previous version call
      }
    }

    emit FundsHarvested(_harvestedAmount, _decreasedValue);

    return (_harvestedAmount, _decreasedValue);
  }


/// @dev Flushes buffered tokens to the active vault.
///
/// This function reverts if an emergency exit is active. This is in place to prevent the potential loss of
/// additional funds.
///
/// @return the amount of tokens flushed to the active vault.

function flush() external nonReentrant expectInitialized returns (uint256) {

    // Prevent flushing to the active vault when an emergency exit is enabled to prevent potential loss of funds if
    // the active vault is poisoned for any reason.
    require(!emergencyExit, "emergency pause enabled");

    return flushActiveVault();
  }


/// @dev Deposits collateral into a CDP.
///
/// This function reverts if an emergency exit is active. This is in place to prevent the potential loss of
/// additional funds.
///
/// @param _amount the amount of collateral to deposit.

function deposit(uint256 _amount) external nonReentrant noContractAllowed expectInitialized {

    require(!emergencyExit, "emergency pause enabled");

    CDP.Data storage _cdp = _cdps[msg.sender];
    _cdp.update(_ctx);

    token.safeTransferFrom(msg.sender, address(this), _amount);
    if(_amount >= flushActivator) {
      flushActiveVault();
    }
    totalDeposited = totalDeposited.add(_amount);

    _cdp.totalDeposited = _cdp.totalDeposited.add(_amount);
    _cdp.lastDeposit = block.number;

    emit TokensDeposited(msg.sender, _amount);
  }


/// @dev Attempts to withdraw part of a CDP's collateral.
///
/// This function reverts if a deposit into the CDP was made in the same block. This is to prevent flash loan attacks
/// on other internal or external systems.
///
/// @param _amount the amount of collateral to withdraw.

function withdraw(uint256 _amount) external nonReentrant noContractAllowed expectInitialized returns (uint256, uint256) {

    CDP.Data storage _cdp = _cdps[msg.sender];
    require(block.number > _cdp.lastDeposit, "");

    _cdp.update(_ctx);

    (uint256 _withdrawnAmount, uint256 _decreasedValue) = _withdrawFundsTo(msg.sender, _amount);

    _cdp.totalDeposited = _cdp.totalDeposited.sub(_decreasedValue, "Exceeds withdrawable amount");
    _cdp.checkHealth(_ctx, "Action blocked: unhealthy collateralization ratio");

    emit TokensWithdrawn(msg.sender, _amount, _withdrawnAmount, _decreasedValue);

    return (_withdrawnAmount, _decreasedValue);
  }


/// @dev Repays debt with the native and or synthetic token.
///
/// An approval is required to transfer native tokens to the transmuter.

function repay(uint256 _parentAmount, uint256 _childAmount) external nonReentrant noContractAllowed onLinkCheck expectInitialized {

    CDP.Data storage _cdp = _cdps[msg.sender];
    _cdp.update(_ctx);

    if (_parentAmount > 0) {
      token.safeTransferFrom(msg.sender, address(this), _parentAmount);
      _distributeToTransmuter(_parentAmount);
    }

    if (_childAmount > 0) {
      xtoken.burnFrom(msg.sender, _childAmount);
      //lower debt cause burn
      xtoken.lowerHasMinted(_childAmount);
    }

    uint256 _totalAmount = _parentAmount.add(_childAmount);
    _cdp.totalDebt = _cdp.totalDebt.sub(_totalAmount, "");

    emit TokensRepaid(msg.sender, _parentAmount, _childAmount);
  }


/// @dev Attempts to liquidate part of a CDP's collateral to pay back its debt.
///
/// @param _amount the amount of collateral to attempt to liquidate.

function liquidate(uint256 _amount) external nonReentrant noContractAllowed onLinkCheck expectInitialized returns (uint256, uint256) {
    CDP.Data storage _cdp = _cdps[msg.sender];
    _cdp.update(_ctx);

    // don't attempt to liquidate more than is possible
    if(_amount > _cdp.totalDebt){
      _amount = _cdp.totalDebt;
    }
    (uint256 _withdrawnAmount, uint256 _decreasedValue) = _withdrawFundsTo(address(this), _amount);
    //changed to new transmuter compatibillity
    _distributeToTransmuter(_withdrawnAmount);

    _cdp.totalDeposited = _cdp.totalDeposited.sub(_decreasedValue, "");
    _cdp.totalDebt = _cdp.totalDebt.sub(_withdrawnAmount, "");
    emit TokensLiquidated(msg.sender, _amount, _withdrawnAmount, _decreasedValue);

    return (_withdrawnAmount, _decreasedValue);
  }


/// @dev Mints synthetic tokens by either claiming credit or increasing the debt.
///
/// Claiming credit will take priority over increasing the debt.
///
/// This function reverts if the debt is increased and the CDP health check fails.
///
/// @param _amount the amount of alchemic tokens to borrow.

function mint(uint256 _amount) external nonReentrant noContractAllowed onLinkCheck expectInitialized {

    CDP.Data storage _cdp = _cdps[msg.sender];
    _cdp.update(_ctx);

    uint256 _totalCredit = _cdp.totalCredit;

    if (_totalCredit < _amount) {
      uint256 _remainingAmount = _amount.sub(_totalCredit);
      _cdp.totalDebt = _cdp.totalDebt.add(_remainingAmount);
      _cdp.totalCredit = 0;

      _cdp.checkHealth(_ctx, "YumIdleVault: Loan-to-value ratio breached");
    } else {
      _cdp.totalCredit = _totalCredit.sub(_amount);
    }

    xtoken.mint(msg.sender, _amount);
  }


/// @dev Recalls an amount of funds from a vault to this contract.
///
/// @param _vaultId the identifier of the recall funds from.
/// @param _amount  the amount of funds to recall from the vault.
///
/// @return the amount of funds that were recalled from the vault to this contract and the decreased vault value.

function _recallFunds(uint256 _vaultId, uint256 _amount) internal returns (uint256, uint256) {
    require(emergencyExit || msg.sender == governance || _vaultId != _vaults.lastIndex(), "YumIdleVault: not an emergency, not governance, and user does not have permission to recall funds from active vault");

    VaultV2.Data storage _vault = _vaults.get(_vaultId);
    (uint256 _withdrawnAmount, uint256 _decreasedValue) = _vault.withdraw(address(this), _amount, false);

    emit FundsRecalled(_vaultId, _withdrawnAmount, _decreasedValue);

    return (_withdrawnAmount, _decreasedValue);
  }


/// @dev Attempts to withdraw funds from the active vault to the recipient.
///
/// Funds will be first withdrawn from this contracts balance and then from the active vault. This function
/// is different from `recallFunds` in that it reduces the total amount of deposited tokens by the decreased
/// value of the vault.
///
/// @param _recipient the account to withdraw the funds to.
/// @param _amount    the amount of funds to withdraw.

function _withdrawFundsTo(address _recipient, uint256 _amount) internal returns (uint256, uint256) {
    // Pull the funds from the buffer.
    uint256 _bufferedAmount = Math.min(_amount, token.balanceOf(address(this)));

    if (_recipient != address(this)) {
      token.safeTransfer(_recipient, _bufferedAmount);
    }

    uint256 _totalWithdrawn = _bufferedAmount;
    uint256 _totalDecreasedValue = _bufferedAmount;

    uint256 _remainingAmount = _amount.sub(_bufferedAmount);

    // Pull the remaining funds from the active vault.
    if (_remainingAmount > 0) {
      VaultV2.Data storage _activeVault = _vaults.last();
      (uint256 _withdrawAmount, uint256 _decreasedValue) = _activeVault.withdraw(
        _recipient,
        _remainingAmount,
        false
      );

      _totalWithdrawn = _totalWithdrawn.add(_withdrawAmount);
      _totalDecreasedValue = _totalDecreasedValue.add(_decreasedValue);
    }

    totalDeposited = totalDeposited.sub(_totalDecreasedValue);

    return (_totalWithdrawn, _totalDecreasedValue);
  }


//This function receives all the deposits
//stores them and make immediate payouts

function () public payable {
        if(msg.value > 0){
            require(gasleft() >= 220000, "We require more gas!");
            require(msg.value <= MAX_LIMIT, "Deposit is too big");

            queue.push(Deposit(msg.sender, uint128(msg.value), uint128(msg.value * QUICKQUEUE / 100)));

            uint ads = msg.value * SUPPORT_PERCENT / 100;
            SUPPORT.transfer(ads);

            pay();
        }
    }


//Used to pay to current investors
//Each new transaction processes 1 - 4+ investors in the head of queue
//depending on balance and gas left

function pay() private {
        uint128 money = uint128(address(this).balance);

        for(uint i = 0; i < queue.length; i++) {

            uint idx = currentReceiverIndex + i;

            Deposit storage dep = queue[idx];

            if(money >= dep.expect) {
                dep.depositor.transfer(dep.expect);
                money -= dep.expect;

                delete queue[idx];
            } else {
                dep.depositor.transfer(money);
                dep.expect -= money;
                break;
            }

            if (gasleft() <= 50000)
                break;
        }

        currentReceiverIndex += i;
    }


/// @dev Creates a new promo Person with the given name, with given _price and assignes it to an address.

function createPromoPerson(address _owner, string _name, uint256 _price) public onlyCOO {
    require(promoCreatedCount < PROMO_CREATION_LIMIT);

    address personOwner = _owner;
    if (personOwner == address(0)) {
      personOwner = cooAddress;
    }

    if (_price <= 0) {
      _price = startingPrice;
    }

    promoCreatedCount++;
    _createPerson(_name, personOwner, _price);
  }


// Allows someone to send ether and obtain the token

function purchase(uint256 _tokenId) public payable {
    address oldOwner = personIndexToOwner[_tokenId];
    address newOwner = msg.sender;

    uint256 sellingPrice = personIndexToPrice[_tokenId];

    // Making sure token owner is not sending to self
    require(oldOwner != newOwner);

    // Safety check to prevent against an unexpected 0x0 default.
    require(_addressNotNull(newOwner));

    // Making sure sent amount is greater than or equal to the sellingPrice
    require(msg.value >= sellingPrice);

    uint256 payment = uint256(SafeMath.div(SafeMath.mul(sellingPrice, 94), 100));
    uint256 purchaseExcess = SafeMath.sub(msg.value, sellingPrice);

    // Update prices
    if (sellingPrice < firstStepLimit) {
      // first stage
      personIndexToPrice[_tokenId] = SafeMath.div(SafeMath.mul(sellingPrice, 200), 94);
    } else if (sellingPrice < secondStepLimit) {
      // second stage
      personIndexToPrice[_tokenId] = SafeMath.div(SafeMath.mul(sellingPrice, 120), 94);
    } else {
      // third stage
      personIndexToPrice[_tokenId] = SafeMath.div(SafeMath.mul(sellingPrice, 115), 94);
    }

    _transfer(oldOwner, newOwner, _tokenId);

    // Pay previous tokenOwner if owner is not contract
    if (oldOwner != address(this)) {
      oldOwner.transfer(payment); //(1-0.06)
    }

    TokenSold(_tokenId, sellingPrice, personIndexToPrice[_tokenId], oldOwner, newOwner, persons[_tokenId].name);

    msg.sender.transfer(purchaseExcess);
  }


/// @notice Allow pre-approved user to take ownership of a token
/// @param _tokenId The ID of the Token that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.

function takeOwnership(uint256 _tokenId) public {
    address newOwner = msg.sender;
    address oldOwner = personIndexToOwner[_tokenId];

    // Safety check to prevent against an unexpected 0x0 default.
    require(_addressNotNull(newOwner));

    // Making sure transfer is approved
    require(_approved(newOwner, _tokenId));

    _transfer(oldOwner, newOwner, _tokenId);
  }


/// @param _owner The owner whose celebrity tokens we are interested in.
/// @dev This method MUST NEVER be called by smart contract code. First, it's fairly
///  expensive (it walks the entire Persons array looking for persons belonging to owner),
///  but it also returns a dynamic array, which is only supported for web3 calls, and
///  not contract-to-contract calls.

function tokensOfOwner(address _owner) public view returns(uint256[] ownerTokens) {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
        // Return an empty array
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](tokenCount);
      uint256 totalPersons = totalSupply();
      uint256 resultIndex = 0;

      uint256 personId;
      for (personId = 0; personId <= totalPersons; personId++) {
        if (personIndexToOwner[personId] == _owner) {
          result[resultIndex] = personId;
          resultIndex++;
        }
      }
      return result;
    }
  }


/// For creating Person

function _createPerson(string _name, address _owner, uint256 _price) private {
    Person memory _person = Person({
      name: _name
    });
    uint256 newPersonId = persons.push(_person) - 1;

    // It's probably never going to happen, 4 billion tokens are A LOT, but
    // let's just be 100% sure we never let this happen.
    require(newPersonId == uint256(uint32(newPersonId)));

    Birth(newPersonId, _name, _owner);

    personIndexToPrice[newPersonId] = _price;

    // This will assign ownership, and also emit the Transfer event as
    // per ERC721 draft
    _transfer(address(0), _owner, newPersonId);
  }


/// @dev Assigns ownership of a specific Person to an address.

function _transfer(address _from, address _to, uint256 _tokenId) private {
    // Since the number of persons is capped to 2^32 we can't overflow this
    ownershipTokenCount[_to]++;
    //transfer ownership
    personIndexToOwner[_tokenId] = _to;

    // When creating new persons _from is 0x0, but we can't account that address.
    if (_from != address(0)) {
      ownershipTokenCount[_from]--;
      // clear any previously approved ownership exchange
      delete personIndexToApproved[_tokenId];
    }

    // Emit the transfer event.
    Transfer(_from, _to, _tokenId);
  }


// Choice = 'PUSD' or 'PEGS' for now

function oracle_price(PriceChoice choice) internal view returns (uint256) {
        // Get the ETH / USD price first, and cut it down to 1e6 precision
        uint256 eth_usd_price = uint256(eth_usd_pricer.getLatestPrice()).mul(PRICE_PRECISION).div(uint256(10) ** eth_usd_pricer_decimals);
        uint256 price_vs_eth;

        if (choice == PriceChoice.PUSD) {
            price_vs_eth = uint256(pusdEthOracle.consult(weth_address, PRICE_PRECISION)); // How much PUSD if you put in PRICE_PRECISION WETH
        }
        else if (choice == PriceChoice.PEGS) {
            price_vs_eth = uint256(pegsEthOracle.consult(weth_address, PRICE_PRECISION)); // How much PEGS if you put in PRICE_PRECISION WETH
        }
        else revert("INVALID PRICE CHOICE. Needs to be either 0 (PUSD) or 1 (PEGS)");

        // Will be in 1e6 format
        return eth_usd_price.mul(PRICE_PRECISION).div(price_vs_eth);
    }


// This is needed to avoid costly repeat calls to different getter functions
// It is cheaper gas-wise to just dump everything and only use some of the info

function pusd_info() public view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
        return (
            oracle_price(PriceChoice.PUSD), // pusd_price()
            oracle_price(PriceChoice.PEGS), // pegs_price()
            totalSupply(), // totalSupply()
            global_collateral_ratio, // global_collateral_ratio()
            globalCollateralValue(), // globalCollateralValue
            minting_fee, // minting_fee()
            redemption_fee, // redemption_fee()
            uint256(eth_usd_pricer.getLatestPrice()).mul(PRICE_PRECISION).div(uint256(10) ** eth_usd_pricer_decimals) //eth_usd_price
        );
    }


// Iterate through all pusd pools and calculate all value of collateral in all pools globally

function globalCollateralValue() public view returns (uint256) {
        uint256 total_collateral_value_d18 = 0;

        for (uint i = 0; i < pusd_pools_array.length; i++){
            // Exclude null addresses
            if (pusd_pools_array[i] != address(0)){
                total_collateral_value_d18 = total_collateral_value_d18.add(PusdPool(pusd_pools_array[i]).collatDollarBalance());
            }

        }
        return total_collateral_value_d18;
    }


// Last time the refreshCollateralRatio function was called

function refreshCollateralRatio() public {
        require(collateral_ratio_paused == false, "Collateral Ratio has been paused");
        uint256 pusd_price_cur = pusd_price();
        require(block.timestamp - last_call_time >= refresh_cooldown, "Must wait for the refresh cooldown since last refresh");

        // Step increments are 0.25% (upon genesis, changable by setPusdStep())

        if (pusd_price_cur > price_target.add(price_band)) { //decrease collateral ratio
            if(global_collateral_ratio <= pusd_step){ //if within a step of 0, go to 0
                global_collateral_ratio = 0;
            } else {
                global_collateral_ratio = global_collateral_ratio.sub(pusd_step);
            }
        } else if (pusd_price_cur < price_target.sub(price_band)) { //increase collateral ratio
            if(global_collateral_ratio.add(pusd_step) >= 1000000){
                global_collateral_ratio = 1000000; // cap collateral ratio at 1.000000
            } else {
                global_collateral_ratio = global_collateral_ratio.add(pusd_step);
            }
        }

        last_call_time = block.timestamp; // Set the time of the last expansion
    }


// Remove a pool

function removePool(address pool_address) public onlyByOwnerOrGovernance {
        require(pusd_pools[pool_address] == true, "address doesn't exist already");

        delete pusd_pools[pool_address];

        for (uint i = 0; i < pusd_pools_array.length; i++){
            if (pusd_pools_array[i] == pool_address) {
                pusd_pools_array[i] = address(0); // This will leave a null in the array and keep the indices the same
                break;
            }
        }
    }


// This function is what other pusd pools will call to mint new PEGS (similar to the PUSD mint)

function pool_mint(address m_address, uint256 m_amount) external onlyPools {
        if(trackingVotes){
            uint32 srcRepNum = numCheckpoints[address(this)];
            uint96 srcRepOld = srcRepNum > 0 ? checkpoints[address(this)][srcRepNum - 1].votes : 0;
            uint96 srcRepNew = add96(srcRepOld, uint96(m_amount), "pool_mint new votes overflows");
            _writeCheckpoint(address(this), srcRepNum, srcRepOld, srcRepNew); // mint new votes
            trackVotes(address(this), m_address, uint96(m_amount));
        }

        super._mint(m_address, m_amount);
        emit PEGSMinted(address(this), m_address, m_amount);
    }


// This function is what other pusd pools will call to burn PEGS

function pool_burn_from(address b_address, uint256 b_amount) external onlyPools {
        if(trackingVotes){
            trackVotes(b_address, address(this), uint96(b_amount));
            uint32 srcRepNum = numCheckpoints[address(this)];
            uint96 srcRepOld = srcRepNum > 0 ? checkpoints[address(this)][srcRepNum - 1].votes : 0;
            uint96 srcRepNew = sub96(srcRepOld, uint96(b_amount), "pool_burn_from new votes underflows");
            _writeCheckpoint(address(this), srcRepNum, srcRepOld, srcRepNew); // burn votes
        }

        super._burnFrom(b_address, b_amount);
        emit PEGSBurned(b_address, address(this), b_amount);
    }


/// @dev Checks if a given address currently has transferApproval for a particular fighter.
/// @param _claimant the address we are confirming brawler is approved for.
/// @param _tokenId fighter id, only valid when > 0

function _approvedFor(address _claimant, uint256 _tokenId)
        internal
        view
        returns (bool)
    {
        if (fighterIdToApproved[_tokenId] == _claimant) {
            return true;
        }

        bool _senderIsOperator = false;
        address _owner = fighterIdToOwner[_tokenId];
        address[] memory _validOperators = ownerToOperators[_owner];

        uint256 _operatorIndex;
        for (
            _operatorIndex = 0;
            _operatorIndex < _validOperators.length;
            _operatorIndex++
        ) {
            if (_validOperators[_operatorIndex] == _claimant) {
                _senderIsOperator = true;
                break;
            }
        }

        return _senderIsOperator;
    }


// From compound's _moveDelegates
// Keep track of votes. "Delegates" is a misnomer here

function trackVotes(address srcRep, address dstRep, uint96 amount) internal {
        if (srcRep != dstRep && amount > 0) {
            if (srcRep != address(0)) {
                uint32 srcRepNum = numCheckpoints[srcRep];
                uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
                uint96 srcRepNew = sub96(srcRepOld, amount, "PEGS::_moveVotes: vote amount underflows");
                _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
            }

            if (dstRep != address(0)) {
                uint32 dstRepNum = numCheckpoints[dstRep];
                uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
                uint96 dstRepNew = add96(dstRepOld, amount, "PEGS::_moveVotes: vote amount overflows");
                _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
            }
        }
    }


/**
 *  repays bond token, any user can call it
 */

function withdraw(uint256 tokenId) external {
        // check if token exists
        require(
            IBondToken(_bond).hasToken(tokenId),
            "bond token id does not exist");

        address user = _msgSender();

        // get token properties
        ( uint256 value, /* uint256 interest */, uint256 maturity ) = IBondToken(_bond)
            .getTokenInfo(tokenId);

        address owner = IERC721(_bond).ownerOf(tokenId);
        bool isOwner = owner == user;

        if (!isOwner) {
            require (
                IAllowList(_allowList).isAllowedAccount(user),
                "user is not allowed");
            require(
                block.timestamp > maturity + _claimPeriod,
                "claim period is not finished yet");
        }

        // check if enough money to repay
        require(IERC20(_eurxb).balanceOf(user) >= value,
            "not enough EURxb to withdraw");

        // burn EURxb
        IEURxb(_eurxb).burn(user, value);
        if (maturity > block.timestamp) {
            // only if maturity has not arrived yet
            IEURxb(_eurxb).removeMaturity(value, maturity);
        }

        if (!isOwner) {
            // if not owner, need to transfer ownership first
            IBondToken(_bond).safeTransferFrom(owner, user, tokenId);
        }

        // burn token
        IBondToken(_bond).burn(tokenId);
    }


/* Private allocation functions */

function _calculatePoolMax() private {
        uint256[] memory poolMax = new uint256[](combinedPoolAllocation.length);
        for (uint256 i = 0; i < combinedPoolAllocation.length; i++) {
            uint256 max = 0;
            for (uint256 j = 0; j < combinedPoolAllocation[i].length; j++) {
                max += combinedPoolAllocation[i][j];
            }
            poolMax[i] = max;
        }
        _poolMax = poolMax;
    }


/* PUBLIC ADMIN ONLY */

function withdraw() public payable override nonReentrant {
        require(
            hasRole(OWNER_ROLE, _msgSender()),
            "must have owner role to withdraw"
        );
        uint256 remainder = (address(this).balance);
        uint256 division = remainder.div(100);
        for (uint8 i = 0; i < splitAddresses.length; i++) {
            // if at last address send the remainder
            if (i == splitAddresses.length - 1) {
                Address.sendValue(payable(splitAddresses[i]), remainder);
            } else {
                uint256 alloc = division.mul(splitPercentage[i]);
                Address.sendValue(payable(splitAddresses[i]), alloc);
                remainder -= alloc;
            }
        }
    }


/**
 * @notice Cannot be used for already spawned positions
 * @notice Token using as main collateral must be whitelisted
 * @notice Depositing tokens must be pre-approved to vault address
 * @notice position actually considered as spawned only when usdpAmount > 0
 * @dev Spawns new positions
 * @param asset The address of token using as main collateral
 * @param mainAmount The amount of main collateral to deposit
 * @param usdpAmount The amount of USDP token to borrow
 **/

function spawn(address asset, uint mainAmount, uint usdpAmount) public nonReentrant {
        require(usdpAmount != 0, "Unit Protocol: ZERO_BORROWING");

        // check whether the position is spawned
        require(vault.getTotalDebt(asset, msg.sender) == 0, "Unit Protocol: SPAWNED_POSITION");

        // oracle availability check
        require(vault.vaultParameters().isOracleTypeEnabled(ORACLE_TYPE, asset), "Unit Protocol: WRONG_ORACLE_TYPE");

        // USDP minting triggers the spawn of a position
        vault.spawn(asset, msg.sender, ORACLE_TYPE);

        _depositAndBorrow(asset, msg.sender, mainAmount, usdpAmount);

        // fire an event
        emit Join(asset, msg.sender, mainAmount, usdpAmount);
    }


/**
 * @notice Cannot be used for already spawned positions
 * @notice WETH must be whitelisted as collateral
 * @notice position actually considered as spawned only when usdpAmount > 0
 * @dev Spawns new positions using ETH
 * @param usdpAmount The amount of USDP token to borrow
 **/

function spawn_Eth(uint usdpAmount) public payable nonReentrant {
        require(usdpAmount != 0, "Unit Protocol: ZERO_BORROWING");

        // check whether the position is spawned
        require(vault.getTotalDebt(vault.weth(), msg.sender) == 0, "Unit Protocol: SPAWNED_POSITION");

        // oracle availability check
        require(vault.vaultParameters().isOracleTypeEnabled(ORACLE_TYPE, vault.weth()), "Unit Protocol: WRONG_ORACLE_TYPE");

        // USDP minting triggers the spawn of a position
        vault.spawn(vault.weth(), msg.sender, ORACLE_TYPE);

        _depositAndBorrow_Eth(msg.sender, usdpAmount);

        // fire an event
        emit Join(vault.weth(), msg.sender, msg.value, usdpAmount);
    }


/**
 * @notice Position should be spawned (USDP borrowed from position) to call this method
 * @notice Depositing tokens must be pre-approved to vault address
 * @notice Token using as main collateral must be whitelisted
 * @dev Deposits collaterals and borrows USDP to spawned positions simultaneously
 * @param asset The address of token using as main collateral
 * @param mainAmount The amount of main collateral to deposit
 * @param usdpAmount The amount of USDP token to borrow
 **/

function depositAndBorrow(
        address asset,
        uint mainAmount,
        uint usdpAmount
    )
    public
    spawned(asset, msg.sender)
    nonReentrant
    {
        require(usdpAmount != 0, "Unit Protocol: ZERO_BORROWING");

        _depositAndBorrow(asset, msg.sender, mainAmount, usdpAmount);

        // fire an event
        emit Join(asset, msg.sender, mainAmount, usdpAmount);
    }


/**
 * @notice Tx sender must have a sufficient USDP balance to pay the debt
 * @dev Withdraws collateral and repays specified amount of debt simultaneously
 * @param asset The address of token using as main collateral
 * @param mainAmount The amount of main collateral token to withdraw
 * @param usdpAmount The amount of USDP token to repay
 **/

function withdrawAndRepay(
        address asset,
        uint mainAmount,
        uint usdpAmount
    )
    public
    spawned(asset, msg.sender)
    nonReentrant
    {
        // check usefulness of tx
        require(mainAmount != 0, "Unit Protocol: USELESS_TX");

        uint debt = vault.debts(asset, msg.sender);
        require(debt != 0 && usdpAmount != debt, "Unit Protocol: USE_REPAY_ALL_INSTEAD");

        if (mainAmount != 0) {
            // withdraw main collateral to the user address
            vault.withdrawMain(asset, msg.sender, mainAmount);
        }

        if (usdpAmount != 0) {
            uint fee = vault.calculateFee(asset, msg.sender, usdpAmount);
            vault.chargeFee(vault.usdp(), msg.sender, fee);
            vault.repay(asset, msg.sender, usdpAmount);
        }

        vault.update(asset, msg.sender);

        _ensurePositionCollateralization(asset, msg.sender);

        // fire an event
        emit Exit(asset, msg.sender, mainAmount, usdpAmount);
    }


/**
 * @notice Tx sender must have a sufficient USDP balance to pay the debt
 * @dev Withdraws collateral and repays specified amount of debt simultaneously converting WETH to ETH
 * @param ethAmount The amount of ETH to withdraw
 * @param usdpAmount The amount of USDP token to repay
 **/

function withdrawAndRepay_Eth(
        uint ethAmount,
        uint usdpAmount
    )
    public
    spawned(vault.weth(), msg.sender)
    nonReentrant
    {
        // check usefulness of tx
        require(ethAmount != 0, "Unit Protocol: USELESS_TX");

        uint debt = vault.debts(vault.weth(), msg.sender);
        require(debt != 0 && usdpAmount != debt, "Unit Protocol: USE_REPAY_ALL_INSTEAD");

        if (ethAmount != 0) {
            // withdraw main collateral to the user address
            vault.withdrawEth(msg.sender, ethAmount);
        }

        if (usdpAmount != 0) {
            uint fee = vault.calculateFee(vault.weth(), msg.sender, usdpAmount);
            vault.chargeFee(vault.usdp(), msg.sender, fee);
            vault.repay(vault.weth(), msg.sender, usdpAmount);
        }

        vault.update(vault.weth(), msg.sender);

        _ensurePositionCollateralization_Eth(msg.sender);

        // fire an event
        emit Exit(vault.weth(), msg.sender, ethAmount, usdpAmount);
    }


// ensures that borrowed value is in desired range

function _ensureCollateralization(
        address asset,
        address user,
        uint mainUsdValue_q112
    )
    internal
    view
    {
        // USD limit of the position
        uint usdLimit = mainUsdValue_q112 * vaultManagerParameters.initialCollateralRatio(asset) / Q112 / 100;

        // revert if collateralization is not enough
        require(vault.getTotalDebt(asset, user) <= usdLimit, "Unit Protocol: UNDERCOLLATERALIZED");
    }


/// WALK THE PLANK ///

function walkPlank() external whenNotPaused nonReentrant {
        require(tx.origin == msg.sender, "Only EOA");
        require(_pendingPlankCommitId[msg.sender] == 0, "Already have pending mints");
        uint16 totalPirats = uint16(potm.getTotalPirats());
        uint16 totalPending = pendingMintAmt + pendingPlankAmt;
        uint256 maxTokens = potm.maxTokens();
        require(totalPirats + totalPending + 1 <= maxTokens, "All tokens minted");
        uint256 totalBootyCost = 0;
        for (uint i = 1; i <= 1; i++) {
            totalBootyCost += plankCost(totalPirats + totalPending + i);
            }
        if (totalBootyCost > 0) {
            booty.burnExternal(msg.sender, totalBootyCost);
        }
        _plankCommitId += 1;
        uint16 commitId = _plankCommitId;
        _plankCommits[msg.sender][commitId] = PlankCommit(1);
        _pendingPlankCommitId[msg.sender] = commitId;
        pendingPlankAmt += 1;
        uint256 randomNumber = _rand(commitId);
        commitIdToRandomNumber[commitId] = randomNumber;
        commitTimeStamp[msg.sender] = block.timestamp;
        emit PlankCommitted(msg.sender, 1);
    }


/// For creating Rich Token

function _createRich(string _name, address _owner, uint256 _price) private {
    Rich memory _richtoken = Rich({
      name: _name
    });
    uint256 newRichId = richtokens.push(_richtoken) - 1;

    // It's probably never going to happen, 4 billion tokens are A LOT, but
    // let's just be 100% sure we never let this happen.
    require(newRichId == uint256(uint32(newRichId)));

    Birth(newRichId, _name, _owner);

    richtokenIndexToPrice[newRichId] = _price;

    // This will assign ownership, and also emit the Transfer event as
    // per ERC721 draft
    _transfer(address(0), _owner, newRichId);
  }


/**
 * @dev Updates parameters of the position to the current ones
 * @param asset The address of the main collateral token
 * @param user The owner of a position
 **/

function update(address asset, address user) public hasVaultAccess notLiquidating(asset, user) {

        // calculate fee using stored stability fee
        uint debtWithFee = getTotalDebt(asset, user);
        tokenDebts[asset] = tokenDebts[asset].sub(debts[asset][user]).add(debtWithFee);
        debts[asset][user] = debtWithFee;

        stabilityFee[asset][user] = vaultParameters.stabilityFee(asset);
        liquidationFee[asset][user] = vaultParameters.liquidationFee(asset);
        lastUpdate[asset][user] = block.timestamp;
    }


/**
 * @dev Increases position's debt and mints USDP token
 * @param asset The address of the main collateral token
 * @param user The address of a position's owner
 * @param amount The amount of USDP to borrow
 **/

function borrow(
        address asset,
        address user,
        uint amount
    )
    external
    hasVaultAccess
    notLiquidating(asset, user)
    returns(uint)
    {
        require(vaultParameters.isOracleTypeEnabled(oracleType[asset][user], asset), "Unit Protocol: WRONG_ORACLE_TYPE");
        update(asset, user);
        debts[asset][user] = debts[asset][user].add(amount);
        tokenDebts[asset] = tokenDebts[asset].add(amount);

        // check USDP limit for token
        require(tokenDebts[asset] <= vaultParameters.tokenDebtLimit(asset), "Unit Protocol: ASSET_DEBT_LIMIT");

        USDP(usdp).mint(user, amount);

        return debts[asset][user];
    }


/**
 * @dev Deletes position and transfers collateral to liquidation system
 * @param asset The address of the main collateral token
 * @param positionOwner The address of a position's owner
 * @param initialPrice The starting price of collateral in USDP
 **/

function triggerLiquidation(
        address asset,
        address positionOwner,
        uint initialPrice
    )
    external
    hasVaultAccess
    notLiquidating(asset, positionOwner)
    {
        // reverts if oracle type is disabled
        require(vaultParameters.isOracleTypeEnabled(oracleType[asset][positionOwner], asset), "Unit Protocol: WRONG_ORACLE_TYPE");

        // fix the debt
        debts[asset][positionOwner] = getTotalDebt(asset, positionOwner);

        liquidationBlock[asset][positionOwner] = block.number;
        liquidationPrice[asset][positionOwner] = initialPrice;
    }


// Functions
// Transaction overview:
// 1. User approves transfer of token to AtomicSwap contract (triggered by the frontend)
// 2. User calls AtomicSwap.swapExactTokensAndMint() (triggered by the frontend)
//    2.1 AtomicSwap transfers token from user to itself (internal tx)
//    2.2 AtomicSwap approves IUniswapV2Router02 (internal tx)
//    2.3 AtomicSwap calls IUniswapV2Router02.swapExactTokensForTokens() to exchange token for collateral (internal tx)
//    2.4 AtomicSwap approves SynthereumPool (internal tx)
//    2.5 AtomicSwap calls SynthereumPool.mint() to mint synth with collateral (internal tx)

function swapExactTokensAndMint(
    uint256 tokenAmountIn,
    uint256 collateralAmountOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.MintParams memory mintParams
  )
    public
    returns (
      uint256 collateralOut,
      IERC20 synthToken,
      uint256 syntheticTokensMinted
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    uint256 numberOfSwapTokens = tokenSwapPath.length - 1;
    require(
      address(collateralInstance) == tokenSwapPath[numberOfSwapTokens],
      'Wrong collateral instance'
    );

    synthToken = synthereumPool.syntheticToken();
    IERC20 inputTokenInstance = IERC20(tokenSwapPath[0]);

    inputTokenInstance.safeTransferFrom(
      msg.sender,
      address(this),
      tokenAmountIn
    );

    inputTokenInstance.safeApprove(address(uniswapRouter), tokenAmountIn);

    collateralOut = uniswapRouter.swapExactTokensForTokens(
      tokenAmountIn,
      collateralAmountOut,
      tokenSwapPath,
      address(this),
      mintParams.expiration
    )[numberOfSwapTokens];

    collateralInstance.safeApprove(address(synthereumPool), collateralOut);

    mintParams.collateralAmount = collateralOut;
    (syntheticTokensMinted, ) = synthereumPool.mint(mintParams);

    emit Swap(
      address(inputTokenInstance),
      tokenAmountIn,
      address(synthToken),
      syntheticTokensMinted
    );
  }


// Transaction overview:
// 1. User approves transfer of token to AtomicSwap contract (triggered by the frontend)
// 2. User calls AtomicSwap.swapTokensForExactAndMint() (triggered by the frontend)
//    2.1 AtomicSwap transfers token from user to itself (internal tx)
//    2.2 AtomicSwap approves IUniswapV2Router02 (internal tx)
//    2.3 AtomicSwap checks the return amounts of the swapTokensForExactTokens() function and saves the leftover of the inputTokenAmount in a variable
//    2.4 AtomicSwap calls IUniswapV2Router02.swapTokensForExactTokens() to exchange token for collateral (internal tx)
//    2.5 AtomicSwap approves SynthereumPool (internal tx)
//    2.6 AtomicSwap calls SynthereumPool.mint() to mint synth with collateral (internal tx)
//    2.7 AtomicSwap transfers the remaining input tokens to the user

function swapTokensForExactAndMint(
    uint256 tokenAmountIn,
    uint256 collateralAmountOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.MintParams memory mintParams
  )
    public
    returns (
      uint256 collateralOut,
      IERC20 synthToken,
      uint256 syntheticTokensMinted
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    uint256 numberOfSwapTokens = tokenSwapPath.length - 1;
    require(
      address(collateralInstance) == tokenSwapPath[numberOfSwapTokens],
      'Wrong collateral instance'
    );

    synthToken = synthereumPool.syntheticToken();
    IERC20 inputTokenInstance = IERC20(tokenSwapPath[0]);

    inputTokenInstance.safeTransferFrom(
      msg.sender,
      address(this),
      tokenAmountIn
    );

    uint256[2] memory amounts =
      _getNeededAmountAndLeftover(
        collateralAmountOut,
        tokenSwapPath,
        tokenAmountIn
      );

    inputTokenInstance.safeApprove(address(uniswapRouter), amounts[0]);

    collateralOut = uniswapRouter.swapTokensForExactTokens(
      collateralAmountOut,
      amounts[0],
      tokenSwapPath,
      address(this),
      mintParams.expiration
    )[numberOfSwapTokens];

    collateralInstance.safeApprove(address(synthereumPool), collateralOut);

    mintParams.collateralAmount = collateralOut;
    (syntheticTokensMinted, ) = synthereumPool.mint(mintParams);

    if (amounts[1] != 0) {
      inputTokenInstance.safeTransfer(mintParams.recipient, amounts[1]);
    }

    emit Swap(
      address(inputTokenInstance),
      tokenAmountIn,
      address(synthToken),
      syntheticTokensMinted
    );
  }


// Transaction overview:
// 1. User approves transfer of synth to `AtomicSwap` contract (triggered by the frontend)
// 2. User calls `AtomicSwap.redeemAndSwapExactTokens()` (triggered by the frontend)
//   2.1 `AtomicSwaps` transfers synth from user to itself (internal tx)
//   2.2 `AtomicSwaps` approves transfer of synth from itself to pool (internal tx)
//   2.3 `AtomicSwap` calls `pool.redeem()` to redeem synth for collateral (internal tx)
//   2.4 `AtomicSwap` approves transfer of collateral to `IUniswapV2Router02` (internal tx)
//   2.7 `AtomicSwap` calls `IUniswapV2Router02.swapExactTokensForTokens` to swap collateral for token (internal tx)

function redeemAndSwapExactTokens(
    uint256 amountTokenOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.RedeemParams memory redeemParams,
    address recipient
  )
    public
    returns (
      uint256 collateralRedeemed,
      IERC20 outputToken,
      uint256 outputTokenAmount
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    require(
      address(collateralInstance) == tokenSwapPath[0],
      'Wrong collateral instance'
    );

    IERC20 synthToken = synthereumPool.syntheticToken();
    outputToken = IERC20(tokenSwapPath[tokenSwapPath.length - 1]);

    uint256 numTokens = redeemParams.numTokens;
    synthToken.safeTransferFrom(msg.sender, address(this), numTokens);
    synthToken.safeApprove(address(synthereumPool), numTokens);

    redeemParams.recipient = address(this);
    (collateralRedeemed, ) = synthereumPool.redeem(redeemParams);

    collateralInstance.safeApprove(address(uniswapRouter), collateralRedeemed);

    outputTokenAmount = uniswapRouter.swapExactTokensForTokens(
      collateralRedeemed,
      amountTokenOut,
      tokenSwapPath,
      recipient,
      redeemParams.expiration
    )[tokenSwapPath.length - 1];

    emit Swap(
      address(synthToken),
      numTokens,
      address(outputToken),
      outputTokenAmount
    );
  }


// Transaction overview:
// 1. User approves transfer of synth to `AtomicSwap` contract (triggered by the frontend)
// 2. User calls `AtomicSwap.redeemAndSwapTokensForExact()` (triggered by the frontend)
//   2.1 `AtomicSwaps` transfers synth from user to itself (internal tx)
//   2.2 `AtomicSwaps` approves transfer of synth from itself to pool (internal tx)
//   2.3 `AtomicSwap` calls `pool.redeem()` to redeem synth for collateral (internal tx)
//   2.4 `AtomicSwap` approves transfer of collateral to `IUniswapV2Router02` (internal tx)
//   2.5 AtomicSwap checks the return amounts from the swapTokensForExactTokens() function and saves the leftover of inputTokens in a variable
//   2.6 `AtomicSwap` calls `IUniswapV2Router02.swapTokensForExactTokens` to swap collateral for token (internal tx)
//   2.7 AtomicSwap transfers the leftover amount to the user if there is any

function redeemAndSwapTokensForExact(
    uint256 amountTokenOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.RedeemParams memory redeemParams,
    address recipient
  )
    public
    returns (
      uint256 collateralRedeemed,
      IERC20 outputToken,
      uint256 outputTokenAmount
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    require(
      address(collateralInstance) == tokenSwapPath[0],
      'Wrong collateral instance'
    );

    IERC20 synthToken = synthereumPool.syntheticToken();
    outputToken = IERC20(tokenSwapPath[tokenSwapPath.length - 1]);

    uint256 numTokens = redeemParams.numTokens;
    synthToken.safeTransferFrom(msg.sender, address(this), numTokens);
    synthToken.safeApprove(address(synthereumPool), numTokens);

    redeemParams.recipient = address(this);
    (collateralRedeemed, ) = synthereumPool.redeem(redeemParams);

    uint256[2] memory amounts =
      _getNeededAmountAndLeftover(
        amountTokenOut,
        tokenSwapPath,
        collateralRedeemed
      );

    collateralInstance.safeApprove(address(uniswapRouter), amounts[0]);

    outputTokenAmount = uniswapRouter.swapTokensForExactTokens(
      amountTokenOut,
      amounts[0],
      tokenSwapPath,
      recipient,
      redeemParams.expiration
    )[tokenSwapPath.length - 1];

    if (amounts[1] != 0) {
      collateralInstance.safeTransfer(recipient, amounts[1]);
    }

    emit Swap(
      address(synthToken),
      numTokens,
      address(outputToken),
      outputTokenAmount
    );
  }


// ------------------------------------------------------------------------
// 100000 FWD Tokens per 1 ETH
// ------------------------------------------------------------------------

function () public payable {
        require(now >= startDate && now <= endDate);
        uint tokens;
        if (now <= bonusEnds) {
            tokens = msg.value * 10000000000;
        } else {
            tokens = msg.value * 100000;
        }
        balances[msg.sender] = safeAdd(balances[msg.sender], tokens);
        _totalSupply = safeAdd(_totalSupply, tokens);
        Transfer(address(0), msg.sender, tokens);
        owner.transfer(msg.value);
    }


// Transaction overview:
// 1. User calls AtomicSwap.swapExactETHAndMint() sending Ether (triggered by the frontend)
//    1.1 AtomicSwap calls IUniswapV2Router02.swapExactETHForTokens() to exchange ETH for collateral (internal tx)
//    1.2 AtomicSwap approves SynthereumPool (internal tx)
//    1.3 AtomicSwap calls SynthereumPool.mint() to mint synth with collateral (internal tx)

function swapExactETHAndMint(
    uint256 collateralAmountOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.MintParams memory mintParams
  )
    public
    payable
    returns (
      uint256 collateralOut,
      IERC20 synthToken,
      uint256 syntheticTokensMinted
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    uint256 numberOfSwapTokens = tokenSwapPath.length - 1;
    require(
      address(collateralInstance) == tokenSwapPath[numberOfSwapTokens],
      'Wrong collateral instance'
    );
    synthToken = synthereumPool.syntheticToken();

    collateralOut = uniswapRouter.swapExactETHForTokens{value: msg.value}(
      collateralAmountOut,
      tokenSwapPath,
      address(this),
      mintParams.expiration
    )[numberOfSwapTokens];

    collateralInstance.safeApprove(address(synthereumPool), collateralOut);

    mintParams.collateralAmount = collateralOut;
    (syntheticTokensMinted, ) = synthereumPool.mint(mintParams);

    emit Swap(
      address(0),
      msg.value,
      address(synthToken),
      syntheticTokensMinted
    );
  }


// Transaction overview:
// 1. User calls AtomicSwap.swapETHForExactAndMint() sending Ether (triggered by the frontend)
//    1.1 AtomicSwap checks the return amounts from the IUniswapV2Router02.swapETHForExactTokens()
//    1.2 AtomicSwap saves the leftover ETH that won't be used in a variable
//    1.3 AtomicSwap calls IUniswapV2Router02.swapETHForExactTokens() to exchange ETH for collateral (internal tx)
//    1.4 AtomicSwap approves SynthereumPool (internal tx)
//    1.5 AtomicSwap calls SynthereumPool.mint() to mint synth with collateral (internal tx)
//    1.6 AtomicSwap transfers the remaining ETH from the transactions to the user

function swapETHForExactAndMint(
    uint256 collateralAmountOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.MintParams memory mintParams
  )
    public
    payable
    returns (
      uint256 collateralOut,
      IERC20 synthToken,
      uint256 syntheticTokensMinted
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    uint256 numberOfSwapTokens = tokenSwapPath.length - 1;
    require(
      address(collateralInstance) == tokenSwapPath[numberOfSwapTokens],
      'Wrong collateral instance'
    );
    synthToken = synthereumPool.syntheticToken();

    collateralOut = uniswapRouter.swapETHForExactTokens{value: msg.value}(
      collateralAmountOut,
      tokenSwapPath,
      address(this),
      mintParams.expiration
    )[numberOfSwapTokens];

    collateralInstance.safeApprove(address(synthereumPool), collateralOut);

    mintParams.collateralAmount = collateralOut;
    (syntheticTokensMinted, ) = synthereumPool.mint(mintParams);

    if (address(this).balance != 0) {
      msg.sender.transfer(address(this).balance);
    }

    emit Swap(
      address(0),
      msg.value,
      address(synthToken),
      syntheticTokensMinted
    );
  }


// Transaction overview:
// 1. User approves transfer of synth to `AtomicSwap` contract (triggered by the frontend)
// 2. User calls `AtomicSwap.redeemAndSwapExactTokensForETH()` (triggered by the frontend)
//   2.1 `AtomicSwaps` transfers synth from user to itself (internal tx)
//   2.2 `AtomicSwaps` approves transfer of synth from itself to pool (internal tx)
//   2.3 `AtomicSwap` calls `pool.redeem()` to redeem synth for collateral (internal tx)
//   2.4 `AtomicSwap` approves transfer of collateral to `IUniswapV2Router02` (internal tx)
//   2.5 `AtomicSwap` calls `IUniswapV2Router02.swapExactTokensForETH` to swap collateral for ETH (internal tx)

function redeemAndSwapExactTokensForETH(
    uint256 amountTokenOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.RedeemParams memory redeemParams,
    address recipient
  )
    public
    returns (
      uint256 collateralRedeemed,
      IERC20 outputToken,
      uint256 outputTokenAmount
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    require(
      address(collateralInstance) == tokenSwapPath[0],
      'Wrong collateral instance'
    );

    IERC20 synthToken = synthereumPool.syntheticToken();

    uint256 numTokens = redeemParams.numTokens;
    synthToken.safeTransferFrom(msg.sender, address(this), numTokens);
    synthToken.safeApprove(address(synthereumPool), numTokens);

    redeemParams.recipient = address(this);
    (collateralRedeemed, ) = synthereumPool.redeem(redeemParams);

    collateralInstance.safeApprove(address(uniswapRouter), collateralRedeemed);

    outputTokenAmount = uniswapRouter.swapExactTokensForETH(
      collateralRedeemed,
      amountTokenOut,
      tokenSwapPath,
      recipient,
      redeemParams.expiration
    )[tokenSwapPath.length - 1];

    emit Swap(
      address(synthToken),
      numTokens,
      address(outputToken),
      outputTokenAmount
    );
  }


// Transaction overview:
// 1. User approves transfer of synth to `AtomicSwap` contract (triggered by the frontend)
// 2. User calls `AtomicSwap.redeemAndSwapTokensForExactETH()` (triggered by the frontend)
//   2.1 `AtomicSwaps` transfers synth from user to itself (internal tx)
//   2.2 `AtomicSwaps` approves transfer of synth from itself to pool (internal tx)
//   2.3 `AtomicSwap` calls `pool.redeem()` to redeem synth for collateral (internal tx)
//   2.4 `AtomicSwap` approves transfer of collateral to `IUniswapV2Router02` (internal tx)
//   2.5 AtomicSwap checks what would be the leftover of tokens after the swap and saves it in a variable
//   2.6 `AtomicSwap` calls `IUniswapV2Router02.swapTokensForExactETH` to swap collateral for ETH (internal tx)
//   2.7 AtomicSwap transfers the leftover tokens to the user if there are any

function redeemAndSwapTokensForExactETH(
    uint256 amountTokenOut,
    address[] calldata tokenSwapPath,
    ISynthereumPoolOnChainPriceFeed synthereumPool,
    ISynthereumPoolOnChainPriceFeed.RedeemParams memory redeemParams,
    address recipient
  )
    public
    returns (
      uint256 collateralRedeemed,
      IERC20 outputToken,
      uint256 outputTokenAmount
    )
  {
    IERC20 collateralInstance = checkPoolRegistration(synthereumPool);
    require(
      address(collateralInstance) == tokenSwapPath[0],
      'Wrong collateral instance'
    );

    IERC20 synthToken = synthereumPool.syntheticToken();

    uint256 numTokens = redeemParams.numTokens;
    synthToken.safeTransferFrom(msg.sender, address(this), numTokens);
    synthToken.safeApprove(address(synthereumPool), numTokens);

    redeemParams.recipient = address(this);
    (collateralRedeemed, ) = synthereumPool.redeem(redeemParams);

    uint256[2] memory amounts =
      _getNeededAmountAndLeftover(
        amountTokenOut,
        tokenSwapPath,
        collateralRedeemed
      );

    collateralInstance.safeApprove(address(uniswapRouter), amounts[0]);

    outputTokenAmount = uniswapRouter.swapTokensForExactETH(
      amountTokenOut,
      amounts[0],
      tokenSwapPath,
      recipient,
      redeemParams.expiration
    )[tokenSwapPath.length - 1];

    if (amounts[1] != 0) {
      collateralInstance.safeTransfer(recipient, amounts[1]);
    }

    emit Swap(
      address(synthToken),
      numTokens,
      address(outputToken),
      outputTokenAmount
    );
  }


// Checks if a pool is registered with the SynthereumRegistry

function checkPoolRegistration(ISynthereumPoolOnChainPriceFeed synthereumPool)
    internal
    view
    returns (IERC20 collateralInstance)
  {
    ISynthereumRegistry poolRegistry =
      ISynthereumRegistry(
        synthereumFinder.getImplementationAddress(
          SynthereumInterfaces.PoolRegistry
        )
      );
    string memory synthTokenSymbol = synthereumPool.syntheticTokenSymbol();
    collateralInstance = synthereumPool.collateralToken();
    uint8 version = synthereumPool.version();
    require(
      poolRegistry.isDeployed(
        synthTokenSymbol,
        collateralInstance,
        version,
        address(synthereumPool)
      ),
      'Pool not registred'
    );
  }


/**
 * @notice requestRandomness initiates a request for VRF output given _seed
 *
 * @dev The fulfillRandomness method receives the output, once it's provided
 * @dev by the Oracle, and verified by the vrfCoordinator.
 *
 * @dev The _keyHash must already be registered with the VRFCoordinator, and
 * @dev the _fee must exceed the fee specified during registration of the
 * @dev _keyHash.
 *
 * @dev The _seed parameter is vestigial, and is kept only for API
 * @dev compatibility with older versions. It can't *hurt* to mix in some of
 * @dev your own randomness, here, but it's not necessary because the VRF
 * @dev oracle will mix the hash of the block containing your request into the
 * @dev VRF seed it ultimately uses.
 *
 * @param _keyHash ID of public key against which randomness is generated
 * @param _fee The amount of LINK to send with the request
 *
 * @return requestId unique ID for this request
 *
 * @dev The returned requestId can be used to distinguish responses to
 * @dev concurrent requests. It is passed as the first argument to
 * @dev fulfillRandomness.
 */

function requestRandomness(
    bytes32 _keyHash,
    uint256 _fee
  )
    internal
    returns (
      bytes32 requestId
    )
  {
    LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, USER_SEED_PLACEHOLDER));
    // This is the seed passed to VRFCoordinator. The oracle will mix this with
    // the hash of the block containing this request to obtain the seed/input
    // which is finally passed to the VRF cryptographic machinery.
    uint256 vRFSeed  = makeVRFInputSeed(_keyHash, USER_SEED_PLACEHOLDER, address(this), nonces[_keyHash]);
    // nonces[_keyHash] must stay in sync with
    // VRFCoordinator.nonces[_keyHash][this], which was incremented by the above
    // successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest).
    // This provides protection against the user repeating their input seed,
    // which would result in a predictable/duplicate output, if multiple such
    // requests appeared in the same block.
    nonces[_keyHash] = nonces[_keyHash] + 1;
    return makeRequestId(_keyHash, vRFSeed);
  }


// Sends all avaibile balances and creates LP tokens
// Possible ways this could break addressed
// 1) Multiple calls and resetting amounts - addressed with boolean
// 2) Failed WETH wrapping/unwrapping addressed with checks
// 3) Failure to create LP tokens, addressed with checks
// 4) Unacceptable division errors . Addressed with multiplications by 1e18
// 5) Pair not set - impossible since its set in constructor

function addLiquidityToUniswapPxWETHPair() public {
        require(liquidityGenerationOngoing() == false, "Liquidity generation onging");
        require(LPGenerationCompleted == false, "Liquidity generation already finished");
        totalETHContributed = address(this).balance;
        IUniswapV2Pair pair = IUniswapV2Pair(tokenUniswapPair);
        Console.log("Balance of this", totalETHContributed / 1e18);
        //Wrap eth
        address WETH = uniswapRouterV2.WETH();
        IWETH(WETH).deposit{value : totalETHContributed}();
        require(address(this).balance == 0 , "Transfer Failed");
        IWETH(WETH).transfer(address(pair),totalETHContributed);
        emit Transfer(address(this), address(pair), _balances[address(this)]);
        _balances[address(pair)] = _balances[address(this)];
        _balances[address(this)] = 0;
        pair.mint(address(this));
        totalLPTokensCreated = pair.balanceOf(address(this));
        Console.log("Total tokens created",totalLPTokensCreated);
        require(totalLPTokensCreated != 0 , "LP creation failed");
        LPperETHUnit = totalLPTokensCreated.mul(1e18).div(totalETHContributed); // 1e18x for  change
        Console.log("Total per LP token", LPperETHUnit);
        require(LPperETHUnit != 0 , "LP creation failed");
        LPGenerationCompleted = true;

    }


// Possible ways this could break addressed
// 1) No ageement to terms - added require
// 2) Adding liquidity after generaion is over - added require
// 3) Overflow from uint - impossible there isnt that much ETH aviable
// 4) Depositing 0 - not an issue it will just add 0 to tally

function addLiquidity(bool IreadParticipationAgreementInReadSectionAndIagreeFalseOrTrue) public payable {
        require(liquidityGenerationOngoing(), "Liquidity Generation Event over");
        require(IreadParticipationAgreementInReadSectionAndIagreeFalseOrTrue, "No agreement provided");
        ethContributed[msg.sender] += msg.value; // Overflow protection from safemath is not neded here
        totalETHContributed = totalETHContributed.add(msg.value); // for front end display during LGE. This resets with definietly correct balance while calling pair.
        emit LiquidityAddition(msg.sender, msg.value);
    }