From fe1fc32a11c79dfae6aee9779fe358feb716deab Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:53:30 -0300 Subject: [PATCH 01/29] Add orderbook contract --- .../templates/orderbook/orderbook.cpp | 417 +++++++++++++++++ src/contract/templates/orderbook/orderbook.h | 429 ++++++++++++++++++ 2 files changed, 846 insertions(+) create mode 100644 src/contract/templates/orderbook/orderbook.cpp create mode 100644 src/contract/templates/orderbook/orderbook.h diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp new file mode 100644 index 00000000..566ee0f2 --- /dev/null +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -0,0 +1,417 @@ +/* + Copyright (c) [2023] [Sparq Network] + + This software is distributed under the MIT License. + See the LICENSE.txt file in the project root for more information. +*/ + +#include "orderbook.h" + +OrderBook::OrderBook(const Address& addA, const std::string& tickerA, + const Address& addB, const std::string& tickerB, + const Address& address, const Address& creator, + const uint64_t& chainId) + : DynamicContract("OrderBook", address, creator, chainId), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this) +{ + this->nextOrderID_ = 0; + this->addressAssetA_ = addA; + this->addressAssetB_ = addB; + this->tickerAssetA_ = tickerA; + this->tickerAssetB_ = tickerB; + this->spread_ = 0; + this->lastPrice_ = 0; + this->lotSize_ = 0; + this->tickSize_ = 0; + this->nextOrderID_.commit(); + this->addressAssetA_.commit(); + this->addressAssetB_.commit(); + this->tickerAssetA_.commit(); + this->tickerAssetB_.commit(); + this->spread_.commit(); + this->lastPrice_.commit(); + this->lotSize_.commit(); + this->tickSize_.commit(); + + this->registerContractFunctions(); + + this->nextOrderID_.enableRegister(); + this->addressAssetA_.enableRegister(); + this->addressAssetB_.enableRegister(); + this->tickerAssetA_.enableRegister(); + this->tickerAssetB_.enableRegister(); + this->spread_.enableRegister(); + this->lastPrice_.enableRegister(); + this->lotSize_.enableRegister(); + this->tickSize_.enableRegister(); +} + +OrderBook::OrderBook( + const Address& address, + const DB &db + ) : DynamicContract(address, db), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this) +{ + this->nextOrderID_ = Utils::bytesToUint256(db.get(std::string("nextOrderID_"), this->getDBPrefix())); + this->addressAssetA_ = Address(db.get(std::string("addressAssetA_"), this->getDBPrefix())); + this->addressAssetB_ = Address(db.get(std::string("addressAssetB_"), this->getDBPrefix())); + this->tickerAssetA_ = Utils::bytesToString(db.get(std::string("tickerAssetA_"), this->getDBPrefix())); + this->tickerAssetB_ = Utils::bytesToString(db.get(std::string("tickerAssetB_"), this->getDBPrefix())); + this->spread_ = Utils::bytesToUint256(db.get(std::string("spread_"), this->getDBPrefix())); + this->tickSize_ = Utils::bytesToUint256(db.get(std::string("tickSize_"), this->getDBPrefix())); + this->lotSize_ = Utils::bytesToUint256(db.get(std::string("lotSize_"), this->getDBPrefix())); + this->lastPrice_ = Utils::bytesToUint256(db.get(std::string("lastPrice_"), this->getDBPrefix())); + this->registerContractFunctions(); +} + +inline void OrderBook::insertAskOrder(const Order& askOrder) +{ + this->asks_.insert(std::move(askOrder)); +} + +inline void OrderBook::insertBidOrder(const Order& bidOrder) +{ + this->bids_.insert(std::move(bidOrder)); +} + +inline void OrderBook::eraseAskOrder(const Order& askOrder) +{ + this->asks_.erase(askOrder); +} + +inline void OrderBook::eraseBidOrder(const Order& bidOrder) +{ + this->bids_.erase(bidOrder); +} + +void OrderBook::executeOrder(const Address& askOwner, + const Address& bidOwner, + uint256_t tokenAmount, + uint256_t lotAmount) +{ + this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, askOwner, tokenAmount); + this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, bidOwner, this->convertLot(lotAmount)); +} + +Order* OrderBook::findMatchAskOrder(const Order& bidOrder) +{ + auto& bidAssetAmount = std::get<3>(bidOrder); + // do we have any asks orders? + // bid order was filled already? + if (this->asks_.empty() || bidAssetAmount == 0) { + // not found + return nullptr; + } + // get the first ask order + const Order* askOrder = &(*(this->asks_.begin())); + auto& askAssetPrice = std::get<4>(*askOrder); + auto& bidAssetPrice = std::get<4>(bidOrder); + // we want to buy for the lower price + // at the limit bid asset price + if (bidAssetPrice <= askAssetPrice) { + return const_cast(askOrder); + } + // not found + return nullptr; +} + +Order* OrderBook::findMatchBidOrder(const Order& askOrder) +{ + auto& askAssetAmount = std::get<3>(askOrder); + // do we have any asks orders? + // ask order was filled already? + if (this->bids_.empty() || askAssetAmount == 0) { + return nullptr; + } + // get the first bid order + const Order* bidOrder = &(*(this->bids_.begin())); + // we want to sell for the higher bid price + // but never not lower to the ask price limit + auto& bidAssetPrice = std::get<4>(*bidOrder); + auto& askAssetPrice = std::get<4>(askOrder); + // we should not sell below the ask price limit + if (bidAssetPrice >= askAssetPrice) { + return const_cast(bidOrder); + } + // not found + return nullptr; +} + +inline uint256_t OrderBook::convertToken(const uint256_t& assetAmount, + const uint256_t& assetPrice) const +{ + return this->convertTick(assetAmount * assetPrice) / this->precision_; +} + +void OrderBook::evaluateBidLimitOrder(Order& bidOrder) +{ + Order *matchAskOrder = findMatchAskOrder(bidOrder); + auto& bidAssetAmount = std::get<3>(bidOrder); + if (matchAskOrder != nullptr) { + // get bid order attributes values + const auto& bidOwner = std::get<2>(bidOrder); + auto& bidAssetPrice = std::get<4>(bidOrder); + // aggressive order, will be executed + // the side effect here will be the transfer from asks order owner + // to the bid owner order + // and the askOrder can be altered leaving a remainder in its assetAmount + // or remove from the asks set in case it's filled + // if the bidOrder is not filled it will the added to the bid order set + do { + // get ask order attributes values + const auto& askOwner = std::get<2>(*matchAskOrder); + auto& askAssetAmount = std::get<3>(*matchAskOrder); + auto& askAssetPrice = std::get<4>(*matchAskOrder); + + // compute the asset and token amount + uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); + uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); + + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokenAmount, assetAmount); + + // update amount information + bidAssetAmount -= assetAmount; + askAssetAmount -= assetAmount; + + // erase the ask asset if filled + if (askAssetAmount == 0) { + this->eraseAskOrder(*matchAskOrder); + } + // find next match ask order + matchAskOrder = findMatchAskOrder(bidOrder); + } while (matchAskOrder != nullptr); + } + // handle the bid order that was not filled (remainder) + if (bidAssetAmount > 0) { + this->insertBidOrder(bidOrder); + } +} + +void OrderBook::evaluateAskLimitOrder(Order& askOrder) +{ + Order *matchBidOrder = findMatchBidOrder(askOrder); + auto& askAssetAmount = std::get<3>(askOrder); + if (matchBidOrder != nullptr) { + const auto& askOwner = std::get<2>(askOrder); + auto& askAssetPrice = std::get<4>(askOrder); + do { + const auto& bidOwner = std::get<2>(*matchBidOrder); + auto& bidAssetAmount = std::get<3>(*matchBidOrder); + auto& bidAssetPrice = std::get<4>(*matchBidOrder); + // compute the asset and token amount + uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); + uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); + // update order asset amounts + askAssetAmount -= assetAmount; + bidAssetAmount -= assetAmount; + // erase the bid order filled + if (bidAssetAmount == 0) { + this->eraseBidOrder(*matchBidOrder); + } + // find next match ask order + matchBidOrder = findMatchBidOrder(askOrder); + } while (matchBidOrder != nullptr); + } + // handle the bid order that was not filled (remainder) + if (askAssetAmount > 0) { + this->insertAskOrder(askOrder); + } +} + +void OrderBook::transferToContract(const Address& assetAddress, + const uint256_t &tokenAmount) +{ + this->callContractFunction(assetAddress, + &ERC20::transferFrom, + this->getCaller(), + this->getContractAddress(), + tokenAmount); +} + +Order* OrderBook::makeOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice) +{ + return (new Order(this->nextOrderID_.get(), + this->getCurrentTimestamp(), + this->getCaller(), + assetAmount, + assetPrice)); +} + +void OrderBook::addBidLimitOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice) +{ + // get caller = owner address + uint256_t assetBalance = \ + this->callContractViewFunction(this->addressAssetB_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert to token amount + uint256_t tokenAmount = this->convertToken(assetAmount, assetPrice); + + // verify the asset balance + if (tokenAmount > assetBalance) { + throw std::runtime_error("OrderBook::addBidLimitOrder: INSUFFICIENT_BALANCE"); + } + // transfer token amount to order book contract + this->callContractFunction(this->addressAssetB_.get(), + &ERC20::transferFrom, + this->getCaller(), + this->getContractAddress(), + tokenAmount); + // evaluate the bid limit order + this->evaluateBidLimitOrder(*(this->makeOrder(assetAmount, assetPrice))); + // increment the order id + this->nextOrderID_++; +} + +void OrderBook::addAskLimitOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice) +{ + // set asset balance + uint256_t assetBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert to lot amount + uint256_t lotAmount = this->convertLot(assetAmount); + // verify if lot amount is bigger than user balance + if (lotAmount > assetBalance) { + throw std::runtime_error("OrderBook::addAskLimitOrder: INSUFFICIENT_BALANCE"); + } + // transfer lot amount to order book contract + this->callContractFunction(this->addressAssetA_.get(), + &ERC20::transferFrom, + this->getCaller(), + this->getContractAddress(), + lotAmount); + // evaluate the the nearly created ask limit order + this->evaluateAskLimitOrder(*(this->makeOrder(assetAmount, assetPrice))); + // increment next order id + this->nextOrderID_++; +} + +void OrderBook::updateLastPrice(const uint256_t &price) +{ + this->lastPrice_ = price; +} + +void OrderBook::updateSpreadAndMidPrice() +{ + uint256_t bidPrice = std::get<4>(*this->bids_.cbegin()); + uint256_t askPrice = std::get<4>(*this->asks_.cbegin()); + this->spread_ = ((bidPrice >= askPrice) ? \ + bidPrice - askPrice : \ + askPrice - bidPrice); +} + +uint64_t OrderBook::getCurrentTimestamp() const +{ + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); +} + +std::vector OrderBook::getBids() const +{ + std::vector ret; + for (const auto& bid : this->bids_) { + ret.push_back(bid); + } + return ret; +} + +std::vector OrderBook::getAsks() const +{ + std::vector ret; + for (const auto& ask : this->asks_) { + ret.push_back(ask); + } + return ret; +} + +std::tuple, + std::vector, + std::vector> +OrderBook::getUserOrders(const Address& user) const +{ + std::tuple, std::vector, std::vector> ret; + auto& bids = std::get<0>(ret); + auto& asks = std::get<1>(ret); + auto& stops = std::get<2>(ret); + for (const auto& ask : this->asks_) { + const auto& askAddress = std::get<2>(ask); + if (askAddress == user) asks.push_back(ask); + } + for (const auto& bid : this->bids_) { + const auto& bidAddress = std::get<2>(bid); + if (bidAddress == user) bids.push_back(bid); + } + for (const auto& stop : this->stops_) { + const auto& stopAddress = std::get<2>(stop); + if (stopAddress == user) stops.push_back(stop); + } + return ret; +} + +void OrderBook::setDecimals() +{ + uint8_t decA = this->callContractViewFunction(this->addressAssetA_.get(), &ERC20::decimals); + uint8_t decB = this->callContractViewFunction(this->addressAssetB_.get(), &ERC20::decimals); + + if (decA <= 8 || decB <= 8) { + throw std::runtime_error("Token decimals must be greater than 8"); + } + this->lotSize_ = Utils::exp10(decA - 4); + this->tickSize_ = Utils::exp10(decB - 4); +} + +void OrderBook::registerContractFunctions() +{ + registerContract(); + this->registerMemberFunction("getNextOrderID", &OrderBook::getNextOrderID, FunctionTypes::View, this); + this->registerMemberFunction("getAddressAssetA", &OrderBook::getAddressAssetA, FunctionTypes::View, this); + this->registerMemberFunction("getAddressAssetB", &OrderBook::getAddressAssetB, FunctionTypes::View, this); + this->registerMemberFunction("getTickerAssetA", &OrderBook::getTickerAssetA, FunctionTypes::View, this); + this->registerMemberFunction("getTickerAssetB", &OrderBook::getTickerAssetB, FunctionTypes::View, this); + this->registerMemberFunction("getSpread", &OrderBook::getSpread, FunctionTypes::View, this); + this->registerMemberFunction("getTickSize", &OrderBook::getTickSize, FunctionTypes::View, this); + this->registerMemberFunction("getLotSize", &OrderBook::getLotSize, FunctionTypes::View, this); + this->registerMemberFunction("getLastPrice", &OrderBook::getLastPrice, FunctionTypes::View, this); + this->registerMemberFunction("getPrecision", &OrderBook::getPrecision, FunctionTypes::View, this); + this->registerMemberFunction("getBids", &OrderBook::getBids, FunctionTypes::View, this); + this->registerMemberFunction("getAsks", &OrderBook::getAsks, FunctionTypes::View, this); + this->registerMemberFunction("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, this); + this->registerMemberFunction("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("setDecimals", &OrderBook::setDecimals, FunctionTypes::NonPayable, this); +} + +DBBatch OrderBook::dump() const +{ + DBBatch b = BaseContract::dump(); + + b.push_back(Utils::stringToBytes("nextOrderID_"), Utils::uint256ToBytes(this->nextOrderID_.get()), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("addressAssetA_"), this->addressAssetA_.get().view(), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("addressAssetB_"), this->addressAssetB_.get().view(), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("tickerAssetA_"), Utils::stringToBytes(this->tickerAssetA_.get()), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("tickerAssetB_"), Utils::stringToBytes(this->tickerAssetB_.get()), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("spread_"), Utils::uint256ToBytes(this->spread_.get()), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("tickSize_"), Utils::uint256ToBytes(this->tickSize_.get()), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("lotSize_"), Utils::uint256ToBytes(this->lotSize_.get()), this->getDBPrefix()); + b.push_back(Utils::stringToBytes("lastPrice_"), Utils::uint256ToBytes(this->lastPrice_.get()), this->getDBPrefix()); + + return b; +} + diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h new file mode 100644 index 00000000..25308203 --- /dev/null +++ b/src/contract/templates/orderbook/orderbook.h @@ -0,0 +1,429 @@ +/* + Copyright (c) [2023] [Sparq Network] + + This software is distributed under the MIT License. + See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef ORDERBOOK_H +#define ORDERBOOK_H + +#include +#include +#include +#include + +#include "../erc20.h" + +#include "../../dynamiccontract.h" +#include "../../variables/safeuint.h" +#include "../../variables/safestring.h" +#include "../../variables/safeaddress.h" +#include "../../variables/safemultiset.h" + +#include "../../../utils/db.h" +#include "../../../utils/utils.h" +#include "../../../utils/strings.h" + +/// Enum for identifying order types (market or limit, and the respective stops). +enum OrderType { MARKET, LIMIT, STOPMARKET, STOPLIMIT }; + +// better names? +// enum OrderType { MARKET_ORDER, \ +LIMIT_ORDER, \ +STOP_MARKET_ORDER, \ +STOP_LIMIT_ORDER }; + +/// Enum for identifying order side (bid or ask). +enum OrderSide { BID, ASK }; +/** + * Tuple for a given stop order in the book. + * 0 - const uint256_t - id - Sequential unique ID of the order. + * 1 - const uint - timestamp - The epoch timestamp of the order's creation. + * 2 - const Address - owner - The address that made the order. + * 3 - uint256_t - assetAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). + * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. + * 5 - const uint256_t - stopLimit - The stop limit price of the order (only for stop limit orders), in WEI. + * 6 - const OrderSide - side - Whether the order originally is a bid or ask. + * 7 - const OrderType - type - Whether the order originally is a market or limit. + */ +using StopOrder = std::tuple; + +/** + * Lesser comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs < rhs, false otherwise. + */ +inline bool operator<(const StopOrder& lhs, const StopOrder& rhs) { + const auto& lhs_stopLimit = std::get<5>(lhs); + const auto& rhs_stopLimit = std::get<5>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_stopLimit < rhs_stopLimit) || + (lhs_stopLimit == rhs_stopLimit && lhs_timestamp < rhs_timestamp); +} + +/** + * Higher comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs > rhs, false otherwise. + */ +inline bool operator>(const StopOrder& lhs, const StopOrder& rhs) { + const auto& lhs_stopLimit = std::get<5>(lhs); + const auto& rhs_stopLimit = std::get<5>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_stopLimit > rhs_stopLimit) || + (lhs_stopLimit == rhs_stopLimit && lhs_timestamp < rhs_timestamp); +} + +/** + * Tuple for a given order in the book. + * 0 - const uint256_t - id - Sequential unique ID of the order. + * 1 - const uint - timestamp - The epoch timestamp of the order's creation. + * 2 - const Address - owner - The address that made the order. + * 3 - uint256_t - assetAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). + * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. + */ +using Order = std::tuple; + +/** + * Lesser comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs < rhs, false otherwise. + */ +inline bool operator<(const Order& lhs, const Order& rhs) { + const auto& lhs_assetPrice = std::get<4>(lhs); + const auto& rhs_assetPrice = std::get<4>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_assetPrice < rhs_assetPrice) || + (lhs_assetPrice == rhs_assetPrice && lhs_timestamp < rhs_timestamp); +} + +/** + * Higher comparison operator. + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @return True if lhs > rhs, false otherwise. + */ +inline bool operator>(const Order& lhs, const Order& rhs) { + const auto& lhs_assetPrice = std::get<4>(lhs); + const auto& rhs_assetPrice = std::get<4>(rhs); + const auto& lhs_timestamp = std::get<1>(lhs); + const auto& rhs_timestamp = std::get<1>(rhs); + return (lhs_assetPrice > rhs_assetPrice) || + (lhs_assetPrice == rhs_assetPrice && lhs_timestamp < rhs_timestamp); +} + +inline Order orderFromStopOrder(const StopOrder& stopOrder, const uint64_t& timestamp) +{ + return std::make_tuple(std::get<0>(stopOrder), + timestamp, + std::get<2>(stopOrder), + std::get<3>(stopOrder), + std::get<4>(stopOrder)); +} + +/// Contract template for a given exchange pair order book. +class OrderBook : public DynamicContract { +private: + SafeUint256_t nextOrderID_; ///< Counter for the next order ID. + SafeAddress addressAssetA_; ///< Address of the first asset of the pair. HAS TO BE AN ERC20 TOKEN. + SafeAddress addressAssetB_; ///< Address of the second asset of the pair. HAS TO BE AN ERC20 TOKEN. + SafeString tickerAssetA_; ///< Ticker of the first asset of the pair. + SafeString tickerAssetB_; ///< Ticker of the second asset of the pair. + SafeUint256_t spread_; ///< Current market spread. + SafeUint256_t tickSize_; ///< The tick size of the order book (minimum difference between price levels). \ + Should be pow(10, AssetB_.decimals() - 4), tokens MUST have at least 8 decimals. + SafeUint256_t lotSize_; ///< The lot size of the order book (minimum difference between order amounts). \ + Should be pow(10, AssetA_.decimals() - 4), tokens MUST have at least 8 decimals. + SafeUint256_t lastPrice_; ///< The last price of the pair. + const uint256_t precision_ = 10000; ///< Equivalent to 10^4, difference between tick/lot size to the actual token value + + SafeMultiSet> bids_; ///< List of currently active bids, from highest to lowest price. + SafeMultiSet> asks_; ///< List of currently active asks, from lowest to highest price. + SafeMultiSet> stops_; ///< List of stop orders, from lower to highest stop price. + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + void transferToContract(const Address& from, const uint256_t &tickAmount); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + Order* makeOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + void executeOrder(const Address& askOwner, + const Address& bidOwner, + uint256_t tokenAmount, + uint256_t assetAmount); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + inline void insertAskOrder(const Order& askOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + inline void insertBidOrder(const Order& bidOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + inline void eraseAskOrder(const Order& askOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + inline void eraseBidOrder(const Order& bidOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + void evaluateBidLimitOrder(Order& bidOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + void evaluateAskLimitOrder(Order& askOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + Order* findMatchAskOrder(const Order& bidOrder); + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + Order* findMatchBidOrder(const Order& askOrder); + + /** + * Update the last price of the pair. + * @param price The new last price. + */ + void updateLastPrice(const uint256_t& price); + + /// Update the current spread and mid price based on top bid and top ask prices. + void updateSpreadAndMidPrice(); + + /// Get the current epoch timestamp, in milliseconds. + uint64_t getCurrentTimestamp() const; + + /** + * Convert from lot size to token amount. + * @param value The value to convert. + */ + inline uint256_t convertLot(const uint256_t& value) const { + return value * lotSize_.get(); + } + + /** + * + * @param Param Description + * @param ... + * @return Return Value Description + */ + inline uint256_t convertToken(const uint256_t& assetAmount, + const uint256_t& assetPrice) const; + + /** + * Convert from tick size to token amount. + * @param value The value to convert. + */ + inline uint256_t convertTick(const uint256_t& value) const { + return value * tickSize_.get(); + } + + /// Call the register functions for the contract. + void registerContractFunctions() override; + + /// Dump method + DBBatch dump() const override; + +public: + using ConstructorArguments = std::tuple< + const Address&, const std::string&, const Address&, const std::string& + >; + /** + * Constructor from scratch. + * @param addA The address of the pair's first asset. + * @param tickerA The ticker of the pair's first asset. + * @param addB The address of the pair's second asset. + * @param tickerB The ticker of the pair's second asset. + * @param address The address of the contract. + * @param creator The address of the creator of the contract. + * @param chainId The chain ID. + */ + OrderBook(const Address& addA, const std::string& tickerA, + const Address& addB, const std::string& tickerB, + const Address& address, const Address& creator, + const uint64_t& chainId); + + /** + * Constructor from load. Load contract from database. + * @param address The address of the contract. + * @param db The database to use. + */ + OrderBook(const Address& address, const DB &db); + + /// Getter for `nextOrderID_`. + uint256_t getNextOrderID() const { return this->nextOrderID_.get(); } + + /// Getter for `addressAssetA_`. + Address getAddressAssetA() const { return this->addressAssetA_.get(); } + + /// Getter for `addressAssetB_`. + Address getAddressAssetB() const { return this->addressAssetB_.get(); } + + /// Getter for `tickerAssetA_`. + std::string getTickerAssetA() const { return this->tickerAssetA_.get(); } + + /// Getter for `tickerAssetB_`. + std::string getTickerAssetB() const { return this->tickerAssetB_.get(); } + + /// Getter for `spread_`. + uint256_t getSpread() const { return this->spread_.get(); } + + /// Getter for `tickSize_`. + uint256_t getTickSize() const { return this->tickSize_.get(); } + + /// Getter for `lotSize_`. + uint256_t getLotSize() const { return this->lotSize_.get(); } + + /// Getter for `lastPrice_`. + uint256_t getLastPrice() const { return this->lastPrice_.get(); } + + /// Getter for `precision_`. + uint256_t getPrecision() const { return this->precision_; } + + /** + * Getter for all bids. + * @return A vector with all bids. + */ + std::vector getBids() const; + + /** + * Getter for all asks. + * @return A vector with all asks. + */ + std::vector getAsks() const; + + /** + * Getter for all users orders + * @param user The user to get orders from. + * @return A tuple with a vector of bids, a vector of asks and a vector of stops. + */ + std::tuple, + std::vector, + std::vector> getUserOrders(const Address& user) const; + + /** + * + * @param Param Description + * @param ... + */ + void addBidLimitOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /** + * + * @param Param Description + * @param ... + */ + void addAskLimitOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); + + /** + * + */ + void setDecimals(); + + /// Register the contract structure. + static void registerContract() { + ContractReflectionInterface::registerContractMethods< + OrderBook, + const Address, + const std::string&, + const Address, + const std::string&, + const Address&, + const Address&, + const uint64_t&, + DB& + >( + std::vector{ "addA", "tickerA", "addB", "tickerB"}, + std::make_tuple("getNextOrderID", &OrderBook::getNextOrderID, FunctionTypes::View, std::vector{}), + std::make_tuple("getAddressAssetA", &OrderBook::getAddressAssetA, FunctionTypes::View, std::vector{}), + std::make_tuple("getAddressAssetB", &OrderBook::getAddressAssetB, FunctionTypes::View, std::vector{}), + std::make_tuple("getTickerAssetA", &OrderBook::getTickerAssetA, FunctionTypes::View, std::vector{}), + std::make_tuple("getTickerAssetB", &OrderBook::getTickerAssetB, FunctionTypes::View, std::vector{}), + std::make_tuple("getSpread", &OrderBook::getSpread, FunctionTypes::View, std::vector{}), + std::make_tuple("getTickSize", &OrderBook::getTickSize, FunctionTypes::View, std::vector{}), + std::make_tuple("getLotSize", &OrderBook::getLotSize, FunctionTypes::View, std::vector{}), + std::make_tuple("getLastPrice", &OrderBook::getLastPrice, FunctionTypes::View, std::vector{}), + std::make_tuple("getPrecision", &OrderBook::getPrecision, FunctionTypes::View, std::vector{}), + std::make_tuple("getBids", &OrderBook::getBids, FunctionTypes::View, std::vector{}), + std::make_tuple("getAsks", &OrderBook::getAsks, FunctionTypes::View, std::vector{}), + std::make_tuple("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, std::vector{"user"}), + std::make_tuple("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("setDecimals", &OrderBook::setDecimals, FunctionTypes::NonPayable, std::vector{}) + ); + } +}; + +#endif // ORDERBOOK_H From 69e6a110a1d146d8367e5a720bd9c11d1871a1eb Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:53:51 -0300 Subject: [PATCH 02/29] Add orderbook tests --- tests/contract/orderbook.cpp | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/contract/orderbook.cpp diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp new file mode 100644 index 00000000..c4acadf2 --- /dev/null +++ b/tests/contract/orderbook.cpp @@ -0,0 +1,77 @@ +/* + Copyright (c) [2023-2024] [AppLayer Developers] + + This software is distributed under the MIT License. + See the LICENSE.txt file in the project root for more information. +*/ + +#include "../../src/utils/db.h" +#include "../../src/core/rdpos.h" +#include "../../src/contract/abi.h" +#include "../../src/utils/options.h" +#include "../../src/contract/contractmanager.h" +#include "../../src/libs/catch2/catch_amalgamated.hpp" + +#include "../../src/contract/templates/orderbook/orderbook.h" +#include "../sdktestsuite.hpp" + +#include + +namespace TORDERBOOK { +std::string testDumpPath = Utils::getTestDumpPath(); +TEST_CASE("OrderBook Class", "[contract][orderbook]") { + SECTION("Orderbook creation") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + REQUIRE(sdk.getState().getDumpManagerSize() == 3); + Address AAddr = sdk.deployContract(std::string("A_Token"), + std::string("TST"), + uint8_t(18), + uint256_t("1000000000000000000")); + REQUIRE(sdk.getState().getDumpManagerSize() == 4); + REQUIRE(sdk.callViewFunction(AAddr, &ERC20::name) == "A_Token"); + REQUIRE(sdk.callViewFunction(AAddr, &ERC20::decimals) > 8); + + Address BAddr = sdk.deployContract(std::string("B_Token"), + std::string("TST"), + uint8_t(18), + uint256_t("1000000000000000000")); + REQUIRE(sdk.getState().getDumpManagerSize() == 5); + REQUIRE(sdk.callViewFunction(BAddr, &ERC20::name) == "B_Token"); + + Address orderBook = sdk.deployContract(AAddr, + std::string("A_Token"), + BAddr, + std::string("B_Token")); + REQUIRE(sdk.getState().getDumpManagerSize() == 6); + } + + SECTION("Orderbook add bit limit order") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), BAddr, std::string("B_Token")); + Address owner = sdk.getChainOwnerAccount().address; + + // why is being called twice? + sdk.callFunction(orderBook, &OrderBook::setDecimals); + + // sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("1000000000000000000")); + // sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("1000000000000000000")); + + // sdk.callFunction(AAddr, &ERC20::approve, BAddr, uint256_t("1000000000000000000")); + // sdk.callFunction(BAddr, &ERC20::approve, AAddr, uint256_t("1000000000000000000")); + + // + sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + + // sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("10000"), uint256_t("10")); + // sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("10000"), uint256_t("10")); + // sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("10000"), uint256_t("10")); + // sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("10000"), uint256_t("10")); + } +} +} From d399afdfa91ec0b15bf5e1b6200963066d778d50 Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:54:03 -0300 Subject: [PATCH 03/29] Add safe multiset wrapper --- src/contract/variables/safemultiset.h | 280 ++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 src/contract/variables/safemultiset.h diff --git a/src/contract/variables/safemultiset.h b/src/contract/variables/safemultiset.h new file mode 100644 index 00000000..d6424479 --- /dev/null +++ b/src/contract/variables/safemultiset.h @@ -0,0 +1,280 @@ +/* +Copyright (c) [2023] [Sparq Network] + +This software is distributed under the MIT License. +See the LICENSE.txt file in the project root for more information. +*/ + +#ifndef SAFEMULTISET_H +#define SAFEMULTISET_H + +#include +#include +#include +#include "safebase.h" + +// TODO: missing functions (not sure if necessary): merge, equal_range, operator==, operator<=> + +/** + * Safe wrapper for std::multiset. + * @tparam Key Defines the type of the set key element. + * @tparam Compare Defines the function object for performing comparisons. + * Defaults to std::less (elements ordered from lower to higher). + * @see SafeBase + */ +template > // TODO: Allocator? Do we need it? +class SafeMultiSet : public SafeBase { + private: + std::multiset set_; ///< The original set. + mutable std::unique_ptr> tmp_; ///< The temporary set. + + /// Check the tmp_ variable and initialize it if necessary. + inline void check() const override { + if (tmp_ == nullptr) tmp_ = std::make_unique>(set_); + } + + public: + /// Default Constructor. + SafeMultiSet() : SafeBase(nullptr) {}; + + /** + * Constructor with owner. + * @param owner The owner of the variable. + */ + SafeMultiSet(DynamicContract* owner) : SafeBase(owner) {}; + + /** + * Constructor with iterators. + * @param first Iterator to the start of the data. + * @param end Iterator to the end of the data. + */ + template SafeMultiSet(InputIt first, InputIt last) { + check(); tmp_->insert(first, last); + } + + /** + * Constructor with initializer list. + * @param init The initializer list. + */ + SafeMultiSet(std::initializer_list init) { + check(); for (const auto& val : init) tmp_->emplace(val); + } + + /// Copy constructor. + SafeMultiSet(const SafeMultiSet& other) { check(); other.check(); *tmp_ = *(other.tmp_); } + + /// Return the TEMPORARY set const begin(). + inline std::multiset::iterator begin() const { check(); return tmp_->begin(); } + + /// Return the TEMPORARY set const end(). + inline std::multiset::iterator end() const { check(); return tmp_->end(); } + + /// Return the TEMPORARY set const crbegin(). + inline std::multiset::reverse_iterator rbegin() const { check(); return tmp_->rbegin(); } + + /// Return the TEMPORARY set const crend(). + inline std::multiset::reverse_iterator rend() const { check(); return tmp_->rend(); } + + /// Return the ORIGINAL set const begin(). + inline std::multiset::const_iterator cbegin() const { return set_.cbegin(); } + + /// Return the ORIGINAL set const end(). + inline std::multiset::const_iterator cend() const { return set_.cend(); } + + /// Return the ORIGINAL set const crbegin(). + inline std::multiset::const_reverse_iterator crbegin() const { return set_.crbegin(); } + + /// Return the ORIGINAL set const crend(). + inline std::multiset::const_reverse_iterator crend() const { return set_.crend(); } + + /// Check if temporary set is empty. + inline bool empty() const { check(); return tmp_->empty(); } + + /// Get temporary set size. + inline std::size_t size() const { check(); return tmp_->size(); } + + /// Get temporary set max_size. + inline std::size_t max_size() const { check(); return tmp_->max_size(); } + + /// Clear sets. + inline void clear() { check(); markAsUsed(); tmp_->clear(); } + + /** + * Insert an element into the set. + * @param value The value to insert + * @return An iterator to the inserted position. + */ + std::multiset::iterator insert(const Key& value) { + check(); markAsUsed(); return tmp_->insert(value); + } + + /// Move overload for insert(). + std::multiset::iterator insert(Key&& value) { + check(); markAsUsed(); return tmp_->insert(value); + } + + /// Iterator overload for insert(). + std::multiset::iterator insert( + std::multiset::const_iterator pos, const Key& value + ) { + check(); markAsUsed(); return tmp_->insert(pos, value); + } + + /// Iterator move overload for insert(). + std::multiset::iterator insert( + std::multiset::const_iterator pos, Key&& value + ) { + check(); markAsUsed(); return tmp_->insert(pos, value); + } + + /// Range iterator overload for insert(). + template void insert(InputIt first, InputIt last) { + check(); markAsUsed(); tmp_->insert(first, last); + } + + /// Initializer list overload for insert(). + void insert(std::initializer_list ilist) { + check(); markAsUsed(); tmp_->insert(ilist); + } + + /** + * Construct elements inplace in the temporary set. + * @param args The elements to insert. + * @return An iterator to the last inserted element. + */ + template std::multiset::iterator emplace(Args&&... args) { + check(); markAsUsed(); return tmp_->emplace(args...); + } + + /** + * Construct elements inplace using a hint. + * @param hint The hint as to where to insert the elements. + * @param args The elements to insert. + * @return An iterator to the last inserted element. + */ + template std::multiset::iterator emplace_hint( + std::multiset::const_iterator hint, Args&&... args + ) { + check(); markAsUsed(); return tmp_->emplace_hint(hint, args...); + } + + /** + * Erase an element from the set. + * @param pos An iterator to the element to be erased. + * @return An iterator to the first element following the erased one. + */ + std::multiset::const_iterator erase(std::multiset::const_iterator pos) { + check(); markAsUsed(); return tmp_->erase(pos); + } + + /// Ranged overload of erase(). Erases [ first, last ) . + std::multiset::const_iterator erase( + std::multiset::const_iterator first, std::multiset::const_iterator last + ) { + check(); markAsUsed(); return tmp_->erase(first, last); + } + + /// Element-specific overload of erase(). Returns the number of erased elements. + size_t erase(const Key& key) { + check(); markAsUsed(); return tmp_->erase(key); + } + + /** + * Swap elements with another set. Swaps only elements from the TEMPORARY sets. + * @param other The set to swap with. + */ + void swap(SafeMultiSet& other) { + check(); other.check(); markAsUsed(); other.markAsUsed(); this->tmp_->swap(*other.tmp_); + } + + /** + * Extract an element from the temporary set. Get the value itself with `.value()`. + * @param pos An iterator to the element to be extracted. + * @return The extracted element. + */ + std::multiset::node_type extract(std::multiset::iterator pos) { + check(); markAsUsed(); return tmp_->extract(pos); + } + + /// Element-specific overload of extract(), copy-wise. + std::multiset::node_type extract(const Key& x) { + check(); markAsUsed(); return tmp_->extract(x); + } + + /// Element-specific overload of extract(), move-wise. + std::multiset::node_type extract(Key&& x) { + check(); markAsUsed(); return tmp_->extract(x); + } + + /** + * Count the number of elements that exist in the set. + * @param key The key value to count. + * @return The number of found elements. + */ + size_t count(const Key& key) const { check(); return tmp_->count(key); } + + /** + * Find an element in the temporary set. + * @param key The key to search for. + * @return An iterator to the found element. + */ + std::multiset::const_iterator find(const Key& key) const { + check(); return tmp_->find(key); + } + + /** + * Check if the set contains a given element. + * @param key The key to check. + * @return `true` if set contains the key, `false` otherwise. + */ + bool contains(const Key& key) const { check(); return tmp_->contains(key); } + + /** + * Get the first element that is not less than the given one. + * @param key The key to use for comparison. + * @return An iterator to the found element. + */ + std::multiset::const_iterator lower_bound(const Key& key) { + check(); return tmp_->lower_bound(key); + } + + /** + * Get the first element that is greater than the given one. + * @param key The key to use for comparison. + * @return An iterator to the found element. + */ + std::multiset::const_iterator upper_bound(const Key& key) { + check(); return tmp_->upper_bound(key); + } + + /// Get the function that compares the keys (same as value_comp). + std::multiset::key_compare key_comp() const { check(); return tmp_->key_comp(); } + + /// Get the function that compares the values (same as key_comp). + std::multiset::value_compare value_comp() const { check(); return tmp_->value_comp(); } + + /** + * Erase all elements that satisfy the predicate from the container. + * @param pred The predicate that returns `true` if the element should be erased. + * @return The number of erased elements. + */ + template size_t erase_if(Pred pred) { + check(); + size_t old_size = tmp_->size(); + for (auto first = tmp_->begin(), last = tmp_->end(); first != last;) { + if (pred(*first)) { markAsUsed(); first = tmp_->erase(first); } else first++; + } + return old_size - tmp_->size(); + } + + /// Commit function. + void commit() override { check(); set_.clear(); set_.insert(tmp_->begin(), tmp_->end()); } + + /// Rollback function. + void revert() override { tmp_->clear(); tmp_ = nullptr; } + + /// Get the inner set (for const functions!) + inline const std::multiset& get() const { return set_; } +}; + +#endif // SAFEMULTISET_H From 7a27b77a6a947d9259c0fdccc6334f4b2920f1b4 Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:54:56 -0300 Subject: [PATCH 04/29] Add orderbook files to be compiled --- src/contract/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/contract/CMakeLists.txt b/src/contract/CMakeLists.txt index 4e4805c9..f785ed53 100644 --- a/src/contract/CMakeLists.txt +++ b/src/contract/CMakeLists.txt @@ -35,6 +35,7 @@ set(CONTRACT_HEADERS ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2pair.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2router02.h ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/uq112x112.h + ${CMAKE_SOURCE_DIR}/src/contract/templates/orderbook/orderbook.h ${CMAKE_SOURCE_DIR}/src/contract/templates/pebble.h ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestA.h ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestB.h @@ -73,6 +74,7 @@ set(CONTRACT_SOURCES ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2library.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2pair.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/dexv2/dexv2router02.cpp + ${CMAKE_SOURCE_DIR}/src/contract/templates/orderbook/orderbook.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/pebble.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestA.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/throwtestB.cpp From 964a9fe3d200e55806ac4328c49edafbc08f8afa Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:55:59 -0300 Subject: [PATCH 05/29] Add check method --- src/contract/variables/safebase.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contract/variables/safebase.h b/src/contract/variables/safebase.h index 6e2820f3..a9a2c180 100644 --- a/src/contract/variables/safebase.h +++ b/src/contract/variables/safebase.h @@ -53,6 +53,14 @@ class SafeBase { } } + /** + * Check if the variable is initialized (and initialize it if not). + * @throw std::runtime_error if not overridden by the child class. + */ + inline virtual void check() const { + throw std::runtime_error("Derived Class from SafeBase does not override check()"); + }; + public: /// Empty constructor. Should be used only within local variables within functions. SafeBase() : owner_(nullptr) {}; From fe40ef86a12f32f4e678925f67eb4ffdf416a9e5 Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:56:20 -0300 Subject: [PATCH 06/29] Add OrderBook contract type --- src/contract/customcontracts.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contract/customcontracts.h b/src/contract/customcontracts.h index c0cc2685..870e5835 100644 --- a/src/contract/customcontracts.h +++ b/src/contract/customcontracts.h @@ -13,6 +13,7 @@ See the LICENSE.txt file in the project root for more information. #include "templates/dexv2/dexv2pair.h" #include "templates/dexv2/dexv2factory.h" #include "templates/dexv2/dexv2router02.h" +#include "templates/orderbook/orderbook.h" #include "templates/throwtestA.h" #include "templates/throwtestB.h" #include "templates/throwtestC.h" @@ -35,7 +36,7 @@ using ContractTypes = std::tuple< using ContractTypes = std::tuple< ERC20, ERC20Wrapper, NativeWrapper, SimpleContract, DEXV2Pair, DEXV2Factory, DEXV2Router02, ERC721, ThrowTestA, ThrowTestB, ThrowTestC, ERC721Test, TestThrowVars, - RandomnessTest, SnailTracer, SnailTracerOptimized, Pebble + RandomnessTest, SnailTracer, SnailTracerOptimized, Pebble, OrderBook >; #endif From dd507929a6178d77a09f451249eb4a441ea5dc33 Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 08:57:14 -0300 Subject: [PATCH 07/29] Add exp10 template function --- src/utils/utils.h | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/utils/utils.h b/src/utils/utils.h index eef085b8..edf361a3 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -10,7 +10,13 @@ See the LICENSE.txt file in the project root for more information. #include #include -#include // used by jsonrpc/parser.cpp +#include +#include +#include +#include +#include +#include +#include #include #include @@ -512,6 +518,18 @@ namespace Utils { * @return A string containing the signal name (or "Unknown signal") and number. */ std::string getSignalName(int signum); + + /** + * Templated function for calculating 10^exponent + */ + template + T exp10(const uint64_t& exponent) { + T base = 10; // Base 10 for decimal exponentiation + if (exponent == 0) { + return T(1); + } + return boost::multiprecision::pow(base, exponent); + } }; #endif // UTILS_H From c56df6c0d7f123805038ed05cdcc9bf373e97189 Mon Sep 17 00:00:00 2001 From: lambdart Date: Mon, 18 Nov 2024 09:04:35 -0300 Subject: [PATCH 08/29] Add orderbook test to be compiled --- tests/CMakeLists.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 64747d58..82fdb992 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,17 @@ set (TESTS_SOURCES ${CMAKE_SOURCE_DIR}/tests/contract/executioncontext.cpp ${CMAKE_SOURCE_DIR}/tests/contract/evmcontractexecutor.cpp ${CMAKE_SOURCE_DIR}/tests/contract/throwtest.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/orderbook.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safeuint_t.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safeint_t.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safestring.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safeaddress.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safeunorderedmap.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safebool.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safebytes.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safevector.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safearray.cpp + ${CMAKE_SOURCE_DIR}/tests/contract/variables/safetuple.cpp ${CMAKE_SOURCE_DIR}/tests/core/rdpos.cpp ${CMAKE_SOURCE_DIR}/tests/core/storage.cpp ${CMAKE_SOURCE_DIR}/tests/core/state.cpp From 3fc744db6c8d4c21f829ae7ab82b479bb9dd694f Mon Sep 17 00:00:00 2001 From: lambdart Date: Fri, 29 Nov 2024 16:32:35 -0300 Subject: [PATCH 09/29] Add market orders --- .../templates/orderbook/orderbook.cpp | 399 +++++++++++------- src/contract/templates/orderbook/orderbook.h | 188 +++++---- 2 files changed, 359 insertions(+), 228 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp index 566ee0f2..01da5d0b 100644 --- a/src/contract/templates/orderbook/orderbook.cpp +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -7,8 +7,8 @@ #include "orderbook.h" -OrderBook::OrderBook(const Address& addA, const std::string& tickerA, - const Address& addB, const std::string& tickerB, +OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint8_t decA, + const Address& addB, const std::string& tickerB, const uint8_t decB, const Address& address, const Address& creator, const uint64_t& chainId) : DynamicContract("OrderBook", address, creator, chainId), @@ -19,6 +19,7 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, tickerAssetB_(this), spread_(this) { + // set this->nextOrderID_ = 0; this->addressAssetA_ = addA; this->addressAssetB_ = addB; @@ -26,8 +27,9 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, this->tickerAssetB_ = tickerB; this->spread_ = 0; this->lastPrice_ = 0; - this->lotSize_ = 0; - this->tickSize_ = 0; + this->tickSize_ = Utils::exp10(decB - 4); + this->lotSize_ = Utils::exp10(decA - 4); + // commit this->nextOrderID_.commit(); this->addressAssetA_.commit(); this->addressAssetB_.commit(); @@ -37,9 +39,9 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, this->lastPrice_.commit(); this->lotSize_.commit(); this->tickSize_.commit(); - + // register functions this->registerContractFunctions(); - + // enable register this->nextOrderID_.enableRegister(); this->addressAssetA_.enableRegister(); this->addressAssetB_.enableRegister(); @@ -52,26 +54,54 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, } OrderBook::OrderBook( - const Address& address, - const DB &db - ) : DynamicContract(address, db), - nextOrderID_(this), - addressAssetA_(this), - tickerAssetA_(this), - addressAssetB_(this), - tickerAssetB_(this), - spread_(this) + const Address& address, + const DB &db + ) : DynamicContract(address, db), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this) { - this->nextOrderID_ = Utils::bytesToUint256(db.get(std::string("nextOrderID_"), this->getDBPrefix())); + // set + this->nextOrderID_ = UintConv::bytesToUint256(db.get(std::string("nextOrderID_"), this->getDBPrefix())); this->addressAssetA_ = Address(db.get(std::string("addressAssetA_"), this->getDBPrefix())); this->addressAssetB_ = Address(db.get(std::string("addressAssetB_"), this->getDBPrefix())); - this->tickerAssetA_ = Utils::bytesToString(db.get(std::string("tickerAssetA_"), this->getDBPrefix())); - this->tickerAssetB_ = Utils::bytesToString(db.get(std::string("tickerAssetB_"), this->getDBPrefix())); - this->spread_ = Utils::bytesToUint256(db.get(std::string("spread_"), this->getDBPrefix())); - this->tickSize_ = Utils::bytesToUint256(db.get(std::string("tickSize_"), this->getDBPrefix())); - this->lotSize_ = Utils::bytesToUint256(db.get(std::string("lotSize_"), this->getDBPrefix())); - this->lastPrice_ = Utils::bytesToUint256(db.get(std::string("lastPrice_"), this->getDBPrefix())); + this->tickerAssetA_ = StrConv::bytesToString(db.get(std::string("tickerAssetA_"), this->getDBPrefix())); + this->tickerAssetB_ = StrConv::bytesToString(db.get(std::string("tickerAssetB_"), this->getDBPrefix())); + this->spread_ = UintConv::bytesToUint256(db.get(std::string("spread_"), this->getDBPrefix())); + this->tickSize_ = UintConv::bytesToUint256(db.get(std::string("tickSize_"), this->getDBPrefix())); + this->lotSize_ = UintConv::bytesToUint256(db.get(std::string("lotSize_"), this->getDBPrefix())); + this->lastPrice_ = UintConv::bytesToUint256(db.get(std::string("lastPrice_"), this->getDBPrefix())); + // commit + this->nextOrderID_.commit(); + this->addressAssetA_.commit(); + this->addressAssetB_.commit(); + this->tickerAssetA_.commit(); + this->tickerAssetB_.commit(); + this->spread_.commit(); + this->lastPrice_.commit(); + this->lotSize_.commit(); + this->tickSize_.commit(); + // register functions this->registerContractFunctions(); + // enable register + this->nextOrderID_.enableRegister(); + this->addressAssetA_.enableRegister(); + this->addressAssetB_.enableRegister(); + this->tickerAssetA_.enableRegister(); + this->tickerAssetB_.enableRegister(); + this->spread_.enableRegister(); + this->lastPrice_.enableRegister(); + this->lotSize_.enableRegister(); + this->tickSize_.enableRegister(); +} + +inline uint256_t OrderBook::convertToken(const uint256_t& assetAmount, + const uint256_t& assetPrice) const +{ + return ((this->convertTick(assetAmount * assetPrice)) / this->precision_); } inline void OrderBook::insertAskOrder(const Order& askOrder) @@ -97,29 +127,36 @@ inline void OrderBook::eraseBidOrder(const Order& bidOrder) void OrderBook::executeOrder(const Address& askOwner, const Address& bidOwner, uint256_t tokenAmount, - uint256_t lotAmount) + uint256_t assetAmount) { this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, askOwner, tokenAmount); - this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, bidOwner, this->convertLot(lotAmount)); + this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, bidOwner, this->convertLot(assetAmount)); } Order* OrderBook::findMatchAskOrder(const Order& bidOrder) { auto& bidAssetAmount = std::get<3>(bidOrder); // do we have any asks orders? - // bid order was filled already? - if (this->asks_.empty() || bidAssetAmount == 0) { - // not found + if (this->asks_.empty()) { return nullptr; } // get the first ask order const Order* askOrder = &(*(this->asks_.begin())); auto& askAssetPrice = std::get<4>(*askOrder); auto& bidAssetPrice = std::get<4>(bidOrder); - // we want to buy for the lower price - // at the limit bid asset price - if (bidAssetPrice <= askAssetPrice) { + auto& bidOrderType = std::get<5>(bidOrder); + switch (bidOrderType) { + // doesn't matter the price return the first ask order found + case OrderType::MARKET: { return const_cast(askOrder); + } + // we want to buy for the lowest price + case OrderType::LIMIT: { + return ((bidAssetPrice <= askAssetPrice) ? const_cast(askOrder) : nullptr); + } + // default do nothing + default: + break; } // not found return nullptr; @@ -128,158 +165,176 @@ Order* OrderBook::findMatchAskOrder(const Order& bidOrder) Order* OrderBook::findMatchBidOrder(const Order& askOrder) { auto& askAssetAmount = std::get<3>(askOrder); - // do we have any asks orders? - // ask order was filled already? - if (this->bids_.empty() || askAssetAmount == 0) { + // do we have bid orders? + if (this->bids_.empty()) { return nullptr; } - // get the first bid order + // get the first bid order (the higher value) const Order* bidOrder = &(*(this->bids_.begin())); // we want to sell for the higher bid price // but never not lower to the ask price limit auto& bidAssetPrice = std::get<4>(*bidOrder); auto& askAssetPrice = std::get<4>(askOrder); - // we should not sell below the ask price limit - if (bidAssetPrice >= askAssetPrice) { + auto& askOrderType = std::get<5>(askOrder); + switch (askOrderType) { + // doesn't matter the price return the first bid order found + case OrderType::MARKET: { return const_cast(bidOrder); + } + // we want to sell at the highest price + case OrderType::LIMIT: { + return ((bidAssetPrice >= askAssetPrice) ? const_cast(bidOrder) : nullptr); + } + default: + break; } // not found return nullptr; } -inline uint256_t OrderBook::convertToken(const uint256_t& assetAmount, - const uint256_t& assetPrice) const -{ - return this->convertTick(assetAmount * assetPrice) / this->precision_; -} - -void OrderBook::evaluateBidLimitOrder(Order& bidOrder) +void OrderBook::evaluateBidOrder(Order& bidOrder) { - Order *matchAskOrder = findMatchAskOrder(bidOrder); + Order *matchAskOrder; + // get bid order attributes values + const auto& bidOwner = std::get<2>(bidOrder); auto& bidAssetAmount = std::get<3>(bidOrder); - if (matchAskOrder != nullptr) { - // get bid order attributes values - const auto& bidOwner = std::get<2>(bidOrder); - auto& bidAssetPrice = std::get<4>(bidOrder); - // aggressive order, will be executed - // the side effect here will be the transfer from asks order owner - // to the bid owner order - // and the askOrder can be altered leaving a remainder in its assetAmount - // or remove from the asks set in case it's filled - // if the bidOrder is not filled it will the added to the bid order set - do { - // get ask order attributes values - const auto& askOwner = std::get<2>(*matchAskOrder); - auto& askAssetAmount = std::get<3>(*matchAskOrder); - auto& askAssetPrice = std::get<4>(*matchAskOrder); - - // compute the asset and token amount - uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); - uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); - - // executes the order, transfer the tokens from ask owner to bid owner - this->executeOrder(askOwner, bidOwner, tokenAmount, assetAmount); - - // update amount information - bidAssetAmount -= assetAmount; - askAssetAmount -= assetAmount; - - // erase the ask asset if filled - if (askAssetAmount == 0) { - this->eraseAskOrder(*matchAskOrder); - } - // find next match ask order - matchAskOrder = findMatchAskOrder(bidOrder); - } while (matchAskOrder != nullptr); + auto& bidAssetPrice = std::get<4>(bidOrder); + while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and + (bidAssetAmount > 0)) { + // get ask order attributes values + const auto& askOwner = std::get<2>(*matchAskOrder); + auto& askAssetAmount = std::get<3>(*matchAskOrder); + auto& askAssetPrice = std::get<4>(*matchAskOrder); + // compute the asset and token amount + uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); + uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokenAmount, assetAmount); + // update amount information + bidAssetAmount -= assetAmount; + askAssetAmount -= assetAmount; + // update the current price + this->updateLastPrice(askAssetPrice); + // erase the ask asset if filled + if (askAssetAmount == 0) { + this->eraseAskOrder(*matchAskOrder); + } } // handle the bid order that was not filled (remainder) if (bidAssetAmount > 0) { this->insertBidOrder(bidOrder); } + // update spread and mid price + this->updateSpreadAndMidPrice(); } -void OrderBook::evaluateAskLimitOrder(Order& askOrder) +void OrderBook::evaluateAskOrder(Order& askOrder) { - Order *matchBidOrder = findMatchBidOrder(askOrder); + Order *matchBidOrder; + const auto& askOwner = std::get<2>(askOrder); auto& askAssetAmount = std::get<3>(askOrder); - if (matchBidOrder != nullptr) { - const auto& askOwner = std::get<2>(askOrder); - auto& askAssetPrice = std::get<4>(askOrder); - do { - const auto& bidOwner = std::get<2>(*matchBidOrder); - auto& bidAssetAmount = std::get<3>(*matchBidOrder); - auto& bidAssetPrice = std::get<4>(*matchBidOrder); - // compute the asset and token amount - uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); - uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); - // update order asset amounts - askAssetAmount -= assetAmount; - bidAssetAmount -= assetAmount; - // erase the bid order filled - if (bidAssetAmount == 0) { - this->eraseBidOrder(*matchBidOrder); - } - // find next match ask order - matchBidOrder = findMatchBidOrder(askOrder); - } while (matchBidOrder != nullptr); + auto& askAssetPrice = std::get<4>(askOrder); + while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and + (askAssetAmount > 0)) { + // get bid order attributes values + const auto& bidOwner = std::get<2>(*matchBidOrder); + auto& bidAssetAmount = std::get<3>(*matchBidOrder); + auto& bidAssetPrice = std::get<4>(*matchBidOrder); + // compute the asset and token amount + uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); + uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokenAmount, assetAmount); + // update order asset amounts + askAssetAmount -= assetAmount; + bidAssetAmount -= assetAmount; + // update the current price + this->updateLastPrice(bidAssetPrice); + // erase the bid order if was filled + if (bidAssetAmount == 0) { + this->eraseBidOrder(*matchBidOrder); + } } - // handle the bid order that was not filled (remainder) + // handle the ask order that was not filled (remainder) if (askAssetAmount > 0) { this->insertAskOrder(askOrder); } + // update spread and mid price + this->updateSpreadAndMidPrice(); } void OrderBook::transferToContract(const Address& assetAddress, - const uint256_t &tokenAmount) + const uint256_t& assetAmount) { this->callContractFunction(assetAddress, &ERC20::transferFrom, this->getCaller(), this->getContractAddress(), - tokenAmount); + assetAmount); } Order* OrderBook::makeOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice) + const uint256_t& assetPrice, + const OrderType orderType) { return (new Order(this->nextOrderID_.get(), this->getCurrentTimestamp(), this->getCaller(), assetAmount, - assetPrice)); + assetPrice, + orderType)); } void OrderBook::addBidLimitOrder(const uint256_t& assetAmount, const uint256_t& assetPrice) { - // get caller = owner address + // get caller will return the owner address uint256_t assetBalance = \ this->callContractViewFunction(this->addressAssetB_.get(), &ERC20::balanceOf, this->getCaller()); // convert to token amount uint256_t tokenAmount = this->convertToken(assetAmount, assetPrice); - // verify the asset balance if (tokenAmount > assetBalance) { throw std::runtime_error("OrderBook::addBidLimitOrder: INSUFFICIENT_BALANCE"); } // transfer token amount to order book contract - this->callContractFunction(this->addressAssetB_.get(), - &ERC20::transferFrom, - this->getCaller(), - this->getContractAddress(), - tokenAmount); // evaluate the bid limit order - this->evaluateBidLimitOrder(*(this->makeOrder(assetAmount, assetPrice))); // increment the order id + this->transferToContract(this->addressAssetB_.get(), tokenAmount); + this->evaluateBidOrder(*(this->makeOrder(assetAmount, + assetPrice, + OrderType::LIMIT))); this->nextOrderID_++; } +void OrderBook::delBidLimitOrder(const uint256_t& id) +{ + this->bids_.erase_if([&id, this](const Order& bidOrder) { + auto const& bidId = std::get<0>(bidOrder); + if (bidId != id) { + return false; + } + auto const& bidOwner = std::get<2>(bidOrder); + auto const& bidAssetAmount = std::get<3>(bidOrder); + auto const& bidAssetPrice = std::get<4>(bidOrder); + if (bidOwner != this->getCaller()) { + throw std::runtime_error("OrderBook::delBidLimitOrder: INVALID_OWNER"); + } + this->callContractFunction(this->addressAssetB_.get(), + &ERC20::transfer, + bidOwner, + this->convertToken(bidAssetAmount, bidAssetPrice)); + return true; + }); +} + void OrderBook::addAskLimitOrder(const uint256_t& assetAmount, const uint256_t& assetPrice) { - // set asset balance + // get owner asset balance + // get caller will return the owner address uint256_t assetBalance = \ this->callContractViewFunction(this->addressAssetA_.get(), &ERC20::balanceOf, @@ -291,36 +346,88 @@ void OrderBook::addAskLimitOrder(const uint256_t& assetAmount, throw std::runtime_error("OrderBook::addAskLimitOrder: INSUFFICIENT_BALANCE"); } // transfer lot amount to order book contract - this->callContractFunction(this->addressAssetA_.get(), - &ERC20::transferFrom, - this->getCaller(), - this->getContractAddress(), - lotAmount); // evaluate the the nearly created ask limit order - this->evaluateAskLimitOrder(*(this->makeOrder(assetAmount, assetPrice))); // increment next order id + this->transferToContract(this->addressAssetA_.get(), lotAmount); + this->evaluateAskOrder(*(this->makeOrder(assetAmount, + assetPrice, + OrderType::LIMIT))); + this->nextOrderID_++; +} + +void OrderBook::delAskLimitOrder(const uint256_t& id) +{ + this->asks_.erase_if([&id, this](const Order& askOrder) { + auto const& askId = std::get<0>(askOrder); + if (askId != id) { + return false; + } + auto const& askOwner = std::get<2>(askOrder); + auto const& askAssetAmount = std::get<3>(askOrder); + if (askOwner != this->getCaller()) { + throw std::runtime_error("OrderBook::delBidLimitOrder: INVALID_OWNER"); + } + this->callContractFunction(this->addressAssetB_.get(), + &ERC20::transfer, + askOwner, + this->convertLot(askAssetAmount)); + return true; + }); +} + +void OrderBook::addAskMarketOrder(const uint256_t& assetAmount) +{ + // set asset balance + uint256_t assetBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert lot amount + uint256_t lotAmount = this->convertLot(assetAmount); + // verify if tick amount is bigger than user balance + if (lotAmount > assetBalance) { + throw std::runtime_error("OrderBook::addAskMarketOrder: INSUFFICIENT_BALANCE"); + } + this->transferToContract(this->addressAssetA_.get(), lotAmount); + this->evaluateAskOrder(*(this->makeOrder(assetAmount, 0, OrderType::MARKET))); + this->nextOrderID_++; +} + +void OrderBook::addBidMarketOrder(const uint256_t& assetAmount) +{ + // set asset balance + uint256_t assetBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert tick amount + uint256_t tickAmount = this->convertTick(assetAmount); + // verify if tick amount is bigger than user balance + if (tickAmount > assetBalance) { + throw std::runtime_error("OrderBook::addBidMarketOrder: INSUFFICIENT_BALANCE"); + } + this->evaluateBidOrder(*(this->makeOrder(assetAmount, 0, OrderType::MARKET))); this->nextOrderID_++; } -void OrderBook::updateLastPrice(const uint256_t &price) +inline void OrderBook::updateLastPrice(const uint256_t &price) { this->lastPrice_ = price; } void OrderBook::updateSpreadAndMidPrice() { + if (this->bids_.empty() or this->asks_.empty()) return; uint256_t bidPrice = std::get<4>(*this->bids_.cbegin()); uint256_t askPrice = std::get<4>(*this->asks_.cbegin()); - this->spread_ = ((bidPrice >= askPrice) ? \ - bidPrice - askPrice : \ - askPrice - bidPrice); + this->spread_ = (std::max(bidPrice, askPrice) -\ + std::min(bidPrice, askPrice)); } uint64_t OrderBook::getCurrentTimestamp() const { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); + return std::chrono::duration_cast + (std::chrono::system_clock::now().time_since_epoch()).count(); } std::vector OrderBook::getBids() const @@ -365,18 +472,6 @@ OrderBook::getUserOrders(const Address& user) const return ret; } -void OrderBook::setDecimals() -{ - uint8_t decA = this->callContractViewFunction(this->addressAssetA_.get(), &ERC20::decimals); - uint8_t decB = this->callContractViewFunction(this->addressAssetB_.get(), &ERC20::decimals); - - if (decA <= 8 || decB <= 8) { - throw std::runtime_error("Token decimals must be greater than 8"); - } - this->lotSize_ = Utils::exp10(decA - 4); - this->tickSize_ = Utils::exp10(decB - 4); -} - void OrderBook::registerContractFunctions() { registerContract(); @@ -393,25 +488,27 @@ void OrderBook::registerContractFunctions() this->registerMemberFunction("getBids", &OrderBook::getBids, FunctionTypes::View, this); this->registerMemberFunction("getAsks", &OrderBook::getAsks, FunctionTypes::View, this); this->registerMemberFunction("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, this); - this->registerMemberFunction("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, this); this->registerMemberFunction("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); - this->registerMemberFunction("setDecimals", &OrderBook::setDecimals, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("delAskLimitOrder", &OrderBook::delAskLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("delBidLimitOrder", &OrderBook::delBidLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addAskMarketOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addBidMarketOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); } DBBatch OrderBook::dump() const { DBBatch b = BaseContract::dump(); - b.push_back(Utils::stringToBytes("nextOrderID_"), Utils::uint256ToBytes(this->nextOrderID_.get()), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("addressAssetA_"), this->addressAssetA_.get().view(), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("addressAssetB_"), this->addressAssetB_.get().view(), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("tickerAssetA_"), Utils::stringToBytes(this->tickerAssetA_.get()), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("tickerAssetB_"), Utils::stringToBytes(this->tickerAssetB_.get()), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("spread_"), Utils::uint256ToBytes(this->spread_.get()), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("tickSize_"), Utils::uint256ToBytes(this->tickSize_.get()), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("lotSize_"), Utils::uint256ToBytes(this->lotSize_.get()), this->getDBPrefix()); - b.push_back(Utils::stringToBytes("lastPrice_"), Utils::uint256ToBytes(this->lastPrice_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("nextOrderID_"), UintConv::uint256ToBytes(this->nextOrderID_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("addressAssetA_"), this->addressAssetA_.get().view(), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("addressAssetB_"), this->addressAssetB_.get().view(), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("tickerAssetA_"), StrConv::stringToBytes(this->tickerAssetA_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("tickerAssetB_"), StrConv::stringToBytes(this->tickerAssetB_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("spread_"), UintConv::uint256ToBytes(this->spread_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("tickSize_"), UintConv::uint256ToBytes(this->tickSize_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("lotSize_"), UintConv::uint256ToBytes(this->lotSize_.get()), this->getDBPrefix()); + b.push_back(StrConv::stringToBytes("lastPrice_"), UintConv::uint256ToBytes(this->lastPrice_.get()), this->getDBPrefix()); return b; } - diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index 25308203..b73a18b4 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -28,14 +28,19 @@ /// Enum for identifying order types (market or limit, and the respective stops). enum OrderType { MARKET, LIMIT, STOPMARKET, STOPLIMIT }; -// better names? -// enum OrderType { MARKET_ORDER, \ -LIMIT_ORDER, \ -STOP_MARKET_ORDER, \ -STOP_LIMIT_ORDER }; - /// Enum for identifying order side (bid or ask). enum OrderSide { BID, ASK }; + +/// Order Fields +enum OrderField { + ID = 0, + TIMESTAMP, + OWNER, + AMOUNT, + PRICE, + TYPE +}; + /** * Tuple for a given stop order in the book. * 0 - const uint256_t - id - Sequential unique ID of the order. @@ -93,12 +98,14 @@ inline bool operator>(const StopOrder& lhs, const StopOrder& rhs) { * 2 - const Address - owner - The address that made the order. * 3 - uint256_t - assetAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. + * 5 - const OrderType - type - Whether the order originally is a market or limit. */ using Order = std::tuple; + const uint256_t, + const OrderType>; /** * Lesser comparison operator. @@ -136,7 +143,8 @@ inline Order orderFromStopOrder(const StopOrder& stopOrder, const uint64_t& time timestamp, std::get<2>(stopOrder), std::get<3>(stopOrder), - std::get<4>(stopOrder)); + std::get<4>(stopOrder), + std::get<7>(stopOrder)); } /// Contract template for a given exchange pair order book. @@ -160,27 +168,32 @@ class OrderBook : public DynamicContract { SafeMultiSet> stops_; ///< List of stop orders, from lower to highest stop price. /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Transfer tokens from an address to the order book contract. + * @param assetAddress, the address where to get the tokens from. + * @param assetAmount, the amount to be transferred. */ - void transferToContract(const Address& from, const uint256_t &tickAmount); + void transferToContract(const Address& assetAddress, + const uint256_t &assetAmount); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Create a order. + * @param assetAmount, the order asset amount. + * @param assetPrice, the order asset prince. + * @return A pointer to the nearly created order. */ Order* makeOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice); + const uint256_t& assetPrice, + const OrderType orderType); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Execute the order, i.e, transfer the token amount to the ask owner and + * transfer the asset amount to the bid owner. + * + * @param askOwner, the address of the ask owner. + * @param bidOwner, the address of the bid owner. + * @param bidOwner, the address of the bid owner. + * @param tokenAmount, the token amount to be transferred to the ask owner. + * @param assetAmount, the asset amount to be transferred to the bid owner. */ void executeOrder(const Address& askOwner, const Address& bidOwner, @@ -188,66 +201,56 @@ class OrderBook : public DynamicContract { uint256_t assetAmount); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Insert an ask order to the ask order list. + * @param askOrder, the ask order to be inserted. */ inline void insertAskOrder(const Order& askOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Insert an bid order to the ask order list. + * @param bidOrder, the bid order to be inserted. */ inline void insertBidOrder(const Order& bidOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Erase (remove) an ask order from the ask order list. + * @param askOrder, the ask order to be removed. */ inline void eraseAskOrder(const Order& askOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Erase (remove) a bid order from the bid order list. + * @param bidOrder, the bid order to be removed. */ inline void eraseBidOrder(const Order& bidOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Evaluate the bid order, i.e, try to find the a matching ask order and + * execute the order pair, if the order isn't filled add the bid order to + * the bid order list (passive order). + * @param bidOrder, the bid order. */ - void evaluateBidLimitOrder(Order& bidOrder); + void evaluateBidOrder(Order& bidOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Evaluate the ask order, i.e, try to find the a matching bid order and + * execute the order pair, if the order isn't filled add the ask order to + * the ask order list (passive order). + * @param askOrder, the ask order. */ - void evaluateAskLimitOrder(Order& askOrder); + void evaluateAskOrder(Order& askOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Find a matching ask order for an arbitrary bid order. + * @param bidOrder, the bid order. + * @return A order pointer if an ask order was found, nullptr otherwise. */ Order* findMatchAskOrder(const Order& bidOrder); /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Find a matching bid order for an arbitrary ask order. + * @param askOrder, the ask order. + * @return A order pointer if a bid order was found, nullptr otherwise. */ Order* findMatchBidOrder(const Order& askOrder); @@ -255,12 +258,16 @@ class OrderBook : public DynamicContract { * Update the last price of the pair. * @param price The new last price. */ - void updateLastPrice(const uint256_t& price); + inline void updateLastPrice(const uint256_t& price); - /// Update the current spread and mid price based on top bid and top ask prices. + /** + * Update the current spread and mid price based on top bid and top ask prices. + */ void updateSpreadAndMidPrice(); - /// Get the current epoch timestamp, in milliseconds. + /** + * Get the current epoch timestamp, in milliseconds. + */ uint64_t getCurrentTimestamp() const; /** @@ -272,10 +279,10 @@ class OrderBook : public DynamicContract { } /** - * - * @param Param Description - * @param ... - * @return Return Value Description + * Compute the token amount from the asset amount and its price. + * @param assetAmount, the asset amount. + * @param assetPrice, the asset price. + * @return The computed token amount */ inline uint256_t convertToken(const uint256_t& assetAmount, const uint256_t& assetPrice) const; @@ -296,22 +303,25 @@ class OrderBook : public DynamicContract { public: using ConstructorArguments = std::tuple< - const Address&, const std::string&, const Address&, const std::string& + const Address&, const std::string&, const uint8_t, + const Address&, const std::string&, const uint8_t >; /** * Constructor from scratch. * @param addA The address of the pair's first asset. * @param tickerA The ticker of the pair's first asset. + * @param decA The decimal number from the first asset. * @param addB The address of the pair's second asset. * @param tickerB The ticker of the pair's second asset. + * @param decB The decimals of the second asset. + * @param tickerB The ticker of the pair's second asset. * @param address The address of the contract. * @param creator The address of the creator of the contract. * @param chainId The chain ID. */ - OrderBook(const Address& addA, const std::string& tickerA, - const Address& addB, const std::string& tickerB, - const Address& address, const Address& creator, - const uint64_t& chainId); + OrderBook(const Address& addA, const std::string& tickerA, const uint8_t decA, + const Address& addB, const std::string& tickerB, const uint8_t decB, + const Address& address, const Address& creator, const uint64_t& chainId); /** * Constructor from load. Load contract from database. @@ -372,25 +382,46 @@ class OrderBook : public DynamicContract { std::vector> getUserOrders(const Address& user) const; /** - * - * @param Param Description - * @param ... + * Add bid limit order to be evaluated, i.e, to be executed or be put in the + * bid order list. + * @param assetAmount, the bid order asset amount. + * @param assetPrice, the bid order asset price. */ void addBidLimitOrder(const uint256_t& assetAmount, const uint256_t& assetPrice); /** - * - * @param Param Description - * @param ... + * Remove the bid order from the bid order list. + * @param id, the bid order identifier. + */ + void delBidLimitOrder(const uint256_t& id); + + /** + * Add ask limit order to be evaluated, i.e, to be executed or be put in the + * ask order list. + * @param assetAmount, the ask order asset amount. + * @param assetPrice, the ask order asset price. */ void addAskLimitOrder(const uint256_t& assetAmount, const uint256_t& assetPrice); /** - * + * Remove the ask order from the ask order list. + * @param id, the ask order identifier. + */ + void delAskLimitOrder(const uint256_t& id); + + /** + * Add a market ask order to be evaluated. + * @param assetAmount, the market ask order asset amount. + */ + void addAskMarketOrder(const uint256_t& assetAmount); + + /** + * Add a market bid order to be evaluated. + * @param assetAmount, the market bid order asset amount. */ - void setDecimals(); + void addBidMarketOrder(const uint256_t& assetAmount); /// Register the contract structure. static void registerContract() { @@ -421,7 +452,10 @@ class OrderBook : public DynamicContract { std::make_tuple("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, std::vector{"user"}), std::make_tuple("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), std::make_tuple("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), - std::make_tuple("setDecimals", &OrderBook::setDecimals, FunctionTypes::NonPayable, std::vector{}) + std::make_tuple("delAskLimitOrder", &OrderBook::delAskLimitOrder, FunctionTypes::NonPayable, std::vector{"id"}), + std::make_tuple("delBidLimitOrder", &OrderBook::delBidLimitOrder, FunctionTypes::NonPayable, std::vector{"id"}), + std::make_tuple("addAskLimitOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount"}), + std::make_tuple("addAskLimitOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount"}) ); } }; From 3bfab28db2db3c5fd25e963d8068d991965b46d6 Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 11 Dec 2024 17:18:29 -0300 Subject: [PATCH 10/29] Add more tests --- tests/contract/orderbook.cpp | 165 ++++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 49 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index c4acadf2..754b5e50 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -18,60 +18,127 @@ #include namespace TORDERBOOK { -std::string testDumpPath = Utils::getTestDumpPath(); -TEST_CASE("OrderBook Class", "[contract][orderbook]") { - SECTION("Orderbook creation") { - SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); - REQUIRE(sdk.getState().getDumpManagerSize() == 3); - Address AAddr = sdk.deployContract(std::string("A_Token"), - std::string("TST"), - uint8_t(18), - uint256_t("1000000000000000000")); - REQUIRE(sdk.getState().getDumpManagerSize() == 4); - REQUIRE(sdk.callViewFunction(AAddr, &ERC20::name) == "A_Token"); - REQUIRE(sdk.callViewFunction(AAddr, &ERC20::decimals) > 8); + std::string testDumpPath = Utils::getTestDumpPath(); + TEST_CASE("OrderBook Class", "[contract][orderbook]") { + SECTION("Orderbook creation") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + REQUIRE(sdk.getState().getDumpManagerSize() == 3); + Address AAddr = sdk.deployContract(std::string("A_Token"), + std::string("TST"), + uint8_t(18), + uint256_t("1000000000000000000")); + REQUIRE(sdk.getState().getDumpManagerSize() == 4); + REQUIRE(sdk.callViewFunction(AAddr, &ERC20::name) == "A_Token"); + REQUIRE(sdk.callViewFunction(AAddr, &ERC20::decimals) > 8); - Address BAddr = sdk.deployContract(std::string("B_Token"), - std::string("TST"), - uint8_t(18), - uint256_t("1000000000000000000")); - REQUIRE(sdk.getState().getDumpManagerSize() == 5); - REQUIRE(sdk.callViewFunction(BAddr, &ERC20::name) == "B_Token"); + Address BAddr = sdk.deployContract(std::string("B_Token"), + std::string("TST"), + uint8_t(18), + uint256_t("1000000000000000000")); + REQUIRE(sdk.getState().getDumpManagerSize() == 5); + REQUIRE(sdk.callViewFunction(BAddr, &ERC20::name) == "B_Token"); - Address orderBook = sdk.deployContract(AAddr, - std::string("A_Token"), - BAddr, - std::string("B_Token")); - REQUIRE(sdk.getState().getDumpManagerSize() == 6); - } - - SECTION("Orderbook add bit limit order") { - SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); - Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); - Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), BAddr, std::string("B_Token")); - Address owner = sdk.getChainOwnerAccount().address; - - // why is being called twice? - sdk.callFunction(orderBook, &OrderBook::setDecimals); + uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, + BAddr, std::string("B_Token"), decB); + REQUIRE(sdk.getState().getDumpManagerSize() == 6); + } - // sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("1000000000000000000")); - // sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("1000000000000000000")); + SECTION("Orderbook add bit limit order") { + // start the sdk environment + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + // create the ERC20 contract + Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + // get decimals from the ERC20 contract + uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, + BAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + // get bids + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(bids.size() == 4); + } - // sdk.callFunction(AAddr, &ERC20::approve, BAddr, uint256_t("1000000000000000000")); - // sdk.callFunction(BAddr, &ERC20::approve, AAddr, uint256_t("1000000000000000000")); + SECTION("Orderbook add ask limit order") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, + BAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + // get asks + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + // verify the number of bid orders + REQUIRE(asks.size() == 4); + } - // - sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); - sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + SECTION("Orderbook add bid and ask order limit to match a transaction") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, + BAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + // get asks and bids + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(asks.size() == 0); + REQUIRE(bids.size() == 0); + } - sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); - sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); - - // sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("10000"), uint256_t("10")); - // sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("10000"), uint256_t("10")); - // sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("10000"), uint256_t("10")); - // sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("10000"), uint256_t("10")); + SECTION("Orderbook add ask and bid limit order to match a transaction") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, + BAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + // get asks and bids + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(asks.size() == 0); + REQUIRE(bids.size() == 0); + } } } -} + From 28c1c664cd80d31816bdc6c3ebad8b2cc787264e Mon Sep 17 00:00:00 2001 From: lambdart Date: Fri, 13 Dec 2024 12:06:05 -0300 Subject: [PATCH 11/29] Add delete bid limit order test --- tests/contract/orderbook.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index 754b5e50..e84598b5 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -139,6 +139,31 @@ namespace TORDERBOOK { REQUIRE(asks.size() == 0); REQUIRE(bids.size() == 0); } + + SECTION("Orderbook delete bid limit order") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, + BAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + // get bids + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(bids.size() == 2); + // get bid id + uint256_t id = std::get<0>(*(bids.cbegin())); + // delete bid order + sdk.callFunction(orderBook, &OrderBook::delBidLimitOrder, id); + // verify the number of bid orders + REQUIRE(sdk.callViewFunction(orderBook, &OrderBook::getBids).size() == 0); + } } } - From 95d164193efb1977edfb0d3a155c69ff416a696a Mon Sep 17 00:00:00 2001 From: lambdart Date: Fri, 13 Dec 2024 12:06:22 -0300 Subject: [PATCH 12/29] Remove const type qualifier (temporary) --- src/contract/templates/orderbook/orderbook.h | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index b73a18b4..1b1e375f 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -100,12 +100,20 @@ inline bool operator>(const StopOrder& lhs, const StopOrder& rhs) { * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. * 5 - const OrderType - type - Whether the order originally is a market or limit. */ -using Order = std::tuple; + +using Order = std::tuple; + OrderType>; /** * Lesser comparison operator. From 2a2ec3d2f43f784f7a7d3bd8a879b547a1c5da8d Mon Sep 17 00:00:00 2001 From: lambdart Date: Fri, 13 Dec 2024 12:08:15 -0300 Subject: [PATCH 13/29] Add build no cache action --- scripts/auto_actions.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/auto_actions.sh b/scripts/auto_actions.sh index 3346a467..16a95ec5 100644 --- a/scripts/auto_actions.sh +++ b/scripts/auto_actions.sh @@ -130,6 +130,14 @@ _build_action () _compose_action build } +_build_no_cache_action () +{ + # update services if necessary + _COMPOSE_SERVICE=${_COMPOSE_SERVICE:=`(_services_action)`} + + # build services + _compose_action build --no-cache +} _start_action () { # update services if necessary From b341be5562c3551f0b79f7f78782f615e28c4f2b Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 18 Dec 2024 16:36:23 -0300 Subject: [PATCH 14/29] Add initialize list (this) --- .../templates/orderbook/orderbook.cpp | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp index 01da5d0b..b8aaaf51 100644 --- a/src/contract/templates/orderbook/orderbook.cpp +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -17,7 +17,13 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint tickerAssetA_(this), addressAssetB_(this), tickerAssetB_(this), - spread_(this) + spread_(this), + tickSize_(this), + lotSize_(this), + lastPrice_(this), + bids_(this), + asks_(this), + stops_(this) { // set this->nextOrderID_ = 0; @@ -36,9 +42,12 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint this->tickerAssetA_.commit(); this->tickerAssetB_.commit(); this->spread_.commit(); - this->lastPrice_.commit(); this->lotSize_.commit(); this->tickSize_.commit(); + this->lastPrice_.commit(); + this->bids_.commit(); + this->asks_.commit(); + this->stops_.commit(); // register functions this->registerContractFunctions(); // enable register @@ -48,9 +57,12 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint this->tickerAssetA_.enableRegister(); this->tickerAssetB_.enableRegister(); this->spread_.enableRegister(); - this->lastPrice_.enableRegister(); this->lotSize_.enableRegister(); this->tickSize_.enableRegister(); + this->lastPrice_.enableRegister(); + this->bids_.enableRegister(); + this->asks_.enableRegister(); + this->stops_.enableRegister(); } OrderBook::OrderBook( @@ -81,9 +93,12 @@ OrderBook::OrderBook( this->tickerAssetA_.commit(); this->tickerAssetB_.commit(); this->spread_.commit(); - this->lastPrice_.commit(); this->lotSize_.commit(); this->tickSize_.commit(); + this->lastPrice_.commit(); + this->bids_.commit(); + this->asks_.commit(); + this->stops_.commit(); // register functions this->registerContractFunctions(); // enable register @@ -93,9 +108,12 @@ OrderBook::OrderBook( this->tickerAssetA_.enableRegister(); this->tickerAssetB_.enableRegister(); this->spread_.enableRegister(); - this->lastPrice_.enableRegister(); this->lotSize_.enableRegister(); this->tickSize_.enableRegister(); + this->lastPrice_.enableRegister(); + this->bids_.enableRegister(); + this->asks_.enableRegister(); + this->stops_.enableRegister(); } inline uint256_t OrderBook::convertToken(const uint256_t& assetAmount, @@ -145,6 +163,7 @@ Order* OrderBook::findMatchAskOrder(const Order& bidOrder) auto& askAssetPrice = std::get<4>(*askOrder); auto& bidAssetPrice = std::get<4>(bidOrder); auto& bidOrderType = std::get<5>(bidOrder); + switch (bidOrderType) { // doesn't matter the price return the first ask order found case OrderType::MARKET: { @@ -196,6 +215,7 @@ void OrderBook::evaluateBidOrder(Order& bidOrder) { Order *matchAskOrder; // get bid order attributes values + const auto& bidId = std::get<0>(bidOrder); const auto& bidOwner = std::get<2>(bidOrder); auto& bidAssetAmount = std::get<3>(bidOrder); auto& bidAssetPrice = std::get<4>(bidOrder); @@ -300,8 +320,7 @@ void OrderBook::addBidLimitOrder(const uint256_t& assetAmount, throw std::runtime_error("OrderBook::addBidLimitOrder: INSUFFICIENT_BALANCE"); } // transfer token amount to order book contract - // evaluate the bid limit order - // increment the order id + // evaluate the bid limit order and increment the next order id this->transferToContract(this->addressAssetB_.get(), tokenAmount); this->evaluateBidOrder(*(this->makeOrder(assetAmount, assetPrice, @@ -322,10 +341,11 @@ void OrderBook::delBidLimitOrder(const uint256_t& id) if (bidOwner != this->getCaller()) { throw std::runtime_error("OrderBook::delBidLimitOrder: INVALID_OWNER"); } + uint256_t tokenAmount = this->convertToken(bidAssetAmount, bidAssetPrice); this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, bidOwner, - this->convertToken(bidAssetAmount, bidAssetPrice)); + tokenAmount); return true; }); } @@ -346,8 +366,7 @@ void OrderBook::addAskLimitOrder(const uint256_t& assetAmount, throw std::runtime_error("OrderBook::addAskLimitOrder: INSUFFICIENT_BALANCE"); } // transfer lot amount to order book contract - // evaluate the the nearly created ask limit order - // increment next order id + // evaluate the the nearly created ask limit order and increment next order id this->transferToContract(this->addressAssetA_.get(), lotAmount); this->evaluateAskOrder(*(this->makeOrder(assetAmount, assetPrice, @@ -417,7 +436,9 @@ inline void OrderBook::updateLastPrice(const uint256_t &price) void OrderBook::updateSpreadAndMidPrice() { - if (this->bids_.empty() or this->asks_.empty()) return; + if (this->bids_.empty() or + this->asks_.empty()) + return; uint256_t bidPrice = std::get<4>(*this->bids_.cbegin()); uint256_t askPrice = std::get<4>(*this->asks_.cbegin()); this->spread_ = (std::max(bidPrice, askPrice) -\ From dec652fa775cd657f2e183030baeaa8807f152c6 Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 18 Dec 2024 16:37:48 -0300 Subject: [PATCH 15/29] Add simple tests --- tests/contract/orderbook.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index e84598b5..cd757ccc 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -45,7 +45,7 @@ namespace TORDERBOOK { REQUIRE(sdk.getState().getDumpManagerSize() == 6); } - SECTION("Orderbook add bit limit order") { + SECTION("Orderbook add bid limit order") { // start the sdk environment SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; @@ -64,10 +64,15 @@ namespace TORDERBOOK { // add bid order sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); // get bids auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // show me + for (const auto& b : bids) { + std::cout << std::get<0>(b) << " " << std::get<1>(b) << std::endl; + } // verify the number of bid orders - REQUIRE(bids.size() == 4); + REQUIRE(bids.size() == 3); } SECTION("Orderbook add ask limit order") { @@ -86,10 +91,15 @@ namespace TORDERBOOK { // add bid order sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); // get asks auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + // show me + for (const auto& a : asks) { + std::cout << std::get<0>(a) << " " << std::get<1>(a) << std::endl; + } // verify the number of bid orders - REQUIRE(asks.size() == 4); + REQUIRE(asks.size() == 3); } SECTION("Orderbook add bid and ask order limit to match a transaction") { @@ -112,7 +122,7 @@ namespace TORDERBOOK { auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders - REQUIRE(asks.size() == 0); + REQUIRE(asks.size() == 1); REQUIRE(bids.size() == 0); } @@ -137,7 +147,7 @@ namespace TORDERBOOK { auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders REQUIRE(asks.size() == 0); - REQUIRE(bids.size() == 0); + REQUIRE(bids.size() == 1); } SECTION("Orderbook delete bid limit order") { @@ -157,7 +167,7 @@ namespace TORDERBOOK { // get bids auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders - REQUIRE(bids.size() == 2); + REQUIRE(bids.size() == 1); // get bid id uint256_t id = std::get<0>(*(bids.cbegin())); // delete bid order From 545cd7bf4cecf52bdcd4c81565a2a91487197a5a Mon Sep 17 00:00:00 2001 From: lambdart Date: Thu, 19 Dec 2024 14:02:29 -0300 Subject: [PATCH 16/29] Fix memory leaks --- .../templates/orderbook/orderbook.cpp | 44 ++++++++++--------- src/contract/templates/orderbook/orderbook.h | 12 ++--- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp index b8aaaf51..0d70acfc 100644 --- a/src/contract/templates/orderbook/orderbook.cpp +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -211,7 +211,7 @@ Order* OrderBook::findMatchBidOrder(const Order& askOrder) return nullptr; } -void OrderBook::evaluateBidOrder(Order& bidOrder) +void OrderBook::evaluateBidOrder(Order&& bidOrder) { Order *matchAskOrder; // get bid order attributes values @@ -248,7 +248,7 @@ void OrderBook::evaluateBidOrder(Order& bidOrder) this->updateSpreadAndMidPrice(); } -void OrderBook::evaluateAskOrder(Order& askOrder) +void OrderBook::evaluateAskOrder(Order&& askOrder) { Order *matchBidOrder; const auto& askOwner = std::get<2>(askOrder); @@ -293,16 +293,16 @@ void OrderBook::transferToContract(const Address& assetAddress, assetAmount); } -Order* OrderBook::makeOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice, - const OrderType orderType) +Order OrderBook::makeOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice, + const OrderType orderType) { - return (new Order(this->nextOrderID_.get(), - this->getCurrentTimestamp(), - this->getCaller(), - assetAmount, - assetPrice, - orderType)); + return Order(this->nextOrderID_.get(), + this->getCurrentTimestamp(), + this->getCaller(), + assetAmount, + assetPrice, + orderType); } void OrderBook::addBidLimitOrder(const uint256_t& assetAmount, @@ -322,9 +322,9 @@ void OrderBook::addBidLimitOrder(const uint256_t& assetAmount, // transfer token amount to order book contract // evaluate the bid limit order and increment the next order id this->transferToContract(this->addressAssetB_.get(), tokenAmount); - this->evaluateBidOrder(*(this->makeOrder(assetAmount, - assetPrice, - OrderType::LIMIT))); + this->evaluateBidOrder(std::move(this->makeOrder(assetAmount, + assetPrice, + OrderType::LIMIT))); this->nextOrderID_++; } @@ -368,9 +368,9 @@ void OrderBook::addAskLimitOrder(const uint256_t& assetAmount, // transfer lot amount to order book contract // evaluate the the nearly created ask limit order and increment next order id this->transferToContract(this->addressAssetA_.get(), lotAmount); - this->evaluateAskOrder(*(this->makeOrder(assetAmount, - assetPrice, - OrderType::LIMIT))); + this->evaluateAskOrder(std::move(this->makeOrder(assetAmount, + assetPrice, + OrderType::LIMIT))); this->nextOrderID_++; } @@ -384,9 +384,9 @@ void OrderBook::delAskLimitOrder(const uint256_t& id) auto const& askOwner = std::get<2>(askOrder); auto const& askAssetAmount = std::get<3>(askOrder); if (askOwner != this->getCaller()) { - throw std::runtime_error("OrderBook::delBidLimitOrder: INVALID_OWNER"); + throw std::runtime_error("OrderBook::delAskLimitOrder: INVALID_OWNER"); } - this->callContractFunction(this->addressAssetB_.get(), + this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, askOwner, this->convertLot(askAssetAmount)); @@ -408,7 +408,9 @@ void OrderBook::addAskMarketOrder(const uint256_t& assetAmount) throw std::runtime_error("OrderBook::addAskMarketOrder: INSUFFICIENT_BALANCE"); } this->transferToContract(this->addressAssetA_.get(), lotAmount); - this->evaluateAskOrder(*(this->makeOrder(assetAmount, 0, OrderType::MARKET))); + this->evaluateAskOrder(std::move(this->makeOrder(assetAmount, + 0, + OrderType::MARKET))); this->nextOrderID_++; } @@ -425,7 +427,7 @@ void OrderBook::addBidMarketOrder(const uint256_t& assetAmount) if (tickAmount > assetBalance) { throw std::runtime_error("OrderBook::addBidMarketOrder: INSUFFICIENT_BALANCE"); } - this->evaluateBidOrder(*(this->makeOrder(assetAmount, 0, OrderType::MARKET))); + this->evaluateBidOrder(std::move(this->makeOrder(assetAmount, 0, OrderType::MARKET))); this->nextOrderID_++; } diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index 1b1e375f..2b4f6aab 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -187,11 +187,11 @@ class OrderBook : public DynamicContract { * Create a order. * @param assetAmount, the order asset amount. * @param assetPrice, the order asset prince. - * @return A pointer to the nearly created order. + * @return Order, the nearly created order. */ - Order* makeOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice, - const OrderType orderType); + Order makeOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice, + const OrderType orderType); /** * Execute the order, i.e, transfer the token amount to the ask owner and @@ -238,7 +238,7 @@ class OrderBook : public DynamicContract { * the bid order list (passive order). * @param bidOrder, the bid order. */ - void evaluateBidOrder(Order& bidOrder); + void evaluateBidOrder(Order&& bidOrder); /** * Evaluate the ask order, i.e, try to find the a matching bid order and @@ -246,7 +246,7 @@ class OrderBook : public DynamicContract { * the ask order list (passive order). * @param askOrder, the ask order. */ - void evaluateAskOrder(Order& askOrder); + void evaluateAskOrder(Order&& askOrder); /** * Find a matching ask order for an arbitrary bid order. From 55b441d98cfd37af16d66d5a3c2d8b6d8fb3ca9d Mon Sep 17 00:00:00 2001 From: lambdart Date: Thu, 19 Dec 2024 14:02:35 -0300 Subject: [PATCH 17/29] Add 'delete ask limit order' test --- tests/contract/orderbook.cpp | 134 ++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 58 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index cd757ccc..3a6dd772 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -23,25 +23,25 @@ namespace TORDERBOOK { SECTION("Orderbook creation") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); REQUIRE(sdk.getState().getDumpManagerSize() == 3); - Address AAddr = sdk.deployContract(std::string("A_Token"), + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TST"), uint8_t(18), uint256_t("1000000000000000000")); REQUIRE(sdk.getState().getDumpManagerSize() == 4); - REQUIRE(sdk.callViewFunction(AAddr, &ERC20::name) == "A_Token"); - REQUIRE(sdk.callViewFunction(AAddr, &ERC20::decimals) > 8); + REQUIRE(sdk.callViewFunction(askAddr, &ERC20::name) == "A_Token"); + REQUIRE(sdk.callViewFunction(askAddr, &ERC20::decimals) > 8); - Address BAddr = sdk.deployContract(std::string("B_Token"), + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TST"), uint8_t(18), uint256_t("1000000000000000000")); REQUIRE(sdk.getState().getDumpManagerSize() == 5); - REQUIRE(sdk.callViewFunction(BAddr, &ERC20::name) == "B_Token"); + REQUIRE(sdk.callViewFunction(bidAddr, &ERC20::name) == "B_Token"); - uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); - uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, - BAddr, std::string("B_Token"), decB); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); REQUIRE(sdk.getState().getDumpManagerSize() == 6); } @@ -50,27 +50,23 @@ namespace TORDERBOOK { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; // create the ERC20 contract - Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); - Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); // get decimals from the ERC20 contract - uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); - uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); // create the contract - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, - BAddr, std::string("B_Token"), decB); + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); // approve orderbook to transfer the tokens - sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); - sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); // get bids auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); - // show me - for (const auto& b : bids) { - std::cout << std::get<0>(b) << " " << std::get<1>(b) << std::endl; - } // verify the number of bid orders REQUIRE(bids.size() == 3); } @@ -78,26 +74,22 @@ namespace TORDERBOOK { SECTION("Orderbook add ask limit order") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; - Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); - Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); - uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); - uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); // create the contract - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, - BAddr, std::string("B_Token"), decB); + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); // approve orderbook to transfer the tokens - sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); - sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); // get asks auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); - // show me - for (const auto& a : asks) { - std::cout << std::get<0>(a) << " " << std::get<1>(a) << std::endl; - } // verify the number of bid orders REQUIRE(asks.size() == 3); } @@ -105,16 +97,16 @@ namespace TORDERBOOK { SECTION("Orderbook add bid and ask order limit to match a transaction") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; - Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); - Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); - uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); - uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); // create the contract - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, - BAddr, std::string("B_Token"), decB); + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); // approve orderbook to transfer the tokens - sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); - sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); @@ -122,23 +114,23 @@ namespace TORDERBOOK { auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders - REQUIRE(asks.size() == 1); + REQUIRE(asks.size() == 0); REQUIRE(bids.size() == 0); } SECTION("Orderbook add ask and bid limit order to match a transaction") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; - Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); - Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); - uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); - uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); // create the contract - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, - BAddr, std::string("B_Token"), decB); + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); // approve orderbook to transfer the tokens - sdk.callFunction(AAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); - sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); @@ -147,21 +139,21 @@ namespace TORDERBOOK { auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders REQUIRE(asks.size() == 0); - REQUIRE(bids.size() == 1); + REQUIRE(bids.size() == 0); } SECTION("Orderbook delete bid limit order") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; - Address AAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); - Address BAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); - uint8_t decA = sdk.callViewFunction(AAddr, &ERC20::decimals); - uint8_t decB = sdk.callViewFunction(BAddr, &ERC20::decimals); + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); // create the contract - Address orderBook = sdk.deployContract(AAddr, std::string("A_Token"), decA, - BAddr, std::string("B_Token"), decB); + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); // approve orderbook to transfer the tokens - sdk.callFunction(BAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); // get bids @@ -175,5 +167,31 @@ namespace TORDERBOOK { // verify the number of bid orders REQUIRE(sdk.callViewFunction(orderBook, &OrderBook::getBids).size() == 0); } + + SECTION("Orderbook delete ask limit order") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + // get bids + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + // verify the number of bid orders + REQUIRE(asks.size() == 1); + // get ask id + uint256_t id = std::get<0>(*(asks.cbegin())); + // delete bid order + sdk.callFunction(orderBook, &OrderBook::delAskLimitOrder, id); + // verify the number of bid orders + REQUIRE(sdk.callViewFunction(orderBook, &OrderBook::getAsks).size() == 0); + } } } From 82c0eadfbae7c3ffe2859b11b4cb5d91b2c71d7a Mon Sep 17 00:00:00 2001 From: lambdart Date: Tue, 21 Jan 2025 16:38:54 -0300 Subject: [PATCH 18/29] Modify market order parameters --- .../templates/orderbook/orderbook.cpp | 33 +++++++++++---- src/contract/templates/orderbook/orderbook.h | 41 +++++++++++++------ 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp index 0d70acfc..1eea462a 100644 --- a/src/contract/templates/orderbook/orderbook.cpp +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -394,7 +394,8 @@ void OrderBook::delAskLimitOrder(const uint256_t& id) }); } -void OrderBook::addAskMarketOrder(const uint256_t& assetAmount) +void OrderBook::addAskMarketOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice) { // set asset balance uint256_t assetBalance = \ @@ -409,16 +410,17 @@ void OrderBook::addAskMarketOrder(const uint256_t& assetAmount) } this->transferToContract(this->addressAssetA_.get(), lotAmount); this->evaluateAskOrder(std::move(this->makeOrder(assetAmount, - 0, + assetPrice, OrderType::MARKET))); this->nextOrderID_++; } -void OrderBook::addBidMarketOrder(const uint256_t& assetAmount) +void OrderBook::addBidMarketOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice) { // set asset balance uint256_t assetBalance = \ - this->callContractViewFunction(this->addressAssetA_.get(), + this->callContractViewFunction(this->addressAssetB_.get(), &ERC20::balanceOf, this->getCaller()); // convert tick amount @@ -427,7 +429,10 @@ void OrderBook::addBidMarketOrder(const uint256_t& assetAmount) if (tickAmount > assetBalance) { throw std::runtime_error("OrderBook::addBidMarketOrder: INSUFFICIENT_BALANCE"); } - this->evaluateBidOrder(std::move(this->makeOrder(assetAmount, 0, OrderType::MARKET))); + this->transferToContract(this->addressAssetB_.get(), tickAmount); + this->evaluateBidOrder(std::move(this->makeOrder(assetAmount, + assetPrice, + OrderType::MARKET))); this->nextOrderID_++; } @@ -453,6 +458,16 @@ uint64_t OrderBook::getCurrentTimestamp() const (std::chrono::system_clock::now().time_since_epoch()).count(); } +Order OrderBook::getFirstBid() const +{ + return *(this->bids_.cbegin()); +} + +Order OrderBook::getFirstAsk() const +{ + return *(this->asks_.cbegin()); +} + std::vector OrderBook::getBids() const { std::vector ret; @@ -508,15 +523,17 @@ void OrderBook::registerContractFunctions() this->registerMemberFunction("getLotSize", &OrderBook::getLotSize, FunctionTypes::View, this); this->registerMemberFunction("getLastPrice", &OrderBook::getLastPrice, FunctionTypes::View, this); this->registerMemberFunction("getPrecision", &OrderBook::getPrecision, FunctionTypes::View, this); - this->registerMemberFunction("getBids", &OrderBook::getBids, FunctionTypes::View, this); this->registerMemberFunction("getAsks", &OrderBook::getAsks, FunctionTypes::View, this); + this->registerMemberFunction("getBids", &OrderBook::getBids, FunctionTypes::View, this); + this->registerMemberFunction("getFirstAsk", &OrderBook::getFirstAsk, FunctionTypes::View, this); + this->registerMemberFunction("getFirstBid", &OrderBook::getFirstBid, FunctionTypes::View, this); this->registerMemberFunction("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, this); this->registerMemberFunction("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); this->registerMemberFunction("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, this); this->registerMemberFunction("delAskLimitOrder", &OrderBook::delAskLimitOrder, FunctionTypes::NonPayable, this); this->registerMemberFunction("delBidLimitOrder", &OrderBook::delBidLimitOrder, FunctionTypes::NonPayable, this); - this->registerMemberFunction("addAskMarketOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); - this->registerMemberFunction("addBidMarketOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addAskMarketOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addBidMarketOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, this); } DBBatch OrderBook::dump() const diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index 2b4f6aab..da36376c 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -100,6 +100,12 @@ inline bool operator>(const StopOrder& lhs, const StopOrder& rhs) { * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. * 5 - const OrderType - type - Whether the order originally is a market or limit. */ +using Order = std::tuple; // using Order = std::tuple(const StopOrder& lhs, const StopOrder& rhs) { // const uint256_t, // const OrderType>; -using Order = std::tuple; - /** * Lesser comparison operator. * @param lhs The left hand side of the comparison. @@ -374,11 +373,23 @@ class OrderBook : public DynamicContract { */ std::vector getBids() const; + /** + * Get the first bid order. + * @return The bid order. + */ + Order getFirstBid() const; + + /** + * Get the first ask order. + * @return The ask order + */ + Order getFirstAsk() const; + /** * Getter for all asks. * @return A vector with all asks. */ - std::vector getAsks() const; + std::vector getAsks() const; /** * Getter for all users orders @@ -423,13 +434,15 @@ class OrderBook : public DynamicContract { * Add a market ask order to be evaluated. * @param assetAmount, the market ask order asset amount. */ - void addAskMarketOrder(const uint256_t& assetAmount); + void addAskMarketOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); /** * Add a market bid order to be evaluated. * @param assetAmount, the market bid order asset amount. */ - void addBidMarketOrder(const uint256_t& assetAmount); + void addBidMarketOrder(const uint256_t& assetAmount, + const uint256_t& assetPrice); /// Register the contract structure. static void registerContract() { @@ -455,15 +468,17 @@ class OrderBook : public DynamicContract { std::make_tuple("getLotSize", &OrderBook::getLotSize, FunctionTypes::View, std::vector{}), std::make_tuple("getLastPrice", &OrderBook::getLastPrice, FunctionTypes::View, std::vector{}), std::make_tuple("getPrecision", &OrderBook::getPrecision, FunctionTypes::View, std::vector{}), - std::make_tuple("getBids", &OrderBook::getBids, FunctionTypes::View, std::vector{}), std::make_tuple("getAsks", &OrderBook::getAsks, FunctionTypes::View, std::vector{}), + std::make_tuple("getBids", &OrderBook::getBids, FunctionTypes::View, std::vector{}), + std::make_tuple("getFirstAsk", &OrderBook::getFirstAsk, FunctionTypes::View, std::vector{}), + std::make_tuple("getFirstBid", &OrderBook::getFirstBid, FunctionTypes::View, std::vector{}), std::make_tuple("getUserOrders", &OrderBook::getUserOrders, FunctionTypes::View, std::vector{"user"}), std::make_tuple("addBidLimitOrder", &OrderBook::addBidLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), std::make_tuple("addAskLimitOrder", &OrderBook::addAskLimitOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), std::make_tuple("delAskLimitOrder", &OrderBook::delAskLimitOrder, FunctionTypes::NonPayable, std::vector{"id"}), std::make_tuple("delBidLimitOrder", &OrderBook::delBidLimitOrder, FunctionTypes::NonPayable, std::vector{"id"}), - std::make_tuple("addAskLimitOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount"}), - std::make_tuple("addAskLimitOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount"}) + std::make_tuple("addAskMarketOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("addBidMarketOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}) ); } }; From 59028287e13eed3afa329ed2a2a0fd6c65ce14f4 Mon Sep 17 00:00:00 2001 From: lambdart Date: Tue, 21 Jan 2025 16:39:11 -0300 Subject: [PATCH 19/29] Add test for market orders --- tests/contract/orderbook.cpp | 88 ++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index 3a6dd772..aa7dd2cd 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -24,17 +24,17 @@ namespace TORDERBOOK { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); REQUIRE(sdk.getState().getDumpManagerSize() == 3); Address askAddr = sdk.deployContract(std::string("A_Token"), - std::string("TST"), - uint8_t(18), - uint256_t("1000000000000000000")); + std::string("TST"), + uint8_t(18), + uint256_t("1000000000000000000")); REQUIRE(sdk.getState().getDumpManagerSize() == 4); REQUIRE(sdk.callViewFunction(askAddr, &ERC20::name) == "A_Token"); REQUIRE(sdk.callViewFunction(askAddr, &ERC20::decimals) > 8); Address bidAddr = sdk.deployContract(std::string("B_Token"), - std::string("TST"), - uint8_t(18), - uint256_t("1000000000000000000")); + std::string("TST"), + uint8_t(18), + uint256_t("1000000000000000000")); REQUIRE(sdk.getState().getDumpManagerSize() == 5); REQUIRE(sdk.callViewFunction(bidAddr, &ERC20::name) == "B_Token"); @@ -65,10 +65,10 @@ namespace TORDERBOOK { sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); - // get bids - auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // get bid + auto bid = sdk.callViewFunction(orderBook, &OrderBook::getFirstBid); // verify the number of bid orders - REQUIRE(bids.size() == 3); + REQUIRE(sdk.callViewFunction(orderBook, &OrderBook::getBids).size() == 3); } SECTION("Orderbook add ask limit order") { @@ -193,5 +193,75 @@ namespace TORDERBOOK { // verify the number of bid orders REQUIRE(sdk.callViewFunction(orderBook, &OrderBook::getAsks).size() == 0); } + + SECTION("Orderbook add market order") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add ask market order + sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); + // get asks orders + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + // verify the number of ask orders + REQUIRE(asks.size() == 3); + } + + SECTION("Orderbook add bid market order") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid market order + sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); + // get bids orders + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(bids.size() == 3); + } + + SECTION("Orderbook add ask and bid market order to match a transaction") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("20")); + sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); + // get asks and bids + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(asks.size() == 0); + REQUIRE(bids.size() == 0); + } } } From 15985f5ef08cec8c2a1e8f1a441d09df138e4d0f Mon Sep 17 00:00:00 2001 From: lambdart Date: Fri, 31 Jan 2025 17:24:43 -0300 Subject: [PATCH 20/29] Add evaluation of market orders methods (bid and ask) --- .../templates/orderbook/orderbook.cpp | 298 +++++++++++------- src/contract/templates/orderbook/orderbook.h | 81 +++-- 2 files changed, 242 insertions(+), 137 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp index 1eea462a..e49b90b7 100644 --- a/src/contract/templates/orderbook/orderbook.cpp +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -33,6 +33,7 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint this->tickerAssetB_ = tickerB; this->spread_ = 0; this->lastPrice_ = 0; + // constant this->tickSize_ = Utils::exp10(decB - 4); this->lotSize_ = Utils::exp10(decA - 4); // commit @@ -65,16 +66,15 @@ OrderBook::OrderBook(const Address& addA, const std::string& tickerA, const uint this->stops_.enableRegister(); } -OrderBook::OrderBook( - const Address& address, - const DB &db - ) : DynamicContract(address, db), - nextOrderID_(this), - addressAssetA_(this), - tickerAssetA_(this), - addressAssetB_(this), - tickerAssetB_(this), - spread_(this) +OrderBook::OrderBook(const Address& address, + const DB &db) + : DynamicContract(address, db), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this) { // set this->nextOrderID_ = UintConv::bytesToUint256(db.get(std::string("nextOrderID_"), this->getDBPrefix())); @@ -116,12 +116,6 @@ OrderBook::OrderBook( this->stops_.enableRegister(); } -inline uint256_t OrderBook::convertToken(const uint256_t& assetAmount, - const uint256_t& assetPrice) const -{ - return ((this->convertTick(assetAmount * assetPrice)) / this->precision_); -} - inline void OrderBook::insertAskOrder(const Order& askOrder) { this->asks_.insert(std::move(askOrder)); @@ -144,24 +138,24 @@ inline void OrderBook::eraseBidOrder(const Order& bidOrder) void OrderBook::executeOrder(const Address& askOwner, const Address& bidOwner, - uint256_t tokenAmount, - uint256_t assetAmount) + uint256_t tokensToBePaid, + uint256_t tokenAmount) { - this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, askOwner, tokenAmount); - this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, bidOwner, this->convertLot(assetAmount)); + this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, askOwner, tokensToBePaid); + this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, bidOwner, this->tokensLot(tokenAmount)); } Order* OrderBook::findMatchAskOrder(const Order& bidOrder) { - auto& bidAssetAmount = std::get<3>(bidOrder); + auto& bidTokenAmount = std::get<3>(bidOrder); // do we have any asks orders? if (this->asks_.empty()) { return nullptr; } // get the first ask order const Order* askOrder = &(*(this->asks_.begin())); - auto& askAssetPrice = std::get<4>(*askOrder); - auto& bidAssetPrice = std::get<4>(bidOrder); + auto& askTokenPrice = std::get<4>(*askOrder); + auto& bidTokenPrice = std::get<4>(bidOrder); auto& bidOrderType = std::get<5>(bidOrder); switch (bidOrderType) { @@ -171,7 +165,7 @@ Order* OrderBook::findMatchAskOrder(const Order& bidOrder) } // we want to buy for the lowest price case OrderType::LIMIT: { - return ((bidAssetPrice <= askAssetPrice) ? const_cast(askOrder) : nullptr); + return ((bidTokenPrice <= askTokenPrice) ? const_cast(askOrder) : nullptr); } // default do nothing default: @@ -183,7 +177,7 @@ Order* OrderBook::findMatchAskOrder(const Order& bidOrder) Order* OrderBook::findMatchBidOrder(const Order& askOrder) { - auto& askAssetAmount = std::get<3>(askOrder); + auto& askTokenAmount = std::get<3>(askOrder); // do we have bid orders? if (this->bids_.empty()) { return nullptr; @@ -192,8 +186,8 @@ Order* OrderBook::findMatchBidOrder(const Order& askOrder) const Order* bidOrder = &(*(this->bids_.begin())); // we want to sell for the higher bid price // but never not lower to the ask price limit - auto& bidAssetPrice = std::get<4>(*bidOrder); - auto& askAssetPrice = std::get<4>(askOrder); + auto& bidTokenPrice = std::get<4>(*bidOrder); + auto& askTokenPrice = std::get<4>(askOrder); auto& askOrderType = std::get<5>(askOrder); switch (askOrderType) { // doesn't matter the price return the first bid order found @@ -202,7 +196,7 @@ Order* OrderBook::findMatchBidOrder(const Order& askOrder) } // we want to sell at the highest price case OrderType::LIMIT: { - return ((bidAssetPrice >= askAssetPrice) ? const_cast(bidOrder) : nullptr); + return ((bidTokenPrice >= askTokenPrice) ? const_cast(bidOrder) : nullptr); } default: break; @@ -211,37 +205,72 @@ Order* OrderBook::findMatchBidOrder(const Order& askOrder) return nullptr; } +void OrderBook::evaluateMarketBidOrder(Order&& bidOrder) +{ + Order *matchAskOrder; + // get bid order attributes values + const auto& bidOwner = std::get<2>(bidOrder); + auto& bidTokenAmount = std::get<3>(bidOrder); + uint256_t bidTokensToBePaid = this->tokensTick(bidTokenAmount); + // find the ask order + while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and + (bidTokensToBePaid > 0)) { + const auto& askOwner = std::get<2>(*matchAskOrder); + auto& askTokenAmount = std::get<3>(*matchAskOrder); + auto& askTokenPrice = std::get<4>(*matchAskOrder); + // compute the tokens amount and how much must be transfer (temporary variables) + uint256_t tokenAmount = std::min(askTokenAmount, ((bidTokensToBePaid * 10000) / askTokenPrice)); + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, askTokenPrice); + // transfer and execute + this->transferToContract(this->addressAssetB_.get(), tokensToBePaid); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); + // update bidTicks information + bidTokensToBePaid -= tokensToBePaid; + // update amount information + bidTokenAmount -= tokenAmount; + askTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(askTokenPrice); + // remove order if it was filled + if (askTokenAmount == 0) { + this->eraseAskOrder(*matchAskOrder); + } + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + void OrderBook::evaluateBidOrder(Order&& bidOrder) { Order *matchAskOrder; // get bid order attributes values - const auto& bidId = std::get<0>(bidOrder); const auto& bidOwner = std::get<2>(bidOrder); - auto& bidAssetAmount = std::get<3>(bidOrder); - auto& bidAssetPrice = std::get<4>(bidOrder); + auto& bidTokenAmount = std::get<3>(bidOrder); + auto& bidTokenPrice = std::get<4>(bidOrder); while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and - (bidAssetAmount > 0)) { + (bidTokenAmount > 0)) { // get ask order attributes values const auto& askOwner = std::get<2>(*matchAskOrder); - auto& askAssetAmount = std::get<3>(*matchAskOrder); - auto& askAssetPrice = std::get<4>(*matchAskOrder); - // compute the asset and token amount - uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); - uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); + auto& askTokenAmount = std::get<3>(*matchAskOrder); + auto& askTokenPrice = std::get<4>(*matchAskOrder); + // compute the aTokens and bTokens to be transfered + uint256_t tokenAmount = std::min(askTokenAmount, bidTokenAmount); + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, askTokenPrice); // executes the order, transfer the tokens from ask owner to bid owner - this->executeOrder(askOwner, bidOwner, tokenAmount, assetAmount); + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); // update amount information - bidAssetAmount -= assetAmount; - askAssetAmount -= assetAmount; + bidTokenAmount -= tokenAmount; + askTokenAmount -= tokenAmount; // update the current price - this->updateLastPrice(askAssetPrice); + this->updateLastPrice(askTokenPrice); // erase the ask asset if filled - if (askAssetAmount == 0) { + if (askTokenAmount == 0) { this->eraseAskOrder(*matchAskOrder); } } // handle the bid order that was not filled (remainder) - if (bidAssetAmount > 0) { + if (bidTokenAmount > 0) { this->insertBidOrder(bidOrder); } // update spread and mid price @@ -252,78 +281,113 @@ void OrderBook::evaluateAskOrder(Order&& askOrder) { Order *matchBidOrder; const auto& askOwner = std::get<2>(askOrder); - auto& askAssetAmount = std::get<3>(askOrder); - auto& askAssetPrice = std::get<4>(askOrder); - while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and - (askAssetAmount > 0)) { + auto& askTokenAmount = std::get<3>(askOrder); + auto& askTokenPrice = std::get<4>(askOrder); + while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and \ + (askTokenAmount > 0)) { // get bid order attributes values const auto& bidOwner = std::get<2>(*matchBidOrder); - auto& bidAssetAmount = std::get<3>(*matchBidOrder); - auto& bidAssetPrice = std::get<4>(*matchBidOrder); + auto& bidTokenAmount = std::get<3>(*matchBidOrder); + auto& bidTokenPrice = std::get<4>(*matchBidOrder); // compute the asset and token amount - uint256_t assetAmount = std::min(askAssetAmount, bidAssetAmount); - uint256_t tokenAmount = this->convertToken(assetAmount, askAssetPrice); + uint256_t tokenAmount = std::min(askTokenAmount, bidTokenAmount); + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, bidTokenPrice); // executes the order, transfer the tokens from ask owner to bid owner - this->executeOrder(askOwner, bidOwner, tokenAmount, assetAmount); + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); // update order asset amounts - askAssetAmount -= assetAmount; - bidAssetAmount -= assetAmount; + askTokenAmount -= tokenAmount; + bidTokenAmount -= tokenAmount; // update the current price - this->updateLastPrice(bidAssetPrice); + this->updateLastPrice(bidTokenPrice); // erase the bid order if was filled - if (bidAssetAmount == 0) { + if (bidTokenAmount == 0) { this->eraseBidOrder(*matchBidOrder); } } // handle the ask order that was not filled (remainder) - if (askAssetAmount > 0) { + if (askTokenAmount > 0) { this->insertAskOrder(askOrder); } // update spread and mid price this->updateSpreadAndMidPrice(); } +void OrderBook::evaluateMarketAskOrder(Order&& askOrder) +{ + Order *matchBidOrder; + const auto& askOwner = std::get<2>(askOrder); + auto& askTokenAmount = std::get<3>(askOrder); + + while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and \ + (askTokenAmount > 0)) { + // get bid order attributes values + const auto& bidOwner = std::get<2>(*matchBidOrder); + auto& bidTokenAmount = std::get<3>(*matchBidOrder); + auto& bidTokenPrice = std::get<4>(*matchBidOrder); + // set the token lot amount + uint256_t tokenAmount = std::min(askTokenAmount, bidTokenAmount); + // computer how much tokens much be paid + uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, bidTokenPrice); + // executes the order, transfer the tokens from ask owner to bid owner + this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); + // update the order and lot amount + bidTokenAmount -= tokenAmount; + askTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(bidTokenPrice); + // erase the bid order if was filled + if (bidTokenAmount == 0) { + this->eraseBidOrder(*matchBidOrder); + } + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + void OrderBook::transferToContract(const Address& assetAddress, - const uint256_t& assetAmount) + const uint256_t& tokenAmount) { this->callContractFunction(assetAddress, &ERC20::transferFrom, this->getCaller(), this->getContractAddress(), - assetAmount); + tokenAmount); } -Order OrderBook::makeOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice, +Order OrderBook::makeOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice, const OrderType orderType) { return Order(this->nextOrderID_.get(), this->getCurrentTimestamp(), this->getCaller(), - assetAmount, - assetPrice, + tokenAmount, + tokenPrice, orderType); } -void OrderBook::addBidLimitOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice) +void OrderBook::addBidLimitOrder(const uint256_t& tokenAmount, + // we want to buy for the lowest price + const uint256_t& tokenPrice) { - // get caller will return the owner address - uint256_t assetBalance = \ + // set the amount of B tokens available + uint256_t tokensBTotalBalance = \ this->callContractViewFunction(this->addressAssetB_.get(), &ERC20::balanceOf, this->getCaller()); - // convert to token amount - uint256_t tokenAmount = this->convertToken(assetAmount, assetPrice); - // verify the asset balance - if (tokenAmount > assetBalance) { + + // convert to the number of tokens + uint256_t tokensBToBePaid = this->tokensToBePaid(tokenAmount, tokenPrice); + + // verify the tokens balance + if (tokensBToBePaid > tokensBTotalBalance) { throw std::runtime_error("OrderBook::addBidLimitOrder: INSUFFICIENT_BALANCE"); } - // transfer token amount to order book contract + // transfer token to be paid to order book contract // evaluate the bid limit order and increment the next order id - this->transferToContract(this->addressAssetB_.get(), tokenAmount); - this->evaluateBidOrder(std::move(this->makeOrder(assetAmount, - assetPrice, + this->transferToContract(this->addressAssetB_.get(), tokensBToBePaid); + this->evaluateBidOrder(std::move(this->makeOrder(tokenAmount, + tokenPrice, OrderType::LIMIT))); this->nextOrderID_++; } @@ -336,12 +400,12 @@ void OrderBook::delBidLimitOrder(const uint256_t& id) return false; } auto const& bidOwner = std::get<2>(bidOrder); - auto const& bidAssetAmount = std::get<3>(bidOrder); - auto const& bidAssetPrice = std::get<4>(bidOrder); + auto const& bidTokenAmount = std::get<3>(bidOrder); + auto const& bidTokenPrice = std::get<4>(bidOrder); if (bidOwner != this->getCaller()) { throw std::runtime_error("OrderBook::delBidLimitOrder: INVALID_OWNER"); } - uint256_t tokenAmount = this->convertToken(bidAssetAmount, bidAssetPrice); + uint256_t tokenAmount = this->tokensToBePaid(bidTokenAmount, bidTokenPrice); this->callContractFunction(this->addressAssetB_.get(), &ERC20::transfer, bidOwner, @@ -350,26 +414,39 @@ void OrderBook::delBidLimitOrder(const uint256_t& id) }); } -void OrderBook::addAskLimitOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice) +// you can sell for the limit value that you want, but +// the value must be a multiplier of the lot size and +// and the tokens amount needs to be less then the total of A tokens +// available +void OrderBook::addAskLimitOrder(const uint256_t& tokenAmount, + // remember this is the low limit, we + // want to sell for the biggest available in + // the order book + const uint256_t& tokenPrice) { - // get owner asset balance - // get caller will return the owner address - uint256_t assetBalance = \ + uint256_t tokensTotalBalance = \ this->callContractViewFunction(this->addressAssetA_.get(), &ERC20::balanceOf, this->getCaller()); - // convert to lot amount - uint256_t lotAmount = this->convertLot(assetAmount); - // verify if lot amount is bigger than user balance - if (lotAmount > assetBalance) { - throw std::runtime_error("OrderBook::addAskLimitOrder: INSUFFICIENT_BALANCE"); + + // convert tokens amount to tokens lot + uint256_t tokensLot = this->tokensLot(tokenAmount); + // verify tokens available + if (tokensLot > tokensTotalBalance) { + throw std::runtime_error("OrderBook::addAskLimitOrder:" \ + "Insufficient number of tokens"); + } + // verify if asset price is of lot sizable + if (not(isLotSizable(tokensLot))) { + throw std::runtime_error("OrderBook::addAskLimitOrder:" \ + "The asset amount must be a multiple of the lot size"); } // transfer lot amount to order book contract // evaluate the the nearly created ask limit order and increment next order id - this->transferToContract(this->addressAssetA_.get(), lotAmount); - this->evaluateAskOrder(std::move(this->makeOrder(assetAmount, - assetPrice, + this->transferToContract(this->addressAssetA_.get(), this->tokensLot(tokenAmount)); + // this should be transfer to another thread of execution? + this->evaluateAskOrder(std::move(this->makeOrder(tokenAmount, + tokenPrice, OrderType::LIMIT))); this->nextOrderID_++; } @@ -382,57 +459,52 @@ void OrderBook::delAskLimitOrder(const uint256_t& id) return false; } auto const& askOwner = std::get<2>(askOrder); - auto const& askAssetAmount = std::get<3>(askOrder); + auto const& askTokenAmount = std::get<3>(askOrder); if (askOwner != this->getCaller()) { throw std::runtime_error("OrderBook::delAskLimitOrder: INVALID_OWNER"); } this->callContractFunction(this->addressAssetA_.get(), &ERC20::transfer, askOwner, - this->convertLot(askAssetAmount)); + this->tokensLot(askTokenAmount)); return true; }); } -void OrderBook::addAskMarketOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice) +void OrderBook::addAskMarketOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice) { - // set asset balance - uint256_t assetBalance = \ + // set tokens balance + uint256_t tokenBalance = \ this->callContractViewFunction(this->addressAssetA_.get(), &ERC20::balanceOf, this->getCaller()); // convert lot amount - uint256_t lotAmount = this->convertLot(assetAmount); - // verify if tick amount is bigger than user balance - if (lotAmount > assetBalance) { + uint256_t tokenLotAmount = this->tokensLot(tokenAmount); + // verify if token lot amount is bigger than user token balance + if (tokenLotAmount > tokenBalance) { throw std::runtime_error("OrderBook::addAskMarketOrder: INSUFFICIENT_BALANCE"); } - this->transferToContract(this->addressAssetA_.get(), lotAmount); - this->evaluateAskOrder(std::move(this->makeOrder(assetAmount, - assetPrice, - OrderType::MARKET))); + this->transferToContract(this->addressAssetA_.get(), tokenLotAmount); + this->evaluateMarketAskOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); this->nextOrderID_++; } -void OrderBook::addBidMarketOrder(const uint256_t& assetAmount, - const uint256_t& assetPrice) +void OrderBook::addBidMarketOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice) { // set asset balance - uint256_t assetBalance = \ + uint256_t tokenBalance = \ this->callContractViewFunction(this->addressAssetB_.get(), &ERC20::balanceOf, this->getCaller()); // convert tick amount - uint256_t tickAmount = this->convertTick(assetAmount); + uint256_t tokensTick = this->tokensTick(tokenAmount); // verify if tick amount is bigger than user balance - if (tickAmount > assetBalance) { + if (tokensTick > tokenBalance) { throw std::runtime_error("OrderBook::addBidMarketOrder: INSUFFICIENT_BALANCE"); } - this->transferToContract(this->addressAssetB_.get(), tickAmount); - this->evaluateBidOrder(std::move(this->makeOrder(assetAmount, - assetPrice, - OrderType::MARKET))); + this->evaluateMarketBidOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); this->nextOrderID_++; } diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index da36376c..0dfab411 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -46,8 +46,8 @@ enum OrderField { * 0 - const uint256_t - id - Sequential unique ID of the order. * 1 - const uint - timestamp - The epoch timestamp of the order's creation. * 2 - const Address - owner - The address that made the order. - * 3 - uint256_t - assetAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). - * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. + * 3 - uint256_t - tokenAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). + * 4 - const uint256_t - tokenPrice - The unit price of the asset the order has to offer in WEI of tokenB. * 5 - const uint256_t - stopLimit - The stop limit price of the order (only for stop limit orders), in WEI. * 6 - const OrderSide - side - Whether the order originally is a bid or ask. * 7 - const OrderType - type - Whether the order originally is a market or limit. @@ -96,8 +96,8 @@ inline bool operator>(const StopOrder& lhs, const StopOrder& rhs) { * 0 - const uint256_t - id - Sequential unique ID of the order. * 1 - const uint - timestamp - The epoch timestamp of the order's creation. * 2 - const Address - owner - The address that made the order. - * 3 - uint256_t - assetAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). - * 4 - const uint256_t - assetPrice - The unit price of the asset the order has to offer in WEI of tokenB. + * 3 - uint256_t - tokenAmount - The amount of the asset the order has to offer (tokenA for bids, tokenB for asks). + * 4 - const uint256_t - tokenPrice - The unit price of the asset the order has to offer in WEI of tokenB. * 5 - const OrderType - type - Whether the order originally is a market or limit. */ using Order = std::tupletokensTick(assetAmount * assetPrice)) / this->precision_); + } + + inline bool isTickSizable(const uint256_t& tokenPrice) { return true; } + + inline bool isLotSizable(const uint256_t& tokenPrice) + { + return ((tokenPrice % this->lotSize_.get()) == 0); } /// Call the register functions for the contract. @@ -389,7 +422,7 @@ class OrderBook : public DynamicContract { * Getter for all asks. * @return A vector with all asks. */ - std::vector getAsks() const; + std::vector getAsks() const; /** * Getter for all users orders From 3977748e5f4bed09a37254bca4cd8f106db43e79 Mon Sep 17 00:00:00 2001 From: lambdart Date: Fri, 31 Jan 2025 17:25:11 -0300 Subject: [PATCH 21/29] Add tests to market orders --- tests/contract/orderbook.cpp | 69 +++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index aa7dd2cd..b469b58e 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -133,13 +133,35 @@ namespace TORDERBOOK { sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); - sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); - // get asks and bids + auto askAddrBalance = sdk.callViewFunction(askAddr, &ERC20::balanceOf, owner); + // get bids auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + // verify the number of bid orders + REQUIRE(asks.size() == 1); + // get ask id + uint256_t id = std::get<0>(*(asks.cbegin())); + // delete bid order + sdk.callFunction(orderBook, &OrderBook::delAskLimitOrder, id); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("2000"), uint256_t("10000")); + // verify balance + auto bidAddrBalance = sdk.callViewFunction(bidAddr, &ERC20::balanceOf, owner); + // print the balance + std::cout << "0: askAddrBalance: " << askAddrBalance << std::endl; + std::cout << "0: bidAddrBalance: " << bidAddrBalance << std::endl; + // add ask limit order again + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + // get asks and bids + asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders REQUIRE(asks.size() == 0); - REQUIRE(bids.size() == 0); + REQUIRE(bids.size() == 1); + // verify balance + askAddrBalance = sdk.callViewFunction(askAddr, &ERC20::balanceOf, owner); + bidAddrBalance = sdk.callViewFunction(bidAddr, &ERC20::balanceOf, owner); + // print the balance + std::cout << "1: askAddrBalance: " << askAddrBalance << std::endl; + std::cout << "2: bidAddrBalance: " << bidAddrBalance << std::endl; } SECTION("Orderbook delete bid limit order") { @@ -209,12 +231,10 @@ namespace TORDERBOOK { sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add ask market order sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); - sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); - sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); // get asks orders auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); // verify the number of ask orders - REQUIRE(asks.size() == 3); + REQUIRE(asks.size() == 0); } SECTION("Orderbook add bid market order") { @@ -232,15 +252,13 @@ namespace TORDERBOOK { sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid market order sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); - sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); - sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); // get bids orders auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); - // verify the number of bid orders - REQUIRE(bids.size() == 3); + // verify the number of bid market orders + REQUIRE(bids.size() == 0); } - SECTION("Orderbook add ask and bid market order to match a transaction") { + SECTION("Orderbook add ask limit order and bid market order to match a transaction") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); Address owner = sdk.getChainOwnerAccount().address; Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); @@ -254,7 +272,7 @@ namespace TORDERBOOK { sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); // add bid order - sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("20")); + sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("20")); sdk.callFunction(orderBook, &OrderBook::addBidMarketOrder, uint256_t("100"), uint256_t("10")); // get asks and bids auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); @@ -263,5 +281,32 @@ namespace TORDERBOOK { REQUIRE(asks.size() == 0); REQUIRE(bids.size() == 0); } + + SECTION("Orderbook add bid limit order and ask market order to match a transaction") { + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + Address askAddr = sdk.deployContract(std::string("A_Token"), std::string("TKN_A"), uint8_t(18), uint256_t("2000000000000000000")); + Address bidAddr = sdk.deployContract(std::string("B_Token"), std::string("TKN_B"), uint8_t(18), uint256_t("2000000000000000000")); + uint8_t decA = sdk.callViewFunction(askAddr, &ERC20::decimals); + uint8_t decB = sdk.callViewFunction(bidAddr, &ERC20::decimals); + // create the contract + Address orderBook = sdk.deployContract(askAddr, std::string("A_Token"), decA, + bidAddr, std::string("B_Token"), decB); + // approve orderbook to transfer the tokens + sdk.callFunction(askAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + sdk.callFunction(bidAddr, &ERC20::approve, orderBook, uint256_t("2000000000000000000")); + // add bid order + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("50"), uint256_t("20")); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("50"), uint256_t("20")); + sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("30")); + sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); + sdk.callFunction(orderBook, &OrderBook::addAskMarketOrder, uint256_t("100"), uint256_t("10")); + // get asks and bids + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + REQUIRE(asks.size() == 0); + REQUIRE(bids.size() == 0); + } } } From 2a450c4c5fae2027173c3db8f4f18d33b19b7b8e Mon Sep 17 00:00:00 2001 From: lambdart Date: Tue, 4 Feb 2025 08:27:30 -0300 Subject: [PATCH 22/29] Simplify the evaluation of ask market order --- .../templates/orderbook/orderbook.cpp | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp index e49b90b7..940570d9 100644 --- a/src/contract/templates/orderbook/orderbook.cpp +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -213,19 +213,20 @@ void OrderBook::evaluateMarketBidOrder(Order&& bidOrder) auto& bidTokenAmount = std::get<3>(bidOrder); uint256_t bidTokensToBePaid = this->tokensTick(bidTokenAmount); // find the ask order - while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and - (bidTokensToBePaid > 0)) { + while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and \ + (bidTokensToBePaid > 0) and \ + (bidTokenAmount > 0)) { const auto& askOwner = std::get<2>(*matchAskOrder); auto& askTokenAmount = std::get<3>(*matchAskOrder); auto& askTokenPrice = std::get<4>(*matchAskOrder); // compute the tokens amount and how much must be transfer (temporary variables) uint256_t tokenAmount = std::min(askTokenAmount, ((bidTokensToBePaid * 10000) / askTokenPrice)); uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, askTokenPrice); - // transfer and execute + // transfer the tokens to the contract this->transferToContract(this->addressAssetB_.get(), tokensToBePaid); // executes the order, transfer the tokens from ask owner to bid owner this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); - // update bidTicks information + // update bid tokens to be paid information bidTokensToBePaid -= tokensToBePaid; // update amount information bidTokenAmount -= tokenAmount; @@ -283,6 +284,7 @@ void OrderBook::evaluateAskOrder(Order&& askOrder) const auto& askOwner = std::get<2>(askOrder); auto& askTokenAmount = std::get<3>(askOrder); auto& askTokenPrice = std::get<4>(askOrder); + auto& askOrderType = std::get<5>(askOrder); while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and \ (askTokenAmount > 0)) { // get bid order attributes values @@ -305,45 +307,13 @@ void OrderBook::evaluateAskOrder(Order&& askOrder) } } // handle the ask order that was not filled (remainder) - if (askTokenAmount > 0) { + if (askTokenAmount > 0 and askOrderType != OrderType::MARKET) { this->insertAskOrder(askOrder); } // update spread and mid price this->updateSpreadAndMidPrice(); } -void OrderBook::evaluateMarketAskOrder(Order&& askOrder) -{ - Order *matchBidOrder; - const auto& askOwner = std::get<2>(askOrder); - auto& askTokenAmount = std::get<3>(askOrder); - - while (((matchBidOrder = findMatchBidOrder(askOrder)) != nullptr) and \ - (askTokenAmount > 0)) { - // get bid order attributes values - const auto& bidOwner = std::get<2>(*matchBidOrder); - auto& bidTokenAmount = std::get<3>(*matchBidOrder); - auto& bidTokenPrice = std::get<4>(*matchBidOrder); - // set the token lot amount - uint256_t tokenAmount = std::min(askTokenAmount, bidTokenAmount); - // computer how much tokens much be paid - uint256_t tokensToBePaid = this->tokensToBePaid(tokenAmount, bidTokenPrice); - // executes the order, transfer the tokens from ask owner to bid owner - this->executeOrder(askOwner, bidOwner, tokensToBePaid, tokenAmount); - // update the order and lot amount - bidTokenAmount -= tokenAmount; - askTokenAmount -= tokenAmount; - // update the current price - this->updateLastPrice(bidTokenPrice); - // erase the bid order if was filled - if (bidTokenAmount == 0) { - this->eraseBidOrder(*matchBidOrder); - } - } - // update spread and mid price - this->updateSpreadAndMidPrice(); -} - void OrderBook::transferToContract(const Address& assetAddress, const uint256_t& tokenAmount) { @@ -486,7 +456,7 @@ void OrderBook::addAskMarketOrder(const uint256_t& tokenAmount, throw std::runtime_error("OrderBook::addAskMarketOrder: INSUFFICIENT_BALANCE"); } this->transferToContract(this->addressAssetA_.get(), tokenLotAmount); - this->evaluateMarketAskOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); + this->evaluateAskOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); this->nextOrderID_++; } From 61a945ab5b510e45f96a7d5072ece045d1179fe1 Mon Sep 17 00:00:00 2001 From: lambdart Date: Tue, 4 Feb 2025 08:37:03 -0300 Subject: [PATCH 23/29] Delete evaluateMarketAskOrder signature --- src/contract/templates/orderbook/orderbook.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index 0dfab411..bccac0f8 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -258,14 +258,6 @@ class OrderBook : public DynamicContract { */ void evaluateAskOrder(Order&& askOrder); - /** - * Evaluate the ask order, i.e, try to find the a matching bid order and - * execute the order pair, if the order isn't filled add the ask order to - * the ask order list. - * @param askOrder, the ask order. - */ - void evaluateMarketAskOrder(Order&& bidOrder); - /** * Find a matching ask order for an arbitrary bid order. * @param bidOrder, the bid order. From bf2b7671a6f260b6d8184d35135122a0d0e4f6a3 Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 5 Feb 2025 11:51:23 -0300 Subject: [PATCH 24/29] Remove print debug information --- tests/contract/orderbook.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index b469b58e..ce94741c 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -145,9 +145,6 @@ namespace TORDERBOOK { sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("2000"), uint256_t("10000")); // verify balance auto bidAddrBalance = sdk.callViewFunction(bidAddr, &ERC20::balanceOf, owner); - // print the balance - std::cout << "0: askAddrBalance: " << askAddrBalance << std::endl; - std::cout << "0: bidAddrBalance: " << bidAddrBalance << std::endl; // add ask limit order again sdk.callFunction(orderBook, &OrderBook::addAskLimitOrder, uint256_t("100"), uint256_t("10")); // get asks and bids @@ -156,12 +153,6 @@ namespace TORDERBOOK { // verify the number of bid orders REQUIRE(asks.size() == 0); REQUIRE(bids.size() == 1); - // verify balance - askAddrBalance = sdk.callViewFunction(askAddr, &ERC20::balanceOf, owner); - bidAddrBalance = sdk.callViewFunction(bidAddr, &ERC20::balanceOf, owner); - // print the balance - std::cout << "1: askAddrBalance: " << askAddrBalance << std::endl; - std::cout << "2: bidAddrBalance: " << bidAddrBalance << std::endl; } SECTION("Orderbook delete bid limit order") { From a9845a4e2483412c459bf82dd46f97d9a7d49696 Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 5 Feb 2025 16:35:49 -0300 Subject: [PATCH 25/29] Fix typo --- tests/contract/orderbook.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp index ce94741c..e9aaa4d6 100644 --- a/tests/contract/orderbook.cpp +++ b/tests/contract/orderbook.cpp @@ -65,10 +65,10 @@ namespace TORDERBOOK { sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); sdk.callFunction(orderBook, &OrderBook::addBidLimitOrder, uint256_t("100"), uint256_t("10")); - // get bid - auto bid = sdk.callViewFunction(orderBook, &OrderBook::getFirstBid); + // get bids + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); // verify the number of bid orders - REQUIRE(sdk.callViewFunction(orderBook, &OrderBook::getBids).size() == 3); + REQUIRE(bids.size() == 3); } SECTION("Orderbook add ask limit order") { From b051573d1d3d1d7e8752ca4ab11234da4ae0087f Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 5 Feb 2025 16:47:00 -0300 Subject: [PATCH 26/29] Add registered false to commit and revert operations --- src/contract/variables/safemultiset.h | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/contract/variables/safemultiset.h b/src/contract/variables/safemultiset.h index d6424479..2c00d8be 100644 --- a/src/contract/variables/safemultiset.h +++ b/src/contract/variables/safemultiset.h @@ -13,7 +13,8 @@ See the LICENSE.txt file in the project root for more information. #include #include "safebase.h" -// TODO: missing functions (not sure if necessary): merge, equal_range, operator==, operator<=> +// TODO: missing functions (not sure if necessary): +// merge, equal_range, operator==, operator<=> /** * Safe wrapper for std::multiset. @@ -151,7 +152,7 @@ class SafeMultiSet : public SafeBase { * @param hint The hint as to where to insert the elements. * @param args The elements to insert. * @return An iterator to the last inserted element. - */ +n */ template std::multiset::iterator emplace_hint( std::multiset::const_iterator hint, Args&&... args ) { @@ -248,10 +249,16 @@ class SafeMultiSet : public SafeBase { } /// Get the function that compares the keys (same as value_comp). - std::multiset::key_compare key_comp() const { check(); return tmp_->key_comp(); } + std::multiset::key_compare key_comp() const { + check(); + return tmp_->key_comp(); + } /// Get the function that compares the values (same as key_comp). - std::multiset::value_compare value_comp() const { check(); return tmp_->value_comp(); } + std::multiset::value_compare value_comp() const { + check(); + return tmp_->value_comp(); + } /** * Erase all elements that satisfy the predicate from the container. @@ -268,10 +275,19 @@ class SafeMultiSet : public SafeBase { } /// Commit function. - void commit() override { check(); set_.clear(); set_.insert(tmp_->begin(), tmp_->end()); } + void commit() override { + check(); + set_.clear(); + set_.insert(tmp_->begin(), tmp_->end()); + this->registered_ = false; + } /// Rollback function. - void revert() override { tmp_->clear(); tmp_ = nullptr; } + void revert() override { + tmp_->clear(); + tmp_ = nullptr; + this->registered_ = false; + } /// Get the inner set (for const functions!) inline const std::multiset& get() const { return set_; } From 9e3a31f0010e527b6f68cf5c0cd1e33ab920099d Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 5 Feb 2025 16:47:38 -0300 Subject: [PATCH 27/29] Remove duplicate parameter (doxygen) --- src/contract/templates/orderbook/orderbook.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index bccac0f8..c148ebea 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -346,7 +346,6 @@ class OrderBook : public DynamicContract { * @param addB The address of the pair's second asset. * @param tickerB The ticker of the pair's second asset. * @param decB The decimals of the second asset. - * @param tickerB The ticker of the pair's second asset. * @param address The address of the contract. * @param creator The address of the creator of the contract. * @param chainId The chain ID. From f8bbedafcb396ef1fc09584cd6d359c662b5814a Mon Sep 17 00:00:00 2001 From: lambdart Date: Wed, 5 Feb 2025 20:22:27 -0300 Subject: [PATCH 28/29] Add constructor names to the registerContract method --- src/contract/templates/orderbook/orderbook.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/contract/templates/orderbook/orderbook.h b/src/contract/templates/orderbook/orderbook.h index c148ebea..88a76265 100644 --- a/src/contract/templates/orderbook/orderbook.h +++ b/src/contract/templates/orderbook/orderbook.h @@ -474,14 +474,16 @@ class OrderBook : public DynamicContract { OrderBook, const Address, const std::string&, + const uint8_t, const Address, const std::string&, + const uint8_t, const Address&, const Address&, const uint64_t&, DB& >( - std::vector{ "addA", "tickerA", "addB", "tickerB"}, + std::vector{ "addA", "tickerA", "decA", "addB", "tickerB", "decB"}, std::make_tuple("getNextOrderID", &OrderBook::getNextOrderID, FunctionTypes::View, std::vector{}), std::make_tuple("getAddressAssetA", &OrderBook::getAddressAssetA, FunctionTypes::View, std::vector{}), std::make_tuple("getAddressAssetB", &OrderBook::getAddressAssetB, FunctionTypes::View, std::vector{}), From eaa2606dd4e92c041c8bda9986ae34a0224fc4f2 Mon Sep 17 00:00:00 2001 From: lambdart Date: Thu, 6 Feb 2025 10:37:33 -0300 Subject: [PATCH 29/29] Update sonar scanner environment path --- docker/bdk_cpp.dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/bdk_cpp.dockerfile b/docker/bdk_cpp.dockerfile index 7698c9b4..aaf2a1db 100644 --- a/docker/bdk_cpp.dockerfile +++ b/docker/bdk_cpp.dockerfile @@ -47,6 +47,7 @@ RUN /sonarcloud.sh ENV PATH=/root/.sonar/build-wrapper-linux-x86:$PATH ENV PATH=/root/.sonar/sonar-scanner-6.2.1.4610-linux-x64/bin:$PATH ENV PATH=/root/.sonar/sonar-scanner-6.2.0.4584-linux-x64/bin:$PATH +ENV PATH=/root/.sonar/sonar-scanner-7.0.1.4817-linux-x64/bin:$PATH ENV PATH=/usr/local/bin:$PATH # Copy the entrypoint script