diff --git a/AUTHORS.txt b/AUTHORS.txt index c8363611..86809447 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,9 +1,10 @@ The Ruby-Eth Contributors are: * Steve Ellis @se3000 * Afri Schoedon @q9f +* Yuta Kurotaki @kurotaky * John Omar @chainoperator * Joshua Peek @josh -* Yuta Kurotaki @kurotaky +* Gerald Bauer @geraldb See also: * https://github.com/q9f/eth.rb/graphs/contributors @@ -12,9 +13,9 @@ The Ruby-Eth project was maintained 2016-2020 in Steve Ellis's (@se3000) repository licensed under MIT conditions: * https://github.com/se3000/ruby-eth -The latest Ruby-Eth gem is not only a rewrite of the aforementioned gem +The latest Ruby-Eth gem is not only a rewrite of the aforementioned gem but also a partial merge of the Ethereum.rb Ruby Ethereum library by -Marek Kirejczyk (@marekkirejczyk) and Yuta Kurotaki (@kurotaky) licensed +Marek Kirejczyk (@marekkirejczyk) and Yuta Kurotaki (@kurotaky) licensed under MIT conditions: * https://github.com/EthWorks/ethereum.rb @@ -23,6 +24,10 @@ ABI gem by Jan Xie (@janx) and Zhang Yaning (@u2) licensed under MIT conditions: * https://github.com/cryptape/ruby-ethereum-abi +The latest version of the Ruby-Eth gem includes a revised version of the +ABI gem by Gerald Bauer (@geraldb) released to the public domain: +* https://github.com/rubycocos/blockchain/tree/master/abicoder + The latest version of the Ruby-Eth gem contains a condensed version of the RLP gem by Jan Xie (@janx) and Zhang Yaning (@u2) licensed under MIT conditions: diff --git a/README.md b/README.md index 55678ad6..cb288a99 100644 --- a/README.md +++ b/README.md @@ -111,5 +111,8 @@ It is not only a rewrite of the `eth` gem but also a partial merge of the `ether This gem also includes a revised version of the ABI gem by Jan Xie and Zhang Yaning. * (MIT) +This gem also includes a revised version of the ABI gem by Gerald Bauer. +* https://github.com/rubycocos/blockchain/tree/master/abicoder (PD) + It also contains a condensed version of the RLP gem by Jan Xie and Zhang Yaning. * (MIT) diff --git a/lib/eth/abi.rb b/lib/eth/abi.rb index aa2a09bd..758afae8 100644 --- a/lib/eth/abi.rb +++ b/lib/eth/abi.rb @@ -33,6 +33,9 @@ class DecodingError < StandardError; end # Provides a special out-of-bounds error for values. class ValueOutOfBounds < StandardError; end + # Provides a specific parser error if type cannot be determined. + class ParseError < StandardError; end + # Encodes Application Binary Interface (ABI) data. It accepts multiple # arguments and encodes using the head/tail mechanism. # @@ -152,3 +155,5 @@ def decode(types, data) require "eth/abi/encoder" require "eth/abi/event" require "eth/abi/type" +require "eth/abi/meta_type" +require "eth/abi/parser" diff --git a/lib/eth/abi/meta_type.rb b/lib/eth/abi/meta_type.rb new file mode 100644 index 00000000..860d78ab --- /dev/null +++ b/lib/eth/abi/meta_type.rb @@ -0,0 +1,68 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse common ABI types. + class MetaType + + # Parses a given type and returns a parsed type object. + # + # @param type [Object] a type to parse. + # + # @return [MetaType] a parsed type object. + def self.parse(type) + Parser.parse(type) + end + + # Allows determining if the type is of dynamic size. + # + # @return [Boolean] true if type is dynamic in size. + def dynamic? + size.nil? + end + + # Interface to force implementing the size method in child classes. + # + # @raise NotImplementedError if it's not implemented. + def size + raise NotImplementedError, "The size is not implemented for #{self.class.name}." + end + + # Interface to force implementing the size method in child classes. + # + # @raise [NotImplementedError] if it's not implemented. + def format + raise NotImplementedError, "The format is not implemented for #{self.class.name}." + end + end + end +end + +require "eth/abi/types/address" +require "eth/abi/types/array" +require "eth/abi/types/boolean" +require "eth/abi/types/bytes" +require "eth/abi/types/fixed_array" +require "eth/abi/types/fixed_bytes" +require "eth/abi/types/int" +require "eth/abi/types/string" +require "eth/abi/types/tuple" +require "eth/abi/types/uint" diff --git a/lib/eth/abi/parser.rb b/lib/eth/abi/parser.rb new file mode 100644 index 00000000..24521b0e --- /dev/null +++ b/lib/eth/abi/parser.rb @@ -0,0 +1,182 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + BASE_TYPES = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/ + TUPLE_TYPES = /^\((.*)\)((\[[0-9]*\])*)/x + + # Provides an ABI type parser. + module Parser + extend self + + # Attempts to parse a given type. + # + # @param type [String] a type to parse. + # @return [MetaType] a parsed type. + # @raise [ParserError] if parsing fails. + def parse(type) + return type if type.kind_of? MetaType + + if type =~ TUPLE_TYPES + sub_types = parse_tuple_type($1) + dimension = parse_dimension($2) + parsed_types = sub_types.map { |t| parse(t) } + return parse_array_type(TupleType.new(parsed_types), dimension) + end + + base_type, sub_type, dimension = parse_base_type(type) + validate_base_type(base_type, sub_type) + + sub_type = case base_type + when "address" + AddressType.new + when "bool" + BooleanType.new + when "bytes" + sub_type ? FixedBytesType.new(sub_type) : BytesType.new + when "int" + IntType.new(sub_type.to_i) + when "string" + StringType.new + when "uint" + UIntType.new(sub_type.to_i) + else + raise ParseError, "Unknown base type: #{base_type}" + end + parse_array_type(sub_type, dimension) + end + + # Parses types for their base-type, sub-type, and dimension (if any). + def parse_base_type(str) + _, base_type, sub_type, dimension = BASE_TYPES.match(str).to_a + if sub_type == "" + sub_type = nil + elsif sub_type.include? "x" + sub_type = sub_type + else + sub_type = sub_type.to_i + end + dimension = parse_dimension(dimension) + [base_type, sub_type, dimension] + end + + # Parses the dimensions of array types. + def parse_dimension(str) + dims = str.scan(/\[[0-9]*\]/) + dims = dims.map do |d| + size = d[1...-1] + size == "" ? -1 : size.to_i + end + return dims + end + + # Parses the type of an array. + def parse_array_type(sub_type, dimension) + return sub_type if dimension.first.nil? + sub_type = if dimension.first == -1 + ArrayType.new(sub_type) + else + FixedArrayType.new(sub_type, dimension) + end + return sub_type + end + + # Parses the types of a tuple. + def parse_tuple_type(str) + depth = 0 + collected = [] + current = "" + + str.each_char do |c| + case c + when "(" + depth += 1 + current += c + when "," + if depth == 0 + collected << current + current = "" + else + current += c + end + when ")" + depth -= 1 + current += c + else + current += c + end + end + collected << current unless current.empty? + return collected + end + + # Validates all known base types and raises if an issue occurs. + def validate_base_type(base_type, sub_type) + case base_type + when "string" + + # string can not have any suffix + raise ParseError, "String type must have no suffix or numerical suffix" if sub_type + when "bytes" + + # bytes can be no longer than 32 bytes + raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub_type.nil? || sub_type.to_i <= 32 + when "tuple" + + # tuples can not have any suffix + raise ParseError, "Tuple type must have no suffix or numerical suffix" unless sub_type.nil? + when "uint", "int" + + # integers must have a numerical suffix + raise ParseError, "Integer type must have numerical suffix" unless sub_type + + # integer size must be valid + size = sub_type.to_i + raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256 + raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0 + when "ureal", "real", "fixed", "ufixed" + + # floats must have valid dimensional suffix + raise ParseError, "Real type must have suffix of form x, e.g. 128x128" unless sub_type =~ /\A[0-9]+x[0-9]+\z/ + high, low = sub_type.split("x").map(&:to_i) + total = high + low + raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256 + raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0 + when "hash" + + # hashs must have numerical suffix + raise ParseError, "Hash type must have numerical suffix" unless sub_type =~ /\A[0-9]+\z/ + when "address" + + # addresses cannot have any suffix + raise ParseError, "Address cannot have suffix" unless sub_type.nil? + when "bool" + + # booleans cannot have any suffix + raise ParseError, "Bool cannot have suffix" unless sub_type.nil? + else + + # we cannot parse arbitrary types such as 'decimal' or 'hex' + raise ParseError, "Unknown base type: #{base_type}" + end + end + end + end +end diff --git a/lib/eth/abi/type.rb b/lib/eth/abi/type.rb index 2320e5e4..4feb8437 100644 --- a/lib/eth/abi/type.rb +++ b/lib/eth/abi/type.rb @@ -23,9 +23,6 @@ module Abi # Provides a class to handle and parse common ABI types. class Type - # Provides a specific parser error if type cannot be determined. - class ParseError < StandardError; end - # The base attribute, e.g., `string` or `bytes`. attr :base_type diff --git a/lib/eth/abi/types/address.rb b/lib/eth/abi/types/address.rb new file mode 100644 index 00000000..073042fb --- /dev/null +++ b/lib/eth/abi/types/address.rb @@ -0,0 +1,48 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse address types. + class AddressType < MetaType + + # Gets the static byte-size of an address type. + # + # @return [Integer] 32 (bytes). + def size + 32.freeze + end + + # Gets the string representation of an address type. + # + # @return [String] `address`. + def format + "address".freeze + end + + # Allows to compare two address types. + # + # @return [Boolean] true if types are of same class. + def ==(another_type) + another_type.kind_of? AddressType + end + end + end +end diff --git a/lib/eth/abi/types/array.rb b/lib/eth/abi/types/array.rb new file mode 100644 index 00000000..bc9083d8 --- /dev/null +++ b/lib/eth/abi/types/array.rb @@ -0,0 +1,60 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse array types. + class ArrayType < MetaType + + # The used sub-type of the array type. + attr_reader :sub_type + + # Creates a dynamic array type of the given sub-type. + # + # @param sub_type [MetaType] a sub-type for the array type. + # @raise [ArgumentError] if an invalid sub-type is provided. + def initialize(sub_type) + raise ArgumentError, "Invalid sub-type provided: #{sub_type}" unless sub_type.kind_of? MetaType + @sub_type = sub_type + end + + # Gets nothing because array types are dynamic in size. + # + # @return nil + def size + nil + end + + # Gets the string representation of an array type. + # + # @return [String] containing the subtype and size. + def format + "#{@sub_type.format}[]".freeze + end + + # Allows to compare two array types. + # + # @return [Boolean] true if types are of same class. + def ==(another_type) + another_type.kind_of? ArrayType and @sub_type == another_type.sub_type + end + end + end +end diff --git a/lib/eth/abi/types/boolean.rb b/lib/eth/abi/types/boolean.rb new file mode 100644 index 00000000..34cd57f1 --- /dev/null +++ b/lib/eth/abi/types/boolean.rb @@ -0,0 +1,48 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse boolean types. + class BooleanType < MetaType + + # Gets the static byte-size of a boolean type. + # + # @return [Integer] 32 (bytes). + def size + 32.freeze + end + + # Gets the string representation of a boolean type. + # + # @return [String] `bool`. + def format + "bool".freeze + end + + # Allows to compare two boolean types. + # + # @return [Boolean] true if types are of same class. + def ==(another_type) + another_type.kind_of? BooleanType + end + end + end +end diff --git a/lib/eth/abi/types/bytes.rb b/lib/eth/abi/types/bytes.rb new file mode 100644 index 00000000..6db6e1a6 --- /dev/null +++ b/lib/eth/abi/types/bytes.rb @@ -0,0 +1,48 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse bytes types. + class BytesType < MetaType + + # Gets nothing because bytes types are dynamic in size. + # + # @return nil + def size + nil + end + + # Gets the string representation of a bytes type. + # + # @return [String] `bytes`. + def format + "bytes".freeze + end + + # Allows to compare two bytes types. + # + # @return [Boolean] true if types are of same class. + def ==(another_type) + another_type.kind_of? BytesType + end + end + end +end diff --git a/lib/eth/abi/types/fixed_array.rb b/lib/eth/abi/types/fixed_array.rb new file mode 100644 index 00000000..4626f846 --- /dev/null +++ b/lib/eth/abi/types/fixed_array.rb @@ -0,0 +1,73 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse fixed array types. + class FixedArrayType < MetaType + + # The used sub-type of the array type. + attr_reader :sub_type + + # The dimension of the fixed array type. + attr_reader :dimension + + # Creates an array type of the given sub-type and fixed dimension. + # + # @param sub_type [MetaType] a sub-type for the array type. + # @param dimension [Integer] the fixed dimension of the array type. + # @raise [ArgumentError] if an invalid sub-type is provided. + def initialize(sub_type, dimension) + raise ArgumentError, "Invalid sub-type provided: #{sub_type}" unless sub_type.kind_of? MetaType + @sub_type = sub_type + @dimension = dimension + end + + # Gets the full size of the fixed array type. + # + # @return nil if subtype is dynamic. + # @return [Integer] size of the fixed array if sub-types are not dynamic. + def size + return nil if @sub_type.dynamic? + size = 0 + @dimension.each { |dim| + size += dim * @sub_type.size + } + return size + end + + # Gets the string representation of a fixed array type. + # + # @return [String] containing the subtype, size, and dimension. + def format + "#{@sub_type.format}#{@dimension}".freeze + end + + # Allows to compare two array types. + # + # @return [Boolean] true if types are of same class. + def ==(another_type) + another_type.kind_of? FixedArrayType and + @sub_type == another_type.sub_type and + @dimension == another_type.dimension + end + end + end +end diff --git a/lib/eth/abi/types/fixed_bytes.rb b/lib/eth/abi/types/fixed_bytes.rb new file mode 100644 index 00000000..5ced13f5 --- /dev/null +++ b/lib/eth/abi/types/fixed_bytes.rb @@ -0,0 +1,60 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse fixed bytes types. + class FixedBytesType < MetaType + + # Size of the fixed bytes type in bytes. + attr_reader :length + + # Initializes a fixed bytes type for a given byte-size. + # + # @param length [Integer] the byte-size of the fixed type (1, 2, .. 32). + # @raise [ArgumentError] if an invalid type byte-size is provided. + def initialize(length = 32) + raise ArgumentError, "Invalid fixed type size provided: bytes#{length}" unless length <= 32 && length > 0 + @length = length + end + + # Gets the static byte-size of a fixed bytes type. + # + # @return [Integer] 32 (bytes). + def size + 32.freeze + end + + # Gets the string representation of a fixed bytes type. + # + # @return [String] `address`. + def format + "bytes#{@length}".freeze + end + + # Allows to compare two fixed bytes types. + # + # @return [Boolean] true if types are of same class and length. + def ==(another_type) + another_type.kind_of? FixedBytesType and @length === another_type.length + end + end + end +end diff --git a/lib/eth/abi/types/int.rb b/lib/eth/abi/types/int.rb new file mode 100644 index 00000000..943b5f96 --- /dev/null +++ b/lib/eth/abi/types/int.rb @@ -0,0 +1,60 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse integer types. + class IntType < MetaType + + # Size of the integer type in bits. + attr_reader :bits + + # Initializes an integer type for a given bit-size. + # + # @param bits [Integer] the bit-size of the fixed type (8, 16, .. 256). + # @raise [ArgumentError] if an invalid type bit-size is provided. + def initialize(bits = 256) + raise ArgumentError, "Invalid fixed type size provided: int#{bits}" unless [8, 16, 32, 64, 128, 256].include? bits + @bits = bits + end + + # Gets the static byte-size of an integer type. + # + # @return [Integer] 32 (bytes). + def size + 32.freeze + end + + # Gets the string representation of an integer type. + # + # @return [String] `address`. + def format + "int#{@bits}".freeze + end + + # Allows to compare two integer types. + # + # @return [Boolean] true if types are of same class and size. + def ==(another_type) + another_type.kind_of? IntType and @bits === another_type.bits + end + end + end +end diff --git a/lib/eth/abi/types/string.rb b/lib/eth/abi/types/string.rb new file mode 100644 index 00000000..f6a67170 --- /dev/null +++ b/lib/eth/abi/types/string.rb @@ -0,0 +1,48 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse string types. + class StringType < MetaType + + # Gets nothing because string types are dynamic in size. + # + # @return nil + def size + nil + end + + # Gets the string representation of a string type. + # + # @return [String] `string`. + def format + "string".freeze + end + + # Allows to compare two string types. + # + # @return [Boolean] true if types are of same class. + def ==(another_type) + another_type.kind_of? StringType + end + end + end +end diff --git a/lib/eth/abi/types/tuple.rb b/lib/eth/abi/types/tuple.rb new file mode 100644 index 00000000..90d67533 --- /dev/null +++ b/lib/eth/abi/types/tuple.rb @@ -0,0 +1,52 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse tuple types. + class TupleType < MetaType + attr_reader :types + + def initialize(types) + types.each do |type| + raise ArgumentError, "Invalid sub-type provided: #{type}" unless type.kind_of? MetaType + end + @types = types + end + + def size + s = 0 + @types.each do |type| + return nil if type.dynamic? + s += type.size + end + return s + end + + def format + "(#{@types.map { |type| type.format }.join(",")})" + end + + def ==(another_type) + another_type.kind_of? TupleType and @types == another_type.types + end + end + end +end diff --git a/lib/eth/abi/types/uint.rb b/lib/eth/abi/types/uint.rb new file mode 100644 index 00000000..f35ac981 --- /dev/null +++ b/lib/eth/abi/types/uint.rb @@ -0,0 +1,41 @@ +# Copyright (c) 2016-2023 The Ruby-Eth Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- encoding : ascii-8bit -*- + +# Provides the {Eth} module. +module Eth + + # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI). + module Abi + + # Provides a class to handle and parse unsigned integer types. + class UIntType < IntType + + # Gets the string representation of an unsigned integer type. + # + # @return [String] `address`. + def format + "uint#{@bits}".freeze + end + + # Allows to compare two unsigned integer types. + # + # @return [Boolean] true if types are of same class and size. + def ==(another_type) + another_type.kind_of? UIntType and @bits === another_type.bits + end + end + end +end diff --git a/spec/eth/abi/meta_type_spec.rb b/spec/eth/abi/meta_type_spec.rb new file mode 100644 index 00000000..6b3cb368 --- /dev/null +++ b/spec/eth/abi/meta_type_spec.rb @@ -0,0 +1,7 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::MetaType do + it_behaves_like "an ABI type implementing the Meta interface" +end diff --git a/spec/eth/abi/parser_spec.rb b/spec/eth/abi/parser_spec.rb new file mode 100644 index 00000000..3d83d85d --- /dev/null +++ b/spec/eth/abi/parser_spec.rb @@ -0,0 +1,170 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::Parser do + it "parses dimensions" do + expect(Abi::Parser.parse_dimension("uint256")).to eq [] + expect(Abi::Parser.parse_dimension("uint256[]")).to eq [-1] + expect(Abi::Parser.parse_dimension("uint256[0]")).to eq [0] + expect(Abi::Parser.parse_dimension("uint256[7]")).to eq [7] + expect(Abi::Parser.parse_dimension("uint256[1][2][3][]")).to eq [1, 2, 3, -1] + end + + # it "parses complex types" do + # type = Abi::Parser.parse("uint256[3][]") + # expect(type.format).to eq "uint256[3][]" + # end + + context "abicoder tests" do + it "test_parse_dims" do + expect(Abi::Parser.parse_dimension("")).to eq [] + expect(Abi::Parser.parse_dimension("[]")).to eq [-1] + expect(Abi::Parser.parse_dimension("[][3]")).to eq [-1, 3] + expect(Abi::Parser.parse_dimension("[2][3]")).to eq [2, 3] + end + + it "test_parse_base_type" do + expect(Abi::Parser.parse_base_type("uint256")).to eq ["uint", 256, []] + expect(Abi::Parser.parse_base_type("uint8")).to eq ["uint", 8, []] + expect(Abi::Parser.parse_base_type("string")).to eq ["string", nil, []] + expect(Abi::Parser.parse_base_type("uint256[]")).to eq ["uint", 256, [-1]] + expect(Abi::Parser.parse_base_type("uint256[][3]")).to eq ["uint", 256, [-1, 3]] + expect(Abi::Parser.parse_base_type("string[]")).to eq ["string", nil, [-1]] + expect(Abi::Parser.parse_base_type("fixed128x128")).to eq ["fixed", "128x128", []] + end + + it "test_base" do + type = Abi::Parser.parse("uint256") + expect(type.format).to eq "uint256" + expect(type.class).to eq Eth::Abi::UIntType + expect(type.bits).to eq 256 + expect(type.dynamic?).to be_falsy + expect(type.size).to eq 32 + + type = Abi::Parser.parse("uint8") + expect(type.format).to eq "uint8" + expect(type.class).to eq Eth::Abi::UIntType + expect(type.bits).to eq 8 + expect(type.dynamic?).to be_falsy + expect(type.size).to eq 32 + + type = Abi::Parser.parse("string") + expect(type.format).to eq "string" + expect(type.class).to eq Eth::Abi::StringType + expect(type.dynamic?).to be_truthy + expect(type.size).not_to be + + type = Abi::Parser.parse("uint256[]") + expect(type.format).to eq "uint256[]" + expect(type.class).to eq Eth::Abi::ArrayType + expect(type.dynamic?).to be_truthy + expect(type.size).not_to be + + type = Abi::Parser.parse("string[]") + expect(type.format).to eq "string[]" + expect(type.class).to eq Eth::Abi::ArrayType + expect(type.dynamic?).to be_truthy + expect(type.size).not_to be + end + + it "test_parse_tuple_type" do + expect(Abi::Parser.parse_tuple_type("string,string,bool")).to eq ["string", "string", "bool"] + expect(Abi::Parser.parse_tuple_type("string,(string,bool)")).to eq ["string", "(string,bool)"] + expect(Abi::Parser.parse_tuple_type("string,(string,(string,uint256[])),address[4]")).to eq ["string", "(string,(string,uint256[]))", "address[4]"] + end + + it "test_tuple" do + type = Abi::Parser.parse("(string,string,bool)") + expect(type.format).to eq "(string,string,bool)" + expect(type.class).to eq Eth::Abi::TupleType + expect(type.dynamic?).to be_truthy + expect(type.size).not_to be + + type = Abi::Parser.parse("(string,(string,(string,uint256[])),address[4])") + expect(type.format).to eq "(string,(string,(string,uint256[])),address[4])" + expect(type.types.map { |t| t.format }).to eq ["string", "(string,(string,uint256[]))", "address[4]"] + expect(type.class).to eq Eth::Abi::TupleType + expect(type.dynamic?).to be_truthy + expect(type.size).not_to be + end + + it "test_type_parse" do + expect(Abi::Parser.parse("uint8")).to eq Abi::UIntType.new(8) + expect(Abi::Parser.parse("bytes32")).to eq Abi::FixedBytesType.new(32) + expect(Abi::Parser.parse("uint256[10]")).to eq Abi::FixedArrayType.new(Abi::UIntType.new(256), [10]) + # expect(Abi::Parser.parse("fixed128x128[1][2][3][]")).to eq "foo" + end + + it "test_type_parse_validations" do + expect { + Abi::Parser.parse("string8") + }.to raise_error Abi::ParseError, "String type must have no suffix or numerical suffix" + expect { + Abi::Parser.parse("bytes33") + }.to raise_error Abi::ParseError, "Maximum 32 bytes for fixed-length string or bytes" + expect { + Abi::Parser.parse("hash") + }.to raise_error Abi::ParseError, "Hash type must have numerical suffix" + expect { + Abi::Parser.parse("address8") + }.to raise_error Abi::ParseError, "Address cannot have suffix" + expect { + Abi::Parser.parse("bool8") + }.to raise_error Abi::ParseError, "Bool cannot have suffix" + expect { + Abi::Parser.parse("decimal") + }.to raise_error Abi::ParseError, "Unknown base type: decimal" + expect { + Abi::Parser.parse("int") + }.to raise_error Abi::ParseError, "Integer type must have numerical suffix" + expect { + Abi::Parser.parse("int2") + }.to raise_error Abi::ParseError, "Integer size out of bounds" + expect { + Abi::Parser.parse("int20") + }.to raise_error Abi::ParseError, "Integer size must be multiple of 8" + expect { + Abi::Parser.parse("int512") + }.to raise_error Abi::ParseError, "Integer size out of bounds" + expect { + Abi::Parser.parse("fixed") + }.to raise_error Abi::ParseError, "Real type must have suffix of form x, e.g. 128x128" + expect { + Abi::Parser.parse("fixed256") + }.to raise_error Abi::ParseError, "Real type must have suffix of form x, e.g. 128x128" + expect { + Abi::Parser.parse("fixed2x2") + }.to raise_error Abi::ParseError, "Real size out of bounds (max 32 bytes)" + expect { + Abi::Parser.parse("fixed20x20") + }.to raise_error Abi::ParseError, "Real high/low sizes must be multiples of 8" + expect { + Abi::Parser.parse("fixed256x256") + }.to raise_error Abi::ParseError, "Real size out of bounds (max 32 bytes)" + end + + it "test_type_size" do + expect(Abi::Parser.parse("string").size).not_to be + expect(Abi::Parser.parse("bytes").size).not_to be + expect(Abi::Parser.parse("uint256[]").size).not_to be + # expect(Abi::Parser.parse("uint256[4][]").size).not_to be + expect(Abi::Parser.parse("uint256").size).to eq 32 + expect(Abi::Parser.parse("bool").size).to eq 32 + expect(Abi::Parser.parse("uint256[2]").size).to eq 64 + expect(Abi::Parser.parse("address[2][2]").size).to eq 128 + expect(Abi::Parser.parse("bytes3[2]").size).to eq 64 + # expect(Abi::Parser.parse("fixed128x128").size).to eq 32 + # expect(Abi::Parser.parse("ufixed192x64[2][2][2][2][2]").size).to eq 1024 + end + + it "test_subtype_of_array" do + type = Abi::Parser.parse("uint256[2][]") + # expect(type.sub_type.dimension).to eq 2 + # pp type + # pp type.sub_type + # pp type.sub_type.sub_type + # pp type.sub_type.sub_type.sub_type + end + end +end diff --git a/spec/eth/abi/type_spec.rb b/spec/eth/abi/type_spec.rb index 6b61975d..bc053338 100644 --- a/spec/eth/abi/type_spec.rb +++ b/spec/eth/abi/type_spec.rb @@ -21,23 +21,23 @@ it "raises parse error for invalid types" do # https://github.com/cryptape/ruby-ethereum-abi/blob/90d4fa3fc6b568581165eaacdc506b9b9b49e520/test/abi/type_test.rb#L15 - expect { Abi::Type.parse "string8" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "bytes33" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "hash" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "address8" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "bool8" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "decimal" }.to raise_error Abi::Type::ParseError - - expect { Abi::Type.parse "int" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "int2" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "int20" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "int512" }.to raise_error Abi::Type::ParseError - - expect { Abi::Type.parse "fixed" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "fixed256" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "fixed2x2" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "fixed20x20" }.to raise_error Abi::Type::ParseError - expect { Abi::Type.parse "fixed256x256" }.to raise_error Abi::Type::ParseError + expect { Abi::Type.parse "string8" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "bytes33" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "hash" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "address8" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "bool8" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "decimal" }.to raise_error Abi::ParseError + + expect { Abi::Type.parse "int" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "int2" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "int20" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "int512" }.to raise_error Abi::ParseError + + expect { Abi::Type.parse "fixed" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "fixed256" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "fixed2x2" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "fixed20x20" }.to raise_error Abi::ParseError + expect { Abi::Type.parse "fixed256x256" }.to raise_error Abi::ParseError end it "raises parse error for invalid types" do diff --git a/spec/eth/abi/types/address_spec.rb b/spec/eth/abi/types/address_spec.rb new file mode 100644 index 00000000..e11cddc5 --- /dev/null +++ b/spec/eth/abi/types/address_spec.rb @@ -0,0 +1,19 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::AddressType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has a size" do + expect(Abi::AddressType.new.size).to eq 32 + end + + it "has a format" do + expect(Abi::AddressType.new.format).to eq "address" + end + + it "can be compared" do + expect(Abi::AddressType.new == Abi::AddressType.new).to be_truthy + end +end diff --git a/spec/eth/abi/types/array_spec.rb b/spec/eth/abi/types/array_spec.rb new file mode 100644 index 00000000..2e96937c --- /dev/null +++ b/spec/eth/abi/types/array_spec.rb @@ -0,0 +1,26 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::ArrayType do + it "has no size" do + expect(Abi::ArrayType.new(Abi::BytesType.new).size).not_to be + end + + it "has a format" do + expect(Abi::ArrayType.new(Abi::BytesType.new).format).to eq "bytes[]" + expect(Abi::ArrayType.new(Abi::IntType.new(256)).format).to eq "int256[]" + expect(Abi::ArrayType.new(Abi::FixedBytesType.new(32)).format).to eq "bytes32[]" + expect(Abi::ArrayType.new(Abi::AddressType.new).format).to eq "address[]" + end + + it "can be compared" do + expect(Abi::ArrayType.new(Abi::BytesType.new) == Abi::ArrayType.new(Abi::BytesType.new)).to be_truthy + expect(Abi::ArrayType.new(Abi::IntType.new(256)) == Abi::ArrayType.new(Abi::IntType.new(256))).to be_truthy + expect(Abi::ArrayType.new(Abi::FixedBytesType.new(32)) == Abi::ArrayType.new(Abi::FixedBytesType.new(32))).to be_truthy + expect(Abi::ArrayType.new(Abi::AddressType.new) == Abi::ArrayType.new(Abi::AddressType.new)).to be_truthy + + expect(Abi::ArrayType.new(Abi::AddressType.new) == Abi::ArrayType.new(Abi::BooleanType.new)).to be_falsy + expect(Abi::ArrayType.new((Abi::BytesType.new)) == Abi::FixedArrayType.new(Abi::BytesType.new, 2)).to be_falsy + end +end diff --git a/spec/eth/abi/types/boolean_spec.rb b/spec/eth/abi/types/boolean_spec.rb new file mode 100644 index 00000000..371acb11 --- /dev/null +++ b/spec/eth/abi/types/boolean_spec.rb @@ -0,0 +1,19 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::BooleanType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has a size" do + expect(Abi::BooleanType.new.size).to eq 32 + end + + it "has a format" do + expect(Abi::BooleanType.new.format).to eq "bool" + end + + it "can be compared" do + expect(Abi::BooleanType.new == Abi::BooleanType.new).to be_truthy + end +end diff --git a/spec/eth/abi/types/bytes_spec.rb b/spec/eth/abi/types/bytes_spec.rb new file mode 100644 index 00000000..3d9d3269 --- /dev/null +++ b/spec/eth/abi/types/bytes_spec.rb @@ -0,0 +1,19 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::BytesType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has no size" do + expect(Abi::BytesType.new.size).not_to be + end + + it "has a format" do + expect(Abi::BytesType.new.format).to eq "bytes" + end + + it "can be compared" do + expect(Abi::BytesType.new == Abi::BytesType.new).to be_truthy + end +end diff --git a/spec/eth/abi/types/fixed_array_spec.rb b/spec/eth/abi/types/fixed_array_spec.rb new file mode 100644 index 00000000..ee57dd16 --- /dev/null +++ b/spec/eth/abi/types/fixed_array_spec.rb @@ -0,0 +1,31 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::FixedArrayType do + it "has a size or not" do + expect(Abi::FixedArrayType.new(Abi::BytesType.new, [2]).size).not_to be + + expect(Abi::FixedArrayType.new(Abi::IntType.new(256), [2]).size).to eq 64 + expect(Abi::FixedArrayType.new(Abi::FixedBytesType.new(32), [16]).size).to eq 512 + expect(Abi::FixedArrayType.new(Abi::AddressType.new, [137]).size).to eq 4384 + end + + it "has a format" do + expect(Abi::FixedArrayType.new(Abi::BytesType.new, [2]).format).to eq "bytes[2]" + expect(Abi::FixedArrayType.new(Abi::IntType.new(256), [2]).format).to eq "int256[2]" + expect(Abi::FixedArrayType.new(Abi::FixedBytesType.new(32), [16]).format).to eq "bytes32[16]" + expect(Abi::FixedArrayType.new(Abi::AddressType.new, [137]).format).to eq "address[137]" + end + + it "can be compared" do + expect(Abi::FixedArrayType.new(Abi::BytesType.new, [2]) == Abi::FixedArrayType.new(Abi::BytesType.new, [2])).to be_truthy + expect(Abi::FixedArrayType.new(Abi::IntType.new(256), [2]) == Abi::FixedArrayType.new(Abi::IntType.new(256), [2])).to be_truthy + expect(Abi::FixedArrayType.new(Abi::FixedBytesType.new(32), [16]) == Abi::FixedArrayType.new(Abi::FixedBytesType.new(32), [16])).to be_truthy + expect(Abi::FixedArrayType.new(Abi::AddressType.new, [137]) == Abi::FixedArrayType.new(Abi::AddressType.new, [137])).to be_truthy + + expect(Abi::FixedArrayType.new(Abi::AddressType.new, [2]) == Abi::FixedArrayType.new(Abi::AddressType.new, [7])).to be_falsy + expect(Abi::FixedArrayType.new(Abi::AddressType.new, [2]) == Abi::FixedArrayType.new(Abi::BooleanType.new, [2])).to be_falsy + expect(Abi::FixedArrayType.new(Abi::BytesType.new, [2]) == Abi::ArrayType.new(Abi::BytesType.new)).to be_falsy + end +end diff --git a/spec/eth/abi/types/fixed_bytes_spec.rb b/spec/eth/abi/types/fixed_bytes_spec.rb new file mode 100644 index 00000000..a73a91a0 --- /dev/null +++ b/spec/eth/abi/types/fixed_bytes_spec.rb @@ -0,0 +1,31 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::FixedBytesType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has a size" do + expect(Abi::FixedBytesType.new(1).size).to eq 32 + expect(Abi::FixedBytesType.new(2).size).to eq 32 + expect(Abi::FixedBytesType.new(8).size).to eq 32 + expect(Abi::FixedBytesType.new(32).size).to eq 32 + end + + it "has a format" do + expect(Abi::FixedBytesType.new(1).format).to eq "bytes1" + expect(Abi::FixedBytesType.new(2).format).to eq "bytes2" + expect(Abi::FixedBytesType.new(8).format).to eq "bytes8" + expect(Abi::FixedBytesType.new(32).format).to eq "bytes32" + end + + it "can be compared" do + expect(Abi::FixedBytesType.new(1) == Abi::FixedBytesType.new(1)).to be_truthy + expect(Abi::FixedBytesType.new(2) == Abi::FixedBytesType.new(2)).to be_truthy + expect(Abi::FixedBytesType.new(8) == Abi::FixedBytesType.new(8)).to be_truthy + expect(Abi::FixedBytesType.new(32) == Abi::FixedBytesType.new(32)).to be_truthy + + expect(Abi::FixedBytesType.new(4) == Abi::FixedBytesType.new(16)).to be_falsy + expect(Abi::FixedBytesType.new(32) == Abi::BytesType.new).to be_falsy + end +end diff --git a/spec/eth/abi/types/int_spec.rb b/spec/eth/abi/types/int_spec.rb new file mode 100644 index 00000000..6f660aa9 --- /dev/null +++ b/spec/eth/abi/types/int_spec.rb @@ -0,0 +1,31 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::IntType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has a size" do + expect(Abi::IntType.new(8).size).to eq 32 + expect(Abi::IntType.new(16).size).to eq 32 + expect(Abi::IntType.new(64).size).to eq 32 + expect(Abi::IntType.new(256).size).to eq 32 + end + + it "has a format" do + expect(Abi::IntType.new(8).format).to eq "int8" + expect(Abi::IntType.new(16).format).to eq "int16" + expect(Abi::IntType.new(64).format).to eq "int64" + expect(Abi::IntType.new(256).format).to eq "int256" + end + + it "can be compared" do + expect(Abi::IntType.new(8) == Abi::IntType.new(8)).to be_truthy + expect(Abi::IntType.new(16) == Abi::IntType.new(16)).to be_truthy + expect(Abi::IntType.new(64) == Abi::IntType.new(64)).to be_truthy + expect(Abi::IntType.new(256) == Abi::IntType.new(256)).to be_truthy + + expect(Abi::IntType.new(32) == Abi::IntType.new(128)).to be_falsy + expect(Abi::IntType.new(32) == Abi::UIntType.new).to be_falsy + end +end diff --git a/spec/eth/abi/types/string_spec.rb b/spec/eth/abi/types/string_spec.rb new file mode 100644 index 00000000..e2d0002d --- /dev/null +++ b/spec/eth/abi/types/string_spec.rb @@ -0,0 +1,19 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::StringType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has no size" do + expect(Abi::StringType.new.size).not_to be + end + + it "has a format" do + expect(Abi::StringType.new.format).to eq "string" + end + + it "can be compared" do + expect(Abi::StringType.new == Abi::StringType.new).to be_truthy + end +end diff --git a/spec/eth/abi/types/tuple_spec.rb b/spec/eth/abi/types/tuple_spec.rb new file mode 100644 index 00000000..3c8409fe --- /dev/null +++ b/spec/eth/abi/types/tuple_spec.rb @@ -0,0 +1,152 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::TupleType do + it "has a size or not" do + expect(Abi::TupleType.new([Abi::BytesType.new, Abi::IntType.new(256)]).size).not_to be + expect( + Abi::TupleType.new([ + Abi::TupleType.new( + [Abi::BytesType.new, Abi::IntType.new(256)] + ), + Abi::TupleType.new( + [Abi::BytesType.new, Abi::IntType.new(256)] + ), + ]).size + ).not_to be + expect( + Abi::TupleType.new([ + Abi::ArrayType.new(Abi::IntType.new(256)), + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + ]).size + ).not_to be + + expect( + Abi::TupleType.new([ + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + Abi::FixedArrayType.new(Abi::UIntType.new(256), [2]), + ]).size + ).to eq 128 + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(8), + Abi::IntType.new(256), + ]).size + ).to eq 64 + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(32), + Abi::FixedArrayType.new(Abi::IntType.new(256), [7]), + ]).size + ).to eq 256 + end + + it "has a format" do + expect(Abi::TupleType.new([Abi::BytesType.new, Abi::IntType.new(256)]).format).to eq "(bytes,int256)" + expect( + Abi::TupleType.new([ + Abi::TupleType.new( + [Abi::BytesType.new, Abi::IntType.new(256)] + ), + Abi::TupleType.new( + [Abi::BytesType.new, Abi::UIntType.new(256)] + ), + ]).format + ).to eq "((bytes,int256),(bytes,uint256))" + expect( + Abi::TupleType.new([ + Abi::ArrayType.new(Abi::IntType.new(256)), + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + ]).format + ).to eq "(int256[],int256[2])" + expect( + Abi::TupleType.new([ + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + Abi::FixedArrayType.new(Abi::UIntType.new(256), [4]), + ]).format + ).to eq "(int256[2],uint256[4])" + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(8), + Abi::IntType.new(256), + ]).format + ).to eq "(bytes8,int256)" + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(32), + Abi::FixedArrayType.new(Abi::IntType.new(256), [7]), + ]).format + ).to eq "(bytes32,int256[7])" + end + + it "can be compared" do + expect( + Abi::TupleType.new([Abi::BytesType.new, Abi::IntType.new(256)]) == Abi::TupleType.new([Abi::BytesType.new, Abi::IntType.new(256)]) + ).to be_truthy + expect( + Abi::TupleType.new([ + Abi::TupleType.new( + [Abi::BytesType.new, Abi::IntType.new(256)] + ), + Abi::TupleType.new( + [Abi::BytesType.new, Abi::UIntType.new(256)] + ), + ]) == Abi::TupleType.new([ + Abi::TupleType.new( + [Abi::BytesType.new, Abi::IntType.new(256)] + ), + Abi::TupleType.new( + [Abi::BytesType.new, Abi::UIntType.new(256)] + ), + ]) + ).to be_truthy + expect( + Abi::TupleType.new([ + Abi::ArrayType.new(Abi::IntType.new(256)), + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + ]) == Abi::TupleType.new([ + Abi::ArrayType.new(Abi::IntType.new(256)), + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + ]) + ).to be_truthy + expect( + Abi::TupleType.new([ + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + Abi::FixedArrayType.new(Abi::UIntType.new(256), [4]), + ]) == Abi::TupleType.new([ + Abi::FixedArrayType.new(Abi::IntType.new(256), [2]), + Abi::FixedArrayType.new(Abi::UIntType.new(256), [4]), + ]) + ).to be_truthy + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(8), + Abi::IntType.new(256), + ]) == + Abi::TupleType.new([ + Abi::FixedBytesType.new(8), + Abi::IntType.new(256), + ]) + ).to be_truthy + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(32), + Abi::FixedArrayType.new(Abi::IntType.new(256), [7]), + ]) == Abi::TupleType.new([ + Abi::FixedBytesType.new(32), + Abi::FixedArrayType.new(Abi::IntType.new(256), [7]), + ]) + ).to be_truthy + + expect( + Abi::TupleType.new([ + Abi::FixedBytesType.new(8), + Abi::IntType.new(256), + ]) == Abi::TupleType.new([ + Abi::AddressType.new, + Abi::FixedArrayType.new(Abi::IntType.new(256), [7]), + ]) + ).to be_falsy + end +end diff --git a/spec/eth/abi/types/uint_spec.rb b/spec/eth/abi/types/uint_spec.rb new file mode 100644 index 00000000..67534544 --- /dev/null +++ b/spec/eth/abi/types/uint_spec.rb @@ -0,0 +1,31 @@ +# -*- encoding : ascii-8bit -*- + +require "spec_helper" + +describe Abi::UIntType do + it_behaves_like "an ABI type implementing the Meta interface" + + it "has a size" do + expect(Abi::UIntType.new(8).size).to eq 32 + expect(Abi::UIntType.new(16).size).to eq 32 + expect(Abi::UIntType.new(64).size).to eq 32 + expect(Abi::UIntType.new(256).size).to eq 32 + end + + it "has a format" do + expect(Abi::UIntType.new(8).format).to eq "uint8" + expect(Abi::UIntType.new(16).format).to eq "uint16" + expect(Abi::UIntType.new(64).format).to eq "uint64" + expect(Abi::UIntType.new(256).format).to eq "uint256" + end + + it "can be compared" do + expect(Abi::UIntType.new(8) == Abi::UIntType.new(8)).to be_truthy + expect(Abi::UIntType.new(16) == Abi::UIntType.new(16)).to be_truthy + expect(Abi::UIntType.new(64) == Abi::UIntType.new(64)).to be_truthy + expect(Abi::UIntType.new(256) == Abi::UIntType.new(256)).to be_truthy + + expect(Abi::UIntType.new(32) == Abi::UIntType.new(128)).to be_falsy + expect(Abi::UIntType.new(32) == Abi::IntType.new).to be_falsy + end +end diff --git a/spec/eth/abi_spec.rb b/spec/eth/abi_spec.rb index 48b85ab6..bf78c426 100644 --- a/spec/eth/abi_spec.rb +++ b/spec/eth/abi_spec.rb @@ -329,7 +329,7 @@ end end - describe "abicoder tests" do + context "abicoder tests" do # https://github.com/rubycocos/blockchain/blob/ccef43a600e0832fb5e662bb0840656c974c0dc5/abicoder/test/test_spec.rb def assert(data, types, args) expect(data).to eq Abi.encode(types, args) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 44f4d16a..58c46219 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,3 +14,8 @@ end include Eth + +shared_examples "an ABI type implementing the Meta interface" do + it { is_expected.to respond_to(:size) } + it { is_expected.to respond_to(:format) } +end