diff --git a/include/ethyl/provider.hpp b/include/ethyl/provider.hpp index a772b35..c2297f4 100644 --- a/include/ethyl/provider.hpp +++ b/include/ethyl/provider.hpp @@ -2,6 +2,7 @@ #pragma once #include +#include #include #include @@ -38,9 +39,10 @@ class Provider { void connectToNetwork(); void disconnectFromNetwork(); - uint64_t getTransactionCount(const std::string& address, const std::string& blockTag); - std::string callReadFunction(const ReadCallData& callData, uint64_t blockNumberInt); - std::string callReadFunction(const ReadCallData& callData, const std::string& blockNumber = "latest"); + uint64_t getTransactionCount(const std::string& address, const std::string& 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(); diff --git a/include/ethyl/signer.hpp b/include/ethyl/signer.hpp index 3d1073b..2fca531 100644 --- a/include/ethyl/signer.hpp +++ b/include/ethyl/signer.hpp @@ -24,7 +24,8 @@ class Signer { // Returns std::pair, std::vector> generate_key_pair(); - std::string addressFromPrivateKey(const std::vector& seckey); + std::array secretKeyToAddress(const std::vector& seckey); + std::string secretKeyToAddressString(const std::vector& seckey); std::vector sign(const std::array& hash, const std::vector& seckey); std::vector sign(const std::string& hash, const std::vector& seckey); diff --git a/include/ethyl/utils.hpp b/include/ethyl/utils.hpp index 4d0986c..c29ee4c 100644 --- a/include/ethyl/utils.hpp +++ b/include/ethyl/utils.hpp @@ -34,18 +34,19 @@ namespace utils return oss.str(); } - std::string decimalToHex(uint64_t decimal); + std::string decimalToHex(uint64_t decimal); + 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 hex_str); - uint64_t fromHexStringToUint64(std::string hex_str); - - std::array fromHexString32Byte(std::string hex_str); + std::vector fromHexString(std::string_view hexStr); + uint64_t fromHexStringToUint64(std::string_view hexStr); + std::array fromHexString32Byte(std::string_view hexStr); std::array hash(std::string in); std::string getFunctionSignature(const std::string& function); - std::string padToNBytes(const std::string& input, size_t byte_count, PaddingDirection direction = PaddingDirection::LEFT); + 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); diff --git a/src/provider.cpp b/src/provider.cpp index 5caa890..e5bbace 100644 --- a/src/provider.cpp +++ b/src/provider.cpp @@ -48,30 +48,44 @@ cpr::Response Provider::makeJsonRpcRequest(const std::string& method, const nloh return session.Post(); } -std::string Provider::callReadFunction(const ReadCallData& callData, uint64_t blockNumberInt) { - std::stringstream stream; - stream << "0x" << std::hex << blockNumberInt; // Convert uint64_t to hex string - std::string blockNumberHex = stream.str(); - - return callReadFunction(callData, blockNumberHex); // Call the original function -} +nlohmann::json Provider::callReadFunctionJSON(const ReadCallData& callData, std::string_view blockNumber) { + nlohmann::json result = {}; -std::string Provider::callReadFunction(const ReadCallData& callData, const std::string& blockNumber) { // Prepare the params for the eth_call request - nlohmann::json params = nlohmann::json::array(); - params[0]["to"] = callData.contractAddress; - params[0]["data"] = callData.data; - params[1] = blockNumber; // use the provided block number or default to "latest" + nlohmann::json params = nlohmann::json::array(); + params[0]["to"] = callData.contractAddress; + params[0]["data"] = callData.data; + params[1] = blockNumber; // use the provided block number or default to "latest" cpr::Response response = makeJsonRpcRequest("eth_call", params); if (response.status_code == 200) { nlohmann::json responseJson = nlohmann::json::parse(response.text); if (!responseJson["result"].is_null()) { - return responseJson["result"]; + result = responseJson["result"]; + return result; } } - throw std::runtime_error("Unable to get the result of the function call"); + std::stringstream stream; + stream << "'eth_call' invoked on node for block '" << blockNumber + << "' to '" << callData.contractAddress + << "' with data payload '" << callData.data + << "' however it returned a response that does not have a result: " + << response.text; + throw std::runtime_error(stream.str()); +} + +std::string Provider::callReadFunction(const ReadCallData& callData, std::string_view blockNumber) { + std::string result = callReadFunctionJSON(callData, blockNumber); + return result; +} + +std::string Provider::callReadFunction(const ReadCallData& callData, uint64_t blockNumberInt) { + std::stringstream stream; + stream << "0x" << std::hex << blockNumberInt; // Convert uint64_t to hex string + std::string blockNumberHex = stream.str(); + std::string result = callReadFunctionJSON(callData, blockNumberHex); + return result; } uint32_t Provider::getNetworkChainId() { diff --git a/src/signer.cpp b/src/signer.cpp index 06ce23c..3bc01cd 100644 --- a/src/signer.cpp +++ b/src/signer.cpp @@ -60,7 +60,7 @@ std::pair, std::vector> Signer::genera std::vector(compressed_pubkey, compressed_pubkey + sizeof(compressed_pubkey))}; } -std::string Signer::addressFromPrivateKey(const std::vector& seckey) { +std::array Signer::secretKeyToAddress(const std::vector& seckey) { std::string address; // Verify the private key. @@ -84,10 +84,18 @@ std::string Signer::addressFromPrivateKey(const std::vector& seck 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. - address = utils::toHexString(hashed_pub); - address = address.substr(address.size() - 40); + std::array result = {}; + std::memcpy(result.data(), hashed_pub.data() + hashed_pub.size() - result.size(), result.size()); + return result; +} - return "0x" + address; +std::string Signer::secretKeyToAddressString(const std::vector& seckey) { + std::array address = secretKeyToAddress(seckey); + std::string result = {}; + result.reserve(2 + (address.max_size() * 2)); + result += "0x"; + result += utils::toHexString(address); + return result; } @@ -173,7 +181,7 @@ std::string Signer::signTransaction(Transaction& txn, const std::vector& seckey) { - const auto senders_address = addressFromPrivateKey(seckey); + const auto senders_address = secretKeyToAddressString(seckey); populateTransaction(txn, senders_address); const auto signature_hex = utils::toHexString(sign(txn.hash(), seckey)); diff --git a/src/utils.cpp b/src/utils.cpp index 79d2fbd..f6d6bdc 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -4,6 +4,7 @@ #include #include #include +#include extern "C" { #include "crypto/keccak.h" @@ -15,37 +16,86 @@ std::string utils::decimalToHex(uint64_t decimal) { return ss.str(); } -std::vector utils::fromHexString(std::string hex_str) { - std::vector bytes; - - // Check for "0x" prefix and remove it - if(hex_str.size() >= 2 && hex_str[0] == '0' && hex_str[1] == 'x') { - hex_str = hex_str.substr(2); +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; +} - for (unsigned int i = 0; i < hex_str.length(); i += 2) { - std::string byteString = hex_str.substr(i, 2); - //if (byteString[0] == 0) byteString[0] = '0'; - //if (byteString[1] == 0) byteString[1] = '0'; - unsigned char byte = static_cast(strtol(byteString.c_str(), nullptr, 16)); - bytes.push_back(byte); +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 bytes; + return result; } -uint64_t utils::fromHexStringToUint64(std::string hex_str) { - // Check for "0x" prefix and remove it - if(hex_str.size() >= 2 && hex_str[0] == '0' && hex_str[1] == 'x') { - hex_str = hex_str.substr(2); +std::vector utils::fromHexString(std::string_view hexStr) { + hexStr = trimPrefix(hexStr, "0x"); + assert(hexStr.size() % 2 == 0); + + std::vector result; + 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; +} - uint64_t value = std::stoull(hex_str, nullptr, 16); - return value; +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); + } + + 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; } -std::array utils::fromHexString32Byte(std::string hex_str) { - std::vector bytesVec = fromHexString(hex_str); +std::array utils::fromHexString32Byte(std::string_view hexStr) { + std::vector bytesVec = fromHexString(hexStr); if(bytesVec.size() != 32) { throw std::invalid_argument("Input string length should be 64 characters for 32 bytes"); @@ -81,7 +131,7 @@ std::string utils::getFunctionSignature(const std::string& function) { return "0x" + hashHex.substr(0, 8); } -std::string utils::padToNBytes(const std::string& input, size_t byte_count, utils::PaddingDirection direction) { +std::string utils::padToNBytes(const std::string& input, size_t byteCount, utils::PaddingDirection direction) { std::string output = input; bool has0xPrefix = false; @@ -92,20 +142,21 @@ std::string utils::padToNBytes(const std::string& input, size_t byte_count, util } // Calculate padding size based on byteCount * 2 (since each byte is represented by 2 hex characters) - size_t targetHexStringSize = byte_count * 2; - size_t nextMultiple = (output.size() + targetHexStringSize - 1) / targetHexStringSize * targetHexStringSize; - size_t paddingSize = nextMultiple - output.size(); - std::string padding(paddingSize, '0'); + 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 = padding + output; + output.insert(0, paddingSize, '0'); } else { - output += padding; + output.append(paddingSize, '0'); } // If input started with "0x", add it back if (has0xPrefix) { - output = "0x" + output; + output.insert(0, "0x"); } return output; diff --git a/test/src/ethereum_client.cpp b/test/src/ethereum_client.cpp index bf9a7e0..218b6cc 100644 --- a/test/src/ethereum_client.cpp +++ b/test/src/ethereum_client.cpp @@ -38,7 +38,7 @@ TEST_CASE( "SigningTest", "[signer]" ) { TEST_CASE( "Get address from private key", "[signer]" ) { std::vector seckey = utils::fromHexString(std::string(PRIVATE_KEY)); Signer signer; - std::string created_address = signer.addressFromPrivateKey(seckey); + std::string created_address = signer.secretKeyToAddressString(seckey); REQUIRE( created_address == ADDRESS ); }