TradePairs.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.3;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";

import "../library/Bytes32Library.sol";
import "../library/StringLibrary.sol";

import "../interfaces/IPortfolio.sol";
import "../interfaces/ITradePairs.sol";

import "./OrderBooks.sol";

contract TradePairs is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable, ITradePairs {
    using EnumerableSetUpgradeable for EnumerableSetUpgradeable.UintSet;
    using StringLibrary for string;
    using Bytes32Library for bytes32;

    // denominator for rate calculations
    uint constant public TENK = 10000;

    // order counter to build a unique handle for each new order
    uint private orderCounter;

    // a dynamic array of trade pairs added to TradePairs contract
    bytes32[] private tradePairsArray;


    struct TradePair {
        bytes32 baseSymbol;          // symbol for base asset
        bytes32 quoteSymbol;         // symbol for quote asset
        bytes32 buyBookId;           // identifier for the buyBook for TradePair
        bytes32 sellBookId;          // identifier for the sellBook for TradePair
        uint minTradeAmount;         // min trade for a TradePair expressed as amount = (price * quantity) / (10 ** quoteDecimals)
        uint maxTradeAmount;         // max trade for a TradePair expressed as amount = (price * quantity) / (10 ** quoteDecimals)
        uint makerRate;              // numerator for maker fee rate % to be used with a denominator of 10000
        uint takerRate;              // numerator for taker fee rate % to be used with a denominator of 10000
        uint8 baseDecimals;          // decimals for base asset
        uint8 baseDisplayDecimals;   // display decimals for base asset
        uint8 quoteDecimals;         // decimals for quote asset
        uint8 quoteDisplayDecimals;  // display decimals for quote asset
        uint8 allowedSlippagePercent;// numerator for allowed slippage rate % to be used with denominator 100
        bool addOrderPaused;         // boolean to control addOrder functionality per TradePair
        bool pairPaused;             // boolean to control addOrder and cancelOrder functionality per TradePair
    }

    // mapping data structure for all trade pairs
    mapping (bytes32 => TradePair) private tradePairMap;

    // mapping  for allowed order types for a TradePair
    mapping (bytes32 => EnumerableSetUpgradeable.UintSet) private allowedOrderTypes;

    // mapping structure for all orders
    mapping (bytes32 => Order) private orderMap;

    // reference to OrderBooks contract (one sell or buy book)
    OrderBooks private orderBooks;

    // reference Portfolio contract
    IPortfolio private portfolio;

    event NewTradePair(bytes32 pair, uint8 basedisplaydecimals, uint8 quotedisplaydecimals, uint mintradeamount, uint maxtradeamount);

    event OrderStatusChanged(address indexed traderaddress, bytes32 indexed pair, bytes32 id,  uint price, uint totalamount, uint quantity,
                             Side side, Type1 type1, Status status, uint quantityfilled, uint totalfee);

    event Executed(bytes32 indexed pair, uint price, uint quantity, bytes32 maker, bytes32 taker, uint feeMaker, uint feeTaker, bool feeMakerBase, uint execId);

    event ParameterUpdated(bytes32 indexed pair, string param, uint oldValue, uint newValue);

    function initialize(address _orderbooks, address _portfolio) public initializer {
        __Ownable_init();
        orderCounter = block.timestamp;
        orderBooks = OrderBooks(_orderbooks);
        portfolio = IPortfolio(_portfolio);
    }

    function addTradePair(bytes32 _tradePairId,
                          bytes32 _baseSymbol, uint8 _baseDecimals, uint8 _baseDisplayDecimals,
                          bytes32 _quoteSymbol, uint8 _quoteDecimals,  uint8 _quoteDisplayDecimals,
                          uint _minTradeAmount, uint _maxTradeAmount) public override onlyOwner {

        if (tradePairMap[_tradePairId].baseSymbol == '') {
            EnumerableSetUpgradeable.UintSet storage enumSet = allowedOrderTypes[_tradePairId];
            enumSet.add(uint(Type1.LIMIT)); // LIMIT orders always allowed
            //enumSet.add(uint(Type1.MARKET));  // trade pairs are added without MARKET orders

            bytes32 _buyBookId = string(abi.encodePacked(_tradePairId.bytes32ToString(), '-BUYBOOK')).stringToBytes32();
            bytes32 _sellBookId = string(abi.encodePacked(_tradePairId.bytes32ToString(), '-SELLBOOK')).stringToBytes32();

            tradePairMap[_tradePairId].baseSymbol = _baseSymbol;
            tradePairMap[_tradePairId].baseDecimals = _baseDecimals;
            tradePairMap[_tradePairId].baseDisplayDecimals = _baseDisplayDecimals;
            tradePairMap[_tradePairId].quoteSymbol = _quoteSymbol;
            tradePairMap[_tradePairId].quoteDecimals = _quoteDecimals;
            tradePairMap[_tradePairId].quoteDisplayDecimals = _quoteDisplayDecimals;
            tradePairMap[_tradePairId].minTradeAmount = _minTradeAmount;
            tradePairMap[_tradePairId].maxTradeAmount = _maxTradeAmount;
            tradePairMap[_tradePairId].buyBookId = _buyBookId;
            tradePairMap[_tradePairId].sellBookId = _sellBookId;
            tradePairMap[_tradePairId].makerRate = 10; // makerRate=10 (0.10% = 10/10000)
            tradePairMap[_tradePairId].takerRate = 20; // takerRate=20 (0.20% = 20/10000)
            tradePairMap[_tradePairId].allowedSlippagePercent = 0; // allowedSlippagePercent=20 (20% = 20/100) market orders can't be filled worst than 80% of the bestBid / 120% of bestAsk
            // tradePairMap[_tradePairId].addOrderPaused = false;   // addOrder is not paused by default (EVM initializes to false)
            // tradePairMap[_tradePairId].pairPaused = false;       // pair is not paused by default (EVM initializes to false)

            enumSet.add(uint(Type1.LIMITFOK));  // LIMITFOK orders allowed only when not in auction mode

            tradePairsArray.push(_tradePairId);

            emit NewTradePair(_tradePairId, _baseDisplayDecimals, _quoteDisplayDecimals, _minTradeAmount, _maxTradeAmount);
        }
    }

    // FRONTEND FUNCTION TO GET A LIST OF TRADE PAIRS
    function getTradePairs() public override view returns (bytes32[] memory) {
        return tradePairsArray;
    }

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

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

    function pauseTradePair(bytes32 _tradePairId, bool _pairPaused) public override onlyOwner {
        tradePairMap[_tradePairId].pairPaused = _pairPaused;
    }

    function pauseAddOrder(bytes32 _tradePairId, bool _addOrderPaused) public override onlyOwner {
        tradePairMap[_tradePairId].addOrderPaused = _addOrderPaused;
    }

    function setMinTradeAmount(bytes32 _tradePairId, uint _minTradeAmount) public override onlyOwner {
        uint oldValue = tradePairMap[_tradePairId].minTradeAmount;
        tradePairMap[_tradePairId].minTradeAmount = _minTradeAmount;
        emit ParameterUpdated(_tradePairId, "T-MINTRAMT", oldValue, _minTradeAmount);
    }

    function getMinTradeAmount(bytes32 _tradePairId) public override view returns (uint) {
        return tradePairMap[_tradePairId].minTradeAmount;
    }

    function setMaxTradeAmount(bytes32 _tradePairId, uint _maxTradeAmount) public override onlyOwner {
        uint oldValue = tradePairMap[_tradePairId].maxTradeAmount;
        tradePairMap[_tradePairId].maxTradeAmount = _maxTradeAmount;
        emit ParameterUpdated(_tradePairId, "T-MAXTRAMT", oldValue, _maxTradeAmount);
    }

    function getMaxTradeAmount(bytes32 _tradePairId) public override view returns (uint) {
        return tradePairMap[_tradePairId].maxTradeAmount;
    }

    function addOrderType(bytes32 _tradePairId, Type1 _type) public override onlyOwner {
        allowedOrderTypes[_tradePairId].add(uint(_type));
        emit ParameterUpdated(_tradePairId, "T-OTYPADD", 0, uint(_type));
    }

    function removeOrderType(bytes32 _tradePairId, Type1 _type) public override onlyOwner {
        require(_type != Type1.LIMIT, "T-LONR-01");
        allowedOrderTypes[_tradePairId].remove(uint(_type));
        emit ParameterUpdated(_tradePairId, "T-OTYPREM", 0, uint(_type));
    }

    function getAllowedOrderTypes(bytes32 _tradePairId) public view returns (uint[] memory) {
        uint size = allowedOrderTypes[_tradePairId].length();
        uint[] memory allowed = new uint[](size);
        for (uint i=0; i<size; i++) {
            allowed[i] = allowedOrderTypes[_tradePairId].at(i);
        }
        return allowed;
    }

    function setDisplayDecimals(bytes32 _tradePairId, uint8 _displayDecimals, bool _isBase) public override onlyOwner {
        uint oldValue = tradePairMap[_tradePairId].baseDisplayDecimals;
        if (_isBase) {
            tradePairMap[_tradePairId].baseDisplayDecimals = _displayDecimals;
        } else {
            oldValue = tradePairMap[_tradePairId].quoteDisplayDecimals;
            tradePairMap[_tradePairId].quoteDisplayDecimals = _displayDecimals;
        }
        emit ParameterUpdated(_tradePairId, "T-DISPDEC", oldValue, _displayDecimals);
    }

    function getDisplayDecimals(bytes32 _tradePairId, bool _isBase) public override view returns (uint8) {
        if (_isBase) {
            return tradePairMap[_tradePairId].baseDisplayDecimals;
        } else {
            return tradePairMap[_tradePairId].quoteDisplayDecimals;
        }
    }

    function getDecimals(bytes32 _tradePairId, bool _isBase) public override view returns (uint8) {
        if (_isBase) {
            return tradePairMap[_tradePairId].baseDecimals;
        } else {
            return tradePairMap[_tradePairId].quoteDecimals;
        }
    }

    function getSymbol(bytes32 _tradePairId, bool _isBase) public override view returns (bytes32) {
        if (_isBase) {
            return tradePairMap[_tradePairId].baseSymbol;
        } else {
            return tradePairMap[_tradePairId].quoteSymbol;
        }
    }

    function updateRate(bytes32 _tradePairId, uint _rate, ITradePairs.RateType _rateType) public override onlyOwner {
        uint oldValue = tradePairMap[_tradePairId].makerRate;
        if (_rateType == ITradePairs.RateType.MAKER) {
            tradePairMap[_tradePairId].makerRate = _rate; // (_rate/100)% = _rate/10000: _rate=10 => 0.10%
            emit ParameterUpdated(_tradePairId, "T-MAKERRATE", oldValue, _rate);
        } else if (_rateType == ITradePairs.RateType.TAKER) {
            oldValue = tradePairMap[_tradePairId].takerRate;
            tradePairMap[_tradePairId].takerRate = _rate; // (_rate/100)% = _rate/10000: _rate=20 => 0.20%
            emit ParameterUpdated(_tradePairId, "T-TAKERRATE", oldValue, _rate);
        } // Ignore the rest for now
    }

    function getMakerRate(bytes32 _tradePairId) public view override returns (uint) {
        return tradePairMap[_tradePairId].makerRate;
    }

    function getTakerRate(bytes32 _tradePairId) public view override returns (uint) {
        return tradePairMap[_tradePairId].takerRate;
    }

    function setAllowedSlippagePercent(bytes32 _tradePairId, uint8 _allowedSlippagePercent) public override onlyOwner {
        uint oldValue = tradePairMap[_tradePairId].allowedSlippagePercent;
        tradePairMap[_tradePairId].allowedSlippagePercent = _allowedSlippagePercent;
        emit ParameterUpdated(_tradePairId, "T-SLIPPAGE", oldValue, _allowedSlippagePercent);
    }

    function getAllowedSlippagePercent(bytes32 _tradePairId) public override view returns (uint8) {
        return tradePairMap[_tradePairId].allowedSlippagePercent;
    }

    function getNSellBook(bytes32 _tradePairId, uint nPrice, uint nOrder, uint lastPrice, bytes32 lastOrder)
                public view override returns (uint[] memory, uint[] memory, uint , bytes32) {
        // get lowest (_type=0) N orders
        return orderBooks.getNOrders(tradePairMap[_tradePairId].sellBookId, nPrice, nOrder, lastPrice, lastOrder,  0);
    }

    function getNBuyBook(bytes32 _tradePairId, uint nPrice, uint nOrder, uint lastPrice, bytes32 lastOrder)
                public view override returns (uint[] memory, uint[] memory, uint , bytes32) {
        // get highest (_type=1) N orders
        return orderBooks.getNOrders(tradePairMap[_tradePairId].buyBookId, nPrice, nOrder, lastPrice, lastOrder, 1);
    }

    function getOrder(bytes32 _orderId) public view override returns (Order memory) {
        return orderMap[_orderId];
    }

    function getOrderId() private returns (bytes32) {
        return keccak256(abi.encodePacked(orderCounter++));
    }

    // get remaining quantity for an Order struct - cheap pure function
    function getRemainingQuantity(Order memory _order) private pure returns (uint) {
        return _order.quantity - _order.quantityFilled;
    }

    // get quote amount
    function getQuoteAmount(bytes32 _tradePairId, uint _price, uint _quantity) private view returns (uint) {
      return  (_price * _quantity) / 10 ** tradePairMap[_tradePairId].baseDecimals;
    }

    function emitStatusUpdate(bytes32 _tradePairId, bytes32 _orderId) private {
        Order storage _order = orderMap[_orderId];
        emit OrderStatusChanged(_order.traderaddress, _tradePairId, _order.id,
                                _order.price, _order.totalAmount, _order.quantity, _order.side,
                                _order.type1, _order.status, _order.quantityFilled,  _order.totalFee);
    }

    //Used to Round Up the auction price interval small restrictions
    // example: a = 1245, m: 100 ==> 1300
    function ceil(uint a, uint m) pure public returns (uint) {
        return ((a + m - 1) / m) * m;
    }

    //Used to Round Down the fees to the display decimals to avoid dust
    //Used to Round Down the auction price interval to avoid small restrictions
    // example: a = 1245, m: 2 ==> 1200
    function floor(uint a, uint m) pure public returns (uint) {
        return (a / 10 ** m) * 10 ** m;
    }

    function handleExecution(bytes32 _tradePairId, bytes32 _orderId, uint _price, uint _quantity, uint _rate) private returns (uint) {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        Order storage _order = orderMap[_orderId];
        require(_order.status != Status.CANCELED, "T-OACA-01");
        _order.quantityFilled += _quantity;
        require(_order.quantityFilled <= _order.quantity, "T-CQFA-01");
        _order.status = _order.quantity == _order.quantityFilled ? Status.FILLED : Status.PARTIAL;
        uint amount = getQuoteAmount(_tradePairId, _price, _quantity);
        _order.totalAmount += amount;
        //Rounding Down the fee based on display decimals to avoid DUST
//        uint lastFeeRounded = _order.side == Side.BUY ?
//                floor(_quantity * _rate / TENK, _tradePair.baseDecimals - _tradePair.baseDisplayDecimals) :
//                floor(amount * _rate / TENK, _tradePair.quoteDecimals - _tradePair.quoteDisplayDecimals);
        uint lastFeeRounded = _order.side == Side.BUY ? _quantity * _rate / TENK : amount * _rate / TENK;
        _order.totalFee += lastFeeRounded;
        return lastFeeRounded;
    }

    function addExecution(bytes32 _tradePairId, Order memory _makerOrder, Order memory _takerOrder, uint _price, uint _quantity) private {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        // fill the maker first so it is out of the book quickly
        uint mlastFee = handleExecution(_tradePairId, _makerOrder.id, _price, _quantity, _tradePair.makerRate); // also updates the order status
        uint tlastFee = handleExecution(_tradePairId, _takerOrder.id, _price, _quantity, _tradePair.takerRate); // also updates the order status
        portfolio.addExecution(_makerOrder, _takerOrder.traderaddress, _tradePair.baseSymbol, _tradePair.quoteSymbol, _quantity,
                               getQuoteAmount(_tradePairId, _price, _quantity), mlastFee, tlastFee);
        emit Executed(_tradePairId, _price, _quantity, _makerOrder.id, _takerOrder.id, mlastFee, tlastFee, _makerOrder.side == Side.BUY ? true : false, orderCounter++);

        emitStatusUpdate(_tradePairId, _makerOrder.id); // EMIT maker order's status update
    }

    function decimalsOk(uint value, uint8 decimals, uint8 displayDecimals) private pure returns (bool) {
        return (value - (value - ((value % 10**decimals) % 10**(decimals - displayDecimals)))) == 0;
    }

    // FRONTEND ENTRY FUNCTION TO CALL TO ADD ORDER
    function addOrder(bytes32 _tradePairId, uint _price, uint _quantity, Side _side, Type1 _type1) public override nonReentrant whenNotPaused {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        require(!_tradePair.pairPaused, "T-PPAU-01");
        require(!_tradePair.addOrderPaused, "T-AOPA-01");
        require(allowedOrderTypes[_tradePairId].contains(uint(_type1)), "T-IVOT-01");

        require(decimalsOk(_quantity, _tradePair.baseDecimals, _tradePair.baseDisplayDecimals), "T-TMDQ-01");

        if (_type1 == Type1.LIMIT  || _type1 == Type1.LIMITFOK ) {
            addLimitOrder(_tradePairId, _price, _quantity, _side, _type1);
        } else if (_type1 == Type1.MARKET) {
            addMarketOrder(_tradePairId, _quantity, _side);
        }
    }

    function addMarketOrder(bytes32 _tradePairId, uint _quantity, Side _side) private {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        uint marketPrice;
        uint worstPrice; // Market Orders will be filled up to allowedSlippagePercent from the marketPrice to protect the trader, the remaining qty gets unsolicited cancel
        bytes32 bookId;
        if (_side == Side.BUY) {
            bookId = _tradePair.sellBookId;
            marketPrice = orderBooks.first(bookId);
            worstPrice = marketPrice * (100 + _tradePair.allowedSlippagePercent) / 100;
        } else {
            bookId = _tradePair.buyBookId;
            marketPrice = orderBooks.last(bookId);
            worstPrice = marketPrice * (100 - _tradePair.allowedSlippagePercent) / 100;
        }

        // don't need digit check here as it is taken from the book
        uint tradeAmnt = (marketPrice * _quantity) / (10 ** _tradePair.baseDecimals);
        // a market order will be rejected here if there is nothing in the book because marketPrice will be 0
        require(tradeAmnt >= _tradePair.minTradeAmount, "T-LTMT-01");
        require(tradeAmnt <= _tradePair.maxTradeAmount, "T-MTMT-01");

        bytes32 orderId = getOrderId();
        Order storage _order = orderMap[orderId];
        _order.id = orderId;
        _order.traderaddress= msg.sender;
        _order.price = worstPrice; // setting the price to the worst price so it can be filled up to this price given enough qty
        _order.quantity = _quantity;
        _order.side = _side;
        //_order.quantityFilled = 0;     // evm intialized
        //_order.totalAmount = 0;        // evm intialized
        //_order.type1 = _type1;         // evm intialized to MARKET
        //_order.status = Status.NEW;    // evm intialized
        //_order.totalFee = 0;           // evm intialized;

        uint takerRemainingQuantity;
        if (_side == Side.BUY) {
            takerRemainingQuantity= matchSellBook(_tradePairId, _order);
        } else {  // == Order.Side.SELL
            takerRemainingQuantity= matchBuyBook(_tradePairId, _order);
        }

        if (!orderBooks.orderListExists(bookId, worstPrice)
                && takerRemainingQuantity > 0) {
            // IF the Market Order fills all the way to the worst price, it gets KILLED for the remaining amount.
            orderMap[_order.id].status = Status.KILLED;
        }
//        _order.price = 0; //Reset the market order price back to 0
        emitStatusUpdate(_tradePairId, _order.id);  // EMIT taker(potential) order status. if no fills, the status will be NEW, if not status will be either PARTIAL or FILLED
    }

    function addLimitOrder(bytes32 _tradePairId, uint _price, uint _quantity, Side _side, Type1 _type1) private {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        require(decimalsOk(_price, _tradePair.quoteDecimals, _tradePair.quoteDisplayDecimals), "T-TMDP-01");
        uint tradeAmnt = (_price * _quantity) / (10 ** _tradePair.baseDecimals);
        require(tradeAmnt >= _tradePair.minTradeAmount, "T-LTMT-02");
        require(tradeAmnt <= _tradePair.maxTradeAmount, "T-MTMT-02");

        bytes32 orderId = getOrderId();
        Order storage _order = orderMap[orderId];
        _order.id = orderId;
        _order.traderaddress = msg.sender;
        _order.price = _price;
        _order.quantity = _quantity;
        _order.side = _side;
        _order.type1 = _type1;
        //_order.totalAmount= 0;         // evm intialized
        //_order.quantityFilled= 0;      // evm intialized
        //_order.status= Status.NEW;     // evm intialized
        //_order.totalFee= 0;            // evm intialized


        if (_side == Side.BUY) {
            _quantity = matchSellBook(_tradePairId, _order);
            // Unfilled Limit Orders Go in the Orderbook (Including Auction Orders)
            if (_quantity > 0  && _type1 == Type1.LIMIT) {
                orderBooks.addOrder(_tradePair.buyBookId, _order.id, _order.price);
                portfolio.adjustAvailable(IPortfolio.Tx.DECREASEAVAIL, _order.traderaddress, _tradePair.quoteSymbol,
                    getQuoteAmount(_tradePairId, _price, _quantity));
            }
        } else {  // == Order.Side.SELL
            _quantity = matchBuyBook(_tradePairId, _order);
            // Unfilled Limit Orders Go in the Orderbook (Including Auction Orders)
            if (_quantity > 0 && _type1 == Type1.LIMIT) {
                orderBooks.addOrder(_tradePair.sellBookId, _order.id, _order.price);
                portfolio.adjustAvailable(IPortfolio.Tx.DECREASEAVAIL, _order.traderaddress, _tradePair.baseSymbol, _quantity);
            }
        }
        if (_type1 == Type1.LIMITFOK && _quantity > 0) {
            _order.status = Status.KILLED;
        }
        emitStatusUpdate(_tradePairId, _order.id);  // EMIT order status. if no fills, the status will be NEW, if any fills status will be either PARTIAL or FILLED
    }

    function matchSellBook(bytes32 _tradePairId, Order memory takerOrder) private returns (uint) {
        bytes32 sellBookId = tradePairMap[_tradePairId].sellBookId;
        uint price = orderBooks.first(sellBookId);
        bytes32 head = orderBooks.getHead(sellBookId, price);
        Order memory makerOrder;
        uint quantity;
        //Don't need price > 0 check as sellBook.getHead(price) != '' takes care of it
        while ( getRemainingQuantity(takerOrder) > 0 && head != '' && (takerOrder.price >=  price || takerOrder.type1 == Type1.MARKET)) {
            makerOrder = getOrder(head);
            quantity = orderBooks.matchTrade(sellBookId, price, getRemainingQuantity(takerOrder), getRemainingQuantity(makerOrder));
            addExecution(_tradePairId, makerOrder, takerOrder, price, quantity); // this makes a state change to Order Map
            takerOrder.quantityFilled += quantity;  // locally keep track of Qty remaining
            price = orderBooks.first(sellBookId);
            head = orderBooks.getHead(sellBookId, price);
        }
        return getRemainingQuantity(takerOrder);
    }

    function matchBuyBook(bytes32 _tradePairId, Order memory takerOrder) private returns (uint) {
        bytes32 buyBookId = tradePairMap[_tradePairId].buyBookId;
        uint price = orderBooks.last(buyBookId);
        bytes32 head = orderBooks.getHead(buyBookId, price);
        Order memory makerOrder;
        uint quantity;
        //Don't need price > 0 check as buyBook.getHead(price) != '' takes care of it
        while (getRemainingQuantity(takerOrder) > 0 && head != '' && (takerOrder.price <=  price || takerOrder.type1 == Type1.MARKET)) {
            makerOrder = getOrder(head);
            quantity = orderBooks.matchTrade(buyBookId, price, getRemainingQuantity(takerOrder), getRemainingQuantity(makerOrder));
            addExecution(_tradePairId, makerOrder, takerOrder, price, quantity); // this makes a state change to Order Map
            takerOrder.quantityFilled += quantity;  // locally keep track of Qty remaining
            price = orderBooks.last(buyBookId);
            head = orderBooks.getHead(buyBookId, price);
        }
        return getRemainingQuantity(takerOrder);
    }

    function doOrderCancel(bytes32 _tradePairId, bytes32 _orderId) private {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        Order storage _order = orderMap[_orderId];
        _order.status = Status.CANCELED;
        if (_order.side == Side.BUY) {
            orderBooks.cancelOrder(_tradePair.buyBookId, _orderId, _order.price);
            portfolio.adjustAvailable(IPortfolio.Tx.INCREASEAVAIL, _order.traderaddress, _tradePair.quoteSymbol,
                                      getQuoteAmount(_tradePairId, _order.price, getRemainingQuantity(_order)));
        } else {
            orderBooks.cancelOrder(_tradePair.sellBookId, _orderId, _order.price);
            portfolio.adjustAvailable(IPortfolio.Tx.INCREASEAVAIL, _order.traderaddress, _tradePair.baseSymbol, getRemainingQuantity(_order));
        }
        emitStatusUpdate(_tradePairId, _order.id);
    }

    // FRONTEND ENTRY FUNCTION TO CALL TO CANCEL ONE ORDER
    function cancelOrder(bytes32 _tradePairId, bytes32 _orderId) public override nonReentrant whenNotPaused {
        Order storage _order = orderMap[_orderId];
        require(_order.id != '', "T-EOID-01");
        require(_order.traderaddress == msg.sender, "T-OOCC-01");
        require(_order.quantityFilled < _order.quantity && (_order.status == Status.PARTIAL || _order.status== Status.NEW), "T-OAEX-01");
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        require(!_tradePair.pairPaused, "T-PPAU-02");
        doOrderCancel(_tradePairId, _order.id);
    }

    // FRONTEND ENTRY FUNCTION TO CALL TO CANCEL A DYNAMIC LIST OF ORDERS
    // THIS FUNCTION MAY RUN OUT OF GAS FOR FOR A TRADER TRYING TO CANCEL MANY ORDERS
    // CALL MAXIMUM 20 ORDERS AT A TIME
    function cancelAllOrders(bytes32 _tradePairId, bytes32[] memory _orderIds) public override nonReentrant whenNotPaused {
        TradePair storage _tradePair = tradePairMap[_tradePairId];
        require(!_tradePair.pairPaused, "T-PPAU-03");
        for (uint i=0; i<_orderIds.length; i++) {
            Order storage _order = orderMap[_orderIds[i]];
            require(_order.traderaddress == msg.sender, "T-OOCC-02");
            if (_order.id != '' && _order.quantityFilled < _order.quantity && (_order.status == Status.PARTIAL || _order.status== Status.NEW)) {
                doOrderCancel(_tradePairId, _order.id);
            }
        }
    }

    fallback() external {}
}

Last updated