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 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 diff --git a/src/contract/CMakeLists.txt b/src/contract/CMakeLists.txt index 5aff3a85..771c52cf 100644 --- a/src/contract/CMakeLists.txt +++ b/src/contract/CMakeLists.txt @@ -36,6 +36,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/buildthevoid.h ${CMAKE_SOURCE_DIR}/src/contract/templates/btvplayer.h @@ -85,6 +86,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/buildthevoid.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/btvplayer.cpp diff --git a/src/contract/customcontracts.h b/src/contract/customcontracts.h index 353eaa82..70b91c5e 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" @@ -40,7 +41,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, BTVPlayer, BTVEnergy, BTVProposals, BuildTheVoid, ERC20Mintable, Ownable + RandomnessTest, SnailTracer, SnailTracerOptimized, Pebble, BTVPlayer, BTVEnergy, BTVProposals, BuildTheVoid, ERC20Mintable, OrderBook, Ownable >; #endif diff --git a/src/contract/templates/orderbook/orderbook.cpp b/src/contract/templates/orderbook/orderbook.cpp new file mode 100644 index 00000000..940570d9 --- /dev/null +++ b/src/contract/templates/orderbook/orderbook.cpp @@ -0,0 +1,596 @@ +/* + 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 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), + nextOrderID_(this), + addressAssetA_(this), + tickerAssetA_(this), + addressAssetB_(this), + tickerAssetB_(this), + spread_(this), + tickSize_(this), + lotSize_(this), + lastPrice_(this), + bids_(this), + asks_(this), + stops_(this) +{ + // set + this->nextOrderID_ = 0; + this->addressAssetA_ = addA; + this->addressAssetB_ = addB; + this->tickerAssetA_ = tickerA; + this->tickerAssetB_ = tickerB; + this->spread_ = 0; + this->lastPrice_ = 0; + // constant + this->tickSize_ = Utils::exp10(decB - 4); + this->lotSize_ = Utils::exp10(decA - 4); + // commit + this->nextOrderID_.commit(); + this->addressAssetA_.commit(); + this->addressAssetB_.commit(); + this->tickerAssetA_.commit(); + this->tickerAssetB_.commit(); + this->spread_.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 + this->nextOrderID_.enableRegister(); + this->addressAssetA_.enableRegister(); + this->addressAssetB_.enableRegister(); + this->tickerAssetA_.enableRegister(); + this->tickerAssetB_.enableRegister(); + this->spread_.enableRegister(); + this->lotSize_.enableRegister(); + this->tickSize_.enableRegister(); + this->lastPrice_.enableRegister(); + this->bids_.enableRegister(); + this->asks_.enableRegister(); + 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) +{ + // 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_ = 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->lotSize_.commit(); + this->tickSize_.commit(); + this->lastPrice_.commit(); + this->bids_.commit(); + this->asks_.commit(); + this->stops_.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->lotSize_.enableRegister(); + this->tickSize_.enableRegister(); + this->lastPrice_.enableRegister(); + this->bids_.enableRegister(); + this->asks_.enableRegister(); + this->stops_.enableRegister(); +} + +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 tokensToBePaid, + uint256_t tokenAmount) +{ + 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& 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& askTokenPrice = std::get<4>(*askOrder); + auto& bidTokenPrice = 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: { + return const_cast(askOrder); + } + // we want to buy for the lowest price + case OrderType::LIMIT: { + return ((bidTokenPrice <= askTokenPrice) ? const_cast(askOrder) : nullptr); + } + // default do nothing + default: + break; + } + // not found + return nullptr; +} + +Order* OrderBook::findMatchBidOrder(const Order& askOrder) +{ + auto& askTokenAmount = std::get<3>(askOrder); + // do we have bid orders? + if (this->bids_.empty()) { + return nullptr; + } + // 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& 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 + case OrderType::MARKET: { + return const_cast(bidOrder); + } + // we want to sell at the highest price + case OrderType::LIMIT: { + return ((bidTokenPrice >= askTokenPrice) ? const_cast(bidOrder) : nullptr); + } + default: + break; + } + // not found + 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) 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 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 bid tokens to be paid 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& bidOwner = std::get<2>(bidOrder); + auto& bidTokenAmount = std::get<3>(bidOrder); + auto& bidTokenPrice = std::get<4>(bidOrder); + while(((matchAskOrder = findMatchAskOrder(bidOrder)) != nullptr) and + (bidTokenAmount > 0)) { + // get ask order attributes values + const auto& askOwner = std::get<2>(*matchAskOrder); + 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, tokensToBePaid, tokenAmount); + // update amount information + bidTokenAmount -= tokenAmount; + askTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(askTokenPrice); + // erase the ask asset if filled + if (askTokenAmount == 0) { + this->eraseAskOrder(*matchAskOrder); + } + } + // handle the bid order that was not filled (remainder) + if (bidTokenAmount > 0) { + this->insertBidOrder(bidOrder); + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + +void OrderBook::evaluateAskOrder(Order&& askOrder) +{ + Order *matchBidOrder; + 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 + const auto& bidOwner = std::get<2>(*matchBidOrder); + auto& bidTokenAmount = std::get<3>(*matchBidOrder); + auto& bidTokenPrice = std::get<4>(*matchBidOrder); + // compute the asset and token amount + 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, tokensToBePaid, tokenAmount); + // update order asset amounts + askTokenAmount -= tokenAmount; + bidTokenAmount -= tokenAmount; + // update the current price + this->updateLastPrice(bidTokenPrice); + // erase the bid order if was filled + if (bidTokenAmount == 0) { + this->eraseBidOrder(*matchBidOrder); + } + } + // handle the ask order that was not filled (remainder) + if (askTokenAmount > 0 and askOrderType != OrderType::MARKET) { + this->insertAskOrder(askOrder); + } + // update spread and mid price + this->updateSpreadAndMidPrice(); +} + +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& tokenAmount, + const uint256_t& tokenPrice, + const OrderType orderType) +{ + return Order(this->nextOrderID_.get(), + this->getCurrentTimestamp(), + this->getCaller(), + tokenAmount, + tokenPrice, + orderType); +} + +void OrderBook::addBidLimitOrder(const uint256_t& tokenAmount, + // we want to buy for the lowest price + const uint256_t& tokenPrice) +{ + // set the amount of B tokens available + uint256_t tokensBTotalBalance = \ + this->callContractViewFunction(this->addressAssetB_.get(), + &ERC20::balanceOf, + this->getCaller()); + + // 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 to be paid to order book contract + // evaluate the bid limit order and increment the next order id + this->transferToContract(this->addressAssetB_.get(), tokensBToBePaid); + this->evaluateBidOrder(std::move(this->makeOrder(tokenAmount, + tokenPrice, + 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& 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->tokensToBePaid(bidTokenAmount, bidTokenPrice); + this->callContractFunction(this->addressAssetB_.get(), + &ERC20::transfer, + bidOwner, + tokenAmount); + return true; + }); +} + +// 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) +{ + uint256_t tokensTotalBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + + // 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(), this->tokensLot(tokenAmount)); + // this should be transfer to another thread of execution? + this->evaluateAskOrder(std::move(this->makeOrder(tokenAmount, + tokenPrice, + 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& 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->tokensLot(askTokenAmount)); + return true; + }); +} + +void OrderBook::addAskMarketOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice) +{ + // set tokens balance + uint256_t tokenBalance = \ + this->callContractViewFunction(this->addressAssetA_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert lot amount + 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(), tokenLotAmount); + this->evaluateAskOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); + this->nextOrderID_++; +} + +void OrderBook::addBidMarketOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice) +{ + // set asset balance + uint256_t tokenBalance = \ + this->callContractViewFunction(this->addressAssetB_.get(), + &ERC20::balanceOf, + this->getCaller()); + // convert tick amount + uint256_t tokensTick = this->tokensTick(tokenAmount); + // verify if tick amount is bigger than user balance + if (tokensTick > tokenBalance) { + throw std::runtime_error("OrderBook::addBidMarketOrder: INSUFFICIENT_BALANCE"); + } + this->evaluateMarketBidOrder(std::move(this->makeOrder(tokenAmount, 0, OrderType::MARKET))); + this->nextOrderID_++; +} + +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_ = (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(); +} + +Order OrderBook::getFirstBid() const +{ + return *(this->bids_.cbegin()); +} + +Order OrderBook::getFirstAsk() const +{ + return *(this->asks_.cbegin()); +} + +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::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("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::addAskMarketOrder, FunctionTypes::NonPayable, this); + this->registerMemberFunction("addBidMarketOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, this); +} + +DBBatch OrderBook::dump() const +{ + DBBatch b = BaseContract::dump(); + + 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 new file mode 100644 index 00000000..88a76265 --- /dev/null +++ b/src/contract/templates/orderbook/orderbook.h @@ -0,0 +1,512 @@ +/* + 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 }; + +/// 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. + * 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 - 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. + */ +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 - 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::tuple; + +// 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), + std::get<7>(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. + + /** + * Transfer tokens from an address to the order book contract. + * @param address, the address where to get the tokens from. + * @param amount, the amount to be transferred. + */ + inline void transferToContract(const Address& address, + const uint256_t& amount); + + /** + * Create a order. + * @param assetAmount, the order asset amount. + * @param assetPrice, the order asset prince. + * @return Order, the nearly created order. + */ + Order makeOrder(const uint256_t& tokenAmount, + const uint256_t& tokenPrice, + const OrderType orderType); + + /** + * 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, + uint256_t tokenAmount, + uint256_t assetAmount); + + /** + * Insert an ask order to the ask order list. + * @param askOrder, the ask order to be inserted. + */ + inline void insertAskOrder(const Order& askOrder); + + /** + * Insert an bid order to the ask order list. + * @param bidOrder, the bid order to be inserted. + */ + inline void insertBidOrder(const Order& bidOrder); + + /** + * Erase (remove) an ask order from the ask order list. + * @param askOrder, the ask order to be removed. + */ + inline void eraseAskOrder(const Order& askOrder); + + /** + * Erase (remove) a bid order from the bid order list. + * @param bidOrder, the bid order to be removed. + */ + inline void eraseBidOrder(const Order& bidOrder); + + /** + * 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 evaluateBidOrder(Order&& bidOrder); + + /** + * 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 the bid order is not + * added to bid order list. + * @param bidOrder, the bid order. + */ + void evaluateMarketBidOrder(Order&& bidOrder); + + /** + * 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 evaluateAskOrder(Order&& askOrder); + + /** + * 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); + + /** + * 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); + + /** + * Update the last price of the pair. + * @param price The new last price. + */ + inline 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 token amount to token lot. + * @param tokenAmount, The token amount to convert. + * @return the computed token lot + */ + inline uint256_t tokensLot(const uint256_t& tokenAmount) const + { + return tokenAmount * lotSize_.get(); + } + + /** + * Convert from tick size to token amount. + * @param value The value to convert. + * @return The computed token amount + */ + inline uint256_t tokensTick(const uint256_t& value) const + { + return value * tickSize_.get(); + } + + /** + * Compute the amount of token at the asset's price. + * @param assetAmount, The token asset amount. + * @param assetPrice, The token asset price. + * @return tokens to be paid regarding the asset's amount and price. + */ + inline uint256_t tokensToBePaid(const uint256_t& assetAmount, + const uint256_t& assetPrice) const + { + return ((this->tokensTick(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. + void registerContractFunctions() override; + + /// Dump method + DBBatch dump() const override; + +public: + using ConstructorArguments = std::tuple< + 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 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 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. + * @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; + + /** + * 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; + + /** + * 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; + + /** + * 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); + + /** + * 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, + 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, + const uint256_t& assetPrice); + + /// Register the contract structure. + static void registerContract() { + ContractReflectionInterface::registerContractMethods< + 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", "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{}), + 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("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("addAskMarketOrder", &OrderBook::addAskMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}), + std::make_tuple("addBidMarketOrder", &OrderBook::addBidMarketOrder, FunctionTypes::NonPayable, std::vector{"assetAmount", "assetPrice"}) + ); + } +}; + +#endif // ORDERBOOK_H 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) {}; diff --git a/src/contract/variables/safemultiset.h b/src/contract/variables/safemultiset.h new file mode 100644 index 00000000..2c00d8be --- /dev/null +++ b/src/contract/variables/safemultiset.h @@ -0,0 +1,296 @@ +/* +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. +n */ + 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()); + this->registered_ = false; + } + + /// Rollback function. + 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_; } +}; + +#endif // SAFEMULTISET_H diff --git a/src/utils/utils.h b/src/utils/utils.h index 41252b5b..f28c2019 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 @@ -529,6 +535,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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e99cb858..da3717e3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,7 @@ 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/evmcreate.cpp ${CMAKE_SOURCE_DIR}/tests/contract/btv.cpp ${CMAKE_SOURCE_DIR}/tests/core/rdpos.cpp diff --git a/tests/contract/orderbook.cpp b/tests/contract/orderbook.cpp new file mode 100644 index 00000000..e9aaa4d6 --- /dev/null +++ b/tests/contract/orderbook.cpp @@ -0,0 +1,303 @@ +/* + 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 askAddr = sdk.deployContract(std::string("A_Token"), + 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")); + REQUIRE(sdk.getState().getDumpManagerSize() == 5); + REQUIRE(sdk.callViewFunction(bidAddr, &ERC20::name) == "B_Token"); + + 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); + } + + SECTION("Orderbook add bid limit order") { + // start the sdk environment + SDKTestSuite sdk = SDKTestSuite::createNewEnvironment(testDumpPath + "/testOrderBookCreation"); + Address owner = sdk.getChainOwnerAccount().address; + // create the ERC20 contract + 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(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("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); + // verify the number of bid orders + REQUIRE(bids.size() == 3); + } + + SECTION("Orderbook add 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")); + 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); + // verify the number of bid orders + REQUIRE(asks.size() == 3); + } + + SECTION("Orderbook add bid and ask order limit 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("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); + } + + SECTION("Orderbook add ask and bid limit 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::addAskLimitOrder, uint256_t("100"), uint256_t("10")); + 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); + // 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() == 1); + } + + SECTION("Orderbook delete bid 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(bidAddr, &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() == 1); + // 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); + } + + 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); + } + + 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")); + // get asks orders + auto asks = sdk.callViewFunction(orderBook, &OrderBook::getAsks); + // verify the number of ask orders + REQUIRE(asks.size() == 0); + } + + 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")); + // get bids orders + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid market orders + REQUIRE(bids.size() == 0); + } + + 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")); + 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::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); + auto bids = sdk.callViewFunction(orderBook, &OrderBook::getBids); + // verify the number of bid orders + 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); + } + } +}