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..bb39c66
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
@@ -0,0 +1,110 @@
+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 };
+ 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.
+ // 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..30c6aec
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
@@ -0,0 +1,535 @@
+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);
+ var order = orderMap.Get(id);
+ return order is null ? new LimitOrder() : (LimitOrder)StdLib.Deserialize(order);
+ }
+
+ ///
+ /// 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);
+ var receipt = receiptMap.Get(maker + id);
+ return receipt is null ? new OrderReceipt() : (OrderReceipt)StdLib.Deserialize(receipt);
+ }
+
+ ///
+ /// 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);
+ var book = bookMap.Get(pairKey);
+ return book is null ? new OrderBook() : (OrderBook)StdLib.Deserialize(book);
+ }
+
+ ///
+ /// Update a book
+ ///
+ ///
+ ///
+ private static void SetOrderBook(byte[] pairKey, OrderBook book)
+ {
+ StorageMap bookMap = new(Storage.CurrentContext, BookMapPrefix);
+ 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
+ ///
+ ///
+ 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..5c8185b
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -0,0 +1,1150 @@
+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, settled by base amount
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Total buy(real get)/sell base token amount of the limit order
+ /// Price limit of the order
+ /// Expected base token 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 bookAmount, BigInteger bookPrice, BigInteger deadLine)
+ {
+ 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");
+ Assert(ContractManagement.GetContract(sender) == null, "Forbidden");
+ 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 -= 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
+ var book = GetOrderBook(pairKey);
+ Assert(book.baseToken.IsAddress() && book.quoteToken.IsAddress(), "Invalid Trade Pair");
+ var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
+ var hasFundFee = HasFundAddress(pairContract);
+
+ var reverse = isBuy
+ ? GetReserves(pairContract, book.quoteToken, book.baseToken)
+ : GetReserves(pairContract, book.baseToken, book.quoteToken);
+ var amountIn = hasFundFee
+ ? 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, reverse[0], reverse[1]);
+
+ if (isBuy && leftAmount < amountOut)
+ {
+ amountOut = leftAmount;
+ amountIn = GetAmountIn(amountOut, reverse[0], reverse[1]);
+ }
+ if (!isBuy && leftAmount < amountIn)
+ {
+ amountIn = leftAmount;
+ amountOut = GetAmountOut(amountIn, reverse[0], reverse[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 = amount
+ });
+ onOrderStatusChanged(book.baseToken, book.quoteToken, id, !!isBuy, sender, price, leftAmount);
+ return id;
+ }
+
+ ///
+ /// 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
+ /// Price limit of bookAmount part
+ ///
+ ///
+ 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 && 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 (amountToBook > 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
+ 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
+ {
+ Assert(amountOutMin <= 0, "Insufficient AmountOut"); // Should get more than amountOutMin
+ return true;
+ }
+ }
+
+ // Swap AMM
+ 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 amountFromBook, BigInteger bookPrice, BigInteger deadLine)
+ {
+ 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 (amountFromBook > 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;
+ Assert((isBuy && price >= bookPrice) || (!isBuy && price <= bookPrice), "BookPrice Beyond Limit");
+
+ // Deal and record consumed amountIn and satisfied amountOut
+ 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
+ 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, tokenFrom, tokenTo, amountIn, amountOut);
+ return true;
+ }
+
+ #region DEX like API
+ ///
+ /// Register a new book
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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");
+ 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));
+ return true;
+ }
+
+ ///
+ /// Set the minimum order amount for addLimitOrder
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool SetMinOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger minOrderAmount)
+ {
+ 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));
+ return true;
+ }
+
+ ///
+ /// Set the maximum trade amount for addLimitOrder
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool SetMaxOrderAmount(UInt160 baseToken, UInt160 quoteToken, BigInteger maxOrderAmount)
+ {
+ 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));
+ return true;
+ }
+
+ ///
+ /// Pause an existing order book
+ ///
+ ///
+ ///
+ ///
+ public static bool PauseOrderBook(UInt160 baseToken, UInt160 quoteToken)
+ {
+ 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));
+ return true;
+ }
+
+ ///
+ /// Resume a paused order book
+ ///
+ ///
+ ///
+ ///
+ public static bool ResumeOrderBook(UInt160 baseToken, UInt160 quoteToken)
+ {
+ 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));
+ return true;
+ }
+
+ ///
+ /// 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);
+ 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);
+ 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 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");
+ 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 bool CancelOrder(UInt160 tokenA, UInt160 tokenB, bool isBuy, ByteString id)
+ {
+ // Check if exist
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var order = GetOrder(id);
+ Assert(order.maker.IsAddress(), "Order Not Exists");
+ 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);
+ return true;
+ }
+
+ ///
+ /// Cancel a limit order with its id and parent order id
+ ///
+ ///
+ ///
+ ///
+ public static bool CancelOrderAt(ByteString parentID, ByteString id)
+ {
+ var order = GetOrder(id);
+ Assert(order.maker.IsAddress(), "Order Not Exists");
+ 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, start from pos
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static OrderReceipt[] GetFirstNOrders(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 first N limit orders and their details, start from orderID
+ ///
+ ///
+ ///
+ ///
+ [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 N orders of maker and their details, start from pos
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Safe]
+ public static OrderReceipt[] GetOrdersOf(UInt160 maker, uint pos, uint n)
+ {
+ var results = new OrderReceipt[n];
+ var iterator = ReceiptsOf(maker);
+ // Make up 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 N orders of maker and their details, start from orderID
+ ///
+ ///
+ ///
+ ///
+ [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
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [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
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Unsatisfied input amount and the amount of another token
+ [Safe]
+ 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, isAmountInBase);
+ }
+
+ private static BigInteger[] MatchOrderInternal(byte[] pairKey, bool isBuy, BigInteger price, BigInteger amount, bool isAmountInBase)
+ {
+ var result = 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;
+
+ var quoteAmount = currentOrder.amount * currentOrder.price / book.quoteScale;
+ var baseAmount = currentOrder.amount;
+
+ if ((isAmountInBase && amount >= baseAmount) || (!isAmountInBase && amount >= quoteAmount))
+ {
+ result += isAmountInBase ? quoteAmount : baseAmount;
+ amount -= isAmountInBase ? baseAmount : quoteAmount;
+ }
+ else
+ {
+ result += isAmountInBase ? amount * currentOrder.price / book.quoteScale :
+ isBuy ? amount * book.quoteScale / currentOrder.price : (amount * book.quoteScale + currentOrder.price - 1) / currentOrder.price;
+ amount = 0;
+ }
+
+ if (currentOrder.nextID is null) break;
+ currentOrder = GetOrder(currentOrder.nextID);
+ }
+
+ return new BigInteger[] { amount, result };
+ }
+
+ ///
+ /// Try to make a market deal with orderbook, taker is not a contract
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// 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");
+ 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, isAmountInBase, false);
+ }
+
+ ///
+ /// Try to make a market deal with orderbook, taker is a contract
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// 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");
+ 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, isAmountInBase, true);
+ }
+
+ ///
+ /// 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[] { 0, 0 };
+ var firstOrder = GetOrder(firstID);
+ var canDeal = (isBuy && firstOrder.price <= price) || (!isBuy && firstOrder.price >= price);
+ if (!canDeal) return new BigInteger[] { 0, 0 };
+
+ 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;
+
+ var quoteAmount = currentOrder.amount * currentOrder.price / book.quoteScale;
+ var baseAmount = currentOrder.amount;
+
+ if ((isAmountInBase && amount >= baseAmount) || (!isAmountInBase && amount >= quoteAmount))
+ {
+ // Full-fill
+ // 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);
+ amount -= isAmountInBase ? baseAmount : quoteAmount;
+ }
+ else
+ {
+ // Part-fill
+ 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 -= baseAmount;
+ SetOrder(currentID, currentOrder);
+
+ onOrderStatusChanged(book.baseToken, book.quoteToken, currentID, !isBuy, currentOrder.maker, currentOrder.price, currentOrder.amount);
+ amount = 0;
+ }
+
+ // Record payment
+ var quotePayment = quoteAmount * 997 / 1000;
+ var 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 (amount == 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]);
+ }
+
+ StageFundFee(book.baseToken, baseFee);
+ StageFundFee(book.quoteToken, quoteFee);
+
+ return new BigInteger[] { takerPayment, takerReceive };
+ }
+
+ ///
+ /// Deal a whole limit order with it id and parent id
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool DealMarketOrderAt(UInt160 taker, ByteString parentID, ByteString id)
+ {
+ // Check parameters
+ Assert(Runtime.CheckWitness(taker), "No Authorization");
+ var order = GetOrder(id);
+ 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();
+
+ 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);
+ }
+
+ StageFundFee(receipt.baseToken, baseFee);
+ StageFundFee(receipt.quoteToken, quoteFee);
+ return true;
+ }
+
+ ///
+ /// Claim the staged fundfee payment to fund address
+ ///
+ ///
+ ///
+ public static bool ClaimFundFee(UInt160[] tokens)
+ {
+ var fundAddress = GetFundAddress();
+ if (fundAddress is null) return false;
+ var me = Runtime.ExecutingScriptHash;
+ foreach (var token in tokens)
+ {
+ var amount = GetStagedFundFee(token);
+ if (amount > 0)
+ {
+ CleanStagedFundFee(token);
+ SafeTransfer(token, me, fundAddress, amount);
+ }
+ }
+ return true;
+ }
+
+ ///
+ /// 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;
+ }
+
+ ///
+ /// 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
+ ///
+ /// 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;
+
+ var result = MatchOrderInternal(pairKey, isBuy, price, amountIn, !isBuy);
+ 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;
+
+ var result = MatchOrderInternal(pairKey, isBuy, price, (amountOut * 1000 + 996) / 997, isBuy); // 0.3% fee
+ return isBuy ? new BigInteger[] { result[0] * 997 / 1000, result[1] + 1 } : 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/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");
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