diff --git a/.gitmodules b/.gitmodules index f049680..2316439 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,12 +4,12 @@ [submodule "external/cpr"] path = external/cpr url = https://github.com/libcpr/cpr.git -[submodule "external/rlpvalue"] - path = external/rlpvalue - url = https://github.com/bloq/rlpvalue.git [submodule "external/secp256k1"] path = external/secp256k1 url = https://github.com/bitcoin-core/secp256k1.git [submodule "external/json"] path = external/json url = https://github.com/nlohmann/json.git +[submodule "external/oxen-encoding"] + path = external/oxen-encoding + url = https://github.com/oxen-io/oxen-encoding.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 823af93..771c585 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # project( - "ethyl" + ethyl VERSION 0.1.0 LANGUAGES C CXX ) @@ -21,12 +21,6 @@ endif() # # Set project options # -if(${PROJECT_NAME}_IS_TOPLEVEL_PROJECT) - set(${PROJECT_NAME}_WARNINGS_AS_ERRORS ON CACHE BOOL "" FORCE) -else() - set(${PROJECT_NAME}_ENABLE_UNIT_TESTING FALSE CACHE BOOL "" FORCE) -endif() - include(cmake/StandardSettings.cmake) include(cmake/StaticAnalyzers.cmake) include(cmake/Utils.cmake) @@ -61,7 +55,6 @@ add_subdirectory(external) add_library( ${PROJECT_NAME} - STATIC ${headers} ${sources} ) @@ -92,7 +85,7 @@ message(STATUS "Added all header and implementation files.\n") include(cmake/CompilerWarnings.cmake) set_project_warnings(${PROJECT_NAME}) -verbose_message("Applied compiler warnings. Using standard ${CMAKE_CXX_STANDARD}.\n") +verbose_message("Applied compiler warnings.\n") # # Model project dependencies @@ -105,9 +98,9 @@ target_link_libraries( cpr::cpr secp256k1 nlohmann_json::nlohmann_json + oxenc::oxenc PRIVATE cncrypto - rlpvalue gmp gmpxx ) @@ -123,7 +116,6 @@ target_include_directories( PUBLIC $ $ - ${CMAKE_CURRENT_SOURCE_DIR}/src ) message(STATUS "Finished setting up include directories.") diff --git a/README.md b/README.md index 791d6eb..0d626b5 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,6 @@ cmake --build build --parallel --verbose pacman -Syuu # Terminal may prompt to restart before proceeding pacman -S git base-devel libargp-devel cmake gcc libcurl-devel gmp-devel autoconf automake libtool -# MSYS packages libargp with a .dll suffix which breaks rlpvalue's autotool -# script so we patch it up -ln -s /usr/lib/libargp.dll.a /usr/lib/libargp.a - # Build cmake -B build -S . cmake --build build --parallel --verbose diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake index b64bffd..7a62601 100644 --- a/cmake/CompilerWarnings.cmake +++ b/cmake/CompilerWarnings.cmake @@ -42,8 +42,6 @@ function(set_project_warnings project_name) set(CLANG_WARNINGS -Wall -Wextra # reasonable and standard - -Wshadow # warn the user if a variable declaration shadows one from a - # parent context -Wnon-virtual-dtor # warn the user if a class with virtual functions has a # non-virtual destructor. This helps catch hard to # track down memory errors diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake index e27d9cb..24b6bed 100644 --- a/cmake/StandardSettings.cmake +++ b/cmake/StandardSettings.cmake @@ -2,7 +2,14 @@ # Compiler options # -option(${PROJECT_NAME}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." OFF) +option(${PROJECT_NAME}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." ${${PROJECT_NAME}_IS_TOPLEVEL_PROJECT}) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +option(BUILD_SHARED_LIBS "Build libraries as shared libraries" ON) Include(FetchContent) @@ -11,7 +18,7 @@ Include(FetchContent) # # Currently supporting: Catch2. -option(${PROJECT_NAME}_ENABLE_UNIT_TESTING "Enable unit tests for the projects (from the `test` subfolder)." ON) +option(${PROJECT_NAME}_ENABLE_UNIT_TESTING "Enable unit tests for the projects (from the `test` subfolder)." ${${PROJECT_NAME}_IS_TOPLEVEL_PROJECT}) # # Crypto Library @@ -32,14 +39,7 @@ option(${PROJECT_NAME}_ENABLE_CPPCHECK "Enable static analysis with Cppcheck." O option(${PROJECT_NAME}_VERBOSE_OUTPUT "Enable verbose output, allowing for a better understanding of each step taken." ON) -# Export all symbols when building a shared library -if(BUILD_SHARED_LIBS) - set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) - set(CMAKE_CXX_VISIBILITY_PRESET hidden) - set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) -endif() - -option(${PROJECT_NAME}_ENABLE_LTO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)." OFF) +option(${PROJECT_NAME}_ENABLE_LTO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)." ON) if(${PROJECT_NAME}_ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT result OUTPUT output) diff --git a/cmake/system_or_submodule.cmake b/cmake/system_or_submodule.cmake new file mode 100644 index 0000000..d8b9afd --- /dev/null +++ b/cmake/system_or_submodule.cmake @@ -0,0 +1,21 @@ +macro(system_or_submodule BIGNAME smallname pkgconf subdir) + option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) + if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES) + pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET) + endif() + if(${BIGNAME}_FOUND) + add_library(${smallname} INTERFACE) + if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") + # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) + else() + target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) + endif() + message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") + else() + message(STATUS "using ${smallname} submodule") + add_subdirectory(${subdir} EXCLUDE_FROM_ALL) + endif() + if(NOT TARGET ${smallname}::${smallname}) + add_library(${smallname}::${smallname} ALIAS ${smallname}) + endif() +endmacro() diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a575594..135c2b9 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,7 +1,18 @@ + +include(../cmake/system_or_submodule.cmake) +find_package(PkgConfig REQUIRED) + + +# Force shared libs off for any libraries we build inside here so that if we build a shared lib we +# don't end up with a libspdlog.so or whatever that would need to be distributed alongside the +# libquic.so +set(BUILD_SHARED_LIBS OFF) + + # # CPR # -if(NOT TARGET cpr) +if(NOT TARGET cpr::cpr) set(CPR_USE_SYSTEM_CURL ON CACHE BOOL "") add_subdirectory(cpr) endif() @@ -9,16 +20,14 @@ endif() # # SECP256k1 # -if(NOT TARGET secp256k1) - set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) - set(SECP256K1_VALGRIND OFF CACHE BOOL "" FORCE) - set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) - set(SECP256K1_BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF CACHE BOOL "" FORCE) - set(SECP256K1_BUILD_CTIME_TESTS OFF CACHE BOOL "" FORCE) - set(SECP256K1_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - add_subdirectory(secp256k1) -endif() +set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE BOOL "" FORCE) +set(SECP256K1_VALGRIND OFF CACHE BOOL "" FORCE) +set(SECP256K1_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) +set(SECP256K1_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF CACHE BOOL "" FORCE) +set(SECP256K1_BUILD_CTIME_TESTS OFF CACHE BOOL "" FORCE) +set(SECP256K1_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +system_or_submodule(SECP256K1 secp256k1 libsecp256k1 secp256k1) # # Catch2 @@ -28,53 +37,21 @@ if(NOT TARGET Catch2) endif() # -# rlpvalue is built using autotools +# oxen-encoding # -include(ExternalProject) -set(RLP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rlpvalue) -set(RLP_BIN ${CMAKE_CURRENT_BINARY_DIR}/librlpvalue) -set(RLP_STATIC_LIB ${RLP_BIN}/lib/librlpvalue.a) -set(RLP_INCLUDES ${RLP_BIN}/include) -file(MAKE_DIRECTORY ${RLP_INCLUDES}) - -# NOTE: rlpvalue includes a sub-project, univalue which it configures from the -# top level script. Passing in the `--prefix` at the top-level does not -# propagate down to the bottom level causing `make install` to install to the -# system level prefix. -# -# We override this by specifying prefix at the last step ensuring that both -# `rlpvalue` and `univalue` are installed to the same prefix. -ExternalProject_Add( - librlpvalue - PREFIX ${RLP_BIN} - SOURCE_DIR ${RLP_DIR} - DOWNLOAD_COMMAND cd ${RLP_DIR} && git clean -dfX - CONFIGURE_COMMAND bash -c "CFLAGS='-fPIC' CXXFLAGS='-fPIC' ${RLP_DIR}/autogen.sh" && - bash -c "CFLAGS='-fPIC' CXXFLAGS='-fPIC' ${RLP_DIR}/configure --srcdir=${RLP_DIR} --disable-shared --enable-static=yes" - PATCH_COMMAND git checkout -- src/InfInt.h && - git apply --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/rlpvalue-001-infint-missing-limits-h.patch - BUILD_COMMAND make - INSTALL_COMMAND make install prefix=${RLP_BIN} - BUILD_BYPRODUCTS ${RLP_STATIC_LIB} ${RLP_BIN}/lib/libunivalue.a -) -add_library (rlpvalue STATIC IMPORTED GLOBAL) -add_dependencies (rlpvalue librlpvalue) -set_target_properties(rlpvalue PROPERTIES IMPORTED_LOCATION ${RLP_STATIC_LIB}) -set_target_properties(rlpvalue PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${RLP_INCLUDES}) +system_or_submodule(OXENC oxenc liboxenc>=1.1.0 oxen-encoding) # # nlohmann_json # -if(NOT TARGET nlohmann_json) - set(JSON_BuildTests OFF CACHE INTERNAL "") - set(JSON_MultipleHeaders ON CACHE BOOL "") # Allows multi-header nlohmann use - add_subdirectory(json EXCLUDE_FROM_ALL) -endif() +set(JSON_MultipleHeaders ON CACHE BOOL "") # Allows multi-header nlohmann use +system_or_submodule(NLOHMANN nlohmann_json nlohmann_json>=3.7.0 nlohmann_json) # # GMP # -find_library(gmp gmp) -if(NOT gmp) - message(FATAL_ERROR "gmp not found") -endif() + +pkg_check_modules(GMP gmp IMPORTED_TARGET REQUIRED) +add_library(gmp INTERFACE) +target_link_libraries(gmp INTERFACE PkgConfig::GMP) +message(STATUS "Found gmp ${GMP_VERSION}") diff --git a/external/cpr b/external/cpr index 2533463..3b15fa8 160000 --- a/external/cpr +++ b/external/cpr @@ -1 +1 @@ -Subproject commit 253346373575b383a56e2741b8250a6acf6fa50e +Subproject commit 3b15fa82ea74739b574d705fea44959b58142eb8 diff --git a/external/oxen-encoding b/external/oxen-encoding new file mode 160000 index 0000000..201c4ca --- /dev/null +++ b/external/oxen-encoding @@ -0,0 +1 @@ +Subproject commit 201c4ca86add82eaedf424c5f7c8fb756aa4fc17 diff --git a/external/rlpvalue b/external/rlpvalue deleted file mode 160000 index 1e58dce..0000000 --- a/external/rlpvalue +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1e58dcec547dd19da4c325344d6f363d533dabe0 diff --git a/include/ethyl/provider.hpp b/include/ethyl/provider.hpp index 7750bc9..2761099 100644 --- a/include/ethyl/provider.hpp +++ b/include/ethyl/provider.hpp @@ -13,6 +13,8 @@ #include "transaction.hpp" #include "logs.hpp" +using namespace std::literals; + struct ReadCallData { std::string contractAddress; std::string data; @@ -32,43 +34,43 @@ class Provider { cpr::Url url; cpr::Session session; public: - Provider(const std::string& name, const std::string& _url); + Provider(std::string name, std::string url); ~Provider(); void connectToNetwork(); void disconnectFromNetwork(); - uint64_t getTransactionCount(const std::string& address, const std::string& blockTag); + uint64_t getTransactionCount(std::string_view address, std::string_view blockTag); nlohmann::json callReadFunctionJSON(const ReadCallData& callData, std::string_view blockNumber = "latest"); std::string callReadFunction(const ReadCallData& callData, std::string_view blockNumber = "latest"); std::string callReadFunction(const ReadCallData& callData, uint64_t blockNumberInt); uint32_t getNetworkChainId(); std::string evm_snapshot(); - bool evm_revert(const std::string& snapshotId); + bool evm_revert(std::string_view snapshotId); uint64_t evm_increaseTime(std::chrono::seconds seconds); - std::optional getTransactionByHash(const std::string& transactionHash); - std::optional getTransactionReceipt(const std::string& transactionHash); - std::vector getLogs(uint64_t fromBlock, uint64_t toBlock, const std::string& address); - std::vector getLogs(uint64_t block, const std::string& address); - std::string getContractStorageRoot(const std::string& address, uint64_t blockNumberInt); - std::string getContractStorageRoot(const std::string& address, const std::string& blockNumber = "latest"); + std::optional getTransactionByHash(std::string_view transactionHash); + std::optional getTransactionReceipt(std::string_view transactionHash); + std::vector getLogs(uint64_t fromBlock, uint64_t toBlock, std::string_view address); + std::vector getLogs(uint64_t block, std::string_view address); + std::string getContractStorageRoot(std::string_view address, uint64_t blockNumberInt); + std::string getContractStorageRoot(std::string_view address, std::string_view blockNumber = "latest"); std::string sendTransaction(const Transaction& signedTx); std::string sendUncheckedTransaction(const Transaction& signedTx); - uint64_t waitForTransaction(const std::string& txHash, int64_t timeout = 320000); - bool transactionSuccessful(const std::string& txHash, int64_t timeout = 320000); - uint64_t gasUsed(const std::string& txHash, int64_t timeout = 320000); - std::string getBalance(const std::string& address); + uint64_t waitForTransaction(std::string_view txHash, std::chrono::milliseconds timeout = 320s); + bool transactionSuccessful(std::string_view txHash, std::chrono::milliseconds timeout = 320s); + uint64_t gasUsed(std::string_view txHash, std::chrono::milliseconds timeout = 320s); + std::string getBalance(std::string_view address); std::string getContractDeployedInLatestBlock(); uint64_t getLatestHeight(); FeeData getFeeData(); private: - cpr::Response makeJsonRpcRequest(const std::string& method, const nlohmann::json& params); + cpr::Response makeJsonRpcRequest(std::string_view method, const nlohmann::json& params); }; diff --git a/include/ethyl/signer.hpp b/include/ethyl/signer.hpp index 2fca531..d2e60f0 100644 --- a/include/ethyl/signer.hpp +++ b/include/ethyl/signer.hpp @@ -1,12 +1,15 @@ #pragma once -#include -#include -#include -#include +#include #include +#include +#include +#include +#include + +#include "provider.hpp" -#include "ethyl/provider.hpp" +typedef struct secp256k1_context_struct secp256k1_context; // forward decl class Signer { private: @@ -24,22 +27,22 @@ class Signer { // Returns std::pair, std::vector> generate_key_pair(); - std::array secretKeyToAddress(const std::vector& seckey); - std::string secretKeyToAddressString(const std::vector& seckey); + std::array secretKeyToAddress(std::span seckey); + std::string secretKeyToAddressString(std::span seckey); - std::vector sign(const std::array& hash, const std::vector& seckey); - std::vector sign(const std::string& hash, const std::vector& seckey); + std::vector sign(const std::array& hash, std::span seckey); + std::vector sign(std::string_view hash, std::span seckey); // Client usage methods bool hasProvider() const { return static_cast(provider); } std::shared_ptr getProvider() { return provider; } - std::vector signMessage(const std::string& message, const std::vector& seckey); - std::string signTransaction(Transaction& tx, const std::vector& seckey); + std::vector signMessage(std::string_view message, std::span seckey); + std::string signTransaction(Transaction& tx, std::span seckey); void populateTransaction(Transaction& tx, std::string sender_address); - std::string sendTransaction(Transaction& tx, const std::vector& seckey); + std::string sendTransaction(Transaction& tx, std::span seckey); private: diff --git a/include/ethyl/transaction.hpp b/include/ethyl/transaction.hpp index 22a8568..9e2afc1 100644 --- a/include/ethyl/transaction.hpp +++ b/include/ethyl/transaction.hpp @@ -4,8 +4,6 @@ #include #include -#include "ethyl/utils.hpp" - struct Signature { uint64_t signatureYParity = 0; std::vector signatureR = {}; @@ -13,15 +11,15 @@ struct Signature { bool isEmpty() const; - void fromHex(std::string hex_str); + void fromHex(std::string_view hex_str); }; class Transaction { public: - uint64_t chainId; - uint64_t nonce; - uint64_t maxPriorityFeePerGas; - uint64_t maxFeePerGas; + uint64_t chainId = 0; + uint64_t nonce = 0; + uint64_t maxPriorityFeePerGas = 0; + uint64_t maxFeePerGas = 0; std::string to; uint64_t value; uint64_t gasLimit; @@ -30,8 +28,8 @@ class Transaction { // Constructor // (toAddress, value, gasLimit, data) - Transaction(const std::string& _to , uint64_t _value , uint64_t _gasLimit = 21000, const std::string& _data = "") - : chainId(0), nonce(0), maxPriorityFeePerGas(0), maxFeePerGas(0), to(_to), value(_value), gasLimit(_gasLimit), data(_data) {} + Transaction(std::string to, uint64_t value, uint64_t gasLimit = 21000, std::string data = "") + : to{std::move(to)}, value{std::move(value)}, gasLimit{gasLimit}, data{std::move(data)} {} std::string serialized() const; std::string hash() const; diff --git a/include/ethyl/utils.hpp b/include/ethyl/utils.hpp index c29ee4c..f298d4c 100644 --- a/include/ethyl/utils.hpp +++ b/include/ethyl/utils.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include #include @@ -8,6 +10,9 @@ #include #include +#include +#include + namespace utils { @@ -38,23 +43,62 @@ namespace utils std::string_view trimPrefix(std::string_view src, std::string_view prefix); std::string_view trimLeadingZeros(std::string_view src); - std::vector fromHexString(std::string_view hexStr); - uint64_t fromHexStringToUint64(std::string_view hexStr); - std::array fromHexString32Byte(std::string_view hexStr); + using oxenc::basic_char; + template + std::vector fromHexString(std::string_view hexStr) { + hexStr = trimPrefix(hexStr, "0x"); - std::array hash(std::string in); + if (!oxenc::is_hex(hexStr)) + throw std::invalid_argument{"input string is not hex"}; - std::string getFunctionSignature(const std::string& function); + std::vector result; + result.reserve(oxenc::from_hex_size(hexStr.size())); + oxenc::from_hex(hexStr.begin(), hexStr.end(), std::back_inserter(result)); + return result; + } + extern template std::vector fromHexString(std::string_view); + extern template std::vector fromHexString(std::string_view); - std::string padToNBytes(const std::string& input, size_t byteCount, PaddingDirection direction = PaddingDirection::LEFT); - std::string padTo8Bytes(const std::string& input, PaddingDirection direction = PaddingDirection::LEFT); - std::string padTo32Bytes(const std::string& input, PaddingDirection direction = PaddingDirection::LEFT); + template + std::array fromHexString32Byte(std::string_view hexStr) { + hexStr = trimPrefix(hexStr, "0x"); - std::vector intToBytes(uint64_t num); + if (!oxenc::is_hex(hexStr) || hexStr.size() != 64) { + throw std::invalid_argument("Input string length should be 64 hex characters for 32 bytes"); + } + + std::array bytesArr; + oxenc::from_hex(hexStr.begin(), hexStr.end(), bytesArr.begin()); + + return bytesArr; + } + extern template std::array fromHexString32Byte(std::string_view); + extern template std::array fromHexString32Byte(std::string_view); - std::vector removeLeadingZeros(std::vector vec); - std::string generateRandomString(size_t length); + uint64_t fromHexStringToUint64(std::string_view hexStr); + + /// Parses an integer of some sort from a string, requiring that the entire string be consumed + /// during parsing. Return false if parsing failed, sets `value` and returns true if the entire + /// string was consumed. + template + bool parseInt(const std::string_view str, T& value, int base = 10) { + T tmp; + auto* strend = str.data() + str.size(); + auto [p, ec] = std::from_chars(str.data(), strend, tmp, base); + if (ec != std::errc() || p != strend) + return false; + value = tmp; + return true; + } + + std::array hash(std::string_view in); + + std::string getFunctionSignature(const std::string& function); + + std::vector intToBytes(uint64_t num); + + std::vector removeLeadingZeros(std::span vec); std::string trimAddress(const std::string& address); diff --git a/src/provider.cpp b/src/provider.cpp index 96cc62a..cc30228 100644 --- a/src/provider.cpp +++ b/src/provider.cpp @@ -1,9 +1,15 @@ // provider.cpp +#include #include #include -#include #include +#pragma GCC diagnostic push +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wuseless-cast" +#endif +#include +#pragma GCC diagnostic pop #include "ethyl/provider.hpp" #include "ethyl/utils.hpp" @@ -11,8 +17,8 @@ #include -Provider::Provider(const std::string& name, const std::string& _url) - : clientName(name), url(_url) { +Provider::Provider(std::string name, std::string url) + : clientName{std::move(name)}, url{std::move(url)} { // Initialize client } @@ -35,7 +41,7 @@ void Provider::disconnectFromNetwork() { std::cout << "Disconnected from the Ethereum network.\n"; } -cpr::Response Provider::makeJsonRpcRequest(const std::string& method, const nlohmann::json& params) { +cpr::Response Provider::makeJsonRpcRequest(std::string_view method, const nlohmann::json& params) { if (url.str() == "") throw std::runtime_error("No URL provided to provider"); nlohmann::json bodyJson; @@ -131,7 +137,7 @@ std::string Provider::evm_snapshot() { throw std::runtime_error("Unable to create snapshot"); } -bool Provider::evm_revert(const std::string& snapshotId) { +bool Provider::evm_revert(std::string_view snapshotId) { nlohmann::json params = nlohmann::json::array(); params.push_back(snapshotId); @@ -182,7 +188,7 @@ uint64_t Provider::evm_increaseTime(std::chrono::seconds seconds) { } -uint64_t Provider::getTransactionCount(const std::string& address, const std::string& blockTag) { +uint64_t Provider::getTransactionCount(std::string_view address, std::string_view blockTag) { nlohmann::json params = nlohmann::json::array(); params.push_back(address); params.push_back(blockTag); @@ -211,7 +217,7 @@ uint64_t Provider::getTransactionCount(const std::string& address, const std::st throw std::runtime_error("Unable to get transaction count"); } -std::optional Provider::getTransactionByHash(const std::string& transactionHash) { +std::optional Provider::getTransactionByHash(std::string_view transactionHash) { nlohmann::json params = nlohmann::json::array(); params.push_back(transactionHash); @@ -236,7 +242,7 @@ std::optional Provider::getTransactionByHash(const std::string& return std::nullopt; } -std::optional Provider::getTransactionReceipt(const std::string& transactionHash) { +std::optional Provider::getTransactionReceipt(std::string_view transactionHash) { nlohmann::json params = nlohmann::json::array(); params.push_back(transactionHash); @@ -261,7 +267,7 @@ std::optional Provider::getTransactionReceipt(const std::string& return std::nullopt; } -std::vector Provider::getLogs(uint64_t fromBlock, uint64_t toBlock, const std::string& address) { +std::vector Provider::getLogs(uint64_t fromBlock, uint64_t toBlock, std::string_view address) { std::vector logEntries; nlohmann::json params = nlohmann::json::array(); @@ -308,19 +314,15 @@ std::vector Provider::getLogs(uint64_t fromBlock, uint64_t toBlock, co return logEntries; } -std::vector Provider::getLogs(uint64_t block, const std::string& address) { +std::vector Provider::getLogs(uint64_t block, std::string_view address) { return getLogs(block, block, address); } -std::string Provider::getContractStorageRoot(const std::string& address, uint64_t blockNumberInt) { - std::stringstream stream; - stream << "0x" << std::hex << blockNumberInt; // Convert uint64_t to hex string - std::string blockNumberHex = stream.str(); - - return getContractStorageRoot(address, blockNumberHex); // Call the original function +std::string Provider::getContractStorageRoot(std::string_view address, uint64_t blockNumberInt) { + return getContractStorageRoot(address, "0x" + utils::decimalToHex(blockNumberInt)); } -std::string Provider::getContractStorageRoot(const std::string& address, const std::string& blockNumber) { +std::string Provider::getContractStorageRoot(std::string_view address, std::string_view blockNumber) { nlohmann::json params = nlohmann::json::array(); params.push_back(address); auto storage_keys = nlohmann::json::array(); @@ -342,28 +344,33 @@ std::string Provider::getContractStorageRoot(const std::string& address, const s throw std::runtime_error("No storage proof found in response"); } +// Calls `f()` which should return an optional repeatedly (sleeping for `call_interval` between +// each call) until it returns a non-nullopt, then returns it. Throws runtime_error on timeout. +template +static auto waitForResult(Func&& f, std::chrono::milliseconds timeout, const std::string& errmsg = "Transaction inclusion in a block timned out", std::chrono::milliseconds call_interval = 500ms) { + auto timeout_at = std::chrono::steady_clock::now() + timeout; + + auto val = f(); + while (!val.has_value() && std::chrono::steady_clock::now() < timeout_at) { + std::this_thread::sleep_for(call_interval); + val = f(); + } + + if (val.has_value()) + return std::move(*val); + + throw std::runtime_error{errmsg}; +} + // Create and send a raw transaction returns the hash but will also check that it got into the mempool std::string Provider::sendTransaction(const Transaction& signedTx) { std::string hash = sendUncheckedTransaction(signedTx); - std::future futureTx = std::async(std::launch::async, [&]() -> std::string { - std::vector timeouts = { 4000, 100, 1000 }; - - while(true) { - const auto maybe_tx = getTransactionByHash(hash); - if(maybe_tx) { + return waitForResult([&]() -> std::optional { + if (getTransactionByHash(hash)) return hash; - } - - if(timeouts.empty()) { - throw std::runtime_error("Transaction request timed out"); - } - std::this_thread::sleep_for(std::chrono::milliseconds(timeouts.back())); - timeouts.pop_back(); - } - }); - - return futureTx.get(); + return std::nullopt; + }, 5s, "Transaction request timed out"); } // Create and send a raw transaction returns the hash without checking if it succeeded in getting into the mempool @@ -386,85 +393,53 @@ std::string Provider::sendUncheckedTransaction(const Transaction& signedTx) { } } -uint64_t Provider::waitForTransaction(const std::string& txHash, int64_t timeout) { - std::future futureTx = std::async(std::launch::async, [&]() -> uint64_t { - auto start = std::chrono::steady_clock::now(); - while(true) { - const auto maybe_tx_json = getTransactionByHash(txHash); - - // If transaction is received, resolve the promise - if(maybe_tx_json && !(*maybe_tx_json)["blockNumber"].is_null()) { - // Parse the block number from the hex string - std::string blockNumberHex = (*maybe_tx_json)["blockNumber"]; - return utils::fromHexStringToUint64(blockNumberHex); - } +uint64_t Provider::waitForTransaction( + std::string_view txHash, std::chrono::milliseconds timeout) { + return waitForResult( + [&]() -> std::optional { + if (const auto maybe_tx_json = getTransactionByHash(txHash); + maybe_tx_json && !(*maybe_tx_json)["blockNumber"].is_null()) { - auto now = std::chrono::steady_clock::now(); - if(std::chrono::duration_cast(now - start).count() > timeout) { - throw std::runtime_error("Transaction inclusion in a block timed out"); - } - - // Wait for a while before next check - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - }); - - return futureTx.get(); + // Parse the block number from the hex string + auto blockNumberHex = (*maybe_tx_json)["blockNumber"].get(); + return utils::fromHexStringToUint64(blockNumberHex); + } + return std::nullopt; + }, + timeout); } -bool Provider::transactionSuccessful(const std::string& txHash, int64_t timeout) { - std::future futureTx = std::async(std::launch::async, [&]() -> uint64_t { - auto start = std::chrono::steady_clock::now(); - while(true) { - const auto maybe_tx_json = getTransactionReceipt(txHash); - - // If transaction is received, resolve the promise - if(maybe_tx_json && !(*maybe_tx_json)["status"].is_null()) { - // Parse the status from the hex string - std::string statusHex = (*maybe_tx_json)["status"]; - return static_cast(utils::fromHexStringToUint64(statusHex)); - } - - auto now = std::chrono::steady_clock::now(); - if(std::chrono::duration_cast(now - start).count() > timeout) { - throw std::runtime_error("Transaction inclusion in a block timed out"); - } +bool Provider::transactionSuccessful(std::string_view txHash, std::chrono::milliseconds timeout) { + return waitForResult( + [&]() -> std::optional { + if (const auto maybe_tx_json = getTransactionReceipt(txHash); + maybe_tx_json && !(*maybe_tx_json)["status"].is_null()) { - // Wait for a while before next check - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - }); - - return futureTx.get(); + // Parse the status from the hex string + auto statusHex = (*maybe_tx_json)["status"].get(); + return static_cast(utils::fromHexStringToUint64(statusHex)); + } + return std::nullopt; + }, + timeout); } -uint64_t Provider::gasUsed(const std::string& txHash, int64_t timeout) { - std::future futureTx = std::async(std::launch::async, [&]() -> uint64_t { - auto start = std::chrono::steady_clock::now(); - while(true) { - const auto maybe_tx_json = getTransactionReceipt(txHash); - - // If transaction is received, resolve the promise - if(maybe_tx_json && !(*maybe_tx_json)["gasUsed"].is_null()) { - // Parse the status from the hex string - std::string gasUsed = (*maybe_tx_json)["gasUsed"]; - return utils::fromHexStringToUint64(gasUsed); - } - - auto now = std::chrono::steady_clock::now(); - if(std::chrono::duration_cast(now - start).count() > timeout) { - throw std::runtime_error("Transaction inclusion in a block timed out"); - } +uint64_t Provider::gasUsed(std::string_view txHash, std::chrono::milliseconds timeout) { + return waitForResult( + [&]() -> std::optional { + if (const auto maybe_tx_json = getTransactionReceipt(txHash); + maybe_tx_json && !(*maybe_tx_json)["gasUsed"].is_null()) { - // Wait for a while before next check - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - }); - - return futureTx.get(); + // Parse the status from the hex string + auto gasUsed = (*maybe_tx_json)["gasUsed"].get(); + return utils::fromHexStringToUint64(gasUsed); + } + return std::nullopt; + }, + timeout); } -std::string Provider::getBalance(const std::string& address) { +std::string Provider::getBalance(std::string_view address) { nlohmann::json params = nlohmann::json::array(); params.push_back(address); params.push_back("latest"); @@ -484,7 +459,7 @@ std::string Provider::getBalance(const std::string& address) { return balance.get_str(); } else { - throw std::runtime_error("Failed to get balance for address " + address); + throw std::runtime_error("Failed to get balance for address " + std::string{address}); } } diff --git a/src/signer.cpp b/src/signer.cpp index 3bc01cd..daaf74a 100644 --- a/src/signer.cpp +++ b/src/signer.cpp @@ -1,9 +1,8 @@ #include "ethyl/signer.hpp" -#include #include #include -#include +#include #include "ethyl/ecdsa_util.h" #include "ethyl/utils.hpp" @@ -60,7 +59,7 @@ std::pair, std::vector> Signer::genera std::vector(compressed_pubkey, compressed_pubkey + sizeof(compressed_pubkey))}; } -std::array Signer::secretKeyToAddress(const std::vector& seckey) { +std::array Signer::secretKeyToAddress(std::span seckey) { std::string address; // Verify the private key. @@ -75,12 +74,13 @@ std::array Signer::secretKeyToAddress(const std::vector pub(65); + std::array pub; size_t pub_len = 65; secp256k1_ec_pubkey_serialize(ctx, pub.data(), &pub_len, &pubkey, SECP256K1_EC_UNCOMPRESSED); + std::string_view pub_string{reinterpret_cast(pub.data()), pub.size()}; // Skip the type byte. - std::string pub_string(pub.begin() + 1, pub.end()); + pub_string.remove_prefix(1); auto hashed_pub = utils::hash(pub_string); // The last 20 bytes of the Keccak-256 hash of the public key in hex is the address. @@ -89,7 +89,7 @@ std::array Signer::secretKeyToAddress(const std::vector& seckey) { +std::string Signer::secretKeyToAddressString(std::span seckey) { std::array address = secretKeyToAddress(seckey); std::string result = {}; result.reserve(2 + (address.max_size() * 2)); @@ -99,7 +99,7 @@ std::string Signer::secretKeyToAddressString(const std::vector& s } -std::vector Signer::sign(const std::array& hash, const std::vector& seckey) { +std::vector Signer::sign(const std::array& hash, std::span seckey) { secp256k1_ecdsa_recoverable_signature sig; unsigned char serialized_signature[64]; int recid; @@ -126,7 +126,7 @@ std::vector Signer::sign(const std::array& has return signature; } -std::vector Signer::sign(const std::string& hash, const std::vector& seckey) { +std::vector Signer::sign(std::string_view hash, std::span seckey) { return sign(utils::fromHexString32Byte(hash), seckey); } @@ -167,12 +167,12 @@ void Signer::populateTransaction(Transaction& tx, std::string sender_address) { } // Hash the message and sign -std::vector Signer::signMessage(const std::string& message, const std::vector& seckey) { +std::vector Signer::signMessage(std::string_view message, std::span seckey) { return sign(utils::hash(message), seckey); } // Hash the transaction and sign -std::string Signer::signTransaction(Transaction& txn, const std::vector& seckey) { +std::string Signer::signTransaction(Transaction& txn, std::span seckey) { const auto signature_hex = utils::toHexString(sign(txn.hash(), seckey)); txn.sig.fromHex(signature_hex); @@ -180,7 +180,7 @@ std::string Signer::signTransaction(Transaction& txn, const std::vector& seckey) { +std::string Signer::sendTransaction(Transaction& txn, std::span seckey) { const auto senders_address = secretKeyToAddressString(seckey); populateTransaction(txn, senders_address); diff --git a/src/transaction.cpp b/src/transaction.cpp index 738dfe8..992a482 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -1,50 +1,39 @@ #include "ethyl/transaction.hpp" #include "ethyl/utils.hpp" -#include -#include -#include - -// Optimized helper function to append data to the RLP array -// Declared inline to hint to the compiler that it should avoid function call overhead by integrating the function's body at each call site. -template -inline void appendDataToRLP(RLPValue& arr, const T& value, Converter convertFunction) { - RLPValue temp_val; - temp_val.assign(convertFunction(value)); - arr.push_back(temp_val); -} - +#include + +/** +* Returns the raw Bytes of the EIP-1559 transaction, in order. +* +* Format: `[chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, +* accessList, signatureYParity, signatureR, signatureS]` +* +* For an unsigned tx this method uses the empty Bytes values for the +* signature parameters `v`, `r` and `s` for encoding. +*/ std::string Transaction::serialized() const { - try { - RLPValue arr(RLPValue::VARR); - arr.setArray(); - - // Serialize transaction data using the optimized helper function - appendDataToRLP(arr, chainId, utils::intToBytes); - appendDataToRLP(arr, nonce, utils::intToBytes); - appendDataToRLP(arr, maxPriorityFeePerGas, utils::intToBytes); - appendDataToRLP(arr, maxFeePerGas, utils::intToBytes); - appendDataToRLP(arr, gasLimit, utils::intToBytes); - appendDataToRLP(arr, to, utils::fromHexString); - appendDataToRLP(arr, value, utils::intToBytes); - appendDataToRLP(arr, this->data, utils::fromHexString); // Explicitly use member variable - - // Handle the access list, which is empty in this implementation - RLPValue access_list(RLPValue::VARR); - access_list.setArray(); - arr.push_back(access_list); - - if (!sig.isEmpty()) { - appendDataToRLP(arr, sig.signatureYParity, utils::intToBytes); - appendDataToRLP(arr, sig.signatureR, utils::removeLeadingZeros); - appendDataToRLP(arr, sig.signatureS, utils::removeLeadingZeros); - } - - return "0x02" + utils::toHexString(arr.write()); - } catch (const std::exception& e) { - std::cerr << "Error serializing transaction: " << e.what() << std::endl; - throw; + using namespace oxenc; + + std::vector, std::vector>> arr; + arr.push_back(chainId); + arr.push_back(nonce); + arr.push_back(maxPriorityFeePerGas); + arr.push_back(maxFeePerGas); + arr.push_back(gasLimit); + arr.push_back(utils::fromHexString(to)); + arr.push_back(value); + arr.push_back(utils::fromHexString(data)); + + // Access list not going to use + arr.push_back(std::vector{}); + + if (!sig.isEmpty()) { + arr.push_back(sig.signatureYParity); + arr.push_back(sig.signatureR); + arr.push_back(sig.signatureS); } + return "0x02" + oxenc::to_hex(rlp_serialize(arr)); } std::string Transaction::hash() const { @@ -55,16 +44,16 @@ bool Signature::isEmpty() const { return signatureYParity == 0 && signatureR.empty() && signatureS.empty(); } -void Signature::fromHex(std::string hex_str) { - if (hex_str.size() >= 2 && hex_str[0] == '0' && hex_str[1] == 'x') { - hex_str = hex_str.substr(2); - } +void Signature::fromHex(std::string_view hex_str) { - if (hex_str.size() != 130) { + auto bytes = utils::fromHexString(hex_str); + if (bytes.size() != 65) { throw std::invalid_argument("Input string length should be 130 characters for 65 bytes"); } - signatureR = utils::fromHexString(hex_str.substr(0, 64)); - signatureS = utils::fromHexString(hex_str.substr(64, 64)); - signatureYParity = std::stoull(hex_str.substr(128, 2), nullptr, 16); + signatureR.resize(32); + signatureS.resize(32); + std::memcpy(signatureR.data(), bytes.data(), 32); + std::memcpy(signatureS.data(), bytes.data() + 32, 32); + signatureYParity = bytes[64]; } diff --git a/src/utils.cpp b/src/utils.cpp index de6241f..1ccebf6 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,123 +1,60 @@ #include "ethyl/utils.hpp" -#include +#include #include #include #include -#include + +#include +#include extern "C" { #include "crypto/keccak.h" } std::string utils::decimalToHex(uint64_t decimal) { - std::stringstream ss; - ss << std::hex << decimal; - return ss.str(); + char buf[20]; + auto [end, ec] = std::to_chars(std::begin(buf), std::end(buf), decimal, 16); + return {buf, ec == std::errc{} ? static_cast(end - buf) : 0}; } std::string_view utils::trimPrefix(std::string_view src, std::string_view prefix) { - std::string_view result = src; - if (result.size() >= prefix.size()) { - if (result.substr(0, prefix.size()) == prefix) { - result = result.substr(prefix.size(), result.size() - prefix.size()); - } - } - return result; + if (src.starts_with(prefix)) + return src.substr(prefix.size()); + return src; } std::string_view utils::trimLeadingZeros(std::string_view src) { - std::string_view result = src; - while (result.size() && result[0] == '0') { - result = result.substr(1, result.size() - 1); - } - return result; -} - -struct HexToU8Result { - bool success; - uint8_t u8; -}; - -static HexToU8Result hexToU8(char ch) { - HexToU8Result result = {}; - result.success = true; - - if (ch >= 'a' && ch <= 'f') - result.u8 = static_cast(ch - 'a' + 10); - else if (ch >= 'A' && ch <= 'F') - result.u8 = static_cast(ch - 'A' + 10); - else if (ch >= '0' && ch <= '9') - result.u8 = static_cast(ch - '0'); - else - result.success = false; - - return result; -} - -std::vector utils::fromHexString(std::string_view hexStr) { - hexStr = trimPrefix(hexStr, "0x"); - assert(hexStr.size() % 2 == 0); - - std::vector result; - result.reserve(hexStr.length() / 2); - for (size_t i = 0; i < hexStr.length(); i += 2) { - std::string_view byteString = hexStr.substr(i, 2); - HexToU8Result hi = hexToU8(byteString[0]); - HexToU8Result lo = hexToU8(byteString[1]); - unsigned char byte = static_cast(hi.u8 << 4 | lo.u8 << 0); - result.push_back(byte); - } - return result; + if (auto p = src.find_first_not_of('0'); p != src.npos) + return src.substr(p); + return src; } uint64_t utils::fromHexStringToUint64(std::string_view hexStr) { - std::string_view realHex = trimPrefix(hexStr, "0x"); - - // NOTE: Trim leading '0's - while (realHex.size() && realHex[0] == '0') { - realHex = realHex.substr(1, realHex.size() - 1); - } + uint64_t val; + if (parseInt(trimPrefix(hexStr, "0x"), val, 16)) + return val; - size_t maxHexSize = sizeof(uint64_t) * 2 /*hex chars per byte*/; - assert(realHex.size() <= maxHexSize); - - size_t size = std::min(maxHexSize, realHex.size()); - uint64_t result = 0; - for (size_t index = 0; index < size; index++) { - char ch = realHex[index]; - HexToU8Result hexResult = hexToU8(ch); - assert(hexResult.success); - result = (result << 4) | hexResult.u8; - } - return result; + throw std::invalid_argument{"failed to parse integer from hex input"}; } -std::array utils::fromHexString32Byte(std::string_view hexStr) { - std::vector bytesVec = fromHexString(hexStr); +template std::vector utils::fromHexString(std::string_view); +template std::vector utils::fromHexString(std::string_view); +template std::array utils::fromHexString32Byte(std::string_view); +template std::array utils::fromHexString32Byte(std::string_view); - if(bytesVec.size() != 32) { - throw std::invalid_argument("Input string length should be 64 characters for 32 bytes"); - } - - std::array bytesArr; - std::copy(bytesVec.begin(), bytesVec.end(), bytesArr.begin()); - - return bytesArr; -} - -std::array utils::hash(std::string in) { - std::vector bytes; +std::array utils::hash(std::string_view in) { + std::vector bytes; // Check for "0x" prefix and if exists, convert the hex to bytes - if(in.size() >= 2 && in[0] == '0' && in[1] == 'x') { - bytes = fromHexString(in); - in = std::string(bytes.begin(), bytes.end()); + if (in.starts_with("0x")) { + bytes = fromHexString(in); + in = {bytes.data(), bytes.size()}; } std::array hash; - keccak(reinterpret_cast(in.c_str()), in.size(), hash.data(), 32); + keccak(reinterpret_cast(in.data()), in.size(), hash.data(), 32); return hash; } @@ -132,85 +69,18 @@ std::string utils::getFunctionSignature(const std::string& function) { return "0x" + hashHex.substr(0, 8); } -std::string utils::padToNBytes(const std::string& input, size_t byteCount, utils::PaddingDirection direction) { - std::string output = input; - bool has0xPrefix = false; - - // Check if input starts with "0x" prefix - if (output.substr(0, 2) == "0x") { - has0xPrefix = true; - output = output.substr(2); // remove "0x" prefix for now - } - - // Calculate padding size based on byteCount * 2 (since each byte is represented by 2 hex characters) - const size_t targetHexStringSize = byteCount * 2; - const size_t startingSize = std::max(output.size(), static_cast(1)); // Size is atleast 1 element such that we handle when output.size == 0 - const size_t startingSizeRoundedUp = startingSize + (targetHexStringSize - 1); - const size_t nextMultiple = /*floor*/ (startingSizeRoundedUp / targetHexStringSize) * targetHexStringSize; - const size_t paddingSize = nextMultiple - output.size(); - - if (direction == PaddingDirection::LEFT) { - output.insert(0, paddingSize, '0'); - } else { - output.append(paddingSize, '0'); - } - - // If input started with "0x", add it back - if (has0xPrefix) { - output.insert(0, "0x"); - } - - return output; -} - -std::string utils::padTo32Bytes(const std::string& input, PaddingDirection direction) { - return padToNBytes(input, 32, direction); -} - -std::string utils::padTo8Bytes(const std::string& input, PaddingDirection direction) { - return padToNBytes(input, 8, direction); -} - std::vector utils::intToBytes(uint64_t num) { - if (num == 0) - return std::vector{}; - - std::stringstream stream; - stream << std::hex << num; - std::string hex = stream.str(); - if (hex.length() % 2) { hex = "0" + hex; } - - std::vector result(hex.length() / 2); - for (size_t i = 0; i < hex.length(); i += 2) { - std::string byteString = hex.substr(i, 2); - unsigned char byte = static_cast(std::stoi(byteString, nullptr, 16)); - result[i / 2] = byte; - } - - return result; + std::array buf; + oxenc::write_host_as_big(num, buf.data()); + return removeLeadingZeros(buf); } -std::vector utils::removeLeadingZeros(std::vector vec) { +std::vector utils::removeLeadingZeros(std::span vec) { auto it = vec.begin(); while(it != vec.end() && *it == 0) { ++it; } - vec.erase(vec.begin(), it); - return vec; -} - -std::string utils::generateRandomString(size_t length) { - srand(static_cast(time(nullptr))); // Seed the random number generator - const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - const int64_t max_index = sizeof(charset) - 1; - - std::string randomString; - - for (size_t i = 0; i < length; ++i) { - randomString += charset[static_cast(rand() % max_index)]; - } - - return randomString; + return {it, vec.end()}; } std::string utils::trimAddress(const std::string& address) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 808a4a7..4e86532 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,23 +4,27 @@ cmake_minimum_required(VERSION 3.15) # Project details # -verbose_message("Adding tests under ${CMAKE_PROJECT_NAME}tests...") +verbose_message("Adding tests under ${PROJECT_NAME}tests...") foreach(file ${test_sources}) - string(REGEX REPLACE "(.*/)([a-zA-Z0-9_ ]+)(\.cpp)" "\\2" test_name ${file}) + get_filename_component(test_name ${file} NAME_WE) add_executable(${test_name}_tests ${file}) - target_compile_features(${test_name}_tests PUBLIC cxx_std_17) - - - set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}) + set(${PROJECT_NAME}_TEST_LIB ${PROJECT_NAME}) target_link_libraries( ${test_name}_tests - PUBLIC + PRIVATE Catch2::Catch2WithMain - ${${CMAKE_PROJECT_NAME}_TEST_LIB} + ethyl + ${${PROJECT_NAME}_TEST_LIB} cncrypto + cpr::cpr + ) + target_include_directories( + ${test_name}_tests + PRIVATE + ${PROJECT_SOURCE_DIR}/src ) add_test( @@ -31,4 +35,4 @@ foreach(file ${test_sources}) ) endforeach() -verbose_message("Finished adding unit tests for ${CMAKE_PROJECT_NAME}.") +verbose_message("Finished adding unit tests for ${PROJECT_NAME}.") diff --git a/test/src/basic.cpp b/test/src/basic.cpp index d4053fc..7e80f6d 100644 --- a/test/src/basic.cpp +++ b/test/src/basic.cpp @@ -3,7 +3,9 @@ #include #pragma GCC diagnostic push +#ifndef __clang__ #pragma GCC diagnostic ignored "-Wduplicated-branches" +#endif #include #pragma GCC diagnostic pop @@ -11,10 +13,6 @@ #include "ethyl/ecdsa_util.h" -extern "C" { -#include "crypto/keccak.h" -} - #include #include diff --git a/test/src/ethereum_client.cpp b/test/src/ethereum_client.cpp index 6407e36..4f94bb1 100644 --- a/test/src/ethereum_client.cpp +++ b/test/src/ethereum_client.cpp @@ -1,5 +1,7 @@ #include +#include + #include "ethyl/provider.hpp" #include "ethyl/signer.hpp" #include "ethyl/utils.hpp" @@ -7,13 +9,13 @@ #include #include +using namespace oxenc::literals; + // Construct the client with the local RPC URL -inline constexpr std::string_view PRIVATE_KEY = "96a656cbd64281ea82257ca9978093b25117592287e4e07f5be660d1701f03e9"; +inline constexpr auto PRIVATE_KEY = "96a656cbd64281ea82257ca9978093b25117592287e4e07f5be660d1701f03e9"_hex_u; inline constexpr std::string_view ADDRESS = "0x2ccb8b65024e4aa9615a8e704dfb11be76674f1f"; inline constexpr std::string_view ANVIL_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; -inline constexpr std::string_view ANVIL_PRIVATE_KEY = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; -std::vector seckey = utils::fromHexString(std::string(PRIVATE_KEY)); -std::vector anvilseckey = utils::fromHexString(std::string(ANVIL_PRIVATE_KEY)); +inline constexpr auto ANVIL_PRIVATE_KEY = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"_hex_u; auto provider = std::make_shared("Client", std::string("127.0.0.1:8545")); Signer signer(provider); @@ -32,13 +34,13 @@ TEST_CASE( "HashTest", "[utils]" ) { TEST_CASE( "SigningTest", "[signer]" ) { std::string hash_hello_world = utils::toHexString(utils::hash("Hello World!\n")); - const auto signature_bytes = signer.signMessage("Hello World!", seckey); + const auto signature_bytes = signer.signMessage("Hello World!", PRIVATE_KEY); std::string signature_hex = utils::toHexString(signature_bytes); REQUIRE( signature_hex == "35f409302082e02b5126c82be93a3946d30e93722ce3ff87bdb01fc385fe312054f3fade7fab80dcabadabf96af75577327dfd064abd47a36543a475e04840e701" ); } TEST_CASE( "Get address from private key", "[signer]" ) { - std::string created_address = signer.secretKeyToAddressString(seckey); + std::string created_address = signer.secretKeyToAddressString(PRIVATE_KEY); REQUIRE( created_address == ADDRESS ); } @@ -80,7 +82,7 @@ TEST_CASE( "Signs an unsigned transaction correctly", "[transaction]" ) { Transaction tx("0xA6C077fd9283421C657EcEa8a9c1422cc6CEbc80", 1000000000000000000, 21000); tx.nonce = 1; tx.chainId = 1; - const auto signature_hex_string = signer.signTransaction(tx, seckey); + const auto signature_hex_string = signer.signTransaction(tx, PRIVATE_KEY); REQUIRE( signature_hex_string == "0x02f86a0101808082520894a6c077fd9283421c657ecea8a9c1422cc6cebc80880de0b6b3a764000080c080a084987299f8dd115333356ab03430ca8de593e03ba03d4ecd72daf15205119cf8a0216c9869da3497ae96dcb98713908af1a0abf866c12d51def821caf0374cccbb" ); } @@ -91,7 +93,7 @@ TEST_CASE( "Does a self transfer", "[transaction]" ) { const auto feeData = provider->getFeeData(); tx.maxFeePerGas = feeData.maxFeePerGas; tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas; - const auto signature_hex_string = signer.signTransaction(tx, anvilseckey); + const auto signature_hex_string = signer.signTransaction(tx, ANVIL_PRIVATE_KEY); const auto hash = provider->sendTransaction(tx); REQUIRE(hash != ""); REQUIRE(provider->transactionSuccessful(hash)); @@ -99,7 +101,7 @@ TEST_CASE( "Does a self transfer", "[transaction]" ) { TEST_CASE( "Does a self transfer on network using signer to populate", "[transaction]" ) { Transaction tx(std::string(ANVIL_ADDRESS), 100000000000000, 21000); - const auto hash = signer.sendTransaction(tx, anvilseckey); + const auto hash = signer.sendTransaction(tx, ANVIL_PRIVATE_KEY); REQUIRE(hash != ""); REQUIRE(provider->transactionSuccessful(hash)); }