Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/abi: adapt abicoder type system #220

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions AUTHORS.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <https://github.com/cryptape/ruby-ethereum-abi> (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.
* <https://github.com/cryptape/ruby-rlp> (MIT)
5 changes: 5 additions & 0 deletions lib/eth/abi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down Expand Up @@ -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"
68 changes: 68 additions & 0 deletions lib/eth/abi/meta_type.rb
Original file line number Diff line number Diff line change
@@ -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"
182 changes: 182 additions & 0 deletions lib/eth/abi/parser.rb
Original file line number Diff line number Diff line change
@@ -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 <high>x<low>, 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
3 changes: 0 additions & 3 deletions lib/eth/abi/type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 48 additions & 0 deletions lib/eth/abi/types/address.rb
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading