-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eth/abi: implement packed encoder (#310)
* eth/abi: implement packed encoder * eth/abi: implement packed encoder * gem: update copyright headers * spec: add more tests * spec: add more tests * spec: add more tests
- Loading branch information
Showing
8 changed files
with
91,484 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# Copyright (c) 2016-2025 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 | ||
|
||
# Encapsulates the module for non-standard packed encoding used in Solidity. | ||
module Packed | ||
|
||
# Provides a utility module to assist encoding ABIs. | ||
module Encoder | ||
extend self | ||
|
||
# Encodes a specific value, either static or dynamic in non-standard | ||
# packed encoding mode. | ||
# | ||
# @param type [Eth::Abi::Type] type to be encoded. | ||
# @param arg [String|Number] value to be encoded. | ||
# @return [String] the packed encoded type. | ||
# @raise [EncodingError] if value does not match type. | ||
# @raise [ArgumentError] if encoding fails for type. | ||
def type(type, arg) | ||
case type | ||
when /^uint(\d+)$/ | ||
uint(arg, $1.to_i / 8) | ||
when /^int(\d+)$/ | ||
int(arg, $1.to_i / 8) | ||
when "bool" | ||
bool(arg) | ||
when /^ureal(\d+)x(\d+)$/, /^ufixed(\d+)x(\d+)$/ | ||
ufixed(arg, $1.to_i / 8, $2.to_i) | ||
when /^real(\d+)x(\d+)$/, /^fixed(\d+)x(\d+)$/ | ||
fixed(arg, $1.to_i / 8, $2.to_i) | ||
when "string" | ||
string(arg) | ||
when /^bytes(\d+)$/ | ||
bytes(arg, $1.to_i) | ||
when "bytes" | ||
string(arg) | ||
when /^tuple\((.+)\)$/ | ||
tuple($1.split(","), arg) | ||
when /^hash(\d+)$/ | ||
hash(arg, $1.to_i / 8) | ||
when "address" | ||
address(arg) | ||
when /^(.+)\[\]$/ | ||
array($1, arg) | ||
when /^(.+)\[(\d+)\]$/ | ||
fixed_array($1, arg, $2.to_i) | ||
else | ||
raise EncodingError, "Unhandled type: #{type}" | ||
end | ||
end | ||
|
||
private | ||
|
||
# Properly encodes signed integers. | ||
def uint(value, byte_size) | ||
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric | ||
raise ValueOutOfBounds, "Number out of range: #{value}" if value > Constant::UINT_MAX or value < Constant::UINT_MIN | ||
i = value.to_i | ||
Util.zpad_int i, byte_size | ||
end | ||
|
||
# Properly encodes signed integers. | ||
def int(value, byte_size) | ||
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric | ||
raise ValueOutOfBounds, "Number out of range: #{value}" if value > Constant::INT_MAX or value < Constant::INT_MIN | ||
real_size = byte_size * 8 | ||
i = value.to_i % 2 ** real_size | ||
Util.zpad_int i, byte_size | ||
end | ||
|
||
# Properly encodes booleans. | ||
def bool(value) | ||
raise EncodingError, "Argument is not bool: #{value}" unless value.instance_of? TrueClass or value.instance_of? FalseClass | ||
(value ? "\x01" : "\x00").b | ||
end | ||
|
||
# Properly encodes unsigned fixed-point numbers. | ||
def ufixed(value, byte_size, decimals) | ||
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric | ||
raise ValueOutOfBounds, value unless value >= 0 and value < 2 ** decimals | ||
scaled_value = (value * (10 ** decimals)).to_i | ||
uint(scaled_value, byte_size) | ||
end | ||
|
||
# Properly encodes signed fixed-point numbers. | ||
def fixed(value, byte_size, decimals) | ||
raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric | ||
raise ValueOutOfBounds, value unless value >= -2 ** (decimals - 1) and value < 2 ** (decimals - 1) | ||
scaled_value = (value * (10 ** decimals)).to_i | ||
int(scaled_value, byte_size) | ||
end | ||
|
||
# Properly encodes byte(-string)s. | ||
def bytes(value, length) | ||
raise EncodingError, "Expecting String: #{value}" unless value.instance_of? String | ||
value = handle_hex_string value, length | ||
raise ArgumentError, "Value must be a string of length #{length}" unless value.is_a?(String) && value.bytesize == length | ||
value.b | ||
end | ||
|
||
# Properly encodes (byte-)strings. | ||
def string(value) | ||
raise ArgumentError, "Value must be a string" unless value.is_a?(String) | ||
value.b | ||
end | ||
|
||
# Properly encodes tuples. | ||
def tuple(types, values) | ||
Abi.solidity_packed(types, values) | ||
end | ||
|
||
# Properly encodes hash-strings. | ||
def hash(value, byte_size) | ||
raise EncodingError, "Argument too long: #{value}" unless byte_size > 0 and byte_size <= 32 | ||
hash_bytes = handle_hex_string value, byte_size | ||
hash_bytes.b | ||
end | ||
|
||
# Properly encodes addresses. | ||
def address(value) | ||
if value.is_a? Address | ||
|
||
# from checksummed address with 0x prefix | ||
Util.zpad_hex value.to_s[2..-1], 20 | ||
elsif value.is_a? Integer | ||
|
||
# address from integer | ||
Util.zpad_int value, 20 | ||
elsif value.size == 20 | ||
|
||
# address from encoded address | ||
Util.zpad value, 20 | ||
elsif value.size == 40 | ||
|
||
# address from hexadecimal address | ||
Util.zpad_hex value, 20 | ||
elsif value.size == 42 and value[0, 2] == "0x" | ||
|
||
# address from hexadecimal address with 0x prefix | ||
Util.zpad_hex value[2..-1], 20 | ||
else | ||
raise EncodingError, "Could not parse address: #{value}" | ||
end | ||
end | ||
|
||
# Properly encodes dynamic-sized arrays. | ||
def array(type, values) | ||
values.map { |value| type(type, value) }.join.b | ||
end | ||
|
||
# Properly encodes fixed-size arrays. | ||
def fixed_array(type, values, size) | ||
raise ArgumentError, "Array size does not match" unless values.size == size | ||
array(type, values) | ||
end | ||
|
||
# The ABI encoder needs to be able to determine between a hex `"123"` | ||
# and a binary `"123"` string. | ||
def handle_hex_string(val, len) | ||
if Util.prefixed? val or | ||
(len === val.size / 2 and Util.hex? val) | ||
|
||
# There is no way telling whether a string is hex or binary with certainty | ||
# in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string. | ||
# Additionally, if the string size is exactly the double of the expected | ||
# binary size, we can assume a hex value. | ||
Util.hex_to_bin val | ||
else | ||
|
||
# Everything else will be assumed binary or raw string. | ||
val.b | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.