diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs
new file mode 100644
index 0000000..b09a7d5
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Extensions.cs
@@ -0,0 +1,30 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using System.Numerics;
+
+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..a2ad05c
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Admin.cs
@@ -0,0 +1,113 @@
+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("NdDvLrbtqeCVQkaLstAwh3md8SYYwqWRaE", ContractParameterType.Hash160)]
+ static readonly UInt160 superAdmin = default;
+
+ [InitialValue("0xc0695bdb8a87a40aff33c73ff6349ccc05fa9f01", ContractParameterType.Hash160)]
+ static readonly UInt160 Factory = default;
+
+ [InitialValue("0xd6abe115ecb75e1fa0b42f5e85934ce8c1ae2893", ContractParameterType.Hash160)]
+ static readonly UInt160 bNEO = default;
+
+ static readonly uint ORDER_PER_PAGE = 1 << 8;
+
+ private const string AdminKey = nameof(superAdmin);
+ private const string GASAdminKey = nameof(GASAdminKey);
+ private const string FundAddresskey = nameof(FundAddresskey);
+
+ private static readonly byte[] OrderCounterKey = new byte[] { 0x00 };
+ private static readonly byte[] PageCounterKey = new byte[] { 0x01 };
+ private static readonly byte[] BookMapPrefix = new byte[] { 0x02 };
+ private static readonly byte[] PageMapPrefix = new byte[] { 0x03 };
+ private static readonly byte[] OrderIndexPrefix = new byte[] { 0x04 };
+ private static readonly byte[] MakerIndexPrefix = new byte[] { 0x05 };
+ private static readonly byte[] OrderMapPrefix = new byte[] { 0x06 };
+
+ // 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..3c4f74e
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Event.cs
@@ -0,0 +1,24 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using System.ComponentModel;
+using System.Numerics;
+
+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);
+ }
+}
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..ad775ce
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Helper.cs
@@ -0,0 +1,151 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using Neo.SmartContract.Framework.Services;
+using System;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public partial class FlamingoSwapOrderBookContract
+ {
+
+ [OpCode(OpCode.APPEND)]
+ private static extern void Append(T[] array, T newItem);
+
+ ///
+ /// 断言
+ ///
+ ///
+ ///
+ private static void Assert(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception(message);
+ }
+ }
+
+ ///
+ /// 断言
+ ///
+ ///
+ ///
+ ///
+ private static void Assert(bool condition, string message, params object[] data)
+ {
+ if (!condition)
+ {
+ throw new Exception(message);
+ }
+ }
+
+ ///
+ /// 查询token缩写
+ ///
+ ///
+ ///
+ ///
+ public static string GetTokenSymbol(UInt160 token)
+ {
+ Assert(token.IsValid, "Invalid Address");
+ return (string)Contract.Call(token, "symbol", CallFlags.ReadOnly, new object[] {});
+ }
+
+ ///
+ /// 安全查询交易对,查不到立即中断合约执行
+ ///
+ ///
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// 查询TokenA,TokenB交易对合约的里的持有量并按A、B顺序返回
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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 };
+ }
+
+ ///
+ /// 查询交易对合约是否抽取fundfee
+ ///
+ ///
+ ///
+ public static bool HasFundAddress(UInt160 pairContract)
+ {
+ return (byte[])Contract.Call(pairContract, "getFundAddress", CallFlags.ReadOnly, new object[] { }) != null;
+ }
+
+ ///
+ /// 向资金池转发兑出请求
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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 from user
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Transfer NEP-5 tokens from contract
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void RequestTransfer(UInt160 token, UInt160 from, UInt160 to, BigInteger amount, byte[] data = null)
+ {
+ try
+ {
+ var balanceBefore = (BigInteger)Contract.Call(token, "balanceOf", CallFlags.ReadOnly, new object[] { to });
+ var result = (bool)Contract.Call(from, "approvedTransfer", CallFlags.All, new object[] { token, to, amount, data });
+ var balanceAfter = (BigInteger)Contract.Call(token, "balanceOf", CallFlags.ReadOnly, new object[] { 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);
+ }
+ }
+ }
+}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Storage.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Storage.cs
new file mode 100644
index 0000000..dea0963
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.Storage.cs
@@ -0,0 +1,228 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using Neo.SmartContract.Framework.Native;
+using Neo.SmartContract.Framework.Services;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public partial class FlamingoSwapOrderBookContract
+ {
+ [Safe]
+ public static BookInfo GetBookInfo(UInt160 tokenA, UInt160 tokenB)
+ {
+ return GetBook(GetPairKey(tokenA, tokenB));
+ }
+
+ [Safe]
+ public static LimitOrder GetLimitOrder(ByteString id)
+ {
+ var index = GetOrderIndex(id);
+ return GetOrder(index, id);
+ }
+
+ [Safe]
+ public static LimitOrder[] GetOrdersOnPage(UInt160 tokenA, UInt160 tokenB, BigInteger page)
+ {
+ var results = new LimitOrder[0];
+ var orderMap = new StorageMap(Storage.CurrentReadOnlyContext, OrderMapPrefix);
+ var prefix = GetBookInfo(tokenA, tokenB).Symbol + page;
+ var iterator = orderMap.Find(prefix, FindOptions.ValuesOnly | FindOptions.DeserializeValues);
+ while (iterator.Next()) Append(results, (LimitOrder)iterator.Value);
+ return results;
+ }
+
+ [Safe]
+ public static LimitOrder[] GetOrdersOf(UInt160 maker)
+ {
+ var results = new LimitOrder[0];
+ var makerIndex = new StorageMap(Storage.CurrentReadOnlyContext, MakerIndexPrefix);
+ var orderMap = new StorageMap(Storage.CurrentReadOnlyContext, OrderMapPrefix);
+ var iterator = makerIndex.Find(maker, FindOptions.ValuesOnly);
+ while (iterator.Next()) Append(results, (LimitOrder)StdLib.Deserialize(orderMap.Get((ByteString)iterator.Value)));
+ return results;
+ }
+
+ private static BigInteger GetFirstAvailablePage(byte[] pairKey)
+ {
+ var pageCount = GetPageCounter(pairKey);
+ for (var page = BigInteger.One; page <= pageCount; page++)
+ {
+ if (GetPageOccupancy(pairKey, page) < ORDER_PER_PAGE) return page;
+ }
+ return pageCount + 1;
+ }
+
+ private static ByteString AddLimitOrder(byte[] pairKey, string symbol, LimitOrder order)
+ {
+ // Get id and page
+ var id = GetUnusedID();
+ var page = GetFirstAvailablePage(pairKey);
+ order.ID = id;
+ order.Page = page;
+ var index = symbol + order.Page;
+ SetOrderIndex(id, index);
+ SetMakerIndex(order.Maker + id, index + id);
+ SetOrder(index, id, order);
+
+ // Update page status
+ if (page > GetPageCounter(pairKey)) UpdatePageCounter(pairKey, page);
+ var pageOccupancy = GetPageOccupancy(pairKey, page) + 1;
+ Assert(pageOccupancy <= ORDER_PER_PAGE, "Using Full Page");
+ UpdatePageOccupancy(pairKey, page, pageOccupancy);
+ return id;
+ }
+
+ private static void UpdateLimitOrder(ByteString index, LimitOrder order)
+ {
+ SetOrder(index, order.ID, order);
+ }
+
+ private static void RemoveLimitOrder(byte[] pairKey, ByteString index, LimitOrder order)
+ {
+ // Delete order and index
+ DeleteOrder(index, order.ID);
+ DeleteMakerIndex(order.Maker + order.ID);
+ DeleteOrderIndex(order.ID);
+
+ // Update page status
+ var pageOccupancy = GetPageOccupancy(pairKey, order.Page) - 1;
+ Assert(pageOccupancy >= 0, "Invalid Page Occupancy");
+ UpdatePageOccupancy(pairKey, order.Page, pageOccupancy);
+ }
+
+ #region BookMap
+ private static void SetBook(byte[] pairKey, BookInfo book)
+ {
+ var bookMap = new StorageMap(Storage.CurrentContext, BookMapPrefix);
+ bookMap.Put(pairKey, StdLib.Serialize(book));
+ }
+
+ private static BookInfo GetBook(byte[] pairKey)
+ {
+ var bookMap = new StorageMap(Storage.CurrentReadOnlyContext, BookMapPrefix);
+ var bookInfo = bookMap.Get(pairKey);
+ return bookInfo is null ? new BookInfo() : (BookInfo)StdLib.Deserialize(bookInfo);
+ }
+ #endregion
+
+ #region MakerIndex
+ private static void SetMakerIndex(ByteString index, ByteString orderIndex)
+ {
+ var makerIndex = new StorageMap(Storage.CurrentContext, MakerIndexPrefix);
+ makerIndex.Put(index, orderIndex);
+ }
+
+ private static ByteString GetMakerIndex(ByteString index)
+ {
+ var makerIndex = new StorageMap(Storage.CurrentReadOnlyContext, MakerIndexPrefix);
+ return makerIndex.Get(index);
+ }
+
+ private static void DeleteMakerIndex(ByteString index)
+ {
+ var makerIndex = new StorageMap(Storage.CurrentContext, MakerIndexPrefix);
+ makerIndex.Delete(index);
+ }
+ #endregion
+
+ #region OrderIndex
+ private static void SetOrderIndex(ByteString id, ByteString index)
+ {
+ var orderIndex = new StorageMap(Storage.CurrentContext, OrderIndexPrefix);
+ orderIndex.Put(id, index);
+ }
+
+ private static ByteString GetOrderIndex(ByteString id)
+ {
+ var orderIndex = new StorageMap(Storage.CurrentReadOnlyContext, OrderIndexPrefix);
+ return orderIndex.Get(id);
+ }
+
+ private static void DeleteOrderIndex(ByteString id)
+ {
+ var orderIndex = new StorageMap(Storage.CurrentContext, OrderIndexPrefix);
+ orderIndex.Delete(id);
+ }
+ #endregion
+
+ #region OrderMap
+ private static void SetOrder(ByteString index, ByteString id, LimitOrder order)
+ {
+ var orderMap = new StorageMap(Storage.CurrentContext, OrderMapPrefix);
+ orderMap.Put(index + id, StdLib.Serialize(order));
+ }
+
+ private static LimitOrder GetOrder(ByteString index, ByteString id)
+ {
+ var orderMap = new StorageMap(Storage.CurrentReadOnlyContext, OrderMapPrefix);
+ var order = orderMap.Get(index + id);
+ return order is null ? new LimitOrder() : (LimitOrder)StdLib.Deserialize(order);
+ }
+
+ private static void DeleteOrder(ByteString index, ByteString id)
+ {
+ var orderMap = new StorageMap(Storage.CurrentContext, OrderMapPrefix);
+ orderMap.Delete(index + id);
+ }
+ #endregion
+
+ #region PageMap
+ private static void UpdatePageOccupancy(byte[] pairKey, BigInteger page, BigInteger amount)
+ {
+ var pageMap = new StorageMap(Storage.CurrentContext, PageMapPrefix);
+ pageMap.Put(pairKey.ToByteString() + page, amount);
+ }
+
+ private static BigInteger GetPageOccupancy(byte[] pairKey, BigInteger page)
+ {
+ var pageMap = new StorageMap(Storage.CurrentReadOnlyContext, PageMapPrefix);
+ return (BigInteger)pageMap.Get(pairKey.ToByteString() + page);
+ }
+ #endregion
+
+ #region PageCounter
+ private static void UpdatePageCounter(byte[] pairKey, BigInteger count)
+ {
+ var counterMap = new StorageMap(Storage.CurrentContext, PageCounterKey);
+ counterMap.Put(pairKey, count);
+ }
+
+ private static BigInteger GetPageCounter(byte[] pairKey)
+ {
+ var counterMap = new StorageMap(Storage.CurrentReadOnlyContext, PageCounterKey);
+ return (BigInteger)counterMap.Get(pairKey);
+ }
+ #endregion
+
+ #region OrderCounter
+ ///
+ /// Find a random number as order ID
+ ///
+ ///
+ private static ByteString GetUnusedID()
+ {
+ var context = Storage.CurrentContext;
+ var counter = Storage.Get(context, OrderCounterKey);
+ Storage.Put(context, OrderCounterKey, (BigInteger)counter + 1);
+ var data = (ByteString)Runtime.ExecutingScriptHash;
+ if (counter is not null) data += counter;
+ return CryptoLib.Sha256(data);
+ }
+ #endregion
+
+ ///
+ /// 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
new file mode 100644
index 0000000..9f29d98
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/FlamingoSwapOrderBookContract.cs
@@ -0,0 +1,456 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using Neo.SmartContract.Framework.Attributes;
+using Neo.SmartContract.Framework.Native;
+using Neo.SmartContract.Framework.Services;
+using System.ComponentModel;
+using System.Numerics;
+
+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
+ {
+ public static void CancelOrder(ByteString orderID)
+ {
+ // Get order and book info
+ var index = GetOrderIndex(orderID);
+ Assert(index is not null, "Order Not Exists");
+ var order = GetOrder(index, orderID);
+ Assert(Runtime.CheckWitness(order.Maker), "No Authorization");
+ var me = Runtime.ExecutingScriptHash;
+ var pairKey = GetPairKey(order.BaseToken, order.QuoteToken);
+ var bookInfo = GetBook(pairKey);
+
+ // Remove order and index
+ RemoveLimitOrder(pairKey, index, order);
+ onOrderStatusChanged(order.BaseToken, order.QuoteToken, orderID, order.IsBuy, order.Maker, order.Price, 0);
+
+ // Do transfer
+ if (order.IsBuy) SafeTransfer(order.QuoteToken, me, order.Maker, order.LeftAmount * order.Price / bookInfo.QuoteScale);
+ else SafeTransfer(order.BaseToken, me, order.Maker, order.LeftAmount);
+ }
+
+ public static BigInteger DealOrder(UInt160 taker, ByteString orderID, BigInteger amount)
+ {
+ // Check Parameters
+ Assert(amount > 0, "Invalid Parameters");
+ Assert(Runtime.CheckWitness(taker), "No Authorization");
+
+ // Get order and book info
+ var index = GetOrderIndex(orderID);
+ Assert(index is not null, "Order Not Exists");
+ var order = GetOrder(index, orderID);
+ var pairKey = GetPairKey(order.BaseToken, order.QuoteToken);
+ var bookInfo = GetBook(pairKey);
+
+ var me = Runtime.ExecutingScriptHash;
+ var fundAddress = GetFundAddress();
+
+ var baseAmount = amount > order.LeftAmount ? order.LeftAmount : amount;
+ var quoteAmount = baseAmount * order.Price / bookInfo.QuoteScale;
+ var basePayment = baseAmount * 997 / 1000;
+ var quotePayment = quoteAmount * 997 / 1000;
+
+ if (order.IsBuy) SafeTransfer(order.BaseToken, taker, me, baseAmount);
+ else SafeTransfer(order.QuoteToken, taker, me, quoteAmount > 0 ? quoteAmount : 1);
+
+ // Update or remove order
+ if (baseAmount < order.LeftAmount)
+ {
+ order.LeftAmount -= baseAmount;
+ UpdateLimitOrder(index, order);
+ onOrderStatusChanged(order.BaseToken, order.QuoteToken, orderID, order.IsBuy, order.Maker, order.Price, order.LeftAmount);
+ }
+ else
+ {
+ RemoveLimitOrder(pairKey, index, order);
+ onOrderStatusChanged(order.BaseToken, order.QuoteToken, orderID, order.IsBuy, order.Maker, order.Price, 0);
+ }
+
+ // Transfer
+ SafeTransfer(order.BaseToken, me, order.IsBuy ? order.Maker : taker, basePayment);
+ SafeTransfer(order.QuoteToken, me, order.IsBuy ? taker : order.Maker, quotePayment);
+
+ if (fundAddress is not null)
+ {
+ SafeTransfer(order.BaseToken, me, fundAddress, baseAmount - basePayment);
+ SafeTransfer(order.QuoteToken, me, fundAddress, quoteAmount - quotePayment);
+ }
+
+ return amount - baseAmount;
+ }
+
+ public static BigInteger[] DealOrders(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger amount, ByteString[] orderIDs)
+ {
+ // Check Parameters
+ Assert(amount > 0, "Invalid Parameters");
+ Assert(Runtime.CheckWitness(taker), "No Authorization");
+ if (orderIDs.Length == 0) return new BigInteger[] { amount, 0 };
+
+ // Get book info
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+
+ var me = Runtime.ExecutingScriptHash;
+ var fundAddress = GetFundAddress();
+
+ var takerPayment = BigInteger.Zero;
+ var takerReceive = BigInteger.Zero;
+ var baseFee = BigInteger.Zero;
+ var quoteFee = BigInteger.Zero;
+ var makerReceive = new Map();
+
+ foreach (var id in orderIDs)
+ {
+ // Get order
+ var index = GetOrderIndex(id);
+ if (index is null) continue;
+ var order = GetOrder(index, id);
+ Assert(order.BaseToken == bookInfo.BaseToken && order.QuoteToken == bookInfo.QuoteToken && order.IsBuy ^ isBuy, "Invalid Trading");
+
+ var baseAmount = amount > order.LeftAmount ? order.LeftAmount : amount;
+ var quoteAmount = baseAmount * order.Price / bookInfo.QuoteScale;
+ var basePayment = baseAmount * 997 / 1000;
+ var quotePayment = quoteAmount * 997 / 1000;
+ baseFee += baseAmount - basePayment;
+ quoteFee += quoteAmount - quotePayment;
+ amount -= baseAmount;
+
+ // Record payment
+ takerPayment += isBuy ? (quoteAmount > 0 ? quoteAmount : 1) : baseAmount;
+ takerReceive += isBuy ? basePayment : quotePayment;
+ if (!makerReceive.HasKey(order.Maker)) makerReceive[order.Maker] = 0;
+ makerReceive[order.Maker] += isBuy ? quotePayment : basePayment;
+
+ // Update or remove order
+ if (baseAmount < order.LeftAmount)
+ {
+ order.LeftAmount -= baseAmount;
+ UpdateLimitOrder(index, order);
+ onOrderStatusChanged(order.BaseToken, order.QuoteToken, id, order.IsBuy, order.Maker, order.Price, order.LeftAmount);
+ }
+ else
+ {
+ RemoveLimitOrder(pairKey, index, order);
+ onOrderStatusChanged(order.BaseToken, order.QuoteToken, id, order.IsBuy, order.Maker, order.Price, 0);
+ }
+
+ if (amount <= 0) break;
+ }
+
+ // Do transfer
+ SafeTransfer(isBuy ? bookInfo.QuoteToken : bookInfo.BaseToken, taker, me, takerPayment);
+ SafeTransfer(isBuy ? bookInfo.BaseToken : bookInfo.QuoteToken, me, taker, takerReceive);
+ foreach (var toAddress in makerReceive.Keys) SafeTransfer(isBuy ? bookInfo.QuoteToken : bookInfo.BaseToken, me, toAddress, makerReceive[toAddress]);
+ if (fundAddress is not null)
+ {
+ SafeTransfer(bookInfo.QuoteToken, me, fundAddress, quoteFee);
+ SafeTransfer(bookInfo.BaseToken, me, fundAddress, baseFee);
+ }
+
+ return new BigInteger[] { amount, takerReceive };
+ }
+
+ public static ByteString DealLimitOrder(UInt160 tokenA, UInt160 tokenB, UInt160 maker, bool isBuy, BigInteger amount, BigInteger price, ByteString[] orderIDs)
+ {
+ // Check Parameters
+ Assert(price > 0, "Invalid Parameters");
+ Assert(ContractManagement.GetContract(maker) == null, "Forbidden");
+
+ // Orders First
+ var result = DealOrders(tokenA, tokenB, maker, isBuy, amount, orderIDs);
+ var leftAmount = result[0];
+
+ // Then AMM
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+ var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
+ var hasFundFee = HasFundAddress(pairContract);
+ var ammReverse = isBuy
+ ? GetReserves(pairContract, bookInfo.QuoteToken, bookInfo.BaseToken)
+ : GetReserves(pairContract, bookInfo.BaseToken, bookInfo.QuoteToken);
+
+ // Get amountIn and amountOut
+ var amountIn = hasFundFee
+ ? GetAmountInTillPriceWithFundFee(isBuy, price, bookInfo.QuoteScale, ammReverse[0], ammReverse[1])
+ : GetAmountInTillPrice(isBuy, price, bookInfo.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]);
+ }
+
+ // Do swap
+ if (amountOut > 0)
+ {
+ SwapAMM(pairContract, maker, isBuy ? bookInfo.QuoteToken : bookInfo.BaseToken, isBuy ? bookInfo.BaseToken : bookInfo.QuoteToken, amountIn, amountOut);
+ leftAmount -= isBuy ? amountOut : amountIn;
+ }
+
+ // Add new limit order
+ if (leftAmount < bookInfo.MinOrderAmount || leftAmount > bookInfo.MaxOrderAmount) return null;
+ var me = Runtime.ExecutingScriptHash;
+ SafeTransfer(isBuy ? bookInfo.QuoteToken : bookInfo.BaseToken, maker, me, isBuy ? leftAmount * price / bookInfo.QuoteScale : leftAmount);
+ var id = AddLimitOrder(pairKey, bookInfo.Symbol, new LimitOrder(){
+ BaseToken = bookInfo.BaseToken,
+ QuoteToken = bookInfo.QuoteToken,
+ Time = Runtime.Time,
+ IsBuy = isBuy,
+ Maker = maker,
+ Price = price,
+ TotalAmount = amount,
+ LeftAmount = leftAmount
+ });
+ onOrderStatusChanged(bookInfo.BaseToken, bookInfo.QuoteToken, id, isBuy, maker, price, leftAmount);
+ return id;
+ }
+
+ public static void DealMarketOrder(UInt160 tokenA, UInt160 tokenB, UInt160 taker, bool isBuy, BigInteger amount, BigInteger amountOutMin, ByteString[] orderIDs)
+ {
+ // Check Parameters
+ Assert(amountOutMin > 0, "Invalid Parameters");
+
+ // Orders First
+ var result = DealOrders(tokenA, tokenB, taker, isBuy, amount, orderIDs);
+ var leftAmount = result[0];
+ var receivedPayment = result[1];
+ if (leftAmount == 0) return;
+
+ // Then AMM
+ var bookInfo = GetBookInfo(tokenA, tokenB);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+ var pairContract = GetExchangePairWithAssert(tokenA, tokenB);
+ var hasFundFee = HasFundAddress(pairContract);
+ var ammReverse = GetReserves(pairContract, bookInfo.BaseToken, bookInfo.QuoteToken);
+
+ // Get amountIn and amountOut
+ var amountIn = isBuy ? GetAmountIn(leftAmount, ammReverse[1], ammReverse[0]) : leftAmount;
+ var amountOut = isBuy ? leftAmount : GetAmountOut(leftAmount, ammReverse[0], ammReverse[1]);
+ Assert(amountOut + receivedPayment >= amountOutMin, "Insufficient AmountOut");
+
+ // Do swap
+ SwapAMM(pairContract, taker, isBuy ? bookInfo.QuoteToken : bookInfo.BaseToken, isBuy ? bookInfo.BaseToken : bookInfo.QuoteToken, amountIn, amountOut);
+ }
+
+ ///
+ /// 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);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is null, "Book Already Exists");
+
+ SetBook(pairKey, new BookInfo(){
+ Symbol = GetTokenSymbol(baseToken) + "/" + GetTokenSymbol(quoteToken),
+ BaseToken = baseToken,
+ QuoteToken = quoteToken,
+ QuoteScale = quoteScale,
+ MinOrderAmount = minOrderAmount,
+ MaxOrderAmount = maxOrderAmount,
+ IsPaused = false
+ });
+ onBookStatusChanged(baseToken, quoteToken, quoteScale, minOrderAmount, maxOrderAmount, false);
+ }
+
+ ///
+ /// Set the minimum order amount for addLimitOrder
+ ///
+ ///
+ ///
+ ///
+ public static void SetMinOrderAmount(UInt160 tokenA, UInt160 tokenB, BigInteger minOrderAmount)
+ {
+ Assert(minOrderAmount > 0, "Invalid Amount Limit");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+ Assert(minOrderAmount <= bookInfo.MaxOrderAmount, "Invalid Amount Limit");
+
+ bookInfo.MinOrderAmount = minOrderAmount;
+ SetBook(pairKey, bookInfo);
+ onBookStatusChanged(bookInfo.BaseToken, bookInfo.QuoteToken, bookInfo.QuoteScale, bookInfo.MinOrderAmount, bookInfo.MaxOrderAmount, bookInfo.IsPaused);
+ }
+
+ ///
+ /// Set the maximum trade amount for addLimitOrder
+ ///
+ ///
+ ///
+ ///
+ public static void SetMaxOrderAmount(UInt160 tokenA, UInt160 tokenB, BigInteger maxOrderAmount)
+ {
+ Assert(maxOrderAmount > 0, "Invalid Amount Limit");
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+ Assert(maxOrderAmount >= bookInfo.MinOrderAmount, "Invalid Amount Limit");
+
+ bookInfo.MaxOrderAmount = maxOrderAmount;
+ SetBook(pairKey, bookInfo);
+ onBookStatusChanged(bookInfo.BaseToken, bookInfo.QuoteToken, bookInfo.QuoteScale, bookInfo.MinOrderAmount, bookInfo.MaxOrderAmount, bookInfo.IsPaused);
+ }
+
+ ///
+ /// Pause an existing order book
+ ///
+ ///
+ ///
+ public static void PauseOrderBook(UInt160 tokenA, UInt160 tokenB)
+ {
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+ Assert(bookInfo.IsPaused != true, "Already Paused");
+
+ bookInfo.IsPaused = true;
+ SetBook(pairKey, bookInfo);
+ onBookStatusChanged(bookInfo.BaseToken, bookInfo.QuoteToken, bookInfo.QuoteScale, bookInfo.MinOrderAmount, bookInfo.MaxOrderAmount, bookInfo.IsPaused);
+ }
+
+ ///
+ /// Resume a paused order book
+ ///
+ ///
+ ///
+ public static void ResumeOrderBook(UInt160 tokenA, UInt160 tokenB)
+ {
+ Assert(Verify(), "No Authorization");
+
+ var pairKey = GetPairKey(tokenA, tokenB);
+ var bookInfo = GetBook(pairKey);
+ Assert(bookInfo.BaseToken is not null, "Book Not Exists");
+ Assert(bookInfo.IsPaused == true, "Not Paused");
+
+ bookInfo.IsPaused = false;
+ SetBook(pairKey, bookInfo);
+ onBookStatusChanged(bookInfo.BaseToken, bookInfo.QuoteToken, bookInfo.QuoteScale, bookInfo.MinOrderAmount, bookInfo.MaxOrderAmount, bookInfo.IsPaused);
+ }
+
+ ///
+ /// 根据要达到的限价簿价格,计算资金池需要输入的Token量
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public 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;
+ }
+
+ public 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;
+ }
+
+ ///
+ /// 根据输入A获取资金池兑换B的量(扣除千分之三手续费)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public 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;
+ }
+
+ ///
+ /// 根据要兑换的输出量B,计算资金池需要输入的A实际量(已计算千分之三手续费)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public 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;
+ }
+
+ ///
+ /// 接受nep17 token必备方法
+ /// SwapTokenInForTokenOut
+ ///
+ ///
+ ///
+ ///
+ public static void OnNEP17Payment(UInt160 from, BigInteger amount, object data)
+ {
+
+ }
+
+ ///
+ /// 根据计算好的输入和输出,使用资金池进行兑换
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void SwapAMM(UInt160 pairContract, UInt160 sender, UInt160 tokenIn, UInt160 tokenOut, BigInteger amountIn, BigInteger amountOut)
+ {
+ //转入tokenIn
+ SafeTransfer(tokenIn, sender, pairContract, amountIn);
+
+ //判定要转出的是token0还是token1
+ BigInteger amount0Out = 0;
+ BigInteger amount1Out = 0;
+ if (tokenIn.ToUInteger() < tokenOut.ToUInteger()) amount1Out = amountOut;
+ else amount0Out = amountOut;
+
+ //转出tokenOut
+ SwapOut(pairContract, amount0Out, amount1Out, sender);
+ }
+ }
+}
diff --git a/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/BookInfo.cs b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/BookInfo.cs
new file mode 100644
index 0000000..e456a04
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/BookInfo.cs
@@ -0,0 +1,16 @@
+using Neo;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public struct BookInfo
+ {
+ public string Symbol;
+ public UInt160 BaseToken;
+ public UInt160 QuoteToken;
+ public BigInteger QuoteScale;
+ public BigInteger MinOrderAmount;
+ public BigInteger MaxOrderAmount;
+ public bool IsPaused;
+ }
+}
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..2f80a48
--- /dev/null
+++ b/Swap/flamingo-contract-swap/FlamingoSwapOrderBook/Models/LimitOrder.cs
@@ -0,0 +1,20 @@
+using Neo;
+using Neo.SmartContract.Framework;
+using System.Numerics;
+
+namespace FlamingoSwapOrderBook
+{
+ public struct LimitOrder
+ {
+ 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;
+ public BigInteger Page;
+ }
+}
\ 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..d671745
--- /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..635c94d 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlamingoSwapOrderBook", "FlamingoSwapOrderBook\FlamingoSwapOrderBook.csproj", "{EEE0CF3F-E6EE-441C-B00B-FCB03FBA4647}"
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
+ {EEE0CF3F-E6EE-441C-B00B-FCB03FBA4647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EEE0CF3F-E6EE-441C-B00B-FCB03FBA4647}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EEE0CF3F-E6EE-441C-B00B-FCB03FBA4647}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EEE0CF3F-E6EE-441C-B00B-FCB03FBA4647}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE