diff --git a/src/bytes/cast.h b/src/bytes/cast.h new file mode 100644 index 00000000..086cf140 --- /dev/null +++ b/src/bytes/cast.h @@ -0,0 +1,30 @@ +#ifndef BDK_BYTES_CAST_H +#define BDK_BYTES_CAST_H + +#include +#include "range.h" + +namespace bytes { + +/** + * Casts a range of bytes from one type to another. + * + * @param src the input bytes to be cast + * @param dest the destiny bytes container + * @return the destiny bytes container + * @throws invalid argument exception on size incompatibility + */ +template +constexpr R cast(const Range auto& src, R dest = R()) { + if (std::ranges::size(src) != std::ranges::size(dest)) { + throw std::invalid_argument("incompatible sizes for casting"); + } + + std::ranges::copy(src, std::ranges::begin(dest)); + + return dest; +} + +} // namespace bytes + +#endif // BDK_BYTES_CAST_H diff --git a/src/bytes/hex.h b/src/bytes/hex.h new file mode 100644 index 00000000..6f898539 --- /dev/null +++ b/src/bytes/hex.h @@ -0,0 +1,52 @@ +#ifndef BDK_BYTES_HEX_H +#define BDK_BYTES_HEX_H + +#include +#include +#include "range.h" +#include "initializer.h" +#include "utils/hex.h" + +namespace bytes { + +/** + * Creates a sized initializer from a hex representation. The initializer will + * fill the container data with by converting the hexadecimal representation + * into binary data. + * @param str the hex string + * @return the sized initializer + */ +constexpr SizedInitializer auto hex(std::string_view str) { + if (str.size() >= 2 && str.starts_with("0x")) { + str = str.substr(2); + } + + if (str.size() % 2) { + throw std::invalid_argument("the length of hex string is required to be even number"); + } + + return makeInitializer(str.size() / 2, [str] (Byte* dest) { + const auto value = [] (char c) -> Byte { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + throw std::invalid_argument("character '" + std::to_string(c) + "' is invalid in hex string"); + }; + + auto it = str.begin(); + + while (it != str.end()) { + const Byte l = value(*it++); + const Byte r = value(*it++); + *dest++ = (l << 4) | r; + } + }); +} + +} // namespace bytes + +#endif // BDK_BYTES_HEX_H diff --git a/src/bytes/initializer.h b/src/bytes/initializer.h index f3439580..507eed05 100644 --- a/src/bytes/initializer.h +++ b/src/bytes/initializer.h @@ -8,6 +8,7 @@ See the LICENSE.txt file in the project root for more information. #ifndef BYTES_INITIALIZER_H #define BYTES_INITIALIZER_H +#include #include "view.h" // range.h -> ranges -> concepts /// Namespace for bytes-related functionalities. diff --git a/src/bytes/join.h b/src/bytes/join.h index e1d6a746..185ea6c1 100644 --- a/src/bytes/join.h +++ b/src/bytes/join.h @@ -11,33 +11,36 @@ See the LICENSE.txt file in the project root for more information. #include "initializer.h" // view.h -> range.h namespace bytes { - namespace detail { - std::size_t joinedSize(const SizedInitializer auto& arg) { - return arg.size(); - } - - std::size_t joinedSize(const DataRange auto& arg) { - return std::ranges::size(arg); - } - - std::size_t joinedSize(const auto& arg, const auto&... args) { - return joinedSize(arg) + joinedSize(args...); - } - - Byte* joinImpl(Byte *dest, const SizedInitializer auto& init) { - init.to(dest); - return dest + init.size(); - } - - Byte* joinImpl(Byte *dest, const DataRange auto& range) { - std::memcpy(dest, std::ranges::data(range), std::ranges::size(range)); - return dest + std::ranges::size(range); - } - - Byte* joinImpl(Byte *dest, const auto& arg, const auto&... args) { - return joinImpl(joinImpl(dest, arg), args...); - } - } // namespace detail + +namespace detail { + +std::size_t joinedSize(const SizedInitializer auto& arg) { + return arg.size(); +} + +std::size_t joinedSize(const DataRange auto& arg) { + return std::ranges::size(arg); +} + +std::size_t joinedSize(const auto& arg, const auto&... args) { + return joinedSize(arg) + joinedSize(args...); +} + +Byte* joinImpl(Byte *dest, const SizedInitializer auto& init) { + init.to(dest); + return dest + init.size(); +} + +Byte* joinImpl(Byte *dest, const DataRange auto& range) { + std::memcpy(dest, std::ranges::data(range), std::ranges::size(range)); + return dest + std::ranges::size(range); +} + +Byte* joinImpl(Byte *dest, const auto& arg, const auto&... args) { + return joinImpl(joinImpl(dest, arg), args...); +} + +} // namespace detail /** * Join several raw byte strings into one. @@ -48,12 +51,13 @@ namespace bytes { template SizedInitializer auto join(Ts&&... args) { const size_t size = detail::joinedSize(args...); - auto func = [args_ = std::tuple(std::forward(args)...)] (Byte *dest) { - std::apply(detail::joinImpl, std::tuple_cat(std::make_tuple(dest), std::tuple(args_))); - }; + auto func = [args_ = std::tuple(std::forward(args)...)] (Byte *dest) { + std::apply(detail::joinImpl, std::tuple_cat(std::make_tuple(dest), std::tuple(args_))); + }; + + return makeInitializer(size, std::move(func)); +} - return makeInitializer(size, std::move(func)); - } } // namespace bytes #endif // BYTES_JOIN_H diff --git a/src/bytes/random.h b/src/bytes/random.h new file mode 100644 index 00000000..25f64fd0 --- /dev/null +++ b/src/bytes/random.h @@ -0,0 +1,40 @@ +#ifndef BDK_BYTES_RANDOM_H +#define BDK_BYTES_RANDOM_H + +#include "range.h" +#include "initializer.h" + +#include + +namespace bytes { + +/** + * Creates an initializer of random bytes. + * The number of generated bytes exactly matches the size of the target bytes range. + * + * Examples: + * Hash hash = bytes::random(); // generates 32 random bytes + * Address addr = bytes::random(); // generates 20 random bytes + * + * @return a random bytes initializer + */ +constexpr Initializer auto random() { + return makeInitializer([] (Span span) { + ::RAND_bytes(span.data(), span.size()); + }); +} + +/** + * Creates an random bytes initializer of the given size. + * + * @return a sized initializer of random bytes + */ +constexpr SizedInitializer auto random(size_t size) { + return makeInitializer(size, [size] (Byte* ptr) { + ::RAND_bytes(ptr, size); + }); +} + +} // namespace bytes + +#endif // BDK_BYTES_RANDOM_H diff --git a/src/bytes/range.h b/src/bytes/range.h index 867e02b0..4d4bfa8c 100644 --- a/src/bytes/range.h +++ b/src/bytes/range.h @@ -9,10 +9,21 @@ See the LICENSE.txt file in the project root for more information. #define BYTES_RANGE_H #include - -using Byte = std::uint8_t; +#include "utils/bytes.h" namespace bytes { + /** + * Concept of a bytes iterator. + */ + template + concept Iterator = std::input_or_output_iterator && std::same_as, Byte>; + + /** + * Concept of a bytes contiguous bytes iterator. + */ + template + concept DataIterator = Iterator && std::contiguous_iterator; + /** * The concept of a range of bytes. */ diff --git a/src/bytes/view.h b/src/bytes/view.h index b2fa2085..f2b7f373 100644 --- a/src/bytes/view.h +++ b/src/bytes/view.h @@ -2,13 +2,10 @@ #define BYTES_VIEW_H #include "range.h" // ranges -> span +#include "utils/view.h" namespace bytes { -/// A view over sized contiguous bytes -using View = std::span; - -/// A span (i.e. a non-owning array of bytes that allows modification) over sized contiguous bytes using Span = std::span; /** @@ -20,7 +17,7 @@ using Span = std::span; * @return a view object of the bytes */ template -constexpr View view(R&& r) { return View(std::forward(r)); } +constexpr View view(R&& r) { return View(std::forward(r)); } /** * Creates a span from the given data range. It needs to be Borrowed @@ -41,8 +38,8 @@ constexpr Span span(R&& r) { return Span(std::forward(r)); } * @param str the target string * @return a bytes view of the string bytes */ -inline View view(std::string_view str) { - return View(reinterpret_cast(str.data()), str.size()); +inline View view(std::string_view str) { + return View(reinterpret_cast(str.data()), str.size()); } /** @@ -55,12 +52,6 @@ inline Span span(std::string& str) { return Span(reinterpret_cast(str.data()), str.size()); } -/// The concept of a type that can be viewed as a sized range of contiguous bytes. -template -concept Viewable = requires(const T& a) { - { view(a) } -> std::convertible_to; -}; - } // namespace bytes #endif // BYTES_VIEW_H diff --git a/src/contract/CMakeLists.txt b/src/contract/CMakeLists.txt index 13606f33..57a5971f 100644 --- a/src/contract/CMakeLists.txt +++ b/src/contract/CMakeLists.txt @@ -52,8 +52,12 @@ set(CONTRACT_SOURCES ${CMAKE_SOURCE_DIR}/src/contract/contracthost.cpp ${CMAKE_SOURCE_DIR}/src/contract/contractmanager.cpp ${CMAKE_SOURCE_DIR}/src/contract/dynamiccontract.cpp - ${CMAKE_SOURCE_DIR}/src/contract/calltracer.cpp + ${CMAKE_SOURCE_DIR}/src/contract/trace/call.cpp ${CMAKE_SOURCE_DIR}/src/contract/event.cpp + ${CMAKE_SOURCE_DIR}/src/contract/common.cpp + ${CMAKE_SOURCE_DIR}/src/contract/executioncontext.cpp + ${CMAKE_SOURCE_DIR}/src/contract/evmcontractexecutor.cpp + ${CMAKE_SOURCE_DIR}/src/contract/precompiledcontractexecutor.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/ownable.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/erc20.cpp ${CMAKE_SOURCE_DIR}/src/contract/templates/erc721.cpp diff --git a/src/contract/abi.cpp b/src/contract/abi.cpp index 757b038e..af650f47 100644 --- a/src/contract/abi.cpp +++ b/src/contract/abi.cpp @@ -31,17 +31,48 @@ Bytes ABI::Encoder::encodeInt(const int256_t& num) { return ret; } -uint256_t ABI::Decoder::decodeUint(const bytes::View &bytes, uint64_t &index) { +uint256_t ABI::Decoder::decodeUint(const View &bytes, uint64_t &index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for uint256"); uint256_t result = UintConv::bytesToUint256(bytes.subspan(index, 32)); index += 32; return result; } -int256_t ABI::Decoder::decodeInt(const bytes::View& bytes, uint64_t& index) { +int256_t ABI::Decoder::decodeInt(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for int256"); int256_t result = IntConv::bytesToInt256(bytes.subspan(index, 32)); index += 32; return result; } +Bytes ABI::Encoder::encodeError(std::string_view reason) { + FixedBytes<32> reasonEncoded{}; + + const size_t count = std::min(reason.size(), reasonEncoded.size()); + std::copy_n(reason.begin(), count, reasonEncoded.begin()); + + const uint256_t size(reason.size()); + const FixedBytes<32> sizeEncoded(UintConv::uint256ToBytes(size)); + + return Utils::makeBytes(bytes::join( + Hex::toBytes("0x08c379a0"), + Hex::toBytes("0x0000000000000000000000000000000000000000000000000000000000000020"), + sizeEncoded, + reasonEncoded + )); +} + +std::string ABI::Decoder::decodeError(View data) { + static constexpr size_t MAX_STR_SIZE = 32; + + if (data.size() != 100) { + throw DynamicException("Encoded revert reason is expected to have exactly 100 bytes"); + } + + const size_t size = std::min(size_t(UintConv::bytesToUint256(data.subspan(36, 32))), MAX_STR_SIZE); + + std::string res; + res.reserve(size); + std::ranges::copy(data.subspan(68, size), std::back_inserter(res)); + return res; +} diff --git a/src/contract/abi.h b/src/contract/abi.h index 5a2030de..c038e52a 100644 --- a/src/contract/abi.h +++ b/src/contract/abi.h @@ -88,7 +88,7 @@ namespace ABI { */ template constexpr bool isDynamic() { if constexpr ( - std::is_same_v || std::is_same_v || std::is_same_v || false + std::is_same_v || std::is_same_v> || std::is_same_v || false ) return true; if constexpr (isVectorV) return true; if constexpr (isTupleOfDynamicTypes::value) return true; @@ -416,7 +416,7 @@ namespace ABI { }; template <> struct TypeEncoder { static Bytes encode(const std::string& str) { - bytes::View bytes = Utils::create_view_span(str); + View bytes = Utils::create_view_span(str); int pad = 0; do { pad += 32; } while (pad < bytes.size()); Bytes len = StrConv::padLeftBytes(Utils::uintToBytes(bytes.size()), 32); @@ -505,6 +505,15 @@ namespace ABI { } }; + // Specialization for std::pair + template + struct TypeEncoder> { + static Bytes encode(const std::pair& p) { + using Tuple = std::tuple; + return TypeEncoder::encode(Tuple(p.first, p.second)); + } + }; + // Specialization for std::vector template Bytes TypeEncoder>::encode(const std::vector& v) { @@ -562,6 +571,8 @@ namespace ABI { result.insert(result.end(), dynamicBytes.begin(), dynamicBytes.end()); return result; } + + Bytes encodeError(std::string_view reason); }; // namespace Encoder /** @@ -602,7 +613,7 @@ namespace ABI { template <> struct TypeEncoder { static Bytes encode(const std::string& str) { - bytes::View bytes = Utils::create_view_span(str); + View bytes = Utils::create_view_span(str); int pad = 0; do { pad += 32; } while (pad < bytes.size()); return StrConv::padRightBytes(bytes, pad); @@ -736,7 +747,7 @@ namespace ABI { * @return The decoded data. * @throw std::length_error if data is too short for the type. */ - uint256_t decodeUint(const bytes::View& bytes, uint64_t& index); + uint256_t decodeUint(const View& bytes, uint64_t& index); /** * Decode an int256. @@ -745,12 +756,12 @@ namespace ABI { * @return The decoded data. * @throw std::length_error if data is too short for the type. */ - int256_t decodeInt(const bytes::View& bytes, uint64_t& index); + int256_t decodeInt(const View& bytes, uint64_t& index); /// @cond // General template for bytes to type decoding template struct TypeDecoder { - static T decode(const bytes::View&, const uint64_t&) { + static T decode(const View&, const uint64_t&) { static_assert(always_false, "TypeName specialization for this type is not defined"); return T(); } @@ -758,7 +769,7 @@ namespace ABI { // Specialization for default solidity types template <> struct TypeDecoder
{ - static Address decode(const bytes::View& bytes, uint64_t& index) { + static Address decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for address"); auto result = Address(bytes.subspan(index + 12, 20)); index += 32; @@ -767,7 +778,7 @@ namespace ABI { }; template <> struct TypeDecoder { - static Hash decode(const bytes::View& bytes, uint64_t& index) { + static Hash decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) { throw std::length_error("Data too short for hash"); } auto result = Hash(bytes.subspan(index, 32)); index += 32; @@ -776,7 +787,7 @@ namespace ABI { }; template <> struct TypeDecoder { - static bool decode(const bytes::View& bytes, uint64_t& index) { + static bool decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for bool"); bool result = (bytes[index + 31] == 0x01); index += 32; @@ -785,7 +796,7 @@ namespace ABI { }; template <> struct TypeDecoder { - static Bytes decode(const bytes::View& bytes, uint64_t& index) { + static Bytes decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for bytes"); Bytes tmp(bytes.begin() + index, bytes.begin() + index + 32); uint64_t bytesStart = Utils::fromBigEndian(tmp); @@ -808,7 +819,7 @@ namespace ABI { }; template <> struct TypeDecoder { - static std::string decode(const bytes::View& bytes, uint64_t& index) { + static std::string decode(const View& bytes, uint64_t& index) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for string 1"); std::string tmp(bytes.begin() + index, bytes.begin() + index + 32); uint64_t bytesStart = Utils::fromBigEndian(tmp); @@ -845,7 +856,7 @@ namespace ABI { std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v struct TypeDecoder { - static T decode(const bytes::View& bytes, uint64_t& index) { + static T decode(const View& bytes, uint64_t& index) { return static_cast(decodeInt(bytes, index)); } }; @@ -865,21 +876,21 @@ namespace ABI { std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v struct TypeDecoder { - static T decode(const bytes::View& bytes, uint64_t& index) { + static T decode(const View& bytes, uint64_t& index) { return static_cast(decodeUint(bytes, index)); } }; // Specialization for enum types template requires std::is_enum_v struct TypeDecoder { - static T decode(const bytes::View& bytes, uint64_t& index) { + static T decode(const View& bytes, uint64_t& index) { return static_cast(decodeUint(bytes, index)); } }; // Forward declaration of TypeDecode> so TypeDecoder> can see it. template requires isVectorV struct TypeDecoder { - static T decode(const bytes::View& bytes, uint64_t& index); + static T decode(const View& bytes, uint64_t& index); }; /** @@ -891,7 +902,7 @@ namespace ABI { * @param index The point on the encoded string to start decoding. * @param ret The tuple object to "return". Needs to be a reference and created outside the function due to recursion. */ - template void decodeTuple(const bytes::View& bytes, uint64_t& index, TupleLike& ret) { + template void decodeTuple(const View& bytes, uint64_t& index, TupleLike& ret) { if constexpr (I < std::tuple_size_v) { using SelectedType = typename std::tuple_element::type; std::get(ret) = TypeDecoder::decode(bytes, index); @@ -901,7 +912,7 @@ namespace ABI { // Specialization for std::tuple template requires isTuple::value struct TypeDecoder { - static T decode(const bytes::View& bytes, uint64_t& index) { + static T decode(const View& bytes, uint64_t& index) { T ret; if constexpr (isTupleOfDynamicTypes::value) { if (index + 32 > bytes.size()) throw std::length_error("Data too short for tuple of dynamic types"); @@ -920,7 +931,7 @@ namespace ABI { }; /// Specialization for std::vector - template requires isVectorV T TypeDecoder::decode(const bytes::View& bytes, uint64_t& index) { + template requires isVectorV T TypeDecoder::decode(const View& bytes, uint64_t& index) { using ElementType = vectorElementTypeT; std::vector retVector; @@ -949,7 +960,7 @@ namespace ABI { /// Specialization of decodeTupleHelper() for when tuple index is the last one template requires (Index == sizeof...(Args)) - void decodeTupleHelper(const bytes::View&, const uint64_t&, std::tuple&) { + void decodeTupleHelper(const View&, const uint64_t&, std::tuple&) { // End of recursion, do nothing } @@ -963,7 +974,7 @@ namespace ABI { */ template requires (Index < sizeof...(Args)) - void decodeTupleHelper(const bytes::View& encodedData, uint64_t& index, std::tuple& tuple) { + void decodeTupleHelper(const View& encodedData, uint64_t& index, std::tuple& tuple) { // TODO: Technically, we could pass std::get(tuple) as a reference to decode<>(). // But, it is worth to reduce code readability for a few nanoseconds? Need to benchmark. std::get(tuple) = TypeDecoder>>::decode(encodedData, index); @@ -978,7 +989,7 @@ namespace ABI { * @return A tuple with the decoded data, or an empty tuple if there's no arguments to decode. */ template - inline std::tuple decodeData(const bytes::View& encodedData, uint64_t index = 0) { + inline std::tuple decodeData(const View& encodedData, uint64_t index = 0) { if constexpr (sizeof...(Args) == 0) { return std::tuple<>(); } else { @@ -988,10 +999,12 @@ namespace ABI { } } + std::string decodeError(View data); + /// Specialization for tuples without args. template struct decodeDataAsTuple { /// Decode the tuple. - static T decode(const bytes::View&) { + static T decode(const View&) { static_assert(always_false, "Can't use decodeDataAsTuple with a non-tuple type"); return T(); } @@ -1000,7 +1013,7 @@ namespace ABI { /// Specialization for tuples with args. template struct decodeDataAsTuple> { /// Decode the tuple. - static std::tuple decode(const bytes::View& encodedData) { + static std::tuple decode(const View& encodedData) { if constexpr (sizeof...(Args) == 0) { throw std::invalid_argument("Can't decode empty tuple"); } else { diff --git a/src/contract/anyencodedmessagehandler.h b/src/contract/anyencodedmessagehandler.h new file mode 100644 index 00000000..aa5f380f --- /dev/null +++ b/src/contract/anyencodedmessagehandler.h @@ -0,0 +1,43 @@ +#ifndef BDK_MESSAGES_ANYENCODEDMESSAGEHANDLER_H +#define BDK_MESSAGES_ANYENCODEDMESSAGEHANDLER_H + +#include "encodedmessages.h" + +class AnyEncodedMessageHandler { +public: + template + static AnyEncodedMessageHandler from(MessageHandler& handler) { + AnyEncodedMessageHandler anyHandler; + + const auto generic = [] (void *obj, auto& msg) { return static_cast(obj)->onMessage(msg); }; + + anyHandler.handler_ = &handler; + anyHandler.onCreate_ = static_cast(generic); + anyHandler.onSaltCreate_ = static_cast(generic); + anyHandler.onCall_ = static_cast(generic); + anyHandler.onStaticCall_ = static_cast(generic); + anyHandler.onDelegateCall_ = static_cast(generic); + + return anyHandler; + } + + Address onMessage(EncodedCreateMessage& msg) { return std::invoke(onCreate_, handler_, msg); } + + Address onMessage(EncodedSaltCreateMessage& msg) { return std::invoke(onSaltCreate_, handler_, msg); } + + Bytes onMessage(EncodedCallMessage& msg) { return std::invoke(onCall_, handler_, msg); } + + Bytes onMessage(EncodedStaticCallMessage& msg) { return std::invoke(onStaticCall_, handler_, msg); } + + Bytes onMessage(EncodedDelegateCallMessage& msg) { return std::invoke(onDelegateCall_, handler_, msg); } + +private: + void *handler_; + Address (*onCreate_)(void*, EncodedCreateMessage&); + Address (*onSaltCreate_)(void*, EncodedSaltCreateMessage&); + Bytes (*onCall_)(void*, EncodedCallMessage&); + Bytes (*onStaticCall_)(void*, EncodedStaticCallMessage&); + Bytes (*onDelegateCall_)(void*, EncodedDelegateCallMessage&); +}; + +#endif // BDK_MESSAGES_ANYENCODEDMESSAGEHANDLER_H diff --git a/src/contract/basemessage.h b/src/contract/basemessage.h new file mode 100644 index 00000000..0818a0fd --- /dev/null +++ b/src/contract/basemessage.h @@ -0,0 +1,149 @@ +#ifndef BDK_MESSAGES_BASEMESSAGE_H +#define BDK_MESSAGES_BASEMESSAGE_H + +#include "bytes/range.h" +#include "utils/address.h" +#include "utils/hash.h" +#include "gas.h" + +struct BaseContract; + +template +struct BaseMessage : BaseMessage, BaseMessage { + template + constexpr BaseMessage(U&& first, Us&&... others) + : BaseMessage(std::forward(first)), + BaseMessage(std::forward(others)...) {} +}; + +template +struct BaseMessage : T { + template + explicit constexpr BaseMessage(U&& first) : T(std::forward(first)) {} +}; + +class FromField { +public: + template + requires std::convertible_to> + explicit constexpr FromField(R&& range) : from_(range) {} + + constexpr View
from() const { return from_; } + +private: + View
from_; +}; + +class ToField { +public: + template + requires std::convertible_to> + explicit constexpr ToField(R&& range) : to_(range) {} + + constexpr View
to() const { return to_; } + +private: + View
to_; +}; + +class GasField { +public: + explicit constexpr GasField(Gas& gas) : gas_(gas) {} + constexpr Gas& gas() { return gas_; } + constexpr const Gas& gas() const { return gas_; } + +private: + Gas& gas_; +}; + +class ValueField { +public: + explicit constexpr ValueField(const uint256_t& value) : value_(value) {} + constexpr const uint256_t& value() const { return value_; } + +private: + const uint256_t& value_; +}; + +class InputField { +public: + explicit constexpr InputField(View input) : input_(input) {} + constexpr View input() const { return input_; } + +private: + View input_; +}; + +class CodeField { +public: + explicit constexpr CodeField(View code) : code_(code) {} + constexpr View code() const { return code_; } + +private: + View code_; +}; + +class SaltField { +public: + template + requires std::convertible_to> + explicit constexpr SaltField(R&& range) : salt_(range) {} + constexpr View salt() const { return salt_; } + +private: + View salt_; +}; + +class CodeAddressField { +public: + template + requires std::convertible_to> + explicit constexpr CodeAddressField(R&& range) : codeAddress_(range) {} + constexpr View
codeAddress() const { return codeAddress_; } + +private: + View
codeAddress_; +}; + +class CallerField { +public: + explicit constexpr CallerField(const BaseContract& caller) : caller_(caller) {} + constexpr const BaseContract& caller() const { return caller_; } + +private: + const BaseContract& caller_; +}; + +template +class MethodField { +public: + explicit constexpr MethodField(M method) : method_(method) {} + constexpr M& method() { return method_; } + constexpr const M& method() const { return method_; } + +private: + M method_; +}; + +template +class ArgsField { +public: + explicit constexpr ArgsField(Args&&... args) : args_(std::forward(args)...) {} + constexpr std::tuple& args() & { return args_; } + constexpr std::tuple&& args() && { return std::move(args_); } + constexpr const std::tuple& args() const& { return args_; } + +private: + std::tuple args_; +}; + +template +ArgsField(Args&&...) -> ArgsField; + +template +struct BaseMessage> : ArgsField { + explicit constexpr BaseMessage(auto&&... args) + : ArgsField(std::forward(args)...) {} +}; + +#endif // BDK_MESSAGES_BASEMESSAGE_H diff --git a/src/contract/calltracer.cpp b/src/contract/calltracer.cpp deleted file mode 100644 index a2e5a928..00000000 --- a/src/contract/calltracer.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* -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 "calltracer.h" - -#include "../utils/uintconv.h" - -namespace trace { - -Bytes encodeRevertReason(std::string_view reason) { - FixedBytes<32> reasonEncoded{}; - - const size_t count = std::min(reason.size(), reasonEncoded.size()); - std::copy_n(reason.begin(), count, reasonEncoded.begin()); - - const uint256_t size(reason.size()); - const FixedBytes<32> sizeEncoded(UintConv::uint256ToBytes(size)); - - return Utils::makeBytes(bytes::join( - Hex::toBytes("0x08c379a0"), - Hex::toBytes("0x0000000000000000000000000000000000000000000000000000000000000020"), - sizeEncoded, - reasonEncoded - )); -} - -std::string decodeRevertReason(bytes::View data) { - if (data.size() != 100) { - throw DynamicException("Encoded revert reason is expected to have exactly 100 bytes"); - } - - const size_t size = UintConv::bytesToUint256(data.subspan(36, 32)).convert_to(); - - std::string res; - res.reserve(size); - std::ranges::copy(data.subspan(68, size), std::back_inserter(res)); - return res; -} - -Call::Call(const evmc_message& msg) - : type(getCallType(msg)), - from(msg.sender), - to(msg.recipient), - value(msg.value.bytes), - gas(msg.gas), - gasUsed(0), - input(Utils::makeBytes(bytes::View(msg.input_data, msg.input_size))) {} - -json Call::toJson() const { - using enum Call::Type; - json res; - - switch (this->type) { - case CALL: res["type"] = "CALL"; break; - case STATICCALL: res["type"] = "STATICCALL"; break; - case DELEGATECALL: res["type"] = "DELEGATECALL"; break; - } - - res["from"] = this->from.hex(true); - res["to"] = this->to.hex(true); - - const uint256_t value = UintConv::bytesToUint256(this->value); - res["value"] = Hex::fromBytes(Utils::uintToBytes(value), true).forRPC(); - - res["gas"] = Hex::fromBytes(Utils::uintToBytes(this->gas), true).forRPC(); - res["gasUsed"] = Hex::fromBytes(Utils::uintToBytes(this->gasUsed), true).forRPC(); - res["input"] = Hex::fromBytes(this->input, true); - - if (!this->output.empty()) res["output"] = Hex::fromBytes(this->output, true); - - switch (this->status) { - case Status::EXECUTION_REVERTED: { - res["error"] = "execution reverted"; - try { - std::string revertReason = decodeRevertReason(this->output); - res["revertReason"] = std::move(revertReason); - } catch (const std::exception& ignored) {} - break; - } - case Status::OUT_OF_GAS: res["error"] = "out of gas"; break; - } - - if (!this->calls.empty()) { - res["calls"] = json::array(); - for (const auto& subcall : this->calls) res["calls"].push_back(subcall.toJson()); - } - - return res; -} - -CallTracer::CallTracer(Call rootCall) : root_(std::make_unique(std::move(rootCall))) { - stack_.emplace_back(root_.get()); -} - -const Call& CallTracer::root() const { - if (!hasCalls()) throw DynamicException("root call does not exists since no call was traced"); - return *root_; -} - -const Call& CallTracer::current() const { - if (!hasCalls()) throw DynamicException("current call does not exists since no call was traced"); - if (isFinished()) throw DynamicException("call tracer is already finished, no call currently opened"); - return *stack_.back(); -} - -void CallTracer::push(Call call) { - if (stack_.empty()) [[unlikely]] { - root_ = std::make_unique(std::move(call)); - stack_.emplace_back(root_.get()); - return; - } - - Call& currCall = *stack_.back(); - Call& newCall = currCall.calls.emplace_back(std::move(call)); - stack_.emplace_back(&newCall); -} - -void CallTracer::pop(Bytes output, Status status, uint64_t gasUsed) { - if (stack_.empty()) [[unlikely]] { - throw DynamicException("No function start was traced yet"); - } - - Call& curr = *stack_.back(); - curr.output = std::move(output); - curr.status = status; - curr.gasUsed = gasUsed; - stack_.pop_back(); -} - -void CallTracer::callStarted(Call call) { - this->push(std::move(call)); -} - -void CallTracer::callOutOfGas() { - this->pop(Bytes(), Status::OUT_OF_GAS, this->current().gas); -} - -void CallTracer::callReverted(uint64_t gasUsed) { - this->pop(Bytes(), Status::EXECUTION_REVERTED, gasUsed); -} - -void CallTracer::callReverted(Bytes output, uint64_t gasUsed) { - this->pop(std::move(output), Status::EXECUTION_REVERTED, gasUsed); -} - -void CallTracer::callSucceeded(Bytes output, uint64_t gasUsed) { - this->pop(std::move(output), Status::SUCCEEDED, gasUsed); -} - -} // namespace trace diff --git a/src/contract/calltracer.h b/src/contract/calltracer.h index 928241ff..00a3e6e9 100644 --- a/src/contract/calltracer.h +++ b/src/contract/calltracer.h @@ -8,146 +8,103 @@ See the LICENSE.txt file in the project root for more information. #ifndef CONTRACT_CALLTRACER_H #define CONTRACT_CALLTRACER_H -#include - -#include "../utils/utils.h" // strings.h, bytes/join.h (used in .cpp) libs/zpp_bits.h -> memory - -/// Namespace for tracing-related classes and functions. -namespace trace { - -/** - * Encode the reason for a call being reverted into raw bytes. - * @param reason The revert reason to encode. - * @return The revert reason as a raw bytes string. - */ -Bytes encodeRevertReason(std::string_view reason); - -/** - * Decode the raw bytes string that states the reason for a call being reverted. - * @param data The data to decode. - * @return The revert reason as a readable string. - */ -std::string decodeRevertReason(bytes::View data); - -/// Enum for the call's status. -enum class Status { SUCCEEDED, EXECUTION_REVERTED, OUT_OF_GAS }; - -/// Abstraction of a contract call. -struct Call { - using serialize = zpp::bits::members<10>; ///< Typedef for the serialization struct. - - /// Enum for the call's type. - enum class Type { CALL, STATICCALL, DELEGATECALL }; - - Type type; ///< The call's type. - Status status; ///< The call's status. - Address from; ///< The address that made the call. - Address to; ///< The address that received the call. - FixedBytes<32> value; ///< The value spent during the call. - uint64_t gas; ///< The call's gas limit. - uint64_t gasUsed; ///< The call's cumulative used gas. - Bytes input; ///< The call's input data. - Bytes output; ///< The call's output data. - boost::container::stable_vector calls; ///< A list of nested calls originated from this one. - - Call() = default; ///< Constructor (default). - - /** - * Constructor based on a call message. - * @param msg The call message to parse. - */ - explicit Call(const evmc_message& msg); - - json toJson() const; ///< Serialize the call data to a JSON object. -}; - -/** - * Get the respective call type from a given message. - * @param msg The message to parse. - * @return The call type. - */ -static Call::Type getCallType(const evmc_message& msg) { - using enum Call::Type; - if (msg.kind == EVMC_CALL) return (msg.flags == EVMC_STATIC) ? STATICCALL : CALL; - if (msg.kind == EVMC_DELEGATECALL) return DELEGATECALL; - throw DynamicException("evmc_message is not from a function call"); -} - -/// Abstraction of a contract call tracer. +#include "utils/utils.h" +#include "utils/options.h" +#include "contract/concepts.h" +#include "contract/outofgas.h" +#include "contract/trace/call.h" +#include "contract/traits.h" +#include "contract/abi.h" + +template class CallTracer { - private: - std::unique_ptr root_; ///< The root call (the one that initiated the trace). - std::deque stack_; ///< The list of subsequent calls that are being traced. - - /** - * Add a call to the stack. - * @param call The call to add. - */ - void push(Call call); - - /** - * Remove a call from the stack. - * @param output The output of the call. - * @param status The status of the call. - * @param gasUsed The cumulative gas used in the call. - * @throw DynamicException if a trace hasn't started yet (stack is empty). - */ - void pop(Bytes output, Status status, uint64_t gasUsed); - - public: - CallTracer() = default; ///< Constructor (default). - - /** - * Constructor based on a root call. - * @param rootCall The call that starts the trace. - */ - explicit CallTracer(Call rootCall); - - /** - * Check if calls are being traced at the moment. - * @return `true` if tracer has a root call, `false` otherwise. - */ - bool hasCalls() const noexcept { return bool(root_); } - - /** - * Check if a call trace has finished (no root call, stack is empty). - * @return `true` is call trace has finished, `false` otherwise. - */ - bool isFinished() const noexcept { return root_ != nullptr && stack_.empty(); } - - const Call& root() const; ///< Getter. - const Call& current() const; ///< Getter. - - /** - * Signal that a call has started (push into the stack). - * @param call The call to signal. - */ - void callStarted(Call call); - - /// Signal that a call has run out of gas (pop out of the stack). - void callOutOfGas(); - - /** - * Signal that a call was reverted (pop out of stack). - * @param gasUsed The cumulative gas used by the call. - */ - void callReverted(uint64_t gasUsed); - - /** - * Overload of callReverted() that accepts the call's output bytes. - * @param output The call's output bytes. - * @param gasUsed The cumulative gas used by the call. - */ - void callReverted(Bytes output, uint64_t gasUsed); - - /** - * Signal that a call has succeeded (pop out of stack). - * @param output The call's output bytes. - * @param gasUsed The cumulative gas used by the call. - */ - void callSucceeded(Bytes output, uint64_t gasUsed); -}; +public: + CallTracer(MessageHandler handler, IndexingMode indexingMode) + : handler_(std::move(handler)), rootCall_(), callStack_(), indexingMode_(indexingMode) {} + + template + decltype(auto) onMessage(Message&& msg) { + using Result = traits::MessageResult; + + if (indexingMode_ != IndexingMode::RPC_TRACE) { + return handler_.onMessage(std::forward(msg)); + } + + trace::Call& callTrace = callStack_.empty() + ? *(rootCall_ = std::make_unique()) + : callStack_.top()->calls.emplace_back(); + + Gas& gas = msg.gas(); + + callTrace.type = trace::getMessageCallType(msg); + callTrace.status = trace::CallStatus::SUCCEEDED; + callTrace.from = Address(msg.from()); + callTrace.to = Address(msg.to()); + callTrace.value = FixedBytes<32>(UintConv::uint256ToBytes(messageValueOrZero(msg))); + callTrace.gas = uint64_t(gas); + + try { + callTrace.input = messageInputEncoded(msg); + } catch (const std::exception& ignored) {} + + callStack_.push(&callTrace); + + try { + if constexpr (not std::same_as) { + Result result = handler_.onMessage(std::forward(msg)); + + if constexpr (concepts::PackedMessage) { + callTrace.output = ABI::Encoder::encodeData(result); + } else { + callTrace.output = result; + } + + callTrace.gasUsed = callTrace.gas - uint64_t(gas); + callStack_.pop(); -} // namespace trace + return result; + } + + handler_.onMessage(std::forward(msg)); + } catch (const OutOfGas& outOfGas) { + callTrace.status = trace::CallStatus::OUT_OF_GAS; + callTrace.gasUsed = callTrace.gas - uint64_t(gas); + callStack_.pop(); + + throw; + } catch (const std::exception& error) { + callTrace.status = trace::CallStatus::EXECUTION_REVERTED; + + if (error.what()) { + try { + callTrace.output = ABI::Encoder::encodeError(error.what()); + } catch (const std::exception& ignored) {} + } + + callTrace.gasUsed = callTrace.gas - uint64_t(gas); + callStack_.pop(); + + throw; + } + } + + decltype(auto) onMessage(concepts::CreateMessage auto&& msg) { + return handler_.onMessage(std::forward(msg)); + } + + const MessageHandler& handler() const { return handler_; } + + MessageHandler& handler() { return handler_; } + + bool hasCallTrace() const { return rootCall_ != nullptr; } + + const trace::Call& getCallTrace() const { return *rootCall_; } + +private: + MessageHandler handler_; + std::unique_ptr rootCall_; + std::stack callStack_; + IndexingMode indexingMode_; +}; #endif // CONTRACT_CALLTRACER_H diff --git a/src/contract/common.cpp b/src/contract/common.cpp new file mode 100644 index 00000000..c7df74ca --- /dev/null +++ b/src/contract/common.cpp @@ -0,0 +1,21 @@ +#include "common.h" + +Address generateContractAddress(uint64_t nonce, View
address) { + uint8_t rlpSize = 0xc0; + rlpSize += 20; + rlpSize += (nonce < 0x80) ? 1 : 1 + Utils::bytesRequired(nonce); + Bytes rlp; + rlp.insert(rlp.end(), rlpSize); + rlp.insert(rlp.end(), address.begin(), address.end()); + rlp.insert(rlp.end(), (nonce < 0x80) ? (char)nonce : (char)0x80 + Utils::bytesRequired(nonce)); + + return Address(Utils::sha3(rlp).view(12)); +} + +Address generateContractAddress(View
from, View salt, View code) { + const Hash codeHash = Utils::sha3(code); + Bytes buffer(from.size() + salt.size() + codeHash.size() + 1); // 85 + buffer[0] = 0xFF; + bytes::join(from, salt, codeHash).to(buffer | std::views::drop(1)); + return Address(Utils::sha3(buffer).view(12)); +} diff --git a/src/contract/common.h b/src/contract/common.h new file mode 100644 index 00000000..68ce0241 --- /dev/null +++ b/src/contract/common.h @@ -0,0 +1,67 @@ +#ifndef BDK_MESSAGES_COMMON_H +#define BDK_MESSAGES_COMMON_H + +#include "utils/utils.h" +#include "utils/contractreflectioninterface.h" +#include "concepts.h" + +Address generateContractAddress(uint64_t nonce, View
address); + +Address generateContractAddress(View
from, View salt, View code); + +constexpr uint256_t messageValueOrZero(const auto& msg) { + if constexpr (concepts::HasValueField) { + return msg.value(); + } else { + return uint256_t(0); + } +} + +constexpr View
messageCodeAddress(const auto& msg) { + if constexpr (concepts::DelegateCallMessage) { + return msg.codeAddress(); + } else { + return msg.to(); + } +} + +constexpr Address messageRecipientOrDefault(const auto& msg) { + if constexpr (concepts::CreateMessage) { + return Address{}; + } else { + return Address(msg.to()); + } +} + +constexpr Hash messageSaltOrDefault(const auto& msg) { + if constexpr (concepts::SaltMessage) { + return Hash(msg.salt()); + } else { + return Hash{}; + } +} + +Bytes messageInputEncoded(const concepts::EncodedMessage auto& msg) { + return Bytes(msg.input()); +} + +Bytes messageInputEncoded(const concepts::PackedMessage auto& msg) { + return std::apply([&] (const auto&... args) -> Bytes { + const std::string functionName = ContractReflectionInterface::getFunctionName(msg.method()); + + if (functionName.empty()) { + throw DynamicException("Contract fuction not found (contract not registered?)"); + } + + const BytesArr<4> encodedFunctor = UintConv::uint32ToBytes(ABI::FunctorEncoder::encode(std::string(functionName)).value); + + if constexpr (sizeof...(args) > 0) { + const Bytes encodedArgs = ABI::Encoder::encodeData(args...); + return Utils::makeBytes(bytes::join(encodedFunctor, encodedArgs)); + } + + return Utils::makeBytes(encodedFunctor); + }, msg.args()); +} + +#endif // BDK_MESSAGES_COMMON_H diff --git a/src/contract/concepts.h b/src/contract/concepts.h new file mode 100644 index 00000000..4cbdb2e9 --- /dev/null +++ b/src/contract/concepts.h @@ -0,0 +1,107 @@ +#ifndef BDK_MESSAGES_CONCEPTS_H +#define BDK_MESSAGES_CONCEPTS_H + +#include "utils/strings.h" +#include "gas.h" + +namespace concepts { + +/** + * Basic message concept. Every message has as sender address and a gas reference to be consumed. + */ +template +concept Message = requires (M m) { + { m.from() } -> std::convertible_to>; + { m.gas() } -> std::convertible_to; +}; + +/** + * A message that also has value. Often used to describe composed concepts. + */ +template +concept HasValueField = requires (M m) { + { m.value() } -> std::convertible_to; +}; + +template +concept HasInputField = requires (M m) { + { m.input() } -> std::convertible_to>; +}; + +template +concept HasCodeField = requires (M m) { + { m.code() } -> std::convertible_to>; +}; + +/** + * A message that also has recipient address. Often used to describe composed concepts. + */ +template +concept HasToField = requires (M m) { + { m.to() } -> std::convertible_to>; +}; + +/** + * A message aimed to create an address. In general, contract creation messages + * do not have a target address (i.e. recipient) but can have value. + */ +template +concept CreateMessage = Message && HasValueField && !HasToField; + +/** + * Call messages can have value and must have a target address. + */ +template +concept CallMessage = Message && HasToField; + +/** + * Static call messages are messages that calls const/view functions. They + * can't have value but (as any call message) must have a recipient address + */ +template +concept StaticCallMessage = CallMessage && !HasValueField; + +/** + * Solution based on the C++ standard: std::ranges::enable_borrowed_range. + * In short, delegate calls have the exact same structure of a normal call, + * but with different intention. Thus, any message that represents a + * delegate message must specialize the EnableDelegate as a true_type, + * allowing the DelegateCallMessage concept to be chosen during overload + * resolution. + */ +template +constexpr bool EnableDelegate = false; + +template +constexpr bool EnableCallCode = false; + +/** + * Concept of delegate call messages. + */ +template +concept DelegateCallMessage = CallMessage && requires (M m) { + { m.codeAddress() } -> std::convertible_to>; +}; + +/** + * Concept of delegate call messages. + */ +template +concept CallCodeMessage = CallMessage && EnableCallCode>; + +template +concept SaltMessage = CreateMessage && requires (M m) { + { m.salt() } -> std::convertible_to>; +}; + +template +concept EncodedMessage = (CallMessage && HasInputField) || (CreateMessage && HasCodeField); + +template +concept PackedMessage = Message && requires (M m) { + m.args(); +}; + +} // namespace concepts + +#endif // BDK_MESSAGES_CONCEPTS_H diff --git a/src/contract/contract.cpp b/src/contract/contract.cpp index 6062cd24..344dd136 100644 --- a/src/contract/contract.cpp +++ b/src/contract/contract.cpp @@ -16,17 +16,17 @@ uint64_t ContractGlobals::blockHeight_ = 0; uint64_t ContractGlobals::blockTimestamp_ = 0; Address BaseContract::getOrigin() const { - if (this->host_ == nullptr) throw DynamicException( - "Contracts going haywire! Trying to get origin without a host!" - ); - return this->host_->get_tx_context().tx_origin; + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to get origin without a host!"); + } + return Address(this->host_->context().getTxOrigin()); } uint64_t BaseContract::getNonce(const Address& address) const { - if (this->host_ == nullptr) throw DynamicException( - "Contracts going haywire! Trying to get nonce without a host!" - ); - return this->host_->getNonce(address); + if (this->host_ == nullptr) { + throw DynamicException("Contracts going haywire! trying to get nonce without a host!"); + } + return this->host_->context().getAccount(address).getNonce(); } void BaseContract::ethCall(const evmc_message& data, ContractHost* host) { diff --git a/src/contract/contract.h b/src/contract/contract.h index 6c87a431..8593e0ea 100644 --- a/src/contract/contract.h +++ b/src/contract/contract.h @@ -40,7 +40,7 @@ class ContractGlobals { /// Class that maintains local variables for contracts. class ContractLocals : public ContractGlobals { - private: + public: // TODO: revert this to private // TODO: DONT RELY ON ContractLocals, INSTEAD, USE CONTRACTHOST TO STORE LOCALS mutable Address caller_; ///< Who sent the transaction. mutable uint256_t value_; ///< Value sent within the transaction. diff --git a/src/contract/contractfactory.h b/src/contract/contractfactory.h index 17012fa4..faf2d2e7 100644 --- a/src/contract/contractfactory.h +++ b/src/contract/contractfactory.h @@ -24,7 +24,7 @@ See the LICENSE.txt file in the project root for more information. * std::function< * void(const evmc_message&, * const Address&, - * boost::unordered_flat_map, SafeHash>& contracts_, + * boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, * const uint64_t&, * ContractHost* * )>, @@ -115,11 +115,11 @@ namespace ContractFactory { * @param host Pointer to the contract host. * @throw DynamicException if the call to the ethCall function fails, or if the contract does not exist. */ - template void createNewContract( - const evmc_message& callInfo, const Address& derivedAddress, - boost::unordered_flat_map, SafeHash>& contracts, - const uint64_t& chainId, ContractHost* host - ) { + template void createNewContract(const evmc_message& callInfo, + const Address& derivedAddress, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts, + const uint64_t& chainId, + ContractHost* host) { using ConstructorArguments = typename TContract::ConstructorArguments; auto decodedData = setupNewContractArgs(callInfo); if (!ContractReflectionInterface::isContractFunctionsRegistered()) { @@ -129,10 +129,9 @@ namespace ContractFactory { // The constructor can set SafeVariable values from the constructor. // We need to take account of that and set the variables accordingly. auto contract = createContractWithTuple( - callInfo.sender, derivedAddress, chainId, decodedData + Address(callInfo.sender), derivedAddress, chainId, decodedData ); - host->registerNewCPPContract(derivedAddress, contract.get()); - contracts.insert(std::make_pair(derivedAddress, std::move(contract))); + host->context().addContract(derivedAddress, std::move(contract)); } /** @@ -141,17 +140,18 @@ namespace ContractFactory { * @param createFunc Function to create a new contract. * @param createContractFuncs Function to create the functions of a new contract. */ - template void addContractFuncs( - const std::function, SafeHash>& contracts_, - const uint64_t&, ContractHost* host - )>& createFunc, - boost::unordered_flat_map, SafeHash>& contracts_, - const uint64_t&, ContractHost* - )>,SafeHash>& createContractFuncs + template + void addContractFuncs(const std::function< + void(const evmc_message&, + const Address&, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost* host)>& createFunc + ,boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost*)>,SafeHash>& createContractFuncs ) { std::string createSignature = "createNew" + Utils::getRealTypeName() + "Contract("; // Append args @@ -171,7 +171,7 @@ namespace ContractFactory { boost::unordered_flat_map, SafeHash>& contracts_, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, const uint64_t&, ContractHost* )>, SafeHash>& createContractFuncs, @@ -180,7 +180,7 @@ namespace ContractFactory { ((addContractFuncs>([]( const evmc_message &callInfo, const Address &derivedAddress, - boost::unordered_flat_map, SafeHash> &contracts, + boost::unordered_flat_map, SafeHash, SafeCompare> &contracts, const uint64_t &chainId, ContractHost* host ) { @@ -195,7 +195,7 @@ namespace ContractFactory { template requires Utils::is_tuple::value void addAllContractFuncs( boost::unordered_flat_map, SafeHash>& contracts_, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, const uint64_t&, ContractHost*)>,SafeHash>& createContractFuncs) { addAllContractFuncsHelper(createContractFuncs, std::make_index_sequence::value>{}); diff --git a/src/contract/contracthost.cpp b/src/contract/contracthost.cpp index 08e64dfb..e5be3fca 100644 --- a/src/contract/contracthost.cpp +++ b/src/contract/contracthost.cpp @@ -8,807 +8,40 @@ See the LICENSE.txt file in the project root for more information. #include "../utils/strconv.h" //cArrayToBytes #include "contracthost.h" -#include "dynamiccontract.h" -static inline bool isCall(const evmc_message& msg) { - return msg.kind == EVMC_CALL || msg.kind == EVMC_DELEGATECALL; -} - -void ContractHost::transfer(const Address& from, const Address& to, const uint256_t& value) { - // the from account **Must exist** on the unordered_map. - // unordered_map references to values are valid **until** you insert a new element - // So we can safely take a reference from it and create a reference from the to account. - auto& toAccount = *accounts_[to]; - auto& fromAccount = *accounts_[from]; - auto& toBalance = toAccount.balance; - auto& fromBalance = fromAccount.balance; - if (fromBalance < value) { - throw DynamicException("ContractHost transfer: insufficient funds"); - } - this->stack_.registerBalance(from, fromBalance); - this->stack_.registerBalance(to, toBalance); - fromBalance -= value; - toBalance += value; +uint256_t ContractHost::getRandomValue() { + return std::invoke(messageHandler_.handler().precompiledExecutor().randomGenerator()); } ContractHost::~ContractHost() { - if (!this->mustRevert_) { - // When the execution is complete and we need to commit the changes to the storage we must do the following steps: - // - Commit all the SafeBase variables - // - Commit all the emitted events to the storage - // We only need to commit the C++ stack, EVM operates directly on the storage (only requiring reverts) - // There is no permanent temporary storage for the EVM stack like on the SaveBase variables - // Instead, we use a journaling system with ContractStack to store the original values of the this->storage_ - // TODO: Maybe we should apply the same logic to the C++ stack as well somehow - for (auto& var : this->stack_.getUsedVars()) { - var.get().commit(); - } - for (const auto& event : this->stack_.getEvents()) { - this->storage_.putEvent(event); - } - for (const auto& contractPair : this->stack_.getContracts()) { - const auto& [address, contract] = contractPair; - if (contract != nullptr) { - this->manager_.pushBack(dynamic_cast(contract)); - } else { - // If the contract is nullptr, it means that it was a EVM contract, we need to link txHash and txIndex - this->addTxData_.contractAddress = address; - } - } - } else { - // When reverting, we must revert all the changes, that means: - // - Revert all the SafeBase variables - // - Remove newly created contracts (if any) - // - Revert all the storage changes - // - Revert all balance changes - // - Revert all nonce changes (contracts creating contracts) - // First, lets revert all the SafeBase variables + if (mustRevert_) { for (auto& var : this->stack_.getUsedVars()) { var.get().revert(); } - // Then lets clear off newly created contracts - for (const auto& contractPair : this->stack_.getContracts()) { - const auto& address = std::get<0>(contractPair); - this->accounts_.erase(address); - this->contracts_.erase(address); - } - // Thirdly, revert all storage changes, erasing them if the key was (0x00) - for (const auto& [key, value] : this->stack_.getStorage()) { - if (value == Hash()) { - // If the storage key was empty, we must erase it. - this->vmStorage_.erase(key); - } else { - this->vmStorage_[key] = value; - } - } - // Fourtly, revert all balance changes - for (const auto& [address, balance] : this->stack_.getBalance()) { - // Make sure we don't create a new account if it doesn't exist (deleted as it was a newly created contract) - auto accountIt = this->accounts_.find(address); - if (accountIt != this->accounts_.end()) { - accountIt->second->balance = balance; - } - } - // Finally, revert nonce changes - for (const auto& [address, nonce] : this->stack_.getNonce()) { - // Make sure we don't create a new account if it doesn't exist (deleted as it was a newly created contract) - auto accountIt = this->accounts_.find(address); - if (accountIt != this->accounts_.end()) { - accountIt->second->nonce = nonce; - } - } - } - - saveTxAdditionalData(); - saveCallTrace(); -} - -Address ContractHost::deriveContractAddress(const uint64_t& nonce, const Address& address) { - // Contract address is last 20 bytes of sha3 - // ( rlp ( tx from address + tx nonce ) ) - uint8_t rlpSize = 0xc0; - rlpSize += 20; - rlpSize += (nonce < 0x80) ? 1 : 1 + Utils::bytesRequired(nonce); - Bytes rlp; - rlp.insert(rlp.end(), rlpSize); - rlp.insert(rlp.end(), address.cbegin(), address.cend()); - rlp.insert(rlp.end(), (nonce < 0x80) ? (char)nonce : (char)0x80 + Utils::bytesRequired(nonce)); - - return Address(Utils::sha3(rlp).view(12)); -} -Address ContractHost::deriveContractAddress( - const Address& fromAddress, const Hash& salt, const bytes::View& code -) { - const auto code_hash = Utils::sha3(code); - Bytes buffer(1 + sizeof(fromAddress) + sizeof(salt) + sizeof(code_hash)); - assert(std::size(buffer) == 85); - - buffer[0] = 0xff; - buffer.insert(buffer.end(), fromAddress.cbegin(), fromAddress.cend()); - buffer.insert(buffer.end(), salt.cbegin(), salt.cend()); - buffer.insert(buffer.end(), code_hash.cbegin(), code_hash.cend()); - - return Address(Utils::sha3(buffer).view(12)); -} - -evmc::Result ContractHost::createEVMContract( - const evmc_message& msg, const Address& contractAddress, const evmc_call_kind& kind -) { - assert (kind == evmc_call_kind::EVMC_CREATE || kind == evmc_call_kind::EVMC_CREATE2); - // Create a new contract - auto createMsg = msg; - createMsg.recipient = contractAddress.toEvmcAddress(); - createMsg.kind = kind; - createMsg.input_data = nullptr; - createMsg.input_size = 0; - auto result = evmc::Result( - evmc_execute(this->vm_, - &this->get_interface(), - this->to_context(), - evmc_revision::EVMC_LATEST_STABLE_REVISION, - &createMsg, - msg.input_data, - msg.input_size)); - // gas_left is not linked with leftoverGas_, we need to link it. - this->leftoverGas_ = result.gas_left; - this->deduceGas(100000); - if (result.status_code) { - // Set the leftOverGas_ to the gas left after the execution - throw DynamicException("Error when creating EVM contract, EVM status code: " + - std::string(evmc_status_code_to_string(result.status_code)) + " bytes: " + - Hex::fromBytes(StrConv::cArrayToBytes(result.output_data, result.output_size)).get()); - } - if (result.output_size > 50000) { - throw DynamicException("ContractHost createEVMContract: contract code too large"); - } - this->registerNewEVMContract(contractAddress, - result.output_data, - result.output_size); - return evmc::Result{result.status_code, this->leftoverGas_, 0, createMsg.recipient}; -} - -bool ContractHost::isTracingCalls() const noexcept { - return bool(txHash_) && storage_.getIndexingMode() == IndexingMode::RPC_TRACE; -} - -void ContractHost::traceCallStarted(const evmc_message& msg) noexcept { - if (this->isTracingCalls()) { - callTracer_.callStarted(trace::Call(msg)); - } -} - -void ContractHost::traceCallSucceeded(Bytes output, uint64_t gasUsed) noexcept { - if (this->isTracingCalls() && callTracer_.hasCalls()) { - callTracer_.callSucceeded(std::move(output), gasUsed); - } -} - -void ContractHost::traceCallFinished(const evmc_result& res) noexcept { - if (!this->isTracingCalls() || !callTracer_.hasCalls()) return; - - const uint64_t gasUsed = callTracer_.current().gas - res.gas_left; - Bytes output = Utils::makeBytes(bytes::View(res.output_data, res.output_size)); - - if (res.status_code == EVMC_SUCCESS) { - callTracer_.callSucceeded(std::move(output), gasUsed); + context_.revert(); } else { - callTracer_.callReverted(std::move(output), gasUsed); - } -} - -void ContractHost::traceCallReverted(Bytes output, uint64_t gasUsed) noexcept { - if (this->isTracingCalls() && callTracer_.hasCalls()) { - callTracer_.callReverted(std::move(output), gasUsed); - } -} - -void ContractHost::traceCallReverted(uint64_t gasUsed) noexcept { - this->traceCallReverted(Bytes(), gasUsed); -} - -void ContractHost::traceCallOutOfGas() noexcept { - if (this->isTracingCalls() && callTracer_.hasCalls()) { - callTracer_.callOutOfGas(); - } -} - -void ContractHost::saveCallTrace() noexcept { - if (!this->isTracingCalls() || !callTracer_.hasCalls()) return; - - if (!callTracer_.isFinished()) { - LOGERROR(std::string("Attempt to persist unfinished call trace, hash: ") + txHash_.hex(true).get()); - return; - } - - try { - storage_.putCallTrace(txHash_, callTracer_.root()); - } catch (const std::exception& err) { - LOGERROR(std::string("Fail to persist call trace: ") + err.what()); - } -} - -void ContractHost::saveTxAdditionalData() noexcept { - if (!this->addTxData_.hash || this->storage_.getIndexingMode() == IndexingMode::DISABLED) return; - try { - this->storage_.putTxAdditionalData(this->addTxData_); - } catch (const std::exception& err) { - LOGERROR(std::string("Fail to persist additional tx data: ") + err.what()); - } -} - -evmc::Result ContractHost::processBDKPrecompile(const evmc_message& msg) { - /** - * interface BDKPrecompile { - * function getRandom() external view returns (uint256); - * } - */ - try { - this->leftoverGas_ = msg.gas; - this->deduceGas(1000); // CPP contract call is 1000 gas - if (msg.input_size < 4) throw DynamicException("ContractHost processBDKPrecompile: invalid input size"); - if (EVMCConv::getFunctor(msg).value != 2865519127) { - // we only have one function on the BDKD precompile - // getRandom() == 0xaacc5a17 == 2865519127 - throw DynamicException("ContractHost processBDKPrecompile: invalid function selector"); - } - auto ret = UintConv::uint256ToBytes(this->randomGen_.operator()()); - return evmc::Result(EVMC_SUCCESS, this->leftoverGas_, 0, ret.data(), ret.size()); - } catch (std::exception &e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return evmc::Result(EVMC_PRECOMPILE_FAILURE, this->leftoverGas_, 0, nullptr, 0); -} - -void ContractHost::execute(const evmc_message& msg, const ContractType& type) { - const Address from(msg.sender); - const Address to(msg.recipient); - const uint256_t value(EVMCConv::evmcUint256ToUint256(msg.value)); - const bool isContractCall = isCall(msg); - - if (isContractCall) { - this->traceCallStarted(msg); - } - - Bytes output; - std::string error; - bool outOfGas = false; - - if (value) { - this->transfer(from, to, value); - } - try { - if (to == Address()) { - // If the destination address of the transaction is 0x00, - // it means that we are creating a new contract - auto contractAddress = this->deriveContractAddress(this->getNonce(from), from); - if (this->accounts_.contains(contractAddress)) { - throw DynamicException("ContractHost create/execute: contract already exists"); - } - this->createEVMContract(msg, contractAddress, EVMC_CREATE); - } else { - - switch (type) - { - case ContractType::CPP: { - auto contractIt = this->contracts_.find(to); - if (contractIt == this->contracts_.end()) { - throw DynamicException("contract not found"); - } - this->setContractVars(contractIt->second.get(), from, value); - contractIt->second->ethCall(msg, this); - break; - } - case ContractType::EVM: { - // Execute a EVM contract. - auto result = evmc::Result(evmc_execute(this->vm_, - &this->get_interface(), - this->to_context(), - evmc_revision::EVMC_LATEST_STABLE_REVISION, &msg, - this->accounts_[to]->code.data(), - this->accounts_[to]->code.size())); - - output = Utils::makeBytes(bytes::View(result.output_data, result.output_size)); - - this->leftoverGas_ = result.gas_left; // gas_left is not linked with leftoverGas_, we need to link it. - if (result.status_code) { - outOfGas = result.status_code == EVMC_OUT_OF_GAS; - - error = evmc_status_code_to_string(result.status_code); - // Set the leftOverGas_ to the gas left after the execution - throw DynamicException("Error when executing EVM contract, EVM status code: " + - std::string(evmc_status_code_to_string(result.status_code)) + " bytes: " + - Hex::fromBytes(StrConv::cArrayToBytes(result.output_data, result.output_size)).get()); - } - break; - } - } - } - // Take out 21000 gas Limit from the tx - this->deduceGas(21000); - } catch (const std::exception &e) { - std::string what = std::string("ContractHost execution failed: ") + e.what(); - what += " OTHER INFO: "; - for (const auto& evmcError : this->evmcThrows_) { - what += evmcError; - what += " --- OTHER INFO: --- "; - } - - this->addTxData_.gasUsed = msg.gas - this->leftoverGas_; - this->addTxData_.succeeded = false; - - if (isContractCall) { - if (outOfGas) { - this->traceCallOutOfGas(); - } else { - this->traceCallReverted(std::move(output), this->addTxData_.gasUsed); - } - } - - throw DynamicException(what); - } - // We only set that we don't revert, if EVMC didn't throw a exception - if (this->evmcThrow_) { - std::string what = std::string("ContractHost execution failed: EVMC threw an exception: "); - for (const auto& evmcError : this->evmcThrows_) { - what += evmcError; - what += " --- OTHER INFO: --- "; - } - - this->addTxData_.gasUsed = msg.gas - this->leftoverGas_; - this->addTxData_.succeeded = false; - - if (isContractCall) { - this->traceCallReverted(std::move(output), this->addTxData_.gasUsed); - } - throw DynamicException(what); - } - - this->addTxData_.gasUsed = msg.gas - this->leftoverGas_; - this->addTxData_.succeeded = true; - this->mustRevert_ = false; - if (isContractCall) { - this->traceCallSucceeded(std::move(output), this->addTxData_.gasUsed); - } -} - -Bytes ContractHost::ethCallView(const evmc_message& msg, const ContractType& type) { - Bytes ret; - //const Address from(tx.sender); - const Address to(msg.recipient); - //const uint256_t value(EVMCConv::evmcUint256ToUint256(tx.value)); - try { - switch (type) { - case ContractType::CPP: { - auto contractIt = this->contracts_.find(to); - if (contractIt == this->contracts_.end()) { - throw DynamicException("contract not found"); - } - ret = contractIt->second->ethCallView(msg, this); - break; - } - case ContractType::EVM: { - // Execute a EVM contract. - auto result = evmc::Result(evmc_execute(this->vm_, &this->get_interface(), this->to_context(), - evmc_revision::EVMC_LATEST_STABLE_REVISION, &msg, - this->accounts_[to]->code.data(), this->accounts_[to]->code.size())); - this->leftoverGas_ = result.gas_left; - if (result.status_code) { - // Set the leftOverGas_ to the gas left after the execution - throw DynamicException("Error when executing (view) EVM contract, EVM status code: " + - std::string(evmc_status_code_to_string(result.status_code)) + " bytes: " + - Hex::fromBytes(StrConv::cArrayToBytes(result.output_data, result.output_size)).get()); - } - ret = Bytes(result.output_data, result.output_data + result.output_size); - break; - } - } - } catch (const std::exception &e) { - std::string what = std::string("ContractHost execution failed: ") + e.what(); - what += " OTHER INFO: "; - for (const auto& evmcError : this->evmcThrows_) { - what += evmcError; - what += " --- OTHER INFO: --- "; - } - throw DynamicException(what); - } - // We only set that we don't revert, if EVMC didn't throw a exception - if (this->evmcThrow_) { - std::string what = std::string("ContractHost execution failed: EVMC threw an exception: "); - for (const auto& evmcError : this->evmcThrows_) { - what += evmcError; - what += " --- OTHER INFO: --- "; - } - throw DynamicException(what); - } - return ret; -} - -void ContractHost::simulate(const evmc_message& msg, const ContractType& type) { - this->execute(msg, type); - // We should set the revert flag to true, as we are only simulating the execution - this->mustRevert_ = true; -} - -bool ContractHost::account_exists(const evmc::address& addr) const noexcept { - return accounts_.find(addr) != accounts_.end(); -} - -evmc::bytes32 ContractHost::get_storage(const evmc::address& addr, - const evmc::bytes32& key) const noexcept { - StorageKey storageKey(addr, key); - try { - auto it = vmStorage_.find(storageKey); - if (it != vmStorage_.end()) - return it->second.toEvmcBytes32(); - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return {}; -} - -// on SET_STORAGE, we can return multiple types of evmc_storage_status -// But we simply say that the storage was modified ;) -// TODO: Make it EIP-2200 compliant -evmc_storage_status ContractHost::set_storage( - const evmc::address& addr, const evmc::bytes32& key, const evmc::bytes32& value -) noexcept { - StorageKey storageKey(addr, key); - try { - Hash hashValue(value); - auto& storageValue = vmStorage_[storageKey]; - this->stack_.registerStorageChange(storageKey, storageValue); - storageValue = hashValue; - return EVMC_STORAGE_MODIFIED; - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - return EVMC_STORAGE_MODIFIED; - } -} - -evmc::uint256be ContractHost::get_balance(const evmc::address& addr) const noexcept { - try { - auto it = accounts_.find(addr); - if (it != accounts_.end()) return EVMCConv::uint256ToEvmcUint256(it->second->balance); - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return {}; -} - -size_t ContractHost::get_code_size(const evmc::address& addr) const noexcept { - try { - auto it = accounts_.find(addr); - if (it != accounts_.end()) return it->second->code.size(); - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return 0; -} - -evmc::bytes32 ContractHost::get_code_hash(const evmc::address& addr) const noexcept { - try { - auto it = accounts_.find(addr); - if (it != accounts_.end()) { - return it->second->codeHash.toEvmcBytes32(); - } - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return {}; -} + for (auto& var : this->stack_.getUsedVars()) + var.get().commit(); -size_t ContractHost::copy_code(const evmc::address& addr, - size_t code_offset, - uint8_t* buffer_data, - size_t buffer_size) const noexcept { - try { - const auto it = this->accounts_.find(addr); - if (it != this->accounts_.end()) { - const auto& code = it->second->code; - if (code_offset < code.size()) { - const auto n = std::min(buffer_size, code.size() - code_offset); - if (n > 0) - std::copy_n(&code[code_offset], n, buffer_data); - return n; + for (auto& [address, contract] : context_.getNewContracts()) { + if (contract == nullptr) { + continue; } - } - } catch (std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - std::cerr << e.what() << std::endl; - this->evmcThrow_ = true; - } - return 0; -} - -bool ContractHost::selfdestruct(const evmc::address& addr, - const evmc::address& beneficiary) noexcept { - // SELFDESTRUCT is not allowed in the current implementation - this->evmcThrow_ = true; - return false; -} - -evmc::Result ContractHost::callEVMCreate(const evmc_message& msg) { - try { - auto sender = Address(msg.sender); - auto& nonce = this->getNonce(sender); - auto contractAddress = this->deriveContractAddress(nonce, sender); - uint256_t value = EVMCConv::evmcUint256ToUint256(msg.value); - - this->stack_.registerNonce(sender, nonce); - ++nonce; - if (value) { - this->transfer(sender, contractAddress, value); - } - return this->createEVMContract(msg, contractAddress, EVMC_CREATE); - } catch (const std::exception &e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return evmc::Result(EVMC_REVERT, this->leftoverGas_, 0, nullptr, 0); -} - -evmc::Result ContractHost::callEVMCreate2(const evmc_message& msg) { - try { - Bytes code(msg.input_data, msg.input_data + msg.input_size); - uint256_t value = EVMCConv::evmcUint256ToUint256(msg.value); - auto salt = Hash(msg.create2_salt); - auto sender = Address(msg.sender); - auto contractAddress = this->deriveContractAddress(sender, salt, code); - - if (value) { - this->transfer(sender, contractAddress, value); - } - return this->createEVMContract(msg, contractAddress, EVMC_CREATE2); - } catch (const std::exception &e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return evmc::Result(EVMC_REVERT, this->leftoverGas_, 0, nullptr, 0); -} - -evmc::Result ContractHost::callCPPContract(const evmc_message& msg) { - Address recipient(msg.recipient); - try { - this->leftoverGas_ = msg.gas; - this->deduceGas(1000); // CPP contract call is 1000 gas - auto& contract = contracts_[recipient]; - if (contract == nullptr) throw DynamicException("ContractHost call: contract not found"); - this->setContractVars( - contract.get(), Address(msg.sender), EVMCConv::evmcUint256ToUint256(msg.value) - ); - Bytes ret = contract->evmEthCall(msg, this); - return evmc::Result(EVMC_SUCCESS, this->leftoverGas_, 0, ret.data(), ret.size()); - } catch (std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - return evmc::Result(EVMC_PRECOMPILE_FAILURE, this->leftoverGas_, 0, nullptr, 0); - } -} - -evmc::Result ContractHost::callEVMContract(const evmc_message& msg) { - Address recipient(msg.recipient); - auto &recipientAccount = *accounts_[recipient]; - evmc::Result result(evmc_execute( - this->vm_, - &this->get_interface(), - this->to_context(), - evmc_revision::EVMC_LATEST_STABLE_REVISION, - &msg, - recipientAccount.code.data(), - recipientAccount.code.size()) - ); - this->leftoverGas_ = result.gas_left; // gas_left is not linked with leftoverGas_, we need to link it. - this->deduceGas(5000); // EVM contract call is 5000 gas - result.gas_left = this->leftoverGas_; // We need to set the gas left to the leftoverGas_ - return result; -} - -ContractType ContractHost::decodeContractCallType(const evmc_message& msg) const { - switch (msg.kind) { - case evmc_call_kind::EVMC_CREATE: { - return ContractType::CREATE; - } - case evmc_call_kind::EVMC_CREATE2: { - return ContractType::CREATE2; - } - default: - if (msg.recipient == BDK_PRECOMPILE) return ContractType::PRECOMPILED; - Address recipient(msg.recipient); - // we need to take a reference to the account, not a reference to the pointer - const auto& recipientAccount = *accounts_[recipient]; - if (recipientAccount.contractType == ContractType::CPP) return ContractType::CPP; - return ContractType::EVM; // else EVM call - } -} - -// EVM -> EVM calls don't need to use this->leftOverGas_ as the final -// evmc::Result will have the gas left after the execution -evmc::Result ContractHost::call(const evmc_message& msg) noexcept { - evmc::Result result; - const bool isContractCall = isCall(msg); - - if (isContractCall) { - this->traceCallStarted(msg); - } - - switch (this->decodeContractCallType(msg)) - { - case ContractType::CREATE: { - result = this->callEVMCreate(msg); - break; - } - case ContractType::CREATE2: { - result = this->callEVMCreate2(msg); - break; - } - case ContractType::PRECOMPILED: { - result = this->processBDKPrecompile(msg); - break; - } - case ContractType::CPP: { - result = this->callCPPContract(msg); - break; - } - default: - result = this->callEVMContract(msg); - break; - } - - if (isContractCall) { - this->traceCallFinished(result.raw()); - } - - return result; -} - -evmc_tx_context ContractHost::get_tx_context() const noexcept { - return this->currentTxContext_; -} - -evmc::bytes32 ContractHost::get_block_hash(int64_t number) const noexcept { - try { - return EVMCConv::uint256ToEvmcUint256(number); - } catch (std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return {}; -} -void ContractHost::emit_log(const evmc::address& addr, - const uint8_t* data, - size_t data_size, - const evmc::bytes32 topics[], - size_t topics_count) noexcept { - try { - // We need the following arguments to build a event: - // (std::string) name The event's name. - // (uint64_t) logIndex The event's position on the block. - // (Hash) txHash The hash of the transaction that emitted the event. - // (uint64_t) txIndex The position of the transaction in the block. - // (Hash) blockHash The hash of the block that emitted the event. - // (uint64_t) blockIndex The height of the block. - // (Address) address The address that emitted the event. - // (Bytes) data The event's arguments. - // (std::vector) topics The event's indexed arguments. - // (bool) anonymous Whether the event is anonymous or not. - std::vector topics_; - for (uint64_t i = 0; i < topics_count; i++) { - topics_.emplace_back(topics[i]); + this->manager_.pushBack(dynamic_cast(contract)); } - Event event("", // EVM events do not have names - this->eventIndex_, - this->txHash_, - this->txIndex_, - this->blockHash_, - this->currentTxContext_.block_number, - addr, - Bytes(data, data + data_size), - topics_, - (topics_count == 0) - ); - ++this->eventIndex_; - this->stack_.registerEvent(std::move(event)); - } catch (std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } -} - -// We always return warm because we are warm (storage is on ram) ;) -evmc_access_status ContractHost::access_account(const evmc::address& addr) noexcept { - return EVMC_ACCESS_WARM; -} -// Same as above -evmc_access_status ContractHost::access_storage(const evmc::address& addr, - const evmc::bytes32& key) noexcept { - return EVMC_ACCESS_WARM; -} - -evmc::bytes32 ContractHost::get_transient_storage(const evmc::address &addr, - const evmc::bytes32 &key) const noexcept { - StorageKey storageKey(addr, key); - try { - auto it = transientStorage_.find(storageKey); - if (it != transientStorage_.end()) { - return it->second.toEvmcBytes32(); + for (const auto& event : context_.getEvents()) { + this->storage_.putEvent(event); } - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } - return {}; -} - -void ContractHost::set_transient_storage(const evmc::address &addr, - const evmc::bytes32 &key, - const evmc::bytes32 &value) noexcept { - StorageKey storageKey(addr, key); - try { - Hash hashValue(value); - transientStorage_[storageKey] = hashValue; - } catch (const std::exception& e) { - this->evmcThrows_.emplace_back(e.what()); - this->evmcThrow_ = true; - } -} - -void ContractHost::emitContractEvent(Event&& event) { - this->stack_.registerEvent(std::move(event)); -} - -uint256_t ContractHost::getBalanceFromAddress(const Address& address) const { - auto it = this->accounts_.find(address); - if (it != this->accounts_.end()) - return it->second->balance; - return 0; -} -void ContractHost::sendTokens(const BaseContract* from, - const Address& to, - const uint256_t& amount) { - this->transfer(from->getContractAddress(), to, amount); -} - -uint64_t& ContractHost::getNonce(const Address& nonce) { - return this->accounts_[nonce]->nonce; -} - -void ContractHost::registerNewCPPContract(const Address& address, - BaseContract* contract) { - Account contractAcc; - contractAcc.contractType = ContractType::CPP; - contractAcc.nonce = 1; - auto emplace = this->accounts_.try_emplace(address, contractAcc); - if (!emplace.second) { - throw DynamicException("ContractHost registerNewCPPContract: account on address already exists"); + context_.commit(); } - this->stack_.registerContract(address, contract); -} -void ContractHost::registerNewEVMContract(const Address& address, - const uint8_t* code, - size_t codeSize) { - Account contractAcc; - contractAcc.contractType = ContractType::EVM; - contractAcc.nonce = 1; - contractAcc.code = Bytes(code, code + codeSize); - contractAcc.codeHash = Utils::sha3(contractAcc.code); - auto emplace = this->accounts_.try_emplace(address, contractAcc); - if (!emplace.second) { - throw DynamicException("ContractHost registerNewCPPContract: account on address already exists"); + if (messageHandler_.hasCallTrace()) { + storage_.putCallTrace(Hash(context_.getTxHash()), messageHandler_.getCallTrace()); } - this->stack_.registerContract(address, nullptr); -} -void ContractHost::registerVariableUse(SafeBase& variable) { - this->stack_.registerVariableUse(variable); + // TODO: save transaction additional data } diff --git a/src/contract/contracthost.h b/src/contract/contracthost.h index c2d74820..2bf5b02b 100644 --- a/src/contract/contracthost.h +++ b/src/contract/contracthost.h @@ -1,9 +1,3 @@ -/* -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. -*/ #ifndef CONTRACT_HOST_H #define CONTRACT_HOST_H @@ -11,10 +5,17 @@ See the LICENSE.txt file in the project root for more information. // utils/{contractreflectioninterface.h, db.h, safehash.h, (strings.h -> hex.h, evmc/evmc.hpp), utils.h}, // contract.h -> core/{dump.h, storage.h -> calltracer.h} #include "contractmanager.h" -#include "contractstack.h" // utils/{strings.h,safehash.h, bytes/join.h} - -#include "../utils/evmcconv.h" -#include "../utils/uintconv.h" +#include "../core/dump.h" +#include "calltracer.h" +#include "bytes/join.h" +#include "bytes/cast.h" +#include "gas.h" +#include "concepts.h" +#include "executioncontext.h" +#include "messagedispatcher.h" +#include "packedmessages.h" +#include "calltracer.h" +#include "costs.h" // TODO: EVMC Static Mode Handling // TODO: Contract creating other contracts (EVM Factories) @@ -40,295 +41,52 @@ See the LICENSE.txt file in the project root for more information. * Any CPP Contract: 50000 */ -// Address for static BDKD precompile contracts. -using namespace evmc::literals; -const auto ZERO_ADDRESS = 0x0000000000000000000000000000000000000000_address; -const auto BDK_PRECOMPILE = 0x1000000000000000000000000000100000000001_address; - -/// Abstraction for a contract host. -class ContractHost : public evmc::Host { +class ContractHost { private: - /** - * Helper class for a nested call safe guard. - * We need this because nested calls can call the same contract multiple times, - * potentially taking advantage of wrong context variables. - */ - class NestedCallSafeGuard { - private: - const ContractLocals* contract_; ///< Pointer to the contract being called. - Address caller_; ///< Address of the caller of the contract. - uint256_t value_; ///< Value used in the contract call. - - public: - /** - * Constructor. - * @param contract Pointer to the contract being called. - * @param caller Address of the caller of the contract. - * @param value Value used in the contract call. - */ - NestedCallSafeGuard(const ContractLocals* contract, const Address& caller, const uint256_t& value) : - contract_(contract), caller_(contract->caller_), value_(contract->value_) {} - - /// Destructor. - ~NestedCallSafeGuard() { contract_->caller_ = caller_; contract_->value_ = value_; } - }; - - evmc_vm* vm_; ///< Pointer to the EVMC virtual machine. - DumpManager& manager_; ///< Reference to the database dumping manager. - Storage& storage_; ///< Reference to the blockchain storage. - mutable ContractStack stack_; ///< Contract stack object (ephemeral). - mutable RandomGen randomGen_; ///< Random generator for the contract. - const evmc_tx_context& currentTxContext_; ///< Current transaction context. MUST be initialized within the constructor. - boost::unordered_flat_map, SafeHash>& contracts_; ///< Map for deployed contracts. - boost::unordered_flat_map, SafeHash>& accounts_; ///< Map for active accounts. - boost::unordered_flat_map& vmStorage_; ///< Map for EVM storage keys. - boost::unordered_flat_map transientStorage_; ///< Map for transient storage keys. - bool mustRevert_ = true; ///< Flag to revert a contract call chain. Defaults to true (we always assume that we must revert until proven otherwise). - mutable bool evmcThrow_ = false; ///< Flag for when the EVMC throws an exception. Defaults to false. - mutable std::vector evmcThrows_; ///< List of EVMC exception throws (ephemeral). - std::vector evmcResults_; ///< List of EVMC call results. evmc_result has a uint8_t* and a size, but we need to allocate memory for it. - uint64_t eventIndex_ = 0; ///< Current event emission index. - const Hash& txHash_; ///< Current transaction hash. - const uint64_t txIndex_; ///< Current transaction index. - const Hash& blockHash_; ///< Current block hash. - int64_t& leftoverGas_; ///< Reference to the leftover gas from the transaction (object given by the State). - TxAdditionalData addTxData_; ///< Additional transaction data. - trace::CallTracer callTracer_; ///< Call tracer object. - - /** - * Transfer a given value from one address to another. - * Function is private because this is not available for contracts as it has safety checks. - * @param from The origin address. - * @param to The destination address. - * @param value The value to transfer. - */ - void transfer(const Address& from, const Address& to, const uint256_t& value); + DumpManager& manager_; + Storage& storage_; + mutable ContractStack stack_; + bool mustRevert_ = true; // We always assume that we must revert until proven otherwise. + ExecutionContext& context_; + CallTracer messageHandler_; - /** - * Set the local variables for a given contract (origin, caller, value). - * Used everytime *before* and *after* (if nested) a contract call. - * @param contract The contract to set the local variables for. - * @param caller The caller address to set. - * @param value The value to set. - */ - inline void setContractVars( - const ContractLocals* contract, const Address& caller, const uint256_t& value - ) const { - contract->caller_ = caller; contract->value_ = value; + public: + ContractHost(evmc_vm* vm, + DumpManager& manager, + Storage& storage, + const Hash& randomnessSeed, + ExecutionContext& context) : + manager_(manager), + storage_(storage), + stack_(), + context_(context), + messageHandler_(MessageDispatcher(context_, CppContractExecutor(context_, *this), EvmContractExecutor(context_, vm), PrecompiledContractExecutor(RandomGen(randomnessSeed))), storage.getIndexingMode()) { + messageHandler_.handler().evmExecutor().setMessageHandler(AnyEncodedMessageHandler::from(messageHandler_)); // TODO: is this really required? } - /** - * Deduce a given gas quantity from the transaction's leftover gas. - * @param gas The gas to deduce. - * @throw DynamicException if leftover gas runs out. - */ - inline void deduceGas(const int64_t& gas) const { - this->leftoverGas_ -= gas; - if (this->leftoverGas_ < 0) throw DynamicException("ContractHost deduceGas: out of gas"); + // Rule of five, no copy/move allowed. + ContractHost(const ContractHost&) = delete; + ContractHost(ContractHost&&) = delete; + ContractHost& operator=(const ContractHost&) = delete; + ContractHost& operator=(ContractHost&&) = delete; + ~ContractHost(); + + decltype(auto) simulate(concepts::Message auto&& msg) { + decltype(auto) result = execute(std::forward(msg)); + mustRevert_ = true; + return result; } - /** - * Create an EVM contract. - * @param msg The call message that creates the contract. - * @param contractAddress The address where the contract will be deployed. - * @param kind The kind of contract call. - * @return The result of the call. - */ - evmc::Result createEVMContract( - const evmc_message& msg, const Address& contractAddress, const evmc_call_kind& kind - ); - - /** - * Decode a given contract call type. - * @param msg The call message to decode. - * @return The contract call type. - */ - ContractType decodeContractCallType(const evmc_message& msg) const; - - /** - * Process a call to a BDK precompile. - * @param msg The call message to process. - * @return The result of the call. - */ - evmc::Result processBDKPrecompile(const evmc_message& msg); - - /** - * Process an EVM CREATE call. - * @param msg The call message to process. - * @return The result of the call. - */ - evmc::Result callEVMCreate(const evmc_message& msg); - - /** - * Process an EVM CREATE2 call. - * @param msg The call message to process. - * @return The result of the call. - */ - evmc::Result callEVMCreate2(const evmc_message& msg); - - /** - * Process an EVM contract call. - * @param msg The call message to process. - * @return The result of the call. - */ - evmc::Result callEVMContract(const evmc_message& msg); - - /** - * Process a C++ contract call. - * @param msg The call message to process. - * @return The result of the call. - */ - evmc::Result callCPPContract(const evmc_message& msg); - - /** - * TODO: document this - */ - Address computeNewAccountAddress( - const Address& fromAddress, const uint64_t& nonce, - const Hash& salt, const bytes::View& init_code - ); - - /** - * Check if the node is tracing contract calls (indexing mode is RPC_TRACE). - * @return `true` if calls are being traced, `false` otherwise. - */ - bool isTracingCalls() const noexcept; - - /** - * Signal a trace call start. - * @param msg The message that starts the call. - */ - void traceCallStarted(const evmc_message& msg) noexcept; - - /** - * Signal a trace call finish. - * @param res The result that finishes the call. - */ - void traceCallFinished(const evmc_result& res) noexcept; - - /** - * Signal a trace call success. - * @param output The raw bytes output of the call. - * @param gasUsed The total gas used by the call. - */ - void traceCallSucceeded(Bytes output, uint64_t gasUsed) noexcept; - - /** - * Signal a trace call finish. - * @param output The raw bytes output of the call. - * @param gasUsed The total gas used by the call. - */ - void traceCallReverted(Bytes output, uint64_t gasUsed) noexcept; - - /** - * Signal a trace call revert. - * @param gasUsed The total gas used by the call. - */ - void traceCallReverted(uint64_t gasUsed) noexcept; - - void traceCallOutOfGas() noexcept; ///< Signal a trace call that ran out of gas. - void saveCallTrace() noexcept; ///< Save the current call trace in storage. - void saveTxAdditionalData() noexcept; ///< Save the current call's transaction's additional data in storage. - - public: - /** - * Constructor. - */ - ContractHost( - evmc_vm* vm, - DumpManager& manager, - Storage& storage, - const Hash& randomnessSeed, - const evmc_tx_context& currentTxContext, - boost::unordered_flat_map, SafeHash>& contracts, - boost::unordered_flat_map, SafeHash>& accounts, - boost::unordered_flat_map& vmStorage, - const Hash& txHash, - const uint64_t txIndex, - const Hash& blockHash, - int64_t& txGasLimit - ) : vm_(vm), - manager_(manager), - storage_(storage), - randomGen_(randomnessSeed), - currentTxContext_(currentTxContext), - contracts_(contracts), - accounts_(accounts), - vmStorage_(vmStorage), - txHash_(txHash), - txIndex_(txIndex), - blockHash_(blockHash), - leftoverGas_(txGasLimit), - addTxData_({.hash = txHash}) {} - - ContractHost(const ContractHost&) = delete; ///< Copy constructor (deleted, Rule of Zero). - ContractHost(ContractHost&&) = delete; ///< Move constructor (deleted, Rule of Zero). - ContractHost& operator=(const ContractHost&) = delete; ///< Copy assignment operator (deleted, Rule of Zero). - ContractHost& operator=(ContractHost&&) = delete; ///< Move assignment operator (deleted, Rule of Zero). - ~ContractHost() noexcept override; ///< Destructor. - - /** - * Derive a new contract address from a given address and nonce. - * @param nonce The nonce to use. - * @param address The address to derive from. - */ - static Address deriveContractAddress(const uint64_t& nonce, const Address& address); - - /** - * Derive a new contract address from a given address, initialization code and a hashed salt. - * @param fromAddress The address to derive from. - * @param salt The salt to use. - * @param init_code The initialization code to use. - */ - static Address deriveContractAddress( - const Address& fromAddress, const Hash& salt, const bytes::View& init_code - ); - - /** - * Execute a contract call (non-view). - * @param msg The call message to execute. - * @param type The type of call to execute. - */ - void execute(const evmc_message& msg, const ContractType& type); - - /** - * Execute an `eth_call` RPC method (view). - * @param msg The call message to execute. - * @param type The type of call to execute. - * @return The result of the call. - */ - Bytes ethCallView(const evmc_message& msg, const ContractType& type); - - /** - * Simulate a contract call (dry run, won't affect state). - * @param msg The call message to execute. - * @param type The type of call to execute. - */ - void simulate(const evmc_message& msg, const ContractType& type); - - ///@{ - /** Wrapper for EVMC function. */ - bool account_exists(const evmc::address& addr) const noexcept final; - evmc::bytes32 get_storage(const evmc::address& addr, const evmc::bytes32& key) const noexcept final; - evmc_storage_status set_storage(const evmc::address& addr, const evmc::bytes32& key, const evmc::bytes32& value) noexcept final; - evmc::uint256be get_balance(const evmc::address& addr) const noexcept final; - size_t get_code_size(const evmc::address& addr) const noexcept final; - evmc::bytes32 get_code_hash(const evmc::address& addr) const noexcept final; - size_t copy_code(const evmc::address& addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) const noexcept final; - bool selfdestruct(const evmc::address& addr, const evmc::address& beneficiary) noexcept final; - evmc::Result call(const evmc_message& msg) noexcept final; - evmc_tx_context get_tx_context() const noexcept final; - evmc::bytes32 get_block_hash(int64_t number) const noexcept final; - void emit_log(const evmc::address& addr, const uint8_t* data, size_t data_size, const evmc::bytes32 topics[], size_t topics_count) noexcept final; - evmc_access_status access_account(const evmc::address& addr) noexcept final; - evmc_access_status access_storage(const evmc::address& addr, const evmc::bytes32& key) noexcept final; - evmc::bytes32 get_transient_storage(const evmc::address &addr, const evmc::bytes32 &key) const noexcept final; - void set_transient_storage(const evmc::address &addr, const evmc::bytes32 &key, const evmc::bytes32 &value) noexcept final; - ///@} - - // ====================================================================== - // CONTRACT INTERFACING FUNCTIONS - // ====================================================================== + decltype(auto) execute(concepts::Message auto&& msg) { + try { + mustRevert_ = false; + msg.gas().use(CONTRACT_EXECUTION_COST); + return dispatchMessage(std::forward(msg)); + } catch (const std::exception& err) { + mustRevert_ = true; + throw; + } + } /** * Call a contract view function based on the basic requirements of a contract call. @@ -342,69 +100,16 @@ class ContractHost : public evmc::Host { * @return The result of the view function. */ template - R callContractViewFunction(const BaseContract* caller, const Address& targetAddr, R(C::*func)(const Args&...) const, const Args&... args) const { - const auto recipientAccIt = this->accounts_.find(targetAddr); - if (recipientAccIt == this->accounts_.end()) { - throw DynamicException(std::string(__func__) + ": Contract Account does not exist - Type: " - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() - ); - } - const auto& recipientAcc = *recipientAccIt->second; - if (!recipientAcc.isContract()) { - throw DynamicException(std::string(__func__) + ": Contract does not exist - Type: " - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() - ); - } - NestedCallSafeGuard guard(caller, caller->caller_, caller->value_); - switch (recipientAcc.contractType) { - case ContractType::EVM : { - this->deduceGas(5000); - evmc_message msg; - msg.kind = EVMC_CALL; - msg.flags = EVMC_STATIC; - msg.depth = 1; - msg.gas = this->leftoverGas_; - msg.recipient = targetAddr.toEvmcAddress(); - msg.sender = caller->getContractAddress().toEvmcAddress(); - auto functionName = ContractReflectionInterface::getFunctionName(func); - if (functionName.empty()) { - throw DynamicException("ContractHost::callContractViewFunction: EVM contract function name is empty (contract not registered?)"); - } - auto functor = ABI::FunctorEncoder::encode(functionName); - Bytes fullData; - Utils::appendBytes(fullData, UintConv::uint32ToBytes(functor.value)); - if constexpr (sizeof...(Args) > 0) { - Utils::appendBytes(fullData, ABI::Encoder::encodeData(args...)); - } - msg.input_data = fullData.data(); - msg.input_size = fullData.size(); - msg.value = {}; - msg.create2_salt = {}; - msg.code_address = targetAddr.toEvmcAddress(); - // TODO: OMG this is so ugly, we need to fix this. - // A **CONST_CAST** is needed because we can't explicity tell the evmc_execute to do a view call. - // Regardless of that, we set flag = 1 to indicate that this is a view/STATIC call. - evmc::Result result (evmc_execute(this->vm_, &this->get_interface(), const_cast(this)->to_context(), - evmc_revision::EVMC_LATEST_STABLE_REVISION, &msg, recipientAcc.code.data(), recipientAcc.code.size())); - this->leftoverGas_ = result.gas_left; - if (result.status_code) { - auto hexResult = Hex::fromBytes(bytes::View(result.output_data, result.output_data + result.output_size)); - throw DynamicException("ContractHost::callContractViewFunction: EVMC call failed - Type: " - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() + " - Result: " + hexResult.get() - ); - } - return std::get<0>(ABI::Decoder::decodeData(bytes::View(result.output_data, result.output_data + result.output_size))); - } break; - case ContractType::CPP : { - this->deduceGas(1000); - const C* contract = this->getContract(targetAddr); - this->setContractVars(contract, caller->getContractAddress(), 0); - return (contract->*func)(args...); - } - default: { - throw DynamicException("PANIC! ContractHost::callContractViewFunction: Unknown contract type"); - } - } + R callContractViewFunction(const BaseContract* caller, const Address& targetAddr, R(C::*func)(const Args&...) const, const Args&... args) { + PackedStaticCallMessage msg( + caller->getContractAddress(), + targetAddr, + this->getCurrentGas(), + *caller, + func, + args...); + + return this->dispatchMessage(std::move(msg)); } /** @@ -424,145 +129,16 @@ class ContractHost : public evmc::Host { BaseContract* caller, const Address& targetAddr, const uint256_t& value, R(C::*func)(const Args&...), const Args&... args) { - if (this->isTracingCalls()) [[unlikely]] { - trace::Call callData; - - const uint64_t gas = leftoverGas_; - - callData.type = trace::Call::Type::CALL; - callData.from = caller->getContractAddress(); - callData.to = targetAddr; - callData.gas = gas; - - const std::string functionName = ContractReflectionInterface::getFunctionName(func); - - const BytesArr<4> encodedFunctor = - UintConv::uint32ToBytes(ABI::FunctorEncoder::encode(functionName).value); - - if constexpr (sizeof...(args) > 0) { - const Bytes encodedArgs = ABI::Encoder::encodeData(args...); - callData.input = Utils::makeBytes(bytes::join(encodedFunctor, encodedArgs)); - } else { - callData.input = Utils::makeBytes(encodedFunctor); - } - - callTracer_.callStarted(std::move(callData)); - - try { - if constexpr (std::same_as) { - callContractFunctionImpl(caller, targetAddr, value, func, args...); - callTracer_.callSucceeded(Bytes(), gas - leftoverGas_); - return; - } else { - R result = callContractFunctionImpl(caller, targetAddr, value, func, args...); - const uint64_t gasUsed = gas - leftoverGas_; - Bytes output = ABI::Encoder::encodeData(result); - callTracer_.callSucceeded(std::move(output), gasUsed); - return result; - } - } catch (const std::exception& err) { - Bytes output; - - if (err.what()) { - output = trace::encodeRevertReason(err.what()); - } - - callTracer_.callReverted(std::move(output), gas - leftoverGas_); - throw err; - } - } else [[likely]] { - return callContractFunctionImpl(caller, targetAddr, value, func, args...); - } - } - - /** - * Call a contract function. Used by DynamicContract to call other contracts. - * A given DynamicContract will only call another contract if triggered by a transaction. - * This will only be called if callContract() or validateCallContractWithTx() was called before. - * Implementation for @tparam ReturnType NOT being void - * @tparam R The return type of the function. - * @tparam C The contract type. - * @tparam Args The arguments types. - * @param caller Pointer to the contract that made the call. - * @param targetAddr The address of the contract to call. - * @param value Flag to indicate if the function is payable., - * @param func The function to call. - * @param args The arguments to pass to the function. - * @return The return value of the function. - */ - template - R callContractFunctionImpl( - BaseContract* caller, const Address& targetAddr, - const uint256_t& value, - R(C::*func)(const Args&...), const Args&... args - ) { - // 1000 Gas Limit for every C++ contract call! - auto& recipientAcc = *this->accounts_[targetAddr]; - if (!recipientAcc.isContract()) { - throw DynamicException(std::string(__func__) + ": Contract does not exist - Type: " - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() - ); - } - if (value) { - this->sendTokens(caller, targetAddr, value); - } - NestedCallSafeGuard guard(caller, caller->caller_, caller->value_); - switch (recipientAcc.contractType) { - case ContractType::EVM: { - this->deduceGas(10000); - evmc_message msg; - msg.kind = EVMC_CALL; - msg.flags = 0; - msg.depth = 1; - msg.gas = this->leftoverGas_; - msg.recipient = targetAddr.toEvmcAddress(); - msg.sender = caller->getContractAddress().toEvmcAddress(); - auto functionName = ContractReflectionInterface::getFunctionName(func); - if (functionName.empty()) { - throw DynamicException("ContractHost::callContractFunction: EVM contract function name is empty (contract not registered?)"); - } - auto functor = ABI::FunctorEncoder::encode(functionName); - Bytes fullData; - Utils::appendBytes(fullData, UintConv::uint32ToBytes(functor.value)); - if constexpr (sizeof...(Args) > 0) { - Utils::appendBytes(fullData, ABI::Encoder::encodeData(args...)); - } - msg.input_data = fullData.data(); - msg.input_size = fullData.size(); - msg.value = EVMCConv::uint256ToEvmcUint256(value); - msg.create2_salt = {}; - msg.code_address = targetAddr.toEvmcAddress(); - evmc::Result result (evmc_execute(this->vm_, &this->get_interface(), this->to_context(), - evmc_revision::EVMC_LATEST_STABLE_REVISION, &msg, recipientAcc.code.data(), recipientAcc.code.size())); - this->leftoverGas_ = result.gas_left; - if (result.status_code) { - auto hexResult = Hex::fromBytes(bytes::View(result.output_data, result.output_data + result.output_size)); - throw DynamicException("ContractHost::callContractFunction: EVMC call failed - Type: " - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() + " - Result: " + hexResult.get() - ); - } - if constexpr (std::same_as) { - return; - } else { - return std::get<0>(ABI::Decoder::decodeData(bytes::View(result.output_data, result.output_data + result.output_size))); - } - } break; - case ContractType::CPP: { - this->deduceGas(1000); - C* contract = this->getContract(targetAddr); - this->setContractVars(contract, caller->getContractAddress(), value); - try { - return contract->callContractFunction(this, func, args...); - } catch (const std::exception& e) { - throw DynamicException(e.what() + std::string(" - Type: ") - + Utils::getRealTypeName() + " at address: " + targetAddr.hex().get() - ); - } - } - default : { - throw DynamicException("PANIC! ContractHost::callContractFunction: Unknown contract type"); - } - } + PackedCallMessage msg( + caller->getContractAddress(), + targetAddr, + this->getCurrentGas(), + value, + *caller, + func, + args...); + + return this->dispatchMessage(std::move(msg)); } /** @@ -573,47 +149,18 @@ class ContractHost : public evmc::Host { * @param fullData The caller data. * @return The address of the new contract. */ - template Address callCreateContract(BaseContract* caller, const Bytes &fullData) { - // 100k gas limit for every contract creation! - this->deduceGas(50000); - evmc_message callInfo; - // evmc_message: - // struct evmc_message - // { - // enum evmc_call_kind kind; - // uint32_t flags; - // int32_t depth; - // int64_t gas; - // evmc_address recipient; - // evmc_address sender; - // const uint8_t* input_data; - // size_t input_size; - // evmc_uint256be value; - // evmc_bytes32 create2_salt; - // evmc_address code_address; - // }; - const auto& to = ProtocolContractAddresses.at("ContractManager"); - const auto& from = caller->getContractAddress(); - callInfo.flags = 0; - callInfo.depth = 1; - callInfo.gas = this->leftoverGas_; - callInfo.recipient = to.toEvmcAddress(); - callInfo.sender = from.toEvmcAddress(); - callInfo.input_data = fullData.data(); - callInfo.input_size = fullData.size(); - callInfo.value = {}; - callInfo.create2_salt = {}; - callInfo.code_address = to.toEvmcAddress(); - // Get the ContractManager from the this->accounts_ map - ContractManager* contractManager = dynamic_cast(this->contracts_.at(to).get()); - this->setContractVars(contractManager, from, 0); - auto& callerNonce = this->accounts_[from]->nonce; - Address newContractAddress = ContractHost::deriveContractAddress(callerNonce, from); - this->stack_.registerNonce(from, callerNonce); - NestedCallSafeGuard guard(caller, caller->caller_, caller->value_); - contractManager->ethCall(callInfo, this); - ++callerNonce; - return newContractAddress; + template + Address callCreateContract(BaseContract& caller, Args&&... args) { + const uint256_t value = 0; + PackedCreateMessage msg( + caller.getContractAddress(), + this->getCurrentGas(), + value, + caller, + std::forward(args)... + ); + + return this->dispatchMessage(std::move(msg)); } /** @@ -624,18 +171,15 @@ class ContractHost : public evmc::Host { * @return A pointer to the contract. * @throw DynamicException if contract is not found or not of the requested type. */ - template const T* getContract(const Address &address) const { - auto it = this->contracts_.find(address); - if (it == this->contracts_.end()) throw DynamicException( - "ContractManager::getContract: contract at address " + - address.hex().get() + " not found." - ); - auto ptr = dynamic_cast(it->second.get()); - if (ptr == nullptr) throw DynamicException( - "ContractManager::getContract: Contract at address " + - address.hex().get() + " is not of the requested type: " + Utils::getRealTypeName() - ); - return ptr; + template + const T* getContract(View
address) const { + const T* pointer = dynamic_cast(&context_.getContract(address)); + + if (pointer == nullptr) { + throw DynamicException("Wrong contract type"); + } + + return pointer; } /** @@ -646,93 +190,43 @@ class ContractHost : public evmc::Host { * @return A pointer to the contract. * @throw DynamicException if contract is not found or not of the requested type. */ - template T* getContract(const Address& address) { - auto it = this->contracts_.find(address); - if (it == this->contracts_.end()) throw DynamicException( - "ContractManager::getContract: contract at address " + - address.hex().get() + " not found." - ); - auto ptr = dynamic_cast(it->second.get()); - if (ptr == nullptr) throw DynamicException( - "ContractManager::getContract: Contract at address " + - address.hex().get() + " is not of the requested type: " + Utils::getRealTypeName() - ); - return ptr; - } - - /** - * Emit an event from a contract. Called by DynamicContract's emitEvent(). - * @param event The event to emit. - * @throw DynamicException if there's an attempt to emit the event outside a contract call. - */ - void emitContractEvent(Event&& event); - - /** - * Get the balance from a given address. Calls populateBalance(), so - * it's technically the same as getting the balance directly from State. - * Does NOT consider the current transaction being processed, if there is one. - * @param address The address to get the balance from. - * @return The balance of the address. - */ - uint256_t getBalanceFromAddress(const Address& address) const; + template + T* getContract(const Address& address) { + T* pointer = dynamic_cast(&context_.getContract(address)); - /** - * Send tokens to a given address. Used by DynamicContract to send tokens to other contracts. - * @param from The address from which the tokens will be sent from. - * @param to The address to send the tokens to. - * @param amount The amount of tokens to send. - */ - void sendTokens(const BaseContract* from, const Address& to, const uint256_t& amount); + if (pointer == nullptr) { + throw DynamicException("Wrong contract type"); + } - /** - * Get the current nonce of a given Account - * Returns a REFERENCE to the nonce, so it can be modified. - * @param acc The address of the account to get the nonce from. - */ - uint64_t& getNonce(const Address& acc); + return pointer; + } - /** - * Emit an event. - * @param name The event's name. - * @param contract The contract that emitted the event. - * @param args The event's arguments. - * @param anonymous Whether the event is anonymous or not. - */ - template void emitEvent( - const std::string& name, const Address& contract, - const std::tuple...>& args, bool anonymous + template + void emitEvent( + const std::string& name, + const Address& contract, + const std::tuple...>& args, + bool anonymous ) { - Event event(name, // EVM events do not have names - this->eventIndex_, this->txHash_, this->txIndex_, this->blockHash_, - this->currentTxContext_.block_number, contract, args, anonymous - ); - this->eventIndex_++; - this->stack_.registerEvent(std::move(event)); + context_.addEvent(name, contract, args, anonymous); } - /** - * Register a new C++ contract. - * @param addr The address where the contract will be deployed. - * @param contract The contract class to be created. - */ - void registerNewCPPContract(const Address& addr, BaseContract* contract); + ExecutionContext& context() { return context_; } - /** - * Register a new EVM contract. - * @param addr The address where the contract will be deployed. - * @param code The contract creation code to be used. - * @param codeSize The size of the contract creation code. - */ - void registerNewEVMContract(const Address& addr, const uint8_t* code, size_t codeSize); + const ExecutionContext& context() const { return context_; } - /** - * Register a new use of a given SafeVariable. - * @param variable Reference to the SafeVariable that was used. - */ - void registerVariableUse(SafeBase& variable); + void registerVariableUse(SafeBase& var) { stack_.registerVariableUse(var); } + + uint256_t getRandomValue(); + +private: + decltype(auto) dispatchMessage(auto&& msg) { + return messageHandler_.onMessage(std::forward(msg)); + } - /// Random value generator. - uint256_t getRandomValue() const { return this->randomGen_.operator()(); } + Gas& getCurrentGas() { + return messageHandler_.handler().cppExecutor().currentGas(); + } }; #endif // CONTRACT_HOST_H diff --git a/src/contract/contractmanager.cpp b/src/contract/contractmanager.cpp index 8cbf1081..666099a6 100644 --- a/src/contract/contractmanager.cpp +++ b/src/contract/contractmanager.cpp @@ -12,7 +12,7 @@ See the LICENSE.txt file in the project root for more information. #include "../core/rdpos.h" ContractManager::ContractManager( - const DB& db, boost::unordered_flat_map, SafeHash>& contracts, + const DB& db, boost::unordered_flat_map, SafeHash, SafeCompare>& contracts, DumpManager& manager, const Options& options ) : BaseContract("ContractManager", ProtocolContractAddresses.at("ContractManager"), options.getChainOwner(), options.getChainID()), contracts_(contracts) @@ -76,9 +76,15 @@ void ContractManager::ethCall(const evmc_message& callInfo, ContractHost* host) throw DynamicException("ContractManager: Invalid function call"); } it->second(callInfo, - ContractHost::deriveContractAddress(this->host_->getNonce(caller), caller), - this->contracts_, this->getContractChainId(), this->host_ - ); + generateContractAddress(this->host_->context().getAccount(caller).getNonce(), caller), + this->contracts_, + this->getContractChainId(), + this->host_); +} + +Bytes ContractManager::evmEthCall(const evmc_message& callInfo, ContractHost* host) { + this->ethCall(callInfo, host); + return Bytes(); } Bytes ContractManager::ethCallView(const evmc_message& callInfo, ContractHost* host) const { diff --git a/src/contract/contractmanager.h b/src/contract/contractmanager.h index 92904b79..5bf8dd0f 100644 --- a/src/contract/contractmanager.h +++ b/src/contract/contractmanager.h @@ -23,20 +23,20 @@ class ContractManager : public BaseContract { private: /// Reference of currently deployed contracts. /// Owned by the State - boost::unordered_flat_map, SafeHash>& contracts_; + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_; /// Functions to create contracts. boost::unordered_flat_map< - Functor, - std::function, SafeHash>& contracts_, - const uint64_t&, - ContractHost* - )>, - SafeHash - > createContractFuncs_; + Functor, + std::function< + void(const evmc_message&, + const Address&, + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts_, + const uint64_t&, + ContractHost* + )>, + SafeHash + > createContractFuncs_; /** * Get all deployed contracts. @@ -112,9 +112,9 @@ class ContractManager : public BaseContract { * @throw DynamicException if contract address doesn't exist in the database. */ ContractManager(const DB& db, - boost::unordered_flat_map, SafeHash>& contracts, - DumpManager& manager, const Options& options - ); + boost::unordered_flat_map, SafeHash, SafeCompare>& contracts, + DumpManager& manager, + const Options& options); ~ContractManager() override; ///< Destructor. Automatically saves contracts to the database before wiping them. @@ -128,6 +128,16 @@ class ContractManager : public BaseContract { */ void ethCall(const evmc_message& callInfo, ContractHost* host) override; + /** + * Override the default contract function call. + * ContractManager processes things in a non-standard way + * (you cannot use SafeVariables as contract creation actively writes to DB). + * @param callInfo The call info to process. + * @return The bytes from the call + * @throw DynamicException if the call is not valid. + */ + Bytes evmEthCall(const evmc_message& callInfo, ContractHost* host) override; + /** * Override the default contract view function call. * ContractManager process things in a non-standard way diff --git a/src/contract/contractstack.h b/src/contract/contractstack.h index 88eff572..d3a7b3aa 100644 --- a/src/contract/contractstack.h +++ b/src/contract/contractstack.h @@ -18,70 +18,13 @@ See the LICENSE.txt file in the project root for more information. */ class ContractStack { private: - boost::unordered_flat_map code_; ///< Map for address code fields. - boost::unordered_flat_map balance_; ///< Map for address balances. - boost::unordered_flat_map nonce_; ///< Map for address nonces. - boost::unordered_flat_map storage_; ///< Map for storage keys. - std::vector events_; ///< List of contract events in the stack. - std::vector> contracts_; ///< List of contracts that have been created during the execution of the call, so they can be reverted if the call reverts. - std::vector> usedVars_; ///< List of used SafeVars during the stack execution. + std::vector> usedVars_; public: - /** - * Register a new address code field in the stack. - * @param addr The address to register. - * @param code The code field to register. - */ - inline void registerCode(const Address& addr, const Bytes& code) { this->code_.try_emplace(addr, code); } + inline void registerVariableUse(SafeBase& var) { + this->usedVars_.emplace_back(var); + } - /** - * Register a new address balance in the stack. - * @param addr The address to register. - * @param balance The balance to register. - */ - inline void registerBalance(const Address& addr, const uint256_t& balance) { this->balance_.try_emplace(addr, balance); } - - /** - * Register a new address nonce in the stack. - * @param addr The address to register. - * @param nonce The nonce to register. - */ - inline void registerNonce(const Address& addr, const uint64_t& nonce) { this->nonce_.try_emplace(addr, nonce); } - - /** - * Register a new storage key change in the stack. - * @param key The storage key to register. - * @param value The storage key value to register. - */ - inline void registerStorageChange(const StorageKey& key, const Hash& value) { this->storage_.try_emplace(key, value); } - - /** - * Register a new emitted contract event in the stack. - * @param event The event to register. - */ - inline void registerEvent(Event event) { this->events_.emplace_back(std::move(event)); } - - /** - * Register a new contract creation in the stack. - * @param addr The contract address to register. - * @param contract The contract class to register. - */ - inline void registerContract(const Address& addr, BaseContract* contract) { this->contracts_.emplace_back(addr, contract); } - - /** - * Register a new used SafeVar in the stack. - * @param var The SafeVar to register. - */ - inline void registerVariableUse(SafeBase& var) { this->usedVars_.emplace_back(var); } - - ///@{ - /** Getter. */ - inline const boost::unordered_flat_map& getCode() const { return this->code_; } - inline const boost::unordered_flat_map& getBalance() const { return this->balance_; } - inline const boost::unordered_flat_map& getNonce() const { return this->nonce_; } - inline const boost::unordered_flat_map& getStorage() const { return this->storage_; } - inline std::vector& getEvents() { return this->events_; } - inline const std::vector>& getContracts() const { return this->contracts_; } inline const std::vector>& getUsedVars() const { return this->usedVars_; } ///@} }; diff --git a/src/contract/costs.h b/src/contract/costs.h new file mode 100644 index 00000000..bd93afa7 --- /dev/null +++ b/src/contract/costs.h @@ -0,0 +1,12 @@ +#ifndef BDK_CONTRACT_COSTS_H +#define BDK_CONTRACT_COSTS_H + +#include + +constexpr uint64_t CONTRACT_EXECUTION_COST = 21'000; +constexpr uint64_t EVM_CONTRACT_CREATION_COST = 100'000; +constexpr uint64_t CPP_CONTRACT_CREATION_COST = 50'000; +constexpr uint64_t CPP_CONTRACT_CALL_COST = 1'000; +constexpr uint64_t EVM_CONTRACT_CALL_COST = 5'000; + +#endif // BDK_CONTRACT_COSTS_H diff --git a/src/contract/cppcontractexecutor.h b/src/contract/cppcontractexecutor.h new file mode 100644 index 00000000..9242632b --- /dev/null +++ b/src/contract/cppcontractexecutor.h @@ -0,0 +1,170 @@ +#ifndef BDK_MESSAGES_CPPCONTRACTEXECUTOR_H +#define BDK_MESSAGES_CPPCONTRACTEXECUTOR_H + +#include "executioncontext.h" +#include "traits.h" +#include "bytes/cast.h" +#include "utils/evmcconv.h" +#include "utils/contractreflectioninterface.h" +#include "contract/costs.h" + +struct ContractHost; + +class CppContractExecutor { +public: + CppContractExecutor(ExecutionContext& context, ContractHost& host) + : context_(context), host_(host) {} + + template + auto execute(M&& msg) -> traits::MessageResult { + if constexpr (concepts::DelegateCallMessage) { + throw DynamicException("Delegate call not supported for C++ contracts"); + } + + auto guard = transactional::checkpoint(currentGas_); + currentGas_ = &msg.gas(); + + if constexpr (concepts::CreateMessage) { + return createContract(std::forward(msg)); + } else { + return callContract(std::forward(msg)); + } + } + + Gas& currentGas() { return *currentGas_; } + +private: + decltype(auto) callContract(concepts::PackedMessage auto&& msg) { + msg.gas().use(CPP_CONTRACT_CALL_COST); + auto& contract = getContractAs>(msg.to()); + + transactional::Group guard = { + transactional::checkpoint(contract.caller_), + transactional::checkpoint(contract.value_) + }; + + const Address caller(msg.from()); + const uint256_t value = messageValueOrZero(msg); + + contract.caller_ = caller; + contract.value_ = value; + + return std::apply([&] (auto&&... args) { + if constexpr (concepts::StaticCallMessage) { + return std::invoke(msg.method(), contract, std::forward(args)...); + } else { + return contract.callContractFunction(&host_, msg.method(), std::forward(args)...); + } + }, std::forward(msg).args()); + } + + decltype(auto) callContract(concepts::EncodedMessage auto&& msg) { + if (msg.to() == ProtocolContractAddresses.at("ContractManager")) [[unlikely]] { + msg.gas().use(CPP_CONTRACT_CREATION_COST); + } else [[likely]] { + msg.gas().use(CPP_CONTRACT_CALL_COST); + } + + const evmc_message evmcMsg{ + .kind = EVMC_CALL, + .flags = (concepts::StaticCallMessage ? EVMC_STATIC : evmc_flags(0)), + .depth = 0, + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(msg.to()), + .sender = bytes::cast(msg.from()), + .input_data = msg.input().data(), + .input_size = msg.input().size(), + .value = evmc_uint256be{}, + .create2_salt = evmc_bytes32{}, + .code_address = bytes::cast(msg.to()) + }; + + auto& contract = context_.getContract(msg.to()); + transactional::Group guard = { + transactional::checkpoint(contract.caller_), + transactional::checkpoint(contract.value_) + }; + Address caller(msg.from()); + uint256_t value = messageValueOrZero(msg); + + contract.caller_ = caller; + contract.value_ = value; + + return contract.evmEthCall(evmcMsg, &host_); + } + + Address createContract(concepts::CreateMessage auto&& msg) { + msg.gas().use(CPP_CONTRACT_CREATION_COST); + + using ContractType = traits::MessageContract; + + const std::string createSignature = "createNew" + + Utils::getRealTypeName() + + "Contract(" + + ContractReflectionInterface::getConstructorArgumentTypesString() + + ")"; + + // We only need the first 4 bytes for the function signature + Bytes fullData = Utils::makeBytes(Utils::sha3(View(bytes::view(createSignature))) | std::views::take(4)); + + std::apply(Utils::Overloaded{ + [] () {}, + [&fullData] (const auto&... args) { + Utils::appendBytes(fullData, ABI::Encoder::encodeData(args...)); + } + }, msg.args()); + + const Address& to = ProtocolContractAddresses.at("ContractManager"); + + const evmc_message evmcMsg{ + .kind = EVMC_CREATE, + .flags = 0, + .depth = 1, + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(to), + .sender = bytes::cast(msg.from()), + .input_data = fullData.data(), + .input_size = fullData.size(), + .value = EVMCConv::uint256ToEvmcUint256(msg.value()), + .create2_salt{}, + .code_address = bytes::cast(to) + }; + + auto& contract = context_.getContract(to); + + transactional::Group guard = { + transactional::checkpoint(contract.caller_), + transactional::checkpoint(contract.value_) + }; + + Address caller(msg.from()); + uint256_t value = 0; + + contract.caller_ = caller; + contract.value_ = value; + + auto account = context_.getAccount(msg.from()); + const Address contractAddress = generateContractAddress(account.getNonce(), msg.from()); + contract.ethCall(evmcMsg, &host_); + account.setNonce(account.getNonce() + 1); + + return contractAddress; + } + + template + C& getContractAs(View
address) { + C* ptr = dynamic_cast(&context_.getContract(address)); + + if (ptr == nullptr) { + throw DynamicException("Wrong contract type"); // TODO: add more info + } + + return *ptr; + } + + ExecutionContext& context_; + ContractHost& host_; + Gas *currentGas_ = nullptr; +}; + +#endif // BDK_MESSAGES_CPPCONTRACTEXECUTOR_H diff --git a/src/contract/dynamiccontract.h b/src/contract/dynamiccontract.h index c20cf5af..680d89c8 100644 --- a/src/contract/dynamiccontract.h +++ b/src/contract/dynamiccontract.h @@ -391,24 +391,20 @@ class DynamicContract : public BaseContract { Bytes evmEthCall(const evmc_message& callInfo, ContractHost* host) final { this->host_ = host; PointerNullifier nullifier(this->host_); - try { - Functor funcName = EVMCConv::getFunctor(callInfo); - if (this->isPayableFunction(funcName)) { - auto func = this->evmFunctions_.find(funcName); - if (func == this->evmFunctions_.end()) throw DynamicException("Functor not found for payable function"); - return func->second(callInfo); - } else { - // value is a uint8_t[32] C array, we need to check if it's zero in modern C++ - if (!evmc::is_zero(callInfo.value)) { - // If the value is not zero, we need to throw an exception - throw DynamicException("Non-payable function called with value"); - } - auto func = this->evmFunctions_.find(funcName); - if (func == this->evmFunctions_.end()) throw DynamicException("Functor not found for non-payable function"); - return func->second(callInfo); + Functor funcName = EVMCConv::getFunctor(callInfo); + if (this->isPayableFunction(funcName)) { + auto func = this->evmFunctions_.find(funcName); + if (func == this->evmFunctions_.end()) throw DynamicException("Functor not found for payable function"); + return func->second(callInfo); + } else { + // value is a uint8_t[32] C array, we need to check if it's zero in modern C++ + if (!evmc::is_zero(callInfo.value)) { + // If the value is not zero, we need to throw an exception + throw DynamicException("Non-payable function called with value"); } - } catch (const std::exception& e) { - throw DynamicException(e.what()); + auto func = this->evmFunctions_.find(funcName); + if (func == this->evmFunctions_.end()) throw DynamicException("Functor not found for non-payable function"); + return func->second(callInfo); } } @@ -422,14 +418,11 @@ class DynamicContract : public BaseContract { Bytes ethCallView(const evmc_message& data, ContractHost* host) const override { this->host_ = host; PointerNullifier nullifier(this->host_); - try { - Functor funcName = EVMCConv::getFunctor(data); - auto func = this->viewFunctions_.find(funcName); - if (func == this->viewFunctions_.end()) throw DynamicException("Functor not found"); - return func->second(data); - } catch (std::exception& e) { - throw DynamicException(e.what()); - } + Functor funcName = EVMCConv::getFunctor(data); + auto func = this->viewFunctions_.find(funcName); + if (func == this->viewFunctions_.end()) + throw DynamicException("Functor not found"); + return func->second(data); } /** @@ -586,17 +579,14 @@ class DynamicContract : public BaseContract { template R callContractFunction( ContractHost* contractHost, R (C::*func)(const Args&...), const Args&... args ) { - try { - // We don't want to ever overwrite the host_ pointer if it's already set (nested calls) - if (this->host_ == nullptr) { - this->host_ = contractHost; - PointerNullifier nullifier(this->host_); - return (static_cast(this)->*func)(args...); - } - return (static_cast(this)->*func)(args...); - } catch (const std::exception& e) { - throw DynamicException(e.what()); + // We don't want to ever overwrite the host_ pointer if it's already set (nested calls) + PointerNullifier nullifier(this->host_); + + if (this->host_ == nullptr) { + this->host_ = contractHost; } + + return (static_cast(this)->*func)(args...); } /** @@ -633,17 +623,7 @@ class DynamicContract : public BaseContract { if (this->host_ == nullptr) { throw DynamicException("Contracts going haywire! trying to create a contract without a host!"); } - std::string createSignature = "createNew" + Utils::getRealTypeName() + "Contract("; - // Append args - createSignature += ContractReflectionInterface::getConstructorArgumentTypesString(); - createSignature += ")"; - Bytes fullData; - Utils::appendBytes(fullData, Utils::sha3(Utils::create_view_span(createSignature))); - fullData.resize(4); // We only need the first 4 bytes for the function signature - if constexpr (sizeof...(Args) > 0) { - Utils::appendBytes(fullData, ABI::Encoder::encodeData(std::forward(args)...)); - } - return this->host_->callCreateContract(this, fullData); + return this->host_->callCreateContract(*this, std::forward(args)...); } /** @@ -655,7 +635,7 @@ class DynamicContract : public BaseContract { if (this->host_ == nullptr) { throw DynamicException("Contracts going haywire! trying to get balance without a host!"); } - return host_->getBalanceFromAddress(address); + return host_->context().getAccount(address).getBalance(); } /** @@ -667,7 +647,7 @@ class DynamicContract : public BaseContract { if (this->host_ == nullptr) { throw DynamicException("Contracts going haywire! trying to send tokens without a host!"); } - host_->sendTokens(this, to, amount); + host_->context().transferBalance(this->getContractAddress(), to, amount); } /** diff --git a/src/contract/encodedmessages.h b/src/contract/encodedmessages.h new file mode 100644 index 00000000..a3a3ed86 --- /dev/null +++ b/src/contract/encodedmessages.h @@ -0,0 +1,45 @@ +#ifndef BDK_ENCODEDMESSAGES_H +#define BDK_ENCODEDMESSAGES_H + +#include "contract/concepts.h" +#include "contract/basemessage.h" + +struct EncodedCallMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedStaticCallMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedCreateMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedSaltCreateMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedDelegateCallMessage : BaseMessage { + using BaseMessage::BaseMessage; +}; + +struct EncodedCallCodeMessage : EncodedCallMessage { + using EncodedCallMessage::EncodedCallMessage; +}; + +template<> +constexpr bool concepts::EnableDelegate = true; + +template<> +constexpr bool concepts::EnableCallCode = true; + +using EncodedMessageVariant = std::variant< + EncodedCreateMessage, + EncodedSaltCreateMessage, + EncodedCallMessage, + EncodedStaticCallMessage, + EncodedDelegateCallMessage +>; + +#endif // BDK_ENCODEDMESSAGES_H diff --git a/src/contract/event.cpp b/src/contract/event.cpp index 8540c687..47d6bf9e 100644 --- a/src/contract/event.cpp +++ b/src/contract/event.cpp @@ -6,6 +6,7 @@ See the LICENSE.txt file in the project root for more information. */ #include "event.h" +#include "bytes/hex.h" #include "../utils/uintconv.h" @@ -17,7 +18,7 @@ Event::Event(const std::string& jsonstr) { this->txIndex_ = obj["txIndex"].get(); this->blockHash_ = Hash(Hex::toBytes(std::string_view(obj["blockHash"].get()).substr(2))); this->blockIndex_ = obj["blockIndex"].get(); - this->address_ = Address(obj["address"].get(), false); + this->address_ = bytes::hex(obj["address"].get()); this->data_ = obj["data"].get(); for (std::string topic : obj["topics"]) this->topics_.emplace_back(Hex::toBytes(topic)); this->anonymous_ = obj["anonymous"].get(); diff --git a/src/contract/evmcontractexecutor.cpp b/src/contract/evmcontractexecutor.cpp new file mode 100644 index 00000000..ade52a04 --- /dev/null +++ b/src/contract/evmcontractexecutor.cpp @@ -0,0 +1,343 @@ +#include "evmcontractexecutor.h" + +#include "bytes/cast.h" +#include "bytes/hex.h" +#include "common.h" +#include "outofgas.h" +#include "utils/evmcconv.h" +#include "contract/costs.h" + +constexpr decltype(auto) getAndThen(auto&& map, const auto& key, auto&& andThen, auto&& orElse) { + const auto it = map.find(key); + + if (it == map.end()) { + return std::invoke(orElse); + } else { + return std::invoke(andThen, it->second); + } +} + +constexpr auto getAndThen(auto&& map, const auto& key, auto&& andThen) { + using Result = std::invoke_result_tsecond)>; + return getAndThen(map, key, andThen, [] () { return Result{}; }); +} + + +template +constexpr evmc_call_kind getEvmcKind(const M& msg) { + if constexpr (concepts::SaltMessage) { + return EVMC_CREATE2; + } else if (concepts::CreateMessage) { + return EVMC_CREATE; + } else if (concepts::DelegateCallMessage) { + return EVMC_DELEGATECALL; + } else { + return EVMC_CALL; + } + + std::unreachable(); +} + +template +constexpr evmc_flags getEvmcFlags(const M& msg) { + if constexpr (concepts::StaticCallMessage) { + return EVMC_STATIC; + } else { + return evmc_flags{}; + } +} + +template +constexpr evmc_message makeEvmcMessage(const M& msg, uint64_t depth) { + return evmc_message{ + .kind = getEvmcKind(msg), + .flags = getEvmcFlags(msg), + .depth = int32_t(depth), + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(messageRecipientOrDefault(msg)), + .sender = bytes::cast(msg.from()), + .input_data = msg.input().data(), + .input_size = msg.input().size(), + .value = EVMCConv::uint256ToEvmcUint256(messageValueOrZero(msg)), + .create2_salt = evmc_bytes32{}, + .code_address = bytes::cast(messageCodeAddress(msg)) + }; +} + +template +constexpr evmc_message makeEvmcMessage(const M& msg, uint64_t depth, View
contractAddress) { + return evmc_message{ + .kind = getEvmcKind(msg), + .flags = 0, + .depth = int32_t(depth), + .gas = int64_t(msg.gas()), + .recipient = bytes::cast(contractAddress), + .sender = bytes::cast(msg.from()), + .input_data = nullptr, + .input_size = 0, + .value = EVMCConv::uint256ToEvmcUint256(messageValueOrZero(msg)), + .create2_salt = bytes::cast(messageSaltOrDefault(msg)), + .code_address = evmc_address{} + }; +} + +static Bytes executeEvmcMessage(evmc_vm* vm, const evmc_host_interface* host, evmc_host_context* context, const evmc_message& msg, Gas& gas, View code) { + evmc::Result result(::evmc_execute( + vm, + host, + context, + evmc_revision::EVMC_LATEST_STABLE_REVISION, + &msg, + code.data(), + code.size())); + + gas = Gas(result.gas_left); + + if (result.status_code == EVMC_SUCCESS) { + return Bytes(result.output_data, result.output_data + result.output_size); + } else if (result.status_code == EVMC_OUT_OF_GAS) { + throw OutOfGas(); + } else { + std::string reason = ""; + + if (result.output_size > 0) { + reason = ABI::Decoder::decodeError(View(result.output_data, result.output_size)); + } + + throw DynamicException(reason); + } +} + +static void createContractImpl(auto& msg, ExecutionContext& context, View
contractAddress, evmc_vm *vm, evmc::Host& host, uint64_t depth) { + Bytes code = executeEvmcMessage(vm, &evmc::Host::get_interface(), host.to_context(), + makeEvmcMessage(msg, depth, contractAddress), msg.gas(), msg.code()); + + auto account = context.getAccount(contractAddress); + account.setNonce(1); + account.setCode(std::move(code)); + account.setContractType(ContractType::EVM); + account.setBalance(account.getBalance() + msg.value()); + context.notifyNewContract(contractAddress, nullptr); +} + +Bytes EvmContractExecutor::execute(EncodedCallMessage& msg) { + msg.gas().use(EVM_CONTRACT_CALL_COST); + auto depthGuard = transactional::copy(depth_); // TODO: checkpoint (and deprecate copy) + ++depth_; + + const Bytes output = executeEvmcMessage(this->vm_, &this->get_interface(), this->to_context(), + makeEvmcMessage(msg, depth_), msg.gas(), context_.getAccount(msg.to()).getCode()); + + return output; +} + +Bytes EvmContractExecutor::execute(EncodedStaticCallMessage& msg) { + msg.gas().use(EVM_CONTRACT_CALL_COST); + View code = context_.getAccount(msg.to()).getCode(); + + auto depthGuard = transactional::copy(depth_); + ++depth_; + + return executeEvmcMessage(this->vm_, &this->get_interface(), this->to_context(), + makeEvmcMessage(msg, depth_), msg.gas(), code); +} + +Bytes EvmContractExecutor::execute(EncodedDelegateCallMessage& msg) { + msg.gas().use(EVM_CONTRACT_CALL_COST); + auto depthGuard = transactional::copy(depth_); + ++depth_; + + const Bytes output = executeEvmcMessage(this->vm_, &this->get_interface(), this->to_context(), + makeEvmcMessage(msg, depth_), msg.gas(), context_.getAccount(msg.codeAddress()).getCode()); + + return output; +} + +Address EvmContractExecutor::execute(EncodedCreateMessage& msg) { + msg.gas().use(EVM_CONTRACT_CREATION_COST); + auto depthGuard = transactional::copy(depth_); + auto account = context_.getAccount(msg.from()); + const Address contractAddress = generateContractAddress(account.getNonce(), msg.from()); + createContractImpl(msg, context_, contractAddress, vm_, *this, ++depth_); + account.setNonce(account.getNonce() + 1); + return contractAddress; +} + +Address EvmContractExecutor::execute(EncodedSaltCreateMessage& msg) { + msg.gas().use(EVM_CONTRACT_CREATION_COST); + auto depthGuard = transactional::copy(depth_); + const Address contractAddress = generateContractAddress(msg.from(), msg.salt(), msg.code()); + createContractImpl(msg, context_, contractAddress, vm_, *this, ++depth_); + return contractAddress; +} + +bool EvmContractExecutor::account_exists(const evmc::address& addr) const noexcept { + return context_.accountExists(addr); +} + +evmc::bytes32 EvmContractExecutor::get_storage(const evmc::address& addr, const evmc::bytes32& key) const noexcept { + return bytes::cast(context_.retrieve(addr, key)); +} + +evmc_storage_status EvmContractExecutor::set_storage(const evmc::address& addr, const evmc::bytes32& key, const evmc::bytes32& value) noexcept { + context_.store(addr, key, value); + return EVMC_STORAGE_MODIFIED; +} + +evmc::uint256be EvmContractExecutor::get_balance(const evmc::address& addr) const noexcept { + try { + return EVMCConv::uint256ToEvmcUint256(context_.getAccount(addr).getBalance()); + } catch (const std::exception&) { + return evmc::uint256be{}; + } +} + +size_t EvmContractExecutor::get_code_size(const evmc::address& addr) const noexcept { + try { + return context_.getAccount(addr).getCode().size(); + } catch (const std::exception&) { + return 0; + } +} + +evmc::bytes32 EvmContractExecutor::get_code_hash(const evmc::address& addr) const noexcept { + try { + return bytes::cast(context_.getAccount(addr).getCodeHash()); + } catch (const std::exception&) { + return evmc::bytes32{}; + } +} + +size_t EvmContractExecutor::copy_code(const evmc::address& addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) const noexcept { + + try { + View code = context_.getAccount(addr).getCode(); + + if (code_offset < code.size()) { + const auto n = std::min(buffer_size, code.size() - code_offset); + if (n > 0) + std::copy_n(&code[code_offset], n, buffer_data); + return n; + } + + } catch (const std::exception&) { + // TODO: makes sense to just ignore? + } + + return 0; +} + +bool EvmContractExecutor::selfdestruct(const evmc::address& addr, const evmc::address& beneficiary) noexcept { + // SELFDESTRUCT is not allowed in the current implementation + return false; +} + +evmc_tx_context EvmContractExecutor::get_tx_context() const noexcept { + return evmc_tx_context{ + .tx_gas_price = EVMCConv::uint256ToEvmcUint256(context_.getTxGasPrice()), + .tx_origin = bytes::cast(context_.getTxOrigin()), + .block_coinbase = bytes::cast(context_.getBlockCoinbase()), + .block_number = context_.getBlockNumber(), + .block_timestamp = context_.getBlockTimestamp(), + .block_gas_limit = context_.getBlockGasLimit(), + .block_prev_randao = {}, + .chain_id = EVMCConv::uint256ToEvmcUint256(context_.getChainId()), + .block_base_fee = {}, + .blob_base_fee = {}, + .blob_hashes = nullptr, + .blob_hashes_count = 0 + }; +} + +evmc::bytes32 EvmContractExecutor::get_block_hash(int64_t number) const noexcept { + return EVMCConv::uint256ToEvmcUint256(number); +} + +void EvmContractExecutor::emit_log(const evmc::address& addr, const uint8_t* data, size_t dataSize, const evmc::bytes32 topics[], size_t topicsCount) noexcept { + try { + // We need the following arguments to build a event: + // (std::string) name The event's name. + // (uint64_t) logIndex The event's position on the block. + // (Hash) txHash The hash of the transaction that emitted the event. + // (uint64_t) txIndex The position of the transaction in the block. + // (Hash) blockHash The hash of the block that emitted the event. + // (uint64_t) blockIndex The height of the block. + // (Address) address The address that emitted the event. + // (Bytes) data The event's arguments. + // (std::vector) topics The event's indexed arguments. + // (bool) anonymous Whether the event is anonymous or not. + std::vector topicsVec; + topicsVec.reserve(topicsCount); + for (uint64_t i = 0; i < topicsCount; i++) { + topicsVec.emplace_back(topics[i]); + } + + context_.addEvent(addr, View(data, dataSize), std::move(topicsVec)); + + } catch (const std::exception& ignored) { + // TODO: log errors + } +} + +evmc_access_status EvmContractExecutor::access_account(const evmc::address& addr) noexcept { + return EVMC_ACCESS_WARM; +} + +evmc_access_status EvmContractExecutor::access_storage(const evmc::address& addr, const evmc::bytes32& key) noexcept { + return EVMC_ACCESS_WARM; +} + +evmc::bytes32 EvmContractExecutor::get_transient_storage(const evmc::address &addr, const evmc::bytes32 &key) const noexcept { + return getAndThen(transientStorage_, StorageKeyView(addr, key), [] (const auto& result) { return bytes::cast(result); }); +} + +void EvmContractExecutor::set_transient_storage(const evmc::address &addr, const evmc::bytes32 &key, const evmc::bytes32 &value) noexcept { + // TODO: This also must be controlled by transactions + transientStorage_.emplace(StorageKeyView{addr, key}, value); +} + +evmc::Result EvmContractExecutor::call(const evmc_message& msg) noexcept { + Gas gas(msg.gas); + const uint256_t value = EVMCConv::evmcUint256ToUint256(msg.value); + + const auto process = [&] (auto& msg) { + try { + const auto output = messageHandler_.onMessage(msg); + + if constexpr (concepts::CreateMessage) { + return evmc::Result(EVMC_SUCCESS, int64_t(gas), 0, bytes::cast(output)); + } else { + return evmc::Result(EVMC_SUCCESS, int64_t(gas), 0, output.data(), output.size()); + } + } catch (const OutOfGas&) { // TODO: ExecutionReverted exception is important + return evmc::Result(EVMC_OUT_OF_GAS); + } catch (const std::exception& err) { + Bytes output; + + if (err.what() != nullptr) { + output = ABI::Encoder::encodeError(err.what()); // TODO: this may throw... + } + + return evmc::Result(EVMC_REVERT, int64_t(gas), 0, output.data(), output.size()); + } + }; + + if (msg.kind == EVMC_DELEGATECALL) { + EncodedDelegateCallMessage encodedMessage(msg.sender, msg.recipient, gas, value, View(msg.input_data, msg.input_size), msg.code_address); + return process(encodedMessage); + } else if (msg.kind == EVMC_CALL && msg.flags == EVMC_STATIC) { + EncodedStaticCallMessage encodedMessage(msg.sender, msg.recipient, gas, View(msg.input_data, msg.input_size)); + return process(encodedMessage); + } else if (msg.kind == EVMC_CALL) { + EncodedCallMessage encodedMessage(msg.sender, msg.recipient, gas, value, View(msg.input_data, msg.input_size)); + return process(encodedMessage); + } else if (msg.kind == EVMC_CREATE) { + EncodedCreateMessage encodedMessage(msg.sender, gas, value, View(msg.input_data, msg.input_size)); + return process(encodedMessage); + } else if (msg.kind == EVMC_CREATE2) { + EncodedSaltCreateMessage encodedMessage(msg.sender, gas, value, View(msg.input_data, msg.input_size), msg.create2_salt); + return process(encodedMessage); + } + + std::unreachable(); +} diff --git a/src/contract/evmcontractexecutor.h b/src/contract/evmcontractexecutor.h new file mode 100644 index 00000000..2c8b0fff --- /dev/null +++ b/src/contract/evmcontractexecutor.h @@ -0,0 +1,99 @@ +#ifndef BDK_MESSAGES_EVMCONTRACTEXECUTOR_H +#define BDK_MESSAGES_EVMCONTRACTEXECUTOR_H + +#include +#include "utils/hash.h" +#include "utils/contractreflectioninterface.h" +#include "contract/contractstack.h" +#include "anyencodedmessagehandler.h" +#include "executioncontext.h" +#include "traits.h" + +class EvmContractExecutor : public evmc::Host { +public: + EvmContractExecutor( + AnyEncodedMessageHandler messageHandler, ExecutionContext& context, evmc_vm *vm) + : messageHandler_(messageHandler), context_(context), vm_(vm), transientStorage_(), depth_(0) {} + + EvmContractExecutor(ExecutionContext& context, evmc_vm *vm) + : context_(context), vm_(vm), transientStorage_(), depth_(0) {} + + void setMessageHandler(AnyEncodedMessageHandler messageHandler) { messageHandler_ = messageHandler; } + + Bytes execute(EncodedCallMessage& msg); + + Bytes execute(EncodedStaticCallMessage& msg); + + Bytes execute(EncodedDelegateCallMessage& msg); + + template + requires concepts::CallMessage + auto execute(M&& msg) -> traits::MessageResult { + const Bytes input = messageInputEncoded(msg); + Bytes output; + + if constexpr (concepts::StaticCallMessage) { + EncodedStaticCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), input); + output = this->execute(encodedMessage); + } else if constexpr (concepts::DelegateCallMessage) { + EncodedDelegateCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), msg.value(), input, msg.codeAddress()); + output = this->execute(encodedMessage); + } else { + EncodedCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), msg.value(), input); + output = this->execute(encodedMessage); + } + + if constexpr (not std::same_as, void>) { + return std::get<0>(ABI::Decoder::decodeData>(output)); + } + } + + Address execute(EncodedCreateMessage& msg); + + Address execute(EncodedSaltCreateMessage& msg); + + decltype(auto) execute(auto&& msg) { + return execute(msg); + } + + bool account_exists(const evmc::address& addr) const noexcept override final; + + evmc::bytes32 get_storage(const evmc::address& addr, const evmc::bytes32& key) const noexcept override final; + + evmc_storage_status set_storage(const evmc::address& addr, const evmc::bytes32& key, const evmc::bytes32& value) noexcept override final; + + evmc::uint256be get_balance(const evmc::address& addr) const noexcept override final; + + size_t get_code_size(const evmc::address& addr) const noexcept override final; + + evmc::bytes32 get_code_hash(const evmc::address& addr) const noexcept override final; + + size_t copy_code(const evmc::address& addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) const noexcept override final; + + bool selfdestruct(const evmc::address& addr, const evmc::address& beneficiary) noexcept override final; + + evmc_tx_context get_tx_context() const noexcept override final; + + evmc::bytes32 get_block_hash(int64_t number) const noexcept override final; + + void emit_log(const evmc::address& addr, const uint8_t* data, size_t data_size, const evmc::bytes32 topics[], size_t topics_count) noexcept override final; + + evmc_access_status access_account(const evmc::address& addr) noexcept override final; + + evmc_access_status access_storage(const evmc::address& addr, const evmc::bytes32& key) noexcept override final; + + evmc::bytes32 get_transient_storage(const evmc::address &addr, const evmc::bytes32 &key) const noexcept override final; + + void set_transient_storage(const evmc::address &addr, const evmc::bytes32 &key, const evmc::bytes32 &value) noexcept override final; + + evmc::Result call(const evmc_message& msg) noexcept override final; + +private: + AnyEncodedMessageHandler messageHandler_; + ExecutionContext& context_; + evmc_vm *vm_; + boost::unordered_flat_map transientStorage_; + uint64_t depth_; +}; + +#endif // BDK_MESSAGES_EVMCONTRACTEXECUTOR_H diff --git a/src/contract/executioncontext.cpp b/src/contract/executioncontext.cpp new file mode 100644 index 00000000..57e4201c --- /dev/null +++ b/src/contract/executioncontext.cpp @@ -0,0 +1,196 @@ +#include "executioncontext.h" + +void ExecutionContext::addEvent(Event event) { + transactional::AnyTransactional emplaceTransaction(transactional::emplaceBack(events_, std::move(event))); + transactions_.push(std::move(emplaceTransaction)); +} + +void ExecutionContext::addEvent(View
address, View data, std::vector topics) { + this->addEvent(Event("", events_.size(), txHash_, txIndex_, blockHash_, blockNumber_, Address(address), Bytes(data), std::move(topics), !topics.empty())); +} + + +ExecutionContext::AccountPointer ExecutionContext::getAccount(View
accountAddress) { + return ExecutionContext::AccountPointer(*accounts_[accountAddress], transactions_); +} + +BaseContract& ExecutionContext::getContract(View
contractAddress) { + const auto it = contracts_.find(contractAddress); + + if (it == contracts_.end()) { + throw DynamicException("contract not found"); + } + + if (it->second == nullptr) { + throw DynamicException("not a C++ contract"); + } + + return *it->second; +} + +const BaseContract& ExecutionContext::getContract(View
contractAddress) const { + const auto it = contracts_.find(contractAddress); + + if (it == contracts_.end()) { + throw DynamicException("contract not found"); + } + + if (it->second == nullptr) { + throw DynamicException("not a C++ contract"); + } + + return *it->second; +} + +Account& ExecutionContext::getMutableAccount(View
accountAddress) { + const auto iterator = accounts_.find(accountAddress); + + if (iterator == accounts_.end()) { + throw DynamicException("account not found"); + } + + return *iterator->second; +} + +bool ExecutionContext::accountExists(View
accountAddress) const { + return accounts_.contains(accountAddress); +} + +void ExecutionContext::addContract(View
address, std::unique_ptr contract) { + if (contract == nullptr) { + throw DynamicException("attempt to insert null contract"); + } + + const auto [iterator, inserted] = contracts_.emplace(address, std::move(contract)); + + if (!inserted) { + throw DynamicException("contract already exists"); + } + + auto account = getAccount(address); + account.setNonce(1); + account.setContractType(ContractType::CPP); + + notifyNewContract(iterator->first, iterator->second.get()); +} + +void ExecutionContext::notifyNewContract(View
address, BaseContract* contract) { + using ContractsTuple = std::tuple>&>; + + newContracts_.emplace_back(address, contract); + + transactions_.push(transactional::AnyTransactional(transactional::BasicTransactional(contracts_, [contractAddress = Address(address)] (auto& contracts) { + contracts.erase(contractAddress); + }))); + + transactions_.push(transactional::AnyTransactional(transactional::BasicTransactional(newContracts_, [] (auto& newContracts) { + newContracts.pop_back(); + }))); +} + +void ExecutionContext::transferBalance(View
fromAddress, View
toAddress, const uint256_t& amount) { + auto sender = getAccount(fromAddress); + auto recipient = getAccount(toAddress); + + if (sender.getBalance() < amount) { + throw DynamicException("insufficient founds"); + } + + sender.setBalance(sender.getBalance() - amount); + recipient.setBalance(recipient.getBalance() + amount); +} + +void ExecutionContext::store(View
addr, View slot, View data) { + transactional::AnyTransactional transaction(transactional::emplaceOrAssign(storage_, StorageKeyView(addr, slot), data)); + transactions_.push(std::move(transaction)); +} + +Hash ExecutionContext::retrieve(View
addr, View slot) const { + const auto iterator = storage_.find(StorageKeyView(addr, slot)); + return (iterator == storage_.end()) ? Hash() : iterator->second; +} + +void ExecutionContext::commit() { + while (!transactions_.empty()) { + transactions_.top().commit(); + transactions_.pop(); + } + + events_.clear(); + newContracts_.clear(); +} + +void ExecutionContext::revert() { + while (!transactions_.empty()) { + transactions_.pop(); // transactions revert on destructor (by default) + } + + events_.clear(); + newContracts_.clear(); +} + +ExecutionContext::Checkpoint ExecutionContext::checkpoint() { + return ExecutionContext::Checkpoint(transactions_); +} + +ExecutionContext::AccountPointer::AccountPointer(Account& account, std::stack& transactions) + : account_(account), transactions_(transactions) {} + +const uint256_t& ExecutionContext::AccountPointer::getBalance() const{ + return account_.balance; +} + +uint64_t ExecutionContext::AccountPointer::getNonce() const { + return account_.nonce; +} + +View ExecutionContext::AccountPointer::getCodeHash() const { + return account_.codeHash; +} + +View ExecutionContext::AccountPointer::getCode() const { + return account_.code; +} + +ContractType ExecutionContext::AccountPointer::getContractType() const { + return account_.contractType; +} + +void ExecutionContext::AccountPointer::setBalance(const uint256_t& amount) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.balance))); + account_.balance = amount; +} + +void ExecutionContext::AccountPointer::setNonce(uint64_t nonce) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.nonce))); + account_.nonce = nonce; +} + +void ExecutionContext::AccountPointer::setCode(Bytes code) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.codeHash))); + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.code))); + account_.codeHash = Utils::sha3(code); + account_.code = std::move(code); +} + +void ExecutionContext::AccountPointer::setContractType(ContractType type) { + transactions_.push(transactional::AnyTransactional(transactional::copy(account_.contractType))); + account_.contractType = type; +} + +ExecutionContext::Checkpoint::Checkpoint(std::stack& transactions) + : transactions_(&transactions), checkpoint_(transactions.size()) {} + +void ExecutionContext::Checkpoint::commit() { + transactions_ = nullptr; +} + +void ExecutionContext::Checkpoint::revert() { + if (transactions_ == nullptr) + return; + + while (transactions_->size() > checkpoint_) + transactions_->pop(); + + transactions_ = nullptr; +} diff --git a/src/contract/executioncontext.h b/src/contract/executioncontext.h new file mode 100644 index 00000000..6b3b94d6 --- /dev/null +++ b/src/contract/executioncontext.h @@ -0,0 +1,226 @@ +#ifndef BDK_EXECUTIONCONTEXT_H +#define BDK_EXECUTIONCONTEXT_H + +#include +#include "utils/hash.h" +#include "utils/address.h" +#include "utils/utils.h" +#include "utils/transactional.h" +#include "utils/safehash.h" +#include "contract/contract.h" +#include "contract/event.h" + +class ExecutionContext { +public: + using Storage = boost::unordered_flat_map; + using Accounts = boost::unordered_flat_map, SafeHash, SafeCompare>; + using Contracts = boost::unordered_flat_map, SafeHash, SafeCompare>; + + class Checkpoint; + + class Builder; + + class AccountPointer; + + ExecutionContext( + Accounts& accounts, Storage& storage, Contracts& contracts, + int64_t blockGasLimit, int64_t blockNumber, int64_t blockTimestamp, int64_t txIndex, + View
blockCoinbase, View
txOrigin, View blockHash, View txHash, + const uint256_t& chainId, const uint256_t& txGasPrice) : + accounts_(accounts), storage_(storage), contracts_(contracts), newContracts_(), + blockGasLimit_(blockGasLimit), blockNumber_(blockNumber), blockTimestamp_(blockTimestamp), txIndex_(txIndex), + blockCoinbase_(blockCoinbase), txOrigin_(txOrigin), blockHash_(blockHash), txHash_(txHash), + chainId_(chainId), txGasPrice_(txGasPrice) {} + + ~ExecutionContext() { revert(); } + + ExecutionContext(const ExecutionContext&) = delete; + + ExecutionContext(ExecutionContext&&) = delete; + + ExecutionContext& operator=(const ExecutionContext&) = delete; + + ExecutionContext& operator=(ExecutionContext&&) = delete; + + View getBlockHash() const { return blockHash_; } + + View getTxHash() const { return txHash_; } + + View
getTxOrigin() const { return txOrigin_; } + + View
getBlockCoinbase() const { return blockCoinbase_; } + + int64_t getBlockGasLimit() const { return blockGasLimit_; } + + int64_t getBlockNumber() const { return blockNumber_; } + + int64_t getBlockTimestamp() const { return blockTimestamp_; } + + int64_t getTxIndex() const { return txIndex_; } + + const uint256_t& getTxGasPrice() const { return txGasPrice_; } + + const uint256_t& getChainId() const { return chainId_; } + + void addEvent(Event event); + + void addEvent(std::string_view name, View
address, const auto& args, bool anonymous) { + Event event(std::string(name), events_.size(), Hash(this->getTxHash()), this->getTxIndex(), Hash(this->getBlockHash()), + this->getBlockNumber(), Address(address), args, anonymous); + + this->addEvent(std::move(event)); + } + + void addEvent(View
address, View data, std::vector topics); + + bool accountExists(View
accountAddress) const; + + AccountPointer getAccount(View
accountAddress); + + BaseContract& getContract(View
contractAddress); + + const BaseContract& getContract(View
contractAddress) const; + + const auto& getEvents() const { return events_; } + + const auto& getNewContracts() const { return newContracts_; } + + void addContract(View
address, std::unique_ptr contract); + + void notifyNewContract(View
address, BaseContract* contract); + + void transferBalance(View
fromAddress, View
toAddress, const uint256_t& amount); + + void store(View
addr, View slot, View data); + + Hash retrieve(View
addr, View slot) const; + + void commit(); + + void revert(); + + Checkpoint checkpoint(); + +private: + Account& getMutableAccount(View
accountAddress); + + Accounts& accounts_; + Storage& storage_; + Contracts& contracts_; + int64_t blockGasLimit_; + int64_t blockNumber_; + int64_t blockTimestamp_; + int64_t txIndex_; + Address blockCoinbase_; + Address txOrigin_; + Hash blockHash_; + Hash txHash_; + uint256_t chainId_; + uint256_t txGasPrice_; + size_t eventIndex_ = 0; + std::vector events_; + std::vector> newContracts_; + std::stack transactions_; +}; + +class ExecutionContext::AccountPointer { +public: + AccountPointer(Account& account, std::stack& transactions); + + const uint256_t& getBalance() const; + + uint64_t getNonce() const; + + View getCodeHash() const; + + View getCode() const; + + ContractType getContractType() const; + + void setBalance(const uint256_t& amount); + + void setNonce(uint64_t nonce); + + void setCode(Bytes code); + + void setContractType(ContractType type); + +private: + Account& account_; + std::stack& transactions_; +}; + +class ExecutionContext::Checkpoint { +public: + explicit Checkpoint(std::stack& transactions); + + Checkpoint(const Checkpoint&) = delete; + Checkpoint(Checkpoint&&) noexcept = delete; + Checkpoint& operator=(const Checkpoint&) = delete; + Checkpoint& operator=(Checkpoint&&) noexcept = delete; + + ~Checkpoint() { revert(); } + + void commit(); + + void revert(); + +private: + std::stack* transactions_; + size_t checkpoint_; +}; + +class ExecutionContext::Builder { +public: + + Builder() {} + + Builder& storage(ExecutionContext::Storage& storage) { storage_ = &storage; return *this; } + + Builder& accounts(ExecutionContext::Accounts& accounts) { accounts_ = &accounts; return *this; } + + Builder& contracts(ExecutionContext::Contracts& contracts) { contracts_ = &contracts; return *this; } + + Builder& blockHash(View blockHash) { blockHash_ = Hash(blockHash); return *this; } + + Builder& txHash(View txHash) { txHash_ = Hash(txHash); return *this; } + + Builder& txOrigin(View
txOrigin) { txOrigin_ = Address(txOrigin); return *this; } + + Builder& blockCoinbase(View
blockCoinbase) { blockCoinbase_ = Address(blockCoinbase); return *this; } + + Builder& txIndex(int64_t txIndex) { txIndex_ = txIndex; return *this; } + + Builder& blockNumber(int64_t blockNumber) { blockNumber_ = blockNumber; return *this; } + + Builder& blockTimestamp(int64_t blockTimestamp) { blockTimestamp_ = blockTimestamp; return *this; } + + Builder& blockGasLimit(int64_t blockGasLimit) { blockGasLimit_ = blockGasLimit; return *this; } + + Builder& txGasPrice(const uint256_t& txGasPrice) { txGasPrice_ = txGasPrice; return *this; } + + Builder& chainId(const uint256_t& chainId) { chainId_ = chainId; return *this; } + + ExecutionContext build() { + return ExecutionContext( + *accounts_, *storage_, *contracts_, blockGasLimit_, blockNumber_, blockTimestamp_, txIndex_, + blockCoinbase_, txOrigin_, blockHash_, txHash_, chainId_, txGasPrice_); + } + +private: + ExecutionContext::Accounts* accounts_; + ExecutionContext::Storage* storage_; + ExecutionContext::Contracts* contracts_; + int64_t blockGasLimit_; + int64_t blockNumber_; + int64_t blockTimestamp_; + int64_t txIndex_; + Address blockCoinbase_; + Address txOrigin_; + Hash blockHash_; + Hash txHash_; + uint256_t chainId_; + uint256_t txGasPrice_; +}; + +#endif // BDK_EXECUTIONCONTEXT_H diff --git a/src/contract/executionreverted.h b/src/contract/executionreverted.h new file mode 100644 index 00000000..05fce1e7 --- /dev/null +++ b/src/contract/executionreverted.h @@ -0,0 +1,15 @@ +#ifndef BDK_MESSAGES_EXECUTIONREVERTED_H +#define BDK_MESSAGES_EXECUTIONREVERTED_H + +#include "utils/dynamicexception.h" + +namespace messages { + +struct ExecutionReverted : std::runtime_error { + explicit ExecutionReverted(std::string_view msg) + : std::runtime_error(str.data()) {} +}; + +} // namespace messages + +#endif // BDK_MESSAGES_EXECUTIONREVERTED_H diff --git a/src/contract/gas.h b/src/contract/gas.h new file mode 100644 index 00000000..a98ce3de --- /dev/null +++ b/src/contract/gas.h @@ -0,0 +1,33 @@ +#ifndef BDK_MESSAGES_GAS_H +#define BDK_MESSAGES_GAS_H + +#include "outofgas.h" + +class Gas { +public: + constexpr explicit Gas(uint256_t value = 0) : value_(value) {} + + constexpr void use(const uint256_t& amount) { + if (amount > value_) { + value_ = 0; + throw OutOfGas(); + } + + value_ -= amount; + } + + constexpr void refund(const uint256_t& amount) { + value_ += amount; + } + + template + requires std::constructible_from + explicit constexpr operator T() const { + return static_cast(value_); + } + +private: + uint256_t value_; +}; + +#endif // BDK_MESSAGES_GAS_H diff --git a/src/contract/messagedispatcher.h b/src/contract/messagedispatcher.h new file mode 100644 index 00000000..33baa3ca --- /dev/null +++ b/src/contract/messagedispatcher.h @@ -0,0 +1,107 @@ +#ifndef BDK_MESSAGES_MESSAGEDISPATCHER_H +#define BDK_MESSAGES_MESSAGEDISPATCHER_H + +#include "common.h" +#include "bytes/hex.h" +#include "utils/utils.h" +#include "concepts.h" +#include "executioncontext.h" +#include "evmcontractexecutor.h" +#include "cppcontractexecutor.h" +#include "precompiledcontractexecutor.h" + +class MessageDispatcher { +public: + MessageDispatcher(ExecutionContext& context, CppContractExecutor cppExecutor, EvmContractExecutor evmExecutor, PrecompiledContractExecutor precompiledExecutor) + : context_(context), cppExecutor_(std::move(cppExecutor)), evmExecutor_(std::move(evmExecutor)), precompiledExecutor_(std::move(precompiledExecutor)) {} + + template> + R onMessage(M&& msg) { + auto checkpoint = context_.checkpoint(); + + if constexpr (std::same_as) { + dispatchMessage(std::forward(msg)); + checkpoint.commit(); + } else { + R result = dispatchMessage(std::forward(msg)); + checkpoint.commit(); + return result; + } + } + + Address dispatchMessage(concepts::CreateMessage auto&& msg) requires concepts::EncodedMessage { + return evmExecutor_.execute(std::forward(msg)); + } + + Address dispatchMessage(concepts::CreateMessage auto&& msg) requires concepts::PackedMessage { + return cppExecutor_.execute(std::forward(msg)); + } + + decltype(auto) dispatchMessage(concepts::CallMessage auto&& msg) { + transferFunds(msg); + + if constexpr (concepts::EncodedMessage) { + if (isPayment(msg)) { + return Bytes(); + } + } + + if (isPrecompiled(msg.to())) { + return precompiledExecutor_.execute(std::forward(msg)); + } + + auto account = context_.getAccount(messageCodeAddress(msg)); + + switch (account.getContractType()) { + case ContractType::CPP: + return dispatchCall(cppExecutor_, std::forward(msg)); + break; + + case ContractType::EVM: + return dispatchCall(evmExecutor_, std::forward(msg)); + break; + + default: + throw DynamicException("Attempt to invoke non-contract or inexistent address"); + } + } + + decltype(auto) dispatchCall(auto& executor, auto&& msg) { + return executor.execute(std::forward(msg)); + } + + CppContractExecutor& cppExecutor() { return cppExecutor_; } + + EvmContractExecutor& evmExecutor() { return evmExecutor_; } + + PrecompiledContractExecutor& precompiledExecutor() { return precompiledExecutor_; } + +private: + void transferFunds(const concepts::Message auto& msg) { + const uint256_t value = messageValueOrZero(msg); + + if (value > 0) { + context_.transferBalance(msg.from(), msg.to(), value); + } + } + + bool isPayment(const concepts::EncodedMessage auto& msg) const { + return msg.input().size() == 0; + } + + bool isPayment(const concepts::PackedMessage auto& msg) const { + return false; + } + + bool isPrecompiled(View
address) const { + constexpr Address randomGeneratorAddress = bytes::hex("0x1000000000000000000000000000100000000001"); + return address == randomGeneratorAddress; + } + + ExecutionContext& context_; + CppContractExecutor cppExecutor_; + EvmContractExecutor evmExecutor_; + PrecompiledContractExecutor precompiledExecutor_; +}; + +#endif // BDK_MESSAGES_MESSAGEDISPATCHER_H diff --git a/src/contract/outofgas.h b/src/contract/outofgas.h new file mode 100644 index 00000000..69b82de5 --- /dev/null +++ b/src/contract/outofgas.h @@ -0,0 +1,8 @@ +#ifndef BDK_MESSAGES_OUTOFGAS_H +#define BDK_MESSAGES_OUTOFGAS_H + +struct OutOfGas : std::runtime_error { + OutOfGas() : std::runtime_error("Out of gas") {} +}; + +#endif // BDK_MESSAGES_OUTOFGAS_H diff --git a/src/contract/packedmessages.h b/src/contract/packedmessages.h new file mode 100644 index 00000000..81f40726 --- /dev/null +++ b/src/contract/packedmessages.h @@ -0,0 +1,38 @@ +#ifndef BDK_PACKEDMESSAGES_H +#define BDK_PACKEDMESSAGES_H + +#include "contract/concepts.h" +#include "contract/basemessage.h" + +template +struct PackedCallMessage : BaseMessage< + FromField, + ToField, + GasField, + ValueField, + CallerField, + MethodField, + ArgsField> { + + using BaseMessage, ArgsField>::BaseMessage; +}; + +template +struct PackedStaticCallMessage : BaseMessage< + FromField, + ToField, + GasField, + CallerField, + MethodField, + ArgsField> { + + using BaseMessage, ArgsField>::BaseMessage; +}; + +template +struct PackedCreateMessage : BaseMessage> { + using BaseMessage>::BaseMessage; + using ContractType = C; +}; + +#endif // BDK_PACKEDMESSAGES_H diff --git a/src/contract/precompiledcontractexecutor.cpp b/src/contract/precompiledcontractexecutor.cpp new file mode 100644 index 00000000..edb513fd --- /dev/null +++ b/src/contract/precompiledcontractexecutor.cpp @@ -0,0 +1,6 @@ +#include "precompiledcontractexecutor.h" + +Bytes PrecompiledContractExecutor::execute(EncodedStaticCallMessage& msg) { + const uint256_t randomValue = std::invoke(randomGen_); + return Utils::makeBytes(UintConv::uint256ToBytes(randomValue)); +} diff --git a/src/contract/precompiledcontractexecutor.h b/src/contract/precompiledcontractexecutor.h new file mode 100644 index 00000000..b9010812 --- /dev/null +++ b/src/contract/precompiledcontractexecutor.h @@ -0,0 +1,44 @@ +#ifndef BDK_CONTRACT_PRECOMPILEDCONTRACTEXECUTOR_H +#define BDK_CONTRACT_PRECOMPILEDCONTRACTEXECUTOR_H + +#include "utils/randomgen.h" +#include "traits.h" +#include "contract/encodedmessages.h" +#include "contract/abi.h" +#include "contract/costs.h" + +class PrecompiledContractExecutor { +public: + explicit PrecompiledContractExecutor(RandomGen randomGen) : randomGen_(std::move(randomGen)) {} + + Bytes execute(EncodedStaticCallMessage& msg); + + template> + Result execute(Message&& msg) { + if constexpr (concepts::DelegateCallMessage) { + throw DynamicException("Delegate calls not allowed for precompiled contracts"); + } + + msg.gas().use(CPP_CONTRACT_CALL_COST); + + const Bytes input = messageInputEncoded(msg); + EncodedStaticCallMessage encodedMessage(msg.from(), msg.to(), msg.gas(), input); + + if constexpr (concepts::PackedMessage) { + if constexpr (std::same_as) { + this->execute(encodedMessage); + } else { + return std::get<0>(ABI::Decoder::decodeData(this->execute(encodedMessage))); + } + } else { + return this->execute(encodedMessage); + } + } + + RandomGen& randomGenerator() { return randomGen_; } + +private: + RandomGen randomGen_; +}; + +#endif // BDK_CONTRACT_PRECOMPILEDCONTRACTEXECUTOR_H diff --git a/src/contract/templates/dexv2/dexv2factory.cpp b/src/contract/templates/dexv2/dexv2factory.cpp index f42b5a01..a2904658 100644 --- a/src/contract/templates/dexv2/dexv2factory.cpp +++ b/src/contract/templates/dexv2/dexv2factory.cpp @@ -21,7 +21,7 @@ DEXV2Factory::DEXV2Factory(const Address &address, const DB& db this->allPairs_.push_back(Address(dbEntry.value)); } for (const auto& dbEntry : db.getBatch(this->getNewPrefix("getPair_"))) { - bytes::View valueView(dbEntry.value); + View valueView(dbEntry.value); this->getPair_[Address(dbEntry.key)][Address(valueView.subspan(0, 20))] = Address(valueView.subspan(20)); } diff --git a/src/contract/templates/erc20.cpp b/src/contract/templates/erc20.cpp index 706c4c0d..fcc36071 100644 --- a/src/contract/templates/erc20.cpp +++ b/src/contract/templates/erc20.cpp @@ -22,7 +22,7 @@ ERC20::ERC20(const Address& address, const DB& db) this->balances_[Address(dbEntry.key)] = Utils::fromBigEndian(dbEntry.value); } for (const auto& dbEntry : db.getBatch(this->getNewPrefix("allowed_"))) { - bytes::View key(dbEntry.key); + View key(dbEntry.key); Address owner(key.subspan(0,20)); Address spender(key.subspan(20)); this->allowed_[owner][spender] = UintConv::bytesToUint256(dbEntry.value); diff --git a/src/contract/templates/erc20wrapper.cpp b/src/contract/templates/erc20wrapper.cpp index be790a5e..d5953504 100644 --- a/src/contract/templates/erc20wrapper.cpp +++ b/src/contract/templates/erc20wrapper.cpp @@ -11,7 +11,7 @@ ERC20Wrapper::ERC20Wrapper(const Address& contractAddress, const DB& db ) : DynamicContract(contractAddress, db), tokensAndBalances_(this) { for (const auto& dbEntry : db.getBatch(this->getNewPrefix("tokensAndBalances_"))) { - bytes::View valueView(dbEntry.value); + View valueView(dbEntry.value); this->tokensAndBalances_[Address(dbEntry.key)][Address(valueView.subspan(0, 20))] = Utils::fromBigEndian(valueView.subspan(20)); } diff --git a/src/contract/templates/erc721.cpp b/src/contract/templates/erc721.cpp index 026bc41d..0aa4d7a3 100644 --- a/src/contract/templates/erc721.cpp +++ b/src/contract/templates/erc721.cpp @@ -16,7 +16,7 @@ ERC721::ERC721(const Address& address, const DB& db this->name_ = StrConv::bytesToString(db.get(std::string("name_"), this->getDBPrefix())); this->symbol_ = StrConv::bytesToString(db.get(std::string("symbol_"), this->getDBPrefix())); for (const auto& dbEntry : db.getBatch(this->getNewPrefix("owners_"))) { - bytes::View valueView(dbEntry.value); + View valueView(dbEntry.value); this->owners_[Utils::fromBigEndian(dbEntry.key)] = Address(valueView.subspan(0, 20)); } for (const auto& dbEntry : db.getBatch(this->getNewPrefix("balances_"))) { @@ -26,7 +26,7 @@ ERC721::ERC721(const Address& address, const DB& db this->tokenApprovals_[Utils::fromBigEndian(dbEntry.key)] = Address(dbEntry.value); } for (const auto& dbEntry : db.getBatch(this->getNewPrefix("operatorAddressApprovals_"))) { - bytes::View keyView(dbEntry.key); + View keyView(dbEntry.key); Address owner(keyView.subspan(0, 20)); Address operatorAddress(keyView.subspan(20)); this->operatorAddressApprovals_[owner][operatorAddress] = dbEntry.value[0]; diff --git a/src/contract/trace/call.cpp b/src/contract/trace/call.cpp new file mode 100644 index 00000000..57312db6 --- /dev/null +++ b/src/contract/trace/call.cpp @@ -0,0 +1,48 @@ +#include "call.h" +#include "contract/abi.h" + +namespace trace { + +json Call::toJson() const { + using enum CallType; + json res; + + switch (this->type) { + case CALL: res["type"] = "CALL"; break; + case STATICCALL: res["type"] = "STATICCALL"; break; + case DELEGATECALL: res["type"] = "DELEGATECALL"; break; + } + + res["from"] = this->from.hex(true); + res["to"] = this->to.hex(true); + + const uint256_t value = UintConv::bytesToUint256(this->value); + res["value"] = Hex::fromBytes(Utils::uintToBytes(value), true).forRPC(); + + res["gas"] = Hex::fromBytes(Utils::uintToBytes(this->gas), true).forRPC(); + res["gasUsed"] = Hex::fromBytes(Utils::uintToBytes(this->gasUsed), true).forRPC(); + res["input"] = Hex::fromBytes(this->input, true); + + if (!this->output.empty()) res["output"] = Hex::fromBytes(this->output, true); + + switch (this->status) { + case CallStatus::EXECUTION_REVERTED: { + res["error"] = "execution reverted"; + try { + std::string revertReason = ABI::Decoder::decodeError(this->output); + res["revertReason"] = std::move(revertReason); + } catch (const std::exception& ignored) {} + break; + } + case CallStatus::OUT_OF_GAS: res["error"] = "out of gas"; break; + } + + if (!this->calls.empty()) { + res["calls"] = json::array(); + for (const auto& subcall : this->calls) res["calls"].push_back(subcall.toJson()); + } + + return res; +} + +} // namespace trace diff --git a/src/contract/trace/call.h b/src/contract/trace/call.h new file mode 100644 index 00000000..1194dfc5 --- /dev/null +++ b/src/contract/trace/call.h @@ -0,0 +1,34 @@ +#ifndef BDK_CONTRACT_TRACE_CALL_H +#define BDK_CONTRACT_TRACE_CALL_H + +#include +#include "libs/zpp_bits.h" +#include "utils/bytes.h" +#include "utils/fixedbytes.h" +#include "utils/address.h" +#include "utils/utils.h" +#include "callstatus.h" +#include "calltype.h" + +namespace trace { + +struct Call { + json toJson() const; + + using serialize = zpp::bits::members<10>; + + CallType type; + CallStatus status; + Address from; + Address to; + FixedBytes<32> value; + uint64_t gas; + uint64_t gasUsed; + Bytes input; + Bytes output; + boost::container::stable_vector calls; +}; + +} // namespace trace + +#endif // BDK_CONTRACT_TRACE_CALL_H diff --git a/src/contract/trace/callstatus.h b/src/contract/trace/callstatus.h new file mode 100644 index 00000000..c8debb47 --- /dev/null +++ b/src/contract/trace/callstatus.h @@ -0,0 +1,14 @@ +#ifndef BDK_CONTRACT_TRACE_CALLSTATUS_H +#define BDK_CONTRACT_TRACE_CALLSTATUS_H + +namespace trace { + +enum class CallStatus { + SUCCEEDED, + EXECUTION_REVERTED, + OUT_OF_GAS +}; + +} // namespace trace + +#endif // BDK_CONTRACT_TRACE_CALLSTATUS_H diff --git a/src/contract/trace/calltype.h b/src/contract/trace/calltype.h new file mode 100644 index 00000000..affbee2f --- /dev/null +++ b/src/contract/trace/calltype.h @@ -0,0 +1,28 @@ +#ifndef BDK_CONTRACT_TRACE_CALLTYPE_H +#define BDK_CONTRACT_TRACE_CALLTYPE_H + +#include "contract/concepts.h" + +namespace trace { + +enum class CallType { + CALL, + STATICCALL, + DELEGATECALL +}; + +template +constexpr CallType getMessageCallType(const M& msg) { + if constexpr (concepts::DelegateCallMessage) { + return CallType::DELEGATECALL; + } else if (concepts::StaticCallMessage) { + return CallType::STATICCALL; + } else { + return CallType::CALL; + } +} + +} // namespace trace + + +#endif // BDK_CONTRACT_TRACE_CALLTYPE_H diff --git a/src/contract/traits.h b/src/contract/traits.h new file mode 100644 index 00000000..1baa9ab0 --- /dev/null +++ b/src/contract/traits.h @@ -0,0 +1,78 @@ +#ifndef BDK_MESSAGES_TRAITS_H +#define BDK_MESSAGES_TRAITS_H + +#include +#include "concepts.h" + +namespace traits { + +template +struct Methods {}; + +template +struct Methods { + using Return = R; + using Class = C; + static constexpr bool IS_VIEW = false; +}; + +template +struct Methods { + using Return = R; + using Class = C; + static constexpr bool IS_VIEW = true; +}; + +template +using MethodReturn = Methods>::Return; + +template +using MethodClass = Methods>::Class; + +template +constexpr bool IsViewMethod = Methods>::IS_VIEW; + +template +struct MessageResultHelper; + +template + requires concepts::PackedMessage +struct MessageResultHelper { + using Type = MethodReturn().method())>; +}; + +template + requires concepts::EncodedMessage +struct MessageResultHelper { + using Type = Bytes; +}; + +template +struct MessageResultHelper { + using Type = Address; +}; + +template +using MessageResult = MessageResultHelper::Type; + +template +struct MessageContractHelper; + +template + requires (concepts::PackedMessage) +struct MessageContractHelper { + using Type = MethodClass().method())>; +}; + +template + requires (concepts::PackedMessage) +struct MessageContractHelper { + using Type = std::remove_cvref_t::ContractType; +}; + +template +using MessageContract = std::remove_cvref_t::Type>; + +} // namespace traits + +#endif // BDK_MESSAGES_TRAITS_H diff --git a/src/core/consensus.cpp b/src/core/consensus.cpp index ab118b4b..aa6202d2 100644 --- a/src/core/consensus.cpp +++ b/src/core/consensus.cpp @@ -6,6 +6,8 @@ See the LICENSE.txt file in the project root for more information. */ #include "consensus.h" +#include "blockchain.h" +#include "bytes/random.h" void Consensus::validatorLoop() { LOGINFO("Starting validator loop."); @@ -327,7 +329,7 @@ void Consensus::doValidatorBlock() { void Consensus::doValidatorTx(const uint64_t& nHeight, const Validator& me) { Utils::safePrint("Validator: " + me.hex(true).get() + " doValidatorTx nHeight: " + std::to_string(nHeight)); - Hash randomness = Hash::random(); + Hash randomness = bytes::random(); Hash randomHash = Utils::sha3(randomness); LOGDEBUG("Creating random Hash transaction"); Bytes randomHashBytes = Hex::toBytes("0xcfffe746"); @@ -351,8 +353,8 @@ void Consensus::doValidatorTx(const uint64_t& nHeight, const Validator& me) { ); // Sanity check if tx is valid - bytes::View randomHashTxView(randomHashTx.getData()); - bytes::View randomSeedTxView(seedTx.getData()); + View randomHashTxView(randomHashTx.getData()); + View randomSeedTxView(seedTx.getData()); if (Utils::sha3(randomSeedTxView.subspan(4)) != Hash(randomHashTxView.subspan(4))) { LOGDEBUG("RandomHash transaction is not valid!!!"); return; diff --git a/src/core/rdpos.cpp b/src/core/rdpos.cpp index 94f9423d..94f006e8 100644 --- a/src/core/rdpos.cpp +++ b/src/core/rdpos.cpp @@ -154,10 +154,10 @@ bool rdPoS::validateBlockTxSanityCheck(const TxValidator& hashTx, const TxValida } // Check if the randomHash transaction matches the random transaction. - bytes::View hashTxData = hashTx.getData(); - bytes::View seedTxData = seedTx.getData(); - bytes::View hash = hashTxData.subspan(4); - bytes::View random = seedTxData.subspan(4); + View hashTxData = hashTx.getData(); + View seedTxData = seedTx.getData(); + View hash = hashTxData.subspan(4); + View random = seedTxData.subspan(4); // Size sanity check, should be 32 bytes. if (hash.size() != 32) { diff --git a/src/core/state.cpp b/src/core/state.cpp index 49f2525f..e49179fb 100644 --- a/src/core/state.cpp +++ b/src/core/state.cpp @@ -12,6 +12,7 @@ See the LICENSE.txt file in the project root for more information. #include "../contract/contracthost.h" // contractmanager.h #include "../utils/uintconv.h" +#include "bytes/random.h" State::State( const DB& db, @@ -47,7 +48,12 @@ State::State( // Load all the EVM Storage Slot/keys from the DB for (const auto& dbEntry : db.getBatch(DBPrefix::vmStorage)) { - this->vmStorage_.emplace(StorageKey(dbEntry.key), dbEntry.value); + Address addr(dbEntry.key | std::views::take(ADDRESS_SIZE)); + Hash hash(dbEntry.key | std::views::drop(ADDRESS_SIZE)); + + this->vmStorage_.emplace( + StorageKeyView(addr, hash), + dbEntry.value); } auto latestBlock = this->storage_.latest(); @@ -139,7 +145,8 @@ DBBatch State::dump() const { } // There is also the need to dump the vmStorage_ map for (const auto& [storageKey, storageValue] : this->vmStorage_) { - stateBatch.push_back(storageKey, storageValue, DBPrefix::vmStorage); + const auto key = Utils::makeBytes(bytes::join(storageKey.first, storageKey.second)); + stateBatch.push_back(key, storageValue, DBPrefix::vmStorage); } return stateBatch; @@ -192,8 +199,6 @@ void State::processTransaction( // processNextBlock already calls validateTransaction in every tx, // as it calls validateNextBlock as a sanity check. Account& accountFrom = *this->accounts_[tx.getFrom()]; - Account& accountTo = *this->accounts_[tx.getTo()]; - auto leftOverGas = int64_t(tx.getGasLimit()); auto& fromNonce = accountFrom.nonce; auto& fromBalance = accountFrom.balance; if (fromBalance < (tx.getValue() + tx.getGasLimit() * tx.getMaxFeePerGas())) { @@ -208,46 +213,58 @@ void State::processTransaction( throw DynamicException("Transaction nonce mismatch"); return; } + + Gas gas(uint64_t(tx.getGasLimit())); + TxAdditionalData txData{.hash = tx.hash()}; + try { - evmc_tx_context txContext; - txContext.tx_gas_price = EVMCConv::uint256ToEvmcUint256(tx.getMaxFeePerGas()); - txContext.tx_origin = tx.getFrom().toEvmcAddress(); - txContext.block_coinbase = ContractGlobals::getCoinbase().toEvmcAddress(); - txContext.block_number = ContractGlobals::getBlockHeight(); - txContext.block_timestamp = ContractGlobals::getBlockTimestamp(); - txContext.block_gas_limit = 10000000; - txContext.block_prev_randao = {}; - txContext.chain_id = EVMCConv::uint256ToEvmcUint256(this->options_.getChainID()); - txContext.block_base_fee = {}; - txContext.blob_base_fee = {}; - txContext.blob_hashes = nullptr; - txContext.blob_hashes_count = 0; - Hash randomSeed(UintConv::uint256ToBytes((randomnessHash.toUint256() + txIndex))); + const Hash randomSeed(UintConv::uint256ToBytes((static_cast(randomnessHash) + txIndex))); + + ExecutionContext context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .blockHash(blockHash) + .txHash(tx.hash()) + .txOrigin(tx.getFrom()) + .blockCoinbase(ContractGlobals::getCoinbase()) + .txIndex(txIndex) + .blockNumber(ContractGlobals::getBlockHeight()) + .blockTimestamp(ContractGlobals::getBlockTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(tx.getMaxFeePerGas()) + .chainId(this->options_.getChainID()) + .build(); + ContractHost host( this->vm_, this->dumpManager_, this->storage_, randomSeed, - txContext, - this->contracts_, - this->accounts_, - this->vmStorage_, - tx.hash(), - txIndex, - blockHash, - leftOverGas - ); + context); - host.execute(tx.txToMessage(), accountTo.contractType); - } catch (std::exception& e) { + std::visit([&] (auto&& msg) { + if constexpr (concepts::CreateMessage) { + txData.contractAddress = host.execute(std::forward(msg)); + } else { + host.execute(std::forward(msg)); + } + }, tx.toMessage(gas)); + + txData.succeeded = true; + } catch (const std::exception& e) { + txData.succeeded = false; LOGERRORP("Transaction: " + tx.hash().hex().get() + " failed to process, reason: " + e.what()); } - if (leftOverGas < 0) { - leftOverGas = 0; // We don't want to """refund""" gas due to negative gas - } + ++fromNonce; - auto usedGas = tx.getGasLimit() - leftOverGas; - fromBalance -= (usedGas * tx.getMaxFeePerGas()); + txData.gasUsed = uint64_t(tx.getGasLimit() - uint256_t(gas)); + + if (storage_.getIndexingMode() != IndexingMode::DISABLED) { + storage_.putTxAdditionalData(txData); + } + + fromBalance -= (txData.gasUsed * tx.getMaxFeePerGas()); } void State::refreshMempool(const FinalizedBlock& block) { @@ -434,87 +451,72 @@ void State::addBalance(const Address& addr) { this->accounts_[addr]->balance += uint256_t("1000000000000000000000"); } -Bytes State::ethCall(const evmc_message& callInfo) { +Bytes State::ethCall(EncodedStaticCallMessage& msg) { // We actually need to lock uniquely here // As the contract host will modify (reverting in the end) the state. std::unique_lock lock(this->stateMutex_); - const auto recipient(callInfo.recipient); - const auto& accIt = this->accounts_.find(recipient); + const auto& accIt = this->accounts_.find(msg.to()); if (accIt == this->accounts_.end()) { return {}; } const auto& acc = accIt->second; if (acc->isContract()) { - int64_t leftOverGas = callInfo.gas; - evmc_tx_context txContext; - txContext.tx_gas_price = {}; - txContext.tx_origin = callInfo.sender; - txContext.block_coinbase = ContractGlobals::getCoinbase().toEvmcAddress(); - txContext.block_number = static_cast(ContractGlobals::getBlockHeight()); - txContext.block_timestamp = static_cast(ContractGlobals::getBlockTimestamp()); - txContext.block_gas_limit = 10000000; - txContext.block_prev_randao = {}; - txContext.chain_id = EVMCConv::uint256ToEvmcUint256(this->options_.getChainID()); - txContext.block_base_fee = {}; - txContext.blob_base_fee = {}; - txContext.blob_hashes = nullptr; - txContext.blob_hashes_count = 0; + ExecutionContext context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .blockHash(Hash()) + .txHash(Hash()) + .txOrigin(msg.from()) + .blockCoinbase(ContractGlobals::getCoinbase()) + .txIndex(0) + .blockNumber(ContractGlobals::getBlockHeight()) + .blockTimestamp(ContractGlobals::getBlockTimestamp()) + .blockGasLimit(10'000'000) + .txGasPrice(0) + .chainId(this->options_.getChainID()) + .build(); + // As we are simulating, the randomSeed can be anything - Hash randomSeed = Hash::random(); + const Hash randomSeed = bytes::random(); + return ContractHost( this->vm_, this->dumpManager_, this->storage_, randomSeed, - txContext, - this->contracts_, - this->accounts_, - this->vmStorage_, - Hash(), - 0, - Hash(), - leftOverGas - ).ethCallView(callInfo, acc->contractType); + context + ).execute(msg); } else { return {}; } } -int64_t State::estimateGas(const evmc_message& callInfo) { +int64_t State::estimateGas(EncodedMessageVariant msg) { std::unique_lock lock(this->stateMutex_); - const Address to = callInfo.recipient; - // ContractHost simulate already do all necessary checks - // We just need to execute and get the leftOverGas - ContractType type = ContractType::NOT_A_CONTRACT; - if (auto accIt = this->accounts_.find(to); accIt != this->accounts_.end()) { - type = accIt->second->contractType; - } - int64_t leftOverGas = callInfo.gas; - Hash randomSeed = Hash::random(); - ContractHost( + ExecutionContext context = ExecutionContext::Builder{} + .storage(this->vmStorage_) + .accounts(this->accounts_) + .contracts(this->contracts_) + .build(); + + const Hash randomSeed = bytes::random(); + + ContractHost host( this->vm_, this->dumpManager_, this->storage_, randomSeed, - evmc_tx_context(), - this->contracts_, - this->accounts_, - this->vmStorage_, - Hash(), - 0, - Hash(), - leftOverGas - ).simulate(callInfo, type); - int64_t left = callInfo.gas - leftOverGas; - if (left < 0) { - left = 0; - } else { - // Increase the estimated amount by 15%, users don't like rejected transactions due to out of gas! - int64_t onePercent = left/100; - left += (onePercent * 15); - } - return left; + context + ); + + return int64_t(std::visit([&host] (auto&& msg) { + const Gas& gas = msg.gas(); + const int64_t initialGas(gas); + host.simulate(std::forward(msg)); + return initialGas - int64_t(gas); + }, std::move(msg)) * 1.15); } std::vector> State::getCppContracts() const { diff --git a/src/core/state.h b/src/core/state.h index 5c59181d..084931bb 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -30,9 +30,9 @@ class State : public Dumpable, public Log::LogicalLocationProvider { DumpWorker dumpWorker_; ///< Dump Manager object P2P::ManagerNormal& p2pManager_; ///< Reference to the P2P connection manager. rdPoS rdpos_; ///< rdPoS object (consensus). - boost::unordered_flat_map, SafeHash> contracts_; ///< Map with information about blockchain contracts (Address -> Contract). - boost::unordered_flat_map vmStorage_; ///< Map with the storage of the EVM. - boost::unordered_flat_map, SafeHash> accounts_; ///< Map with information about blockchain accounts (Address -> Account). + boost::unordered_flat_map, SafeHash, SafeCompare> contracts_; ///< Map with information about blockchain contracts (Address -> Contract). + boost::unordered_flat_map vmStorage_; ///< Map with the storage of the EVM. + boost::unordered_flat_map, SafeHash, SafeCompare> accounts_; ///< Map with information about blockchain accounts (Address -> Account). boost::unordered_flat_map mempool_; ///< TxBlock mempool. /** @@ -234,7 +234,7 @@ class State : public Dumpable, public Log::LogicalLocationProvider { * @param callInfo Tuple with info about the call (from, to, gasLimit, gasPrice, value, data). * @return The return of the called function as a data string. */ - Bytes ethCall(const evmc_message& callInfo); + Bytes ethCall(EncodedStaticCallMessage& msg); /** * Estimate gas for callInfo in RPC. @@ -242,7 +242,7 @@ class State : public Dumpable, public Log::LogicalLocationProvider { * @param callInfo Tuple with info about the call (from, to, gasLimit, gasPrice, value, data). * @return The used gas limit of the transaction. */ - int64_t estimateGas(const evmc_message& callInfo); + int64_t estimateGas(EncodedMessageVariant msg); /// Get a list of the C++ contract addresses and names. std::vector> getCppContracts() const; diff --git a/src/core/storage.cpp b/src/core/storage.cpp index 903aa2d8..c446cb59 100644 --- a/src/core/storage.cpp +++ b/src/core/storage.cpp @@ -84,7 +84,7 @@ void Storage::initializeBlockchain() { } } -TxBlock Storage::getTxFromBlockWithIndex(bytes::View blockData, uint64_t txIndex) const { +TxBlock Storage::getTxFromBlockWithIndex(View blockData, uint64_t txIndex) const { uint64_t index = 217; // Start of block tx range // Count txs until index. uint64_t currentTx = 0; @@ -132,7 +132,7 @@ std::tuple< const Bytes txData = blocksDb_.get(tx, DBPrefix::txToBlock); if (txData.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); - const bytes::View txDataView = txData; + const View txDataView = txData; const Hash blockHash(txDataView.subspan(0, 32)); const uint64_t blockIndex = UintConv::bytesToUint32(txDataView.subspan(32, 4)); const uint64_t blockHeight = UintConv::bytesToUint64(txDataView.subspan(36, 8)); @@ -150,7 +150,7 @@ std::tuple< const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); if (blockData.empty()) std::make_tuple(nullptr, Hash(), 0u, 0u); - const uint64_t blockHeight = UintConv::bytesToUint64(bytes::View(blockData).subspan(201, 8)); + const uint64_t blockHeight = UintConv::bytesToUint64(View(blockData).subspan(201, 8)); return std::make_tuple( std::make_shared(getTxFromBlockWithIndex(blockData, blockIndex)), blockHash, blockIndex, blockHeight diff --git a/src/core/storage.h b/src/core/storage.h index deaebda5..e32f8338 100644 --- a/src/core/storage.h +++ b/src/core/storage.h @@ -37,7 +37,7 @@ class Storage : public Log::LogicalLocationProvider { * @param blockData The raw block string. * @param txIndex The index of the transaction to get. */ - TxBlock getTxFromBlockWithIndex(bytes::View blockData, uint64_t txIndex) const; + TxBlock getTxFromBlockWithIndex(View blockData, uint64_t txIndex) const; public: /** diff --git a/src/net/http/jsonrpc/methods.cpp b/src/net/http/jsonrpc/methods.cpp index 4f9d4ad5..a450b1ed 100644 --- a/src/net/http/jsonrpc/methods.cpp +++ b/src/net/http/jsonrpc/methods.cpp @@ -8,7 +8,10 @@ See the LICENSE.txt file in the project root for more information. #include "methods.h" #include "blocktag.h" -#include "variadicparser.h" // parser.h -> ranges, utils/utils.h -> libs/json.hpp +#include "variadicparser.h" +#include "../../../core/storage.h" +#include "../../../core/state.h" +#include "bytes/cast.h" #include "../../../utils/evmcconv.h" @@ -69,11 +72,10 @@ json getBlockJson(const FinalizedBlock* block, bool includeTransactions) { return ret; } -std::pair parseEvmcMessage(const json& request, const Storage& storage, bool recipientRequired) { - std::pair res{}; +static std::tuple parseMessage(const json& request, const Storage& storage, bool recipientRequired) { + std::tuple result; - Bytes& buffer = res.first; - evmc_message& msg = res.second; + auto& [from, to, gas, value, data] = result; const auto [txJson, optionalBlockNumber] = parseAllParams>(request); @@ -83,30 +85,19 @@ std::pair parseEvmcMessage(const json& request, const Stora //if (optionalBlockNumber.has_value() && !optionalBlockNumber->isLatest(storage)) // throw Error(-32601, "Only the latest block is supported"); - msg.sender = parseIfExists
(txJson, "from") - .transform([] (const Address& addr) { return addr.toEvmcAddress(); }) - .value_or(evmc::address{}); + from = parseIfExists
(txJson, "from").value_or(Address{}); - if (recipientRequired) - msg.recipient = parse
(txJson.at("to")).toEvmcAddress(); - else - msg.recipient = parseIfExists
(txJson, "to") - .transform([] (const Address& addr) { return addr.toEvmcAddress(); }) - .value_or(evmc::address{}); - - msg.gas = parseIfExists(txJson, "gas").value_or(10000000); - parseIfExists(txJson, "gasPrice"); // gas price ignored as chain is fixed at 1 GWEI + to = recipientRequired + ? parse
(txJson.at("to")) + : parseIfExists
(txJson, "to").value_or(Address{}); - msg.value = parseIfExists(txJson, "value") - .transform([] (uint64_t val) { return EVMCConv::uint256ToEvmcUint256(uint256_t(val)); }) - .value_or(evmc::uint256be{}); + gas = Gas(parseIfExists(txJson, "gas").value_or(10'000'000)); - buffer = parseIfExists(txJson, "data").value_or(Bytes{}); + value = parseIfExists(txJson, "value").value_or(0); - msg.input_size = buffer.size(); - msg.input_data = buffer.empty() ? nullptr : buffer.data(); + data = parseIfExists(txJson, "data").value_or(Bytes{}); - return res; + return result; } // ======================================================================== @@ -199,25 +190,25 @@ json eth_blockNumber(const json& request, const Storage& storage) { } json eth_call(const json& request, const Storage& storage, State& state) { - auto [buffer, callInfo] = parseEvmcMessage(request, storage, true); - callInfo.kind = EVMC_CALL; - callInfo.flags = 0; - callInfo.depth = 0; - return Hex::fromBytes(state.ethCall(callInfo), true); + auto [from, to, gas, value, data] = parseMessage(request, storage, true); + EncodedStaticCallMessage msg(from, to, gas, data); + return Hex::fromBytes(state.ethCall(msg)); } json eth_estimateGas(const json& request, const Storage& storage, State& state) { - auto [buffer, callInfo] = parseEvmcMessage(request, storage, false); - callInfo.flags = 0; - callInfo.depth = 0; + auto [from, to, gas, value, data] = parseMessage(request, storage, false); - // TODO: "kind" is uninitialized if recipient is not zeroes - if (evmc::is_zero(callInfo.recipient)) - callInfo.kind = EVMC_CREATE; + uint64_t gasUsed; - const auto usedGas = state.estimateGas(callInfo); + if (to == Address()) { + EncodedCreateMessage msg(from, gas, value, data); + gasUsed = state.estimateGas(msg); + } else { + EncodedCallMessage msg(from, to, gas, value, data); + gasUsed = state.estimateGas(msg); + } - return Hex::fromBytes(Utils::uintToBytes(static_cast(usedGas)), true).forRPC(); + return Hex::fromBytes(Utils::uintToBytes(static_cast(gasUsed)), true).forRPC(); } json eth_gasPrice(const json& request) { diff --git a/src/net/http/jsonrpc/parser.cpp b/src/net/http/jsonrpc/parser.cpp index 1b911e2b..83fdc9a3 100644 --- a/src/net/http/jsonrpc/parser.cpp +++ b/src/net/http/jsonrpc/parser.cpp @@ -50,5 +50,21 @@ namespace jsonrpc { if (!std::regex_match(value, numberFormat)) throw Error::invalidFormat(value); return uint64_t(Hex(value).getUint()); } + + uint256_t Parser::operator()(const json& data) const { + if (data.is_number_unsigned()) + return uint256_t(data.get()); + + if (!data.is_string()) + throw Error::invalidType("string", data.type_name()); + + auto value = data.get(); + + if (!std::regex_match(value, numberFormat)) + throw Error::invalidFormat(value); + + return Hex(value).getUint(); + } + } // namespace jsonrpc diff --git a/src/net/http/jsonrpc/parser.h b/src/net/http/jsonrpc/parser.h index e0a0c2a4..ab3260d4 100644 --- a/src/net/http/jsonrpc/parser.h +++ b/src/net/http/jsonrpc/parser.h @@ -63,6 +63,11 @@ namespace jsonrpc { uint64_t operator()(const json& data) const; ///< Function call operator. }; + /// Specialization. + template<> struct Parser { + uint256_t operator()(const json& data) const; ///< Function call operator. + }; + /// Partial specialization to optionally parse a json field. template struct Parser> { /** diff --git a/src/net/p2p/encoding.cpp b/src/net/p2p/encoding.cpp index 349fdd9b..cf820d2d 100644 --- a/src/net/p2p/encoding.cpp +++ b/src/net/p2p/encoding.cpp @@ -16,7 +16,7 @@ namespace P2P { // These are shared between messages of various types that share the same encoding and decoding patterns. // ------------------------------------------------------------------------------------------------------------------ - boost::unordered_flat_map nodesFromMessage(bytes::View data) { + boost::unordered_flat_map nodesFromMessage(View data) { boost::unordered_flat_map nodes; size_t index = 0; while (index < data.size()) { @@ -65,7 +65,7 @@ namespace P2P { } } - NodeInfo nodeInfoFromMessage(const bytes::View& data) { + NodeInfo nodeInfoFromMessage(const View& data) { uint64_t nodeVersion = UintConv::bytesToUint64(data.subspan(0, 8)); uint64_t nodeEpoch = UintConv::bytesToUint64(data.subspan(8, 8)); uint64_t nodeHeight = UintConv::bytesToUint64(data.subspan(16, 8)); @@ -94,7 +94,7 @@ namespace P2P { nodesToMessage(message, nodes); } - std::vector blocksFromMessage(const bytes::View& data, const uint64_t& requiredChainId) { + std::vector blocksFromMessage(const View& data, const uint64_t& requiredChainId) { std::vector blocks; size_t index = 0; while (index < data.size()) { @@ -102,7 +102,7 @@ namespace P2P { uint64_t blockSize = UintConv::bytesToUint64(data.subspan(index, 8)); index += 8; if (data.size() - index < blockSize) { throw DynamicException("Invalid data size (block too small)"); } - bytes::View blockData = data.subspan(index, blockSize); + View blockData = data.subspan(index, blockSize); index += blockSize; blocks.emplace_back(FinalizedBlock::fromBytes(blockData, requiredChainId)); } @@ -125,7 +125,7 @@ namespace P2P { * @throw DynamicException if data size is invalid. */ template - std::vector txsFromMessage(const bytes::View& data, const uint64_t& requiredChainId) { + std::vector txsFromMessage(const View& data, const uint64_t& requiredChainId) { std::vector txs; size_t index = 0; while (index < data.size()) { @@ -133,7 +133,7 @@ namespace P2P { uint32_t txSize = UintConv::bytesToUint32(data.subspan(index, 4)); index += 4; if (data.size() - index < txSize) { throw DynamicException("Invalid data size (tx too small)"); } - bytes::View txData = data.subspan(index, txSize); + View txData = data.subspan(index, txSize); index += txSize; // Assuming requiredChainId is declared elsewhere txs.emplace_back(txData, requiredChainId); @@ -179,7 +179,7 @@ namespace P2P { RequestID RequestID::random() { return RequestID(Utils::randBytes(8)); } - CommandType getCommandType(const bytes::View message) { + CommandType getCommandType(const View message) { if (message.size() != 2) { throw DynamicException("Invalid Command Type size." + std::to_string(message.size())); } uint16_t commandType = UintConv::bytesToUint16(message); if (commandType > commandPrefixes.size()) { throw DynamicException("Invalid command type."); } @@ -188,7 +188,7 @@ namespace P2P { const Bytes& getCommandPrefix(const CommandType& commType) { return commandPrefixes[commType]; } - RequestType getRequestType(const bytes::View message) { + RequestType getRequestType(const View message) { if (message.size() != 1) { throw DynamicException("Invalid Request Type size. " + std::to_string(message.size())); } uint8_t requestType = UintConv::bytesToUint8(message); if (requestType > typePrefixes.size()) { throw DynamicException("Invalid request type."); } diff --git a/src/net/p2p/encoding.h b/src/net/p2p/encoding.h index c43ae3ee..ad6b5176 100644 --- a/src/net/p2p/encoding.h +++ b/src/net/p2p/encoding.h @@ -94,7 +94,7 @@ namespace P2P { * @param message The message to parse. * @return The request type. */ - RequestType getRequestType(const bytes::View message); + RequestType getRequestType(const View message); /** * Get the 1-byte prefix of a given request inside typePrefixes. @@ -108,7 +108,7 @@ namespace P2P { * @param message The message to parse. * @return The command type. */ - CommandType getCommandType(const bytes::View message); + CommandType getCommandType(const View message); /** * Get the 2-byte prefix of a given command inside commandPrefixes. @@ -586,19 +586,19 @@ namespace P2P { } /// Get the request type of the message. - RequestType type() const { return getRequestType(bytes::View(rawMessage_).subspan(0,1)); } + RequestType type() const { return getRequestType(View(rawMessage_).subspan(0,1)); } /// Get the request ID of the message. - RequestID id() const { return RequestID(bytes::View(rawMessage_).subspan(1, 8)); } + RequestID id() const { return RequestID(View(rawMessage_).subspan(1, 8)); } /// Get the command type of the message. - CommandType command() const { return getCommandType(bytes::View(rawMessage_).subspan(9,2)); } + CommandType command() const { return getCommandType(View(rawMessage_).subspan(9,2)); } /// Get the message data (without the flags and IDs). - bytes::View message() const { return bytes::View(rawMessage_).subspan(11); } + View message() const { return View(rawMessage_).subspan(11); } /// Get the whole message. - bytes::View raw() const { return this->rawMessage_; } + View raw() const { return this->rawMessage_; } /// Get the message's size. size_t size() const { return this->rawMessage_.size(); } @@ -660,7 +660,7 @@ namespace P2P { * @return A map of the nodes and their respective IDs. * @throw DynamicException if data size or IP version is invalid. */ - boost::unordered_flat_map nodesFromMessage(bytes::View data); + boost::unordered_flat_map nodesFromMessage(View data); /** * Helper function for converting nodes to a message. Conversion is done in-place. @@ -674,7 +674,7 @@ namespace P2P { * @param data The raw bytes string to parse. * @return A struct with the node's information. */ - NodeInfo nodeInfoFromMessage(const bytes::View& data); + NodeInfo nodeInfoFromMessage(const View& data); /** * Helper function for converting node information to a message. Conversion is done in-place. @@ -697,7 +697,7 @@ namespace P2P { * @return A list of blocks. * @throw DynamicException if data size is invalid. */ - std::vector blocksFromMessage(const bytes::View& data, const uint64_t& requiredChainId); + std::vector blocksFromMessage(const View& data, const uint64_t& requiredChainId); /** * Helper function for converting block data to a message. Conversion is done in-place. diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 2c3c9774..e0d5af97 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -24,8 +24,9 @@ set(UTILS_HEADERS set(UTILS_SOURCES ${CMAKE_SOURCE_DIR}/src/utils/clargs.cpp ${CMAKE_SOURCE_DIR}/src/utils/db.cpp + ${CMAKE_SOURCE_DIR}/src/utils/address.cpp + ${CMAKE_SOURCE_DIR}/src/utils/hash.cpp ${CMAKE_SOURCE_DIR}/src/utils/utils.cpp - ${CMAKE_SOURCE_DIR}/src/utils/strings.cpp ${CMAKE_SOURCE_DIR}/src/utils/hex.cpp ${CMAKE_SOURCE_DIR}/src/utils/merkle.cpp ${CMAKE_SOURCE_DIR}/src/utils/ecdsa.cpp diff --git a/src/utils/address.cpp b/src/utils/address.cpp new file mode 100644 index 00000000..b5c71f4d --- /dev/null +++ b/src/utils/address.cpp @@ -0,0 +1,36 @@ +#include "address.h" +#include "utils.h" +#include "bytes/hex.h" + +Hex Address::checksum(View
address) { + // Hash requires lowercase address without "0x" + std::string str = Hex::fromBytes(address, false).get(); + Hex hash = Utils::sha3(Utils::create_view_span(str)).hex(); + for (int i = 0; i < str.length(); i++) { + if (!std::isdigit(str[i])) { // Only check letters (A-F) + // If character hash is 8-F then make it uppercase + int nibble = std::stoi(hash.substr(i, 1), nullptr, 16); + str[i] = (nibble >= 8) ? std::toupper(str[i]) : std::tolower(str[i]); + } + } + str.insert(0, "0x"); + return Hex(str, true); +} + +bool Address::isValid(const std::string_view add, bool inBytes) { + if (inBytes) return (add.size() == 20); + if (add[0] == '0' && (add[1] == 'x' || add[1] == 'X')) { + return (add.size() == 42 && + add.substr(2).find_first_not_of("0123456789abcdefABCDEF") == std::string::npos + ); + } else { + return (add.size() == 40 && + add.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos + ); + } +} + +bool Address::isChksum(const std::string_view add) { + Address myAdd = bytes::hex(add); + return (add == std::string_view(Address::checksum(myAdd))); +} diff --git a/src/utils/address.h b/src/utils/address.h new file mode 100644 index 00000000..0ca06c56 --- /dev/null +++ b/src/utils/address.h @@ -0,0 +1,194 @@ +#ifndef BDK_UTILS_ADDRESS_H +#define BDK_UTILS_ADDRESS_H + +#include "bytes/range.h" +#include "bytesinterface.h" +#include "evmc/evmc.hpp" +#include "view.h" +#include "zpp_bits.h" + +constexpr size_t ADDRESS_SIZE = 20; + +/// Abstraction for a single 20-byte address (e.g. "1234567890abcdef...") +class Address : public BytesInterface { +public: + + /** + * Constructs an address with all bits clear. + */ + constexpr Address() : data_() {} + + /** + * Constructs an address from a given input. + * The input should construct a BytesInterface. + * + * Implicit construction is enabled when input + * meet the Initializer requirements. + */ + template + explicit (not bytes::Initializer) + constexpr Address(I&& input) : BytesInterface(std::forward(input)) {} + + /** + * Constructs an address from a initializer list + */ + constexpr Address(std::initializer_list initList) { + if (initList.size() != ADDRESS_SIZE) { + throw std::invalid_argument("20 bytes are required to initialize an address object"); + } + + std::ranges::copy(initList, data_.begin()); + } + + /** + * Returns the hexadecimal checksum of the given address representation. + * Per [EIP-55](https://eips.ethereum.org/EIPS/eip-55). + * @param address the address representation + * @return the checksum hexadecimal + */ + static Hex checksum(View
address); + + /** + * Check if a given address string is valid. + * If the address has both upper *and* lowercase letters, will also check the checksum. + * @param add The address to be checked. + * @param inBytes If `true`, treats the input as a raw bytes string. + * @return `true` if the address is valid, `false` otherwise. + */ + static bool isValid(const std::string_view add, bool inBytes); + + /** + * Check if an address string is checksummed, as per [EIP-55](https://eips.ethereum.org/EIPS/eip-55). + * Uses `toChksum()` internally. Does not alter the original string. + * @param add The string to check. + * @return `true` if the address is checksummed, `false` otherwise. + */ + static bool isChksum(const std::string_view add); + + /** + * Returns the beginning iterator of the address. + * @return the beginning iterator of the address. + */ + constexpr auto begin() { return data_.begin(); } + + /** + * Returns the beginning constant iterator of the address. + * @return the beginning constant iterator of the address. + */ + constexpr auto begin() const { return data_.begin(); } + +private: + friend zpp::bits::access; + using serialize = zpp::bits::members<1>; + + std::array data_; +}; + +/** + * Returns the beginning constant iterator of the bytes range. + * @param addr the target bytes range + * @return the beginning constant iterator of the data + */ +constexpr const Byte* begin(const evmc_address& addr) { return addr.bytes; } + +/** + * Returns the beginning iterator of the bytes range. + * @param addr the target bytes range + * @return the beginning iterator of the data + */ +constexpr Byte* begin(evmc_address& addr) { return addr.bytes; } + +/** + * Returns the sentinel constant iterator of the bytes range. + * @param addr the target bytes range + * @return the sentinel constant iterator of the data + */ +constexpr const Byte* end(const evmc_address& addr) { return addr.bytes + ADDRESS_SIZE; } + +/** + * Returns the sentinel iterator of the bytes range. + * @param addr the target bytes range + * @return the sentinel iterator of the data + */ +constexpr Byte* end(evmc_address& addr) { return addr.bytes + ADDRESS_SIZE; } + +/** + * Returns the beginning constant iterator of the bytes range. + * @param addr the target bytes range + * @return the beginning constant iterator of the data + */ +constexpr const Byte* begin(const evmc::address& addr) { return addr.bytes; } + +/** + * Returns the beginning iterator of the bytes range. + * @param addr the target bytes range + * @return the beginning iterator of the data + */ +constexpr Byte* begin(evmc::address& addr) { return addr.bytes; } + +/** + * Returns the sentinel constant iterator of the bytes range. + * @param addr the target bytes range + * @return the sentinel constant iterator of the data + */ +constexpr const Byte* end(const evmc::address& addr) { return addr.bytes + ADDRESS_SIZE; } + +/** + * Returns the sentinel iterator of the bytes range. + * @param addr the target bytes range + * @return the sentinel iterator of the data + */ +constexpr Byte* end(evmc::address& addr) { return addr.bytes + ADDRESS_SIZE; } + +/** + * View of a address representation type. + */ +template<> +class View
: public BytesInterface, ADDRESS_SIZE> { +public: + + /** + * Constructs a address view from the given bytes ranges. + * + * @param range the contiguous and sized range of bytes to be viewed + * @throw invalid argument exception case the range size is incompatible with the view size. + */ + template + explicit constexpr View(R&& range) : data_(range) { + if (const size_t size = std::ranges::size(range); size != ADDRESS_SIZE) { + throw std::invalid_argument("address view requires exactly 20 bytes, but " + std::to_string(size) + " were given"); + } + } + + /** + * Implicitly contructs an address view from a address object + * + * @param address the address object + */ + constexpr View(const Address& address) : data_(address) {} + + /** + * Implictly constructs an address view from the evmc address type. + * + * @param address te address object + */ + constexpr View(const evmc_address& address) : data_(address) {} + + /** + * Implictly constructs an address view from the evmc address type. + * + * @param address te address object + */ + constexpr View(const evmc::address& address) : data_(address) {} + + /** + * Returns the beginning constant iterator of the range + * @return the beginning constant iterator of the range + */ + constexpr auto begin() const { return data_.begin(); } + +private: + std::span data_; +}; + +#endif // BDK_UTILS_ADDRESS_H diff --git a/src/utils/bytes.h b/src/utils/bytes.h new file mode 100644 index 00000000..6b91716b --- /dev/null +++ b/src/utils/bytes.h @@ -0,0 +1,11 @@ +#ifndef BDK_UTILS_BYTES_H +#define BDK_UTILS_BYTES_H + +#include +#include + +using Byte = uint8_t; + +using Bytes = std::vector; + +#endif // BDK_UTILS_BYTES_H diff --git a/src/utils/bytesinterface.h b/src/utils/bytesinterface.h new file mode 100644 index 00000000..ca2d8b15 --- /dev/null +++ b/src/utils/bytesinterface.h @@ -0,0 +1,169 @@ +#ifndef BDK_UTILS_BYTESINTERFACE_H +#define BDK_UTILS_BYTESINTERFACE_H + +#include +#include +#include + +#include "bytes/initializer.h" +#include "hex.h" + +/** + * CRTP helper class template for defining a range of bytes. + * + * Derived classes of static extent must only implement begin(). + * Derived classes of dynamic extent must also implement end(). + * + * This class will generate all common functions of bytes range, + * such as size(), data(), operator[], and comparison operators. + * + * This class is heavily based on std::ranges::view_interface. + */ +template +class BytesInterface { +public: + constexpr BytesInterface() = default; + + /** + * Helper constructor from bytes initializer. + */ + explicit constexpr BytesInterface(bytes::Initializer auto&& initializer) { + initializer.to(self()); + } + + /** + * Construction by copying another bytes range. + */ + explicit constexpr BytesInterface(const bytes::Range auto& data) + requires (N != std::dynamic_extent) { + if (const size_t size = std::ranges::size(data); size != N) + throw std::invalid_argument("Given bytes range of size " + std::to_string(size) + + " is not suitable for initializing a FixedBytes<" + std::to_string(N) + ">"); + + std::ranges::copy(data, self().begin()); + } + + /** + * Default equality operator for ranges of bytes. + */ + friend constexpr bool operator==(const T& lhs, const T& rhs) { + return std::ranges::equal(lhs, rhs); + } + + + /** + * Default "space-ship" operator for ranges of bytes. This will generate + * operator<, operator<=, operator>, and operator>=. + */ + friend constexpr std::strong_ordering operator<=>(const T& lhs, const T& rhs) { + assert(std::ranges::size(lhs) == std::ranges::size(rhs)); + + const int r = std::memcmp(std::ranges::data(lhs), std::ranges::data(rhs), std::ranges::size(lhs)); + + if (r < 0) { + return std::strong_ordering::less; + } else if (r > 0) { + return std::strong_ordering::greater; + } else { + return std::strong_ordering::equivalent; + } + } + + /** + * @return constant beginning iterator of the range. + */ + constexpr auto cbegin() const { return self().begin(); } + + /** + * @return sentinel iterator of the range. + */ + constexpr auto end() requires (N != std::dynamic_extent) { return self().begin() + N; } + + /** + * @return sentinel iterator of the range. + */ + constexpr auto end() const requires (N != std::dynamic_extent) { return self().begin() + N; } + + /** + * @return sentinel constant iterator of the range. + */ + constexpr auto cend() const { return end(); } + + /** + * @return size of the range. + */ + constexpr size_t size() const { + if constexpr (N == std::dynamic_extent) + return std::distance(self().begin(), self().end()); + else + return N; + } + + /** + * @return pointer to the beginning of the range. + */ + constexpr auto data() { return std::to_address(self().begin()); } + + /** + * @return pointer to the beginning of the range. + */ + constexpr auto data() const { return std::to_address(self().begin()); } + + /** + * @return (usually) a reference to the element at given index. + */ + constexpr decltype(auto) operator[](size_t i) { return *(self().begin() + i); } + + /** + * @return (usually) a reference to the element at given index. + */ + constexpr decltype(auto) operator[](size_t i) const { return *(self().begin() + i); } + + /** + * @return false if all elements are 0, true otherwise. + */ + explicit constexpr operator bool() const { + return std::ranges::any_of(self(), [] (Byte b) { return b != 0; }); + } + + /** + * @return hexadecimal representation of the range + */ + Hex hex(bool strict = false) const { return Hex::fromBytes(self(), strict); } + + /** + * @param pos the starting position of the view + * @param len the length of the view + * @return a view from the given position and with the given length + * @throw out of range exception if given position or length are invalid + */ + constexpr View view(size_t pos, size_t len) const { + const size_t real_len = std::min(len, N - pos); + + if (pos + real_len > size()) { + throw std::out_of_range("len greater than size"); + } + + return View(self().begin() + pos, self().begin() + pos + real_len); + } + + constexpr View view(size_t pos) const { return view(pos, size()); } + + constexpr View view() const { return view(0); } + + /** + * Transforms the range to Bytes + */ + Bytes asBytes() const { + Bytes res; + res.resize(size()); + std::ranges::copy(self(), res.begin()); + return res; + } + +private: + constexpr T& self() { return static_cast(*this); } + constexpr const T& self() const { return static_cast(*this); } +}; + +#endif // BDK_UTILS_BYTESINTERFACE_H diff --git a/src/utils/db.h b/src/utils/db.h index 399f65b7..c1e303d1 100644 --- a/src/utils/db.h +++ b/src/utils/db.h @@ -84,7 +84,7 @@ class DBBatch { * @param value The entry's value. * @param prefix The entry's prefix. */ - void push_back(const bytes::View key, const bytes::View value, const Bytes& prefix) { + void push_back(const View key, const View value, const Bytes& prefix) { Bytes tmp = prefix; tmp.reserve(prefix.size() + key.size()); tmp.insert(tmp.end(), key.begin(), key.end()); @@ -102,7 +102,7 @@ class DBBatch { * @param key The entry's key. * @param prefix The entry's prefix. */ - void delete_key(const bytes::View key, const Bytes& prefix) { + void delete_key(const View key, const Bytes& prefix) { Bytes tmp = prefix; tmp.reserve(prefix.size() + key.size()); tmp.insert(tmp.end(), key.begin(), key.end()); diff --git a/src/utils/dynamicexception.h b/src/utils/dynamicexception.h index 9a29bf6d..6bec165d 100644 --- a/src/utils/dynamicexception.h +++ b/src/utils/dynamicexception.h @@ -8,6 +8,7 @@ See the LICENSE.txt file in the project root for more information. #ifndef DYNAMIC_EXCEPTION_H #define DYNAMIC_EXCEPTION_H +#include // put_time #include // ctime #include // ostringstream diff --git a/src/utils/evmcconv.cpp b/src/utils/evmcconv.cpp index 370807ed..4675ec95 100644 --- a/src/utils/evmcconv.cpp +++ b/src/utils/evmcconv.cpp @@ -10,7 +10,7 @@ See the LICENSE.txt file in the project root for more information. uint256_t EVMCConv::evmcUint256ToUint256(const evmc::uint256be& x) { // We can use the uint256ToBytes directly as it is std::span and we can create a span from an array - return UintConv::bytesToUint256(bytes::View(x.bytes, 32)); + return UintConv::bytesToUint256(View(x.bytes, 32)); } evmc::uint256be EVMCConv::uint256ToEvmcUint256(const uint256_t& x) { @@ -28,7 +28,7 @@ BytesArr<32> EVMCConv::evmcUint256ToBytes(const evmc::uint256be& x) { return ret; } -evmc::uint256be EVMCConv::bytesToEvmcUint256(const bytes::View x) { +evmc::uint256be EVMCConv::bytesToEvmcUint256(const View x) { evmc::uint256be ret; std::copy(x.begin(), x.end(), ret.bytes); return ret; @@ -38,12 +38,12 @@ Functor EVMCConv::getFunctor(const evmc_message& msg) { Functor ret; if (msg.input_size < 4) return ret; // Memcpy the first 4 bytes from the input data to the function signature - ret.value = UintConv::bytesToUint32(bytes::View(msg.input_data, 4)); + ret.value = UintConv::bytesToUint32(View(msg.input_data, 4)); return ret; } -bytes::View EVMCConv::getFunctionArgs(const evmc_message& msg) { - if (msg.input_size < 4) return bytes::View(); - return bytes::View(msg.input_data + 4, msg.input_size - 4); +View EVMCConv::getFunctionArgs(const evmc_message& msg) { + if (msg.input_size < 4) return View(); + return View(msg.input_data + 4, msg.input_size - 4); } diff --git a/src/utils/evmcconv.h b/src/utils/evmcconv.h index ef62c32e..bdfc0113 100644 --- a/src/utils/evmcconv.h +++ b/src/utils/evmcconv.h @@ -42,7 +42,7 @@ namespace EVMCConv { uint256_t evmcUint256ToUint256(const evmc::uint256be& x); evmc::uint256be uint256ToEvmcUint256(const uint256_t& x); BytesArr<32> evmcUint256ToBytes(const evmc::uint256be& x); - evmc::uint256be bytesToEvmcUint256(const bytes::View x); + evmc::uint256be bytesToEvmcUint256(const View x); ///@} /** @@ -53,11 +53,11 @@ namespace EVMCConv { Functor getFunctor(const evmc_message& msg); /** - * Get the bytes::View representing the function arguments of a given evmc_message. + * Get the View representing the function arguments of a given evmc_message. * @param msg The evmc_message to get the function arguments from. - * @return The bytes::View representing the function arguments. + * @return The View representing the function arguments. */ - bytes::View getFunctionArgs(const evmc_message& msg); + View getFunctionArgs(const evmc_message& msg); }; #endif // EVMCCONV_H diff --git a/src/utils/finalizedblock.cpp b/src/utils/finalizedblock.cpp index 7423387d..5805d2b6 100644 --- a/src/utils/finalizedblock.cpp +++ b/src/utils/finalizedblock.cpp @@ -11,7 +11,7 @@ See the LICENSE.txt file in the project root for more information. #include "../utils/uintconv.h" -FinalizedBlock FinalizedBlock::fromBytes(const bytes::View bytes, const uint64_t& requiredChainId) { +FinalizedBlock FinalizedBlock::fromBytes(const View bytes, const uint64_t& requiredChainId) { try { // Verify minimum size for a valid block SLOGTRACE("Deserializing block..."); @@ -132,7 +132,7 @@ FinalizedBlock FinalizedBlock::fromBytes(const bytes::View bytes, const uint64_t if (expectedRandomness != blockRandomness) throw std::invalid_argument("Invalid block randomness"); /// Block header to hash is the 144 after the signature - bytes::View headerBytes = bytes.subspan(65, 144); + View headerBytes = bytes.subspan(65, 144); Hash hash = Utils::sha3(headerBytes); UPubKey validatorPubKey = Secp256k1::recover(validatorSig, hash); return { diff --git a/src/utils/finalizedblock.h b/src/utils/finalizedblock.h index 076b5781..11b2b36b 100644 --- a/src/utils/finalizedblock.h +++ b/src/utils/finalizedblock.h @@ -120,7 +120,7 @@ class FinalizedBlock { * @return A FinalizedBlock instance. * @throw std::domain_error if deserialization fails for some reason. */ - static FinalizedBlock fromBytes(const bytes::View bytes, const uint64_t& requiredChainId); + static FinalizedBlock fromBytes(const View bytes, const uint64_t& requiredChainId); /** * Serialize the entire block (including the header) to a raw bytes string. diff --git a/src/utils/fixedbytes.h b/src/utils/fixedbytes.h new file mode 100644 index 00000000..57ba63c6 --- /dev/null +++ b/src/utils/fixedbytes.h @@ -0,0 +1,74 @@ +#ifndef BDK_UTILS_FIXEDBYTES_H +#define BDK_UTILS_FIXEDBYTES_H + +#include "bytesinterface.h" +#include "hex.h" + +/** + * Abstraction of a fixed-size bytes container. + * `FixedBytes<10>` would have *exactly* 10 bytes, no more, no less. + * Used as a base for both aliases (e.g. PrivKey, PubKey, etc.) and classes inheriting it (e.g. Hash, Signature, etc.). + */ +template +class FixedBytes : public BytesInterface, N> { +public: + + /** + * Constructs a fixed bytes container with all bits clear + */ + constexpr FixedBytes() : data_() {} + + /** + * Constructs a fixed bytes container from the given initializer list + * + * @param initList the initalizer list + * @throw invalid argument exception if the initializer list size does not match the container size + */ + constexpr FixedBytes(std::initializer_list initList) { + if (initList.size() != N) + throw std::invalid_argument("Given initializer list of size " + std::to_string(initList.size()) + + " is not suitable for initializing a FixedBytes<" + std::to_string(N) + ">"); + + std::ranges::copy(initList, data_.begin()); + } + + /** + * Constructs a fixed bytes container from the given bytes initializer + * + * @param initializer the bytes initializer + */ + constexpr FixedBytes(bytes::Initializer auto&& initializer) { initializer.to(data_); } + + /** + * Constructs a fixed bytes container by copying the bytes from the given range. + * + * @param input the input bytes + * @throw invalid argument exception if the input size does not match the container size + */ + explicit constexpr FixedBytes(const bytes::Range auto& input) { + if (const size_t size = std::ranges::size(input); size != N) { + throw std::invalid_argument("Given bytes range of size " + std::to_string(size) + + " is not suitable for initializing a FixedBytes<" + std::to_string(N) + ">"); + } + + std::ranges::copy(input, data_.begin()); + } + + /** + * @return the beginning iterator of this container bytes + */ + constexpr auto begin() { return data_.begin(); } + + /** + * @return the beginning constant iterator of this container bytes + */ + constexpr auto begin() const { return data_.begin(); } + +private: + BytesArr data_; + + friend zpp::bits::access; + using serialize = zpp::bits::members<1>; +}; + +#endif // BDK_UTILS_FIXEDBYTES_H diff --git a/src/utils/hash.cpp b/src/utils/hash.cpp new file mode 100644 index 00000000..436c5d7b --- /dev/null +++ b/src/utils/hash.cpp @@ -0,0 +1,12 @@ +#include "hash.h" +#include "utils.h" + +Hash::Hash(const uint256_t& value) : Hash(UintConv::uint256ToBytes(value)) {} + +Hash::operator uint256_t() const { + return UintConv::bytesToUint256(*this); +} + +View::operator uint256_t() const { + return UintConv::bytesToUint256(*this); +} \ No newline at end of file diff --git a/src/utils/hash.h b/src/utils/hash.h new file mode 100644 index 00000000..68f3ef6a --- /dev/null +++ b/src/utils/hash.h @@ -0,0 +1,184 @@ +#ifndef BDK_UTILS_HASH_H +#define BDK_UTILS_HASH_H + +#include "bytes/range.h" +#include "bytesinterface.h" +#include "evmc/evmc.hpp" +#include "view.h" +#include "zpp_bits.h" + +constexpr size_t HASH_SIZE = 32; + +/// Abstraction of a 32-byte hash. +class Hash : public BytesInterface { +public: + + /** + * Initializes the hash object with all bits clear. + */ + constexpr Hash() : data_() {} + + /** + * Constructs a hash from the given argument. The argument + * must be able to initialize a BytesInterface. Refer to BytesInterface + * for checking constructor overloads. + * + * @param input the argument capable of constructing a BytesInterface + */ + template + requires std::constructible_from, I&&> + explicit (not bytes::Initializer) + constexpr Hash(I&& input) : BytesInterface(std::forward(input)) {} + + /** + * Constructs a hash using the bits of the unsigned 256 bits integer. + * @param value the 256 bits unsigned integer value + */ + explicit Hash(const uint256_t& value); + + /** + * Converts the 256 bits of the hash into a unsigned integer representation. + * @return the uint256_t representation + */ + explicit operator uint256_t() const; + + /** + * @return the beginning iterator of the hash bytes + */ + constexpr auto begin() { return data_.begin(); } + + /** + * @return the beginning constant iterator of the hash bytes + */ + constexpr auto begin() const { return data_.begin(); } + +private: + friend zpp::bits::access; + using serialize = zpp::bits::members<1>; + + std::array data_; +}; + +/** + * Returns the beginning constant iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the beginning constant iterator of the data + */ +constexpr const Byte* begin(const evmc_bytes32& bytes32) { return bytes32.bytes; } + +/** + * Returns the beginning iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the beginning iterator of the data + */ +constexpr Byte* begin(evmc_bytes32& bytes32) { return bytes32.bytes; } + + +/** + * Returns the sentinel constant iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the sentinel constant iterator of the data + */ +constexpr const Byte* end(const evmc_bytes32& bytes32) { return bytes32.bytes + HASH_SIZE; } + +/** + * Returns the sentinel iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the sentinel iterator of the data + */ +constexpr Byte* end(evmc_bytes32& bytes32) { return bytes32.bytes + HASH_SIZE; } + + +/** + * Returns the beginning constant iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the beginning constant iterator of the data + */ +constexpr const Byte* begin(const evmc::bytes32& hash) { return hash.bytes; } + +/** + * Returns the beginning iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the beginning iterator of the data + */ +constexpr Byte* begin(evmc::bytes32& bytes32) { return bytes32.bytes; } + +/** + * Returns the sentinel constant iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the sentinel constant iterator of the data + */ +constexpr const Byte* end(const evmc::bytes32& bytes32) { return bytes32.bytes + HASH_SIZE; } + +/** + * Returns the sentinel iterator of the bytes range. + * @param bytes32 the target bytes range + * @return the sentinel iterator of the data + */ +constexpr Byte* end(evmc::bytes32& bytes32) { return bytes32.bytes + HASH_SIZE; } + +/** + * Views a type as a hash type. + */ +template<> +class View : public BytesInterface, HASH_SIZE> { +public: + + /** + * Explicitly constructs a hash view from a borrowed and contiguous + * range of exactly 32 bytes. Pretty much any type that follows + * these constraints can be seen as a hash view. + * + * @param range the target sized, contiguous, and borrowed bytes range + * @throw invalid argument exception if the input range has incorrect size + */ + template + explicit constexpr View(R&& range) : data_(range) { + if (const size_t size = std::ranges::size(range); size != HASH_SIZE) { + throw std::invalid_argument("hash view requires exactly 32 bytes, but " + std::to_string(size) + " were given"); + } + } + + /** + * Constructs a view from a const reference to a hash. + * Implicit construction is allowed. + * + * @param hash the target hash + */ + constexpr View(const Hash& hash) : data_(hash) {} + + /** + * Constructs a view from a const reference to a evmc bytes32. + * Implicit construction is allowed. + * + * @param bytes32 the target bytes + */ + constexpr View(const evmc_bytes32& bytes32) : data_(bytes32) {} + + /** + * Constructs a view from a const reference to a evmc bytes32. + * Implicit construction is allowed. + * + * @param bytes32 the target bytes + */ + constexpr View(const evmc::bytes32& bytes32) : data_(bytes32) {} + + /** + * Returns the beginning constant iterator of this range + * + * @return the range beginnig constant iterator + */ + constexpr auto begin() const { return data_.begin(); } + + /** + * Casts this hash bits to a unsigned 256-bits integer + * + * @return the unsigned 256-bits integer + */ + explicit operator uint256_t() const; + +private: + std::span data_; +}; + +#endif // BDK_UTILS_HASH_H diff --git a/src/utils/hex.cpp b/src/utils/hex.cpp index 263b7240..11d51edc 100644 --- a/src/utils/hex.cpp +++ b/src/utils/hex.cpp @@ -48,7 +48,7 @@ bool Hex::isValid(const std::string_view hex, bool strict) { return true; } -Hex Hex::fromBytes(const std::span bytes, bool strict) { +Hex Hex::fromBytes(View bytes, bool strict) { auto beg = bytes.begin(); auto end = bytes.end(); static const char* digits = "0123456789abcdef"; diff --git a/src/utils/hex.h b/src/utils/hex.h index 63b8f942..cc946943 100644 --- a/src/utils/hex.h +++ b/src/utils/hex.h @@ -11,9 +11,9 @@ See the LICENSE.txt file in the project root for more information. #include #include "bytes/view.h" // bytes/ranges.h -> ranges -> span libs/zpp_bits.h -> span +#include "bytes.h" +#include "utils/view.h" -using Byte = uint8_t; -using Bytes = std::vector; template using BytesArr = std::array; @@ -54,7 +54,7 @@ class Hex { * @param strict (optional) If `true`, includes "0x". Defaults to `false`. * @return The constructed Hex object. */ - static Hex fromBytes(const bytes::View bytes, bool strict = false); + static Hex fromBytes(const View bytes, bool strict = false); /** * Build a Hex object from a UTF-8 string ("example" = "6578616d706c65"). @@ -104,7 +104,7 @@ class Hex { inline uint256_t getUint() const { Bytes b = Hex::toBytes(this->hex_); if (b.size() > 32) throw std::length_error("Hex too big for uint conversion"); - bytes::View bV(b.data(), b.size()); + View bV(b.data(), b.size()); uint256_t ret; boost::multiprecision::import_bits(ret, bV.begin(), bV.end(), 8); return ret; diff --git a/src/utils/intconv.cpp b/src/utils/intconv.cpp index 976960d2..dc993042 100644 --- a/src/utils/intconv.cpp +++ b/src/utils/intconv.cpp @@ -64,7 +64,7 @@ BytesArr<8> IntConv::int64ToBytes(const int64_t& i) { // BYTES TO INT // ========================================================================== -int256_t IntConv::bytesToInt256(const bytes::View b) { +int256_t IntConv::bytesToInt256(const View b) { if (b.size() != 32) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 32, got " + std::to_string(b.size()) ); @@ -87,7 +87,7 @@ int256_t IntConv::bytesToInt256(const bytes::View b) { } } -int136_t IntConv::bytesToInt136(const bytes::View b) { +int136_t IntConv::bytesToInt136(const View b) { if (b.size() != 17) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 17, got " + std::to_string(b.size()) ); @@ -96,7 +96,7 @@ int136_t IntConv::bytesToInt136(const bytes::View b) { return ret; } -int64_t IntConv::bytesToInt64(const bytes::View b) { +int64_t IntConv::bytesToInt64(const View b) { if (b.size() != 8) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 8, got " + std::to_string(b.size()) ); diff --git a/src/utils/intconv.h b/src/utils/intconv.h index f8e38b93..ad65914a 100644 --- a/src/utils/intconv.h +++ b/src/utils/intconv.h @@ -73,9 +73,9 @@ namespace IntConv { * @return The converted integer. * @throw DynamicException if string size is invalid. */ - int256_t bytesToInt256(const bytes::View b); - int136_t bytesToInt136(const bytes::View b); - int64_t bytesToInt64(const bytes::View b); + int256_t bytesToInt256(const View b); + int136_t bytesToInt136(const View b); + int64_t bytesToInt64(const View b); ///@} }; diff --git a/src/utils/randomgen.cpp b/src/utils/randomgen.cpp index 7234bbc1..e513f51d 100644 --- a/src/utils/randomgen.cpp +++ b/src/utils/randomgen.cpp @@ -7,10 +7,32 @@ See the LICENSE.txt file in the project root for more information. #include "randomgen.h" +RandomGen::RandomGen(const RandomGen& other) { + std::unique_lock lock(other.seedLock_); + seed_ = other.seed_; +} + +RandomGen& RandomGen::operator=(const RandomGen& other) { + std::unique_lock lockA(seedLock_, std::defer_lock); + std::unique_lock lockB(other.seedLock_, std::defer_lock); + + std::lock(lockA, lockB); + + seed_ = other.seed_; + + return *this; +} + +RandomGen& RandomGen::operator=(RandomGen&& other) noexcept { + std::unique_lock lock(seedLock_); + seed_ = other.seed_; + return *this; +} + uint256_t RandomGen::operator()() { std::lock_guard lock(this->seedLock_); this->seed_ = Utils::sha3(this->seed_); - uint256_t ret = this->seed_.toUint256(); + uint256_t ret = static_cast(this->seed_); return ret; } diff --git a/src/utils/randomgen.h b/src/utils/randomgen.h index cf832fe7..5587f414 100644 --- a/src/utils/randomgen.h +++ b/src/utils/randomgen.h @@ -27,7 +27,17 @@ class RandomGen { * Constructor. * @param seed A random seed for initialization. */ - explicit RandomGen(const Hash& seed) : seed_(seed) {}; + explicit RandomGen(const Hash& seed) : seed_(seed) {} + + ~RandomGen() = default; + + RandomGen(const RandomGen&); + + RandomGen(RandomGen&& other) noexcept : seed_(other.seed_) {} + + RandomGen& operator=(const RandomGen&); + + RandomGen& operator=(RandomGen&&) noexcept; /// Getter for `seed_`. inline const Hash& getSeed() const { std::lock_guard lock(seedLock_); return this->seed_; } @@ -51,7 +61,7 @@ class RandomGen { for (uint64_t i = 0; i < v.size(); ++i) { this->seed_ = Utils::sha3(this->seed_); // Print seed as hex here if you want to debug it - uint64_t n = uint64_t(i + this->seed_.toUint256() % (v.size() - i)); + uint64_t n = uint64_t(i + static_cast(this->seed_) % (v.size() - i)); std::swap(v[n], v[i]); } } diff --git a/src/utils/safehash.h b/src/utils/safehash.h index cf007eec..5deeb6fb 100644 --- a/src/utils/safehash.h +++ b/src/utils/safehash.h @@ -8,6 +8,11 @@ See the LICENSE.txt file in the project root for more information. #ifndef HASH_H #define HASH_H +#include +#include +#include + +#include "../bytes/join.h" #include "../libs/wyhash.h" #include "tx.h" // ecdsa.h -> utils.h -> strings.h, bytes/join.h, boost/asio/ip/address.hpp @@ -18,12 +23,18 @@ See the LICENSE.txt file in the project root for more information. * We use the highest and fastest quality hash function available for size_t (64-bit) hashes (Wyhash) */ struct SafeHash { + using is_transparent = void; + ///@{ /** Wrapper for `splitmix()`. */ size_t operator()(const uint64_t& i) const { return wyhash(std::bit_cast(&i), sizeof(i), 0, _wyp); } + size_t operator()(const uint256_t& i) const { + return (*this)(Hash(i)); + } + size_t operator()(const std::string& str) const { return wyhash(str.c_str(), str.size(), 0, _wyp); } @@ -32,38 +43,25 @@ struct SafeHash { return wyhash(str.data(), str.size(), 0, _wyp); } - size_t operator()(const Bytes& bytes) const { - return wyhash(std::bit_cast(bytes.data()), bytes.size(), 0, _wyp); - } - - template size_t operator()(const BytesArr& bytesArr) const { - return wyhash(std::bit_cast(bytesArr.data()), bytesArr.size(), 0, _wyp); - } - - size_t operator()(const bytes::View& bytesArrView) const { - return wyhash(std::bit_cast(bytesArrView.data()), bytesArrView.size(), 0, _wyp); - } - - size_t operator()(const Address& address) const { - return wyhash(std::bit_cast(address.data()), address.size(), 0, _wyp); - } - size_t operator()(const Functor& functor) const { return functor.value; // Functor is already a hash. Just return it. } - size_t operator()(const Hash& hash) const { - return wyhash(std::bit_cast(hash.data()), hash.size(), 0, _wyp); - } - size_t operator()(const TxValidator& tx) const { return SafeHash()(tx.hash()); } template size_t operator()(const std::shared_ptr& ptr) const { return SafeHash()(*ptr->get()); } - template size_t operator()(const FixedBytes& bytes) const { - return wyhash(std::bit_cast(bytes.data()), bytes.size(), 0, _wyp); + size_t operator()(View data) const { + return wyhash(data.data(), data.size(), 0, _wyp); + } + + template + size_t operator()(const std::pair& pair) const { + size_t hash = (*this)(pair.first); + boost::hash_combine(hash, pair.second); + return hash; } template size_t operator()(const boost::unordered_flat_map& a) const { @@ -88,6 +86,18 @@ struct SafeHash { ///@} }; +struct SafeCompare { + using is_transparent = void; + + constexpr bool operator()(View lhs, View rhs) const { + return std::ranges::equal(lhs, rhs); + } + + constexpr bool operator()(const std::pair, View>& lhs, const std::pair, View>& rhs) const { + return (*this)(lhs.first, rhs.first) && (*this)(lhs.second, rhs.second); + } +}; + /** * [Fowler-Noll-Vo](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) * hash struct used within broadcast messages. @@ -99,7 +109,7 @@ struct FNVHash { * Call operator. * @param s The string to hash. */ - size_t operator()(bytes::View s) const { + size_t operator()(View s) const { size_t result = 2166136261U; for (auto it = s.begin(); it != s.end(); it++) result = (16777619 * result) ^ (*it); return result; diff --git a/src/utils/signature.h b/src/utils/signature.h new file mode 100644 index 00000000..a985c194 --- /dev/null +++ b/src/utils/signature.h @@ -0,0 +1,113 @@ +#ifndef BDK_UTILS_SIGNATURE_H +#define BDK_UTILS_SIGNATURE_H + +#include "bytes/range.h" +#include "bytesinterface.h" +#include "evmc/evmc.hpp" +#include "hash.h" +#include "view.h" + +constexpr size_t SIGNATURE_SIZE = 65; + +/** + * Base class for all signature types. Should NOT be used for polymorphism. + * It's helper class template aimed to define a common interface for all signature types. + * This class uses CRTP. + */ +template +class SignatureInterface : public BytesInterface { +public: + using BytesInterface::BytesInterface; + + /** + * @return get the first half (32 bytes) of the signature. + */ + constexpr uint256_t r() const { + return static_cast(View(self() | std::views::take(HASH_SIZE))); + } + + /** + * @return get the second half (32 bytes) of the signature. + */ + constexpr uint256_t s() const { + return static_cast(View( + self() | std::views::drop(HASH_SIZE) | std::views::take(HASH_SIZE))); + } + + /** + * @return get the recovery ID (1 byte) of the signature. + */ + constexpr uint8_t v() const { + return *std::ranges::rbegin(self()); + } + +private: + constexpr const T& self() const { return static_cast(*this); } +}; + +/// Abstraction of a 65-byte ECDSA signature. +class Signature : public SignatureInterface { +public: + /** + * Constructs a signature object with all bits clear + */ + constexpr Signature() : data_() {} + + /** + * Constructs a hash from the given argument. The argument + * must be able to initialize a BytesInterface. Refer to BytesInterface + * for checking constructor overloads. + * + * @param input the argument capable of constructing a BytesInterface + */ + template + explicit (not bytes::Initializer) + constexpr Signature(I&& input) : SignatureInterface(std::forward(input)) {} + + /** + * @return the beginning iterator of the bytes range + */ + constexpr auto begin() { return data_.begin(); } + + /** + * @return the beginning constant iterator of the bytes range + */ + constexpr auto begin() const { return data_.begin(); } + +private: + std::array data_; +}; + +template<> +class View : public SignatureInterface> { +public: + /** + * Constructs a signature view from the given bytes range of 65 bytes. + * + * @param range a contiguous and sized byte range of exactly 65 bytes + * @throw invalid argument exception if the input range does not have the correct size + */ + template + explicit constexpr View(R&& range) : data_(range) { + if (const size_t size = std::ranges::size(range); size != HASH_SIZE) { + throw std::invalid_argument("signature view requires exactly 65 bytes, but " + std::to_string(size) + " were given"); + } + } + + /** + * Constructs a signature view from a signature object. + * + * @param the signature object + */ + constexpr View(const Signature& signature) : data_(signature) {} + + /** + * @return the beginning constant iterator of the signature bytes range + */ + constexpr auto begin() const { return data_.begin(); } + +private: + std::span data_; +}; + +#endif // BDK_UTILS_SIGNATURE_H diff --git a/src/utils/strconv.cpp b/src/utils/strconv.cpp index 754243bf..dc92a250 100644 --- a/src/utils/strconv.cpp +++ b/src/utils/strconv.cpp @@ -16,7 +16,7 @@ Bytes StrConv::cArrayToBytes(const uint8_t* arr, size_t size) { return ret; } -Bytes StrConv::padLeftBytes(const bytes::View bytes, unsigned int charAmount, uint8_t sign) { +Bytes StrConv::padLeftBytes(const View bytes, unsigned int charAmount, uint8_t sign) { size_t padding = (charAmount > bytes.size()) ? (charAmount - bytes.size()) : 0; Bytes padded = (padding != 0) ? Bytes(padding, sign) : Bytes(0, 0x00); padded.reserve(bytes.size() + padded.size()); @@ -24,7 +24,7 @@ Bytes StrConv::padLeftBytes(const bytes::View bytes, unsigned int charAmount, ui return padded; } -Bytes StrConv::padRightBytes(const bytes::View bytes, unsigned int charAmount, uint8_t sign) { +Bytes StrConv::padRightBytes(const View bytes, unsigned int charAmount, uint8_t sign) { size_t padding = (charAmount > bytes.size()) ? (charAmount - bytes.size()) : 0; Bytes padded = (padding != 0) ? Bytes(padding, sign) : Bytes(0, 0x00); Bytes ret; diff --git a/src/utils/strconv.h b/src/utils/strconv.h index 231f24e7..718ae182 100644 --- a/src/utils/strconv.h +++ b/src/utils/strconv.h @@ -37,7 +37,7 @@ namespace StrConv { * @param sign (optional) The character to use as padding. Defaults to '0'. * @return The padded vector. */ - Bytes padLeftBytes(const bytes::View bytes, unsigned int charAmount, uint8_t sign = 0x00); + Bytes padLeftBytes(const View bytes, unsigned int charAmount, uint8_t sign = 0x00); /** * Add padding to the right of a byte vector. @@ -49,7 +49,7 @@ namespace StrConv { * @param sign (optional) The character to use as padding. Defaults to '0'. * @return The padded vector. */ - Bytes padRightBytes(const bytes::View bytes, unsigned int charAmount, uint8_t sign = 0x00); + Bytes padRightBytes(const View bytes, unsigned int charAmount, uint8_t sign = 0x00); /// Overload of padLeftBytes() that works with UTF-8 strings. std::string padLeft(std::string str, unsigned int charAmount, char sign = '\x00'); diff --git a/src/utils/strings.cpp b/src/utils/strings.cpp deleted file mode 100644 index f4dda96b..00000000 --- a/src/utils/strings.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* -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 "strings.h" -#include "utils.h" // also includes "strings.h" - -Hash::Hash(const uint256_t& data) : FixedBytes<32>(UintConv::uint256ToBytes(data)) {}; - -Hash::Hash(const evmc::bytes32& data) { - // Copy the data from the evmc::bytes32 struct to this->data_ - std::copy(data.bytes, data.bytes + 32, this->begin()); -} - -uint256_t Hash::toUint256() const { return UintConv::bytesToUint256(*this); } - -evmc::bytes32 Hash::toEvmcBytes32() const { - evmc::bytes32 bytes; - std::memcpy(bytes.bytes, this->data(), 32); - return bytes; -} - -uint256_t Signature::r() const { return UintConv::bytesToUint256(this->view(0, 32)); } - -uint256_t Signature::s() const { return UintConv::bytesToUint256(this->view(32, 32)); } - -uint8_t Signature::v() const { return UintConv::bytesToUint8(this->view(64, 1)); } - -Address::Address(const std::string_view add, bool inBytes) { - if (inBytes) { - if (add.size() != 20) throw std::invalid_argument("Address must be 20 bytes long."); - std::ranges::copy(add, this->begin()); - } else { - if (!Address::isValid(add, false)) throw std::invalid_argument("Invalid Hex address."); - auto bytes = Hex::toBytes(add); - std::ranges::copy(bytes, this->begin()); - } -} - -Address::Address(const evmc::address& data) { - // Copy the data from the evmc::address struct to this->data_ - std::copy(data.bytes, data.bytes + 20, this->begin()); -} - -evmc::address Address::toEvmcAddress() const { - evmc::address addr; - std::ranges::copy(*this, addr.bytes); - return addr; -} - -Address::Address(const evmc_address &data) { - // Same as evmc::address - std::copy(data.bytes, data.bytes + 20, this->begin()); -} - -Hex Address::toChksum() const { - // Hash requires lowercase address without "0x" - std::string str = Hex::fromBytes(*this, false).get(); - Hex hash = Utils::sha3(Utils::create_view_span(str)).hex(); - for (int i = 0; i < str.length(); i++) { - if (!std::isdigit(str[i])) { // Only check letters (A-F) - // If character hash is 8-F then make it uppercase - int nibble = std::stoi(hash.substr(i, 1), nullptr, 16); - str[i] = (nibble >= 8) ? std::toupper(str[i]) : std::tolower(str[i]); - } - } - str.insert(0, "0x"); - return Hex(str, true); -} - -bool Address::isValid(const std::string_view add, bool inBytes) { - if (inBytes) return (add.size() == 20); - if (add[0] == '0' && (add[1] == 'x' || add[1] == 'X')) { - return (add.size() == 42 && - add.substr(2).find_first_not_of("0123456789abcdefABCDEF") == std::string::npos - ); - } else { - return (add.size() == 40 && - add.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos - ); - } -} - -bool Address::isChksum(const std::string_view add) { - Address myAdd(add, false); - return (add == std::string_view(myAdd.toChksum())); -} - -StorageKey::StorageKey(const evmc::address& addr, const evmc::bytes32& slot) { - // Copy the data from the evmc::address struct to this->data_ - std::copy_n(addr.bytes, 20, this->begin()); - // Copy the data from the evmc::bytes32 struct to this->data_ - std::copy_n(slot.bytes, 32, this->begin() + 20); -} - -StorageKey::StorageKey(const evmc_address& addr, const evmc_bytes32& slot) { - // Copy the data from the evmc::address struct to this->data_ - std::copy_n(addr.bytes, 20, this->begin()); - // Copy the data from the evmc::bytes32 struct to this->data_ - std::copy_n(slot.bytes, 32, this->begin() + 20); -} - -StorageKey::StorageKey(const evmc_address& addr, const evmc::bytes32& slot) { - // Copy the data from the evmc::address struct to this->data_ - std::copy_n(addr.bytes, 20, this->begin()); - // Copy the data from the evmc::bytes32 struct to this->data_ - std::copy_n(slot.bytes, 32, this->begin() + 20); -} - -StorageKey::StorageKey(const evmc::address& addr, const evmc_bytes32& slot) { - // Copy the data from the evmc::address struct to this->data_ - std::copy_n(addr.bytes, 20, this->begin()); - // Copy the data from the evmc::bytes32 struct to this->data_ - std::copy_n(slot.bytes, 32, this->begin() + 20); -} - -StorageKey::StorageKey(const Address& addr, const Hash& slot) { - // Copy the data from the evmc::address struct to this->data_ - std::copy_n(addr.cbegin(), 20, this->begin()); - // Copy the data from the evmc::bytes32 struct to this->data_ - std::copy_n(slot.cbegin(), 32, this->begin() + 20); -} - diff --git a/src/utils/strings.h b/src/utils/strings.h index 598bb6ee..1d1bd7e0 100644 --- a/src/utils/strings.h +++ b/src/utils/strings.h @@ -15,172 +15,18 @@ See the LICENSE.txt file in the project root for more information. #include "../bytes/initializer.h" // bytes/view.h -> bytes/range.h -> ranges -> span -#include "dynamicexception.h" // TODO: see the size todo below +#include "dynamicexception.h" #include "hex.h" #include "uintconv.h" -// TODO: It is possible to implement **fast** operators for some types, -// such as Address, Functor and Hash. Taking advantage that memory located within -// the array are contiguous, we can cast the data to a pointer of native types -// (such as uint64_t*) and compare them faster than using a for loop. Example: -// // Fast equality operator for h256. -// template<> inline bool FixedHash<32>::operator==(FixedHash<32> const& _other) const -// { -// const uint64_t* hash1 = (const uint64_t*)data(); -// const uint64_t* hash2 = (const uint64_t*)_other.data(); -// return (hash1[0] == hash2[0]) && (hash1[1] == hash2[1]) && (hash1[2] == hash2[2]) && (hash1[3] == hash2[3]); -// } +#include "fixedbytes.h" +#include "address.h" +#include "hash.h" +#include "signature.h" -/** - * Abstraction of a fixed-size bytes container. - * `FixedBytes<10>` would have *exactly* 10 bytes, no more, no less. - * Used as a base for both aliases (e.g. PrivKey, PubKey, etc.) and classes inheriting it (e.g. Hash, Signature, etc.). - */ -template class FixedBytes { - private: - BytesArr data_; ///< The underlying data. +using StorageKey = std::pair; +using StorageKeyView = std::pair, View>; - friend zpp::bits::access; - using serialize = zpp::bits::members<1>; ///< Typedef for the serialization struct. - - public: - /// Empty constructor. - constexpr FixedBytes() : data_() {}; - - // TODO: maybe rework the size checks here so we don't have to use exceptions? - - /// Constructor with initializer list. - constexpr FixedBytes(std::initializer_list ilist) { - if (ilist.size() != N) - throw DynamicException("Given initializer list of size " + std::to_string(ilist.size()) + - " is not suitable for initializing a FixedBytes<" + std::to_string(N) + ">"); - std::ranges::copy(ilist, data_.begin()); - } - - /// Constructor with a bytes initializer. - constexpr FixedBytes(const bytes::Initializer auto& initializer) { initializer.to(data_); } - - /// Constructor with a bytes range. - constexpr explicit FixedBytes(const bytes::Range auto& data) { - if (const size_t size = std::ranges::size(data); size != N) - throw DynamicException("Given bytes range of size " + std::to_string(size) + - " is not suitable for initializing a FixedBytes<" + std::to_string(N) + ">"); - std::ranges::copy(data, data_.begin()); - } - - /// Get a pointer to the beginning of the bytes string (non-const). - constexpr auto begin() { return data_.begin(); } - - /// Get a pointer to the beginning of the bytes string (const). - constexpr auto begin() const { return data_.begin(); } - - /// Get a const pointer to the beginning of the bytes string (const). - constexpr auto cbegin() const { return data_.cbegin(); } - - /// Get a pointer to the end of the bytes string (non-const). - constexpr auto end() { return data_.end(); } - - /// Get a pointer to the end of the bytes string (const). - constexpr auto end() const { return data_.end(); } - - /// Get a const pointer to the end of the bytes string (const). - constexpr auto cend() const { return data_.cend(); } - - /// Get a pointer to the bytes string's underlying data (non-const). - constexpr Byte* data() { return data_.data(); } - - /// Get a pointer to the bytes string's underlying data (const). - constexpr const Byte* data() const { return data_.data(); } - - /// Get the total size of the bytes string. - constexpr size_t size() const { return data_.size(); } - - /** - * Indexing operator (non-const). - * @param index The index to operate on. - */ - constexpr Byte& operator[](size_t index) { return data_[index]; } - - /** - * Indexing operator (const). - * @param index The index to operate on. - */ - constexpr const Byte& operator[](size_t index) const { return data_[index]; } - - /** - * Getter for `data_`, but returns it as a hex string. - * @param strict If `true`, returns the value with an appended "0x" prefix. - */ - inline Hex hex(bool strict = false) const { return Hex::fromBytes(this->view(), strict); } - - /** - * Getter for `data_`, but returns it as a span of the data string. - * @param pos (optional) Index to start getting chars from. Defaults to the start of the string. - * @param len (optional) Number of chars to get. Defaults to the whole string. - * @return A string view of the data, in bytes. - * @throws std::out_of_range if pos goes beyond the length. - */ - inline bytes::View view(size_t pos = 0, size_t len = N) const { - auto real_len = std::min(len, N - pos); - if (pos + real_len > N) { throw std::out_of_range("len > N"); } - return bytes::View(this->data_.begin() + pos, this->data_.begin() + pos + real_len); - } - - /// Create a Bytes object from the internal data string. - inline Bytes asBytes() const { return Bytes(this->data_.begin(), this->data_.end()); } - - /// Equality operator. Checks if both internal strings are the same. - inline bool operator==(const FixedBytes& other) const { return (this->data_ == other.data_); } - - /// Lesser operator. Does a lexicographical check on both data strings. - inline bool operator<(const FixedBytes& other) const { return (this->data_ < other.data_); } - - /// Greater-or-equal operator. Does a lexicographical check on both data strings. - inline bool operator>=(const FixedBytes& other) const { return (this->data_ >= other.data_); } - - /// Lesser-or-equal operator. Does a lexicographical check on both data strings. - inline bool operator<=(const FixedBytes& other) const { return (this->data_ <= other.data_); } - - /// Greater operator. Does a lexicographical check on both data strings. - inline bool operator>(const FixedBytes& other) const { return (this->data_ > other.data_); } - - /** - * Operator for checking the string's "real emptyness" (all zeroes). - * @return `true` if string is an empty value, `false` otherwise. - */ - explicit operator bool() const { - return std::ranges::any_of(*this, [] (Byte b) { return b != 0; }); - } -}; - -/// Abstraction of a 32-byte hash. Inherits `FixedBytes<32>`. -class Hash : public FixedBytes<32> { - public: - using FixedBytes<32>::FixedBytes; - using FixedBytes<32>::operator=; - using FixedBytes<32>::operator>=; - using FixedBytes<32>::operator<=; - using FixedBytes<32>::operator>; - using FixedBytes<32>::operator<; - - /** - * Constructor using uint256_t. - * @param data The unsigned 256-bit number to convert into a hash string. - */ - Hash(const uint256_t& data); - - /** - * Constructor using a reference to evmc::bytes32. - * @param data The evmc::bytes32 pointer to convert into a hash string. - */ - Hash(const evmc::bytes32& data); - - uint256_t toUint256() const; ///< Convert the hash string back to an unsigned 256-bit number. - evmc::bytes32 toEvmcBytes32() const; ///< Convert the hash string back to an evmc::bytes32 pointer. - - /// Generate a CRYPTOGRAPHICALLY SECURE random 32-byte/256-bit hash. - inline static Hash random() { Hash h; RAND_bytes(h.data(), 32); return h; } -}; /// Abstraction of a functor (the first 4 bytes of a function's keccak hash). struct Functor { @@ -190,124 +36,4 @@ struct Functor { inline bool operator==(const Functor& other) const { return this->value == other.value; } }; -/// Abstraction of a 65-byte ECDSA signature. Inherits `FixedBytes<65>`. -class Signature : public FixedBytes<65> { - public: - using FixedBytes<65>::FixedBytes; - uint256_t r() const; ///< Get the first half (32 bytes) of the signature. - uint256_t s() const; ///< Get the second half (32 bytes) of the signature. - uint8_t v() const; ///< Get the recovery ID (1 byte) of the signature. -}; - -/// Abstraction for a single 20-byte address (e.g. "1234567890abcdef..."). Inherits `FixedBytes<20>`. -class Address : public FixedBytes<20> { - public: - using FixedBytes<20>::FixedBytes; - using FixedBytes<20>::operator<; - using FixedBytes<20>::operator<=; - using FixedBytes<20>::operator>; - using FixedBytes<20>::operator>=; - using FixedBytes<20>::operator=; - - /** - * Constructor using a reference to evmc::address - * @param data The evmc::address pointer to convert into an address. - */ - Address(const evmc::address& data); - - /** - * Constructor using a reference to evmc_address - * @param data The evmc_address pointer to convert into an address. - */ - Address(const evmc_address& data); - - /** - * Copy constructor. - * @param add The address itself. - * @param inBytes If `true`, treats the input as a raw bytes string. - * @throw DynamicException if address has wrong size or is invalid. - */ - Address(const std::string_view add, bool inBytes); - - evmc::address toEvmcAddress() const; ///< Convert the address string back to an evmc::address. - - /** - * Convert the address to checksum format, as per [EIP-55](https://eips.ethereum.org/EIPS/eip-55). - * @return A copy of the checksummed address as a Hex object. - */ - Hex toChksum() const; - - /** - * Check if a given address string is valid. - * If the address has both upper *and* lowercase letters, will also check the checksum. - * @param add The address to be checked. - * @param inBytes If `true`, treats the input as a raw bytes string. - * @return `true` if the address is valid, `false` otherwise. - */ - static bool isValid(const std::string_view add, bool inBytes); - - /** - * Check if an address string is checksummed, as per [EIP-55](https://eips.ethereum.org/EIPS/eip-55). - * Uses `toChksum()` internally. Does not alter the original string. - * @param add The string to check. - * @return `true` if the address is checksummed, `false` otherwise. - */ - static bool isChksum(const std::string_view add); -}; - -/// Abstraction of a EVM Storage key (20-bytes address + 32 bytes slot key). Inherits `FixedBytes<52>`. -class StorageKey : public FixedBytes<52> { - public: - using FixedBytes<52>::operator<; - using FixedBytes<52>::operator<=; - using FixedBytes<52>::operator>; - using FixedBytes<52>::operator>=; - using FixedBytes<52>::operator=; - - /** - * Constructor using a bytes::View - * @param data The bytes::View pointer to convert into a storage key. - */ - StorageKey(const bytes::View& data) { - if (data.size() != 52) throw std::invalid_argument("Invalid StorageKey size."); - std::copy(data.begin(), data.end(), this->begin()); - } - - /** - * Constructor using a reference to evmc::address and a reference to evmc::bytes32. - * @param addr The evmc::address pointer to convert into a storage key. - * @param slot The evmc::bytes32 pointer to convert into a storage key. - */ - StorageKey(const evmc::address& addr, const evmc::bytes32& slot); - - /** - * Constructor using a reference to evmc_address and a reference to evmc_bytes32. - * @param addr The evmc_address pointer to convert into a storage key. - * @param slot The evmc::bytes32 pointer to convert into a storage key. - */ - StorageKey(const evmc_address& addr, const evmc_bytes32& slot); - - /** - * Constructor using a reference to evmc_address and a reference to evmc::bytes32. - * @param addr The evmc::address pointer to convert into a storage key. - * @param slot The evmc::bytes32 pointer to convert into a storage key. - */ - StorageKey(const evmc_address& addr, const evmc::bytes32& slot); - - /** - * Constructor using a reference to evmc::address and a reference to evmc_bytes32. - * @param addr The evmc_address pointer to convert into a storage key. - * @param slot The evmc::bytes32 pointer to convert into a storage key. - */ - StorageKey(const evmc::address& addr, const evmc_bytes32& slot); - - /** - * Constructor using a reference to Address and a reference to Hash. - * @param addr The Address pointer to convert into a storage key. - * @param slot The Hash pointer to convert into a storage key. - */ - StorageKey(const Address& addr, const Hash& slot); -}; - - #endif // STRINGS_H diff --git a/src/utils/transactional.h b/src/utils/transactional.h new file mode 100644 index 00000000..b989c7ed --- /dev/null +++ b/src/utils/transactional.h @@ -0,0 +1,150 @@ +#ifndef BDK_TRANSACTIONAL_H +#define BDK_TRANSACTIONAL_H + +#include +#include + +namespace transactional { + +template Revert> +class BasicTransactional { +public: + BasicTransactional(T& target, Revert revert) + : target_(&target), revert_(std::move(revert)) {} + + ~BasicTransactional() { revert(); } + + BasicTransactional(const BasicTransactional&) = delete; + + BasicTransactional(BasicTransactional&& other) noexcept + : target_(other.target_), revert_(std::move(other.revert_)) { + other.target_ = nullptr; + } + + BasicTransactional& operator=(const BasicTransactional&) = delete; + + BasicTransactional& operator=(BasicTransactional&& other) noexcept { + using std::swap; + swap(target_, other.target_); + swap(revert_, other.revert_); + return *this; + } + + void commit() { target_ = nullptr; } + + void revert() { + if (target_ == nullptr) + return; + + std::invoke(revert_, *target_); + target_ = nullptr; + } + +private: + T *target_; + Revert revert_; +}; + +class AnyTransactional { +public: + template + explicit AnyTransactional(T transactional) : impl_(std::make_unique>(std::move(transactional))) {} + + void commit() { impl_->commit(); } + + void revert() { impl_->revert(); } + +private: + struct Base { + virtual ~Base() = default; + virtual void commit() = 0; + virtual void revert() = 0; + }; + + template + struct Derived : Base { + explicit Derived(T impl) : impl_(std::move(impl)) {} + void commit() override { impl_.commit(); } + void revert() override { impl_.revert(); } + T impl_; + }; + + std::unique_ptr impl_; +}; + +template +class Group { +public: + constexpr Group(Ts... transactions) : transactions_(std::forward(transactions)...) {} + + constexpr ~Group() { revert(); } + + constexpr void commit() { + std::invoke([this] (std::index_sequence) { + (void) std::initializer_list({(std::get(transactions_).commit(), 0)...}); + }, std::make_index_sequence{}); + } + + constexpr void revert() { + std::invoke([this] (std::index_sequence) { + (void) std::initializer_list({(std::get(transactions_).revert(), 0)...}); + }, std::make_index_sequence{}); + } + +private: + std::tuple transactions_; +}; + +template +concept Checkpointable = requires (T t) { t.checkpoint(); }; + +auto checkpoint(auto& any) { + return BasicTransactional(any, [any] (auto& ref) mutable { ref = std::move(any); }); +} + +auto checkpoint(Checkpointable auto& checkpointable) { + return checkpointable.checkpoint(); +} + +auto copy(auto& target) { + return BasicTransactional(target, [target] (auto& ref) mutable { ref = std::move(target); }); +} + +auto emplace(auto& container, auto&&... args) { + auto [iterator, inserted] = container.emplace(std::forward(args)...); + + return std::make_tuple(BasicTransactional(container, [key = iterator->first, inserted] (auto& container) { + if (inserted) { + container.erase(key); + } + }), inserted); +} + +auto emplaceOrAssign(auto& container, const auto& key, auto&&... args) { + auto [iterator, inserted] = container.try_emplace(key, std::forward(args)...); + + using Mapped = std::remove_cvref_tsecond)>; + std::optional previousValue; + + if (!inserted) { + previousValue = std::move(iterator->second); + iterator->second = Mapped(std::forward(args)...); + } + + return BasicTransactional(container, [key = iterator->first, mapped = std::move(previousValue)] (auto& container) { + if (mapped.has_value()) { + container.at(key) = std::move(mapped).value(); + } else { + container.erase(key); + } + }); +} + +auto emplaceBack(auto& container, auto&&... args) { + container.emplace_back(std::forward(args)...); + return BasicTransactional(container, [] (auto& container) { container.pop_back(); }); +} + +} // namespace transactional + +#endif // BDK_TRANSACTIONAL_H diff --git a/src/utils/tx.cpp b/src/utils/tx.cpp index 34030882..32075f57 100644 --- a/src/utils/tx.cpp +++ b/src/utils/tx.cpp @@ -6,13 +6,13 @@ See the LICENSE.txt file in the project root for more information. */ #include "tx.h" - +#include "bytes/cast.h" #include "dynamicexception.h" #include "evmcconv.h" -TxBlock::TxBlock(const bytes::View bytes, const uint64_t&) { +TxBlock::TxBlock(const View bytes, const uint64_t&) { uint64_t index = 0; - bytes::View txData = bytes.subspan(1); + View txData = bytes.subspan(1); // Check if Tx is type 2 and if first byte is equal or higher than 0xf7, meaning it is a list if (bytes[0] != 0x02) throw DynamicException("Tx is not type 2"); @@ -82,7 +82,7 @@ TxBlock::TxBlock( this->hash_ = Utils::sha3(this->rlpSerialize(true)); // Include signature } -void TxBlock::parseChainId(bytes::View txData, uint64_t& index) { +void TxBlock::parseChainId(View txData, uint64_t& index) { // If chainId > 0, get chainId from string. // chainId can be a small string or the byte itself if ( @@ -100,7 +100,7 @@ void TxBlock::parseChainId(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseNonce(bytes::View txData, uint64_t& index) { +void TxBlock::parseNonce(View txData, uint64_t& index) { // If nonce > 0, get nonce from string. // nonce can be a small string or the byte itself if ( @@ -118,7 +118,7 @@ void TxBlock::parseNonce(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseMaxPriorityFeePerGas(bytes::View txData, uint64_t& index) { +void TxBlock::parseMaxPriorityFeePerGas(View txData, uint64_t& index) { // If maxPriorityFeePerGas > 0, get maxPriorityFeePerGas from string. // maxPriorityFeePerGas can be a small string or the byte itself if ( @@ -138,7 +138,7 @@ void TxBlock::parseMaxPriorityFeePerGas(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseMaxFeePerGas(bytes::View txData, uint64_t& index) { +void TxBlock::parseMaxFeePerGas(View txData, uint64_t& index) { // If maxFeePerGas > 0, get nonce from string. // maxFeePerGas can be a small string or the byte itself if ( @@ -156,7 +156,7 @@ void TxBlock::parseMaxFeePerGas(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseGasLimit(bytes::View txData, uint64_t& index) { +void TxBlock::parseGasLimit(View txData, uint64_t& index) { // If gasLimit > 0, get gasLimit from string. // gasLimit can be a small string or the byte itself if ( @@ -174,7 +174,7 @@ void TxBlock::parseGasLimit(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseTo(bytes::View txData, uint64_t& index) { +void TxBlock::parseTo(View txData, uint64_t& index) { // Get receiver address (to) - small string. // It can either be 20 bytes or 0x80 (empty string, Address()). Anything else is invalid. uint8_t toLength = txData[index] - 0x80; @@ -190,7 +190,7 @@ void TxBlock::parseTo(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseValue(bytes::View txData, uint64_t& index) { +void TxBlock::parseValue(View txData, uint64_t& index) { // Get value - small string or byte itself. if ( uint8_t valueLength = (txData[index]) >= 0x80 ? txData[index] - 0x80 : 0; @@ -207,7 +207,7 @@ void TxBlock::parseValue(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseData(bytes::View txData, uint64_t& index) { +void TxBlock::parseData(View txData, uint64_t& index) { // Get data - it can be anything really, from nothing (0x80) to a big string (0xb7) if (uint8_t(txData[index]) < 0x80) { this->data_.assign(txData.begin() + index, txData.begin() + index + 1); @@ -231,13 +231,13 @@ void TxBlock::parseData(bytes::View txData, uint64_t& index) { } } -void TxBlock::parseAccessList(bytes::View txData, uint64_t& index) const { +void TxBlock::parseAccessList(View txData, uint64_t& index) const { // Get access list - ALWAYS 0xc0 (empty list) if (txData[index] != 0xc0) throw DynamicException("Access list is not empty"); index++; // Index at rlp[9] size } -void TxBlock::parseVRS(bytes::View txData, uint64_t& index) { +void TxBlock::parseVRS(View txData, uint64_t& index) { // Get v - always byte itself (1 byte) if (txData[index] == 0x80) { this->v_ = 0; @@ -478,17 +478,25 @@ evmc_message TxBlock::txToMessage() const { msg.flags = 0; msg.depth = 1; msg.gas = static_cast(this->gasLimit_); - msg.recipient = this->to_.toEvmcAddress(); - msg.sender = this->from_.toEvmcAddress(); + msg.recipient = bytes::cast(this->to_); + msg.sender = bytes::cast(this->from_); msg.input_data = (this->data_.empty()) ? nullptr : this->data_.data(); msg.input_size = this->data_.size(); msg.value = EVMCConv::uint256ToEvmcUint256(this->value_); msg.create2_salt = {}; - msg.code_address = this->to_.toEvmcAddress(); + msg.code_address = bytes::cast(this->to_); return msg; } -TxValidator::TxValidator(const bytes::View bytes, const uint64_t&) { +EncodedMessageVariant TxBlock::toMessage(Gas& gas) const { + if (this->to_ == Address()) { + return EncodedCreateMessage(this->from_, gas, this->value_, this->data_); + } else { + return EncodedCallMessage(this->from_, this->to_, gas, this->value_, this->data_); + } +} + +TxValidator::TxValidator(const View bytes, const uint64_t&) { uint64_t index = 0; // Check if first byte is equal or higher than 0xf7, meaning it is a list @@ -559,7 +567,7 @@ TxValidator::TxValidator( this->hash_ = Utils::sha3(this->rlpSerialize(true)); // Include signature } -void TxValidator::parseData(bytes::View bytes, uint64_t& index) { +void TxValidator::parseData(View bytes, uint64_t& index) { // Get data - it can be anything really, from nothing (0x80) to a big string (0xb7) if (uint8_t(bytes[index]) < 0x80) { this->data_.assign(bytes.begin() + index, bytes.begin() + index + 1); @@ -581,7 +589,7 @@ void TxValidator::parseData(bytes::View bytes, uint64_t& index) { } } -void TxValidator::parseNHeight(bytes::View bytes, uint64_t& index) { +void TxValidator::parseNHeight(View bytes, uint64_t& index) { // Get nHeight - can be a small string or the byte itself if ( const uint8_t nHeightLength = (bytes[index] >= 0x80) ? bytes[index] - 0x80 : 0; @@ -597,7 +605,7 @@ void TxValidator::parseNHeight(bytes::View bytes, uint64_t& index) { } } -void TxValidator::parseVRS(bytes::View bytes, uint64_t& index) { +void TxValidator::parseVRS(View bytes, uint64_t& index) { // Get v - small string or the byte itself if ( uint8_t vLength = (bytes[index] >= 0x80) ? bytes[index] - 0x80 : 0; diff --git a/src/utils/tx.h b/src/utils/tx.h index f2ca2e14..fdf6ec19 100644 --- a/src/utils/tx.h +++ b/src/utils/tx.h @@ -10,6 +10,7 @@ See the LICENSE.txt file in the project root for more information. #include "ecdsa.h" // utils.h -> strings.h, (bytes/join.h -> bytes/view.h) #include "uintconv.h" +#include "contract/encodedmessages.h" /** * Abstraction of a block transaction. @@ -41,16 +42,16 @@ class TxBlock { * @param txData The raw data to parse. * @param index The index to start parsing. */ - void parseChainId(bytes::View txData, uint64_t& index); - void parseNonce(bytes::View txData, uint64_t& index); - void parseMaxPriorityFeePerGas(bytes::View txData, uint64_t& index); - void parseMaxFeePerGas(bytes::View txData, uint64_t& index); - void parseGasLimit(bytes::View txData, uint64_t& index); - void parseTo(bytes::View txData, uint64_t& index); - void parseValue(bytes::View txData, uint64_t& index); - void parseData(bytes::View txData, uint64_t& index); - void parseAccessList(bytes::View txData, uint64_t& index) const; // We don't support access lists, therefore we don't alter the object - void parseVRS(bytes::View txData, uint64_t& index); + void parseChainId(View txData, uint64_t& index); + void parseNonce(View txData, uint64_t& index); + void parseMaxPriorityFeePerGas(View txData, uint64_t& index); + void parseMaxFeePerGas(View txData, uint64_t& index); + void parseGasLimit(View txData, uint64_t& index); + void parseTo(View txData, uint64_t& index); + void parseValue(View txData, uint64_t& index); + void parseData(View txData, uint64_t& index); + void parseAccessList(View txData, uint64_t& index) const; // We don't support access lists, therefore we don't alter the object + void parseVRS(View txData, uint64_t& index); ///@} ///@{ @@ -92,7 +93,7 @@ class TxBlock { * @param requiredChainId The chain ID of the transaction. * @throw DynamicException on any parsing failure. */ - TxBlock(const bytes::View bytes, const uint64_t& requiredChainId); + TxBlock(const View bytes, const uint64_t& requiredChainId); /** * Manual constructor. Leave fields blank ("" or 0) if they're not required. @@ -176,6 +177,8 @@ class TxBlock { */ evmc_message txToMessage() const; + EncodedMessageVariant toMessage(Gas& gas) const; + /// Copy assignment operator. TxBlock& operator=(const TxBlock& other) { this->to_ = other.to_; @@ -246,9 +249,9 @@ class TxValidator { * @param bytes The raw data to parse. * @param index The index to start parsing. */ - void parseData(bytes::View bytes, uint64_t& index); - void parseNHeight(bytes::View bytes, uint64_t& index); - void parseVRS(bytes::View bytes, uint64_t& index); + void parseData(View bytes, uint64_t& index); + void parseNHeight(View bytes, uint64_t& index); + void parseVRS(View bytes, uint64_t& index); ///@} ///@{ @@ -280,7 +283,7 @@ class TxValidator { * @param requiredChainId The chain ID of the transaction. * @throw DynamicException on any parsing failure. */ - TxValidator(const bytes::View bytes, const uint64_t& requiredChainId); + TxValidator(const View bytes, const uint64_t& requiredChainId); /** * Manual constructor. Leave fields blank ("" or 0) if they're not required. diff --git a/src/utils/uintconv.cpp b/src/utils/uintconv.cpp index 08467f29..a2223f0a 100644 --- a/src/utils/uintconv.cpp +++ b/src/utils/uintconv.cpp @@ -304,7 +304,7 @@ BytesArr<1> UintConv::uint8ToBytes(const uint8_t& i) { // BYTES TO UINT // ========================================================================== -uint256_t UintConv::bytesToUint256(const bytes::View b) { +uint256_t UintConv::bytesToUint256(const View b) { if (b.size() != 32) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 32, got " + std::to_string(b.size()) ); @@ -313,7 +313,7 @@ uint256_t UintConv::bytesToUint256(const bytes::View b) { return ret; } -uint248_t UintConv::bytesToUint248(const bytes::View b) { +uint248_t UintConv::bytesToUint248(const View b) { if (b.size() != 31) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 31, got " + std::to_string(b.size()) ); @@ -322,7 +322,7 @@ uint248_t UintConv::bytesToUint248(const bytes::View b) { return ret; } -uint240_t UintConv::bytesToUint240(const bytes::View b) { +uint240_t UintConv::bytesToUint240(const View b) { if (b.size() != 30) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 30, got " + std::to_string(b.size()) ); @@ -331,7 +331,7 @@ uint240_t UintConv::bytesToUint240(const bytes::View b) { return ret; } -uint232_t UintConv::bytesToUint232(const bytes::View b) { +uint232_t UintConv::bytesToUint232(const View b) { if (b.size() != 29) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 29, got " + std::to_string(b.size()) ); @@ -340,7 +340,7 @@ uint232_t UintConv::bytesToUint232(const bytes::View b) { return ret; } -uint224_t UintConv::bytesToUint224(const bytes::View b) { +uint224_t UintConv::bytesToUint224(const View b) { if (b.size() != 28) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 28, got " + std::to_string(b.size()) ); @@ -349,7 +349,7 @@ uint224_t UintConv::bytesToUint224(const bytes::View b) { return ret; } -uint216_t UintConv::bytesToUint216(const bytes::View b) { +uint216_t UintConv::bytesToUint216(const View b) { if (b.size() != 27) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 27, got " + std::to_string(b.size()) ); @@ -358,7 +358,7 @@ uint216_t UintConv::bytesToUint216(const bytes::View b) { return ret; } -uint208_t UintConv::bytesToUint208(const bytes::View b) { +uint208_t UintConv::bytesToUint208(const View b) { if (b.size() != 26) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 26, got " + std::to_string(b.size()) ); @@ -367,7 +367,7 @@ uint208_t UintConv::bytesToUint208(const bytes::View b) { return ret; } -uint200_t UintConv::bytesToUint200(const bytes::View b) { +uint200_t UintConv::bytesToUint200(const View b) { if (b.size() != 25) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 25, got " + std::to_string(b.size()) ); @@ -376,7 +376,7 @@ uint200_t UintConv::bytesToUint200(const bytes::View b) { return ret; } -uint192_t UintConv::bytesToUint192(const bytes::View b) { +uint192_t UintConv::bytesToUint192(const View b) { if (b.size() != 24) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 24, got " + std::to_string(b.size()) ); @@ -385,7 +385,7 @@ uint192_t UintConv::bytesToUint192(const bytes::View b) { return ret; } -uint184_t UintConv::bytesToUint184(const bytes::View b) { +uint184_t UintConv::bytesToUint184(const View b) { if (b.size() != 23) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 23, got " + std::to_string(b.size()) ); @@ -394,7 +394,7 @@ uint184_t UintConv::bytesToUint184(const bytes::View b) { return ret; } -uint176_t UintConv::bytesToUint176(const bytes::View b) { +uint176_t UintConv::bytesToUint176(const View b) { if (b.size() != 22) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 22, got " + std::to_string(b.size()) ); @@ -403,7 +403,7 @@ uint176_t UintConv::bytesToUint176(const bytes::View b) { return ret; } -uint168_t UintConv::bytesToUint168(const bytes::View b) { +uint168_t UintConv::bytesToUint168(const View b) { if (b.size() != 21) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 21, got " + std::to_string(b.size()) ); @@ -412,7 +412,7 @@ uint168_t UintConv::bytesToUint168(const bytes::View b) { return ret; } -uint160_t UintConv::bytesToUint160(const bytes::View b) { +uint160_t UintConv::bytesToUint160(const View b) { if (b.size() != 20) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 20, got " + std::to_string(b.size()) ); @@ -421,7 +421,7 @@ uint160_t UintConv::bytesToUint160(const bytes::View b) { return ret; } -uint152_t UintConv::bytesToUint152(const bytes::View b) { +uint152_t UintConv::bytesToUint152(const View b) { if (b.size() != 19) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 19, got " + std::to_string(b.size()) ); @@ -430,7 +430,7 @@ uint152_t UintConv::bytesToUint152(const bytes::View b) { return ret; } -uint144_t UintConv::bytesToUint144(const bytes::View b) { +uint144_t UintConv::bytesToUint144(const View b) { if (b.size() != 18) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 18, got " + std::to_string(b.size()) ); @@ -439,7 +439,7 @@ uint144_t UintConv::bytesToUint144(const bytes::View b) { return ret; } -uint136_t UintConv::bytesToUint136(const bytes::View b) { +uint136_t UintConv::bytesToUint136(const View b) { if (b.size() != 17) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 17, got " + std::to_string(b.size()) ); @@ -448,7 +448,7 @@ uint136_t UintConv::bytesToUint136(const bytes::View b) { return ret; } -uint128_t UintConv::bytesToUint128(const bytes::View b) { +uint128_t UintConv::bytesToUint128(const View b) { if (b.size() != 16) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 16, got " + std::to_string(b.size()) ); @@ -457,7 +457,7 @@ uint128_t UintConv::bytesToUint128(const bytes::View b) { return ret; } -uint120_t UintConv::bytesToUint120(const bytes::View b) { +uint120_t UintConv::bytesToUint120(const View b) { if (b.size() != 15) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 15, got " + std::to_string(b.size()) ); @@ -466,7 +466,7 @@ uint120_t UintConv::bytesToUint120(const bytes::View b) { return ret; } -uint112_t UintConv::bytesToUint112(const bytes::View b) { +uint112_t UintConv::bytesToUint112(const View b) { if (b.size() != 14) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 16, got " + std::to_string(b.size()) ); @@ -475,7 +475,7 @@ uint112_t UintConv::bytesToUint112(const bytes::View b) { return ret; } -uint104_t UintConv::bytesToUint104(const bytes::View b) { +uint104_t UintConv::bytesToUint104(const View b) { if (b.size() != 13) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 13, got " + std::to_string(b.size()) ); @@ -484,7 +484,7 @@ uint104_t UintConv::bytesToUint104(const bytes::View b) { return ret; } -uint96_t UintConv::bytesToUint96(const bytes::View b) { +uint96_t UintConv::bytesToUint96(const View b) { if (b.size() != 12) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 12, got " + std::to_string(b.size()) ); @@ -493,7 +493,7 @@ uint96_t UintConv::bytesToUint96(const bytes::View b) { return ret; } -uint88_t UintConv::bytesToUint88(const bytes::View b) { +uint88_t UintConv::bytesToUint88(const View b) { if (b.size() != 11) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 11, got " + std::to_string(b.size()) ); @@ -502,7 +502,7 @@ uint88_t UintConv::bytesToUint88(const bytes::View b) { return ret; } -uint80_t UintConv::bytesToUint80(const bytes::View b) { +uint80_t UintConv::bytesToUint80(const View b) { if (b.size() != 10) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 10, got " + std::to_string(b.size()) ); @@ -511,7 +511,7 @@ uint80_t UintConv::bytesToUint80(const bytes::View b) { return ret; } -uint72_t UintConv::bytesToUint72(const bytes::View b) { +uint72_t UintConv::bytesToUint72(const View b) { if (b.size() != 9) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 9, got " + std::to_string(b.size()) ); @@ -520,7 +520,7 @@ uint72_t UintConv::bytesToUint72(const bytes::View b) { return ret; } -uint56_t UintConv::bytesToUint56(const bytes::View b) { +uint56_t UintConv::bytesToUint56(const View b) { if (b.size() != 7) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 7, got " + std::to_string(b.size()) ); @@ -529,7 +529,7 @@ uint56_t UintConv::bytesToUint56(const bytes::View b) { return ret; } -uint48_t UintConv::bytesToUint48(const bytes::View b) { +uint48_t UintConv::bytesToUint48(const View b) { if (b.size() != 6) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 6, got " + std::to_string(b.size()) ); @@ -538,7 +538,7 @@ uint48_t UintConv::bytesToUint48(const bytes::View b) { return ret; } -uint40_t UintConv::bytesToUint40(const bytes::View b) { +uint40_t UintConv::bytesToUint40(const View b) { if (b.size() != 5) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 5, got " + std::to_string(b.size()) ); @@ -547,7 +547,7 @@ uint40_t UintConv::bytesToUint40(const bytes::View b) { return ret; } -uint24_t UintConv::bytesToUint24(const bytes::View b) { +uint24_t UintConv::bytesToUint24(const View b) { if (b.size() != 3) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 3, got " + std::to_string(b.size()) ); @@ -556,7 +556,7 @@ uint24_t UintConv::bytesToUint24(const bytes::View b) { return ret; } -uint64_t UintConv::bytesToUint64(const bytes::View b) { +uint64_t UintConv::bytesToUint64(const View b) { if (b.size() != 8) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 8, got " + std::to_string(b.size()) ); @@ -568,7 +568,7 @@ uint64_t UintConv::bytesToUint64(const bytes::View b) { return ret; } -uint32_t UintConv::bytesToUint32(const bytes::View b) { +uint32_t UintConv::bytesToUint32(const View b) { if (b.size() != 4) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 4, got " + std::to_string(b.size()) ); @@ -580,7 +580,7 @@ uint32_t UintConv::bytesToUint32(const bytes::View b) { return ret; } -uint16_t UintConv::bytesToUint16(const bytes::View b) { +uint16_t UintConv::bytesToUint16(const View b) { if (b.size() != 2) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 2, got " + std::to_string(b.size()) ); @@ -592,7 +592,7 @@ uint16_t UintConv::bytesToUint16(const bytes::View b) { return ret; } -uint8_t UintConv::bytesToUint8(const bytes::View b) { +uint8_t UintConv::bytesToUint8(const View b) { if (b.size() != 1) throw DynamicException(std::string(__func__) + ": Invalid bytes size - expected 1, got " + std::to_string(b.size()) ); diff --git a/src/utils/uintconv.h b/src/utils/uintconv.h index 017dec17..09de7399 100644 --- a/src/utils/uintconv.h +++ b/src/utils/uintconv.h @@ -98,39 +98,39 @@ namespace UintConv { * @return The converted integer. * @throw DynamicException if string size is invalid. */ - uint256_t bytesToUint256(const bytes::View b); - uint248_t bytesToUint248(const bytes::View b); - uint240_t bytesToUint240(const bytes::View b); - uint232_t bytesToUint232(const bytes::View b); - uint224_t bytesToUint224(const bytes::View b); - uint216_t bytesToUint216(const bytes::View b); - uint208_t bytesToUint208(const bytes::View b); - uint200_t bytesToUint200(const bytes::View b); - uint192_t bytesToUint192(const bytes::View b); - uint184_t bytesToUint184(const bytes::View b); - uint176_t bytesToUint176(const bytes::View b); - uint168_t bytesToUint168(const bytes::View b); - uint160_t bytesToUint160(const bytes::View b); - uint152_t bytesToUint152(const bytes::View b); - uint144_t bytesToUint144(const bytes::View b); - uint136_t bytesToUint136(const bytes::View b); - uint128_t bytesToUint128(const bytes::View b); - uint120_t bytesToUint120(const bytes::View b); - uint112_t bytesToUint112(const bytes::View b); - uint104_t bytesToUint104(const bytes::View b); - uint96_t bytesToUint96(const bytes::View b); - uint88_t bytesToUint88(const bytes::View b); - uint80_t bytesToUint80(const bytes::View b); - uint72_t bytesToUint72(const bytes::View b); - uint56_t bytesToUint56(const bytes::View b); - uint48_t bytesToUint48(const bytes::View b); - uint40_t bytesToUint40(const bytes::View b); - uint24_t bytesToUint24(const bytes::View b); + uint256_t bytesToUint256(const View b); + uint248_t bytesToUint248(const View b); + uint240_t bytesToUint240(const View b); + uint232_t bytesToUint232(const View b); + uint224_t bytesToUint224(const View b); + uint216_t bytesToUint216(const View b); + uint208_t bytesToUint208(const View b); + uint200_t bytesToUint200(const View b); + uint192_t bytesToUint192(const View b); + uint184_t bytesToUint184(const View b); + uint176_t bytesToUint176(const View b); + uint168_t bytesToUint168(const View b); + uint160_t bytesToUint160(const View b); + uint152_t bytesToUint152(const View b); + uint144_t bytesToUint144(const View b); + uint136_t bytesToUint136(const View b); + uint128_t bytesToUint128(const View b); + uint120_t bytesToUint120(const View b); + uint112_t bytesToUint112(const View b); + uint104_t bytesToUint104(const View b); + uint96_t bytesToUint96(const View b); + uint88_t bytesToUint88(const View b); + uint80_t bytesToUint80(const View b); + uint72_t bytesToUint72(const View b); + uint56_t bytesToUint56(const View b); + uint48_t bytesToUint48(const View b); + uint40_t bytesToUint40(const View b); + uint24_t bytesToUint24(const View b); - uint64_t bytesToUint64(const bytes::View b); - uint32_t bytesToUint32(const bytes::View b); - uint16_t bytesToUint16(const bytes::View b); - uint8_t bytesToUint8(const bytes::View b); + uint64_t bytesToUint64(const View b); + uint32_t bytesToUint32(const View b); + uint16_t bytesToUint16(const View b); + uint8_t bytesToUint8(const View b); ///@} }; diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 68cb752e..108b15af 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -30,7 +30,7 @@ void Utils::logToFile(std::string_view str) { Functor Utils::makeFunctor(std::string_view functionSignature) { Functor ret; // Create the hash - Hash hash = Utils::sha3(bytes::View(reinterpret_cast(functionSignature.data()), functionSignature.size())); + Hash hash = Utils::sha3(View(reinterpret_cast(functionSignature.data()), functionSignature.size())); // Copy the first 4 bytes of the hash to the value ret.value = UintConv::bytesToUint32(hash.view(0,4)); return ret; @@ -44,14 +44,14 @@ void Utils::safePrintTest(std::string_view str) { Log::safePrintTest(str); } -Hash Utils::sha3(const bytes::View input) { +Hash Utils::sha3(const View input) { ethash_hash256 h = ethash_keccak256(input.data(), input.size()); Hash ret; std::copy(reinterpret_cast(h.bytes), reinterpret_cast(h.bytes + 32), ret.begin()); return ret; } -Account::Account(const bytes::View& bytes) { +Account::Account(const View& bytes) { if (bytes.size() < 73) throw DynamicException(std::string(__func__) + ": Invalid bytes size"); this->balance = UintConv::bytesToUint256(bytes.subspan(0,32)); this->nonce = UintConv::bytesToUint64(bytes.subspan(32,8)); @@ -105,13 +105,13 @@ std::string Utils::getSignalName(int signum) { return n; } -bytes::View Utils::create_view_span(const Bytes& vec, size_t start, size_t size) { +View Utils::create_view_span(const Bytes& vec, size_t start, size_t size) { if (start + size > vec.size()) throw DynamicException("Invalid range for span"); - return bytes::View(vec.data() + start, size); + return View(vec.data() + start, size); } -bytes::View Utils::create_view_span(const std::string_view str, size_t start, size_t size) { +View Utils::create_view_span(const std::string_view str, size_t start, size_t size) { if (start + size > str.size()) throw DynamicException("Invalid range for span"); - return bytes::View(reinterpret_cast(str.data()) + start, size); + return View(reinterpret_cast(str.data()) + start, size); } diff --git a/src/utils/utils.h b/src/utils/utils.h index fa10a75a..b79a3c1e 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -137,7 +137,7 @@ struct Account { Account(uint256_t&& balance, uint64_t&& nonce) : balance(std::move(balance)), nonce(std::move(nonce)) {} /// Deserialize constructor. - Account(const bytes::View& bytes); + Account(const View& bytes); /** * Serialize the account. @@ -201,6 +201,10 @@ template struct EventParam { /// Namespace for utility functions. namespace Utils { + + template + struct Overloaded : Ts... { using Ts::operator()...; }; + std::string getTestDumpPath(); ///< Get the path to the test dump folder. /** @@ -335,7 +339,7 @@ namespace Utils { * @param input The string to hash. * @return The SHA3-hashed string. */ - Hash sha3(const bytes::View input); + Hash sha3(const View input); /** * Generate a random bytes string of a given size. @@ -422,8 +426,8 @@ namespace Utils { * @param vec The vector to convert. * @return The converted span. */ - inline bytes::View create_view_span(const Bytes& vec) { - return bytes::View(vec.data(), vec.size()); + inline View create_view_span(const Bytes& vec) { + return View(vec.data(), vec.size()); } /** @@ -434,15 +438,15 @@ namespace Utils { * @return The converted span. * @throw DynamicException if range is invalid. */ - bytes::View create_view_span(const Bytes& vec, size_t start, size_t size); + View create_view_span(const Bytes& vec, size_t start, size_t size); /** * Convert an array to const span. * @param arr The array to convert. * @return The converted span. */ - template inline bytes::View create_view_span(const BytesArr& arr) { - return bytes::View(arr.data(), arr.size()); + template inline View create_view_span(const BytesArr& arr) { + return View(arr.data(), arr.size()); } /** @@ -453,12 +457,12 @@ namespace Utils { * @return The converted span. * @throw DynamicException if range is invalid. */ - template inline bytes::View create_view_span( + template inline View create_view_span( const BytesArr& arr, size_t start, size_t size ) { // TODO: somehow migrate this to the cpp file so we can include dynamicexception.h only there, OR get rid of the exception altogether if (start + size > arr.size()) throw DynamicException("Invalid range for span"); - return bytes::View(arr.data() + start, size); + return View(arr.data() + start, size); } /** @@ -466,8 +470,8 @@ namespace Utils { * @param str The string to convert. * @return The converted span. */ - inline bytes::View create_view_span(const std::string_view str) { - return bytes::View(reinterpret_cast(str.data()), str.size()); + inline View create_view_span(const std::string_view str) { + return View(reinterpret_cast(str.data()), str.size()); } /** @@ -478,7 +482,7 @@ namespace Utils { * @return The converted span. * @throw DynamicException if range is invalid. */ - bytes::View create_view_span(const std::string_view str, size_t start, size_t size); + View create_view_span(const std::string_view str, size_t start, size_t size); /** * Append a vector to another. diff --git a/src/utils/view.h b/src/utils/view.h new file mode 100644 index 00000000..5b9ccc11 --- /dev/null +++ b/src/utils/view.h @@ -0,0 +1,71 @@ +#ifndef BDK_UTILS_VIEW_H +#define BDK_UTILS_VIEW_H + +#include +#include +#include "bytes/range.h" +#include "bytes.h" + +/** + * Base class template for defining a view of a type. + * Specific view types must specialize it. + */ +template +struct View; + +/** + * Most generic purpose view specialization. + * Behaves just like a span of constant bytes. + */ +template<> +struct View : std::span { + + /** + * Constructs an empty view of size 0 + */ + constexpr View() = default; + + /** + * Constructs a view from a given data range. + * The range must also follow the constraints for initializing a span. + * + * @param range the contiguous and sized range of bytes + */ + template + constexpr View(R&& range) : span(std::forward(range)) {} + + /** + * Constructs a view from an data iterator and a size. + * + * @param it the contiguous iterator of the range beginning + * @param size the number of bytes to be viewed + */ + template + constexpr View(I it, size_t size) : span(it, size) {} + + /** + * Constructs a view from a iterator-sentinel pair. + * + * @param begin the contiguous iterator of the range beginning + * @param end the sentinel iterator of the range end + */ + template S> + constexpr View(I begin, S end) : span(begin, end) {} + + constexpr auto cbegin() const { return begin(); } + + constexpr auto cend() const { return end(); } + + explicit operator Bytes() const { + return Bytes(begin(), end()); + } +}; + +/** + * Partial initialization for enabling all view types as borrowed. + * All view types are borrowed since they don't own the data. + */ +template +constexpr bool std::ranges::enable_borrowed_range> = true; + +#endif // BDK_UTILS_VIEW_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d72fba75..22700b1f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,6 +42,8 @@ set (TESTS_SOURCES ${CMAKE_SOURCE_DIR}/tests/contract/calltracer.cpp ${CMAKE_SOURCE_DIR}/tests/contract/createcontract.cpp ${CMAKE_SOURCE_DIR}/tests/contract/pebble.cpp + ${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/core/rdpos.cpp ${CMAKE_SOURCE_DIR}/tests/core/storage.cpp diff --git a/tests/benchmark/erc20.cpp b/tests/benchmark/erc20.cpp index 6cfce86e..eae9258d 100644 --- a/tests/benchmark/erc20.cpp +++ b/tests/benchmark/erc20.cpp @@ -71,7 +71,7 @@ namespace TERC20BENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = transferTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); uint64_t iterations = 2500000; @@ -133,7 +133,7 @@ namespace TERC20BENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = transferTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); uint64_t iterations = 250000; diff --git a/tests/benchmark/erc721.cpp b/tests/benchmark/erc721.cpp index 344d47d9..b7af9c34 100644 --- a/tests/benchmark/erc721.cpp +++ b/tests/benchmark/erc721.cpp @@ -75,7 +75,7 @@ namespace TERC721BENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = transferTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); uint64_t iterations = 1000000; @@ -128,7 +128,7 @@ namespace TERC721BENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = transferTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); uint64_t iterations = 100000; diff --git a/tests/benchmark/snailtracer.cpp b/tests/benchmark/snailtracer.cpp index 9ade4d54..3b9f63b9 100644 --- a/tests/benchmark/snailtracer.cpp +++ b/tests/benchmark/snailtracer.cpp @@ -54,7 +54,7 @@ namespace TSNAILTRACERBENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = benchmarkTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); auto start = std::chrono::high_resolution_clock::now(); @@ -99,7 +99,7 @@ namespace TSNAILTRACERBENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = benchmarkTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); auto start = std::chrono::high_resolution_clock::now(); diff --git a/tests/benchmark/snailtraceroptimized.cpp b/tests/benchmark/snailtraceroptimized.cpp index afee6e2b..29e0a83d 100644 --- a/tests/benchmark/snailtraceroptimized.cpp +++ b/tests/benchmark/snailtraceroptimized.cpp @@ -54,7 +54,7 @@ namespace TSNAILTRACEROPTIMIZEDBENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = benchmarkTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); auto start = std::chrono::high_resolution_clock::now(); @@ -99,7 +99,7 @@ namespace TSNAILTRACEROPTIMIZEDBENCHMARK { txContext.blob_hashes_count = 0; auto callInfo = benchmarkTx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); auto start = std::chrono::high_resolution_clock::now(); diff --git a/tests/benchmark/uniswapv2.cpp b/tests/benchmark/uniswapv2.cpp index fb965094..efeacf55 100644 --- a/tests/benchmark/uniswapv2.cpp +++ b/tests/benchmark/uniswapv2.cpp @@ -78,7 +78,7 @@ namespace TDEXV2 { txContext.blob_hashes_count = 0; auto callInfo = tx.txToMessage(); - Hash randomnessHash = Hash::random(); + Hash randomnessHash = bytes::random(); int64_t leftOverGas = std::numeric_limits::max(); uint64_t iterations = 250000; diff --git a/tests/blockchainwrapper.hpp b/tests/blockchainwrapper.hpp index edc21593..de9661ce 100644 --- a/tests/blockchainwrapper.hpp +++ b/tests/blockchainwrapper.hpp @@ -9,6 +9,7 @@ See the LICENSE.txt file in the project root for more information. #define BLOCKCHAINWRAPPER_H #include "../src/core/blockchain.h" // net/http/httpserver.h, consensus.h -> state.h -> (rdpos.h -> net/p2p/managernormal.h), dump.h -> (storage.h -> utils/options.h), utils/db.h -> utils.h +#include "bytes/random.h" /** * Simple wrapper struct for management of all blockchain related objects. @@ -168,7 +169,7 @@ inline FinalizedBlock createValidBlock(const std::vector& validatorPrivKey std::vector randomHashTxs; std::vector randomTxs; - std::vector randomSeeds(orderedPrivKeys.size(), Hash::random()); + std::vector randomSeeds(orderedPrivKeys.size(), bytes::random()); for (uint64_t i = 0; i < orderedPrivKeys.size(); ++i) { Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(orderedPrivKeys[i])); Bytes hashTxData = Hex::toBytes("0xcfffe746"); diff --git a/tests/contract/calltracer.cpp b/tests/contract/calltracer.cpp index 4ac9b47d..a37ce096 100644 --- a/tests/contract/calltracer.cpp +++ b/tests/contract/calltracer.cpp @@ -15,6 +15,9 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/utils/uintconv.h" #include "../sdktestsuite.hpp" +#include "../blockchainwrapper.hpp" +#include "../src/contract/templates/simplecontract.h" +#include "bytes/hex.h" static const Bytes testBytecode = Hex::toBytes("0x608060405234801561001057600080fd5b50610219806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631003e2d2146100465780634fa522db14610062578063853255cc14610092575b600080fd5b610060600480360381019061005b9190610129565b6100b0565b005b61007c60048036038101906100779190610129565b6100cb565b6040516100899190610165565b60405180910390f35b61009a6100e5565b6040516100a79190610165565b60405180910390f35b806000808282546100c191906101af565b9250508190555050565b60006100d6826100b0565b6100de6100e5565b9050919050565b60008054905090565b600080fd5b6000819050919050565b610106816100f3565b811461011157600080fd5b50565b600081359050610123816100fd565b92915050565b60006020828403121561013f5761013e6100ee565b5b600061014d84828501610114565b91505092915050565b61015f816100f3565b82525050565b600060208201905061017a6000830184610156565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006101ba826100f3565b91506101c5836100f3565b92508282019050808211156101dd576101dc610180565b5b9291505056fea264697066735822122010806f8bd0eb78dd8bf1e05d0621ad54dfe78cd22c6a67e02decd89cd4a2208064736f6c63430008130033"); static const Bytes testProxyBytecode = Hex::toBytes("0x608060405234801561001057600080fd5b50610341806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80637714eaca1461003b5780638e6113211461006b575b600080fd5b61005560048036038101906100509190610232565b61009b565b6040516100629190610281565b60405180910390f35b6100856004803603810190610080919061029c565b610121565b6040516100929190610281565b60405180910390f35b60008273ffffffffffffffffffffffffffffffffffffffff16634fa522db836040518263ffffffff1660e01b81526004016100d69190610281565b6020604051808303816000875af11580156100f5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061011991906102de565b905092915050565b60008173ffffffffffffffffffffffffffffffffffffffff1663853255cc6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561016e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061019291906102de565b9050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101c98261019e565b9050919050565b6101d9816101be565b81146101e457600080fd5b50565b6000813590506101f6816101d0565b92915050565b6000819050919050565b61020f816101fc565b811461021a57600080fd5b50565b60008135905061022c81610206565b92915050565b6000806040838503121561024957610248610199565b5b6000610257858286016101e7565b92505060206102688582860161021d565b9150509250929050565b61027b816101fc565b82525050565b60006020820190506102966000830184610272565b92915050565b6000602082840312156102b2576102b1610199565b5b60006102c0848285016101e7565b91505092915050565b6000815190506102d881610206565b92915050565b6000602082840312156102f4576102f3610199565b5b6000610302848285016102c9565b9150509291505056fea26469706673582212201c63da23a5ecb525a33d51b61ad178576a9dbc8733cc98401d2be1db76021bbf64736f6c63430008130033"); @@ -84,22 +87,6 @@ struct UserWrapper { namespace TCallTracer { TEST_CASE("CallTracer Tests", "[trace]") { - SECTION("Call Type Parsing") { - evmc_message msgCall; - evmc_message msgStaticCall; - evmc_message msgDelegateCall; - evmc_message msgInvalidCall; - msgCall.kind = EVMC_CALL; - msgStaticCall.kind = EVMC_CALL; - msgDelegateCall.kind = EVMC_DELEGATECALL; - msgInvalidCall.kind = evmc_call_kind(-1); - msgStaticCall.flags = EVMC_STATIC; - REQUIRE(trace::getCallType(msgCall) == trace::Call::Type::CALL); - REQUIRE(trace::getCallType(msgStaticCall) == trace::Call::Type::STATICCALL); - REQUIRE(trace::getCallType(msgDelegateCall) == trace::Call::Type::DELEGATECALL); - REQUIRE_THROWS(trace::getCallType(msgInvalidCall)); - } - SECTION("EVM Single Call") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment("TestTraceContracts"); @@ -112,55 +99,38 @@ namespace TCallTracer { sdk.callFunction(contractAddress, &TestWrapper::add, uint256_t(33)); - Hash txHash = getLatestTransactionHash(sdk.getStorage()); - std::optional callTrace = sdk.getStorage().getCallTrace(txHash); - REQUIRE(callTrace); - REQUIRE(callTrace->type == trace::Call::Type::CALL); - REQUIRE(callTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(callTrace->to == contractAddress); - REQUIRE(callTrace->value == FixedBytes<32>()); - // TODO: gas and gasUsed? - // TODO: what are these 4 bytes prefix? - REQUIRE(FixedBytes<32>(callTrace->input | std::views::drop(4)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(33)))); - REQUIRE(callTrace->output == Bytes()); - REQUIRE(callTrace->calls.empty()); - - json callJson = callTrace->toJson(); - REQUIRE(callJson["type"] == "CALL"); - REQUIRE(callJson["from"] == "0x00dead00665771855a34155f5e7405489df2c3c6"); - REQUIRE(callJson["to"] == "0x5b41cef7f46a4a147e31150c3c5ffd077e54d0e1"); - REQUIRE(callJson["value"] == "0x0"); - REQUIRE(callJson["gas"] == "0x9582"); - REQUIRE(callJson["gasUsed"] == "0x6017"); - REQUIRE(callJson["input"] == "0x1003e2d20000000000000000000000000000000000000000000000000000000000000021"); + Hash txHash = getLatestTransactionHash(sdk.getStorage()); + std::optional callTrace = sdk.getStorage().getCallTrace(txHash); + REQUIRE(callTrace); + + REQUIRE(callTrace->type == trace::CallType::CALL); + REQUIRE(callTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(callTrace->to == contractAddress); + REQUIRE(callTrace->value == FixedBytes<32>()); + // TODO: gas and gasUsed? + REQUIRE(FixedBytes<32>(callTrace->input | std::views::drop(4)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(33)))); + + REQUIRE(callTrace->output == Bytes()); + REQUIRE(callTrace->calls.empty()); res = sdk.callViewFunction(contractAddress, &TestWrapper::sum); REQUIRE(res == 33); sdk.callFunction(contractAddress, &TestWrapper::addAndReturn, uint256_t(66)); - txHash = getLatestTransactionHash(sdk.getStorage()); - callTrace = sdk.getStorage().getCallTrace(txHash); - REQUIRE(callTrace); - REQUIRE(callTrace->type == trace::Call::Type::CALL); - REQUIRE(callTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(callTrace->to == contractAddress); - REQUIRE(callTrace->value == FixedBytes<32>()); - // TODO: gas and gasUsed? - // TODO: what are these 4 bytes prefix? - REQUIRE(FixedBytes<32>(callTrace->input | std::views::drop(4)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(66)))); - REQUIRE(callTrace->output == Utils::makeBytes(UintConv::uint256ToBytes(uint256_t(99)))); - REQUIRE(callTrace->calls.empty()); - - json callJson2 = callTrace->toJson(); - REQUIRE(callJson2["type"] == "CALL"); - REQUIRE(callJson2["from"] == "0x00dead00665771855a34155f5e7405489df2c3c6"); - REQUIRE(callJson2["to"] == "0x5b41cef7f46a4a147e31150c3c5ffd077e54d0e1"); - REQUIRE(callJson2["value"] == "0x0"); - REQUIRE(callJson2["gas"] == "0x973c"); - REQUIRE(callJson2["gasUsed"] == "0x6195"); - REQUIRE(callJson2["input"] == "0x4fa522db0000000000000000000000000000000000000000000000000000000000000042"); - REQUIRE(callJson2["output"] == "0x0000000000000000000000000000000000000000000000000000000000000063"); + txHash = getLatestTransactionHash(sdk.getStorage()); + callTrace = sdk.getStorage().getCallTrace(txHash); + REQUIRE(callTrace); + + REQUIRE(callTrace->type == trace::CallType::CALL); + REQUIRE(callTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(callTrace->to == contractAddress); + REQUIRE(callTrace->value == FixedBytes<32>()); + // TODO: gas and gasUsed? + REQUIRE(FixedBytes<32>(callTrace->input | std::views::drop(4)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(66)))); + + REQUIRE(callTrace->output == Utils::makeBytes(UintConv::uint256ToBytes(uint256_t(99)))); + REQUIRE(callTrace->calls.empty()); res = sdk.callViewFunction(contractAddress, &TestWrapper::sum); REQUIRE(res == 99); @@ -187,24 +157,24 @@ namespace TCallTracer { std::optional callTrace = sdk.getStorage().getCallTrace(txHash); REQUIRE(callTrace); - REQUIRE(callTrace->type == trace::Call::Type::CALL); - REQUIRE(callTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(callTrace->to == testProxyContractAddress); - REQUIRE(callTrace->value == FixedBytes<32>()); - // TODO: gas and gasUsed - REQUIRE(Address(callTrace->input | std::views::drop(16) | std::views::take(20)) == testContractAddress); - REQUIRE(FixedBytes<32>(callTrace->input | std::views::drop(36)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(55)))); + REQUIRE(callTrace->type == trace::CallType::CALL); + REQUIRE(callTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(callTrace->to == testProxyContractAddress); + REQUIRE(callTrace->value == FixedBytes<32>()); + // TODO: gas and gasUsed + REQUIRE(Address(callTrace->input | std::views::drop(16) | std::views::take(20)) == testContractAddress); + REQUIRE(FixedBytes<32>(callTrace->input | std::views::drop(36)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(55)))); REQUIRE(callTrace->output == Utils::makeBytes(UintConv::uint256ToBytes(uint256_t(100)))); REQUIRE(callTrace->calls.size() == 1); const trace::Call& nestedCall = callTrace->calls.front(); - REQUIRE(nestedCall.type == trace::Call::Type::CALL); - REQUIRE(nestedCall.from == testProxyContractAddress); - REQUIRE(nestedCall.to == testContractAddress); - REQUIRE(nestedCall.value == FixedBytes<32>()); - REQUIRE(FixedBytes<32>(nestedCall.input | std::views::drop(4)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(55)))); + REQUIRE(nestedCall.type == trace::CallType::CALL); + REQUIRE(nestedCall.from == testProxyContractAddress); + REQUIRE(nestedCall.to == testContractAddress); + REQUIRE(nestedCall.value == FixedBytes<32>()); + REQUIRE(FixedBytes<32>(nestedCall.input | std::views::drop(4)) == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(55)))); REQUIRE(nestedCall.output == Utils::makeBytes(UintConv::uint256ToBytes(uint256_t(100)))); REQUIRE(nestedCall.calls.empty()); @@ -236,34 +206,34 @@ namespace TCallTracer { const auto approveCallTrace = sdk.getStorage().getCallTrace(approveTx); const auto depositCallTrace = sdk.getStorage().getCallTrace(depositTx); - REQUIRE(approveCallTrace); - REQUIRE(approveCallTrace->type == trace::Call::Type::CALL); - REQUIRE(approveCallTrace->status == trace::Status::SUCCEEDED); - REQUIRE(approveCallTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(approveCallTrace->to == erc20); - REQUIRE(approveCallTrace->value == FixedBytes<32>()); - REQUIRE(approveCallTrace->input == Hex::toBytes("0x095ea7b30000000000000000000000006d48fdfe009e309dd5c4e69dec87365bfa0c811900000000000000000000000000000000000000000000000006f05b59d3b20000")); - REQUIRE(approveCallTrace->output == Bytes()); - REQUIRE(approveCallTrace->calls.empty()); - - REQUIRE(depositCallTrace); - REQUIRE(depositCallTrace->type == trace::Call::Type::CALL); - REQUIRE(depositCallTrace->status == trace::Status::SUCCEEDED); - REQUIRE(depositCallTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(depositCallTrace->to == erc20Wrapper); - REQUIRE(depositCallTrace->value == FixedBytes<32>()); - REQUIRE(depositCallTrace->input == Hex::toBytes("0x47e7ef240000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e100000000000000000000000000000000000000000000000006f05b59d3b20000")); - REQUIRE(depositCallTrace->output == Bytes()); - REQUIRE(!depositCallTrace->calls.empty()); - REQUIRE(depositCallTrace->calls[0].type == trace::Call::Type::CALL); - REQUIRE(depositCallTrace->calls[0].status == trace::Status::SUCCEEDED); - REQUIRE(depositCallTrace->calls[0].from == erc20Wrapper); - REQUIRE(depositCallTrace->calls[0].to == erc20); - REQUIRE(depositCallTrace->calls[0].value == FixedBytes<32>()); - REQUIRE(depositCallTrace->calls[0].input == Hex::toBytes("0x23b872dd00000000000000000000000000dead00665771855a34155f5e7405489df2c3c60000000000000000000000006d48fdfe009e309dd5c4e69dec87365bfa0c811900000000000000000000000000000000000000000000000006f05b59d3b20000")); - REQUIRE(depositCallTrace->calls[0].output == Hex::toBytes("0x0000000000000000000000000000000000000000000000000000000000000001")); - REQUIRE(depositCallTrace->calls[0].calls.empty()); - } + REQUIRE(approveCallTrace); + REQUIRE(approveCallTrace->type == trace::CallType::CALL); + REQUIRE(approveCallTrace->status == trace::CallStatus::SUCCEEDED); + REQUIRE(approveCallTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(approveCallTrace->to == erc20); + REQUIRE(approveCallTrace->value == FixedBytes<32>()); + REQUIRE(approveCallTrace->input == Hex::toBytes("0x095ea7b30000000000000000000000006d48fdfe009e309dd5c4e69dec87365bfa0c811900000000000000000000000000000000000000000000000006f05b59d3b20000")); + REQUIRE(approveCallTrace->output == Utils::makeBytes(bytes::hex("0x0000000000000000000000000000000000000000000000000000000000000001"))); + REQUIRE(approveCallTrace->calls.empty()); + + REQUIRE(depositCallTrace); + REQUIRE(depositCallTrace->type == trace::CallType::CALL); + REQUIRE(depositCallTrace->status == trace::CallStatus::SUCCEEDED); + REQUIRE(depositCallTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(depositCallTrace->to == erc20Wrapper); + REQUIRE(depositCallTrace->value == FixedBytes<32>()); + REQUIRE(depositCallTrace->input == Hex::toBytes("0x47e7ef240000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e100000000000000000000000000000000000000000000000006f05b59d3b20000")); + REQUIRE(depositCallTrace->output == Bytes()); + REQUIRE(!depositCallTrace->calls.empty()); + REQUIRE(depositCallTrace->calls[0].type == trace::CallType::CALL); + REQUIRE(depositCallTrace->calls[0].status == trace::CallStatus::SUCCEEDED); + REQUIRE(depositCallTrace->calls[0].from == erc20Wrapper); + REQUIRE(depositCallTrace->calls[0].to == erc20); + REQUIRE(depositCallTrace->calls[0].value == FixedBytes<32>()); + REQUIRE(depositCallTrace->calls[0].input == Hex::toBytes("0x23b872dd00000000000000000000000000dead00665771855a34155f5e7405489df2c3c60000000000000000000000006d48fdfe009e309dd5c4e69dec87365bfa0c811900000000000000000000000000000000000000000000000006f05b59d3b20000")); + REQUIRE(depositCallTrace->calls[0].output == Hex::toBytes("0x0000000000000000000000000000000000000000000000000000000000000001")); + REQUIRE(depositCallTrace->calls[0].calls.empty()); + } SECTION("Errors and payable functions") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment("TestCallTracingErrosAndPays"); @@ -282,81 +252,56 @@ namespace TCallTracer { const Hash validWithdrawTxHash = sdk.callFunction(userAddress, &UserWrapper::tryWithdraw, bankAddress, uint256_t(300)); const Hash payTxHash = sdk.callFunction(bankAddress, &BankWrapper::pay, uint256_t(4568)); - const auto errorCallTrace = sdk.getStorage().getCallTrace(invalidWithdrawTxHash); - const auto successCallTrace = sdk.getStorage().getCallTrace(validWithdrawTxHash); - const auto payCallTrace = sdk.getStorage().getCallTrace(payTxHash); - - Bytes reasonInsufficientFunds = trace::encodeRevertReason("Insufficient funds"); - REQUIRE(trace::decodeRevertReason(reasonInsufficientFunds) == "Insufficient funds"); - REQUIRE_THROWS(trace::decodeRevertReason(Bytes{0x00, 0x01, 0x02, 0x03, 0x04})); // Data has to be exactly 100 bytes - - REQUIRE(errorCallTrace); - REQUIRE(errorCallTrace->type == trace::Call::Type::CALL); - REQUIRE(errorCallTrace->status == trace::Status::SUCCEEDED); - REQUIRE(errorCallTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(errorCallTrace->to == userAddress); - REQUIRE(errorCallTrace->value == FixedBytes<32>()); - REQUIRE(errorCallTrace->input == Hex::toBytes("0x7f3358bc0000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e100000000000000000000000000000000000000000000000000000000000001f5")); - REQUIRE(errorCallTrace->output == Bytes(32)); - REQUIRE(!errorCallTrace->calls.empty()); - REQUIRE(errorCallTrace->calls[0].type == trace::Call::Type::CALL); - REQUIRE(errorCallTrace->calls[0].status == trace::Status::EXECUTION_REVERTED); - REQUIRE(errorCallTrace->calls[0].from == userAddress); - REQUIRE(errorCallTrace->calls[0].to == bankAddress); - REQUIRE(errorCallTrace->calls[0].value == FixedBytes<32>()); - REQUIRE(errorCallTrace->calls[0].input == Hex::toBytes("0x2e1a7d4d00000000000000000000000000000000000000000000000000000000000001f5")); - REQUIRE(errorCallTrace->calls[0].output == reasonInsufficientFunds); - REQUIRE(errorCallTrace->calls[0].calls.empty()); - - json errorJson = errorCallTrace->toJson(); - REQUIRE(errorJson["type"] == "CALL"); - REQUIRE(errorJson["from"] == "0x00dead00665771855a34155f5e7405489df2c3c6"); - REQUIRE(errorJson["to"] == "0x6d48fdfe009e309dd5c4e69dec87365bfa0c8119"); - REQUIRE(errorJson["value"] == "0x0"); - REQUIRE(errorJson["gas"] == "0xa611"); - REQUIRE(errorJson["gasUsed"] == "0x6e7b"); - REQUIRE(errorJson["input"] == "0x7f3358bc0000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e100000000000000000000000000000000000000000000000000000000000001f5"); - REQUIRE(errorJson.contains("calls")); - REQUIRE(!errorJson["calls"].empty()); - json errorJsonCall = errorJson["calls"][0]; - REQUIRE(errorJsonCall["type"] == "CALL"); - REQUIRE(errorJsonCall["from"] == "0x6d48fdfe009e309dd5c4e69dec87365bfa0c8119"); - REQUIRE(errorJsonCall["to"] == "0x5b41cef7f46a4a147e31150c3c5ffd077e54d0e1"); - REQUIRE(errorJsonCall["value"] == "0x0"); - REQUIRE(errorJsonCall["gas"] == "0x9f5c"); - REQUIRE(errorJsonCall["gasUsed"] == "0x16bb"); - REQUIRE(errorJsonCall["input"] == "0x2e1a7d4d00000000000000000000000000000000000000000000000000000000000001f5"); - REQUIRE(errorJsonCall["output"] == "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012496e73756666696369656e742066756e64730000000000000000000000000000"); - REQUIRE(errorJsonCall["error"] == "execution reverted"); - REQUIRE(errorJsonCall["revertReason"] == "Insufficient funds"); - - REQUIRE(successCallTrace); - REQUIRE(successCallTrace->type == trace::Call::Type::CALL); - REQUIRE(successCallTrace->status == trace::Status::SUCCEEDED); - REQUIRE(successCallTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(successCallTrace->to == userAddress); - REQUIRE(successCallTrace->value == FixedBytes<32>()); - REQUIRE(successCallTrace->input == Hex::toBytes("0x7f3358bc0000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e1000000000000000000000000000000000000000000000000000000000000012c")); - REQUIRE(successCallTrace->output == Hex::toBytes("0x0000000000000000000000000000000000000000000000000000000000000001")); - REQUIRE(!successCallTrace->calls.empty()); - REQUIRE(successCallTrace->calls[0].type == trace::Call::Type::CALL); - REQUIRE(successCallTrace->calls[0].status == trace::Status::SUCCEEDED); - REQUIRE(successCallTrace->calls[0].from == userAddress); - REQUIRE(successCallTrace->calls[0].to == bankAddress); - REQUIRE(successCallTrace->calls[0].value == FixedBytes<32>()); - REQUIRE(successCallTrace->calls[0].input == Hex::toBytes("0x2e1a7d4d000000000000000000000000000000000000000000000000000000000000012c")); - REQUIRE(successCallTrace->calls[0].output == Bytes()); - REQUIRE(successCallTrace->calls[0].calls.empty()); - - REQUIRE(payCallTrace); - REQUIRE(payCallTrace->type == trace::Call::Type::CALL); - REQUIRE(payCallTrace->status == trace::Status::SUCCEEDED); - REQUIRE(payCallTrace->from == sdk.getOptions().getChainOwner()); - REQUIRE(payCallTrace->to == bankAddress); - REQUIRE(payCallTrace->value == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(4568)))); - REQUIRE(payCallTrace->input == Hex::toBytes("0x1b9265b8")); - REQUIRE(payCallTrace->output == Bytes()); - REQUIRE(payCallTrace->calls.empty()); - } + const auto errorCallTrace = sdk.getStorage().getCallTrace(invalidWithdrawTxHash); + const auto successCallTrace = sdk.getStorage().getCallTrace(validWithdrawTxHash); + const auto payCallTrace = sdk.getStorage().getCallTrace(payTxHash); + + REQUIRE(errorCallTrace); + REQUIRE(errorCallTrace->type == trace::CallType::CALL); + REQUIRE(errorCallTrace->status == trace::CallStatus::SUCCEEDED); + REQUIRE(errorCallTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(errorCallTrace->to == userAddress); + REQUIRE(errorCallTrace->value == FixedBytes<32>()); + REQUIRE(errorCallTrace->input == Hex::toBytes("0x7f3358bc0000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e100000000000000000000000000000000000000000000000000000000000001f5")); + REQUIRE(errorCallTrace->output == Bytes(32)); + REQUIRE(!errorCallTrace->calls.empty()); + REQUIRE(errorCallTrace->calls[0].type == trace::CallType::CALL); + REQUIRE(errorCallTrace->calls[0].status == trace::CallStatus::EXECUTION_REVERTED); + REQUIRE(errorCallTrace->calls[0].from == userAddress); + REQUIRE(errorCallTrace->calls[0].to == bankAddress); + REQUIRE(errorCallTrace->calls[0].value == FixedBytes<32>()); + REQUIRE(errorCallTrace->calls[0].input == Hex::toBytes("0x2e1a7d4d00000000000000000000000000000000000000000000000000000000000001f5")); + REQUIRE(errorCallTrace->calls[0].output == ABI::Encoder::encodeError("Insufficient funds")); + REQUIRE(errorCallTrace->calls[0].calls.empty()); + + REQUIRE(successCallTrace); + REQUIRE(successCallTrace->type == trace::CallType::CALL); + REQUIRE(successCallTrace->status == trace::CallStatus::SUCCEEDED); + REQUIRE(successCallTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(successCallTrace->to == userAddress); + REQUIRE(successCallTrace->value == FixedBytes<32>()); + REQUIRE(successCallTrace->input == Hex::toBytes("0x7f3358bc0000000000000000000000005b41cef7f46a4a147e31150c3c5ffd077e54d0e1000000000000000000000000000000000000000000000000000000000000012c")); + REQUIRE(successCallTrace->output == Hex::toBytes("0x0000000000000000000000000000000000000000000000000000000000000001")); + REQUIRE(!successCallTrace->calls.empty()); + REQUIRE(successCallTrace->calls[0].type == trace::CallType::CALL); + REQUIRE(successCallTrace->calls[0].status == trace::CallStatus::SUCCEEDED); + REQUIRE(successCallTrace->calls[0].from == userAddress); + REQUIRE(successCallTrace->calls[0].to == bankAddress); + REQUIRE(successCallTrace->calls[0].value == FixedBytes<32>()); + REQUIRE(successCallTrace->calls[0].input == Hex::toBytes("0x2e1a7d4d000000000000000000000000000000000000000000000000000000000000012c")); + REQUIRE(successCallTrace->calls[0].output == Bytes()); + REQUIRE(successCallTrace->calls[0].calls.empty()); + + REQUIRE(payCallTrace); + REQUIRE(payCallTrace->type == trace::CallType::CALL); + REQUIRE(payCallTrace->status == trace::CallStatus::SUCCEEDED); + REQUIRE(payCallTrace->from == sdk.getOptions().getChainOwner()); + REQUIRE(payCallTrace->to == bankAddress); + REQUIRE(payCallTrace->value == FixedBytes<32>(UintConv::uint256ToBytes(uint256_t(4568)))); + REQUIRE(payCallTrace->input == Hex::toBytes("0x1b9265b8")); + REQUIRE(payCallTrace->output == Bytes()); + REQUIRE(payCallTrace->calls.empty()); } +} + } // namespace TCallTracer diff --git a/tests/contract/createcontract.cpp b/tests/contract/createcontract.cpp index b80ff692..42177bfc 100644 --- a/tests/contract/createcontract.cpp +++ b/tests/contract/createcontract.cpp @@ -87,7 +87,7 @@ namespace TContractRandomness { SECTION("EVM Create Another EVM Contract Test") { auto sdk = SDKTestSuite::createNewEnvironment("ContractCreateContract"); auto createContractAddress = sdk.deployBytecode(contractCreateAnotherContractBytecode); - auto salt = Hash::random(); + Hash salt = bytes::random(); REQUIRE(sdk.getState().getEvmContracts().size() == 1); sdk.callFunction(createContractAddress, &SolCreateContract::deployWithNew, uint256_t(100)); Address newContractAddress = Address(); @@ -110,14 +110,14 @@ namespace TContractRandomness { } REQUIRE(sdk.getState().getEvmContracts().size() == 3); REQUIRE(sdk.getNativeNonce(createContractAddress) == 2); // 1 when contract was created + 1 when contract called CREATE, CREATE2 does **NOT** increment nonce - REQUIRE(newContractAddress == ContractHost::deriveContractAddress(1, createContractAddress)); + REQUIRE(newContractAddress == generateContractAddress(1, createContractAddress)); Bytes init_code = Hex::toBytes("6080604052348015600e575f80fd5b506040516101493803806101498339818101604052810190602e9190606b565b805f81905550506091565b5f80fd5b5f819050919050565b604d81603d565b81146056575f80fd5b50565b5f815190506065816046565b92915050565b5f60208284031215607d57607c6039565b5b5f6088848285016059565b91505092915050565b60ac8061009d5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c80633fa4f24514602a575b5f80fd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea2646970667358221220800668e87144b8625a7e59ac82528e013d51d6ed08562ba8b641f0a2e66c0f3764736f6c634300081a00330000000000000000000000000000000000000000000000000000000000000064"); - REQUIRE(newContractAddressCreate2 == ContractHost::deriveContractAddress(createContractAddress, salt, init_code)); + REQUIRE(newContractAddressCreate2 == generateContractAddress(createContractAddress, salt, init_code)); // For coverage std::string contractCode = "6080604052348015600e575f80fd5b50600436106026575f3560e01c80633fa4f24514602a575b5f80fd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea2646970667358221220800668e87144b8625a7e59ac82528e013d51d6ed08562ba8b641f0a2e66c0f3764736f6c634300081a0033"; REQUIRE(Hex::fromBytes(sdk.getState().getContractCode(newContractAddressCreate2)).get() == contractCode); - Address randAdd("0x1234567890123456789012345678901234567890", false); + Address randAdd(bytes::hex("0x1234567890123456789012345678901234567890")); REQUIRE(Hex::fromBytes(sdk.getState().getContractCode(randAdd)).get() == ""); } } diff --git a/tests/contract/dexv2.cpp b/tests/contract/dexv2.cpp index 261afc3b..91c50dab 100644 --- a/tests/contract/dexv2.cpp +++ b/tests/contract/dexv2.cpp @@ -39,7 +39,7 @@ namespace TDEXV2 { Address pair; Address tokenA; Address tokenB; - Address chainOwner("0x00dead00665771855a34155f5e7405489df2c3c6", false); + Address chainOwner(bytes::hex("0x00dead00665771855a34155f5e7405489df2c3c6")); std::unique_ptr options; { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment("testDEXV2Pair"); @@ -133,7 +133,7 @@ namespace TDEXV2 { std::vector
allPairs = sdk.callViewFunction(factory, &DEXV2Factory::allPairs); REQUIRE(allPairs.size() == 1); REQUIRE(allPairs[0] == pair); - Address add("0x1234567890123456789012345678901234567890", false); + Address add(bytes::hex("0x1234567890123456789012345678901234567890")); sdk.callFunction(factory, &DEXV2Factory::setFeeTo, add); sdk.callFunction(factory, &DEXV2Factory::setFeeToSetter, add); REQUIRE(sdk.callViewFunction(factory, &DEXV2Factory::feeTo) == add); diff --git a/tests/contract/erc20.cpp b/tests/contract/erc20.cpp index 77c917a4..c518ba95 100644 --- a/tests/contract/erc20.cpp +++ b/tests/contract/erc20.cpp @@ -11,6 +11,8 @@ See the LICENSE.txt file in the project root for more information. #include "../sdktestsuite.hpp" +#include "bytes/hex.h" + // TODO: test events if/when implemented namespace TERC20 { @@ -80,7 +82,7 @@ namespace TERC20 { REQUIRE(std::get<0>(ABI::Decoder::decodeData(approveEvents[0].getData())) == uint256_t("500000000000000000")); // Search for a non-existing spender (for coverage) - Address ghost("0x1234567890123456789012345678901234567890", false); + Address ghost(bytes::hex("0x1234567890123456789012345678901234567890")); REQUIRE(sdk.callViewFunction(erc20, &ERC20::allowance, owner, ghost) == uint256_t(0)); } diff --git a/tests/contract/erc721test.cpp b/tests/contract/erc721test.cpp index 5c58491e..8e31688c 100644 --- a/tests/contract/erc721test.cpp +++ b/tests/contract/erc721test.cpp @@ -10,6 +10,8 @@ #include "../sdktestsuite.hpp" +#include "bytes/hex.h" + namespace TERC721Test { TEST_CASE("ERC721Test Class", "[contract][erc721test]") { SECTION("ERC721Test Creation + Dump") { @@ -70,8 +72,8 @@ namespace TERC721Test { REQUIRE_THROWS(sdk.callFunction(ERC721Address, &ERC721Test::mint, Address())); // Try transferring to zero address and from wrong owner - Address add1("0x1234567890123456789012345678901234567890", false); - Address add2("0x0987654321098765432109876543210987654321", false); + Address add1(bytes::hex("0x1234567890123456789012345678901234567890")); + Address add2(bytes::hex("0x0987654321098765432109876543210987654321")); REQUIRE_THROWS(sdk.callFunction(ERC721Address, &ERC721Test::transferFrom, sdk.getChainOwnerAccount().address, Address(), uint256_t(0))); REQUIRE_THROWS(sdk.callFunction(ERC721Address, &ERC721Test::transferFrom, add1, add2, uint256_t(0))); diff --git a/tests/contract/event.cpp b/tests/contract/event.cpp index 02f1dda3..dde1561f 100644 --- a/tests/contract/event.cpp +++ b/tests/contract/event.cpp @@ -5,19 +5,19 @@ This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ -#include "../../src/libs/catch2/catch_amalgamated.hpp" - -#include "../../src/contract/event.h" - -#include "../../src/bytes/view.h" +#include "libs/catch2/catch_amalgamated.hpp" +#include "contract/event.h" +#include "bytes/view.h" +#include "bytes/random.h" +#include "bytes/hex.h" namespace TEvent { TEST_CASE("Event Class", "[contract][event]") { SECTION("Event Constructor (EVM)") { - Hash txHash = Hash::random(); - Hash blockHash = Hash::random(); - std::vector topics = {Hash::random(), Hash::random(), Hash::random(), Hash::random(), Hash::random()}; - Address add("0x1234567890123456789012345678901234567890", false); + Hash txHash = bytes::random(); + Hash blockHash = bytes::random(); + std::vector topics = {bytes::random(), bytes::random(), bytes::random(), bytes::random(), bytes::random()}; + Address add(bytes::hex("0x1234567890123456789012345678901234567890")); Bytes data{0xDE, 0xAD, 0xBE, 0xEF}; Event e("myEvent", 0, txHash, 1, blockHash, 2, add, data, topics, false); @@ -40,9 +40,9 @@ namespace TEvent { } SECTION("Event Constructor (CPP)") { - Hash txHash = Hash::random(); - Hash blockHash = Hash::random(); - Address add("0x1234567890123456789012345678901234567890", false); + Hash txHash = bytes::random(); + Hash blockHash = bytes::random(); + Address add(bytes::hex("0x1234567890123456789012345678901234567890")); // Anonymous event Event e1("myEvent", 0, txHash, 1, blockHash, 2, add, std::make_tuple( @@ -129,7 +129,7 @@ namespace TEvent { SECTION("Event Serialization (Normal + RPC)") { Hash txHash(Hex::toBytes("0x53472c61f1db8612fcdd17f24b78986bfa111ea3e323522456b1a78560f2215a")); Hash blockHash(Hex::toBytes("0x2b9b8644330d50ffb90c5fea02b73b562dfc550ec7f8c85f643b20391a972d5f")); - Address add("0x1234567890123456789012345678901234567890", false); + Address add(bytes::hex("0x1234567890123456789012345678901234567890")); Event e1("myEvent", 0, txHash, 1, blockHash, 2, add, std::make_tuple( EventParam("p1"), EventParam("p2"), diff --git a/tests/contract/evmcontractexecutor.cpp b/tests/contract/evmcontractexecutor.cpp new file mode 100644 index 00000000..42b679df --- /dev/null +++ b/tests/contract/evmcontractexecutor.cpp @@ -0,0 +1,182 @@ +/* +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 "evmone/evmone.h" +#include "catch2/catch_amalgamated.hpp" +#include "contract/evmcontractexecutor.h" +#include "bytes/random.h" +#include "bytes/hex.h" +#include "bytes/cast.h" +#include "utils/utils.h" + +/** + * Test cases + * + * 1. Simple function call, run some math code, whatever + * 2. Nested function call + * 3. Call with value + * 4. Out of gas call? + * 5. Delegate Call + * 6. Attempt to make mutable call from static call (error expected) + * 7. Any other error call? + * + * 8... Corner cases? + */ + +constexpr Address ORIGIN_ADDRESS = bytes::hex("0x00dead00665771855a34155f5e7405489df2c3c6"); +constexpr FixedBytes<585> SIMPLE_CONTRACT_BYTECODE = bytes::hex("0x608060405234801561001057600080fd5b50610229806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806306661abd1461003b57806311a1d3e814610059575b600080fd5b610043610089565b60405161005091906100d5565b60405180910390f35b610073600480360381019061006e9190610121565b610092565b60405161008091906100d5565b60405180910390f35b60008054905090565b600060016000546100a3919061017d565b60008190555081826100b591906101b1565b9050919050565b6000819050919050565b6100cf816100bc565b82525050565b60006020820190506100ea60008301846100c6565b92915050565b600080fd5b6100fe816100bc565b811461010957600080fd5b50565b60008135905061011b816100f5565b92915050565b600060208284031215610137576101366100f0565b5b60006101458482850161010c565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610188826100bc565b9150610193836100bc565b92508282019050808211156101ab576101aa61014e565b5b92915050565b60006101bc826100bc565b91506101c7836100bc565b92508282026101d5816100bc565b915082820484148315176101ec576101eb61014e565b5b509291505056fea26469706673582212200803e42b1acf7991b8d0f02907360f1e72660bcb1a223e3e5b29bdcf8b283b2664736f6c63430008130033"); +constexpr FixedBytes<477> FUNDS_CONTRACT_BYTECODE = bytes::hex("0x608060405234801561001057600080fd5b506101bd806100206000396000f3fe6080604052600436106100295760003560e01c8063d0e30db01461002e578063f3fef3a314610038575b600080fd5b610036610061565b005b34801561004457600080fd5b5061005f600480360381019061005a9190610147565b610063565b005b565b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156100a9573d6000803e3d6000fd5b505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100de826100b3565b9050919050565b6100ee816100d3565b81146100f957600080fd5b50565b60008135905061010b816100e5565b92915050565b6000819050919050565b61012481610111565b811461012f57600080fd5b50565b6000813590506101418161011b565b92915050565b6000806040838503121561015e5761015d6100ae565b5b600061016c858286016100fc565b925050602061017d85828601610132565b915050925092905056fea2646970667358221220f033d70d7f542aeddd138086a36a545f06b35e7a56f135b907fa8a98160682b064736f6c63430008130033"); +const uint256_t ONE_ETHER{"1000000000000000000"}; +constexpr Hash EMPTY_HASH{}; + +struct NoOpObserver { + constexpr void operator()(auto&&) const {} +}; + +template +struct MockedEnvironment { + MockedEnvironment(Observer observer) + : vm(evmc_create_evmone(), evmc_destroy), storage(), accounts(), context(ExecutionContext::Builder().storage(storage).accounts(accounts).build()), + executor(AnyEncodedMessageHandler::from(*this), context, vm.get()) {} + + MockedEnvironment() : MockedEnvironment(NoOpObserver{}) {} + + ~MockedEnvironment() = default; + MockedEnvironment(const MockedEnvironment&) = delete; + MockedEnvironment(MockedEnvironment&&) = delete; + MockedEnvironment& operator=(const MockedEnvironment&) = delete; + MockedEnvironment& operator=(MockedEnvironment&&) = delete; + + Bytes onMessage(concepts::CallMessage auto&& msg) { + std::invoke(observer_, msg); + return executor.execute(std::forward(msg)); + } + + Address onMessage(concepts::CreateMessage auto&& msg) { + std::invoke(observer_, msg); + return executor.execute(std::forward(msg)); + } + + std::unique_ptr vm; + ExecutionContext::Storage storage; + ExecutionContext::Accounts accounts; + ExecutionContext context; + EvmContractExecutor executor; + Observer observer_; +}; + +MockedEnvironment() -> MockedEnvironment; + +TEST_CASE("Evm Message Executor Tests", "[evmcontractexecutor]") { + // SECTION("Simple calls") { + // MockedEnvironment env; + + // env.accounts.emplace(ORIGIN_ADDRESS, Account(1000000, 0)); + + // Gas gas(1000000); + // uint256_t value = 0; + // const Bytes getCountInput = Utils::makeBytes(bytes::hex("0x06661abd")); + // const Bytes getSquaredInput = Utils::makeBytes(bytes::hex("0x11a1d3e80000000000000000000000000000000000000000000000000000000000000019")); + // const Address expectedContractAddress = bytes::hex("0x5b41cef7f46a4a147e31150c3c5ffd077e54d0e1"); + + // EncodedCreateMessage createMessage(ORIGIN_ADDRESS, gas, value, SIMPLE_CONTRACT_BYTECODE); + // EncodedStaticCallMessage getCountMessage(ORIGIN_ADDRESS, expectedContractAddress, gas, getCountInput); + // EncodedCallMessage getSquaredMessage(ORIGIN_ADDRESS, expectedContractAddress, gas, value, getSquaredInput); + + // const Address contractAddress = env.executor.execute(createMessage); + // REQUIRE(contractAddress == expectedContractAddress); + + // uint256_t count = std::get<0>(ABI::Decoder::decodeData(env.executor.execute(getCountMessage))); + // REQUIRE(count == uint256_t(0)); + + // uint256_t squared = std::get<0>(ABI::Decoder::decodeData(env.executor.execute(getSquaredMessage))); + // REQUIRE(squared == uint256_t(625)); + + // count = std::get<0>(ABI::Decoder::decodeData(env.executor.execute(getCountMessage))); + // REQUIRE(count == uint256_t(1)); + + // value = 200; + // REQUIRE_THROWS(env.executor.execute(getSquaredMessage)); + // } + + // SECTION("Calls with value") { + // MockedEnvironment env([] (const auto& msg) { + // printf("foo!\n"); + // }); + + // const Address firstAccountAddress = bytes::hex("0x05ed5b0cec75408bb57ab2a5413b9d6b0d6c756f"); + // const Address secondAccountAddress = bytes::hex("0xf11abf1f64dbcc256599b57e82f8d6654b0aba40"); + + // Account& firstAccount = *env.accounts.emplace(firstAccountAddress, Account(ONE_ETHER, 1)).first->second; + // Account& secondAccount = *env.accounts.emplace(secondAccountAddress, Account(0, 1)).first->second; + + // const Bytes fundsBytecode = Utils::makeBytes(bytes::hex("0x608060405234801561001057600080fd5b50610225806100206000396000f3fe6080604052600436106100345760003560e01c8063b69ef8a814610039578063d0e30db014610064578063f3fef3a31461006e575b600080fd5b34801561004557600080fd5b5061004e610097565b60405161005b9190610105565b60405180910390f35b61006c61009f565b005b34801561007a57600080fd5b50610095600480360381019061009091906101af565b6100a1565b005b600047905090565b565b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156100e7573d6000803e3d6000fd5b505050565b6000819050919050565b6100ff816100ec565b82525050565b600060208201905061011a60008301846100f6565b92915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061015082610125565b9050919050565b61016081610145565b811461016b57600080fd5b50565b60008135905061017d81610157565b92915050565b61018c816100ec565b811461019757600080fd5b50565b6000813590506101a981610183565b92915050565b600080604083850312156101c6576101c5610120565b5b60006101d48582860161016e565b92505060206101e58582860161019a565b915050925092905056fea26469706673582212206df27ae5d2764317da225f7c3853c25e3786f13b2f78691a76f215a48ab6955a64736f6c63430008130033")); + // const Bytes casinoBytecode = Utils::makeBytes(bytes::hex("0x608060405260016000806101000a81548160ff02191690831515021790555034801561002a57600080fd5b506103ad8061003a6000396000f3fe6080604052600436106100555760003560e01c806311610c251461005a5780632ad957861461006457806343d726d61461008d57806347535d7b146100a4578063b69ef8a8146100cf578063fcfff16f146100fa575b600080fd5b610062610111565b005b34801561007057600080fd5b5061008b60048036038101906100869190610263565b610160565b005b34801561009957600080fd5b506100a26101aa565b005b3480156100b057600080fd5b506100b96101c6565b6040516100c691906102ab565b60405180910390f35b3480156100db57600080fd5b506100e46101dc565b6040516100f191906102df565b60405180910390f35b34801561010657600080fd5b5061010f6101e4565b005b60008054906101000a900460ff1661015e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161015590610357565b60405180910390fd5b565b8073ffffffffffffffffffffffffffffffffffffffff166108fc479081150290604051600060405180830381858888f193505050501580156101a6573d6000803e3d6000fd5b5050565b60008060006101000a81548160ff021916908315150217905550565b60008060009054906101000a900460ff16905090565b600047905090565b60016000806101000a81548160ff021916908315150217905550565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061023082610205565b9050919050565b61024081610225565b811461024b57600080fd5b50565b60008135905061025d81610237565b92915050565b60006020828403121561027957610278610200565b5b60006102878482850161024e565b91505092915050565b60008115159050919050565b6102a581610290565b82525050565b60006020820190506102c0600083018461029c565b92915050565b6000819050919050565b6102d9816102c6565b82525050565b60006020820190506102f460008301846102d0565b92915050565b600082825260208201905092915050565b7f6265747320636c6f736564000000000000000000000000000000000000000000600082015250565b6000610341600b836102fa565b915061034c8261030b565b602082019050919050565b6000602082019050818103600083015261037081610334565b905091905056fea26469706673582212207757c95d521b5892342b02350317f2c90bcb46f0e30b16a13de45e34c659e21764736f6c63430008130033")); + // const Bytes gamblerBytecode = Utils::makeBytes(bytes::hex("0x608060405234801561001057600080fd5b50610292806100206000396000f3fe6080604052600436106100295760003560e01c80632c5433051461002e578063b69ef8a81461004a575b600080fd5b610048600480360381019061004391906101e8565b610075565b005b34801561005657600080fd5b5061005f61017d565b60405161006c9190610241565b60405180910390f35b600082905060008290508173ffffffffffffffffffffffffffffffffffffffff166311610c25346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156100c757600080fd5b505af1935050505080156100d9575060015b610176573d8060008114610109576040519150601f19603f3d011682016040523d82523d6000602084013e61010e565b606091505b508173ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561015757600080fd5b505af115801561016b573d6000803e3d6000fd5b505050505050610177565b5b50505050565b600047905090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101b58261018a565b9050919050565b6101c5816101aa565b81146101d057600080fd5b50565b6000813590506101e2816101bc565b92915050565b600080604083850312156101ff576101fe610185565b5b600061020d858286016101d3565b925050602061021e858286016101d3565b9150509250929050565b6000819050919050565b61023b81610228565b82525050565b60006020820190506102566000830184610232565b9291505056fea2646970667358221220ce419e18421bafa55bf72c1e46652f2e313a338b574ece304a4044cb4cc9c05164736f6c63430008130033")); + + // Gas gas(1'000'000); + // uint256_t value = 0; + // Bytes input; + + // const Address fundsAddress = env.executor.execute(EncodedCreateMessage(firstAccountAddress, gas, value, fundsBytecode)); + // firstAccount.nonce++; + // const Address casinoAddress = env.executor.execute(EncodedCreateMessage(firstAccountAddress, gas, value, casinoBytecode)); + // firstAccount.nonce++; + // const Address gamblerAddress = env.executor.execute(EncodedCreateMessage(firstAccountAddress, gas, value, gamblerBytecode)); + // firstAccount.nonce++; + + // // TODO: with the call bellow, things doesn't work + // input.clear(); + // auto functor1 = ABI::FunctorEncoder::encode<>("close"); + // Utils::appendBytes(input, UintConv::uint32ToBytes(functor1.value)); + // env.executor.execute(EncodedCallMessage(firstAccountAddress, casinoAddress, gas, value, input)); + + // input.clear(); + // auto functor2 = ABI::FunctorEncoder::encode("gamble"); + // Utils::appendBytes(input, UintConv::uint32ToBytes(functor2.value)); + // Utils::appendBytes(input, ABI::Encoder::encodeData(casinoAddress, fundsAddress)); + + // value = ONE_ETHER / 2; + // env.executor.execute(EncodedCallMessage(firstAccountAddress, gamblerAddress, gas, value, input)); + + // std::cout << "gambler balance: " << env.accounts.at(gamblerAddress)->balance << "\n"; + // std::cout << "casino balance: " << env.accounts.at(casinoAddress)->balance << "\n"; + // std::cout << "funds balance: " << env.accounts.at(fundsAddress)->balance << "\n"; + // } + + // SECTION("Delegate calls") { + // // TODO: the code field from the create message also has the constructor arguments + + // MockedEnvironment env; + // Gas gas(1'000'000); + // uint256_t value = 0; + + // const Address accountAddress = bytes::hex("0x05ed5b0cec75408bb57ab2a5413b9d6b0d6c756f"); + // Account& account = *env.accounts.emplace(accountAddress, Account(ONE_ETHER, 1)).first->second; + + // const Bytes delegatorBytecode = Utils::makeBytes(bytes::hex("0x60806040526040518060400160405280600681526020017f416c666163650000000000000000000000000000000000000000000000000000815250600090816200004a91906200032e565b5073ab8483f64d9c6d1ecf9b849ae677dd3315835cb2600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550348015620000ad57600080fd5b5062000415565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200013657607f821691505b6020821081036200014c576200014b620000ee565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620001b67fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000177565b620001c2868362000177565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006200020f620002096200020384620001da565b620001e4565b620001da565b9050919050565b6000819050919050565b6200022b83620001ee565b620002436200023a8262000216565b84845462000184565b825550505050565b600090565b6200025a6200024b565b6200026781848462000220565b505050565b5b818110156200028f576200028360008262000250565b6001810190506200026d565b5050565b601f821115620002de57620002a88162000152565b620002b38462000167565b81016020851015620002c3578190505b620002db620002d28562000167565b8301826200026c565b50505b505050565b600082821c905092915050565b60006200030360001984600802620002e3565b1980831691505092915050565b60006200031e8383620002f0565b9150826002028217905092915050565b6200033982620000b4565b67ffffffffffffffff811115620003555762000354620000bf565b5b6200036182546200011d565b6200036e82828562000293565b600060209050601f831160018114620003a6576000841562000391578287015190505b6200039d858262000310565b8655506200040d565b601f198416620003b68662000152565b60005b82811015620003e057848901518255600182019150602085019450602081019050620003b9565b86831015620004005784890151620003fc601f891682620002f0565b8355505b6001600288020188555050505b505050505050565b61076e80620004256000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063893d20e81461005c578063ce6d41de1461007a578063d129b79a14610098578063d6026296146100b6578063e7663079146100d2575b600080fd5b6100646100f0565b60405161007191906103e6565b60405180910390f35b61008261011a565b60405161008f9190610491565b60405180910390f35b6100a06101ac565b6040516100ad9190610491565b60405180910390f35b6100d060048036038101906100cb919061054e565b61023a565b005b6100da61037f565b6040516100e791906103e6565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060008054610129906105dd565b80601f0160208091040260200160405190810160405280929190818152602001828054610155906105dd565b80156101a25780601f10610177576101008083540402835291602001916101a2565b820191906000526020600020905b81548152906001019060200180831161018557829003601f168201915b5050505050905090565b600080546101b9906105dd565b80601f01602080910402602001604051908101604052809291908181526020018280546101e5906105dd565b80156102325780601f1061020757610100808354040283529160200191610232565b820191906000526020600020905b81548152906001019060200180831161021557829003601f168201915b505050505081565b6000808473ffffffffffffffffffffffffffffffffffffffff1663368b877260e01b858560405160240161026f92919061064a565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516102d991906106b5565b600060405180830381855af49150503d8060008114610314576040519150601f19603f3d011682016040523d82523d6000602084013e610319565b606091505b509150915060001515821515036103785760008151111561033d5780518082602001fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161036f90610718565b60405180910390fd5b5050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103d0826103a5565b9050919050565b6103e0816103c5565b82525050565b60006020820190506103fb60008301846103d7565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561043b578082015181840152602081019050610420565b60008484015250505050565b6000601f19601f8301169050919050565b600061046382610401565b61046d818561040c565b935061047d81856020860161041d565b61048681610447565b840191505092915050565b600060208201905081810360008301526104ab8184610458565b905092915050565b600080fd5b600080fd5b6104c6816103c5565b81146104d157600080fd5b50565b6000813590506104e3816104bd565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261050e5761050d6104e9565b5b8235905067ffffffffffffffff81111561052b5761052a6104ee565b5b602083019150836001820283011115610547576105466104f3565b5b9250929050565b600080600060408486031215610567576105666104b3565b5b6000610575868287016104d4565b935050602084013567ffffffffffffffff811115610596576105956104b8565b5b6105a2868287016104f8565b92509250509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806105f557607f821691505b602082108103610608576106076105ae565b5b50919050565b82818337600083830152505050565b6000610629838561040c565b935061063683858461060e565b61063f83610447565b840190509392505050565b6000602082019050818103600083015261066581848661061d565b90509392505050565b600081519050919050565b600081905092915050565b600061068f8261066e565b6106998185610679565b93506106a981856020860161041d565b80840191505092915050565b60006106c18284610684565b915081905092915050565b7f46756e6374696f6e2063616c6c20726576657274656400000000000000000000600082015250565b600061070260168361040c565b915061070d826106cc565b602082019050919050565b60006020820190508181036000830152610731816106f5565b905091905056fea26469706673582212207bb9c9ffbef5fbdb6a396f9c4e1962581051cafee1ecbf4367476185b9bdaadd64736f6c63430008130033")); + // const Bytes delegatedBytecode = Utils::makeBytes(bytes::hex("0x60806040526040518060400160405280600681526020017f416c666163650000000000000000000000000000000000000000000000000000815250600090816200004a919062000340565b503480156200005857600080fd5b5060405162000caa38038062000caa83398181016040528101906200007e919062000491565b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050620004c3565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200014857607f821691505b6020821081036200015e576200015d62000100565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620001c87fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000189565b620001d4868362000189565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620002216200021b6200021584620001ec565b620001f6565b620001ec565b9050919050565b6000819050919050565b6200023d8362000200565b620002556200024c8262000228565b84845462000196565b825550505050565b600090565b6200026c6200025d565b6200027981848462000232565b505050565b5b81811015620002a1576200029560008262000262565b6001810190506200027f565b5050565b601f821115620002f057620002ba8162000164565b620002c58462000179565b81016020851015620002d5578190505b620002ed620002e48562000179565b8301826200027e565b50505b505050565b600082821c905092915050565b60006200031560001984600802620002f5565b1980831691505092915050565b600062000330838362000302565b9150826002028217905092915050565b6200034b82620000c6565b67ffffffffffffffff811115620003675762000366620000d1565b5b6200037382546200012f565b62000380828285620002a5565b600060209050601f831160018114620003b85760008415620003a3578287015190505b620003af858262000322565b8655506200041f565b601f198416620003c88662000164565b60005b82811015620003f257848901518255600182019150602085019450602081019050620003cb565b868310156200041257848901516200040e601f89168262000302565b8355505b6001600288020188555050505b505050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600062000459826200042c565b9050919050565b6200046b816200044c565b81146200047757600080fd5b50565b6000815190506200048b8162000460565b92915050565b600060208284031215620004aa57620004a962000427565b5b6000620004ba848285016200047a565b91505092915050565b6107d780620004d36000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063368b87721461005c578063893d20e814610078578063ce6d41de14610096578063d129b79a146100b4578063e7663079146100d2575b600080fd5b61007660048036038101906100719190610326565b6100f0565b005b610080610147565b60405161008d91906103b4565b60405180910390f35b61009e610171565b6040516100ab919061045f565b60405180910390f35b6100bc610203565b6040516100c9919061045f565b60405180910390f35b6100da610291565b6040516100e791906103b4565b60405180910390f35b8181600091826101019291906106d1565b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060008054610180906104ea565b80601f01602080910402602001604051908101604052809291908181526020018280546101ac906104ea565b80156101f95780601f106101ce576101008083540402835291602001916101f9565b820191906000526020600020905b8154815290600101906020018083116101dc57829003601f168201915b5050505050905090565b60008054610210906104ea565b80601f016020809104026020016040519081016040528092919081815260200182805461023c906104ea565b80156102895780601f1061025e57610100808354040283529160200191610289565b820191906000526020600020905b81548152906001019060200180831161026c57829003601f168201915b505050505081565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f8401126102e6576102e56102c1565b5b8235905067ffffffffffffffff811115610303576103026102c6565b5b60208301915083600182028301111561031f5761031e6102cb565b5b9250929050565b6000806020838503121561033d5761033c6102b7565b5b600083013567ffffffffffffffff81111561035b5761035a6102bc565b5b610367858286016102d0565b92509250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061039e82610373565b9050919050565b6103ae81610393565b82525050565b60006020820190506103c960008301846103a5565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104095780820151818401526020810190506103ee565b60008484015250505050565b6000601f19601f8301169050919050565b6000610431826103cf565b61043b81856103da565b935061044b8185602086016103eb565b61045481610415565b840191505092915050565b600060208201905081810360008301526104798184610426565b905092915050565b600082905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061050257607f821691505b602082108103610515576105146104bb565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b60006008830261057d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610540565b6105878683610540565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006105ce6105c96105c48461059f565b6105a9565b61059f565b9050919050565b6000819050919050565b6105e8836105b3565b6105fc6105f4826105d5565b84845461054d565b825550505050565b600090565b610611610604565b61061c8184846105df565b505050565b5b8181101561064057610635600082610609565b600181019050610622565b5050565b601f821115610685576106568161051b565b61065f84610530565b8101602085101561066e578190505b61068261067a85610530565b830182610621565b50505b505050565b600082821c905092915050565b60006106a86000198460080261068a565b1980831691505092915050565b60006106c18383610697565b9150826002028217905092915050565b6106db8383610481565b67ffffffffffffffff8111156106f4576106f361048c565b5b6106fe82546104ea565b610709828285610644565b6000601f8311600181146107385760008415610726578287013590505b61073085826106b5565b865550610798565b601f1984166107468661051b565b60005b8281101561076e57848901358255600182019150602085019450602081019050610749565b8683101561078b5784890135610787601f891682610697565b8355505b6001600288020188555050505b5050505050505056fea26469706673582212204d0495ba5050deed7ead9ed3191f798cb26ec86deac8246cd32b297fec3664cb64736f6c634300081300330000000000000000000000009fc12574abe3e595c73b6d4380762f232507aeeb")); + + // const Address delegatorAddress = env.executor.execute(EncodedCreateMessage(accountAddress, gas, value, delegatorBytecode)); + // account.nonce++; + + // const Address delegatedAddress = env.executor.execute(EncodedCreateMessage(accountAddress, gas, value, delegatedBytecode)); + // account.nonce++; + + // Bytes input; + // auto functor = ABI::FunctorEncoder::encode("delegateTo"); + // Utils::appendBytes(input, UintConv::uint32ToBytes(functor.value)); + // Utils::appendBytes(input, ABI::Encoder::encodeData(delegatedAddress, std::string("cometa"))); + + // std::cout << "\n\n"; + // std::cout << "account address: " << accountAddress.hex(true) << "\n"; + // std::cout << "delegator address: " << delegatorAddress.hex(true) << "\n"; + // std::cout << "delegated address: " << delegatedAddress.hex(true) << "\n\n"; + + // env.executor.execute(EncodedCallMessage(accountAddress, delegatorAddress, gas, value, input)); + // } +} diff --git a/tests/contract/executioncontext.cpp b/tests/contract/executioncontext.cpp new file mode 100644 index 00000000..c2e76a31 --- /dev/null +++ b/tests/contract/executioncontext.cpp @@ -0,0 +1,352 @@ +/* +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 "catch2/catch_amalgamated.hpp" +#include "bytes/hex.h" +#include "contract/executioncontext.h" + +static inline void addAccount(ExecutionContext& context, View
address, const Account& account) { + auto pointer = context.getAccount(address); + pointer.setBalance(account.balance); + pointer.setNonce(account.nonce); + pointer.setCode(account.code); + pointer.setContractType(account.contractType); +} + +TEST_CASE("Execution Context Test Cases", "[executioncontext]") { + SECTION("Building correctly") { + ExecutionContext::Accounts accounts; + ExecutionContext::Storage storage; + + const Address accountAddress1 = bytes::hex("0xa29F7649159DBF66daaa6D03F9ed5733c85BDc27"); + const Address accountAddress2 = bytes::hex("0x87e42c3307c79334e4A22EF406BDe0A004D9c8C7"); + + accounts.emplace(accountAddress1, Account(1000, 5)); + accounts.emplace(accountAddress2, Account(666, 3)); + + const Hash slot1 = bytes::hex("0x0000000000000000000000000000000000000000000000000000000000000001"); + const Hash slot2 = bytes::hex("0x0000000000000000000000000000000000000000000000000000000000000002"); + const Hash data = bytes::hex("0xc89a747ae61fb49aeefadaa8d6fce73ab2f61b444a196c026d46cbe550b90b5b"); + + storage.emplace(StorageKeyView(accountAddress1, slot1), data); + + const int64_t blockGasLimit = 5000; + const int64_t blockNumber = 123; + const int64_t blockTimestamp = 31987233180528; + const int64_t txIndex = 2; + const Address blockCoinbase = bytes::hex("0xe4d327487dd563e93dd09743d1aad131f89e1866"); + const Address txOrigin = bytes::hex("0x35b5758593b3da41a2cc5574d1c4a9aa7cc994f4"); + const Hash blockHash = bytes::hex("0x044475f2cb0876a477b9f7fb401162317ea6ae98c5a7fc84b84cda820c864541"); + const Hash txHash = bytes::hex("0xac7dbb9fd2bf03c58b61664bf453bf7760de39edd91cf812f4b8d49763d29a03"); + const uint256_t chainId = 45; + const uint256_t txGasPrice = 1234753453245; + + ExecutionContext context = ExecutionContext::Builder() + .storage(storage) + .accounts(accounts) + .blockHash(blockHash) + .txHash(txHash) + .txOrigin(txOrigin) + .blockCoinbase(blockCoinbase) + .txIndex(txIndex) + .blockNumber(blockNumber) + .blockTimestamp(blockTimestamp) + .blockGasLimit(blockGasLimit) + .txGasPrice(txGasPrice) + .chainId(chainId) + .build(); + + REQUIRE(context.getBlockHash() == blockHash); + REQUIRE(context.getTxHash() == txHash); + REQUIRE(context.getTxOrigin() == txOrigin); + REQUIRE(context.getBlockCoinbase() == blockCoinbase); + REQUIRE(context.getTxIndex() == txIndex); + REQUIRE(context.getBlockNumber() == blockNumber); + REQUIRE(context.getBlockTimestamp() == blockTimestamp); + REQUIRE(context.getBlockGasLimit() == blockGasLimit); + REQUIRE(context.getTxGasPrice() == txGasPrice); + REQUIRE(context.getChainId() == chainId); + REQUIRE(context.retrieve(accountAddress1, slot1) == data); + REQUIRE(context.retrieve(accountAddress1, slot2) == Hash()); + REQUIRE(context.retrieve(accountAddress2, slot1) == Hash()); + REQUIRE(context.getAccount(accountAddress1).getBalance() == 1000); + REQUIRE(context.getAccount(accountAddress2).getBalance() == 666); + } + + SECTION("Checkpoint revert to accounts") { + ExecutionContext::Accounts accounts; + ExecutionContext::Storage storage; + + const std::array accountsArr = { + Account(200, 3), Account(100, 2), Account(150, 1), + Account(800, 2), Account(100, 4), Account(666, 0), + Account(111, 0), Account(222, 3), Account(0, 0) + }; + + const std::array addresses = { + bytes::hex("0xbb1fe74127ed514da3715ea15f567095215dbf9c"), + bytes::hex("0x0eb51dd6be2169a9a0696a34034a31886647dc3f"), + bytes::hex("0x2a2baa3d4772ed5f2fa34758e4f23633c70fcc9b"), + bytes::hex("0x349dbcc860b78cdeac089bace1eb6a212d8460c4"), + bytes::hex("0xaba088c6004908ab1eee30598ffe528472c15f0f"), + bytes::hex("0x6efa98097f6b6b8f4e6347af82220eb969e1f509"), + bytes::hex("0xaa8b62e65fb53029d46de33e0073a4e813249ab2"), + bytes::hex("0x2f6ea6302015283fe13a38e8d8ba813426acacf2"), + bytes::hex("0x9a6cabc2cd5f053961bbc2dae59f0bde2d5daa76"), + bytes::hex("0xa0ca021de6ee33fd980129ce06af50764a461771") + }; + + accounts.emplace(addresses[0], accountsArr[0]); + + ExecutionContext context = ExecutionContext::Builder() + .storage(storage) + .accounts(accounts) + .build(); + + addAccount(context, addresses[1], accountsArr[1]); + addAccount(context, addresses[2], accountsArr[2]); + + REQUIRE(context.getAccount(addresses[0]).getBalance() == accountsArr[0].balance); + REQUIRE(context.getAccount(addresses[1]).getBalance() == accountsArr[1].balance); + REQUIRE(context.getAccount(addresses[2]).getBalance() == accountsArr[2].balance); + + auto checkpoint = context.checkpoint(); + + addAccount(context, addresses[3], accountsArr[3]); + addAccount(context, addresses[4], accountsArr[4]); + + REQUIRE(context.getAccount(addresses[0]).getBalance() == accountsArr[0].balance); + REQUIRE(context.getAccount(addresses[1]).getBalance() == accountsArr[1].balance); + REQUIRE(context.getAccount(addresses[2]).getBalance() == accountsArr[2].balance); + REQUIRE(context.getAccount(addresses[3]).getBalance() == accountsArr[3].balance); + REQUIRE(context.getAccount(addresses[4]).getBalance() == accountsArr[4].balance); + + context.transferBalance(addresses[0], addresses[1], 100); + REQUIRE(context.getAccount(addresses[0]).getBalance() == accountsArr[0].balance - 100); + REQUIRE(context.getAccount(addresses[1]).getBalance() == accountsArr[1].balance + 100); + REQUIRE_THROWS(context.transferBalance(addresses[0], addresses[1], 101)); + + checkpoint.revert(); + + REQUIRE(context.getAccount(addresses[0]).getBalance() == accountsArr[0].balance); + REQUIRE(context.getAccount(addresses[1]).getBalance() == accountsArr[1].balance); + REQUIRE(context.getAccount(addresses[2]).getBalance() == accountsArr[2].balance); + REQUIRE(context.getAccount(addresses[3]).getBalance() == uint256_t(0)); + REQUIRE(context.getAccount(addresses[4]).getBalance() == uint256_t(0)); + + { + auto checkpoint2 = context.checkpoint(); + + addAccount(context, addresses[5], accountsArr[5]); + addAccount(context, addresses[6], accountsArr[6]); + context.transferBalance(addresses[1], addresses[2], 50); + + checkpoint2.commit(); + } + + REQUIRE(context.getAccount(addresses[1]).getBalance() == accountsArr[1].balance - 50); + REQUIRE(context.getAccount(addresses[2]).getBalance() == accountsArr[2].balance + 50); + REQUIRE(context.getAccount(addresses[5]).getBalance() == accountsArr[5].balance); + REQUIRE(context.getAccount(addresses[6]).getBalance() == accountsArr[6].balance); + } + + SECTION("Nested: revert, revert, revert") { + + std::array addr = { + bytes::hex("5f6e2d4d9d847b820362bf62e1e8b4d4897ce760"), + bytes::hex("c84c5ecbcc4d5932dfdb4034ab2b8e2b246aef41"), + bytes::hex("69ca633ac018da9f3356148717b6818b8b37c379"), + bytes::hex("cc75db077372b3dfcfbf0f3bb37b995aaf8ef155"), + bytes::hex("0625070d23c0228e75c08c2aac6beba86d349f76"), + bytes::hex("9c1d041e67b9fb6367a857a98e7a22097f02b28d"), + bytes::hex("671a7c172bd07caf482c6b6a0e8f74740bf0b2b4"), + bytes::hex("404e19eca6b9ce9012f6959650a7d19cea4cfb39"), + bytes::hex("47212cde146e973b8291038e4b7da890e128d2d9"), + bytes::hex("061e32675637d8accd4a23e32cb6d31dd082f344") + }; + + std::array slots = { + Hash(uint256_t(0)), Hash(uint256_t(1)), Hash(uint256_t(2)), + Hash(uint256_t(3)), Hash(uint256_t(4)), Hash(uint256_t(5)), + Hash(uint256_t(6)), Hash(uint256_t(7)), Hash(uint256_t(8)), + Hash(uint256_t(9)) + }; + + std::array data = { + bytes::hex("a2122ba388a9bf54565d31a711a1a863cb0c7472433253122f8307f8edca73a8"), + bytes::hex("4a84e91e23c4872751210b7088ba266cbdf515e0a053e95f567aac5e1a45b537"), + bytes::hex("432b7b1c5c00d2bd3b37d72672328468271884b311cd094fa5759cbd66310048"), + bytes::hex("b80d1fcfb14cc5b8a01f7264f326786cefe5db0fc6b1a75d3b7dd0730120678f"), + bytes::hex("a559ea3e765536ab3610fcf455e44320a5ce221275eb3ed7f84470a370ac2aee"), + bytes::hex("78886f68e66985f4e7813dc929e6ad0e427ae577ca0a6336442e18426f5a80a1"), + bytes::hex("66db09b48ea9a16269f07cbd6442abdf19a11d442fe5697b833c99e6cf9e3794"), + bytes::hex("41047dcdfb2e6dd03a61511beaabc0be7c7f72a74bb999622566bb2e1d0628ca"), + bytes::hex("6f9f1638daa7e7a8bd2bfaab03db43e29f78f1c920414638cfcebaff41c70ffa"), + bytes::hex("7bc77af21a9f554fb5d8e5f12255191b96b830c953420072f62b1e507aeed638") + }; + + ExecutionContext::Accounts accounts; + ExecutionContext::Storage storage; + + storage.emplace(StorageKeyView(addr[0], slots[0]), data[0]); + + ExecutionContext context = ExecutionContext::Builder() + .storage(storage) + .accounts(accounts) + .build(); + + context.store(addr[1], slots[1], data[1]); + context.store(addr[2], slots[2], data[2]); + + { + auto cp1 = context.checkpoint(); + + context.store(addr[3], slots[3], data[3]); + context.store(addr[4], slots[4], data[4]); + + { + auto cp2 = context.checkpoint(); + + context.store(addr[5], slots[5], data[5]); + context.store(addr[6], slots[6], data[6]); + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == data[3]); + REQUIRE(context.retrieve(addr[4], slots[4]) == data[4]); + REQUIRE(context.retrieve(addr[5], slots[5]) == data[5]); + REQUIRE(context.retrieve(addr[6], slots[6]) == data[6]); + } + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == data[3]); + REQUIRE(context.retrieve(addr[4], slots[4]) == data[4]); + REQUIRE(context.retrieve(addr[5], slots[5]) == Hash()); + REQUIRE(context.retrieve(addr[6], slots[6]) == Hash()); + } + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == Hash()); + REQUIRE(context.retrieve(addr[4], slots[4]) == Hash()); + REQUIRE(context.retrieve(addr[5], slots[5]) == Hash()); + REQUIRE(context.retrieve(addr[6], slots[6]) == Hash()); + + context.revert(); + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == Hash()); + REQUIRE(context.retrieve(addr[2], slots[2]) == Hash()); + REQUIRE(context.retrieve(addr[3], slots[3]) == Hash()); + REQUIRE(context.retrieve(addr[4], slots[4]) == Hash()); + REQUIRE(context.retrieve(addr[5], slots[5]) == Hash()); + REQUIRE(context.retrieve(addr[6], slots[6]) == Hash()); + } + + SECTION("Nested: commit, revert, commit") { + + std::array addr = { + bytes::hex("5f6e2d4d9d847b820362bf62e1e8b4d4897ce760"), + bytes::hex("c84c5ecbcc4d5932dfdb4034ab2b8e2b246aef41"), + bytes::hex("69ca633ac018da9f3356148717b6818b8b37c379"), + bytes::hex("cc75db077372b3dfcfbf0f3bb37b995aaf8ef155"), + bytes::hex("0625070d23c0228e75c08c2aac6beba86d349f76"), + bytes::hex("9c1d041e67b9fb6367a857a98e7a22097f02b28d"), + bytes::hex("671a7c172bd07caf482c6b6a0e8f74740bf0b2b4"), + bytes::hex("404e19eca6b9ce9012f6959650a7d19cea4cfb39"), + bytes::hex("47212cde146e973b8291038e4b7da890e128d2d9"), + bytes::hex("061e32675637d8accd4a23e32cb6d31dd082f344") + }; + + std::array slots = { + Hash(uint256_t(0)), Hash(uint256_t(1)), Hash(uint256_t(2)), + Hash(uint256_t(3)), Hash(uint256_t(4)), Hash(uint256_t(5)), + Hash(uint256_t(6)), Hash(uint256_t(7)), Hash(uint256_t(8)), + Hash(uint256_t(9)) + }; + + std::array data = { + bytes::hex("a2122ba388a9bf54565d31a711a1a863cb0c7472433253122f8307f8edca73a8"), + bytes::hex("4a84e91e23c4872751210b7088ba266cbdf515e0a053e95f567aac5e1a45b537"), + bytes::hex("432b7b1c5c00d2bd3b37d72672328468271884b311cd094fa5759cbd66310048"), + bytes::hex("b80d1fcfb14cc5b8a01f7264f326786cefe5db0fc6b1a75d3b7dd0730120678f"), + bytes::hex("a559ea3e765536ab3610fcf455e44320a5ce221275eb3ed7f84470a370ac2aee"), + bytes::hex("78886f68e66985f4e7813dc929e6ad0e427ae577ca0a6336442e18426f5a80a1"), + bytes::hex("66db09b48ea9a16269f07cbd6442abdf19a11d442fe5697b833c99e6cf9e3794"), + bytes::hex("41047dcdfb2e6dd03a61511beaabc0be7c7f72a74bb999622566bb2e1d0628ca"), + bytes::hex("6f9f1638daa7e7a8bd2bfaab03db43e29f78f1c920414638cfcebaff41c70ffa"), + bytes::hex("7bc77af21a9f554fb5d8e5f12255191b96b830c953420072f62b1e507aeed638") + }; + + ExecutionContext::Accounts accounts; + ExecutionContext::Storage storage; + + storage.emplace(StorageKeyView(addr[0], slots[0]), data[0]); + + ExecutionContext context = ExecutionContext::Builder() + .storage(storage) + .accounts(accounts) + .build(); + + context.store(addr[1], slots[1], data[1]); + context.store(addr[2], slots[2], data[2]); + + { + auto cp1 = context.checkpoint(); + + context.store(addr[3], slots[3], data[3]); + context.store(addr[4], slots[4], data[4]); + + { + auto cp2 = context.checkpoint(); + + context.store(addr[5], slots[5], data[5]); + context.store(addr[6], slots[6], data[6]); + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == data[3]); + REQUIRE(context.retrieve(addr[4], slots[4]) == data[4]); + REQUIRE(context.retrieve(addr[5], slots[5]) == data[5]); + REQUIRE(context.retrieve(addr[6], slots[6]) == data[6]); + + cp2.commit(); + } + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == data[3]); + REQUIRE(context.retrieve(addr[4], slots[4]) == data[4]); + REQUIRE(context.retrieve(addr[5], slots[5]) == data[5]); + REQUIRE(context.retrieve(addr[6], slots[6]) == data[6]); + } + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == Hash()); + REQUIRE(context.retrieve(addr[4], slots[4]) == Hash()); + REQUIRE(context.retrieve(addr[5], slots[5]) == Hash()); + REQUIRE(context.retrieve(addr[6], slots[6]) == Hash()); + + context.commit(); + + REQUIRE(context.retrieve(addr[0], slots[0]) == data[0]); + REQUIRE(context.retrieve(addr[1], slots[1]) == data[1]); + REQUIRE(context.retrieve(addr[2], slots[2]) == data[2]); + REQUIRE(context.retrieve(addr[3], slots[3]) == Hash()); + REQUIRE(context.retrieve(addr[4], slots[4]) == Hash()); + REQUIRE(context.retrieve(addr[5], slots[5]) == Hash()); + REQUIRE(context.retrieve(addr[6], slots[6]) == Hash()); + } +} diff --git a/tests/contract/nativewrapper.cpp b/tests/contract/nativewrapper.cpp index 2a0200f1..cf10b82c 100644 --- a/tests/contract/nativewrapper.cpp +++ b/tests/contract/nativewrapper.cpp @@ -29,22 +29,31 @@ namespace TNativeWrapper { SECTION("NativeWrapper deposit() and withdraw()") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment("testNativeWrapperDepositAndWithdraw"); + + const uint256_t gasPrice = 1'000'000'000; + Address nativeWrapper = sdk.deployContract( std::string("WrappedToken"), std::string("WTKN"), uint8_t(18) ); Address owner = sdk.getChainOwnerAccount().address; uint256_t amountToTransfer = uint256_t("192838158112259"); uint256_t amountToWithdraw = amountToTransfer / 3; + Hash depositTx = sdk.callFunction(nativeWrapper, &NativeWrapper::deposit, amountToTransfer); + REQUIRE(sdk.getNativeBalance(nativeWrapper) == amountToTransfer); - // SDKTestSuite::createNewTx() is adding a tx fee of 21000 gwei, thus * 2 (or * 3 in the test below) - REQUIRE(sdk.getNativeBalance(owner) == uint256_t("1000000000000000000000") - amountToTransfer - (uint256_t(1000000000) * 21000 * 2)); + + uint256_t expectedGasUsed = gasPrice * (uint256_t(CONTRACT_EXECUTION_COST) * 2 + CPP_CONTRACT_CREATION_COST + CPP_CONTRACT_CALL_COST); + + REQUIRE(sdk.getNativeBalance(owner) == uint256_t("1000000000000000000000") - amountToTransfer - expectedGasUsed); REQUIRE(sdk.callViewFunction(nativeWrapper, &NativeWrapper::balanceOf, owner) == amountToTransfer); + Hash withdrawTx = sdk.callFunction(nativeWrapper, &NativeWrapper::withdraw, amountToWithdraw); + expectedGasUsed += gasPrice * (CONTRACT_EXECUTION_COST + CPP_CONTRACT_CALL_COST); + REQUIRE(sdk.getNativeBalance(nativeWrapper) == amountToTransfer - amountToWithdraw); - REQUIRE(sdk.getNativeBalance(owner) == uint256_t("1000000000000000000000") - amountToTransfer + amountToWithdraw - (uint256_t(1000000000) * 21000 * 3)); + REQUIRE(sdk.getNativeBalance(owner) == uint256_t("1000000000000000000000") - amountToTransfer + amountToWithdraw - expectedGasUsed); REQUIRE(sdk.callViewFunction(nativeWrapper, &NativeWrapper::balanceOf, owner) == amountToTransfer - amountToWithdraw); } } } - diff --git a/tests/contract/pebble.cpp b/tests/contract/pebble.cpp index d9d03a4c..b0aa3dd0 100644 --- a/tests/contract/pebble.cpp +++ b/tests/contract/pebble.cpp @@ -57,8 +57,8 @@ namespace TPEBBLE { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment("testPebbleOwnershipTransfer"); Address pebbleAddr = sdk.deployContract(uint256_t(100000)); REQUIRE_THROWS(sdk.callFunction(pebbleAddr, &Pebble::transferOwnership, Address())); // cannot transfer to zero address - REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::owner) == Address("0x00dead00665771855a34155f5e7405489df2c3c6", false)); - Address newOwner("0x1234567890123456789012345678901234567890", false); + REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::owner) == Address(bytes::hex("0x00dead00665771855a34155f5e7405489df2c3c6"))); + Address newOwner(bytes::hex("0x1234567890123456789012345678901234567890")); sdk.callFunction(pebbleAddr, &Pebble::transferOwnership, newOwner); REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::owner) == newOwner); } @@ -66,7 +66,7 @@ namespace TPEBBLE { SECTION("Pebble ownership renounce (Ownable coverage)") { SDKTestSuite sdk = SDKTestSuite::createNewEnvironment("testPebbleOwnershipTransfer"); Address pebbleAddr = sdk.deployContract(uint256_t(100000)); - REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::owner) == Address("0x00dead00665771855a34155f5e7405489df2c3c6", false)); + REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::owner) == Address(bytes::hex("0x00dead00665771855a34155f5e7405489df2c3c6"))); sdk.callFunction(pebbleAddr, &Pebble::renounceOwnership); REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::owner) == Address()); } @@ -104,7 +104,7 @@ namespace TPEBBLE { // Derive the same randomness as the one generated to create the rarity // then check against the rarity inside the event. auto latestBlock = sdk.getStorage().latest(); - auto latestRandomness = latestBlock->getBlockRandomness().toUint256(); + auto latestRandomness = static_cast(latestBlock->getBlockRandomness()); auto expectedRarity = sdk.callViewFunction(pebbleAddr, &Pebble::determineRarity, latestRandomness); REQUIRE(std::get<2>(event) == expectedRarity); REQUIRE(sdk.callViewFunction(pebbleAddr, &Pebble::totalSupply) == uint256_t(1)); diff --git a/tests/core/rdpos.cpp b/tests/core/rdpos.cpp index 36bed141..e0138458 100644 --- a/tests/core/rdpos.cpp +++ b/tests/core/rdpos.cpp @@ -127,7 +127,7 @@ namespace TRdPoS { auto blockchainWrapper = initialize(validatorPrivKeysRdpos, validatorKey, SDKTestSuite::getTestPort(), true, testDumpPath + "/rdPoSValidateBlockCoverage"); // Wrong signature (not randomList_[0]) - FinalizedBlock b1(Signature(), UPubKey(), Hash::random(), Hash::random(), Hash::random(), Hash::random(), 0, 0, {}, {}, Hash::random(), 1); + FinalizedBlock b1(Signature(), UPubKey(), bytes::random(), bytes::random(), bytes::random(), bytes::random(), 0, 0, {}, {}, bytes::random(), 1); REQUIRE_FALSE(blockchainWrapper.state.rdposValidateBlock(b1)); // TODO: this should be covered further, but faking block contents is too much hassle as it is (same goes for addValidatorTx and maybe getTxValidatorFunction which is not exposed by State) @@ -192,7 +192,7 @@ namespace TRdPoS { uint64_t newBlocknHeight = blockchainWrapper1.storage.latest()->getNHeight() + 1; std::vector txValidators; - std::vector randomSeeds(orderedPrivKeys.size(), Hash::random()); + std::vector randomSeeds(orderedPrivKeys.size(), bytes::random()); for (uint64_t i = 0; i < orderedPrivKeys.size(); ++i) { Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(orderedPrivKeys[i])); Bytes hashTxData = Hex::toBytes("0xcfffe746"); @@ -443,7 +443,7 @@ namespace TRdPoS { // Create a block with 8 TxValidator transactions, 2 for each validator, in order (randomHash and random) uint64_t newBlocknHeight = blockchainWrapper1.storage.latest()->getNHeight() + 1; std::vector txValidators; - std::vector randomSeeds(orderedPrivKeys.size(), Hash::random()); + std::vector randomSeeds(orderedPrivKeys.size(), bytes::random()); for (uint64_t i = 0; i < orderedPrivKeys.size(); ++i) { Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(orderedPrivKeys[i])); Bytes hashTxData = Hex::toBytes("0xcfffe746"); diff --git a/tests/core/state.cpp b/tests/core/state.cpp index ca51f4f3..355c002f 100644 --- a/tests/core/state.cpp +++ b/tests/core/state.cpp @@ -46,16 +46,23 @@ std::pair buildCallInfo(const Address& addressToCall, const callFlags = 0; callDepth = 1; callGas = 100000000; - callRecipient = addressToCall.toEvmcAddress(); + callRecipient = bytes::cast(addressToCall); callSender = {}; callInputData = messageBytes.data(); callInputSize = messageBytes.size(); callValue = {}; callCreate2Salt = {}; - callCodeAddress = addressToCall.toEvmcAddress(); + callCodeAddress = bytes::cast(addressToCall); return callInfo; } +static Bytes buildMessageData(const Functor& function, View callData) { + Bytes messageData; + Utils::appendBytes(messageData, UintConv::uint32ToBytes(function.value)); + Utils::appendBytes(messageData, callData); + return messageData; +} + namespace TState { std::string testDumpPath = Utils::getTestDumpPath(); TEST_CASE("State Class", "[core][state]") { @@ -1399,7 +1406,7 @@ namespace TState { // We are doing 10 ERC20 txs, one per block std::vector txs; Hash creationHash = Hash(); - Address ERC20ContractAddress = ContractHost::deriveContractAddress(blockchainWrapper1.state.getNativeNonce(owner), owner); + Address ERC20ContractAddress = generateContractAddress(blockchainWrapper1.state.getNativeNonce(owner), owner); uint64_t nonce = 0; for (uint64_t i = 0; i < 10; ++i) { if (i == 0) { @@ -1423,7 +1430,7 @@ namespace TState { 0, 1000000000, 1000000000, - 21000, + 71000, ownerPrivKey ); creationHash = createNewERC2OTx.hash(); @@ -1481,50 +1488,49 @@ namespace TState { Bytes getBalanceMeEncoder = ABI::Encoder::encodeData(targetOfTransactions); Functor getBalanceMeFunctor = ABI::FunctorEncoder::encode
("balanceOf"); - Bytes getBalanceMeNode1Result = blockchainWrapper1.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + + + const Address from{}; + Gas gas(10'000'000); + const Bytes data = buildMessageData(getBalanceMeFunctor, getBalanceMeEncoder); + EncodedStaticCallMessage msg(from, ERC20ContractAddress, gas, data); + + Bytes getBalanceMeNode1Result = blockchainWrapper1.state.ethCall(msg); auto getBalanceMeNode1Decoder = ABI::Decoder::decodeData(getBalanceMeNode1Result); REQUIRE(std::get<0>(getBalanceMeNode1Decoder) == targetExpectedValue); - Bytes getBalanceMeNode2Result = blockchainWrapper2.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode2Result = blockchainWrapper2.state.ethCall(msg); auto getBalanceMeNode2Decoder = ABI::Decoder::decodeData(getBalanceMeNode2Result); REQUIRE(std::get<0>(getBalanceMeNode2Decoder) == targetExpectedValue); - Bytes getBalanceMeNode3Result = blockchainWrapper3.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode3Result = blockchainWrapper3.state.ethCall(msg); auto getBalanceMeNode3Decoder = ABI::Decoder::decodeData(getBalanceMeNode3Result); REQUIRE(std::get<0>(getBalanceMeNode3Decoder) == targetExpectedValue); - Bytes getBalanceMeNode4Result = blockchainWrapper4.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode4Result = blockchainWrapper4.state.ethCall(msg); auto getBalanceMeNode4Decoder = ABI::Decoder::decodeData(getBalanceMeNode4Result); REQUIRE(std::get<0>(getBalanceMeNode4Decoder) == targetExpectedValue); - Bytes getBalanceMeNode5Result = blockchainWrapper5.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode5Result = blockchainWrapper5.state.ethCall(msg); auto getBalanceMeNode5Decoder = ABI::Decoder::decodeData(getBalanceMeNode5Result); REQUIRE(std::get<0>(getBalanceMeNode5Decoder) == targetExpectedValue); - Bytes getBalanceMeNode6Result = blockchainWrapper6.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode6Result = blockchainWrapper6.state.ethCall(msg); auto getBalanceMeNode6Decoder = ABI::Decoder::decodeData(getBalanceMeNode6Result); REQUIRE(std::get<0>(getBalanceMeNode6Decoder) == targetExpectedValue); - Bytes getBalanceMeNode7Result = blockchainWrapper7.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode7Result = blockchainWrapper7.state.ethCall(msg); auto getBalanceMeNode7Decoder = ABI::Decoder::decodeData(getBalanceMeNode7Result); REQUIRE(std::get<0>(getBalanceMeNode7Decoder) == targetExpectedValue); - Bytes getBalanceMeNode8Result = blockchainWrapper8.state.ethCall( - buildCallInfo(ERC20ContractAddress, getBalanceMeFunctor, getBalanceMeEncoder).first); + Bytes getBalanceMeNode8Result = blockchainWrapper8.state.ethCall(msg); auto getBalanceMeNode8Decoder = ABI::Decoder::decodeData(getBalanceMeNode8Result); REQUIRE(std::get<0>(getBalanceMeNode8Decoder) == targetExpectedValue); diff --git a/tests/core/storage.cpp b/tests/core/storage.cpp index 6ac623f4..0d6ffe4e 100644 --- a/tests/core/storage.cpp +++ b/tests/core/storage.cpp @@ -10,6 +10,8 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/utils/uintconv.h" #include "../blockchainwrapper.hpp" // blockchain.h -> consensus.h -> state.h -> dump.h -> (storage.h -> utils/options.h), utils/db.h +#include "bytes/random.h" +#include "bytes/hex.h" const std::vector validatorPrivKeysStorage { Hash(Hex::toBytes("0x0a0415d68a5ec2df57aab65efc2a7231b59b029bae7ff1bd2e40df9af96418c8")), @@ -24,7 +26,7 @@ const std::vector validatorPrivKeysStorage { // Random transaction TxBlock createRandomTx(const uint64_t& requiredChainId) { - PrivKey txPrivKey = PrivKey::random(); + PrivKey txPrivKey = bytes::random(); Address from = Secp256k1::toAddress(Secp256k1::toUPub(txPrivKey)); Address to(Utils::randBytes(20)); Bytes data = Utils::randBytes(32); @@ -44,10 +46,10 @@ std::pair, Bytes> createRandomTxValidatorList(uint64_t Bytes randomnessStr; ret.first.reserve(N); - std::vector seeds(N, Hash::random()); + std::vector seeds(N, bytes::random()); for (const auto& seed : seeds) { Utils::appendBytes(ret.second, seed); - PrivKey txValidatorPrivKey = PrivKey::random(); + PrivKey txValidatorPrivKey = bytes::random(); Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(txValidatorPrivKey)); Bytes hashTxData = Hex::toBytes("0xcfffe746"); Utils::appendBytes(hashTxData, Utils::sha3(seed)); @@ -73,7 +75,7 @@ std::pair, Bytes> createRandomTxValidatorList(uint64_t } FinalizedBlock createRandomBlock(uint64_t txCount, uint64_t validatorCount, uint64_t nHeight, Hash prevHash, const uint64_t& requiredChainId) { - PrivKey blockValidatorPrivKey = PrivKey::random(); + PrivKey blockValidatorPrivKey = bytes::random(); uint64_t timestamp = 230915972837111; // Timestamp doesn't really matter. std::vector txs; @@ -116,33 +118,33 @@ namespace TStorage { } SECTION("Storage topicsMatch") { - Hash txHash = Hash::random(); - Hash blockHash = Hash::random(); - std::vector topics = {Hash::random(), Hash::random(), Hash::random(), Hash::random(), Hash::random()}; - Address add("0x1234567890123456789012345678901234567890", false); + Hash txHash = bytes::random(); + Hash blockHash = bytes::random(); + std::vector topics = {bytes::random(), bytes::random(), bytes::random(), bytes::random(), bytes::random()}; + Address add(bytes::hex("0x1234567890123456789012345678901234567890")); Bytes data{0xDE, 0xAD, 0xBE, 0xEF}; Event e("myEvent", 0, txHash, 1, blockHash, 2, add, data, topics, false); REQUIRE(Storage::topicsMatch(e, topics)); // For coverage REQUIRE(Storage::topicsMatch(e, {})); // Empty topics - topics.push_back(Hash::random()); + topics.push_back(bytes::random()); REQUIRE_FALSE(Storage::topicsMatch(e, topics)); // Event has fewer topics than required topics.pop_back(); - topics[0] = Hash::random(); + topics[0] = bytes::random(); REQUIRE_FALSE(Storage::topicsMatch(e, topics)); // Event has wrong topics } SECTION("Storage getEvents") { auto blockchainWrapper = initialize(validatorPrivKeysStorage, PrivKey(), 8080, true, "StorageGetEvents"); - Address add("0x1234567890123456789012345678901234567890", false); + Address add(bytes::hex("0x1234567890123456789012345678901234567890")); Bytes data{0xDE, 0xAD, 0xBE, 0xEF}; std::vector> topics; std::vector events; for (int i = 0; i < 5; i++) { - topics.push_back({Hash::random(), Hash::random(), Hash::random()}); + topics.push_back({bytes::random(), bytes::random(), bytes::random()}); events.push_back(Event( - "event" + std::to_string(i), 0, Hash::random(), 0, Hash::random(), i, add, data, topics[i], false + "event" + std::to_string(i), 0, bytes::random(), 0, bytes::random(), i, add, data, topics[i], false )); blockchainWrapper.storage.putEvent(events[i]); } diff --git a/tests/net/http/httpjsonrpc.cpp b/tests/net/http/httpjsonrpc.cpp index 16d2402c..0b3dcb94 100644 --- a/tests/net/http/httpjsonrpc.cpp +++ b/tests/net/http/httpjsonrpc.cpp @@ -300,11 +300,10 @@ namespace THTTPJsonRPC { {"to", "0xaaA85B2B2bD0bFdF6Bc5D0d61B6192c53818567b"}, {"gas", "0xffffff"}, {"gasPrice", "0x1"}, - {"value", "0x1"}, - {"data", "0x1"}, + {"value", "0x1"} }), "latest"})); - REQUIRE(eth_estimateGasResponse["result"] == "0x5e56"); + REQUIRE(eth_estimateGasResponse["result"] == "0x5e55"); json eth_gasPriceResponse = requestMethod("eth_gasPrice", json::array()); REQUIRE(eth_gasPriceResponse["result"] == "0x9502f900"); diff --git a/tests/net/http/parser.cpp b/tests/net/http/parser.cpp index 48395e7c..94dd9d31 100644 --- a/tests/net/http/parser.cpp +++ b/tests/net/http/parser.cpp @@ -9,15 +9,18 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/net/http/jsonrpc/blocktag.h" // parser.h +#include "bytes/random.h" +#include "bytes/hex.h" + namespace THTTPJSONRPCParser { TEST_CASE("HTTP JSON RPC Parser Tests", "[net][http][jsonrpc][parser]") { SECTION("Parser operator()") { - Hash h = Hash::random(); + Hash h = bytes::random(); std::vector v = {10, 20, 30, 40, 50}; // Parser regex REQUIRES hex prefix (0x) json jsonHash = h.hex(true).get(); - json jsonAdd = Address("0x0000111122223333444455556666777788889999", false).hex(true).get(); + json jsonAdd = Address(bytes::hex("0x0000111122223333444455556666777788889999")).hex(true).get(); json jsonBytes = Hex::fromBytes(Bytes{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, true).get(); json jsonBool = true; json jsonFloat = 13.37f; @@ -48,7 +51,7 @@ namespace THTTPJSONRPCParser { std::vector resVectorObj = jsonrpc::parse>(jsonVectorObj); REQUIRE(resHash == h); - REQUIRE(resAdd == Address("0x0000111122223333444455556666777788889999", false)); + REQUIRE(resAdd == Address(bytes::hex("0x0000111122223333444455556666777788889999"))); REQUIRE(resBytes == Bytes{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}); REQUIRE(resBool == true); REQUIRE(resFloat == 13.37f); @@ -66,9 +69,9 @@ namespace THTTPJSONRPCParser { SECTION("Parser operator() (throws)") { // Same thing but everything is wrong on purpose to cover throw cases json hashWrongType = json::array(); // Type is not string (or the required type) - json hashWrongFormat = Hash::random().hex().get(); // No "0x" + json hashWrongFormat = Hash(bytes::random()).hex().get(); // No "0x" json addWrongType = json::array(); - json addWrongFormat = Address("0x0000111122223333444455556666777788889999", false).hex().get(); + json addWrongFormat = Address(bytes::hex("0x0000111122223333444455556666777788889999")).hex().get(); json bytesWrongType = json::array(); json bytesWrongFormat = "0x000g"; // Invalid hex (0-9a-fA-F) json boolWrongType = json::array(); diff --git a/tests/net/p2p/nodeinfo.cpp b/tests/net/p2p/nodeinfo.cpp index 39a5647d..11b6f813 100644 --- a/tests/net/p2p/nodeinfo.cpp +++ b/tests/net/p2p/nodeinfo.cpp @@ -9,11 +9,13 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/net/p2p/encoding.h" +#include "bytes/random.h" + // For coverage namespace TP2PNodeInfo { TEST_CASE("P2P NodeInfo", "[p2p][nodeinfo]") { SECTION("NodeInfo Constructor") { - Hash randomBlockHash = Hash::random(); + Hash randomBlockHash = bytes::random(); P2P::NodeID randomId(boost::asio::ip::address::from_string("127.0.0.1"), uint16_t(8000)); P2P::NodeInfo emptyNode; P2P::NodeInfo node(uint64_t(1), uint64_t(15000), uint64_t(30000), uint64_t(5), uint64_t(12345), randomBlockHash, {randomId}); @@ -34,12 +36,12 @@ namespace TP2PNodeInfo { } SECTION("NodeInfo operator==") { - Hash randomBlockHash = Hash::random(); + Hash randomBlockHash = bytes::random(); P2P::NodeID randomId(boost::asio::ip::address::from_string("127.0.0.1"), uint16_t(8000)); P2P::NodeID randomId2(boost::asio::ip::address::from_string("127.0.0.2"), uint16_t(8001)); P2P::NodeInfo node1(uint64_t(1), uint64_t(15000), uint64_t(30000), uint64_t(5), uint64_t(12345), randomBlockHash, {randomId}); P2P::NodeInfo node2(uint64_t(1), uint64_t(15000), uint64_t(30000), uint64_t(5), uint64_t(12345), randomBlockHash, {randomId}); - P2P::NodeInfo node3(uint64_t(2), uint64_t(1000), uint64_t(3000), uint64_t(4), uint64_t(54321), Hash::random(), {randomId2}); + P2P::NodeInfo node3(uint64_t(2), uint64_t(1000), uint64_t(3000), uint64_t(4), uint64_t(54321), bytes::random(), {randomId2}); REQUIRE(node1 == node2); REQUIRE_FALSE(node1 == node3); } diff --git a/tests/sdktestsuite.hpp b/tests/sdktestsuite.hpp index 6f3fae23..d1ceae8c 100644 --- a/tests/sdktestsuite.hpp +++ b/tests/sdktestsuite.hpp @@ -14,6 +14,7 @@ See the LICENSE.txt file in the project root for more information. #include "../src/utils/uintconv.h" #include "statetest.hpp" +#include "bytes/random.h" /// Wrapper struct for accounts used within the SDKTestSuite. struct TestAccount { @@ -246,7 +247,7 @@ class SDKTestSuite { std::vector randomHashTxs; std::vector randomTxs; - std::vector randomSeeds(orderedPrivKeys.size(), Hash::random()); + std::vector randomSeeds(orderedPrivKeys.size(), bytes::random()); for (uint64_t i = 0; i < orderedPrivKeys.size(); ++i) { Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(orderedPrivKeys[i])); Bytes hashTxData = Hex::toBytes("0xcfffe746"); @@ -312,39 +313,23 @@ class SDKTestSuite { TxBlock createNewTx( const TestAccount& from, const Address& to, const uint256_t& value, Bytes data = Bytes() ) { - evmc_message callInfo; - auto& [callKind, - callFlags, - callDepth, - callGas, - callRecipient, - callSender, - callInputData, - callInputSize, - callValue, - callCreate2Salt, - callCodeAddress] = callInfo; - - callKind = (to == Address()) ? EVMC_CREATE : EVMC_CALL; - callFlags = 0; - callDepth = 1; - callGas = 1000000000; - callRecipient = to.toEvmcAddress(); - callSender = from.address.toEvmcAddress(); - callInputData = data.data(); - callInputSize = data.size(); - callValue = EVMCConv::uint256ToEvmcUint256(value); - callCreate2Salt = {}; - callCodeAddress = {}; - auto usedGas = this->state_.estimateGas(callInfo); - usedGas += 10000; // Add some extra gas for the transaction itself - /// Estimate the gas to see how much gaslimit we should give to the tx itself + + Gas gas(1'000'000'000); + + const uint64_t gasUsed = 10'000 + std::invoke([&] () { + if (to) { + return this->state_.estimateGas(EncodedCallMessage(from.address, to, gas, value, data)); + } else { + return this->state_.estimateGas(EncodedCreateMessage(from.address, gas, value, data)); + } + }); + return TxBlock(to, from.address, data, this->options_.getChainID(), this->state_.getNativeNonce(from.address), value, 1000000000, 1000000000, - usedGas, + gasUsed, from.privKey ); } @@ -397,7 +382,7 @@ class SDKTestSuite { * @return Address of the deployed contract. */ Address deployBytecode(const Bytes& bytecode) { - Address newContractAddress = ContractHost::deriveContractAddress(this->getNativeNonce(this->getChainOwnerAccount().address), this->getChainOwnerAccount().address); + Address newContractAddress = generateContractAddress(this->getNativeNonce(this->getChainOwnerAccount().address), this->getChainOwnerAccount().address); auto createTx = this->createNewTx(this->getChainOwnerAccount(), Address(), 0, bytecode); this->advanceChain(0, {createTx}); return newContractAddress; @@ -755,45 +740,23 @@ class SDKTestSuite { const Address& contractAddress, ReturnType(TContract::*func)() const ) { TContract::registerContract(); - evmc_message callData; - auto& [callKind, - callFlags, - callDepth, - callGas, - callRecipient, - callSender, - callInputData, - callInputSize, - callValue, - callCreate2Salt, - callCodeAddress] = callData; auto functor = ABI::FunctorEncoder::encode<>(ContractReflectionInterface::getFunctionName(func)); Bytes fullData; Utils::appendBytes(fullData, UintConv::uint32ToBytes(functor.value)); - callKind = EVMC_CALL; - callFlags = 0; - callDepth = 1; - callGas = 10000000; - callRecipient = contractAddress.toEvmcAddress(); - callSender = this->getChainOwnerAccount().address.toEvmcAddress(); - callInputData = fullData.data(); - callInputSize = fullData.size(); - callValue = EVMCConv::uint256ToEvmcUint256(0); - callCreate2Salt = {}; - callCodeAddress = contractAddress.toEvmcAddress(); - - // If function is void do not return any data - if constexpr (std::is_same_v) { - this->state_.ethCall(callData); - return; - } else { - return std::get<0>(ABI::Decoder::decodeData(this->state_.ethCall(callData))); + Gas gas(10'000'000); + const Address from = this->getChainOwnerAccount().address; + EncodedStaticCallMessage msg(from, contractAddress, gas, fullData); + + const Bytes result = this->state_.ethCall(msg); + + if constexpr (not std::same_as) { + return std::get<0>(ABI::Decoder::decodeData(result)); } } - /** + /** * Call a contract view function with args and return the result. * @tparam ReturnType Return type of the function. * @tparam TContract Contract type to call. @@ -810,41 +773,19 @@ class SDKTestSuite { const Args&... args ) { TContract::registerContract(); - evmc_message callData; - auto& [callKind, - callFlags, - callDepth, - callGas, - callRecipient, - callSender, - callInputData, - callInputSize, - callValue, - callCreate2Salt, - callCodeAddress] = callData; auto functor = ABI::FunctorEncoder::encode(ContractReflectionInterface::getFunctionName(func)); Bytes fullData; Utils::appendBytes(fullData, UintConv::uint32ToBytes(functor.value)); Utils::appendBytes(fullData, ABI::Encoder::encodeData(std::forward(args)...)); - callKind = EVMC_CALL; - callFlags = 0; - callDepth = 1; - callGas = 10000000; - callRecipient = contractAddress.toEvmcAddress(); - callSender = this->getChainOwnerAccount().address.toEvmcAddress(); - callInputData = fullData.data(); - callInputSize = fullData.size(); - callValue = EVMCConv::uint256ToEvmcUint256(0); - callCreate2Salt = {}; - callCodeAddress = {}; - - // If function is void do not return any data - if constexpr (std::is_same_v) { - this->state_.ethCall(callData); - return; - } else { - return std::get<0>(ABI::Decoder::decodeData(this->state_.ethCall(callData))); + Gas gas(10'000'000); + const Address from = this->getChainOwnerAccount().address; + EncodedStaticCallMessage msg(from, contractAddress, gas, fullData); + + const Bytes result = this->state_.ethCall(msg); + + if constexpr (not std::same_as) { + return std::get<0>(ABI::Decoder::decodeData(result)); } } diff --git a/tests/statetest.hpp b/tests/statetest.hpp index 7f69fb69..b58586f8 100644 --- a/tests/statetest.hpp +++ b/tests/statetest.hpp @@ -12,6 +12,7 @@ See the LICENSE.txt file in the project root for more information. #include "../src/contract/contracthost.h" + // TestState class // The only purpose of this class is to be able to allow // Direct call to internal methods of State class @@ -21,37 +22,8 @@ See the LICENSE.txt file in the project root for more information. class StateTest : public State { public: // StateTest has the same constructor as State - StateTest( - const DB& db, Storage& storage, P2P::ManagerNormal& p2pManager, - const uint64_t& snapshotHeight, const Options& options - ) : State(db, storage, p2pManager, snapshotHeight, options) {}; - - // Force a contract call regardless of the current state - inline void call( - const evmc_message& callInfo, - const evmc_tx_context& txContext, - const ContractType& type, - const Hash& randomness, - const Hash& txHash, - const Hash& blockHash, - int64_t& leftOverGas - ) { - ContractHost host( - this->vm_, - this->dumpManager_, - this->storage_, - randomness, - txContext, - this->contracts_, - this->accounts_, - this->vmStorage_, - txHash, - 0, - blockHash, - leftOverGas - ); - host.execute(callInfo, type); - } + StateTest(const DB& db, Storage& storage, P2P::ManagerNormal& p2pManager, const uint64_t& snapshotHeight, const Options& options) : + State(db, storage, p2pManager, snapshotHeight, options) {}; }; #endif // STATETEST_HPP diff --git a/tests/utils/block.cpp b/tests/utils/block.cpp index 3f99d80d..6aca5237 100644 --- a/tests/utils/block.cpp +++ b/tests/utils/block.cpp @@ -10,6 +10,7 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/utils/finalizedblock.h" // merkle.h -> tx.h -> ecdsa.h -> utils.h #include "../../src/utils/uintconv.h" +#include "bytes/random.h" using Catch::Matchers::Equals; @@ -91,7 +92,7 @@ namespace TBlock { for (uint64_t i = 0; i < 64; i++) txs.emplace_back(tx); // Create and append 8 - std::vector randomSeeds(8, Hash::random()); + std::vector randomSeeds(8, bytes::random()); Bytes randomSeed; // Concatenated random seed of block. for (const auto &seed : randomSeeds) randomSeed.insert(randomSeed.end(), seed.begin(), seed.end()); @@ -152,15 +153,15 @@ namespace TBlock { SECTION("Block with 500 dynamically created transactions and 64 dynamically created validator transactions") { // There is 16 TxValidator transactions, but only 8 of them are used for block randomness. - PrivKey blockValidatorPrivKey = PrivKey::random(); - Hash nPrevBlockHash = Hash::random(); + PrivKey blockValidatorPrivKey = bytes::random(); + Hash nPrevBlockHash = bytes::random(); uint64_t timestamp = 64545214244; uint64_t nHeight = 6414363551; std::vector txs; for (uint64_t i = 0; i < 500; ++i) { - PrivKey txPrivKey = PrivKey::random(); + PrivKey txPrivKey = bytes::random(); Address from = Secp256k1::toAddress(Secp256k1::toUPub(txPrivKey)); Address to(Utils::randBytes(20)); Bytes data = Utils::randBytes(32); @@ -184,7 +185,7 @@ namespace TBlock { } // Create and append 32 randomSeeds - std::vector randomSeeds(32, Hash::random()); + std::vector randomSeeds(32, bytes::random()); Bytes randomSeed; // Concatenated random seed of block. for (const auto &seed : randomSeeds) randomSeed.insert(randomSeed.end(), seed.begin(), seed.end()); @@ -192,7 +193,7 @@ namespace TBlock { // Create 64 TxValidator transactions, half for each type. for (const auto &seed : randomSeeds) { - PrivKey txValidatorPrivKey = PrivKey::random(); + PrivKey txValidatorPrivKey = bytes::random(); Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(txValidatorPrivKey)); Bytes hashTxData = Hex::toBytes("0xcfffe746"); Utils::appendBytes(hashTxData, Utils::sha3(seed)); @@ -243,8 +244,8 @@ namespace TBlock { SECTION("Block with 40000 dynamically created transactions and 256 dynamically created validator transactions") { // There is 16 TxValidator transactions, but only 8 of them are used for block randomness. - PrivKey blockValidatorPrivKey = PrivKey::random(); - Hash nPrevBlockHash = Hash::random(); + PrivKey blockValidatorPrivKey = bytes::random(); + Hash nPrevBlockHash = bytes::random(); uint64_t timestamp = 230915972837112; uint64_t nHeight = 239178513; @@ -261,7 +262,7 @@ namespace TBlock { uint64_t coreJobs = nJobPerCore[i]; futures.push_back(std::async(std::launch::async, [&txs, &txLock, coreJobs, i] { for (uint64_t j = 0; j < coreJobs; ++j) { - PrivKey txPrivKey = PrivKey::random(); + PrivKey txPrivKey = bytes::random(); Address from = Secp256k1::toAddress(Secp256k1::toUPub(txPrivKey)); Address to(Utils::randBytes(20)); Bytes data = Utils::randBytes(32); @@ -293,7 +294,7 @@ namespace TBlock { // Create and append 32 randomSeeds - std::vector randomSeeds(128, Hash::random()); + std::vector randomSeeds(128, bytes::random()); Bytes randomSeed; // Concatenated random seed of block. for (const auto &seed : randomSeeds) Utils::appendBytes(randomSeed, seed); @@ -301,7 +302,7 @@ namespace TBlock { // Create 64 TxValidator transactions, half for each type. for (const auto &seed : randomSeeds) { - PrivKey txValidatorPrivKey = PrivKey::random(); + PrivKey txValidatorPrivKey = bytes::random(); Address validatorAddress = Secp256k1::toAddress(Secp256k1::toUPub(txValidatorPrivKey)); Bytes hashTxData = Hex::toBytes("0xcfffe746"); Utils::appendBytes(hashTxData, Utils::sha3(seed)); diff --git a/tests/utils/db.cpp b/tests/utils/db.cpp index 57cb78c9..d05194f1 100644 --- a/tests/utils/db.cpp +++ b/tests/utils/db.cpp @@ -8,7 +8,7 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/libs/catch2/catch_amalgamated.hpp" #include "../../src/utils/db.h" // utils.h -> strings.h, libs/json.hpp -> (filesystem, string) - +#include "bytes/random.h" #include "../../src/utils/strconv.h" using Catch::Matchers::Equals; @@ -75,7 +75,7 @@ namespace TDB { DBBatch batchP; DBBatch batchD; for (int i = 0; i < 32; i++) { - batchP.push_back(Hash::random().asBytes(), Hash::random().asBytes(), pfx); + batchP.push_back(Utils::makeBytes(bytes::random(32)), Utils::makeBytes(bytes::random(32)), pfx); batchD.delete_key(batchP.getPuts()[i].key, pfx); } std::vector keys; // Reference vector for read checks @@ -131,7 +131,7 @@ namespace TDB { // Update DBBatch newPutB; for (int i = 0; i < 32; i++) { - newPutB.push_back(batchP.getPuts()[i].key, Hash::random().asBytes(), pfx); + newPutB.push_back(batchP.getPuts()[i].key, Utils::makeBytes(bytes::random(32)), pfx); } REQUIRE(db.putBatch(newPutB)); // No need to pass prefix as entry.key already contains it diff --git a/tests/utils/evmcconv.cpp b/tests/utils/evmcconv.cpp index 8981ad8e..3b3166bb 100644 --- a/tests/utils/evmcconv.cpp +++ b/tests/utils/evmcconv.cpp @@ -16,7 +16,7 @@ namespace TJsonAbi { SECTION("EVMCConv uint256 <-> evmcUint256") { uint256_t i = 12345678; evmc::uint256be resEVMC = EVMCConv::uint256ToEvmcUint256(i); - REQUIRE(UintConv::bytesToUint256(bytes::View(resEVMC.bytes, 32)) == i); + REQUIRE(UintConv::bytesToUint256(View(resEVMC.bytes, 32)) == i); uint256_t resUINT = EVMCConv::evmcUint256ToUint256(resEVMC); REQUIRE(resUINT == i); } @@ -27,7 +27,7 @@ namespace TJsonAbi { BytesArr<32> resBYTES = EVMCConv::evmcUint256ToBytes(iEVMC); REQUIRE(UintConv::bytesToUint256(resBYTES) == i); evmc::uint256be resEVMC = EVMCConv::bytesToEvmcUint256(resBYTES); - REQUIRE(UintConv::bytesToUint256(bytes::View(resEVMC.bytes, 32)) == i); + REQUIRE(UintConv::bytesToUint256(View(resEVMC.bytes, 32)) == i); } SECTION("EVMCConv getFunctor") { @@ -53,8 +53,8 @@ namespace TJsonAbi { msg1.input_data = msg1Data.data(); msg2.input_size = 16; msg2.input_data = msg2Data.data(); - bytes::View get1 = EVMCConv::getFunctionArgs(msg1); - bytes::View get2 = EVMCConv::getFunctionArgs(msg2); + View get1 = EVMCConv::getFunctionArgs(msg1); + View get2 = EVMCConv::getFunctionArgs(msg2); REQUIRE(Hex::fromBytes(get1).get() == ""); REQUIRE(Hex::fromBytes(get2).get() == "0405060708090a0b0c0d0e0f"); } diff --git a/tests/utils/merkle.cpp b/tests/utils/merkle.cpp index a95e8ddb..21dd7697 100644 --- a/tests/utils/merkle.cpp +++ b/tests/utils/merkle.cpp @@ -6,6 +6,7 @@ See the LICENSE.txt file in the project root for more information. */ #include "../../src/libs/catch2/catch_amalgamated.hpp" +#include "bytes/random.h" #include "../../src/utils/merkle.h" // tx.h @@ -41,10 +42,10 @@ namespace TMerkle { SECTION("Random Merkle Tree") { std::vector hashedLeafs { - Hash::random(), Hash::random(), Hash::random(), Hash::random(), - Hash::random(), Hash::random(), Hash::random(), Hash::random(), - Hash::random(), Hash::random(), Hash::random(), Hash::random(), - Hash::random(), Hash::random(), Hash::random() + bytes::random(), bytes::random(), bytes::random(), bytes::random(), + bytes::random(), bytes::random(), bytes::random(), bytes::random(), + bytes::random(), bytes::random(), bytes::random(), bytes::random(), + bytes::random(), bytes::random(), bytes::random() }; Merkle tree(hashedLeafs); diff --git a/tests/utils/randomgen.cpp b/tests/utils/randomgen.cpp index a94d0456..c9d8f7f7 100644 --- a/tests/utils/randomgen.cpp +++ b/tests/utils/randomgen.cpp @@ -8,6 +8,7 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/libs/catch2/catch_amalgamated.hpp" #include "../../src/utils/randomgen.h" +#include "bytes/random.h" using Catch::Matchers::Equals; @@ -32,9 +33,9 @@ namespace TRandomGen { RandomGen generator(seed); REQUIRE(generator.getSeed() == seed); auto newSeed = generator(); - REQUIRE(generator.getSeed().toUint256() == newSeed); + REQUIRE(static_cast(generator.getSeed()) == newSeed); newSeed = generator(); - REQUIRE(generator.getSeed().toUint256() == newSeed); + REQUIRE(static_cast(generator.getSeed()) == newSeed); } SECTION("RandomGen Min/Max") { @@ -66,7 +67,7 @@ namespace TRandomGen { } SECTION("RandomGen randomness") { - Hash seed = Hash::random(); + Hash seed = bytes::random(); RandomGen generator(seed); std::vector randoms; diff --git a/tests/utils/safehash.cpp b/tests/utils/safehash.cpp index c8616f76..eef2dc46 100644 --- a/tests/utils/safehash.cpp +++ b/tests/utils/safehash.cpp @@ -5,9 +5,10 @@ This software is distributed under the MIT License. See the LICENSE.txt file in the project root for more information. */ -#include "../../src/libs/catch2/catch_amalgamated.hpp" - -#include "../../src/utils/safehash.h" +#include "libs/catch2/catch_amalgamated.hpp" +#include "utils/safehash.h" +#include "bytes/hex.h" +#include "bytes/random.h" namespace TSafeHash { TEST_CASE("SafeHash Struct", "[utils][safehash]") { @@ -17,10 +18,10 @@ namespace TSafeHash { const std::string_view strView = "Goodbye Planet"; const Bytes bytes = Bytes{0xDE, 0xAD, 0xBE, 0xEF}; const BytesArr<4> bytesArr = {0xDE, 0xAD, 0xBE, 0xEF}; - const bytes::View bytesView = Utils::create_view_span(str); - const Address add(std::string("0x1234567890123456789012345678901234567890"), false); + const View bytesView = Utils::create_view_span(str); + const Address add(bytes::hex("0x1234567890123456789012345678901234567890")); const Functor func{83892529}; - const Hash hash = Hash::random(); + const Hash hash = bytes::random(); const TxValidator tx(Hex::toBytes("f845808026a08a4591f48d6307bb4cb8a0b0088b544d923d00bc1f264c3fdf16f946fdee0b34a077a6f6e8b3e78b45478827604f070d03060f413d823eae7fab9b139be7a41d81"), 1); const std::shared_ptr ptr = std::make_shared(str); const FixedBytes<4> fixed{0xDE, 0xAD, 0xBE, 0xEF}; diff --git a/tests/utils/strings.cpp b/tests/utils/strings.cpp index 4a5ec2d1..c27dd599 100644 --- a/tests/utils/strings.cpp +++ b/tests/utils/strings.cpp @@ -8,8 +8,11 @@ See the LICENSE.txt file in the project root for more information. #include "../../src/libs/catch2/catch_amalgamated.hpp" #include "../../src/utils/strings.h" // bytes/initializer.h -> bytes/view.h - #include "../../src/utils/strconv.h" +#include "bytes/view.h" +#include "bytes/random.h" +#include "bytes/hex.h" +#include "bytes/cast.h" using Catch::Matchers::Equals; @@ -185,7 +188,7 @@ namespace THash { SECTION("Hash toUint256") { uint256_t i = uint256_t("70518832285973061936518038480459635341011381946952877582230426678885538674712"); Hash hash(i); - REQUIRE(hash.toUint256() == i); + REQUIRE(static_cast(hash) == i); } SECTION("Hash toEvmcBytes32") { @@ -193,12 +196,12 @@ namespace THash { Bytes b = Hex::toBytes("9be83ea08b549e7c77644c451b55a674bb12e4668d018183ff9723b1de493818"); for (int i = 0; i < 32; i++) num.bytes[i] = b[i]; Hash hash(num); - REQUIRE(hash.toEvmcBytes32() == num); + REQUIRE(bytes::cast(hash) == num); } SECTION("Hash random()") { - Hash hash1 = Hash::random(); - Hash hash2 = Hash::random(); + Hash hash1 = bytes::random(); + Hash hash2 = bytes::random(); REQUIRE(hash1 != hash2); } } @@ -226,29 +229,29 @@ namespace TSignature { namespace TAddress { TEST_CASE("Address Class", "[utils][strings][address]") { SECTION("Address String View Constructor") { - Address addStr(std::string_view("0x71c7656ec7ab88b098defb751b7401b5f6d8976f"), false); - Address addBytes(std::string_view("\x71\xc7\x65\x6e\xc7\xab\x88\xb0\x98\xde\xfb\x75\x1b\x74\x01\xb5\xf6\xd8\x97\x6f"), true); + Address addStr(bytes::hex("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")); + Address addBytes(bytes::view("\x71\xc7\x65\x6e\xc7\xab\x88\xb0\x98\xde\xfb\x75\x1b\x74\x01\xb5\xf6\xd8\x97\x6f")); REQUIRE(addStr == addBytes); REQUIRE_THAT(addStr.hex(), Equals("71c7656ec7ab88b098defb751b7401b5f6d8976f")); REQUIRE_THAT(addBytes.hex(), Equals("71c7656ec7ab88b098defb751b7401b5f6d8976f")); // For coverage - REQUIRE_THROWS(Address(std::string_view("0x71c7656ec7ab88b098defb751b7401b5f6d8976h"), false)); // last char is "h" - REQUIRE_THROWS(Address("\x71\xc7\x65\x6e\xc7\xab\x88\xb0\x98\xde\xfb\x75\x1b\x74\x01\xb5\xf6\xd8\x97", true)); // missing last byte "\x6f" + REQUIRE_THROWS(Address(bytes::hex("0x71c7656ec7ab88b098defb751b7401b5f6d8976h"))); // last char is "h" + REQUIRE_THROWS(Address(bytes::view("\x71\xc7\x65\x6e\xc7\xab\x88\xb0\x98\xde\xfb\x75\x1b\x74\x01\xb5\xf6\xd8\x97"))); // missing last byte "\x6f" } SECTION("Address Copy Constructor") { Address addr1(Bytes({0x71, 0xc7, 0x65, 0x6e, 0xc7, 0xab, 0x88, 0xb0, 0x98, 0xde, 0xfb, 0x75, 0x1b, 0x74, 0x01, 0xb5, 0xf6, 0xd8, 0x97, 0x6f})); - Address addr2(std::string("\x71\xc7\x65\x6e\xc7\xab\x88\xb0\x98\xde\xfb\x75\x1b\x74\x01\xb5\xf6\xd8\x97\x6f"), true); + Address addr2(bytes::view("\x71\xc7\x65\x6e\xc7\xab\x88\xb0\x98\xde\xfb\x75\x1b\x74\x01\xb5\xf6\xd8\x97\x6f")); REQUIRE(addr1 == addr2); REQUIRE_THAT(addr1.hex(), Equals("71c7656ec7ab88b098defb751b7401b5f6d8976f")); REQUIRE(addr2 == Address({0x71, 0xc7, 0x65, 0x6e, 0xc7, 0xab, 0x88, 0xb0, 0x98, 0xde, 0xfb, 0x75, 0x1b, 0x74, 0x01, 0xb5, 0xf6, 0xd8, 0x97, 0x6f})); } SECTION("Address toChksum") { - Address inputAddress(std::string("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359"), false); - std::string inputChecksum = inputAddress.toChksum(); - Address outputAddress(inputChecksum, false); - Address expectedOutputAddress(std::string("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"), false); + Address inputAddress = bytes::hex("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359"); + std::string inputChecksum = Address::checksum(inputAddress); + Address outputAddress = bytes::hex(inputChecksum); + Address expectedOutputAddress = bytes::hex("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"); REQUIRE(outputAddress == expectedOutputAddress); } @@ -295,40 +298,3 @@ namespace TAddress { } } } - -namespace TStorageKey { - TEST_CASE("StorageKey Class", "[utils][strings][storagekey]") { - SECTION("StorageKey Constructors") { - evmc::address addr1; - evmc_address addr2; - evmc::bytes32 slot1; - evmc_bytes32 slot2; - Address addr3(std::string("0x1234567890123456789012345678901234567890"), false); - Hash slot3(Hex::toBytes("aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffff0000000099999999")); - - // TODO: replace this with one of the std::ranges alternatives after ContractHost refactor is merged: - // std::ranges::fill(addr, 0xFF); // <--- preferred - // std::ranges::fill(addr.bytes, 0xFF); - // std::fill(addr.bytes, addr.bytes + sizeof(addr.bytes), 0xFF); - for (int i = 0; i < 20; i++) { addr1.bytes[i] = 0xAA; addr2.bytes[i] = 0xFF; } - for (int i = 0; i < 32; i++) { slot1.bytes[i] = 0xAA; slot2.bytes[i] = 0xFF; } - - StorageKey key1(addr1, slot1); - StorageKey key2(addr2, slot2); - StorageKey key3(addr2, slot1); - StorageKey key4(addr1, slot2); - StorageKey key5(addr3, slot3); - - REQUIRE_THAT(Hex::fromBytes(key1.asBytes()).get(), Equals("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); - REQUIRE_THAT(Hex::fromBytes(key2.asBytes()).get(), Equals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - REQUIRE_THAT(Hex::fromBytes(key3.asBytes()).get(), Equals("ffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); - REQUIRE_THAT(Hex::fromBytes(key4.asBytes()).get(), Equals("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - REQUIRE_THAT(Hex::fromBytes(key5.asBytes()).get(), Equals("1234567890123456789012345678901234567890aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffff0000000099999999")); - - // Testing throw for coverage - FixedBytes<5> keyWrongSize({0x00, 0x00, 0x00, 0x00, 0x00}); - REQUIRE_THROWS(StorageKey(keyWrongSize.view())); - } - } -} - diff --git a/tests/utils/tx_throw.cpp b/tests/utils/tx_throw.cpp index db39fbf2..ee1ef872 100644 --- a/tests/utils/tx_throw.cpp +++ b/tests/utils/tx_throw.cpp @@ -6,6 +6,9 @@ See the LICENSE.txt file in the project root for more information. */ #include "../../src/libs/catch2/catch_amalgamated.hpp" +#include "../../src/utils/utils.h" +#include "../../src/utils/tx.h" +#include "bytes/random.h" #include "../../src/utils/tx.h" // ecdsa.h -> utils.h @@ -204,7 +207,7 @@ namespace TTX { SECTION("Tx invalid PrivKey (doesn't match from)") { bool catched = false; try { - PrivKey privKey(Hash::random()); + PrivKey privKey(bytes::random()); TxBlock tx( Address(Hex::toBytes("0x1234567890123456789012345678901234567890")), Address(Hex::toBytes("0x1234567890123456789012345678901234567890")), @@ -314,7 +317,7 @@ namespace TTX { SECTION("Tx invalid PrivKey (doesn't match from)") { bool catched = false; try { - PrivKey privKey(Hash::random()); + PrivKey privKey(bytes::random()); TxValidator tx( Address(Hex::toBytes("0x1234567890123456789012345678901234567890")), {}, 8080, 0, privKey diff --git a/tests/utils/utils.cpp b/tests/utils/utils.cpp index 8d88a73e..9ae24166 100644 --- a/tests/utils/utils.cpp +++ b/tests/utils/utils.cpp @@ -106,8 +106,8 @@ namespace TUtils { SECTION("create_view_span") { Bytes b{0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; std::string_view sv("abcdef"); - bytes::View v1 = Utils::create_view_span(b, 0, 6); - bytes::View v2 = Utils::create_view_span(sv, 0, 6); + View v1 = Utils::create_view_span(b, 0, 6); + View v2 = Utils::create_view_span(sv, 0, 6); REQUIRE(Hex::fromBytes(v1).get() == "0a0b0c0d0e0f"); REQUIRE(Hex::fromBytes(v2).get() == "616263646566"); REQUIRE_THROWS(Utils::create_view_span(b, 0, 12));