From 92f7e9f065fa0f36310787b0e64ceb348d2ba350 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 26 Aug 2022 17:59:21 +0800
Subject: [PATCH 01/16] remove aggregator from v1
---
.../FlamingoSwapOrderBook/Extensions.cs | 30 +
.../FlamingoSwapOrderBook.csproj | 12 +
.../FlamingoSwapOrderBookContract.Admin.cs | 109 ++
.../FlamingoSwapOrderBookContract.Event.cs | 24 +
.../FlamingoSwapOrderBookContract.Helper.cs | 499 +++++++++
.../FlamingoSwapOrderBookContract.cs | 990 ++++++++++++++++++
.../Models/LimitOrder.cs | 14 +
.../FlamingoSwapOrderBook/Models/OrderBook.cs | 18 +
.../Models/OrderReceipt.cs | 19 +
.../Models/ReservesData.cs | 10 +
.../flamingo-contract-swap.sln | 8 +-
11 files changed, 1732 insertions(+), 1 deletion(-)
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBook.csproj
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Event.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/LimitOrder.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderBook.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderReceipt.cs
create mode 100644 Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/ReservesData.cs
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs
new file mode 100644
index 0000000..e0f8941
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs
@@ -0,0 +1,30 @@
+using System.Numerics;
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+
+namespace FlamingoSwapOrderBook
+{
+ public static class Extensions
+ {
+ ///
+ /// uint160 转为正整数,用于合约地址排序,其它场景勿用
+ ///
+ /// 合约地址
+ ///
+ [OpCode(OpCode.PUSHDATA1, "0100")]
+ [OpCode(OpCode.CAT)]
+ [OpCode(OpCode.CONVERT, "21")]
+ public static extern BigInteger ToUInteger(this UInt160 val);
+
+ ///
+ /// Is Valid and not Zero address
+ ///
+ ///
+ ///
+ public static bool IsAddress(this UInt160 address)
+ {
+ return address.IsValid && !address.IsZero;
+ }
+ }
+}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBook.csproj b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBook.csproj
new file mode 100644
index 0000000..ec0abe1
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBook.csproj
@@ -0,0 +1,12 @@
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
new file mode 100644
index 0000000..54be322
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
@@ -0,0 +1,109 @@
+using Neo;
+using Neo.SmartContract;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using Neo.SmartContract.Framework.Native;
+using Neo.SmartContract.Framework.Services;
+
+namespace FlamingoSwapOrderBook
+{
+ public partial class FlamingoSwapOrderBookContract
+ {
+ #region Admin
+
+#warning Update the admin address if necessary
+ [InitialValue("NdrUjmLFCmr6RjM52njho5sFUeeTdKPxG9", ContractParameterType.Hash160)]
+ static readonly UInt160 superAdmin = default;
+
+ [InitialValue("0xc0695bdb8a87a40aff33c73ff6349ccc05fa9f01", ContractParameterType.Hash160)]
+ static readonly UInt160 Factory = default;
+
+ [InitialValue("0x48c40d4666f93408be1bef038b6722404d9a4c2a", ContractParameterType.Hash160)]
+ static readonly UInt160 bNEO = default;
+
+ private const string AdminKey = nameof(superAdmin);
+ private const string GASAdminKey = nameof(GASAdminKey);
+ private const string FundAddresskey = nameof(FundAddresskey);
+
+ private static readonly byte[] OrderIDKey = new byte[] { 0x00 };
+ private static readonly byte[] BookMapPrefix = new byte[] { 0x01 };
+ private static readonly byte[] OrderMapPrefix = new byte[] { 0x02 };
+ private static readonly byte[] ReceiptMapPrefix = new byte[] { 0x03 };
+ private static readonly byte[] PauseMapPrefix = new byte[] { 0x04 };
+
+ // When this contract address is included in the transaction signature,
+ // this method will be triggered as a VerificationTrigger to verify that the signature is correct.
+ // For example, this method needs to be called when withdrawing token from the contract.
+ [Safe]
+ public static bool Verify() => Runtime.CheckWitness(GetAdmin());
+
+ [Safe]
+ public static UInt160 GetAdmin()
+ {
+ var admin = Storage.Get(Storage.CurrentReadOnlyContext, AdminKey);
+ return admin?.Length == 20 ? (UInt160)admin : superAdmin;
+ }
+
+ public static bool SetAdmin(UInt160 admin)
+ {
+ Assert(Verify(), "No Authorization");
+ Assert(admin.IsAddress(), "Invalid Address");
+ Storage.Put(Storage.CurrentContext, AdminKey, admin);
+ return true;
+ }
+
+ public static void ClaimGASFrombNEO(UInt160 receiveAddress)
+ {
+ Assert(Runtime.CheckWitness(GetGASAdmin()), "Forbidden");
+ var me = Runtime.ExecutingScriptHash;
+ var beforeBalance = GAS.BalanceOf(me);
+ Assert((bool)Contract.Call(bNEO, "transfer", CallFlags.All, Runtime.ExecutingScriptHash, bNEO, 0, null), "claim fail");
+ var afterBalance = GAS.BalanceOf(me);
+
+ GAS.Transfer(me, receiveAddress, afterBalance - beforeBalance);
+ }
+
+ [Safe]
+ public static UInt160 GetGASAdmin()
+ {
+ var address = Storage.Get(Storage.CurrentReadOnlyContext, GASAdminKey);
+ return address?.Length == 20 ? (UInt160)address : null;
+ }
+
+ public static bool SetGASAdmin(UInt160 GASAdmin)
+ {
+ Assert(GASAdmin.IsAddress(), "Invalid Address");
+ Assert(Verify(), "No Authorization");
+ Storage.Put(Storage.CurrentContext, GASAdminKey, GASAdmin);
+ return true;
+ }
+ #endregion
+
+ #region FundFee
+
+ [Safe]
+ public static UInt160 GetFundAddress()
+ {
+ var address = Storage.Get(Storage.CurrentReadOnlyContext, FundAddresskey);
+ return address?.Length == 20 ? (UInt160)address : null;
+ }
+
+ public static bool SetFundAddress(UInt160 address)
+ {
+ Assert(address.IsAddress(), "Invalid Address");
+ Assert(Verify(), "No Authorization");
+ Storage.Put(Storage.CurrentContext, FundAddresskey, address);
+ return true;
+ }
+ #endregion
+
+ #region Upgrade
+
+ public static void Update(ByteString nefFile, string manifest)
+ {
+ Assert(Verify(), "No Authorization");
+ ContractManagement.Update(nefFile, manifest, null);
+ }
+ #endregion
+ }
+}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Event.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Event.cs
new file mode 100644
index 0000000..dd3a4da
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Event.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using System.Numerics;
+using Neo;
+using Neo.SmartContract.Framework;
+
+namespace FlamingoSwapOrderBook
+{
+ public partial class FlamingoSwapOrderBookContract
+ {
+ ///
+ /// When orderbook status changed
+ ///
+ [DisplayName("BookStatusChanged")]
+ public static event BookStatusChangedEvent onBookStatusChanged;
+ public delegate void BookStatusChangedEvent(UInt160 baseToken, UInt160 quoteToken, BigInteger quoteScale, BigInteger minOrderAmount, BigInteger maxOrderAmount, bool isPaused);
+
+ ///
+ /// When order status changed
+ ///
+ [DisplayName("OrderStatusChanged")]
+ public static event OrderStatusChangedEvent onOrderStatusChanged;
+ public delegate void OrderStatusChangedEvent(UInt160 baseToken, UInt160 quoteToken, ByteString id, bool isBuy, UInt160 maker, BigInteger price, BigInteger leftAmount);
+ }
+}
\ No newline at end of file
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
new file mode 100644
index 0000000..c9de1a8
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
@@ -0,0 +1,499 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using Neo.SmartContract.Framework.Native;
+using Neo.SmartContract.Framework.Services;
+using System;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public partial class FlamingoSwapOrderBookContract
+ {
+ ///
+ /// Insert a not-fully-deal limit order into orderbook
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static bool InsertOrder(byte[] pairKey, OrderBook book, ByteString id, LimitOrder order, bool isBuy)
+ {
+ var firstID = isBuy ? book.firstBuyID : book.firstSellID;
+
+ // Check if there is no order
+ var canBeFirst = firstID is null;
+ if (!canBeFirst)
+ {
+ // Check if this order is the worthiest
+ var firstOrder = GetOrder(firstID);
+ canBeFirst = (isBuy && (firstOrder.price < order.price)) || (!isBuy && (firstOrder.price > order.price));
+ }
+ if (canBeFirst)
+ {
+ // Insert to the first
+ if (isBuy) book.firstBuyID = id;
+ else book.firstSellID = id;
+ SetOrderBook(pairKey, book);
+ order.nextID = firstID;
+ SetOrder(id, order);
+ return true;
+ }
+ else
+ {
+ // Find the position
+ return InsertNotFirst(firstID, id, order, isBuy);
+ }
+ }
+
+ private static bool InsertNotFirst(ByteString firstID, ByteString id, LimitOrder order, bool isBuy)
+ {
+ var currentID = firstID;
+ while (currentID is not null)
+ {
+ // Check before
+ var currentOrder = GetOrder(currentID);
+ var canFollow = (isBuy && (order.price <= currentOrder.price)) || (!isBuy && (order.price >= currentOrder.price));
+ if (!canFollow) break;
+
+ if (currentOrder.nextID is not null)
+ {
+ // Check after
+ var nextOrder = GetOrder(currentOrder.nextID);
+ var canPrecede = (isBuy && (nextOrder.price < order.price)) || (!isBuy && (nextOrder.price > order.price));
+ canFollow = canFollow && canPrecede;
+ }
+ if (canFollow)
+ {
+ // Do insert
+ order.nextID = currentOrder.nextID;
+ SetOrder(id, order);
+ currentOrder.nextID = id;
+ SetOrder(currentID, currentOrder);
+ return true;
+ }
+ currentID = currentOrder.nextID;
+ }
+ return false;
+ }
+
+ private static bool InsertOrderAt(ByteString parentID, ByteString id, LimitOrder order, bool isBuy)
+ {
+ var parentOrder = GetOrder(parentID);
+ var canFollow = (isBuy && (order.price <= parentOrder.price)) || (!isBuy && (order.price >= parentOrder.price));
+ if (!canFollow) return false;
+
+ if (parentOrder.nextID is not null)
+ {
+ // Check after
+ var nextOrder = GetOrder(parentOrder.nextID);
+ var canPrecede = (isBuy && (nextOrder.price < order.price)) || (!isBuy && (nextOrder.price > order.price));
+ canFollow = canFollow && canPrecede;
+ }
+ if (canFollow)
+ {
+ // Do insert
+ order.nextID = parentOrder.nextID;
+ SetOrder(id, order);
+ parentOrder.nextID = id;
+ SetOrder(parentID, parentOrder);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Get the parent order id
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static ByteString GetParentID(byte[] pairKey, bool isBuy, ByteString childID)
+ {
+ var book = GetOrderBook(pairKey);
+ var firstID = isBuy ? book.firstBuyID : book.firstSellID;
+ if (firstID == childID) return null;
+
+ var currentID = firstID;
+ while (currentID is not null)
+ {
+ var currentOrder = GetOrder(currentID);
+ if (currentOrder.nextID == childID) return currentID;
+ currentID = currentOrder.nextID;
+ }
+ return null;
+ }
+
+ ///
+ /// Remove a canceled limit order from orderbook
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static bool RemoveOrder(byte[] pairKey, OrderBook book, ByteString id, bool isBuy)
+ {
+ // Remove from BookMap
+ var firstID = isBuy ? book.firstBuyID : book.firstSellID;
+ if (firstID is null) return false;
+ if (firstID == id)
+ {
+ // Delete the first
+ if (isBuy) book.firstBuyID = GetOrder(firstID).nextID;
+ else book.firstSellID = GetOrder(firstID).nextID;
+ SetOrderBook(pairKey, book);
+ DeleteOrder(firstID);
+ return true;
+ }
+ else
+ {
+ // Find the position
+ return RemoveNotFirst(firstID, id);
+ }
+ }
+
+ private static bool RemoveNotFirst(ByteString firstID, ByteString id)
+ {
+ var currentID = firstID;
+ var currentOrder = GetOrder(currentID);
+ while (currentOrder.nextID is not null)
+ {
+ // Check next
+ if (currentOrder.nextID == id)
+ {
+ // Do remove
+ var newNextID = GetOrder(currentOrder.nextID).nextID;
+ DeleteOrder(currentOrder.nextID);
+ currentOrder.nextID = newNextID;
+ SetOrder(currentID, currentOrder);
+ return true;
+ }
+ currentID = currentOrder.nextID;
+ currentOrder = GetOrder(currentID);
+ }
+ return false;
+ }
+
+ private static bool RemoveOrderAt(ByteString parentID, ByteString id)
+ {
+ var parentOrder = GetOrder(parentID);
+ if (parentOrder.nextID != id) return false;
+
+ // Do remove
+ var newNextID = GetOrder(id).nextID;
+ DeleteOrder(id);
+ parentOrder.nextID = newNextID;
+ SetOrder(parentID, parentOrder);
+ return true;
+ }
+
+ ///
+ /// Check if a limit order exists
+ ///
+ ///
+ ///
+ private static bool OrderExists(ByteString id)
+ {
+ StorageMap orderMap = new(Storage.CurrentReadOnlyContext, OrderMapPrefix);
+ return orderMap.Get(id) is not null;
+ }
+
+ ///
+ /// Check if an order book exists
+ ///
+ ///
+ ///
+ private static bool BookExists(byte[] pairKey)
+ {
+ StorageMap bookMap = new(Storage.CurrentReadOnlyContext, BookMapPrefix);
+ return bookMap.Get(pairKey) is not null;
+ }
+
+ ///
+ /// Set an order book as paused
+ ///
+ ///
+ ///
+ private static void SetPaused(byte[] pairKey)
+ {
+ StorageMap pauseMap = new(Storage.CurrentContext, PauseMapPrefix);
+ pauseMap.Put(pairKey, 1);
+ }
+
+ ///
+ /// Remove an order book from paused
+ ///
+ ///
+ ///
+ private static void RemovePaused(byte[] pairKey)
+ {
+ StorageMap pauseMap = new(Storage.CurrentContext, PauseMapPrefix);
+ pauseMap.Delete(pairKey);
+ }
+
+ ///
+ /// Check if an order book is paused
+ ///
+ ///
+ ///
+ private static bool BookPaused(byte[] pairKey)
+ {
+ StorageMap pauseMap = new(Storage.CurrentReadOnlyContext, PauseMapPrefix);
+ return pauseMap.Get(pairKey) is not null;
+ }
+
+ ///
+ /// Get the detail of a limit order
+ ///
+ ///
+ ///
+ private static LimitOrder GetOrder(ByteString id)
+ {
+ StorageMap orderMap = new(Storage.CurrentReadOnlyContext, OrderMapPrefix);
+ return (LimitOrder)StdLib.Deserialize(orderMap.Get(id));
+ }
+
+ ///
+ /// Update a limit order
+ ///
+ ///
+ ///
+ private static void SetOrder(ByteString id, LimitOrder order)
+ {
+ StorageMap orderMap = new(Storage.CurrentContext, OrderMapPrefix);
+ orderMap.Put(id, StdLib.Serialize(order));
+ }
+
+ ///
+ /// Delete a limit order
+ ///
+ ///
+ private static void DeleteOrder(ByteString id)
+ {
+ StorageMap orderMap = new(Storage.CurrentContext, OrderMapPrefix);
+ orderMap.Delete(id);
+ }
+
+ ///
+ /// Get the detail of an order receipt
+ ///
+ ///
+ ///
+ ///
+ private static OrderReceipt GetReceipt(UInt160 maker, ByteString id)
+ {
+ StorageMap receiptMap = new(Storage.CurrentReadOnlyContext, ReceiptMapPrefix);
+ return (OrderReceipt)StdLib.Deserialize(receiptMap.Get(maker + id));
+ }
+
+ ///
+ /// Get all receipts of the maker
+ ///
+ ///
+ ///
+ private static Iterator ReceiptsOf(UInt160 maker)
+ {
+ StorageMap receiptMap = new(Storage.CurrentReadOnlyContext, ReceiptMapPrefix);
+ return receiptMap.Find(maker, FindOptions.ValuesOnly | FindOptions.DeserializeValues);
+ }
+
+ [OpCode(OpCode.APPEND)]
+ private static extern void Append(T[] array, T newItem);
+
+ ///
+ /// Update an order receipt
+ ///
+ ///
+ ///
+ ///
+ private static void SetReceipt(UInt160 maker, ByteString id, OrderReceipt receipt)
+ {
+ StorageMap receiptMap = new(Storage.CurrentContext, ReceiptMapPrefix);
+ receiptMap.Put(maker + id, StdLib.Serialize(receipt));
+ }
+
+ ///
+ /// Delete an order receipt
+ ///
+ ///
+ ///
+ private static void DeleteReceipt(UInt160 maker, ByteString id)
+ {
+ StorageMap receiptMap = new(Storage.CurrentContext, ReceiptMapPrefix);
+ receiptMap.Delete(maker + id);
+ }
+
+ ///
+ /// Get the detail of a book
+ ///
+ ///
+ ///
+ private static OrderBook GetOrderBook(byte[] pairKey)
+ {
+ StorageMap bookMap = new(Storage.CurrentReadOnlyContext, BookMapPrefix);
+ return (OrderBook)StdLib.Deserialize(bookMap.Get(pairKey));
+ }
+
+ ///
+ /// Update a book
+ ///
+ ///
+ ///
+ private static void SetOrderBook(byte[] pairKey, OrderBook book)
+ {
+ StorageMap bookMap = new(Storage.CurrentContext, BookMapPrefix);
+ bookMap.Put(pairKey, StdLib.Serialize(book));
+ }
+
+ ///
+ /// Find a random number as order ID
+ ///
+ ///
+ private static ByteString GetUnusedID()
+ {
+ StorageContext context = Storage.CurrentContext;
+ ByteString counter = Storage.Get(context, OrderIDKey);
+ Storage.Put(context, OrderIDKey, (BigInteger)counter + 1);
+ ByteString data = Runtime.ExecutingScriptHash;
+ if (counter is not null) data += counter;
+ return CryptoLib.Sha256(data);
+ }
+
+ ///
+ /// Get the pair contract
+ ///
+ ///
+ ///
+ ///
+ public static UInt160 GetExchangePairWithAssert(UInt160 tokenA, UInt160 tokenB)
+ {
+ Assert(tokenA.IsValid && tokenB.IsValid, "Invalid A or B Address");
+ var pairContract = (byte[])Contract.Call(Factory, "getExchangePair", CallFlags.ReadOnly, new object[] { tokenA, tokenB });
+ Assert(pairContract != null && pairContract.Length == 20, "PairContract Not Found", tokenA, tokenB);
+ return (UInt160)pairContract;
+ }
+
+ ///
+ /// Get TokenA and TokenB reserves from paicontract
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static BigInteger[] GetReserves(UInt160 pairContract, UInt160 tokenA, UInt160 tokenB)
+ {
+ var reserveData = (ReservesData)Contract.Call(pairContract, "getReserves", CallFlags.ReadOnly, new object[] { });
+ return tokenA.ToUInteger() < tokenB.ToUInteger() ? new BigInteger[] { reserveData.Reserve0, reserveData.Reserve1 } : new BigInteger[] { reserveData.Reserve1, reserveData.Reserve0 };
+ }
+
+ ///
+ /// Check if pair contract charge a fundfee
+ ///
+ ///
+ ///
+ public static bool HasFundAddress(UInt160 pairContract)
+ {
+ return (byte[])Contract.Call(pairContract, "getFundAddress", CallFlags.ReadOnly, new object[] { }) != null;
+ }
+
+ ///
+ /// Get amountOut from pair
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void SwapOut(UInt160 pairContract, BigInteger amount0Out, BigInteger amount1Out, UInt160 toAddress)
+ {
+ Contract.Call(pairContract, "swap", CallFlags.All, new object[] { amount0Out, amount1Out, toAddress, null });
+ }
+
+ ///
+ /// Transfer NEP-5 tokens
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void SafeTransfer(UInt160 token, UInt160 from, UInt160 to, BigInteger amount, byte[] data = null)
+ {
+ try
+ {
+ var result = (bool)Contract.Call(token, "transfer", CallFlags.All, new object[] { from, to, amount, data });
+ Assert(result, "Transfer Fail in OrderBook", token);
+ }
+ catch (Exception)
+ {
+ Assert(false, "Transfer Error in OrderBook", token);
+ }
+ }
+
+ private static void RequestTransfer(UInt160 token, UInt160 from, UInt160 to, BigInteger amount, byte[] data = null)
+ {
+ try
+ {
+ var balanceBefore = GetBalanceOf(token, to);
+ var result = (bool)Contract.Call(from, "approvedTransfer", CallFlags.All, new object[] { token, to, amount, data });
+ var balanceAfter = GetBalanceOf(token, to);
+ Assert(result, "Transfer Not Approved in OrderBook", token);
+ Assert(balanceAfter == balanceBefore + amount, "Unexpected Transfer in OrderBook", token);
+ }
+ catch (Exception)
+ {
+ Assert(false, "Transfer Error in OrderBook", token);
+ }
+ }
+
+ private static BigInteger GetBalanceOf(UInt160 token, UInt160 address)
+ {
+ return (BigInteger)Contract.Call(token, "balanceOf", CallFlags.ReadOnly, new object[] { address });
+ }
+
+ ///
+ /// Get unique pair key
+ ///
+ ///
+ ///
+ ///
+ private static byte[] GetPairKey(UInt160 tokenA, UInt160 tokenB)
+ {
+ return (BigInteger)tokenA < (BigInteger)tokenB
+ ? BookMapPrefix.Concat(tokenA).Concat(tokenB)
+ : BookMapPrefix.Concat(tokenB).Concat(tokenA);
+ }
+
+ ///
+ /// Check if
+ ///
+ ///
+ ///
+ ///
+ private static void Assert(bool condition, string message, object data = null)
+ {
+ if (!condition)
+ {
+ throw new Exception(message);
+ }
+ }
+
+ ///
+ /// Check if
+ ///
+ ///
+ ///
+ ///
+ private static void Assert(bool condition, string message, params object[] data)
+ {
+ if (!condition)
+ {
+ throw new Exception(message);
+ }
+ }
+ }
+}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
new file mode 100644
index 0000000..c21234c
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -0,0 +1,990 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using Neo.SmartContract.Framework.Native;
+using Neo.SmartContract.Framework.Services;
+using System.Numerics;
+using System.ComponentModel;
+
+namespace FlamingoSwapOrderBook
+{
+ [DisplayName("FlamingoSwapOrderBook")]
+ [ManifestExtra("Author", "Flamingo Finance")]
+ [ManifestExtra("Email", "developer@flamingo.finance")]
+ [ManifestExtra("Description", "This is a Flamingo Contract")]
+ [ContractPermission("*")]
+ public partial class FlamingoSwapOrderBookContract : SmartContract
+ {
+ ///
+ /// Deal and add limit order base on input strategy
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger expectBookAmount)
+ {
+ // Market order
+ var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrder(tokenA, tokenB, sender, isBuy, price, expectBookAmount) : amount;
+
+ // Swap AMM
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var book = GetOrderBook(pairKey);
+ var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
+ var hasFundFee = HasFundAddress(pairContract);
+
+ var ammReverse = isBuy
+ ? GetReserves(pairContract, book.quoteToken, book.baseToken)
+ : GetReserves(pairContract, book.baseToken, book.quoteToken);
+ var amountIn = hasFundFee
+ ? GetAmountInTillPriceWithFundFee(isBuy, price, book.quoteScale, ammReverse[0], ammReverse[1])
+ : GetAmountInTillPrice(isBuy, price, book.quoteScale, ammReverse[0], ammReverse[1]);
+ if (amountIn < 0) amountIn = 0;
+ var amountOut = GetAmountOut(amountIn, ammReverse[0], ammReverse[1]);
+
+ if (isBuy && leftAmount < amountOut)
+ {
+ amountOut = leftAmount;
+ amountIn = GetAmountIn(amountOut, ammReverse[0], ammReverse[1]);
+ }
+ if (!isBuy && leftAmount < amountIn)
+ {
+ amountIn = leftAmount;
+ amountOut = GetAmountOut(amountIn, ammReverse[0], ammReverse[1]);
+ }
+
+ if (amountOut > 0)
+ {
+ SwapAMM(pairContract, sender, isBuy ? book.quoteToken : book.baseToken, isBuy ? book.baseToken : book.quoteToken, amountIn, amountOut);
+ leftAmount -= isBuy ? amountOut : amountIn;
+ }
+
+ // Add limit order
+ if (leftAmount < book.minOrderAmount || leftAmount > book.maxOrderAmount) return null;
+ var me = Runtime.ExecutingScriptHash;
+ if (isBuy) SafeTransfer(book.quoteToken, sender, me, leftAmount * price / book.quoteScale);
+ else SafeTransfer(book.baseToken, sender, me, leftAmount);
+
+ var id = GetUnusedID();
+ Assert(InsertOrder(pairKey, book, id, new LimitOrder(){
+ maker = sender,
+ price = price,
+ amount = leftAmount
+ }, isBuy), "Add Order Fail");
+
+ // Add receipt
+ SetReceipt(sender, id, new OrderReceipt(){
+ baseToken = book.baseToken,
+ quoteToken = book.quoteToken,
+ id = id,
+ time = Runtime.Time,
+ isBuy = isBuy,
+ totalAmount = leftAmount
+ });
+ onOrderStatusChanged(book.baseToken, book.quoteToken, id, !!isBuy, sender, price, leftAmount);
+ return id;
+ }
+
+ ///
+ /// Deal market order based on input strategy
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger expectBookAmount)
+ {
+ // Market order
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var book = GetOrderBook(pairKey);
+ var price = slippage * book.quoteScale / amount;
+
+ var balanceBefore = GetBalanceOf(isBuy ? book.quoteToken : book.quoteToken, sender);
+ var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrder(tokenA, tokenB, sender, isBuy, price, expectBookAmount) : amount;
+ var balanceAfter = GetBalanceOf(isBuy ? book.quoteToken : book.quoteToken, sender);
+
+ // Swap AMM
+ var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
+ var ammReverse = GetReserves(pairContract, book.baseToken, book.quoteToken);
+
+ var amountIn = isBuy ? GetAmountIn(leftAmount, ammReverse[1], ammReverse[0]) : leftAmount;
+ var amountOut = isBuy ? leftAmount : GetAmountOut(leftAmount, ammReverse[0], ammReverse[1]);
+
+ if (amountOut > 0) SwapAMM(pairContract, sender, isBuy ? book.quoteToken : book.baseToken, isBuy ? book.baseToken : book.quoteToken, amountIn, amountOut);
+ Assert(isBuy ? amountIn + balanceBefore - balanceAfter <= slippage : amountOut + balanceAfter - balanceBefore >= slippage, "Insufficient Slippage");
+ }
+
+ #region DEX like API
+ ///
+ /// Register a new book
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void RegisterOrderBook(UInt160 baseToken, UInt160 quoteToken, uint quoteDecimals, BigInteger minOrderAmount, BigInteger maxOrderAmount)
+ {
+ Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
+ Assert(baseToken != quoteToken, "Invalid Trade Pair");
+ Assert(minOrderAmount > 0 && maxOrderAmount > 0 && minOrderAmount <= maxOrderAmount, "Invalid Amount Limit");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(baseToken, quoteToken);
+ var quoteScale = BigInteger.Pow(10, (int)quoteDecimals);
+ Assert(!BookExists(pairKey), "Book Already Registered");
+ SetOrderBook(pairKey, new OrderBook(){
+ baseToken = baseToken,
+ quoteToken = quoteToken,
+ quoteScale = quoteScale,
+ minOrderAmount = minOrderAmount,
+ maxOrderAmount = maxOrderAmount
+ });
+ onBookStatusChanged(baseToken, quoteToken, quoteScale, minOrderAmount, maxOrderAmount, BookPaused(pairKey));
+ }
+
+ ///
+ /// Set the minimum order amount for addLimitOrder
+ ///
+ ///
+ ///
+ ///
+ public static void SetMinOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger minOrderAmount)
+ {
+ Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
+ Assert(minOrderAmount > 0, "Invalid Amount Limit");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(baseToken, quoteToken);
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken == baseToken && book.quoteToken == quoteToken, "Invalid Trade Pair");
+ Assert(minOrderAmount <= book.maxOrderAmount, "Invalid Amount Limit");
+
+ book.minOrderAmount = minOrderAmount;
+ SetOrderBook(pairKey, book);
+ onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ }
+
+ ///
+ /// Set the maximum trade amount for addLimitOrder
+ ///
+ ///
+ ///
+ ///
+ public static void SetMaxOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger maxOrderAmount)
+ {
+ Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
+ Assert(maxOrderAmount > 0, "Invalid Amount Limit");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(baseToken, quoteToken);
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken == baseToken && book.quoteToken == quoteToken, "Invalid Trade Pair");
+ Assert(maxOrderAmount >= book.minOrderAmount, "Invalid Amount Limit");
+
+ book.maxOrderAmount = maxOrderAmount;
+ SetOrderBook(pairKey, book);
+ onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ }
+
+ ///
+ /// Pause an existing order book
+ ///
+ ///
+ ///
+ public static void PauseOrderBook(UInt160 baseToken, UInt160 quoteToken)
+ {
+ Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(baseToken, quoteToken);
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken == baseToken && book.quoteToken == quoteToken, "Invalid Trade Pair");
+ Assert(!BookPaused(pairKey), "Book Already Paused");
+
+ SetPaused(pairKey);
+ onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ }
+
+ ///
+ /// Resume a paused order book
+ ///
+ ///
+ ///
+ public static void ResumeOrderBook(UInt160 baseToken, UInt160 quoteToken)
+ {
+ Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(baseToken, quoteToken);
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken == baseToken && book.quoteToken == quoteToken, "Invalid Trade Pair");
+ Assert(BookPaused(pairKey), "Book Not Paused");
+
+ RemovePaused(pairKey);
+ onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ }
+
+ ///
+ /// Add a new order into orderbook but try deal it first
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Null or a new order id
+ public static ByteString AddLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 maker, bool isBuy, BigInteger price, BigInteger amount)
+ {
+ // Check parameters
+ Assert(price > 0 && amount > 0, "Invalid Parameters");
+ var pairKey = GetPairKey(tokenA, tokenB);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(Runtime.CheckWitness(maker), "No Authorization");
+ Assert(ContractManagement.GetContract(maker) == null, "Forbidden");
+
+ // Deposit token
+ var book = GetOrderBook(pairKey);
+ if (amount < book.minOrderAmount || amount > book.maxOrderAmount) return null;
+ var me = Runtime.ExecutingScriptHash;
+ if (isBuy) SafeTransfer(book.quoteToken, maker, me, amount * price / book.quoteScale);
+ else SafeTransfer(book.baseToken, maker, me, amount);
+
+ // Do add
+ var id = GetUnusedID();
+ Assert(InsertOrder(pairKey, book, id, new LimitOrder(){
+ maker = maker,
+ price = price,
+ amount = amount
+ }, isBuy), "Add Order Fail");
+
+ // Add receipt
+ SetReceipt(maker, id, new OrderReceipt(){
+ baseToken = book.baseToken,
+ quoteToken = book.quoteToken,
+ id = id,
+ time = Runtime.Time,
+ isBuy = isBuy,
+ totalAmount = amount
+ });
+ onOrderStatusChanged(book.baseToken, book.quoteToken, id, !!isBuy, maker, price, amount);
+ return id;
+ }
+
+ ///
+ /// Add a new order into orderbook with an expected parent order id
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Null or a new order id
+ public static ByteString AddLimitOrderAt(ByteString parentID, UInt160 maker, BigInteger price, BigInteger amount)
+ {
+ // Check parameters
+ Assert(price > 0 && amount > 0, "Invalid Parameters");
+ var receipt = GetReceipt(GetOrder(parentID).maker, parentID);
+ var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(Runtime.CheckWitness(maker), "No Authorization");
+ Assert(ContractManagement.GetContract(maker) == null, "Forbidden");
+
+ // Check amount
+ var book = GetOrderBook(pairKey);
+ Assert(amount >= book.minOrderAmount && amount <= book.maxOrderAmount, "Invalid Limit Order Amount");
+
+ // Deposit token
+ var me = Runtime.ExecutingScriptHash;
+ if (receipt.isBuy) SafeTransfer(receipt.quoteToken, maker, me, amount * price / book.quoteScale);
+ else SafeTransfer(receipt.baseToken, maker, me, amount);
+
+ // Insert new order
+ var id = GetUnusedID();
+ Assert(InsertOrderAt(parentID, id, new LimitOrder(){
+ maker = maker,
+ price = price,
+ amount = amount
+ }, receipt.isBuy), "Add Order Fail");
+
+ // Add receipt
+ SetReceipt(maker, id, new OrderReceipt(){
+ baseToken = receipt.baseToken,
+ quoteToken = receipt.quoteToken,
+ id = id,
+ time = Runtime.Time,
+ isBuy = receipt.isBuy,
+ totalAmount = amount
+ });
+ onOrderStatusChanged(receipt.baseToken, receipt.quoteToken, id, !!receipt.isBuy, maker, price, amount);
+ return id;
+ }
+
+ ///
+ /// Cancel a limit order with its id
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void CancelOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteString id)
+ {
+ // Check if exist
+ var pairKey = GetPairKey(tokenA, tokenB);
+ Assert(OrderExists(id), "Order Not Exists");
+ var order = GetOrder(id);
+ Assert(Runtime.CheckWitness(order.maker), "No Authorization");
+
+ // Do remove
+ var book = GetOrderBook(pairKey);
+ Assert(RemoveOrder(pairKey, book, id, isBuy), "Remove Order Fail");
+
+ // Remove receipt
+ DeleteReceipt(order.maker, id);
+ onOrderStatusChanged(book.baseToken, book.quoteToken, id, !!isBuy, order.maker, order.price, 0);
+
+ // Withdraw token
+ var me = Runtime.ExecutingScriptHash;
+ if (isBuy) SafeTransfer(book.quoteToken, me, order.maker, order.amount * order.price / book.quoteScale);
+ else SafeTransfer(book.baseToken, me, order.maker, order.amount);
+ }
+
+ ///
+ /// Cancel a limit order with its id and parent order id
+ ///
+ ///
+ ///
+ ///
+ public static bool CancelOrderAt(ByteString parentID, ByteString id)
+ {
+ if (!OrderExists(id)) return false;
+ Assert(OrderExists(parentID), "Parent Order Not Exists");
+ var order = GetOrder(id);
+ Assert(Runtime.CheckWitness(order.maker), "No Authorization");
+
+ // Do remove
+ var receipt = GetReceipt(order.maker, id);
+ var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
+ var book = GetOrderBook(pairKey);
+
+ Assert(RemoveOrderAt(parentID, id), "Remove Order Fail");
+
+ // Remove receipt
+ DeleteReceipt(order.maker, id);
+ onOrderStatusChanged(receipt.baseToken, receipt.quoteToken, id, !!receipt.isBuy, order.maker, order.price, 0);
+
+ // Withdraw token
+ var me = Runtime.ExecutingScriptHash;
+ if (receipt.isBuy) SafeTransfer(receipt.quoteToken, me, order.maker, order.amount * order.price / book.quoteScale);
+ else SafeTransfer(receipt.baseToken, me, order.maker, order.amount);
+ return true;
+ }
+
+ ///
+ /// Try get the parent order id of an existing order
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static ByteString GetParentOrderID(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteString id)
+ {
+ var pairKey = GetPairKey(tokenA, tokenB);
+ return GetParentID(pairKey, isBuy, id);
+ }
+
+ ///
+ /// Get first N limit orders and their details
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static OrderReceipt[] GetNOrders(UInt160 tokenA, UInt160 tokenB, bool isBuy, uint pos, uint n)
+ {
+ var results = new OrderReceipt[n];
+
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var book = GetOrderBook(pairKey);
+ var firstID = isBuy ? book.firstBuyID : book.firstSellID;
+ if (firstID is null) return results;
+
+ var currentOrderID = firstID;
+ var currentOrder = GetOrder(currentOrderID);
+ for (int i = 0; i < pos + n; i++)
+ {
+ if (i >= pos)
+ {
+ var receipt = GetReceipt(currentOrder.maker, currentOrderID);
+ receipt.maker = currentOrder.maker;
+ receipt.price = currentOrder.price;
+ receipt.leftAmount = currentOrder.amount;
+ results[i - pos] = receipt;
+
+ if (currentOrder.nextID is null) break;
+ }
+ currentOrderID = currentOrder.nextID;
+ currentOrder = GetOrder(currentOrder.nextID);
+ }
+
+ return results;
+ }
+
+ ///
+ /// Get all orders and their details of maker
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static OrderReceipt[] GetOrdersOf(UInt160 maker, uint pos, uint n)
+ {
+ var results = new OrderReceipt[n];
+ var iterator = ReceiptsOf(maker);
+ // Makeup details
+ for (int i = 0; i < pos + n; i++)
+ {
+ if (iterator.Next() && i >= pos)
+ {
+ results[i - pos] = (OrderReceipt)iterator.Value;
+ var order = GetOrder(results[i - pos].id);
+ results[i - pos].maker = order.maker;
+ results[i - pos].price = order.price;
+ results[i - pos].leftAmount = order.amount;
+ }
+ }
+ return results;
+ }
+
+ ///
+ /// Get the total reverse of tradable orders with an expected price
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static BigInteger GetTotalTradable(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price)
+ {
+ var pairKey = GetPairKey(tokenA, tokenB);
+ if (BookPaused(pairKey)) return 0;
+ return GetTotalTradableInternal(pairKey, isBuy, price);
+ }
+
+ private static BigInteger GetTotalTradableInternal(byte[] pairKey, bool isBuy, BigInteger price)
+ {
+ var totalTradable = BigInteger.Zero;
+ var book = GetOrderBook(pairKey);
+ var currentID = isBuy ? book.firstSellID : book.firstBuyID;
+ if (currentID is null) return totalTradable;
+ while (currentID is not null)
+ {
+ var currentOrder = GetOrder(currentID);
+ if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
+ totalTradable += currentOrder.amount;
+ currentID = currentOrder.nextID;
+ }
+ return totalTradable;
+ }
+
+ ///
+ /// Try to match without real payment
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static BigInteger[] MatchOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
+ {
+ // Check if exist
+ var pairKey = GetPairKey(tokenA, tokenB);
+ if (BookPaused(pairKey)) return new BigInteger[] { amount, 0 };
+ return MatchOrderInternal(pairKey, isBuy, price, amount);
+ }
+
+ private static BigInteger[] MatchOrderInternal(byte[] pairKey, bool isBuy, BigInteger price, BigInteger amount)
+ {
+ var totalPayment = BigInteger.Zero;
+ var book = GetOrderBook(pairKey);
+ var currentID = isBuy ? book.firstSellID : book.firstBuyID;
+ if (currentID is null) return new BigInteger[] { amount, 0 };
+ var currentOrder = GetOrder(currentID);
+
+ while (amount > 0)
+ {
+ // Check price
+ if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
+
+ if (currentOrder.amount <= amount)
+ {
+ totalPayment += currentOrder.amount * currentOrder.price / book.quoteScale;
+ amount -= currentOrder.amount;
+ }
+ else
+ {
+ totalPayment += amount * currentOrder.price / book.quoteScale;
+ amount = 0;
+ }
+
+ if (currentOrder.nextID is null) break;
+ currentOrder = GetOrder(currentOrder.nextID);
+ }
+
+ // A least payment for buyer
+ if (isBuy && totalPayment == 0) totalPayment += 1;
+ return new BigInteger[] { amount, totalPayment };
+ }
+
+ ///
+ /// Try to match without real payment
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Left amount and tradable base
+ [Safe]
+ public static BigInteger[] MatchQuote(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger quoteAmount)
+ {
+ var pairKey = GetPairKey(tokenA, tokenB);
+ if (BookPaused(pairKey)) return new BigInteger[] { quoteAmount, 0 };
+ return MatchQuoteInternal(pairKey, isBuy, price, quoteAmount);
+ }
+
+ private static BigInteger[] MatchQuoteInternal(byte[] pairKey, bool isBuy, BigInteger price, BigInteger quoteAmount)
+ {
+ var totalTradable = BigInteger.Zero;
+ var book = GetOrderBook(pairKey);
+ var currentID = isBuy ? book.firstSellID : book.firstBuyID;
+ if (currentID is null) return new BigInteger[] { quoteAmount, 0 };
+ var currentOrder = GetOrder(currentID);
+
+ while (quoteAmount > 0)
+ {
+ // Check price
+ if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
+ var payment = currentOrder.amount * currentOrder.price / book.quoteScale;
+ if (payment <= quoteAmount)
+ {
+ totalTradable += currentOrder.amount;
+ quoteAmount -= payment;
+ }
+ else
+ {
+ // For buyer, real payment <= expected
+ if (isBuy) totalTradable += quoteAmount * book.quoteScale / currentOrder.price;
+ // For seller, real payment >= expected
+ else totalTradable += (quoteAmount * book.quoteScale + currentOrder.price - 1) / currentOrder.price;
+ quoteAmount = 0;
+ }
+
+ if (currentOrder.nextID is null) break;
+ currentOrder = GetOrder(currentOrder.nextID);
+ }
+ return new BigInteger[] { quoteAmount, totalTradable };
+ }
+
+ ///
+ /// Try to make a market deal with orderbook
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Left amount
+ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger price, BigInteger amount)
+ {
+ // Check parameters
+ Assert(price > 0 && amount > 0, "Invalid Parameters");
+ var pairKey = GetPairKey(tokenA, tokenB);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(Runtime.CheckWitness(taker), "No Authorization");
+ Assert(ContractManagement.GetContract(taker) == null, "Forbidden");
+
+ return DealMarketOrderInternal(pairKey, taker, isBuy, price, amount, false);
+ }
+
+ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
+ {
+ // Check parameters
+ Assert(price > 0 && amount > 0, "Invalid Parameters");
+ var pairKey = GetPairKey(tokenA, tokenB);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ var caller = Runtime.CallingScriptHash;
+ Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
+
+ return DealMarketOrderInternal(pairKey, caller, isBuy, price, amount, true);
+ }
+
+ private static BigInteger DealMarketOrderInternal(byte[] pairKey, UInt160 taker, bool isBuy, BigInteger price, BigInteger leftAmount, bool shouldRequest)
+ {
+ // Check if can deal
+ var book = GetOrderBook(pairKey);
+ var firstID = isBuy ? book.firstSellID : book.firstBuyID;
+ if (firstID is null) return leftAmount;
+ var firstOrder = GetOrder(firstID);
+ var canDeal = (isBuy && firstOrder.price <= price) || (!isBuy && firstOrder.price >= price);
+ if (!canDeal) return leftAmount;
+
+ var me = Runtime.ExecutingScriptHash;
+ var fundAddress = GetFundAddress();
+
+ var quoteFee = BigInteger.Zero;
+ var baseFee = BigInteger.Zero;
+
+ var takerReceive = BigInteger.Zero;
+ var takerPayment = BigInteger.Zero;
+ var makerReceive = new Map();
+
+ var currentID = firstID;
+ while (currentID is not null)
+ {
+ var currentOrder = GetOrder(currentID);
+ if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
+
+ BigInteger quoteAmount;
+ BigInteger baseAmount;
+ BigInteger quotePayment;
+ BigInteger basePayment;
+
+ if (currentOrder.amount <= leftAmount)
+ {
+ // Full-fill
+ quoteAmount = currentOrder.amount * currentOrder.price / book.quoteScale;
+ baseAmount = currentOrder.amount;
+
+ // Remove full-fill order
+ DeleteOrder(currentID);
+ DeleteReceipt(currentOrder.maker, currentID);
+ firstID = currentOrder.nextID;
+
+ onOrderStatusChanged(book.baseToken, book.quoteToken, currentID, !isBuy, currentOrder.maker, currentOrder.price, 0);
+ leftAmount -= currentOrder.amount;
+ }
+ else
+ {
+ // Part-fill
+ quoteAmount = leftAmount * currentOrder.price / book.quoteScale;
+ baseAmount = leftAmount;
+
+ // Update order
+ currentOrder.amount -= leftAmount;
+ SetOrder(currentID, currentOrder);
+
+ onOrderStatusChanged(book.baseToken, book.quoteToken, currentID, !isBuy, currentOrder.maker, currentOrder.price, currentOrder.amount);
+ leftAmount = 0;
+ }
+
+ // Record payment
+ quotePayment = quoteAmount * 997 / 1000;
+ basePayment = baseAmount * 997 / 1000;
+ quoteFee += quoteAmount - quotePayment;
+ baseFee += baseAmount - basePayment;
+
+ if (isBuy)
+ {
+ takerPayment += quoteAmount;
+ takerReceive += basePayment;
+ if (makerReceive.HasKey(currentOrder.maker)) makerReceive[currentOrder.maker] += quotePayment;
+ else makerReceive[currentOrder.maker] = quotePayment;
+ }
+ else
+ {
+ takerPayment += baseAmount;
+ takerReceive += quotePayment;
+ if (makerReceive.HasKey(taker)) makerReceive[taker] += basePayment;
+ else makerReceive[taker] = basePayment;
+ }
+
+ // Check if still tradable
+ if (leftAmount == 0) break;
+ currentID = currentOrder.nextID;
+ }
+
+ // Update book if necessary
+ if (isBuy && book.firstSellID != firstID)
+ {
+ book.firstSellID = firstID;
+ SetOrderBook(pairKey, book);
+ }
+ if (!isBuy && book.firstBuyID != firstID)
+ {
+ book.firstBuyID = firstID;
+ SetOrderBook(pairKey, book);
+ }
+
+ // Do transfer
+ if (takerPayment == 0) takerPayment += 1;
+ if (isBuy)
+ {
+ if (shouldRequest) RequestTransfer(book.quoteToken, taker, me, takerPayment);
+ else SafeTransfer(book.quoteToken, taker, me, takerPayment);
+ SafeTransfer(book.baseToken, me, taker, takerReceive);
+ foreach (var toAddress in makerReceive.Keys) SafeTransfer(book.quoteToken, me, toAddress, makerReceive[toAddress]);
+ }
+ else
+ {
+ if (shouldRequest) RequestTransfer(book.baseToken, taker, me, takerPayment);
+ else SafeTransfer(book.baseToken, taker, me, takerPayment);
+ SafeTransfer(book.quoteToken, me, taker, takerReceive);
+ foreach (var toAddress in makerReceive.Keys) SafeTransfer(book.baseToken, me, toAddress, makerReceive[toAddress]);
+ }
+
+
+ if (fundAddress is not null)
+ {
+ SafeTransfer(book.quoteToken, me, fundAddress, quoteFee);
+ SafeTransfer(book.baseToken, me, fundAddress, baseFee);
+ }
+ return leftAmount;
+ }
+
+ ///
+ /// Deal a whole limit order with it id and parent id
+ ///
+ ///
+ ///
+ ///
+ public static void DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteString id)
+ {
+ Assert(Runtime.CheckWitness(taker), "No Authorization");
+
+ // Do deal
+ var order = GetOrder(id);
+ var me = Runtime.ExecutingScriptHash;
+ var receipt = GetReceipt(order.maker, id);
+ var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ var quoteScale = GetOrderBook(pairKey).quoteScale;
+ var fundAddress = GetFundAddress();
+
+ var baseAmount = order.amount;
+ var quoteAmount = order.amount * order.price / quoteScale;
+ var basePayment = baseAmount * 997 / 1000;
+ var quotePayment = quoteAmount * 997 / 1000;
+ var baseFee = baseAmount - basePayment;
+ var quoteFee = quoteAmount - quotePayment;
+
+ if (receipt.isBuy) SafeTransfer(receipt.baseToken, taker, me, baseAmount);
+ else SafeTransfer(receipt.quoteToken, taker, me, quoteAmount);
+
+ // Remove order and receipt
+ Assert(RemoveOrderAt(parentID, id), "Remove Order Fail");
+ DeleteReceipt(order.maker, id);
+ onOrderStatusChanged(receipt.baseToken, receipt.quoteToken, id, !!receipt.isBuy, order.maker, order.price, 0);
+
+ // Transfer
+ if (receipt.isBuy)
+ {
+ SafeTransfer(receipt.baseToken, me, order.maker, basePayment);
+ SafeTransfer(receipt.quoteToken, me, taker, quotePayment);
+ }
+ else
+ {
+ SafeTransfer(receipt.baseToken, me, taker, basePayment);
+ SafeTransfer(receipt.quoteToken, me, order.maker, quotePayment);
+ }
+
+ if (fundAddress is not null)
+ {
+ SafeTransfer(receipt.baseToken, me, fundAddress, baseFee);
+ SafeTransfer(receipt.quoteToken, me, fundAddress, quoteFee);
+ }
+ }
+
+ ///
+ /// Price reporter
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static BigInteger GetMarketPrice(UInt160 tokenA, UInt160 tokenB, bool isBuy)
+ {
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var book = GetOrderBook(pairKey);
+ var firstID = isBuy ? book.firstSellID : book.firstBuyID;
+ if (firstID is null) return 0;
+ return GetOrder(firstID).price;
+ }
+
+ [Safe]
+ public static OrderBook GetBookInfo(UInt160 tokenA, UInt160 tokenB)
+ {
+ var pairKey = GetPairKey(tokenA, tokenB);
+ return GetOrderBook(pairKey);
+ }
+ #endregion
+
+ #region AMM like API
+ ///
+ /// Calculate amountOut with amountIn
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Unsatisfied amountIn and amountOut
+ [Safe]
+ public static BigInteger[] GetAmountOut(UInt160 tokenFrom, UInt160 tokenTo, BigInteger price, BigInteger amountIn)
+ {
+ // Check if exist
+ var pairKey = GetPairKey(tokenFrom, tokenTo);
+ if (BookPaused(pairKey)) return new BigInteger[] { amountIn, 0 };
+ var book = GetOrderBook(pairKey);
+ var isBuy = tokenFrom == book.quoteToken;
+ if (isBuy)
+ {
+ var result = MatchQuoteInternal(pairKey, isBuy, price, amountIn);
+ return new BigInteger[]{ result[0], result[1] * 997 / 1000 }; // 0.3% fee
+ }
+ else
+ {
+ var result = MatchOrderInternal(pairKey, isBuy, price, amountIn);
+ return new BigInteger[]{ result[0], result[1] * 997 / 1000 }; // 0.3% fee
+ }
+ }
+
+ ///
+ /// Calculate amountOut with amountIn
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Unsatisfied amountOut and amountIn
+ [Safe]
+ public static BigInteger[] GetAmountIn(UInt160 tokenFrom, UInt160 tokenTo, BigInteger price, BigInteger amountOut)
+ {
+ // Check if exist
+ var pairKey = GetPairKey(tokenFrom, tokenTo);
+ if (BookPaused(pairKey)) return new BigInteger[] { amountOut, 0 };
+ var book = GetOrderBook(pairKey);
+ var isBuy = tokenFrom == book.quoteToken;
+ if (isBuy)
+ {
+ var result = MatchOrderInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997); // 0.3% fee
+ return new BigInteger[]{ result[0] * 997 / 1000, result[1] };
+ }
+ else
+ {
+ var result = MatchQuoteInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997); // 0.3% fee
+ return new BigInteger[]{ (result[0] * 997 + 999) / 1000, result[1] };
+ }
+ }
+ #endregion
+
+ ///
+ /// Accept NEP-17 token
+ /// SwapTokenInForTokenOut
+ ///
+ ///
+ ///
+ ///
+ public static void OnNEP17Payment(UInt160 from, BigInteger amount, object data)
+ {
+
+ }
+
+ ///
+ /// Calculate amountIn to reach AMM price
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static BigInteger GetAmountInTillPrice(bool isBuy, BigInteger price, BigInteger quoteScale, BigInteger reserveIn, BigInteger reserveOut)
+ {
+ Assert(price > 0 && quoteScale > 0 && reserveIn > 0 && reserveOut > 0, "Parameter Invalid");
+ var amountIn = BigInteger.Pow(reserveIn, 2) * 9000000;
+ if (isBuy) amountIn += reserveIn * reserveOut * price * 3988000000000 / quoteScale;
+ else amountIn += reserveIn * reserveOut * quoteScale * 3988000000000 / price;
+ return (amountIn.Sqrt() - reserveIn * 1997000) / 1994000;
+ }
+
+ private static BigInteger GetAmountInTillPriceWithFundFee(bool isBuy, BigInteger price, BigInteger quoteScale, BigInteger reserveIn, BigInteger reserveOut)
+ {
+ Assert(price > 0 && quoteScale > 0 && reserveIn > 0 && reserveOut > 0, "Parameter Invalid");
+ var amountIn = BigInteger.Pow(reserveIn, 2) * 6250000;
+ if (isBuy) amountIn += reserveIn * reserveOut * price * 3986006000000 / quoteScale;
+ else amountIn += reserveIn * reserveOut * quoteScale * 3986006000000 / price;
+ return (amountIn.Sqrt() - reserveIn * 1996500) / 1993003;
+ }
+
+ ///
+ /// AMM GetAmountOut
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static BigInteger GetAmountOut(BigInteger amountIn, BigInteger reserveIn, BigInteger reserveOut)
+ {
+ Assert(amountIn >= 0 && reserveIn > 0 && reserveOut > 0, "AmountIn Must >= 0");
+ var amountInWithFee = amountIn * 997;
+ var numerator = amountInWithFee * reserveOut;
+ var denominator = reserveIn * 1000 + amountInWithFee;
+ var amountOut = numerator / denominator;
+ return amountOut;
+ }
+
+ ///
+ /// AMM GetAmountIn
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static BigInteger GetAmountIn(BigInteger amountOut, BigInteger reserveIn, BigInteger reserveOut)
+ {
+ Assert(amountOut >= 0 && reserveIn > 0 && reserveOut > 0, "AmountOut Must >= 0");
+ var numerator = reserveIn * amountOut * 1000;
+ var denominator = (reserveOut - amountOut) * 997;
+ var amountIn = (numerator / denominator) + 1;
+ return amountIn;
+ }
+
+ ///
+ /// Swap as a router
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void SwapAMM(UInt160 pairContract, UInt160 sender, UInt160 tokenIn, UInt160 tokenOut, BigInteger amountIn, BigInteger amountOut)
+ {
+ SafeTransfer(tokenIn, sender, pairContract, amountIn);
+
+ BigInteger amount0Out = 0;
+ BigInteger amount1Out = 0;
+ if (tokenIn.ToUInteger() < tokenOut.ToUInteger()) amount1Out = amountOut;
+ else amount0Out = amountOut;
+
+ SwapOut(pairContract, amount0Out, amount1Out, sender);
+ }
+ }
+}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/LimitOrder.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/LimitOrder.cs
new file mode 100644
index 0000000..3c2aa46
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/LimitOrder.cs
@@ -0,0 +1,14 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public struct LimitOrder
+ {
+ public UInt160 maker;
+ public BigInteger price;
+ public BigInteger amount;
+ public ByteString nextID;
+ }
+}
\ No newline at end of file
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderBook.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderBook.cs
new file mode 100644
index 0000000..5c8f02c
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderBook.cs
@@ -0,0 +1,18 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public struct OrderBook
+ {
+ public UInt160 baseToken;
+ public UInt160 quoteToken;
+ public BigInteger quoteScale;
+ public BigInteger minOrderAmount;
+ public BigInteger maxOrderAmount;
+
+ public ByteString firstBuyID;
+ public ByteString firstSellID;
+ }
+}
\ No newline at end of file
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderReceipt.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderReceipt.cs
new file mode 100644
index 0000000..90770a5
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/OrderReceipt.cs
@@ -0,0 +1,19 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public struct OrderReceipt
+ {
+ public UInt160 baseToken;
+ public UInt160 quoteToken;
+ public ByteString id;
+ public ulong time;
+ public bool isBuy;
+ public UInt160 maker;
+ public BigInteger price;
+ public BigInteger totalAmount;
+ public BigInteger leftAmount;
+ }
+}
\ No newline at end of file
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/ReservesData.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/ReservesData.cs
new file mode 100644
index 0000000..4a89fbf
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/ReservesData.cs
@@ -0,0 +1,10 @@
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public struct ReservesData
+ {
+ public BigInteger Reserve0;
+ public BigInteger Reserve1;
+ }
+}
diff --git a/Swap/flamingo-contract-swap/flamingo-contract-swap.sln b/Swap/flamingo-contract-swap/flamingo-contract-swap.sln
index 16c7070..cc927ca 100644
--- a/Swap/flamingo-contract-swap/flamingo-contract-swap.sln
+++ b/Swap/flamingo-contract-swap/flamingo-contract-swap.sln
@@ -15,7 +15,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlashLoanTemple", "FlashLoa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlamingoSwapFactory", "FlamingoSwapFactory\FlamingoSwapFactory.csproj", "{7F4FDB74-D11E-45A7-B6A0-C85D4528748A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProxyTemplate", "ProxyTemplate\ProxyTemplate.csproj", "{71582763-E444-4646-BCCD-828BE7859047}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxyTemplate", "ProxyTemplate\ProxyTemplate.csproj", "{71582763-E444-4646-BCCD-828BE7859047}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlamingoSwapOrderBook", "FlamingoSwapOrderBook\FlamingoSwapOrderBook.csproj", "{99811C5F-FF6B-465F-A341-E60B13BBCD1B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -51,6 +53,10 @@ Global
{71582763-E444-4646-BCCD-828BE7859047}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71582763-E444-4646-BCCD-828BE7859047}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71582763-E444-4646-BCCD-828BE7859047}.Release|Any CPU.Build.0 = Release|Any CPU
+ {99811C5F-FF6B-465F-A341-E60B13BBCD1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {99811C5F-FF6B-465F-A341-E60B13BBCD1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {99811C5F-FF6B-465F-A341-E60B13BBCD1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {99811C5F-FF6B-465F-A341-E60B13BBCD1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
From 9eb3589416113d3798c4ffae961855206252e7d9 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Mon, 29 Aug 2022 15:50:01 +0800
Subject: [PATCH 02/16] bugfix and assertion
---
.../FlamingoSwapOrderBookContract.Helper.cs | 9 +-
.../FlamingoSwapOrderBookContract.cs | 87 +++++++++++++------
2 files changed, 67 insertions(+), 29 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
index c9de1a8..8cec70d 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
@@ -254,7 +254,8 @@ private static bool BookPaused(byte[] pairKey)
private static LimitOrder GetOrder(ByteString id)
{
StorageMap orderMap = new(Storage.CurrentReadOnlyContext, OrderMapPrefix);
- return (LimitOrder)StdLib.Deserialize(orderMap.Get(id));
+ var order = orderMap.Get(id);
+ return order is null ? new LimitOrder() : (LimitOrder)StdLib.Deserialize(order);
}
///
@@ -287,7 +288,8 @@ private static void DeleteOrder(ByteString id)
private static OrderReceipt GetReceipt(UInt160 maker, ByteString id)
{
StorageMap receiptMap = new(Storage.CurrentReadOnlyContext, ReceiptMapPrefix);
- return (OrderReceipt)StdLib.Deserialize(receiptMap.Get(maker + id));
+ var receipt = receiptMap.Get(maker + id);
+ return receipt is null ? new OrderReceipt() : (OrderReceipt)StdLib.Deserialize(receipt);
}
///
@@ -335,7 +337,8 @@ private static void DeleteReceipt(UInt160 maker, ByteString id)
private static OrderBook GetOrderBook(byte[] pairKey)
{
StorageMap bookMap = new(Storage.CurrentReadOnlyContext, BookMapPrefix);
- return (OrderBook)StdLib.Deserialize(bookMap.Get(pairKey));
+ var book = bookMap.Get(pairKey);
+ return book is null ? new OrderBook() : (OrderBook)StdLib.Deserialize(book);
}
///
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index c21234c..4405317 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -28,12 +28,19 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger expectBookAmount)
{
+ Assert(amount > 0 && price > 0 && expectBookAmount >= 0 && amount >= expectBookAmount, "Invalid Parameters");
+ var pairKey = GetPairKey(tokenA, tokenB);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(Runtime.CheckWitness(sender), "No Authorization");
+ Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
+
// Market order
- var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrder(tokenA, tokenB, sender, isBuy, price, expectBookAmount) : amount;
+ var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : amount;
+ if (leftAmount == 0) return null;
// Swap AMM
- var pairKey = GetPairKey(tokenA, tokenB);
var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
var hasFundFee = HasFundAddress(pairContract);
@@ -83,7 +90,7 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
id = id,
time = Runtime.Time,
isBuy = isBuy,
- totalAmount = leftAmount
+ totalAmount = amount
});
onOrderStatusChanged(book.baseToken, book.quoteToken, id, !!isBuy, sender, price, leftAmount);
return id;
@@ -99,16 +106,28 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
///
///
///
- public static void RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger expectBookAmount)
+ ///
+ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger expectBookAmount)
{
- // Market order
+ Assert(amount > 0 && slippage > 0 && expectBookAmount >= 0 && amount >= expectBookAmount, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(Runtime.CheckWitness(sender), "No Authorization");
+ Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
+
+ // Market order
var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var price = slippage * book.quoteScale / amount;
- var balanceBefore = GetBalanceOf(isBuy ? book.quoteToken : book.quoteToken, sender);
- var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrder(tokenA, tokenB, sender, isBuy, price, expectBookAmount) : amount;
- var balanceAfter = GetBalanceOf(isBuy ? book.quoteToken : book.quoteToken, sender);
+ var balanceBefore = GetBalanceOf(book.quoteToken, sender);
+ var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : amount;
+ var balanceAfter = GetBalanceOf(book.quoteToken, sender);
+ if (leftAmount == 0)
+ {
+ Assert(isBuy ? balanceBefore - balanceAfter <= slippage : balanceAfter - balanceBefore >= slippage, "Insufficient Slippage");
+ return true;
+ }
// Swap AMM
var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
@@ -119,6 +138,7 @@ public static void RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 send
if (amountOut > 0) SwapAMM(pairContract, sender, isBuy ? book.quoteToken : book.baseToken, isBuy ? book.baseToken : book.quoteToken, amountIn, amountOut);
Assert(isBuy ? amountIn + balanceBefore - balanceAfter <= slippage : amountOut + balanceAfter - balanceBefore >= slippage, "Insufficient Slippage");
+ return true;
}
#region DEX like API
@@ -130,7 +150,8 @@ public static void RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 send
///
///
///
- public static void RegisterOrderBook(UInt160 baseToken, UInt160 quoteToken, uint quoteDecimals, BigInteger minOrderAmount, BigInteger maxOrderAmount)
+ ///
+ public static bool RegisterOrderBook(UInt160 baseToken, UInt160 quoteToken, uint quoteDecimals, BigInteger minOrderAmount, BigInteger maxOrderAmount)
{
Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
Assert(baseToken != quoteToken, "Invalid Trade Pair");
@@ -148,6 +169,7 @@ public static void RegisterOrderBook(UInt160 baseToken, UInt160 quoteToken, uint
maxOrderAmount = maxOrderAmount
});
onBookStatusChanged(baseToken, quoteToken, quoteScale, minOrderAmount, maxOrderAmount, BookPaused(pairKey));
+ return true;
}
///
@@ -156,9 +178,9 @@ public static void RegisterOrderBook(UInt160 baseToken, UInt160 quoteToken, uint
///
///
///
- public static void SetMinOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger minOrderAmount)
+ ///
+ public static bool SetMinOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger minOrderAmount)
{
- Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
Assert(minOrderAmount > 0, "Invalid Amount Limit");
Assert(Verify(), "No Authorization");
@@ -170,6 +192,7 @@ public static void SetMinOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigI
book.minOrderAmount = minOrderAmount;
SetOrderBook(pairKey, book);
onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ return true;
}
///
@@ -178,9 +201,9 @@ public static void SetMinOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigI
///
///
///
- public static void SetMaxOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger maxOrderAmount)
+ ///
+ public static bool SetMaxOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger maxOrderAmount)
{
- Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
Assert(maxOrderAmount > 0, "Invalid Amount Limit");
Assert(Verify(), "No Authorization");
@@ -192,6 +215,7 @@ public static void SetMaxOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigI
book.maxOrderAmount = maxOrderAmount;
SetOrderBook(pairKey, book);
onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ return true;
}
///
@@ -199,9 +223,9 @@ public static void SetMaxOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigI
///
///
///
- public static void PauseOrderBook(UInt160 baseToken, UInt160 quoteToken)
+ ///
+ public static bool PauseOrderBook(UInt160 baseToken, UInt160 quoteToken)
{
- Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
Assert(Verify(), "No Authorization");
var pairKey = GetPairKey(baseToken, quoteToken);
@@ -211,6 +235,7 @@ public static void PauseOrderBook(UInt160 baseToken, UInt160 quoteToken)
SetPaused(pairKey);
onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ return true;
}
///
@@ -218,9 +243,9 @@ public static void PauseOrderBook(UInt160 baseToken, UInt160 quoteToken)
///
///
///
- public static void ResumeOrderBook(UInt160 baseToken, UInt160 quoteToken)
+ ///
+ public static bool ResumeOrderBook(UInt160 baseToken, UInt160 quoteToken)
{
- Assert(baseToken.IsAddress() && quoteToken.IsAddress(), "Invalid Address");
Assert(Verify(), "No Authorization");
var pairKey = GetPairKey(baseToken, quoteToken);
@@ -230,6 +255,7 @@ public static void ResumeOrderBook(UInt160 baseToken, UInt160 quoteToken)
RemovePaused(pairKey);
onBookStatusChanged(book.baseToken, book.quoteToken, book.quoteScale, book.minOrderAmount, book.maxOrderAmount, BookPaused(pairKey));
+ return true;
}
///
@@ -253,6 +279,7 @@ public static ByteString AddLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 m
// Deposit token
var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
if (amount < book.minOrderAmount || amount > book.maxOrderAmount) return null;
var me = Runtime.ExecutingScriptHash;
if (isBuy) SafeTransfer(book.quoteToken, maker, me, amount * price / book.quoteScale);
@@ -291,7 +318,9 @@ public static ByteString AddLimitOrderAt(ByteString parentID, UInt160 maker, Big
{
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
- var receipt = GetReceipt(GetOrder(parentID).maker, parentID);
+ var parent = GetOrder(parentID);
+ Assert(parent.maker.IsAddress(), "Parent Not Exists");
+ var receipt = GetReceipt(parent.maker, parentID);
var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
Assert(!BookPaused(pairKey), "Book is Paused");
Assert(Runtime.CheckWitness(maker), "No Authorization");
@@ -334,12 +363,13 @@ public static ByteString AddLimitOrderAt(ByteString parentID, UInt160 maker, Big
///
///
///
- public static void CancelOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteString id)
+ ///
+ public static bool CancelOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteString id)
{
// Check if exist
var pairKey = GetPairKey(tokenA, tokenB);
- Assert(OrderExists(id), "Order Not Exists");
var order = GetOrder(id);
+ Assert(order.maker.IsAddress(), "Order Not Exists");
Assert(Runtime.CheckWitness(order.maker), "No Authorization");
// Do remove
@@ -354,6 +384,7 @@ public static void CancelOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteS
var me = Runtime.ExecutingScriptHash;
if (isBuy) SafeTransfer(book.quoteToken, me, order.maker, order.amount * order.price / book.quoteScale);
else SafeTransfer(book.baseToken, me, order.maker, order.amount);
+ return true;
}
///
@@ -364,9 +395,8 @@ public static void CancelOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteS
///
public static bool CancelOrderAt(ByteString parentID, ByteString id)
{
- if (!OrderExists(id)) return false;
- Assert(OrderExists(parentID), "Parent Order Not Exists");
var order = GetOrder(id);
+ Assert(order.maker.IsAddress(), "Order Not Exists");
Assert(Runtime.CheckWitness(order.maker), "No Authorization");
// Do remove
@@ -640,6 +670,7 @@ private static BigInteger DealMarketOrderInternal(byte[] pairKey, UInt160 taker,
{
// Check if can deal
var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var firstID = isBuy ? book.firstSellID : book.firstBuyID;
if (firstID is null) return leftAmount;
var firstOrder = GetOrder(firstID);
@@ -765,16 +796,19 @@ private static BigInteger DealMarketOrderInternal(byte[] pairKey, UInt160 taker,
///
///
///
- public static void DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteString id)
+ ///
+ public static bool DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteString id)
{
+ // Check parameters
Assert(Runtime.CheckWitness(taker), "No Authorization");
-
- // Do deal
var order = GetOrder(id);
- var me = Runtime.ExecutingScriptHash;
+ Assert(order.maker.IsAddress(), "Order Not Exists");
var receipt = GetReceipt(order.maker, id);
var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
Assert(!BookPaused(pairKey), "Book is Paused");
+
+ // Do deal
+ var me = Runtime.ExecutingScriptHash;
var quoteScale = GetOrderBook(pairKey).quoteScale;
var fundAddress = GetFundAddress();
@@ -810,6 +844,7 @@ public static void DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteStr
SafeTransfer(receipt.baseToken, me, fundAddress, baseFee);
SafeTransfer(receipt.quoteToken, me, fundAddress, quoteFee);
}
+ return true;
}
///
From 27cd5307d5b4f3bd2221c3571660250a786ca842 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Mon, 29 Aug 2022 17:38:57 +0800
Subject: [PATCH 03/16] fix amount
---
.../FlamingoSwapOrderBookContract.cs | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 4405317..bdcfb4e 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -35,7 +35,9 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
// Market order
- var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : amount;
+ var leftAmount = amount;
+ if (isBuy) leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, (expectBookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
+ else leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : 0;
if (leftAmount == 0) return null;
// Swap AMM
@@ -120,8 +122,10 @@ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 send
Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var price = slippage * book.quoteScale / amount;
+ var leftAmount = amount;
var balanceBefore = GetBalanceOf(book.quoteToken, sender);
- var leftAmount = expectBookAmount > 0 ? amount - expectBookAmount + DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : amount;
+ if (isBuy) leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, (expectBookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
+ else leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : 0;
var balanceAfter = GetBalanceOf(book.quoteToken, sender);
if (leftAmount == 0)
{
From 6d65e40cb8501ffcd9cc68324124cca1af732256 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Mon, 29 Aug 2022 18:08:11 +0800
Subject: [PATCH 04/16] method name
---
.../FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index bdcfb4e..a7629b1 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -446,7 +446,7 @@ public static ByteString GetParentOrderID(UInt160 tokenA, UInt160 tokenB, bool i
///
///
[Safe]
- public static OrderReceipt[] GetNOrders(UInt160 tokenA, UInt160 tokenB, bool isBuy, uint pos, uint n)
+ public static OrderReceipt[] GetFirstNOrders(UInt160 tokenA, UInt160 tokenB, bool isBuy, uint pos, uint n)
{
var results = new OrderReceipt[n];
From f8055538c9f91cae14e72cd50d5a05979172ff50 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Thu, 1 Sep 2022 17:25:38 +0800
Subject: [PATCH 05/16] add claim fee payment, add getters, and optimize
---
.../FlamingoSwapOrderBookContract.Admin.cs | 1 +
.../FlamingoSwapOrderBookContract.Helper.cs | 33 ++++++
.../FlamingoSwapOrderBookContract.cs | 108 +++++++++++++++---
3 files changed, 125 insertions(+), 17 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
index 54be322..bb39c66 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
@@ -30,6 +30,7 @@ public partial class FlamingoSwapOrderBookContract
private static readonly byte[] OrderMapPrefix = new byte[] { 0x02 };
private static readonly byte[] ReceiptMapPrefix = new byte[] { 0x03 };
private static readonly byte[] PauseMapPrefix = new byte[] { 0x04 };
+ private static readonly byte[] FeeMapPrefix = new byte[] { 0x05 };
// When this contract address is included in the transaction signature,
// this method will be triggered as a VerificationTrigger to verify that the signature is correct.
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
index 8cec70d..30c6aec 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
@@ -352,6 +352,39 @@ private static void SetOrderBook(byte[] pairKey, OrderBook book)
bookMap.Put(pairKey, StdLib.Serialize(book));
}
+ ///
+ /// Stage the fundfee payment for later claim
+ ///
+ ///
+ ///
+ private static void StageFundFee(UInt160 token, BigInteger amount)
+ {
+ Assert(amount >= 0, "Invalid Fee Amount");
+ StorageMap feeMap = new(Storage.CurrentContext, FeeMapPrefix);
+ feeMap.Put(token, (BigInteger)feeMap.Get(token) + amount);
+ }
+
+ ///
+ /// Get staged fundfee amount
+ ///
+ ///
+ ///
+ private static BigInteger GetStagedFundFee(UInt160 token)
+ {
+ StorageMap feeMap = new(Storage.CurrentReadOnlyContext, FeeMapPrefix);
+ return (BigInteger)feeMap.Get(token);
+ }
+
+ ///
+ /// Reset the staged fundfee amount to 0
+ ///
+ ///
+ private static void CleanStagedFundFee(UInt160 token)
+ {
+ StorageMap feeMap = new(Storage.CurrentContext, FeeMapPrefix);
+ feeMap.Delete(token);
+ }
+
///
/// Find a random number as order ID
///
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index a7629b1..dc7abdf 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -122,26 +122,30 @@ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 send
Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var price = slippage * book.quoteScale / amount;
- var leftAmount = amount;
- var balanceBefore = GetBalanceOf(book.quoteToken, sender);
- if (isBuy) leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, (expectBookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
- else leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : 0;
- var balanceAfter = GetBalanceOf(book.quoteToken, sender);
- if (leftAmount == 0)
+ var quoteAmount = BigInteger.Zero;
+ if (expectBookAmount > 0)
{
- Assert(isBuy ? balanceBefore - balanceAfter <= slippage : balanceAfter - balanceBefore >= slippage, "Insufficient Slippage");
- return true;
+ var balanceBefore = GetBalanceOf(book.quoteToken, sender);
+ if (isBuy) amount -= expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, (expectBookAmount * 1000 + 996) / 997, false) * 997 / 1000;
+ else amount -= expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false);
+ var balanceAfter = GetBalanceOf(book.quoteToken, sender);
+ quoteAmount = isBuy ? balanceBefore - balanceAfter : balanceAfter - balanceBefore;
+ if (amount == 0)
+ {
+ Assert(isBuy ? quoteAmount <= slippage : quoteAmount >= slippage, "Insufficient Slippage");
+ return true;
+ }
}
// Swap AMM
var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
var ammReverse = GetReserves(pairContract, book.baseToken, book.quoteToken);
- var amountIn = isBuy ? GetAmountIn(leftAmount, ammReverse[1], ammReverse[0]) : leftAmount;
- var amountOut = isBuy ? leftAmount : GetAmountOut(leftAmount, ammReverse[0], ammReverse[1]);
+ var amountIn = isBuy ? GetAmountIn(amount, ammReverse[1], ammReverse[0]) : amount;
+ var amountOut = isBuy ? amount : GetAmountOut(amount, ammReverse[0], ammReverse[1]);
if (amountOut > 0) SwapAMM(pairContract, sender, isBuy ? book.quoteToken : book.baseToken, isBuy ? book.baseToken : book.quoteToken, amountIn, amountOut);
- Assert(isBuy ? amountIn + balanceBefore - balanceAfter <= slippage : amountOut + balanceAfter - balanceBefore >= slippage, "Insufficient Slippage");
+ Assert(isBuy ? amountIn + quoteAmount <= slippage : amountOut + quoteAmount >= slippage, "Insufficient Slippage");
return true;
}
@@ -476,6 +480,30 @@ public static OrderReceipt[] GetFirstNOrders(UInt160 tokenA, UInt160 tokenB, boo
return results;
}
+ [Safe]
+ public static OrderReceipt[] GetFirstNOrders(ByteString orderID, uint n)
+ {
+ var results = new OrderReceipt[n];
+
+ var currentOrderID = orderID;
+ var currentOrder = GetOrder(currentOrderID);
+ if (!currentOrder.maker.IsAddress()) return results;
+ for (int i = 0; i < n; i++)
+ {
+ var receipt = GetReceipt(currentOrder.maker, currentOrderID);
+ receipt.maker = currentOrder.maker;
+ receipt.price = currentOrder.price;
+ receipt.leftAmount = currentOrder.amount;
+ results[i] = receipt;
+
+ if (currentOrder.nextID is null) break;
+
+ currentOrderID = currentOrder.nextID;
+ currentOrder = GetOrder(currentOrder.nextID);
+ }
+ return results;
+ }
+
///
/// Get all orders and their details of maker
///
@@ -488,7 +516,7 @@ public static OrderReceipt[] GetOrdersOf(UInt160 maker, uint pos, uint n)
{
var results = new OrderReceipt[n];
var iterator = ReceiptsOf(maker);
- // Makeup details
+ // Make up details
for (int i = 0; i < pos + n; i++)
{
if (iterator.Next() && i >= pos)
@@ -503,6 +531,36 @@ public static OrderReceipt[] GetOrdersOf(UInt160 maker, uint pos, uint n)
return results;
}
+ [Safe]
+ public static OrderReceipt[] GetOrdersOf(ByteString orderID, uint n)
+ {
+ var results = new OrderReceipt[n];
+ var order = GetOrder(orderID);
+ if (!order.maker.IsAddress()) return results;
+ var iterator = ReceiptsOf(order.maker);
+ // Make up details
+ while (iterator.Next())
+ {
+ var receipt = (OrderReceipt)iterator.Value;
+ if (receipt.id.Equals(orderID))
+ {
+ for (int i = 0; i < n; i++)
+ {
+ results[i] = receipt;
+ order = GetOrder(results[i].id);
+ results[i].maker = order.maker;
+ results[i].price = order.price;
+ results[i].leftAmount = order.amount;
+
+ if (!iterator.Next()) break;
+ receipt = (OrderReceipt)iterator.Value;
+ }
+ return results;
+ }
+ }
+ return results;
+ }
+
///
/// Get the total reverse of tradable orders with an expected price
///
@@ -785,12 +843,9 @@ private static BigInteger DealMarketOrderInternal(byte[] pairKey, UInt160 taker,
foreach (var toAddress in makerReceive.Keys) SafeTransfer(book.baseToken, me, toAddress, makerReceive[toAddress]);
}
+ StageFundFee(book.baseToken, baseFee);
+ StageFundFee(book.quoteToken, quoteFee);
- if (fundAddress is not null)
- {
- SafeTransfer(book.quoteToken, me, fundAddress, quoteFee);
- SafeTransfer(book.baseToken, me, fundAddress, baseFee);
- }
return leftAmount;
}
@@ -851,6 +906,25 @@ public static bool DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteStr
return true;
}
+ ///
+ /// Claim the staged fundfee payment to fund address
+ ///
+ ///
+ ///
+ public static BigInteger ClaimFundFee(UInt160 token)
+ {
+ var fundAddress = GetFundAddress();
+ if (fundAddress is null) return 0;
+ var amount = GetStagedFundFee(token);
+ var me = Runtime.ExecutingScriptHash;
+ if (amount > 0)
+ {
+ CleanStagedFundFee(token);
+ SafeTransfer(token, me, fundAddress, amount);
+ }
+ return amount;
+ }
+
///
/// Price reporter
///
From 0a6970167d0b02ecdd23d41e89950cf62a01f2b4 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Thu, 1 Sep 2022 17:44:46 +0800
Subject: [PATCH 06/16] summarize claim fund
---
.../FlamingoSwapOrderBookContract.cs | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index dc7abdf..734bd93 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -909,20 +909,23 @@ public static bool DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteStr
///
/// Claim the staged fundfee payment to fund address
///
- ///
+ ///
///
- public static BigInteger ClaimFundFee(UInt160 token)
+ public static bool ClaimFundFee(UInt160[] tokens)
{
var fundAddress = GetFundAddress();
- if (fundAddress is null) return 0;
- var amount = GetStagedFundFee(token);
+ if (fundAddress is null) return false;
var me = Runtime.ExecutingScriptHash;
- if (amount > 0)
+ foreach (var token in tokens)
{
- CleanStagedFundFee(token);
- SafeTransfer(token, me, fundAddress, amount);
+ var amount = GetStagedFundFee(token);
+ if (amount > 0)
+ {
+ CleanStagedFundFee(token);
+ SafeTransfer(token, me, fundAddress, amount);
+ }
}
- return amount;
+ return true;
}
///
From b25056ff8f771616a43ea389dc754c21af918ac4 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 2 Sep 2022 10:55:15 +0800
Subject: [PATCH 07/16] remove duplicated assert, add deadline, some fix
---
.../FlamingoSwapOrderBookContract.cs | 16 +++++++--------
.../FlamingoSwapRouterContract.cs | 20 ++++++++-----------
2 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 734bd93..e4009a2 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -25,14 +25,16 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
///
///
+ ///
///
- public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger expectBookAmount)
+ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger expectBookAmount, BigInteger deadLine)
{
Assert(amount > 0 && price > 0 && expectBookAmount >= 0 && amount >= expectBookAmount, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
Assert(!BookPaused(pairKey), "Book is Paused");
Assert(Runtime.CheckWitness(sender), "No Authorization");
Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
+ Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the deadline");
// Market order
var leftAmount = amount;
@@ -108,14 +110,15 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
///
///
///
+ ///
///
- public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger expectBookAmount)
+ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger expectBookAmount, BigInteger deadLine)
{
Assert(amount > 0 && slippage > 0 && expectBookAmount >= 0 && amount >= expectBookAmount, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
Assert(!BookPaused(pairKey), "Book is Paused");
Assert(Runtime.CheckWitness(sender), "No Authorization");
- Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
+ Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the deadline");
// Market order
var book = GetOrderBook(pairKey);
@@ -898,11 +901,8 @@ public static bool DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteStr
SafeTransfer(receipt.quoteToken, me, order.maker, quotePayment);
}
- if (fundAddress is not null)
- {
- SafeTransfer(receipt.baseToken, me, fundAddress, baseFee);
- SafeTransfer(receipt.quoteToken, me, fundAddress, quoteFee);
- }
+ StageFundFee(receipt.baseToken, baseFee);
+ StageFundFee(receipt.quoteToken, quoteFee);
return true;
}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapRouter/FlamingoSwapRouterContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapRouter/FlamingoSwapRouterContract.cs
index 5bd733d..37b108f 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapRouter/FlamingoSwapRouterContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapRouter/FlamingoSwapRouterContract.cs
@@ -31,7 +31,7 @@ public partial class FlamingoSwapRouterContract : SmartContract
public static BigInteger[] AddLiquidity(UInt160 sender, UInt160 tokenA, UInt160 tokenB, BigInteger amountADesired, BigInteger amountBDesired, BigInteger amountAMin, BigInteger amountBMin, BigInteger deadLine)
{
//验证参数
- Assert(sender.IsValid && tokenA.IsValid && tokenB.IsValid && amountADesired >= 0 && amountBDesired >= 0 && amountAMin >= 0 && amountBMin >= 0 && deadLine > 0, "Invalid Parameters");
+ Assert(amountADesired >= 0 && amountBDesired >= 0 && amountAMin >= 0 && amountBMin >= 0, "Invalid Parameters");
//验证权限
Assert(Runtime.CheckWitness(sender), "Forbidden");
//看看有没有超过最后期限
@@ -80,7 +80,7 @@ public static BigInteger[] AddLiquidity(UInt160 sender, UInt160 tokenA, UInt160
public static BigInteger[] AddLiquidity(UInt160 tokenA, UInt160 tokenB, BigInteger amountADesired, BigInteger amountBDesired, BigInteger amountAMin, BigInteger amountBMin, BigInteger deadLine)
{
//验证参数
- Assert(tokenA.IsValid && tokenB.IsValid && amountADesired >= 0 && amountBDesired >= 0 && amountAMin >= 0 && amountBMin >= 0 && deadLine > 0, "Invalid Parameters");
+ Assert(amountADesired >= 0 && amountBDesired >= 0 && amountAMin >= 0 && amountBMin >= 0, "Invalid Parameters");
//验证权限
var caller = Runtime.CallingScriptHash;
Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
@@ -142,7 +142,7 @@ public static BigInteger[] AddLiquidity(UInt160 tokenA, UInt160 tokenB, BigInteg
public static BigInteger[] RemoveLiquidity(UInt160 sender, UInt160 tokenA, UInt160 tokenB, BigInteger liquidity, BigInteger amountAMin, BigInteger amountBMin, BigInteger deadLine)
{
//验证参数
- Assert(sender.IsValid && tokenA.IsValid && tokenB.IsValid && liquidity >= 0 && amountAMin >= 0 && amountBMin >= 0 && deadLine > 0, "Invalid Parameters");
+ Assert(liquidity > 0 && amountAMin >= 0 && amountBMin >= 0, "Invalid Parameters");
//验证权限
Assert(Runtime.CheckWitness(sender), "Forbidden");
//看看有没有超过最后期限
@@ -166,7 +166,7 @@ public static BigInteger[] RemoveLiquidity(UInt160 sender, UInt160 tokenA, UInt1
public static BigInteger[] RemoveLiquidity(UInt160 tokenA, UInt160 tokenB, BigInteger liquidity, BigInteger amountAMin, BigInteger amountBMin, BigInteger deadLine)
{
//验证参数
- Assert(tokenA.IsValid && tokenB.IsValid && liquidity > 0 && amountAMin >= 0 && amountBMin >= 0 && deadLine > 0, "Invalid Parameters");
+ Assert(liquidity > 0 && amountAMin >= 0 && amountBMin >= 0, "Invalid Parameters");
//验证权限
var caller = Runtime.CallingScriptHash;
Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
@@ -214,8 +214,6 @@ public static BigInteger Quote(BigInteger amountA, BigInteger reserveA, BigInteg
///
public static BigInteger GetAmountOut(BigInteger amountIn, BigInteger reserveIn, BigInteger reserveOut)
{
- // Assert(amountIn > 0, "amountIn should be positive number");
- // Assert(reserveIn > 0 && reserveOut > 0, "reserve should be positive number");
Assert(amountIn > 0 && reserveIn > 0 && reserveOut > 0, "AmountIn Must > 0");
var amountInWithFee = amountIn * 997;
@@ -234,8 +232,6 @@ public static BigInteger GetAmountOut(BigInteger amountIn, BigInteger reserveIn,
///
public static BigInteger GetAmountIn(BigInteger amountOut, BigInteger reserveIn, BigInteger reserveOut)
{
- //Assert(amountOut > 0, "amountOut should be positive number");
- //Assert(reserveIn > 0 && reserveOut > 0, "reserve should be positive number");
Assert(amountOut > 0 && reserveIn > 0 && reserveOut > 0, "AmountOut Must > 0");
var numerator = reserveIn * amountOut * 1000;
var denominator = (reserveOut - amountOut) * 997;
@@ -320,7 +316,7 @@ public static void OnNEP17Payment(UInt160 sender, BigInteger amountIn, object da
public static bool SwapTokenInForTokenOut(UInt160 sender, BigInteger amountIn, BigInteger amountOutMin, UInt160[] paths, BigInteger deadLine)
{
//验证参数
- Assert(sender.IsValid && amountIn > 0 && amountOutMin >= 0 && paths.Length >= 2 && deadLine > 0, "Invalid Parameters");
+ Assert(amountOutMin >= 0, "Invalid Parameters");
//验证权限
Assert(Runtime.CheckWitness(sender), "Forbidden");
//看看有没有超过最后期限
@@ -339,7 +335,7 @@ public static bool SwapTokenInForTokenOut(UInt160 sender, BigInteger amountIn, B
public static bool SwapTokenInForTokenOut(BigInteger amountIn, BigInteger amountOutMin, UInt160[] paths, BigInteger deadLine)
{
//验证参数
- Assert(amountIn > 0 && amountOutMin >= 0 && paths.Length >= 2 && deadLine > 0, "Invalid Parameters");
+ Assert(amountOutMin >= 0, "Invalid Parameters");
//验证权限
var caller = Runtime.CallingScriptHash;
Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
@@ -369,7 +365,7 @@ public static bool SwapTokenInForTokenOut(BigInteger amountIn, BigInteger amount
public static bool SwapTokenOutForTokenIn(UInt160 sender, BigInteger amountOut, BigInteger amountInMax, UInt160[] paths, BigInteger deadLine)
{
//验证参数
- Assert(sender.IsValid && amountOut > 0 && amountInMax >= 0 && paths.Length >= 2 && deadLine > 0, "Invalid Parameters");
+ Assert(amountInMax >= 0, "Invalid Parameters");
//验证权限
Assert(Runtime.CheckWitness(sender), "Forbidden");
//看看有没有超过最后期限
@@ -388,7 +384,7 @@ public static bool SwapTokenOutForTokenIn(UInt160 sender, BigInteger amountOut,
public static bool SwapTokenOutForTokenIn(BigInteger amountOut, BigInteger amountInMax, UInt160[] paths, BigInteger deadLine)
{
//验证参数
- Assert(amountOut > 0 && amountInMax >= 0 && paths.Length >= 2 && deadLine > 0, "Invalid Parameters");
+ Assert(amountInMax >= 0, "Invalid Parameters");
//验证权限
var caller = Runtime.CallingScriptHash;
Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
From 39fa2490ef25826ff06443562fe5d607ade7a44b Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 2 Sep 2022 17:16:03 +0800
Subject: [PATCH 08/16] [Safe] BookTradable
---
.../FlamingoSwapOrderBookContract.cs | 46 +++++++++++++++++--
1 file changed, 43 insertions(+), 3 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index e4009a2..9bebd2c 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -444,7 +444,7 @@ public static ByteString GetParentOrderID(UInt160 tokenA, UInt160 tokenB, bool i
}
///
- /// Get first N limit orders and their details
+ /// Get first N limit orders and their details, start from pos
///
///
///
@@ -483,6 +483,12 @@ public static OrderReceipt[] GetFirstNOrders(UInt160 tokenA, UInt160 tokenB, boo
return results;
}
+ ///
+ /// Get first N limit orders and their details, start from orderID
+ ///
+ ///
+ ///
+ ///
[Safe]
public static OrderReceipt[] GetFirstNOrders(ByteString orderID, uint n)
{
@@ -508,7 +514,7 @@ public static OrderReceipt[] GetFirstNOrders(ByteString orderID, uint n)
}
///
- /// Get all orders and their details of maker
+ /// Get N orders of maker and their details, start from pos
///
///
///
@@ -534,6 +540,12 @@ public static OrderReceipt[] GetOrdersOf(UInt160 maker, uint pos, uint n)
return results;
}
+ ///
+ /// Get N orders of maker and their details, start from orderID
+ ///
+ ///
+ ///
+ ///
[Safe]
public static OrderReceipt[] GetOrdersOf(ByteString orderID, uint n)
{
@@ -698,7 +710,7 @@ private static BigInteger[] MatchQuoteInternal(byte[] pairKey, bool isBuy, BigIn
}
///
- /// Try to make a market deal with orderbook
+ /// Try to make a market deal with orderbook, taker is not a contract
///
///
///
@@ -719,6 +731,15 @@ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160
return DealMarketOrderInternal(pairKey, taker, isBuy, price, amount, false);
}
+ ///
+ /// Try to make a market deal with orderbook, taker is a contract
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Left amount
public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
{
// Check parameters
@@ -945,12 +966,31 @@ public static BigInteger GetMarketPrice(UInt160 tokenA, UInt160 tokenB, bool isB
return GetOrder(firstID).price;
}
+ ///
+ /// Get trade pair details
+ ///
+ ///
+ ///
+ ///
[Safe]
public static OrderBook GetBookInfo(UInt160 tokenA, UInt160 tokenB)
{
var pairKey = GetPairKey(tokenA, tokenB);
return GetOrderBook(pairKey);
}
+
+ ///
+ /// Check if a pair of token is tradable
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static bool BookTradable(UInt160 tokenA, UInt160 tokenB)
+ {
+ var pairKey = GetPairKey(tokenA, tokenB);
+ return BookExists(pairKey) && !BookPaused(pairKey);
+ }
#endregion
#region AMM like API
From 498f21d011297dfe64f9aef9c1b494ade8f585c2 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Thu, 22 Sep 2022 18:06:38 +0800
Subject: [PATCH 09/16] add price limit for book part
---
.../FlamingoSwapOrderBookContract.cs | 50 ++++++++++---------
1 file changed, 27 insertions(+), 23 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 9bebd2c..f21f7c0 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -22,24 +22,26 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
///
///
- ///
- ///
- ///
+ /// Total buy(real get)/sell amount of the limit order
+ /// Price limit of the order
+ /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Price limit of bookAmount part
///
///
- public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger expectBookAmount, BigInteger deadLine)
+ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
{
- Assert(amount > 0 && price > 0 && expectBookAmount >= 0 && amount >= expectBookAmount, "Invalid Parameters");
+ Assert(amount > 0 && price > 0 && bookAmount >= 0 && amount >= bookAmount, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(!BookPaused(pairKey), "Book Is Paused");
Assert(Runtime.CheckWitness(sender), "No Authorization");
Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
- Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the deadline");
+ if (bookAmount > 0) Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
+ Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded The Deadline");
// Market order
var leftAmount = amount;
- if (isBuy) leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, (expectBookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
- else leftAmount -= expectBookAmount > 0 ? expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false) : 0;
+ if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
+ else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false) : 0;
if (leftAmount == 0) return null;
// Swap AMM
@@ -107,30 +109,32 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
///
///
///
- ///
- ///
- ///
+ /// Total buy(real get)/sell amount of the limit order
+ /// The amount limit of final payment/receive(real get)
+ /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Price limit of bookAmount part
///
///
- public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger expectBookAmount, BigInteger deadLine)
+ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
{
- Assert(amount > 0 && slippage > 0 && expectBookAmount >= 0 && amount >= expectBookAmount, "Invalid Parameters");
+ Assert(amount > 0 && slippage > 0 && bookAmount >= 0 && amount >= bookAmount, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
Assert(!BookPaused(pairKey), "Book is Paused");
Assert(Runtime.CheckWitness(sender), "No Authorization");
- Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the deadline");
+ Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the Deadline");
// Market order
var book = GetOrderBook(pairKey);
Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var price = slippage * book.quoteScale / amount;
+ if (bookAmount > 0) Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
var quoteAmount = BigInteger.Zero;
- if (expectBookAmount > 0)
+ if (bookAmount > 0)
{
var balanceBefore = GetBalanceOf(book.quoteToken, sender);
- if (isBuy) amount -= expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, (expectBookAmount * 1000 + 996) / 997, false) * 997 / 1000;
- else amount -= expectBookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, price, expectBookAmount, false);
+ if (isBuy) amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000;
+ else amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
var balanceAfter = GetBalanceOf(book.quoteToken, sender);
quoteAmount = isBuy ? balanceBefore - balanceAfter : balanceAfter - balanceBefore;
if (amount == 0)
@@ -284,7 +288,7 @@ public static ByteString AddLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 m
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(!BookPaused(pairKey), "Book Is Paused");
Assert(Runtime.CheckWitness(maker), "No Authorization");
Assert(ContractManagement.GetContract(maker) == null, "Forbidden");
@@ -333,7 +337,7 @@ public static ByteString AddLimitOrderAt(ByteString parentID, UInt160 maker, Big
Assert(parent.maker.IsAddress(), "Parent Not Exists");
var receipt = GetReceipt(parent.maker, parentID);
var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(!BookPaused(pairKey), "Book Is Paused");
Assert(Runtime.CheckWitness(maker), "No Authorization");
Assert(ContractManagement.GetContract(maker) == null, "Forbidden");
@@ -724,7 +728,7 @@ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(!BookPaused(pairKey), "Book Is Paused");
Assert(Runtime.CheckWitness(taker), "No Authorization");
Assert(ContractManagement.GetContract(taker) == null, "Forbidden");
@@ -745,7 +749,7 @@ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool is
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(!BookPaused(pairKey), "Book Is Paused");
var caller = Runtime.CallingScriptHash;
Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
@@ -888,7 +892,7 @@ public static bool DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteStr
Assert(order.maker.IsAddress(), "Order Not Exists");
var receipt = GetReceipt(order.maker, id);
var pairKey = GetPairKey(receipt.baseToken, receipt.quoteToken);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(!BookPaused(pairKey), "Book Is Paused");
// Do deal
var me = Runtime.ExecutingScriptHash;
From 9fb405930a744d446888985b2a622412b684a115 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 23 Sep 2022 11:50:21 +0800
Subject: [PATCH 10/16] change bookAmount to in-book amount
---
.../FlamingoSwapOrderBookContract.cs | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index f21f7c0..6dd7193 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -22,9 +22,9 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
///
///
- /// Total buy(real get)/sell amount of the limit order
+ /// Total buy/sell(real get/pay) amount of the limit order
/// Price limit of the order
- /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Expected amount to buy/sell(in-book amount) in book before amm
/// Price limit of bookAmount part
///
///
@@ -40,7 +40,7 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
// Market order
var leftAmount = amount;
- if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
+ if (isBuy) leftAmount -= bookAmount > 0 ? (bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false)) * 997 / 1000 : 0;
else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false) : 0;
if (leftAmount == 0) return null;
@@ -109,9 +109,9 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
///
///
///
- /// Total buy(real get)/sell amount of the limit order
- /// The amount limit of final payment/receive(real get)
- /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Total buy/sell(real get/pay) amount of the limit order
+ /// The amount limit of final receive/payment(real get/pay)
+ /// Expected amount to buy/sell(in-book amount) in book before amm
/// Price limit of bookAmount part
///
///
@@ -133,7 +133,7 @@ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 send
if (bookAmount > 0)
{
var balanceBefore = GetBalanceOf(book.quoteToken, sender);
- if (isBuy) amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000;
+ if (isBuy) amount -= (bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false)) * 997 / 1000;
else amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
var balanceAfter = GetBalanceOf(book.quoteToken, sender);
quoteAmount = isBuy ? balanceBefore - balanceAfter : balanceAfter - balanceBefore;
From 4a01d27666b7c1c50a5e8accdb260f32ecf8ab13 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 23 Sep 2022 14:37:49 +0800
Subject: [PATCH 11/16] Revert "change bookAmount to in-book amount"
This reverts commit 9fb405930a744d446888985b2a622412b684a115.
---
.../FlamingoSwapOrderBookContract.cs | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 6dd7193..f21f7c0 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -22,9 +22,9 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
///
///
- /// Total buy/sell(real get/pay) amount of the limit order
+ /// Total buy(real get)/sell amount of the limit order
/// Price limit of the order
- /// Expected amount to buy/sell(in-book amount) in book before amm
+ /// Expected amount to buy(real get)/sell from/to book before amm
/// Price limit of bookAmount part
///
///
@@ -40,7 +40,7 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
// Market order
var leftAmount = amount;
- if (isBuy) leftAmount -= bookAmount > 0 ? (bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false)) * 997 / 1000 : 0;
+ if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false) : 0;
if (leftAmount == 0) return null;
@@ -109,9 +109,9 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
///
///
///
- /// Total buy/sell(real get/pay) amount of the limit order
- /// The amount limit of final receive/payment(real get/pay)
- /// Expected amount to buy/sell(in-book amount) in book before amm
+ /// Total buy(real get)/sell amount of the limit order
+ /// The amount limit of final payment/receive(real get)
+ /// Expected amount to buy(real get)/sell from/to book before amm
/// Price limit of bookAmount part
///
///
@@ -133,7 +133,7 @@ public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 send
if (bookAmount > 0)
{
var balanceBefore = GetBalanceOf(book.quoteToken, sender);
- if (isBuy) amount -= (bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false)) * 997 / 1000;
+ if (isBuy) amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000;
else amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
var balanceAfter = GetBalanceOf(book.quoteToken, sender);
quoteAmount = isBuy ? balanceBefore - balanceAfter : balanceAfter - balanceBefore;
From 7baa058e8b3af6490e9d3afa80295c2f2141e059 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Tue, 8 Nov 2022 17:39:21 +0800
Subject: [PATCH 12/16] route market order as swap
---
.../FlamingoSwapOrderBookContract.cs | 168 +++++++++++++-----
1 file changed, 120 insertions(+), 48 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index f21f7c0..8d7b4b3 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -16,7 +16,7 @@ namespace FlamingoSwapOrderBook
public partial class FlamingoSwapOrderBookContract : SmartContract
{
///
- /// Deal and add limit order base on input strategy
+ /// Deal and add limit order base on input strategy, settled by base amount
///
///
///
@@ -30,7 +30,7 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger price, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
{
- Assert(amount > 0 && price > 0 && bookAmount >= 0 && amount >= bookAmount, "Invalid Parameters");
+ Assert(amount > 0 && price > 0 && bookAmount >= 0 && bookPrice > 0 && amount >= bookAmount, "Invalid Parameters");
var pairKey = GetPairKey(tokenA, tokenB);
Assert(!BookPaused(pairKey), "Book Is Paused");
Assert(Runtime.CheckWitness(sender), "No Authorization");
@@ -40,8 +40,8 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
// Market order
var leftAmount = amount;
- if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000 : 0;
- else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false) : 0;
+ if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false)[0] * 997 / 1000 : 0;
+ else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false)[0] : 0;
if (leftAmount == 0) return null;
// Swap AMM
@@ -50,24 +50,24 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
var hasFundFee = HasFundAddress(pairContract);
- var ammReverse = isBuy
+ var reverse = isBuy
? GetReserves(pairContract, book.quoteToken, book.baseToken)
: GetReserves(pairContract, book.baseToken, book.quoteToken);
var amountIn = hasFundFee
- ? GetAmountInTillPriceWithFundFee(isBuy, price, book.quoteScale, ammReverse[0], ammReverse[1])
- : GetAmountInTillPrice(isBuy, price, book.quoteScale, ammReverse[0], ammReverse[1]);
+ ? GetAmountInTillPriceWithFundFee(isBuy, price, book.quoteScale, reverse[0], reverse[1])
+ : GetAmountInTillPrice(isBuy, price, book.quoteScale, reverse[0], reverse[1]);
if (amountIn < 0) amountIn = 0;
- var amountOut = GetAmountOut(amountIn, ammReverse[0], ammReverse[1]);
+ var amountOut = GetAmountOut(amountIn, reverse[0], reverse[1]);
if (isBuy && leftAmount < amountOut)
{
amountOut = leftAmount;
- amountIn = GetAmountIn(amountOut, ammReverse[0], ammReverse[1]);
+ amountIn = GetAmountIn(amountOut, reverse[0], reverse[1]);
}
if (!isBuy && leftAmount < amountIn)
{
amountIn = leftAmount;
- amountOut = GetAmountOut(amountIn, ammReverse[0], ammReverse[1]);
+ amountOut = GetAmountOut(amountIn, reverse[0], reverse[1]);
}
if (amountOut > 0)
@@ -103,56 +103,128 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
}
///
- /// Deal market order based on input strategy
+ /// Deal market order based on input strategy, half swap like (amountIn & amountOut) but half book like (bookAmount)
///
- ///
- ///
+ ///
+ ///
///
- ///
- /// Total buy(real get)/sell amount of the limit order
- /// The amount limit of final payment/receive(real get)
+ ///
+ ///
/// Expected amount to buy(real get)/sell from/to book before amm
/// Price limit of bookAmount part
///
///
- public static bool RouteMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 sender, bool isBuy, BigInteger amount, BigInteger slippage, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
+ public static bool RouteMarketOrderInForOut(UInt160 tokenFrom, UInt160 tokenTo, UInt160 sender, BigInteger amountIn, BigInteger amountOutMin, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
{
- Assert(amount > 0 && slippage > 0 && bookAmount >= 0 && amount >= bookAmount, "Invalid Parameters");
- var pairKey = GetPairKey(tokenA, tokenB);
- Assert(!BookPaused(pairKey), "Book is Paused");
+ Assert(amountIn > 0 && amountOutMin > 0 && bookAmount >= 0 && bookPrice > 0, "Invalid Parameters");
Assert(Runtime.CheckWitness(sender), "No Authorization");
Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the Deadline");
- // Market order
- var book = GetOrderBook(pairKey);
- Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
- var price = slippage * book.quoteScale / amount;
- if (bookAmount > 0) Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
-
- var quoteAmount = BigInteger.Zero;
+ // Deal in order book
if (bookAmount > 0)
{
- var balanceBefore = GetBalanceOf(book.quoteToken, sender);
- if (isBuy) amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false) * 997 / 1000;
- else amount -= bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
- var balanceAfter = GetBalanceOf(book.quoteToken, sender);
- quoteAmount = isBuy ? balanceBefore - balanceAfter : balanceAfter - balanceBefore;
- if (amount == 0)
+ // Check pair status
+ var pairKey = GetPairKey(tokenFrom, tokenTo);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
+
+ // Check price limit
+ var isBuy = tokenTo == book.baseToken;
+ var price = isBuy ? amountIn * book.quoteScale / amountOutMin : amountOutMin * book.quoteScale / amountIn;
+ Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
+
+ // Deal and record consumed amountIn and satisfied amountOut
+ if (isBuy)
+ {
+ var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false);
+ amountOutMin -= bookAmount - result[0] * 997 / 1000;
+ amountIn -= result[1];
+ }
+ else
{
- Assert(isBuy ? quoteAmount <= slippage : quoteAmount >= slippage, "Insufficient Slippage");
+ var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
+ amountIn -= bookAmount - result[0];
+ amountOutMin -= result[1];
+ }
+ Assert(amountIn >= 0, "Exceeded AmountIn"); // Should not spend more than amountIn
+ if (amountIn == 0) // No longer need further swap
+ {
+ Assert(amountOutMin <= 0, "Insufficient AmountOut"); // Should get more than amountOutMin
return true;
}
}
// Swap AMM
- var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
- var ammReverse = GetReserves(pairContract, book.baseToken, book.quoteToken);
+ var pairContract = GetExchangePairWithAssert(tokenFrom, tokenTo);
+ var reverse = GetReserves(pairContract, tokenFrom, tokenTo);
+ var amountOut = GetAmountOut(amountIn, reverse[0], reverse[1]);
+ Assert(amountOut >= amountOutMin, "Insufficient AmountOut");
+
+ if (amountOut > 0) SwapAMM(pairContract, sender, tokenFrom, tokenTo, amountIn, amountOut);
+ return true;
+ }
+
+ ///
+ /// Deal market order based on input strategy, half swap like (amountIn & amountOut) but half book like (bookAmount)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Price limit of bookAmount part
+ ///
+ ///
+ public static bool RouteMarketOrderOutForIn(UInt160 tokenFrom, UInt160 tokenTo, UInt160 sender, BigInteger amountOut, BigInteger amountInMax, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
+ {
+ Assert(amountOut > 0 && amountInMax > 0 && bookAmount >= 0 && bookPrice > 0, "Invalid Parameters");
+ Assert(Runtime.CheckWitness(sender), "No Authorization");
+ Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the Deadline");
+
+ // Market order
+ if (bookAmount > 0)
+ {
+ // Check pair status
+ var pairKey = GetPairKey(tokenFrom, tokenTo);
+ Assert(!BookPaused(pairKey), "Book is Paused");
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
+
+ // Check price limit
+ var isBuy = tokenTo == book.baseToken;
+ var price = isBuy ? amountInMax * book.quoteScale / amountOut : amountOut * book.quoteScale / amountInMax;
+ if (bookAmount > 0) Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
+
+ // Deal and record consumed amountIn and satisfied amountOut
+ if (isBuy)
+ {
+ var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false);
+ amountOut -= bookAmount - result[0] * 997 / 1000;
+ amountInMax -= result[1];
+ }
+ else
+ {
+ var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
+ amountInMax -= bookAmount - result[0];
+ amountOut -= result[1];
+ }
+ Assert(amountOut >= 0, "Exceeded AmountOut"); // Should not get more than amountOut
+ if (amountOut == 0) // No longer need further swap
+ {
+ Assert(amountInMax >= 0, "Insufficient AmountIn"); // Should spend no more than amountInMax
+ return true;
+ }
+ }
- var amountIn = isBuy ? GetAmountIn(amount, ammReverse[1], ammReverse[0]) : amount;
- var amountOut = isBuy ? amount : GetAmountOut(amount, ammReverse[0], ammReverse[1]);
+ // Swap AMM
+ var pairContract = GetExchangePairWithAssert(tokenFrom, tokenTo);
+ var reverse = GetReserves(pairContract, tokenFrom, tokenTo);
+ var amountIn = GetAmountIn(amountOut, reverse[0], reverse[1]);
+ Assert(amountIn <= amountInMax, "Insufficient AmountIn");
- if (amountOut > 0) SwapAMM(pairContract, sender, isBuy ? book.quoteToken : book.baseToken, isBuy ? book.baseToken : book.quoteToken, amountIn, amountOut);
- Assert(isBuy ? amountIn + quoteAmount <= slippage : amountOut + quoteAmount >= slippage, "Insufficient Slippage");
+ if (amountOut > 0) SwapAMM(pairContract, sender, tokenFrom, tokenTo, amountIn, amountOut);
return true;
}
@@ -722,8 +794,8 @@ private static BigInteger[] MatchQuoteInternal(byte[] pairKey, bool isBuy, BigIn
///
///
///
- /// Left amount
- public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger price, BigInteger amount)
+ /// Left amount and real payment
+ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger price, BigInteger amount)
{
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
@@ -743,8 +815,8 @@ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160
///
///
///
- /// Left amount
- public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
+ /// Left amount and real payment
+ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
{
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
@@ -756,16 +828,16 @@ public static BigInteger DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool is
return DealMarketOrderInternal(pairKey, caller, isBuy, price, amount, true);
}
- private static BigInteger DealMarketOrderInternal(byte[] pairKey, UInt160 taker, bool isBuy, BigInteger price, BigInteger leftAmount, bool shouldRequest)
+ private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 taker, bool isBuy, BigInteger price, BigInteger leftAmount, bool shouldRequest)
{
// Check if can deal
var book = GetOrderBook(pairKey);
Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var firstID = isBuy ? book.firstSellID : book.firstBuyID;
- if (firstID is null) return leftAmount;
+ if (firstID is null) return new BigInteger[] { leftAmount, 0 };
var firstOrder = GetOrder(firstID);
var canDeal = (isBuy && firstOrder.price <= price) || (!isBuy && firstOrder.price >= price);
- if (!canDeal) return leftAmount;
+ if (!canDeal) return new BigInteger[] { leftAmount, 0 };
var me = Runtime.ExecutingScriptHash;
var fundAddress = GetFundAddress();
@@ -874,7 +946,7 @@ private static BigInteger DealMarketOrderInternal(byte[] pairKey, UInt160 taker,
StageFundFee(book.baseToken, baseFee);
StageFundFee(book.quoteToken, quoteFee);
- return leftAmount;
+ return new BigInteger[] { leftAmount, isBuy ? takerPayment : takerReceive };;
}
///
From 17c43c59c403079c4abc97daf4183cd547793c9b Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Thu, 10 Nov 2022 17:54:41 +0800
Subject: [PATCH 13/16] allow DealMarketOrder to accept quote amount
---
.../FlamingoSwapOrderBookContract.cs | 232 ++++++------------
1 file changed, 80 insertions(+), 152 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 8d7b4b3..1a6e1e7 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -22,9 +22,9 @@ public partial class FlamingoSwapOrderBookContract : SmartContract
///
///
///
- /// Total buy(real get)/sell amount of the limit order
+ /// Total buy(real get)/sell base token amount of the limit order
/// Price limit of the order
- /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Expected base token amount to buy(real get)/sell from/to book before amm
/// Price limit of bookAmount part
///
///
@@ -40,8 +40,8 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
// Market order
var leftAmount = amount;
- if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false)[0] * 997 / 1000 : 0;
- else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false)[0] : 0;
+ if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, true, false)[0] * 997 / 1000 : 0;
+ else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, true, false)[0] : 0;
if (leftAmount == 0) return null;
// Swap AMM
@@ -103,25 +103,25 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
}
///
- /// Deal market order based on input strategy, half swap like (amountIn & amountOut) but half book like (bookAmount)
+ /// Deal market order based on input strategy, settled by base amount or quote amount
///
///
///
///
///
///
- /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Expected amount to buy(real get)/sell from/to book before amm
/// Price limit of bookAmount part
///
///
- public static bool RouteMarketOrderInForOut(UInt160 tokenFrom, UInt160 tokenTo, UInt160 sender, BigInteger amountIn, BigInteger amountOutMin, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
+ public static bool RouteMarketOrderInForOut(UInt160 tokenFrom, UInt160 tokenTo, UInt160 sender, BigInteger amountIn, BigInteger amountOutMin, BigInteger amountToBook, BigInteger bookPrice, BigInteger deadLine)
{
- Assert(amountIn > 0 && amountOutMin > 0 && bookAmount >= 0 && bookPrice > 0, "Invalid Parameters");
+ Assert(amountIn > 0 && amountOutMin > 0 && amountToBook >= 0 && amountIn >= amountToBook && bookPrice > 0, "Invalid Parameters");
Assert(Runtime.CheckWitness(sender), "No Authorization");
Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the Deadline");
// Deal in order book
- if (bookAmount > 0)
+ if (amountToBook > 0)
{
// Check pair status
var pairKey = GetPairKey(tokenFrom, tokenTo);
@@ -135,18 +135,10 @@ public static bool RouteMarketOrderInForOut(UInt160 tokenFrom, UInt160 tokenTo,
Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
// Deal and record consumed amountIn and satisfied amountOut
- if (isBuy)
- {
- var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false);
- amountOutMin -= bookAmount - result[0] * 997 / 1000;
- amountIn -= result[1];
- }
- else
- {
- var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
- amountIn -= bookAmount - result[0];
- amountOutMin -= result[1];
- }
+ var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, amountToBook, !isBuy, false);
+ amountOutMin -= result[1];
+ amountIn -= result[0];
+
Assert(amountIn >= 0, "Exceeded AmountIn"); // Should not spend more than amountIn
if (amountIn == 0) // No longer need further swap
{
@@ -173,18 +165,18 @@ public static bool RouteMarketOrderInForOut(UInt160 tokenFrom, UInt160 tokenTo,
///
///
///
- /// Expected amount to buy(real get)/sell from/to book before amm
+ /// Expected amount to buy(real get)/sell from/to book before amm
/// Price limit of bookAmount part
///
///
- public static bool RouteMarketOrderOutForIn(UInt160 tokenFrom, UInt160 tokenTo, UInt160 sender, BigInteger amountOut, BigInteger amountInMax, BigInteger bookAmount, BigInteger bookPrice, BigInteger deadLine)
+ public static bool RouteMarketOrderOutForIn(UInt160 tokenFrom, UInt160 tokenTo, UInt160 sender, BigInteger amountOut, BigInteger amountInMax, BigInteger amountFromBook, BigInteger bookPrice, BigInteger deadLine)
{
- Assert(amountOut > 0 && amountInMax > 0 && bookAmount >= 0 && bookPrice > 0, "Invalid Parameters");
+ Assert(amountOut > 0 && amountInMax > 0 && amountFromBook >= 0 && amountOut >= amountFromBook && bookPrice > 0, "Invalid Parameters");
Assert(Runtime.CheckWitness(sender), "No Authorization");
Assert((BigInteger)Runtime.Time <= deadLine, "Exceeded the Deadline");
// Market order
- if (bookAmount > 0)
+ if (amountFromBook > 0)
{
// Check pair status
var pairKey = GetPairKey(tokenFrom, tokenTo);
@@ -195,27 +187,16 @@ public static bool RouteMarketOrderOutForIn(UInt160 tokenFrom, UInt160 tokenTo,
// Check price limit
var isBuy = tokenTo == book.baseToken;
var price = isBuy ? amountInMax * book.quoteScale / amountOut : amountOut * book.quoteScale / amountInMax;
- if (bookAmount > 0) Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
+ Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
// Deal and record consumed amountIn and satisfied amountOut
- if (isBuy)
- {
- var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, false);
- amountOut -= bookAmount - result[0] * 997 / 1000;
- amountInMax -= result[1];
- }
- else
- {
- var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, false);
- amountInMax -= bookAmount - result[0];
- amountOut -= result[1];
- }
- Assert(amountOut >= 0, "Exceeded AmountOut"); // Should not get more than amountOut
- if (amountOut == 0) // No longer need further swap
- {
- Assert(amountInMax >= 0, "Insufficient AmountIn"); // Should spend no more than amountInMax
- return true;
- }
+ var result = DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (amountFromBook * 1000 + 996) / 997, isBuy, false);
+ amountOut -= result[1];
+ amountInMax -= result[0];
+
+ Assert(amountOut >= 0, "Exceeded AmountIn"); // Should not get more than amountOut
+ Assert(amountInMax >= 0, "Insufficient AmountIn"); // Should spend no more than amountInMax
+ if (amountOut <= 0) return true; // No longer need further swap
}
// Swap AMM
@@ -692,19 +673,20 @@ private static BigInteger GetTotalTradableInternal(byte[] pairKey, bool isBuy, B
///
///
///
- ///
+ ///
+ /// Unsatisfied input amount and the amount of another token
[Safe]
- public static BigInteger[] MatchOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
+ public static BigInteger[] MatchOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount, bool isAmountInBase)
{
// Check if exist
var pairKey = GetPairKey(tokenA, tokenB);
if (BookPaused(pairKey)) return new BigInteger[] { amount, 0 };
- return MatchOrderInternal(pairKey, isBuy, price, amount);
+ return MatchOrderInternal(pairKey, isBuy, price, amount, isAmountInBase);
}
- private static BigInteger[] MatchOrderInternal(byte[] pairKey, bool isBuy, BigInteger price, BigInteger amount)
+ private static BigInteger[] MatchOrderInternal(byte[] pairKey, bool isBuy, BigInteger price, BigInteger amount, bool isAmountInBase)
{
- var totalPayment = BigInteger.Zero;
+ var result = BigInteger.Zero;
var book = GetOrderBook(pairKey);
var currentID = isBuy ? book.firstSellID : book.firstBuyID;
if (currentID is null) return new BigInteger[] { amount, 0 };
@@ -715,14 +697,18 @@ private static BigInteger[] MatchOrderInternal(byte[] pairKey, bool isBuy, BigIn
// Check price
if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
- if (currentOrder.amount <= amount)
+ var quoteAmount = currentOrder.amount * currentOrder.price / book.quoteScale;
+ var baseAmount = currentOrder.amount;
+
+ if ((isAmountInBase && amount >= baseAmount) || (!isAmountInBase && amount >= quoteAmount))
{
- totalPayment += currentOrder.amount * currentOrder.price / book.quoteScale;
- amount -= currentOrder.amount;
+ result += isAmountInBase ? quoteAmount : baseAmount;
+ amount -= isAmountInBase ? baseAmount : quoteAmount;
}
else
{
- totalPayment += amount * currentOrder.price / book.quoteScale;
+ result += isAmountInBase ? amount * currentOrder.price / book.quoteScale :
+ isBuy ? amount * book.quoteScale / currentOrder.price : (amount * book.quoteScale + currentOrder.price - 1) / currentOrder.price;
amount = 0;
}
@@ -730,59 +716,7 @@ private static BigInteger[] MatchOrderInternal(byte[] pairKey, bool isBuy, BigIn
currentOrder = GetOrder(currentOrder.nextID);
}
- // A least payment for buyer
- if (isBuy && totalPayment == 0) totalPayment += 1;
- return new BigInteger[] { amount, totalPayment };
- }
-
- ///
- /// Try to match without real payment
- ///
- ///
- ///
- ///
- ///
- ///
- /// Left amount and tradable base
- [Safe]
- public static BigInteger[] MatchQuote(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger quoteAmount)
- {
- var pairKey = GetPairKey(tokenA, tokenB);
- if (BookPaused(pairKey)) return new BigInteger[] { quoteAmount, 0 };
- return MatchQuoteInternal(pairKey, isBuy, price, quoteAmount);
- }
-
- private static BigInteger[] MatchQuoteInternal(byte[] pairKey, bool isBuy, BigInteger price, BigInteger quoteAmount)
- {
- var totalTradable = BigInteger.Zero;
- var book = GetOrderBook(pairKey);
- var currentID = isBuy ? book.firstSellID : book.firstBuyID;
- if (currentID is null) return new BigInteger[] { quoteAmount, 0 };
- var currentOrder = GetOrder(currentID);
-
- while (quoteAmount > 0)
- {
- // Check price
- if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
- var payment = currentOrder.amount * currentOrder.price / book.quoteScale;
- if (payment <= quoteAmount)
- {
- totalTradable += currentOrder.amount;
- quoteAmount -= payment;
- }
- else
- {
- // For buyer, real payment <= expected
- if (isBuy) totalTradable += quoteAmount * book.quoteScale / currentOrder.price;
- // For seller, real payment >= expected
- else totalTradable += (quoteAmount * book.quoteScale + currentOrder.price - 1) / currentOrder.price;
- quoteAmount = 0;
- }
-
- if (currentOrder.nextID is null) break;
- currentOrder = GetOrder(currentOrder.nextID);
- }
- return new BigInteger[] { quoteAmount, totalTradable };
+ return new BigInteger[] { amount, result };
}
///
@@ -794,8 +728,9 @@ private static BigInteger[] MatchQuoteInternal(byte[] pairKey, bool isBuy, BigIn
///
///
///
- /// Left amount and real payment
- public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger price, BigInteger amount)
+ ///
+ /// Payment and receive
+ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger price, BigInteger amount, bool isAmountInBase)
{
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
@@ -804,7 +739,7 @@ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt1
Assert(Runtime.CheckWitness(taker), "No Authorization");
Assert(ContractManagement.GetContract(taker) == null, "Forbidden");
- return DealMarketOrderInternal(pairKey, taker, isBuy, price, amount, false);
+ return DealMarketOrderInternal(pairKey, taker, isBuy, price, amount, isAmountInBase, false);
}
///
@@ -815,8 +750,9 @@ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt1
///
///
///
- /// Left amount and real payment
- public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount)
+ ///
+ /// Payment and receive
+ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, BigInteger price, BigInteger amount, bool isAmountInBase)
{
// Check parameters
Assert(price > 0 && amount > 0, "Invalid Parameters");
@@ -825,19 +761,30 @@ public static BigInteger[] DealMarketOrder(UInt160 tokenA, UInt160 tokenB, bool
var caller = Runtime.CallingScriptHash;
Assert(ContractManagement.GetContract(caller) != null, "Forbidden");
- return DealMarketOrderInternal(pairKey, caller, isBuy, price, amount, true);
+ return DealMarketOrderInternal(pairKey, caller, isBuy, price, amount, isAmountInBase, true);
}
- private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 taker, bool isBuy, BigInteger price, BigInteger leftAmount, bool shouldRequest)
+ ///
+ /// Order execution
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 taker, bool isBuy, BigInteger price, BigInteger amount, bool isAmountInBase, bool shouldRequest)
{
// Check if can deal
var book = GetOrderBook(pairKey);
Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var firstID = isBuy ? book.firstSellID : book.firstBuyID;
- if (firstID is null) return new BigInteger[] { leftAmount, 0 };
+ if (firstID is null) return new BigInteger[] { amount, 0 };
var firstOrder = GetOrder(firstID);
var canDeal = (isBuy && firstOrder.price <= price) || (!isBuy && firstOrder.price >= price);
- if (!canDeal) return new BigInteger[] { leftAmount, 0 };
+ if (!canDeal) return new BigInteger[] { amount, 0 };
var me = Runtime.ExecutingScriptHash;
var fundAddress = GetFundAddress();
@@ -855,42 +802,37 @@ private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 take
var currentOrder = GetOrder(currentID);
if ((isBuy && currentOrder.price > price) || (!isBuy && currentOrder.price < price)) break;
- BigInteger quoteAmount;
- BigInteger baseAmount;
- BigInteger quotePayment;
- BigInteger basePayment;
+ var quoteAmount = currentOrder.amount * currentOrder.price / book.quoteScale;
+ var baseAmount = currentOrder.amount;
- if (currentOrder.amount <= leftAmount)
+ if ((isAmountInBase && amount >= baseAmount) || (!isAmountInBase && amount >= quoteAmount))
{
// Full-fill
- quoteAmount = currentOrder.amount * currentOrder.price / book.quoteScale;
- baseAmount = currentOrder.amount;
-
// Remove full-fill order
DeleteOrder(currentID);
DeleteReceipt(currentOrder.maker, currentID);
firstID = currentOrder.nextID;
onOrderStatusChanged(book.baseToken, book.quoteToken, currentID, !isBuy, currentOrder.maker, currentOrder.price, 0);
- leftAmount -= currentOrder.amount;
+ amount -= isAmountInBase ? baseAmount : quoteAmount;
}
else
{
// Part-fill
- quoteAmount = leftAmount * currentOrder.price / book.quoteScale;
- baseAmount = leftAmount;
+ quoteAmount = isAmountInBase ? amount * currentOrder.price / book.quoteScale : amount;
+ baseAmount = isAmountInBase ? amount : isBuy ? amount * book.quoteScale / currentOrder.price : (amount * book.quoteScale + currentOrder.price - 1) / currentOrder.price;
// Update order
- currentOrder.amount -= leftAmount;
+ currentOrder.amount -= baseAmount;
SetOrder(currentID, currentOrder);
onOrderStatusChanged(book.baseToken, book.quoteToken, currentID, !isBuy, currentOrder.maker, currentOrder.price, currentOrder.amount);
- leftAmount = 0;
+ amount = 0;
}
// Record payment
- quotePayment = quoteAmount * 997 / 1000;
- basePayment = baseAmount * 997 / 1000;
+ var quotePayment = quoteAmount * 997 / 1000;
+ var basePayment = baseAmount * 997 / 1000;
quoteFee += quoteAmount - quotePayment;
baseFee += baseAmount - basePayment;
@@ -910,7 +852,7 @@ private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 take
}
// Check if still tradable
- if (leftAmount == 0) break;
+ if (amount == 0) break;
currentID = currentOrder.nextID;
}
@@ -946,7 +888,7 @@ private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 take
StageFundFee(book.baseToken, baseFee);
StageFundFee(book.quoteToken, quoteFee);
- return new BigInteger[] { leftAmount, isBuy ? takerPayment : takerReceive };;
+ return new BigInteger[] { takerPayment, takerReceive };
}
///
@@ -1086,16 +1028,9 @@ public static BigInteger[] GetAmountOut(UInt160 tokenFrom, UInt160 tokenTo, BigI
if (BookPaused(pairKey)) return new BigInteger[] { amountIn, 0 };
var book = GetOrderBook(pairKey);
var isBuy = tokenFrom == book.quoteToken;
- if (isBuy)
- {
- var result = MatchQuoteInternal(pairKey, isBuy, price, amountIn);
- return new BigInteger[]{ result[0], result[1] * 997 / 1000 }; // 0.3% fee
- }
- else
- {
- var result = MatchOrderInternal(pairKey, isBuy, price, amountIn);
- return new BigInteger[]{ result[0], result[1] * 997 / 1000 }; // 0.3% fee
- }
+
+ var result = MatchOrderInternal(pairKey, isBuy, price, amountIn, !isBuy);
+ return new BigInteger[]{ result[0], result[1] * 997 / 1000 }; // 0.3% fee
}
///
@@ -1114,16 +1049,9 @@ public static BigInteger[] GetAmountIn(UInt160 tokenFrom, UInt160 tokenTo, BigIn
if (BookPaused(pairKey)) return new BigInteger[] { amountOut, 0 };
var book = GetOrderBook(pairKey);
var isBuy = tokenFrom == book.quoteToken;
- if (isBuy)
- {
- var result = MatchOrderInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997); // 0.3% fee
- return new BigInteger[]{ result[0] * 997 / 1000, result[1] };
- }
- else
- {
- var result = MatchQuoteInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997); // 0.3% fee
- return new BigInteger[]{ (result[0] * 997 + 999) / 1000, result[1] };
- }
+
+ var result = MatchOrderInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997, isBuy); // 0.3% fee
+ return new BigInteger[]{ isBuy ? result[0] * 997 / 1000 : (result[0] * 997 + 999) / 1000, result[1] };
}
#endregion
From 218de7797e673f38cad541bf8145492ee1dbf6d1 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 11 Nov 2022 15:13:22 +0800
Subject: [PATCH 14/16] bugfix
---
.../FlamingoSwapOrderBookContract.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 1a6e1e7..a3bb0a0 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -40,8 +40,8 @@ public static ByteString RouteLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160
// Market order
var leftAmount = amount;
- if (isBuy) leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, true, false)[0] * 997 / 1000 : 0;
- else leftAmount -= bookAmount > 0 ? bookAmount - DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, true, false)[0] : 0;
+ if (isBuy) leftAmount -= bookAmount > 0 ? DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, (bookAmount * 1000 + 996) / 997, true, false)[1] : 0;
+ else leftAmount -= bookAmount > 0 ? DealMarketOrderInternal(pairKey, sender, isBuy, bookPrice, bookAmount, true, false)[0] : 0;
if (leftAmount == 0) return null;
// Swap AMM
@@ -781,10 +781,10 @@ private static BigInteger[] DealMarketOrderInternal(byte[] pairKey, UInt160 take
var book = GetOrderBook(pairKey);
Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
var firstID = isBuy ? book.firstSellID : book.firstBuyID;
- if (firstID is null) return new BigInteger[] { amount, 0 };
+ if (firstID is null) return new BigInteger[] { 0, 0 };
var firstOrder = GetOrder(firstID);
var canDeal = (isBuy && firstOrder.price <= price) || (!isBuy && firstOrder.price >= price);
- if (!canDeal) return new BigInteger[] { amount, 0 };
+ if (!canDeal) return new BigInteger[] { 0, 0 };
var me = Runtime.ExecutingScriptHash;
var fundAddress = GetFundAddress();
From 2f13598651f1fb6a588751a9a2acd782ec351131 Mon Sep 17 00:00:00 2001
From: txhsl <799498265@qq.com>
Date: Fri, 11 Nov 2022 18:20:22 +0800
Subject: [PATCH 15/16] update GetAmountIn
---
.../FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index a3bb0a0..770c6b9 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -1051,7 +1051,7 @@ public static BigInteger[] GetAmountIn(UInt160 tokenFrom, UInt160 tokenTo, BigIn
var isBuy = tokenFrom == book.quoteToken;
var result = MatchOrderInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997, isBuy); // 0.3% fee
- return new BigInteger[]{ isBuy ? result[0] * 997 / 1000 : (result[0] * 997 + 999) / 1000, result[1] };
+ return new BigInteger[]{ (result[0] * 997 + 999) / 1000, result[1] + 1 };
}
#endregion
From 3af405a4be30d2df0d6f8364069579b0afca4785 Mon Sep 17 00:00:00 2001
From: Hu Shili <799498265@qq.com>
Date: Fri, 11 Nov 2022 19:49:22 +0800
Subject: [PATCH 16/16] update GetAmountIn
---
.../FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
index 770c6b9..5c8185b 100644
--- a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -1030,7 +1030,7 @@ public static BigInteger[] GetAmountOut(UInt160 tokenFrom, UInt160 tokenTo, BigI
var isBuy = tokenFrom == book.quoteToken;
var result = MatchOrderInternal(pairKey, isBuy, price, amountIn, !isBuy);
- return new BigInteger[]{ result[0], result[1] * 997 / 1000 }; // 0.3% fee
+ return new BigInteger[] { result[0], result[1] * 997 / 1000 }; // 0.3% fee
}
///
@@ -1051,7 +1051,7 @@ public static BigInteger[] GetAmountIn(UInt160 tokenFrom, UInt160 tokenTo, BigIn
var isBuy = tokenFrom == book.quoteToken;
var result = MatchOrderInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997, isBuy); // 0.3% fee
- return new BigInteger[]{ (result[0] * 997 + 999) / 1000, result[1] + 1 };
+ return isBuy ? new BigInteger[] { result[0] * 997 / 1000, result[1] + 1 } : new BigInteger[]{ (result[0] * 997 + 999) / 1000, result[1] };
}
#endregion