public ExchangeOrder(ExchangeOrder order, BigInteger newPrice, BigInteger newOrderSize)
        {
            Uid       = order.Uid;
            Timestamp = order.Timestamp;
            Creator   = order.Creator;

            Amount     = newOrderSize;
            BaseSymbol = order.BaseSymbol;

            Price       = newOrderSize;
            QuoteSymbol = order.QuoteSymbol;

            Side = order.Side;
            Type = order.Type;
        }
Example #2
0
        public ExchangeOrder GetExchangeOrder(BigInteger uid)
        {
            Runtime.Expect(_orderMap.ContainsKey <BigInteger>(uid), "order not found");

            var         key       = _orderMap.Get <BigInteger, string>(uid);
            StorageList orderList = _orders.Get <string, StorageList>(key);

            var count = orderList.Count();
            var order = new ExchangeOrder();

            for (int i = 0; i < count; i++)
            {
                order = orderList.Get <ExchangeOrder>(i);
                if (order.Uid == uid)
                {
                    //Runtime.Expect(IsWitness(order.Creator), "invalid witness");
                    break;
                }
            }

            return(order);
        }
Example #3
0
        private void OpenOrder(Address from, Address provider, string baseSymbol, string quoteSymbol, ExchangeOrderSide side, ExchangeOrderType orderType, BigInteger orderSize, BigInteger price)
        {
            Runtime.Expect(IsWitness(from), "invalid witness");

            Runtime.Expect(Runtime.GasTarget == provider, "invalid gas target");

            Runtime.Expect(baseSymbol != quoteSymbol, "invalid base/quote pair");

            Runtime.Expect(Runtime.Nexus.TokenExists(baseSymbol), "invalid base token");
            var baseToken = Runtime.Nexus.GetTokenInfo(baseSymbol);

            Runtime.Expect(baseToken.Flags.HasFlag(TokenFlags.Fungible), "token must be fungible");

            Runtime.Expect(Runtime.Nexus.TokenExists(quoteSymbol), "invalid quote token");
            var quoteToken = Runtime.Nexus.GetTokenInfo(quoteSymbol);

            Runtime.Expect(quoteToken.Flags.HasFlag(TokenFlags.Fungible), "token must be fungible");

            if (orderType != Market)
            {
                Runtime.Expect(orderSize >= GetMinimumTokenQuantity(baseToken), "order size is not sufficient");
                Runtime.Expect(price >= GetMinimumTokenQuantity(quoteToken), "order price is not sufficient");
            }

            var uid = Runtime.Chain.GenerateUID(this.Storage);

            //--------------
            //perform escrow for non-market orders
            string     orderEscrowSymbol = CalculateEscrowSymbol(baseToken, quoteToken, side);
            TokenInfo  orderEscrowToken  = orderEscrowSymbol == baseSymbol ? baseToken : quoteToken;
            BigInteger orderEscrowAmount;
            BigInteger orderEscrowUsage = 0;

            if (orderType == Market)
            {
                orderEscrowAmount = orderSize;
                Runtime.Expect(orderEscrowAmount >= GetMinimumTokenQuantity(orderEscrowToken), "market order size is not sufficient");
            }
            else
            {
                orderEscrowAmount = CalculateEscrowAmount(orderSize, price, baseToken, quoteToken, side);
            }

            //BigInteger baseTokensUnfilled = orderSize;

            var balances = new BalanceSheet(orderEscrowSymbol);
            var balance  = balances.Get(this.Storage, from);

            Runtime.Expect(balance >= orderEscrowAmount, "not enough balance");

            Runtime.Expect(Runtime.Nexus.TransferTokens(Runtime, orderEscrowSymbol, from, this.Address, orderEscrowAmount), "transfer failed");
            //------------

            var         thisOrder = new ExchangeOrder();
            StorageList orderList;
            BigInteger  orderIndex = 0;

            thisOrder = new ExchangeOrder(uid, Runtime.Time, from, provider, orderSize, baseSymbol, price, quoteSymbol, side, orderType);
            Runtime.Notify(EventKind.OrderCreated, from, uid);

            var key = BuildOrderKey(side, quoteSymbol, baseSymbol);

            orderList  = _orders.Get <string, StorageList>(key);
            orderIndex = orderList.Add <ExchangeOrder>(thisOrder);
            _orderMap.Set <BigInteger, string>(uid, key);

            var makerSide   = side == Buy ? Sell : Buy;
            var makerKey    = BuildOrderKey(makerSide, quoteSymbol, baseSymbol);
            var makerOrders = _orders.Get <string, StorageList>(makerKey);

            do
            {
                int        bestIndex          = -1;
                BigInteger bestPrice          = 0;
                Timestamp  bestPriceTimestamp = 0;

                ExchangeOrder takerOrder = thisOrder;

                var makerOrdersCount = makerOrders.Count();
                for (int i = 0; i < makerOrdersCount; i++)
                {
                    var makerOrder = makerOrders.Get <ExchangeOrder>(i);

                    if (side == Buy)
                    {
                        if (makerOrder.Price > takerOrder.Price && orderType != Market) // too expensive, we wont buy at this price
                        {
                            continue;
                        }

                        if (bestIndex == -1 || makerOrder.Price < bestPrice || (makerOrder.Price == bestPrice && makerOrder.Timestamp < bestPriceTimestamp))
                        {
                            bestIndex          = i;
                            bestPrice          = makerOrder.Price;
                            bestPriceTimestamp = makerOrder.Timestamp;
                        }
                    }
                    else
                    {
                        if (makerOrder.Price < takerOrder.Price && orderType != Market) // too cheap, we wont sell at this price
                        {
                            continue;
                        }

                        if (bestIndex == -1 || makerOrder.Price > bestPrice || (makerOrder.Price == bestPrice && makerOrder.Timestamp < bestPriceTimestamp))
                        {
                            bestIndex          = i;
                            bestPrice          = makerOrder.Price;
                            bestPriceTimestamp = makerOrder.Timestamp;
                        }
                    }
                }

                if (bestIndex >= 0)
                {
                    //since order "uid" has found a match, the creator of this order will be a taker as he will remove liquidity from the market
                    //and the creator of the "bestIndex" order is the maker as he is providing liquidity to the taker
                    var takerAvailableEscrow = orderEscrowAmount - orderEscrowUsage;
                    var takerEscrowUsage     = BigInteger.Zero;
                    var takerEscrowSymbol    = orderEscrowSymbol;

                    var makerOrder        = makerOrders.Get <ExchangeOrder>(bestIndex);
                    var makerEscrow       = _escrows.Get <BigInteger, BigInteger>(makerOrder.Uid);
                    var makerEscrowUsage  = BigInteger.Zero;;
                    var makerEscrowSymbol = orderEscrowSymbol == baseSymbol ? quoteSymbol : baseSymbol;

                    //Get fulfilled order size in base tokens
                    //and then calculate the corresponding fulfilled order size in quote tokens
                    if (takerEscrowSymbol == baseSymbol)
                    {
                        var makerEscrowBaseEquivalent = ConvertQuoteToBase(makerEscrow, makerOrder.Price, baseToken, quoteToken);
                        takerEscrowUsage = takerAvailableEscrow < makerEscrowBaseEquivalent ? takerAvailableEscrow : makerEscrowBaseEquivalent;

                        makerEscrowUsage = CalculateEscrowAmount(takerEscrowUsage, makerOrder.Price, baseToken, quoteToken, Buy);
                    }
                    else
                    {
                        var takerEscrowBaseEquivalent = ConvertQuoteToBase(takerAvailableEscrow, makerOrder.Price, baseToken, quoteToken);
                        makerEscrowUsage = makerEscrow < takerEscrowBaseEquivalent ? makerEscrow : takerEscrowBaseEquivalent;

                        takerEscrowUsage = CalculateEscrowAmount(makerEscrowUsage, makerOrder.Price, baseToken, quoteToken, Buy);
                    }

                    Runtime.Expect(takerEscrowUsage <= takerAvailableEscrow, "Taker tried to use more escrow than available");
                    Runtime.Expect(makerEscrowUsage <= makerEscrow, "Maker tried to use more escrow than available");

                    if (takerEscrowUsage < GetMinimumSymbolQuantity(takerEscrowSymbol) ||
                        makerEscrowUsage < GetMinimumSymbolQuantity(makerEscrowSymbol))
                    {
                        break;
                    }

                    Runtime.Nexus.TransferTokens(Runtime, takerEscrowSymbol, this.Address, makerOrder.Creator, takerEscrowUsage);
                    Runtime.Nexus.TransferTokens(Runtime, makerEscrowSymbol, this.Address, takerOrder.Creator, makerEscrowUsage);

                    Runtime.Notify(EventKind.TokenReceive, makerOrder.Creator, new TokenEventData()
                    {
                        chainAddress = this.Address, symbol = takerEscrowSymbol, value = takerEscrowUsage
                    });
                    Runtime.Notify(EventKind.TokenReceive, takerOrder.Creator, new TokenEventData()
                    {
                        chainAddress = this.Address, symbol = makerEscrowSymbol, value = makerEscrowUsage
                    });

                    orderEscrowUsage += takerEscrowUsage;

                    Runtime.Notify(EventKind.OrderFilled, takerOrder.Creator, takerOrder.Uid);
                    Runtime.Notify(EventKind.OrderFilled, makerOrder.Creator, makerOrder.Uid);

                    if (makerEscrowUsage == makerEscrow)
                    {
                        makerOrders.RemoveAt <ExchangeOrder>(bestIndex);
                        _orderMap.Remove(makerOrder.Uid);

                        Runtime.Expect(_escrows.ContainsKey(makerOrder.Uid), "An orderbook entry must have registered escrow");
                        _escrows.Remove(makerOrder.Uid);

                        Runtime.Notify(EventKind.OrderClosed, makerOrder.Creator, makerOrder.Uid);
                    }
                    else
                    {
                        _escrows.Set(makerOrder.Uid, makerEscrow - makerEscrowUsage);
                    }
                }
                else
                {
                    break;
                }
            } while (orderEscrowUsage < orderEscrowAmount);

            var leftoverEscrow = orderEscrowAmount - orderEscrowUsage;

            if (leftoverEscrow == 0 || orderType != Limit)
            {
                orderList.RemoveAt <ExchangeOrder>(orderIndex);
                _orderMap.Remove(thisOrder.Uid);
                _escrows.Remove(thisOrder.Uid);

                if (leftoverEscrow > 0)
                {
                    Runtime.Nexus.TransferTokens(Runtime, orderEscrowSymbol, this.Address, thisOrder.Creator, leftoverEscrow);
                    Runtime.Notify(EventKind.TokenReceive, thisOrder.Creator, new TokenEventData()
                    {
                        chainAddress = this.Address, symbol = orderEscrowSymbol, value = leftoverEscrow
                    });
                    Runtime.Notify(EventKind.OrderCancelled, thisOrder.Creator, thisOrder.Uid);
                }
                else
                {
                    Runtime.Notify(EventKind.OrderClosed, thisOrder.Creator, thisOrder.Uid);
                }
            }
            else
            {
                _escrows.Set(uid, leftoverEscrow);
            }

            //TODO: ADD FEES, SEND THEM TO this.Address FOR NOW
        }