From d7b23b7e5de17326616aa3ed62835ec1910bbcd9 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:00:01 +0200 Subject: [PATCH] [ETH]: Handle `tuple[]` ABI type parameter (#3352) --- src/Ethereum/ABI/Array.cpp | 17 ++- src/Ethereum/ABI/Array.h | 41 +++--- src/Ethereum/ABI/Bytes.cpp | 12 ++ src/Ethereum/ABI/Bytes.h | 59 +++++---- src/Ethereum/ABI/ParamAddress.cpp | 8 +- src/Ethereum/ABI/ParamAddress.h | 17 ++- src/Ethereum/ABI/ParamBase.h | 4 + src/Ethereum/ABI/ParamNumber.h | 121 ++++++++++-------- src/Ethereum/ABI/ParamStruct.cpp | 16 +++ src/Ethereum/ABI/ParamStruct.h | 51 ++++---- src/Ethereum/ABI/Parameters.cpp | 46 +++++-- src/Ethereum/ABI/Parameters.h | 21 +-- src/Ethereum/ABI/Tuple.cpp | 6 + src/Ethereum/ABI/Tuple.h | 23 ++-- src/Ethereum/ContractCall.cpp | 49 +++++-- tests/chains/Ethereum/AbiTests.cpp | 18 ++- tests/chains/Ethereum/ContractCallTests.cpp | 14 ++ tests/chains/Ethereum/Data/swap_v2.json | 70 ++++++++++ .../chains/Ethereum/Data/swap_v2_decoded.json | 105 +++++++++++++++ 19 files changed, 521 insertions(+), 177 deletions(-) create mode 100644 tests/chains/Ethereum/Data/swap_v2.json create mode 100644 tests/chains/Ethereum/Data/swap_v2_decoded.json diff --git a/src/Ethereum/ABI/Array.cpp b/src/Ethereum/ABI/Array.cpp index 472f5a86e1f..9dc78a40d77 100644 --- a/src/Ethereum/ABI/Array.cpp +++ b/src/Ethereum/ABI/Array.cpp @@ -79,7 +79,7 @@ bool ParamArray::decode(const Data& encoded, size_t& offset_inout) { // pad with first type auto first = _params.getParamUnsafe(0); for (size_t i = 0; i < len - n; i++) { - _params.addParam(ParamFactory::make(first->getType())); + _params.addParam(first->clone()); } } @@ -105,7 +105,7 @@ bool ParamArray::setValueJson(const std::string& value) { } // make sure enough elements are in the array while (_params.getCount() < valuesJson.size()) { - addParam(ParamFactory::make(getProtoType())); + addParam(getProtoElem()->clone()); } int cnt = 0; for (const auto& e : valuesJson) { @@ -133,6 +133,13 @@ std::string ParamArray::getExtraTypes(std::vector& ignoreList) cons return (proto != nullptr) ? proto->getExtraTypes(ignoreList) : ""; } +std::shared_ptr ParamArray::clone() const { + auto newArray = std::make_shared(); + newArray->_params = _params.clone(); + newArray->_proto = _proto->clone(); + return newArray; +} + void ParamArrayFix::encode(Data& data) const { this->_params.encode(data); } @@ -156,6 +163,12 @@ bool ParamArrayFix::setValueJson(const std::string& value) { return true; } +std::shared_ptr ParamArrayFix::clone() const { + auto newArray = std::make_shared(); + newArray->_params = _params.clone(); + return newArray; +} + void ParamArrayFix::addParams(const Params& params) { auto addParamFunctor = [this](auto&& param) { if (param == nullptr) { diff --git a/src/Ethereum/ABI/Array.h b/src/Ethereum/ABI/Array.h index 8be902174e6..9a2e38bbd08 100644 --- a/src/Ethereum/ABI/Array.h +++ b/src/Ethereum/ABI/Array.h @@ -15,7 +15,7 @@ namespace TW::Ethereum::ABI { /// Dynamic array of the same types, "[]" /// Normally has at least one element. Empty array can have prototype set so its type is known. /// Empty with no prototype is possible, but should be avoided. -class ParamArray : public ParamCollection { +class ParamArray final : public ParamCollection { private: ParamSet _params; std::shared_ptr _proto; // an optional prototype element, determines the array type, useful in empty array case @@ -36,15 +36,17 @@ class ParamArray : public ParamCollection { void addParams(const std::vector>& params); void setProto(const std::shared_ptr& proto) { _proto = proto; } std::shared_ptr getParam(int paramIndex) { return _params.getParamUnsafe(paramIndex); } - virtual std::string getType() const { return getProtoType() + "[]"; } - virtual size_t getSize() const; - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _params.getCount(); } - virtual void encode(Data& data) const; - virtual bool decode(const Data& encoded, size_t& offset_inout); - virtual bool setValueJson(const std::string& value); - virtual Data hashStruct() const; - virtual std::string getExtraTypes(std::vector& ignoreList) const; + + std::string getType() const override { return getProtoType() + "[]"; } + size_t getSize() const override; + bool isDynamic() const override { return true; } + size_t getCount() const override { return _params.getCount(); } + void encode(Data& data) const override; + bool decode(const Data& encoded, size_t& offset_inout) override; + bool setValueJson(const std::string& value) override; + Data hashStruct() const override; + std::string getExtraTypes(std::vector& ignoreList) const override; + std::shared_ptr clone() const override; }; /// Fixed-size array of the same type e.g, "type[4]" @@ -53,20 +55,23 @@ class ParamArrayFix final : public ParamCollection { //! Public Definitions using Params = std::vector>; - //! Public constructor + //! Public constructors + ParamArrayFix() = default; + explicit ParamArrayFix(const Params& params) noexcept(false) : ParamCollection() { this->addParams(params); } //! Public member methods - [[nodiscard]] std::size_t getCount() const final { return _params.getCount(); } - [[nodiscard]] size_t getSize() const final { return _params.getSize(); } - [[nodiscard]] bool isDynamic() const final { return false; } - [[nodiscard]] std::string getType() const final { return _params.getParamUnsafe(0)->getType() + "[" + std::to_string(_params.getCount()) + "]"; } - void encode(Data& data) const final; - bool decode(const Data& encoded, size_t& offset_inout) final; - bool setValueJson(const std::string& value) final; + [[nodiscard]] std::size_t getCount() const override { return _params.getCount(); } + [[nodiscard]] size_t getSize() const override { return _params.getSize(); } + [[nodiscard]] bool isDynamic() const override { return false; } + [[nodiscard]] std::string getType() const override { return _params.getParamUnsafe(0)->getType() + "[" + std::to_string(_params.getCount()) + "]"; } + void encode(Data& data) const override; + bool decode(const Data& encoded, size_t& offset_inout) override; + bool setValueJson(const std::string& value) override; + std::shared_ptr clone() const override; private: //! Private member functions diff --git a/src/Ethereum/ABI/Bytes.cpp b/src/Ethereum/ABI/Bytes.cpp index cb1277b96f9..5cbee7fc970 100644 --- a/src/Ethereum/ABI/Bytes.cpp +++ b/src/Ethereum/ABI/Bytes.cpp @@ -56,6 +56,10 @@ Data ParamByteArray::hashStruct() const { return Hash::keccak256(_bytes); } +std::shared_ptr ParamByteArray::clone() const { + return std::make_shared(_bytes); +} + void ParamByteArrayFix::setVal(const Data& val) { if (val.size() > _n) { // crop right _bytes = subData(val, 0, _n); @@ -105,6 +109,10 @@ Data ParamByteArrayFix::hashStruct() const { return ParamBase::hashStruct(); } +std::shared_ptr ParamByteArrayFix::clone() const { + return std::make_shared(_n, _bytes); +} + void ParamString::encodeString(const std::string& decoded, Data& data) { auto bytes = Data(decoded.begin(), decoded.end()); ParamByteArray::encodeBytes(bytes, data); @@ -126,4 +134,8 @@ Data ParamString::hashStruct() const { return hash; } +std::shared_ptr ParamString::clone() const { + return std::make_shared(_str); +} + } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Bytes.h b/src/Ethereum/ABI/Bytes.h index a3695a813b5..3fc92f4c78e 100644 --- a/src/Ethereum/ABI/Bytes.h +++ b/src/Ethereum/ABI/Bytes.h @@ -13,7 +13,7 @@ namespace TW::Ethereum::ABI { /// Dynamic array of bytes "bytes" -class ParamByteArray: public ParamCollection +class ParamByteArray final: public ParamCollection { private: Data _bytes; @@ -22,22 +22,23 @@ class ParamByteArray: public ParamCollection ParamByteArray(const Data& val) : ParamCollection() { setVal(val); } void setVal(const Data& val) { _bytes = val; } const Data& getVal() const { return _bytes; } - virtual std::string getType() const { return "bytes"; }; - virtual size_t getSize() const { return 32 + ValueEncoder::paddedTo32(_bytes.size()); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _bytes.size(); } + std::string getType() const override { return "bytes"; }; + size_t getSize() const override { return 32 + ValueEncoder::paddedTo32(_bytes.size()); } + bool isDynamic() const override { return true; } + size_t getCount() const override { return _bytes.size(); } static void encodeBytes(const Data& bytes, Data& data); - virtual void encode(Data& data) const { encodeBytes(_bytes, data); } + void encode(Data& data) const override { encodeBytes(_bytes, data); } static bool decodeBytes(const Data& encoded, Data& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout) { + bool decode(const Data& encoded, size_t& offset_inout) override { return decodeBytes(encoded, _bytes, offset_inout); } - virtual bool setValueJson(const std::string& value); - virtual Data hashStruct() const; + bool setValueJson(const std::string& value) override; + Data hashStruct() const override; + std::shared_ptr clone() const override; }; /// Fixed-size array of bytes, "bytes" -class ParamByteArrayFix: public ParamCollection +class ParamByteArrayFix final: public ParamCollection { private: size_t _n; @@ -47,41 +48,43 @@ class ParamByteArrayFix: public ParamCollection ParamByteArrayFix(size_t n, const Data& val): ParamCollection(), _n(n), _bytes(Data(_n)) { setVal(val); } void setVal(const Data& val); const std::vector& getVal() const { return _bytes; } - virtual std::string getType() const { return "bytes" + std::to_string(_n); }; - virtual size_t getSize() const { return ValueEncoder::paddedTo32(_bytes.size()); } - virtual bool isDynamic() const { return false; } - virtual size_t getCount() const { return _bytes.size(); } - virtual void encode(Data& data) const; + std::string getType() const override { return "bytes" + std::to_string(_n); }; + size_t getSize() const override { return ValueEncoder::paddedTo32(_bytes.size()); } + bool isDynamic() const override { return false; } + size_t getCount() const override { return _bytes.size(); } + void encode(Data& data) const override; static bool decodeBytesFix(const Data& encoded, size_t n, Data& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout) { + bool decode(const Data& encoded, size_t& offset_inout) override { return decodeBytesFix(encoded, _n, _bytes, offset_inout); } - virtual bool setValueJson(const std::string& value); - virtual Data hashStruct() const; + bool setValueJson(const std::string& value) override; + Data hashStruct() const override; + std::shared_ptr clone() const override; }; /// Var-length string parameter -class ParamString: public ParamCollection +class ParamString final: public ParamCollection { private: std::string _str; public: ParamString() = default; - ParamString(std::string val): ParamCollection() { setVal(val); } + ParamString(const std::string& val): ParamCollection() { setVal(val); } void setVal(const std::string& val) { _str = val; } const std::string& getVal() const { return _str; } - virtual std::string getType() const { return "string"; }; - virtual size_t getSize() const { return 32 + ValueEncoder::paddedTo32(_str.size()); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _str.size(); } + std::string getType() const override { return "string"; }; + size_t getSize() const override { return 32 + ValueEncoder::paddedTo32(_str.size()); } + bool isDynamic() const override { return true; } + size_t getCount() const override { return _str.size(); } static void encodeString(const std::string& decoded, Data& data); - virtual void encode(Data& data) const { ParamString::encodeString(_str, data); } + void encode(Data& data) const override { ParamString::encodeString(_str, data); } static bool decodeString(const Data& encoded, std::string& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout) { + bool decode(const Data& encoded, size_t& offset_inout) override { return decodeString(encoded, _str, offset_inout); } - virtual bool setValueJson(const std::string& value) { _str = value; return true; } - virtual Data hashStruct() const; + bool setValueJson(const std::string& value) override { _str = value; return true; } + Data hashStruct() const override; + std::shared_ptr clone() const override; }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamAddress.cpp b/src/Ethereum/ABI/ParamAddress.cpp index df29b67763e..1707f9f2084 100644 --- a/src/Ethereum/ABI/ParamAddress.cpp +++ b/src/Ethereum/ABI/ParamAddress.cpp @@ -11,13 +11,17 @@ namespace TW::Ethereum::ABI { Data ParamAddress::getData() const { - Data data = store(getVal(), bytes); + Data data = store(_val.getVal(), bytes); return data; } bool ParamAddress::setValueJson(const std::string& value) { - setVal(load(parse_hex(value))); + _val.setVal(load(parse_hex(value))); return true; } +std::shared_ptr ParamAddress::clone() const { + return std::make_shared(getData()); +} + } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamAddress.h b/src/Ethereum/ABI/ParamAddress.h index 38b05bd342c..c9ae0fa9d16 100644 --- a/src/Ethereum/ABI/ParamAddress.h +++ b/src/Ethereum/ABI/ParamAddress.h @@ -12,16 +12,23 @@ namespace TW::Ethereum::ABI { /// 160-bit Address parameter, "address". Padded to the right, treated like ParamUInt160 -class ParamAddress: public ParamUIntN +class ParamAddress final: public ParamBase { +private: + ParamUIntN _val; public: static const size_t bytes = 20; - ParamAddress(): ParamUIntN(bytes * 8) {} - ParamAddress(const Data& val): ParamUIntN(bytes * 8, load(val)) {} - virtual std::string getType() const { return "address"; }; + ParamAddress(): _val(bytes * 8) {} + explicit ParamAddress(const Data& val): _val(bytes * 8, load(val)) {} + std::string getType() const override { return "address"; }; + size_t getSize() const override { return _val.getSize(); } + bool isDynamic() const override { return _val.isDynamic(); } + void encode(Data& data) const override { _val.encode(data); } + bool decode(const Data& encoded, size_t& offset_inout) override { return _val.decode(encoded, offset_inout); } // get the value as (20-byte) byte array (as opposed to uint256_t) Data getData() const; - virtual bool setValueJson(const std::string& value); + bool setValueJson(const std::string& value) override; + std::shared_ptr clone() const override; }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/ParamBase.h b/src/Ethereum/ABI/ParamBase.h index 673d4c43047..11039de5a75 100644 --- a/src/Ethereum/ABI/ParamBase.h +++ b/src/Ethereum/ABI/ParamBase.h @@ -9,6 +9,7 @@ #include "Data.h" #include +#include namespace TW::Ethereum::ABI { @@ -27,6 +28,9 @@ class ParamBase virtual Data hashStruct() const; // Helper for EIP712 encoding; provide full type of all used types (recursively). Default is empty implementation. virtual std::string getExtraTypes([[maybe_unused]] std::vector& ignoreList) const { return ""; } + // Creates a copy of this element. + // This method **must** be implemented in a `final` class only. + virtual std::shared_ptr clone() const = 0; }; /// Collection of parameters base class diff --git a/src/Ethereum/ABI/ParamNumber.h b/src/Ethereum/ABI/ParamNumber.h index 05e10753b47..a9b9ea6f631 100644 --- a/src/Ethereum/ABI/ParamNumber.h +++ b/src/Ethereum/ABI/ParamNumber.h @@ -31,12 +31,12 @@ inline bool decode(const Data& encoded, uint256_t& decoded, size_t& offset_inout } /// Generic parameter class for numeric types, like bool, uint32, int64, etc. All are stored on 256 bits. -template +template class ParamNumberType : public ParamBase { public: ParamNumberType() = default; - ParamNumberType(T val) { _val = val; } + explicit ParamNumberType(const T& val): _val(val) { } void setVal(T val) { _val = val; } T getVal() const { return _val; } virtual std::string getType() const = 0; @@ -63,108 +63,119 @@ class ParamNumberType : public ParamBase T _val; }; -class ParamUInt256 : public ParamNumberType +class ParamUInt256 final : public ParamNumberType { public: ParamUInt256() : ParamNumberType(uint256_t(0)) {} - ParamUInt256(uint256_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint256"; } + explicit ParamUInt256(const uint256_t& val) : ParamNumberType(val) {} + std::string getType() const override { return "uint256"; } uint256_t getVal() const { return ParamNumberType::getVal(); } - virtual bool setValueJson(const std::string& value) { return setUInt256FromValueJson(_val, value); } + bool setValueJson(const std::string& value) override { return setUInt256FromValueJson(_val, value); } static bool setUInt256FromValueJson(uint256_t& dest, const std::string& value); + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamInt256 : public ParamNumberType +class ParamInt256 final : public ParamNumberType { public: ParamInt256() : ParamNumberType(int256_t(0)) {} - ParamInt256(int256_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int256"; } + explicit ParamInt256(const int256_t& val) : ParamNumberType(val) {} + std::string getType() const override { return "int256"; } int256_t getVal() const { return ParamNumberType::getVal(); } - virtual bool setValueJson(const std::string& value) { return setInt256FromValueJson(_val, value); } + bool setValueJson(const std::string& value) override { return setInt256FromValueJson(_val, value); } static bool setInt256FromValueJson(int256_t& dest, const std::string& value); + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamBool : public ParamNumberType +class ParamBool final : public ParamNumberType { public: ParamBool() : ParamNumberType(false) {} - ParamBool(bool val) : ParamNumberType(val) {} - virtual std::string getType() const { return "bool"; } + explicit ParamBool(bool val) : ParamNumberType(val) {} + std::string getType() const override { return "bool"; } bool getVal() const { return ParamNumberType::getVal(); } - virtual bool setValueJson(const std::string& value); + bool setValueJson(const std::string& value) override; + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamUInt8 : public ParamNumberType +class ParamUInt8 final : public ParamNumberType { public: ParamUInt8() : ParamNumberType(0) {} - ParamUInt8(uint8_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint8"; } - virtual bool setValueJson(const std::string& value); + explicit ParamUInt8(uint8_t val) : ParamNumberType(val) {} + std::string getType() const override { return "uint8"; } + bool setValueJson(const std::string& value) override; + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamInt8 : public ParamNumberType +class ParamInt8 final : public ParamNumberType { public: ParamInt8() : ParamNumberType(0) {} - ParamInt8(int8_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int8"; } - virtual bool setValueJson(const std::string& value); + explicit ParamInt8(int8_t val) : ParamNumberType(val) {} + std::string getType() const override { return "int8"; } + bool setValueJson(const std::string& value) override; + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamUInt16 : public ParamNumberType +class ParamUInt16 final : public ParamNumberType { public: ParamUInt16() : ParamNumberType(0) {} - ParamUInt16(uint16_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint16"; } + explicit ParamUInt16(uint16_t val) : ParamNumberType(val) {} + std::string getType() const override { return "uint16"; } + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamInt16 : public ParamNumberType +class ParamInt16 final : public ParamNumberType { public: ParamInt16() : ParamNumberType(0) {} - ParamInt16(int16_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int16"; } + explicit ParamInt16(int16_t val) : ParamNumberType(val) {} + std::string getType() const override { return "int16"; } + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamUInt32 : public ParamNumberType +class ParamUInt32 final : public ParamNumberType { public: ParamUInt32() : ParamNumberType(0) {} - ParamUInt32(uint32_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint32"; } + explicit ParamUInt32(uint32_t val) : ParamNumberType(val) {} + std::string getType() const override { return "uint32"; } + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamInt32 : public ParamNumberType +class ParamInt32 final : public ParamNumberType { public: ParamInt32() : ParamNumberType(0) {} - ParamInt32(int32_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int32"; } + explicit ParamInt32(int32_t val) : ParamNumberType(val) {} + std::string getType() const override { return "int32"; } + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamUInt64 : public ParamNumberType +class ParamUInt64 final : public ParamNumberType { public: ParamUInt64() : ParamNumberType(0) {} - ParamUInt64(uint64_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "uint64"; } + explicit ParamUInt64(uint64_t val) : ParamNumberType(val) {} + std::string getType() const override { return "uint64"; } + std::shared_ptr clone() const override { return std::make_shared(_val); } }; -class ParamInt64 : public ParamNumberType +class ParamInt64 final : public ParamNumberType { public: ParamInt64() : ParamNumberType(0) {} - ParamInt64(int64_t val) : ParamNumberType(val) {} - virtual std::string getType() const { return "int64"; } + explicit ParamInt64(int64_t val) : ParamNumberType(val) {} + std::string getType() const override { return "int64"; } + std::shared_ptr clone() const override { return std::make_shared(_val); } }; /// Generic parameter class for all other bit sizes, like UInt24, 40, 48, ... 248. /// For predefined sizes (8, 16, 32, 64, 256) use the sepcial types like UInt32. /// Stored on 256 bits. -class ParamUIntN : public ParamBase +class ParamUIntN final : public ParamBase { public: const size_t bits; @@ -172,16 +183,17 @@ class ParamUIntN : public ParamBase ParamUIntN(size_t bits_in, uint256_t val) : bits(bits_in) { init(); setVal(val); } void setVal(uint256_t val); uint256_t getVal() const { return _val; } - virtual std::string getType() const { return "uint" + std::to_string(bits); } - virtual size_t getSize() const { return ValueEncoder::encodedIntSize; } - virtual bool isDynamic() const { return false; } - virtual void encode(Data& data) const { ValueEncoder::encodeUInt256(_val, data); } + std::string getType() const override { return "uint" + std::to_string(bits); } + size_t getSize() const override { return ValueEncoder::encodedIntSize; } + bool isDynamic() const override { return false; } + void encode(Data& data) const override { ValueEncoder::encodeUInt256(_val, data); } static bool decodeNumber(const Data& encoded, uint256_t& decoded, size_t& offset_inout) { return ABI::decode(encoded, decoded, offset_inout); } - virtual bool decode(const Data& encoded, size_t& offset_inout); + bool decode(const Data& encoded, size_t& offset_inout) override; static uint256_t maskForBits(size_t bits); - virtual bool setValueJson(const std::string& value) { return ParamUInt256::setUInt256FromValueJson(_val, value); } + bool setValueJson(const std::string& value) override { return ParamUInt256::setUInt256FromValueJson(_val, value); } + std::shared_ptr clone() const override { return std::make_shared(bits, _val); } private: void init(); @@ -192,7 +204,7 @@ class ParamUIntN : public ParamBase /// Generic parameter class for all other bit sizes, like Int24, 40, 48, ... 248. /// For predefined sizes (8, 16, 32, 64, 256) use the sepcial types like Int32. /// Stored on 256 bits. -class ParamIntN : public ParamBase +class ParamIntN final : public ParamBase { public: const size_t bits; @@ -200,13 +212,14 @@ class ParamIntN : public ParamBase ParamIntN(size_t bits_in, int256_t val) : bits(bits_in) { init(); setVal(val); } void setVal(int256_t val); int256_t getVal() const { return _val; } - virtual std::string getType() const { return "int" + std::to_string(bits); } - virtual size_t getSize() const { return ValueEncoder::encodedIntSize; } - virtual bool isDynamic() const { return false; } - virtual void encode(Data& data) const { ValueEncoder::encodeUInt256((uint256_t)_val, data); } + std::string getType() const override { return "int" + std::to_string(bits); } + size_t getSize() const override { return ValueEncoder::encodedIntSize; } + bool isDynamic() const override { return false; } + void encode(Data& data) const override { ValueEncoder::encodeUInt256((uint256_t)_val, data); } static bool decodeNumber(const Data& encoded, int256_t& decoded, size_t& offset_inout); - virtual bool decode(const Data& encoded, size_t& offset_inout); - virtual bool setValueJson(const std::string& value) { return ParamInt256::setInt256FromValueJson(_val, value); } + bool decode(const Data& encoded, size_t& offset_inout) override; + bool setValueJson(const std::string& value) override { return ParamInt256::setInt256FromValueJson(_val, value); } + std::shared_ptr clone() const override { return std::make_shared(bits, _val); } private: void init(); diff --git a/src/Ethereum/ABI/ParamStruct.cpp b/src/Ethereum/ABI/ParamStruct.cpp index ac4de7b0ccb..680037ceb3d 100644 --- a/src/Ethereum/ABI/ParamStruct.cpp +++ b/src/Ethereum/ABI/ParamStruct.cpp @@ -27,6 +27,10 @@ std::string ParamNamed::getType() const { return _param->getType() + " " + _name; } +std::shared_ptr ParamNamed::cloneNamed() const { + return std::make_shared(_name, _param->clone()); +} + ParamSetNamed::~ParamSetNamed() { _params.clear(); } @@ -104,6 +108,14 @@ std::shared_ptr ParamSetNamed::findParamByName(const std::string& na return nullptr; } +ParamSetNamed ParamSetNamed::clone() const { + ParamSetNamed newSet; + for (const auto& p : _params) { + newSet.addParam(p->cloneNamed()); + } + return newSet; +} + Data ParamStruct::hashType() const { return Hash::keccak256(TW::data(encodeType())); } @@ -138,6 +150,10 @@ std::string ParamStruct::getExtraTypes(std::vector& ignoreList) con return types; } +std::shared_ptr ParamStruct::clone() const { + return std::make_shared(_name, _params.clone()); +} + Data ParamStruct::hashStructJson(const std::string& messageJson) { auto message = json::parse(messageJson, nullptr, false); if (message.is_discarded()) { diff --git a/src/Ethereum/ABI/ParamStruct.h b/src/Ethereum/ABI/ParamStruct.h index c043e588796..64b550d2cfb 100644 --- a/src/Ethereum/ABI/ParamStruct.h +++ b/src/Ethereum/ABI/ParamStruct.h @@ -15,25 +15,27 @@ namespace TW::Ethereum::ABI { /// A named parameter. -class ParamNamed: public ParamBase +class ParamNamed final : public ParamBase { public: std::string _name; std::shared_ptr _param; public: - ParamNamed(const std::string& name, std::shared_ptr param): _name(name), _param(param) {} - - virtual std::string getName() const { return _name; } - virtual std::shared_ptr getParam() const { return _param; } - virtual std::string getType() const; - virtual size_t getSize() const { return _param->getSize(); } - virtual bool isDynamic() const { return _param->isDynamic(); } - virtual void encode(Data& data) const { return _param->encode(data); } - virtual bool decode(const Data& encoded, size_t& offset_inout) { return _param->decode(encoded, offset_inout); } - virtual bool setValueJson(const std::string& value) { return _param->setValueJson(value); } - virtual Data hashStruct() const { return _param->hashStruct(); } - virtual std::string getExtraTypes(std::vector& ignoreList) const { return _param->getExtraTypes(ignoreList); } + explicit ParamNamed(const std::string& name, std::shared_ptr param): _name(name), _param(param) {} + + std::string getName() const { return _name; } + std::shared_ptr getParam() const { return _param; } + std::string getType() const override; + size_t getSize() const override { return _param->getSize(); } + bool isDynamic() const override { return _param->isDynamic(); } + void encode(Data& data) const override { return _param->encode(data); } + bool decode(const Data& encoded, size_t& offset_inout) override { return _param->decode(encoded, offset_inout); } + bool setValueJson(const std::string& value) override { return _param->setValueJson(value); } + Data hashStruct() const override { return _param->hashStruct(); } + std::string getExtraTypes(std::vector& ignoreList) const override { return _param->getExtraTypes(ignoreList); } + std::shared_ptr clone() const override { return cloneNamed(); } + std::shared_ptr cloneNamed() const; }; /// A collection of named parameters. See also: ParamStruct @@ -56,10 +58,11 @@ class ParamSetNamed { Data encodeHashes() const; std::string getExtraTypes(std::vector& ignoreList) const; std::shared_ptr findParamByName(const std::string& name) const; + ParamSetNamed clone() const; }; /// A named structure (set of parameters plus a type name). -class ParamStruct: public ParamCollection +class ParamStruct final : public ParamCollection { private: std::string _name; @@ -68,12 +71,13 @@ class ParamStruct: public ParamCollection public: ParamStruct() = default; ParamStruct(const std::string& name, const std::vector>& params) : ParamCollection(), _name(name), _params(ParamSetNamed(params)) {} + ParamStruct(const std::string& name, ParamSetNamed&& params) : ParamCollection(), _name(name), _params(std::move(params)) {} - std::string getType() const { return _name; } + std::string getType() const override { return _name; } const ParamSetNamed& getParams() const { return _params; } /// Compute the hash of a struct, used for signing, according to EIP712 - virtual Data hashStruct() const; + Data hashStruct() const override; /// Get full type, extended by used sub-types, of the form 'Mail(Person from,Person to,string contents)Person(string name,address wallet)' std::string encodeType() const { std::vector ignoreList; @@ -82,14 +86,15 @@ class ParamStruct: public ParamCollection /// Get the hash of the full type. Data hashType() const; - virtual size_t getSize() const { return _params.getCount(); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _params.getCount(); } - virtual void encode([[maybe_unused]] Data& data) const {} - virtual bool decode([[maybe_unused]] const Data& encoded, [[maybe_unused]] size_t& offset_inout) { return true; } - virtual bool setValueJson([[maybe_unused]] const std::string& value) { return false; } // see makeStruct + size_t getSize() const override { return _params.getCount(); } + bool isDynamic() const override { return true; } + size_t getCount() const override { return _params.getCount(); } + void encode([[maybe_unused]] Data& data) const override {} + bool decode([[maybe_unused]] const Data& encoded, [[maybe_unused]] size_t& offset_inout) override { return true; } + bool setValueJson([[maybe_unused]] const std::string& value) override { return false; } // see makeStruct Data encodeHashes() const; - virtual std::string getExtraTypes(std::vector& ignoreList) const; + std::string getExtraTypes(std::vector& ignoreList) const override; + std::shared_ptr clone() const override; std::shared_ptr findParamByName(const std::string& name) const { return _params.findParamByName(name); } /// Compute the hash of a struct, used for signing, according to EIP712 ("v4"). diff --git a/src/Ethereum/ABI/Parameters.cpp b/src/Ethereum/ABI/Parameters.cpp index b3eeb61ca04..973443fa540 100644 --- a/src/Ethereum/ABI/Parameters.cpp +++ b/src/Ethereum/ABI/Parameters.cpp @@ -128,28 +128,37 @@ void ParamSet::encode(Data& data) const { } bool ParamSet::decode(const Data& encoded, size_t& offset_inout) { - // pass 1: small values - for (auto p : _params) { + size_t arrayOffset = offset_inout; + + for (const auto& p : _params) { + // Decode a dynamic element. if (p->isDynamic()) { uint256_t index; if (!ABI::decode(encoded, index, offset_inout)) { return false; } - // index is read but not used - } else { - if (!p->decode(encoded, offset_inout)) { + + // Check if length is in the size_t range. + auto indexSize = static_cast(index); + if (indexSize != index) { return false; } - } - } - // pass2: large values - for (auto p : _params) { - if (p->isDynamic()) { - if (!p->decode(encoded, offset_inout)) { + + // Calculate an offset relative to the beginning of this array. + size_t newOffset = arrayOffset + indexSize; + if (!p->decode(encoded, newOffset)) { return false; } + + continue; + } + + // Otherwise decode a static element, e.g `p->isDynamic() == false`. + if (!p->decode(encoded, offset_inout)) { + return false; } } + return true; } @@ -161,6 +170,15 @@ Data ParamSet::encodeHashes() const { return hashes; } +ParamSet ParamSet::clone() const { + ParamSet newSet; + for (const auto& p : _params) { + newSet.addParam(p->clone()); + } + + return newSet; +} + Data Parameters::hashStruct() const { Data hash(32); Data hashes = _params.encodeHashes(); @@ -170,4 +188,10 @@ Data Parameters::hashStruct() const { return hash; } +std::shared_ptr Parameters::clone() const { + auto newParams = std::make_shared(); + newParams->_params = _params.clone(); + return newParams; +} + } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Parameters.h b/src/Ethereum/ABI/Parameters.h index 30aabf3d340..cc6d0844cd7 100644 --- a/src/Ethereum/ABI/Parameters.h +++ b/src/Ethereum/ABI/Parameters.h @@ -40,13 +40,15 @@ class ParamSet { virtual void encode(Data& data) const; virtual bool decode(const Data& encoded, size_t& offset_inout); Data encodeHashes() const; + /// Creates a copy of this set with exactly the same params. + ParamSet clone() const; private: size_t getHeadSize() const; }; /// Collection of different parameters, dynamic length, "(,,...)". -class Parameters: public ParamCollection +class Parameters final : public ParamCollection { private: ParamSet _params; @@ -57,14 +59,15 @@ class Parameters: public ParamCollection void addParam(const std::shared_ptr& param) { _params.addParam(param); } void addParams(const std::vector>& params) { _params.addParams(params); } std::shared_ptr getParam(size_t paramIndex) const { return _params.getParamUnsafe(paramIndex); } - virtual std::string getType() const { return _params.getType(); } - virtual size_t getSize() const { return _params.getSize(); } - virtual bool isDynamic() const { return true; } - virtual size_t getCount() const { return _params.getCount(); } - virtual void encode(Data& data) const { _params.encode(data); } - virtual bool decode(const Data& encoded, size_t& offset_inout) { return _params.decode(encoded, offset_inout); } - virtual bool setValueJson([[maybe_unused]] const std::string& value) { return false; } - virtual Data hashStruct() const; + std::string getType() const override { return _params.getType(); } + size_t getSize() const override { return _params.getSize(); } + bool isDynamic() const override { return true; } + size_t getCount() const override { return _params.getCount(); } + void encode(Data& data) const override { _params.encode(data); } + bool decode(const Data& encoded, size_t& offset_inout) override { return _params.decode(encoded, offset_inout); } + bool setValueJson([[maybe_unused]] const std::string& value) override { return false; } + Data hashStruct() const override; + std::shared_ptr clone() const override; }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ABI/Tuple.cpp b/src/Ethereum/ABI/Tuple.cpp index 5f36ac33024..1f661b4aeca 100644 --- a/src/Ethereum/ABI/Tuple.cpp +++ b/src/Ethereum/ABI/Tuple.cpp @@ -14,4 +14,10 @@ int ParamTuple::addParam(std::shared_ptr param) { return _params.addParam(param); } +std::shared_ptr ParamTuple::clone() const { + auto newTuple = std::make_shared(); + newTuple->_params = _params.clone(); + return newTuple; +} + } // namespace TW::Ethereum::ABI \ No newline at end of file diff --git a/src/Ethereum/ABI/Tuple.h b/src/Ethereum/ABI/Tuple.h index 6c8df69a789..2911703987e 100644 --- a/src/Ethereum/ABI/Tuple.h +++ b/src/Ethereum/ABI/Tuple.h @@ -13,13 +13,13 @@ namespace TW::Ethereum::ABI { /// A Tuple is a collection of parameters -class ParamTuple: public ParamCollection +class ParamTuple final : public ParamCollection { public: ParamSet _params; - ParamTuple() {} - ParamTuple(const std::vector>& params) : _params(ParamSet(params)) {} + ParamTuple() = default; + explicit ParamTuple(const std::vector>& params) : _params(ParamSet(params)) {} /// Add a parameter. Returns the index of the parameter. int addParam(std::shared_ptr param); @@ -28,14 +28,15 @@ class ParamTuple: public ParamCollection return _params.getParam(paramIndex, param_out); } /// Return the type signature, of the form "(int32,uint256)" - std::string getType() const { return _params.getType(); } - - virtual size_t getSize() const { return _params.getSize(); } - virtual bool isDynamic() const { return _params.isDynamic(); } - virtual void encode(Data& data) const { return _params.encode(data); } - virtual bool decode(const Data& encoded, size_t& offset_inout) { return _params.decode(encoded, offset_inout); } - virtual bool setValueJson([[maybe_unused]] const std::string& value) { return false; } - virtual size_t getCount() const { return _params.getCount(); } + std::string getType() const override { return _params.getType(); } + + size_t getSize() const override { return _params.getSize(); } + bool isDynamic() const override { return _params.isDynamic(); } + void encode(Data& data) const override { return _params.encode(data); } + bool decode(const Data& encoded, size_t& offset_inout) override { return _params.decode(encoded, offset_inout); } + bool setValueJson([[maybe_unused]] const std::string& value) override { return false; } + size_t getCount() const override { return _params.getCount(); } + std::shared_ptr clone() const override; }; } // namespace TW::Ethereum::ABI diff --git a/src/Ethereum/ContractCall.cpp b/src/Ethereum/ContractCall.cpp index 1cd9e76fb85..f2850c21dc4 100644 --- a/src/Ethereum/ContractCall.cpp +++ b/src/Ethereum/ContractCall.cpp @@ -31,15 +31,15 @@ static void fill(ParamSet& paramSet, const string& type) { } } -static vector getArrayValue(ParamSet& paramSet, const string& type, int idx) { +static vector getArrayValue(const ParamSet& paramSet, const string& type, int idx) { shared_ptr param; paramSet.getParam(idx, param); return ParamFactory::getArrayValue(param, type); } -static json buildInputs(ParamSet& paramSet, const json& registry); // forward +static json buildInputs(const ParamSet& paramSet, const json& registry); // forward -static json getTupleValue(ParamSet& paramSet, [[maybe_unused]] const string& type, int idx, const json& typeInfo) { +static json getTupleValue(const ParamSet& paramSet, [[maybe_unused]] const string& type, int idx, const json& typeInfo) { shared_ptr param; paramSet.getParam(idx, param); auto paramTuple = dynamic_pointer_cast(param); @@ -49,13 +49,32 @@ static json getTupleValue(ParamSet& paramSet, [[maybe_unused]] const string& typ return buildInputs(paramTuple->_params, typeInfo["components"]); } -static string getValue(ParamSet& paramSet, const string& type, int idx) { +static vector getArrayValueTuple(const ParamSet& paramSet, int idx, const json& typeInfo) { + shared_ptr param; + paramSet.getParam(idx, param); + + auto array = dynamic_pointer_cast(param); + if (!array) { + return {}; + } + + std::vector values; + for (const auto& tuple : array->getVal()) { + if (auto paramTuple = dynamic_pointer_cast(tuple); paramTuple) { + values.emplace_back(buildInputs(paramTuple->_params, typeInfo["components"])); + } + } + + return values; +} + +static string getValue(const ParamSet& paramSet, const string& type, int idx) { shared_ptr param; paramSet.getParam(idx, param); return ParamFactory::getValue(param, type); } -static json buildInputs(ParamSet& paramSet, const json& registry) { +static json buildInputs(const ParamSet& paramSet, const json& registry) { auto inputs = json::array(); for (auto i = 0ul; i < registry.size(); i++) { auto info = registry[i]; @@ -64,7 +83,9 @@ static json buildInputs(ParamSet& paramSet, const json& registry) { {"name", info["name"]}, {"type", type} }; - if (boost::algorithm::ends_with(type.get(), "[]")) { + if (type == "tuple[]") { + input["components"] = getArrayValueTuple(paramSet, static_cast(i), info); + } else if (boost::algorithm::ends_with(type.get(), "[]")) { input["value"] = json(getArrayValue(paramSet, type, static_cast(i))); } else if (type == "tuple") { input["components"] = getTupleValue(paramSet, type, static_cast(i), info); @@ -80,14 +101,18 @@ static json buildInputs(ParamSet& paramSet, const json& registry) { void fillTuple(ParamSet& paramSet, const json& jsonSet); // forward +void fillTupleArray(ParamSet& paramSet, const json& jsonSet); // forward + void decodeParamSet(ParamSet& paramSet, const json& jsonSet) { for (auto& comp : jsonSet) { if (comp["type"] == "tuple") { fillTuple(paramSet, comp["components"]); - } else { + } else if (comp["type"] == "tuple[]") { + fillTupleArray(paramSet, comp["components"]); + } else { fill(paramSet, comp["type"]); } - } + } } void fillTuple(ParamSet& paramSet, const json& jsonSet) { @@ -96,6 +121,14 @@ void fillTuple(ParamSet& paramSet, const json& jsonSet) { paramSet.addParam(param); } +void fillTupleArray(ParamSet& paramSet, const json& jsonSet) { + std::shared_ptr tuple = make_shared(); + decodeParamSet(tuple->_params, jsonSet); + + auto tupleArray = make_shared(tuple); + paramSet.addParam(tupleArray); +} + optional decodeCall(const Data& call, const json& abi) { // check bytes length if (call.size() <= 4) { diff --git a/tests/chains/Ethereum/AbiTests.cpp b/tests/chains/Ethereum/AbiTests.cpp index c1eceb2acf3..14e9e971fdb 100644 --- a/tests/chains/Ethereum/AbiTests.cpp +++ b/tests/chains/Ethereum/AbiTests.cpp @@ -856,7 +856,8 @@ TEST(EthereumAbi, DecodeArrayOfByteArray) { bool res = param.decode(encoded, offset); EXPECT_TRUE(res); EXPECT_EQ(2ul, param.getCount()); - EXPECT_EQ(7 * 32ul, offset); + // `size_of(array.len())` + `size_of(byte_array[0].len())` + `size_of(byte_array[1].len()) + EXPECT_EQ(3 * 32ul, offset); EXPECT_EQ(2ul, param.getVal().size()); } @@ -986,7 +987,8 @@ TEST(EthereumAbi, DecodeParamsMixed) { EXPECT_EQ(3, (std::dynamic_pointer_cast((std::dynamic_pointer_cast(p.getParam(1)))->getParam(2)))->getVal()); EXPECT_EQ(true, (std::dynamic_pointer_cast(p.getParam(2)))->getVal()); EXPECT_EQ("Hello", (std::dynamic_pointer_cast(p.getParam(3)))->getVal()); - EXPECT_EQ(13 * 32ul, offset); + // Offset of `ParamUInt256`, `ParamBool` static elements and sizes of `ParamArray`, `ParamBool`, `ParamByteArray`. + EXPECT_EQ(5 * 32ul, offset); } ///// Function encode & decode @@ -1113,7 +1115,8 @@ TEST(EthereumAbi, DecodeFunctionOutputCase2) { size_t offset = 0; bool res = func.decodeOutput(encoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(128ul, offset); + // The offset should only be shifted by the size of the array. + EXPECT_EQ(32ul, offset); // new output values std::shared_ptr param; @@ -1185,7 +1188,8 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase1) { EXPECT_EQ(3ul, (std::dynamic_pointer_cast(param))->getCount()); EXPECT_EQ(uint256_t(1), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[0]))->getVal()); EXPECT_EQ(uint256_t(3), (std::dynamic_pointer_cast((std::dynamic_pointer_cast(param))->getVal()[2]))->getVal()); - EXPECT_EQ(4 + 9 * 32ul, offset); + // Offset of `ParamBool` static element and sizes of `ParamByteArray`, `ParamArray` arrays. + EXPECT_EQ(4 + 3 * 32ul, offset); } TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { @@ -1228,7 +1232,8 @@ TEST(EthereumAbi, DecodeFunctionInputWithDynamicArgumentsCase2) { EXPECT_EQ("31323334353637383930", hex((std::dynamic_pointer_cast(param))->getVal())); EXPECT_TRUE(func.getInParam(3, param)); EXPECT_EQ(std::string("Hello, world!"), (std::dynamic_pointer_cast(param))->getVal()); - EXPECT_EQ(4 + 9 * 32ul, offset); + // Offset of `ParamUInt256` static element and sizes of `ParamArray`, `ParamByteArrayFix`, `ParamString` dynamic arrays. + EXPECT_EQ(4 + 4 * 32ul, offset); } TEST(EthereumAbi, DecodeFunctionContractMulticall) { @@ -1271,7 +1276,8 @@ TEST(EthereumAbi, DecodeFunctionContractMulticall) { size_t offset = 0; bool res = func.decodeInput(encoded, offset); EXPECT_TRUE(res); - EXPECT_EQ(4 + 29 * 32ul, offset); + // The offset should only be shifted by the size of the array. + EXPECT_EQ(4 + 32ul, offset); } TEST(EthereumAbi, ParamFactoryMake) { diff --git a/tests/chains/Ethereum/ContractCallTests.cpp b/tests/chains/Ethereum/ContractCallTests.cpp index ca3108d4a05..19af2d14dbf 100644 --- a/tests/chains/Ethereum/ContractCallTests.cpp +++ b/tests/chains/Ethereum/ContractCallTests.cpp @@ -217,4 +217,18 @@ TEST(ContractCall, TupleNested) { EXPECT_EQ(decoded.value(), expected); } +TEST(ContractCall, TupleArray) { + auto abiPath = TESTS_ROOT + "/chains/Ethereum/Data/swap_v2.json"; + auto decodedPath = TESTS_ROOT + "/chains/Ethereum/Data/swap_v2_decoded.json"; + auto abi = load_json(abiPath); + auto expectedJson = load_json(decodedPath); + + auto call = parse_hex("846a1bc6000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007c00000000000000000000000000000000000000000000000000000000000000820000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000003840651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d66600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000762696e616e636500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a307863653136463639333735353230616230313337376365374238386635424138433438463844363636000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + auto decoded = decodeCall(call, abi); + ASSERT_TRUE(decoded.has_value()); + + nlohmann::json parsedJson = nlohmann::json::parse(decoded.value()); + EXPECT_EQ(parsedJson, expectedJson); +} + } // namespace TW::Ethereum::ABI::tests diff --git a/tests/chains/Ethereum/Data/swap_v2.json b/tests/chains/Ethereum/Data/swap_v2.json new file mode 100644 index 00000000000..3651cd7b1ac --- /dev/null +++ b/tests/chains/Ethereum/Data/swap_v2.json @@ -0,0 +1,70 @@ +{ + "846a1bc6":{ + "inputs":[ + { + "name":"token", + "type":"address" + }, + { + "name":"amount", + "type":"uint256" + }, + { + "components":[ + { + "name":"callType", + "type":"uint8" + }, + { + "name":"target", + "type":"address" + }, + { + "name":"value", + "type":"uint256" + }, + { + "name":"callData", + "type":"bytes" + }, + { + "name":"payload", + "type":"bytes" + } + ], + "name":"calls", + "type":"tuple[]" + }, + { + "name":"bridgedTokenSymbol", + "type":"string" + }, + { + "name":"destinationChain", + "type":"string" + }, + { + "name":"destinationAddress", + "type":"string" + }, + { + "name":"payload", + "type":"bytes" + }, + { + "name":"gasRefundRecipient", + "type":"address" + }, + { + "name":"enableExpress", + "type":"bool" + } + ], + "name":"callBridgeCall", + "outputs":[ + + ], + "stateMutability":"nonpayable", + "type":"function" + } +} \ No newline at end of file diff --git a/tests/chains/Ethereum/Data/swap_v2_decoded.json b/tests/chains/Ethereum/Data/swap_v2_decoded.json new file mode 100644 index 00000000000..49a95d7806e --- /dev/null +++ b/tests/chains/Ethereum/Data/swap_v2_decoded.json @@ -0,0 +1,105 @@ +{ + "function": "callBridgeCall(address,uint256,(uint8,address,uint256,bytes,bytes)[],string,string,string,bytes,address,bool)", + "inputs": [ + { + "name": "token", + "type": "address", + "value": "0xdac17f958d2ee523a2206206994597c13d831ec7" + }, + { + "name": "amount", + "type": "uint256", + "value": "20000000000000000" + }, + { + "components": [ + [ + { + "name": "callType", + "type": "uint8", + "value": "0" + }, + { + "name": "target", + "type": "address", + "value": "0xdac17f958d2ee523a2206206994597c13d831ec7" + }, + { + "name": "value", + "type": "uint256", + "value": "0" + }, + { + "name": "callData", + "type": "bytes", + "value": "0x095ea7b300000000000000000000000099a58482bd75cbab83b27ec03ca68ff489b5788f00000000000000000000000000000000000000000000000000470de4df820000" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x" + } + ], + [ + { + "name": "callType", + "type": "uint8", + "value": "0" + }, + { + "name": "target", + "type": "address", + "value": "0x99a58482bd75cbab83b27ec03ca68ff489b5788f" + }, + { + "name": "value", + "type": "uint256", + "value": "0" + }, + { + "name": "callData", + "type": "bytes", + "value": "0x0651cb35000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000bebc44782c7db0a1a60cb6fe97d0b483032ff1c7000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000000000298ce42936ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce16f69375520ab01377ce7b88f5ba8c48f8d666" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x" + } + ] + ], + "name": "calls", + "type": "tuple[]" + }, + { + "name": "bridgedTokenSymbol", + "type": "string", + "value": "USDC" + }, + { + "name": "destinationChain", + "type": "string", + "value": "binance" + }, + { + "name": "destinationAddress", + "type": "string", + "value": "0xce16F69375520ab01377ce7B88f5BA8C48F8D666" + }, + { + "name": "payload", + "type": "bytes", + "value": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000ac000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf3890000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf300000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000640000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7044000000000000000000000000000000000000000000000000000029a23529cf68000000000000000000000000000000000000000000005af4f3f913bd553d03b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004268b8f0b87b6eae5d897996e6b845ddbd99adf30000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001b81d678ffb9c0263b24a97847620c99d213eb14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000104414bf38900000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000001f40000000000000000000000004fd39c9e151e50580779bd04b1f7ecc310079fd300000000000000000000000000000000000000000000000000000189c04a7045000000000000000000000000000000000000000000005b527785e694f805bdd300000000000000000000000000000000000000000000005f935a1fa5c4a6ec61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000055d398326f99059ff775485246999027b319795500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000242e1a7d4d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a140f413c63fbda84e9008607e678258fffbc76b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "gasRefundRecipient", + "type": "address", + "value": "0xa140f413c63fbda84e9008607e678258fffbc76b" + }, + { + "name": "enableExpress", + "type": "bool", + "value": true + } + ] +} \ No newline at end of file