ethereum-contract-abi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/3.0 +0 -0
  5. data/Gemfile +15 -0
  6. data/Gemfile.lock +37 -0
  7. data/LICENSE +21 -0
  8. data/README.md +27 -0
  9. data/Rakefile +8 -0
  10. data/bin/console +15 -0
  11. data/ethereum_contract_abi.gemspec +15 -0
  12. data/lib/ethereum-contract-abi.rb +24 -0
  13. data/lib/ethereum-contract-abi/constants.rb +6 -0
  14. data/lib/ethereum-contract-abi/contract.rb +59 -0
  15. data/lib/ethereum-contract-abi/contract/abi_types/address.rb +29 -0
  16. data/lib/ethereum-contract-abi/contract/abi_types/base_type.rb +35 -0
  17. data/lib/ethereum-contract-abi/contract/abi_types/bool.rb +33 -0
  18. data/lib/ethereum-contract-abi/contract/abi_types/bytes.rb +42 -0
  19. data/lib/ethereum-contract-abi/contract/abi_types/fixed.rb +46 -0
  20. data/lib/ethereum-contract-abi/contract/abi_types/int.rb +48 -0
  21. data/lib/ethereum-contract-abi/contract/abi_types/string.rb +44 -0
  22. data/lib/ethereum-contract-abi/contract/abi_types/uint.rb +30 -0
  23. data/lib/ethereum-contract-abi/contract/eip/constants.rb +10 -0
  24. data/lib/ethereum-contract-abi/contract/eip/erc1155_metadata_interface.rb +30 -0
  25. data/lib/ethereum-contract-abi/contract/eip/erc165_interface.rb +30 -0
  26. data/lib/ethereum-contract-abi/contract/eip/erc721_enumerable_interface.rb +46 -0
  27. data/lib/ethereum-contract-abi/contract/eip/erc721_metadata_interface.rb +46 -0
  28. data/lib/ethereum-contract-abi/contract/function.rb +60 -0
  29. data/lib/ethereum-contract-abi/contract/input.rb +16 -0
  30. data/lib/ethereum-contract-abi/contract/output.rb +16 -0
  31. data/lib/ethereum-contract-abi/contract/parsers/abi_type_parser.rb +40 -0
  32. data/lib/ethereum-contract-abi/contract/parsers/contract_parser.rb +29 -0
  33. data/lib/ethereum-contract-abi/contract/parsers/function_parser.rb +42 -0
  34. data/lib/ethereum-contract-abi/decoders/function_decoder.rb +37 -0
  35. data/lib/ethereum-contract-abi/decoders/int_decoder.rb +11 -0
  36. data/lib/ethereum-contract-abi/decoders/string_decoder.rb +16 -0
  37. data/lib/ethereum-contract-abi/encoders/bytes_encoder.rb +31 -0
  38. data/lib/ethereum-contract-abi/encoders/decimal_encoder.rb +14 -0
  39. data/lib/ethereum-contract-abi/encoders/int_encoder.rb +16 -0
  40. data/lib/ethereum-contract-abi/util.rb +24 -0
  41. metadata +82 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: be70442c2225b17ac53a7d627051bba9388a2ff7ef3f6f9163c05ece75ec792a
4
+ data.tar.gz: e476f57e5107298dda5ea8a3dbf0fe9c30b593145b13fac1cc014dedf0b4a5df
5
+ SHA512:
6
+ metadata.gz: 521f0496cb783a54a79090ff6b1fd7536fcd325cede0a6a46ed732646e2969679e0f49d53d2733f66067524f359f4efcc5eb32707511d88f347d24fedcaf7670
7
+ data.tar.gz: '09c33f3ede2778e8fe8ceeb9379f5084884c2094c04ebb9fe065554699ca832b921283d1a40df78d3f825fd2e35be789190df6b74bdc87da794de62560387b08'
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .idea/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/3.0 ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'rake'
6
+
7
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
8
+
9
+ gem "rspec", "~> 3.10"
10
+
11
+ gem "json", "~> 2.5"
12
+
13
+ git 'https://github.com/evtaylor/sha3-pure-ruby.git' do
14
+ gem 'sha3-pure-ruby'
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ GIT
2
+ remote: https://github.com/evtaylor/sha3-pure-ruby.git
3
+ revision: c3e764863f4926db33308d42e0fe7ec8ce3b72ce
4
+ specs:
5
+ sha3-pure-ruby (1.0.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.4.4)
11
+ json (2.5.1)
12
+ rake (13.0.3)
13
+ rspec (3.10.0)
14
+ rspec-core (~> 3.10.0)
15
+ rspec-expectations (~> 3.10.0)
16
+ rspec-mocks (~> 3.10.0)
17
+ rspec-core (3.10.1)
18
+ rspec-support (~> 3.10.0)
19
+ rspec-expectations (3.10.1)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.10.0)
22
+ rspec-mocks (3.10.2)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.10.0)
25
+ rspec-support (3.10.2)
26
+
27
+ PLATFORMS
28
+ x64-mingw32
29
+
30
+ DEPENDENCIES
31
+ json (~> 2.5)
32
+ rake
33
+ rspec (~> 3.10)
34
+ sha3-pure-ruby!
35
+
36
+ BUNDLED WITH
37
+ 2.2.3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Evan Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # ethereum-contract-abi
2
+
3
+ A library for interacting with Ethereum smart contracts via the Contract Application Binary Interface (ABI).
4
+
5
+ For more info see: https://docs.soliditylang.org/en/latest/abi-spec.html
6
+
7
+ Can encode function calls with types:
8
+ - ✅ `address`
9
+ - ✅ `bool`
10
+ - ✅ `bytes`
11
+ - ✅ `fixed`
12
+ - ✅ `int`
13
+ - ✅ `uint`
14
+ - ✅ `string`
15
+ - ❌ `<type>[]`
16
+ - ❌ `tuple`
17
+
18
+ Can decode function calls with types:
19
+ - ❌ `address`
20
+ - ❌ `bool`
21
+ - ❌ `bytes`
22
+ - ❌ `fixed`
23
+ - ✅ `int`
24
+ - ✅ `uint`
25
+ - ✅ `string`
26
+ - ❌ `<type>[]`
27
+ - ❌ `tuple`
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ RSpec::Core::RakeTask.new(:test) do |t|
5
+ t.pattern = "spec/**/*_spec.rb"
6
+ end
7
+
8
+ task default: %w[test]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "ethereum-contract-abi"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'ethereum-contract-abi'
3
+ s.version = '0.1.0'
4
+ s.summary = "Ethereum contract ABI encoder and decoder"
5
+ s.description = "A library for interacting with Ethereum smart contracts via the Contract Application Binary Interface (ABI)"
6
+ s.authors = ["Evan Taylor"]
7
+ s.email = 'evan@evantaylor.ca'
8
+ s.homepage = "https://evantaylor.ca"
9
+ s.license = 'MIT'
10
+
11
+ s.files = Dir.chdir(File.expand_path(__dir__)) do
12
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
13
+ end
14
+ s.require_paths = ["lib"]
15
+ end
@@ -0,0 +1,24 @@
1
+ require 'bundler/setup'
2
+ require 'ethereum-contract-abi/contract/parsers/contract_parser'
3
+ require 'ethereum-contract-abi/contract/eip/erc165_interface'
4
+ require 'ethereum-contract-abi/contract/eip/erc721_metadata_interface'
5
+ require 'ethereum-contract-abi/contract/eip/erc1155_metadata_interface'
6
+
7
+ module EthereumContractABI
8
+ def self.contract_from_json(json_string)
9
+ ContractParser.from_json(json_string)
10
+ end
11
+
12
+ def self.get_interface(interface_id)
13
+ case interface_id
14
+ when EIP::ERC721_METADATA_ID
15
+ EIP::ERC721MetadataInterface
16
+ when EIP::ERC1155_METADATA_ID
17
+ EIP::ERC1155MetadataInterface
18
+ when EIP::ERC165_ID
19
+ EIP::ERC165Interface
20
+ else
21
+ raise ArgumentError.new('Unknown interface identifier')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module EthereumContractABI
2
+ module Constants
3
+ BYTE_ZERO = "\x00".force_encoding(Encoding::ASCII_8BIT).freeze
4
+ BYTE_ONE = "\xFF".force_encoding(Encoding::ASCII_8BIT).freeze
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ require 'ethereum-contract-abi/contract/eip/constants'
2
+ require 'ethereum-contract-abi/contract/eip/erc721_metadata_interface'
3
+ require 'ethereum-contract-abi/contract/eip/erc721_enumerable_interface'
4
+ require 'ethereum-contract-abi/contract/eip/erc1155_metadata_interface'
5
+
6
+ include EthereumContractABI::ContractInterface
7
+
8
+ module EthereumContractABI
9
+ class Contract
10
+ def initialize(functions, events)
11
+ @functions = functions.to_h { |func| [func.name, func]}
12
+ @events = events
13
+
14
+ functions.each do |f|
15
+ self.class.send(:define_method, f.name) { function(f.name) }
16
+ end
17
+ end
18
+
19
+ def functions
20
+ @functions.values
21
+ end
22
+
23
+ def function(name)
24
+ @functions.dig(name)
25
+ end
26
+
27
+ def function_exists?(name)
28
+ @functions.key? name
29
+ end
30
+
31
+ def implements_interface(identifier)
32
+ case identifier
33
+ when EIP::ERC721_METADATA_ID
34
+ EIP::ERC721MetadataInterface.is_implemented_by?(self)
35
+ when EIP::ERC721_ENUMERABLE_ID
36
+ EIP::ERC721EnumerableInterface.is_implemented_by?(self)
37
+ when EIP::ERC1155_METADATA_ID
38
+ EIP::ERC1155MetadataInterface.is_implemented_by?(self)
39
+ else
40
+ raise ArgumentError.new('Unknown interface identifier')
41
+ end
42
+ end
43
+
44
+ def has_function?(func)
45
+ return false unless function_exists?(func.name)
46
+ contract_func = function(func.name)
47
+ return false unless contract_func.inputs.size == func.inputs.size
48
+ return false unless contract_func.outputs.size == func.outputs.size
49
+
50
+ input_type_strings = contract_func.inputs.map {|i| i.type.to_s }
51
+ expected_input_type_strings = func.inputs.map {|i| i.type.to_s }
52
+ output_type_string = contract_func.outputs.map {|i| i.type.to_s }
53
+ expected_output_type_string = func.outputs.map {|i| i.type.to_s }
54
+
55
+ Set.new(expected_input_type_strings) == Set.new(input_type_strings) &&
56
+ Set.new(expected_output_type_string) == Set.new(output_type_string)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ require 'ethereum-contract-abi/encoders/int_encoder'
2
+ require 'ethereum-contract-abi/contract/abi_types/uint'
3
+
4
+ include EthereumContractABI::Encoders
5
+ include EthereumContractABI::ContractInterface::AbiTypes
6
+
7
+ module EthereumContractABI
8
+ module ContractInterface
9
+ module AbiTypes
10
+ class Address < Uint
11
+ def initialize
12
+ super(160)
13
+ end
14
+
15
+ def is_dynamic
16
+ false
17
+ end
18
+
19
+ def to_s
20
+ "address"
21
+ end
22
+
23
+ def self.from_string(string_type)
24
+ string_type === 'address' ? self.new : nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+ module EthereumContractABI
2
+ module ContractInterface
3
+ module AbiTypes
4
+ class BaseType
5
+ def to_s
6
+ raise NotImplementedError.new('to_s not implemented on type')
7
+ end
8
+
9
+ def is_dynamic
10
+ true
11
+ end
12
+
13
+ def bytesize
14
+ 32
15
+ end
16
+
17
+ def encode_value(value)
18
+ raise NotImplementedError.new('encode_value not implemented on type')
19
+ end
20
+
21
+ def decode_value(value)
22
+ raise NotImplementedError.new('decode_value not implemented on type')
23
+ end
24
+
25
+ def self.from_string(string_type)
26
+ raise NotImplementedError.new('from_string not implemented on type')
27
+ end
28
+
29
+ def is_type_equal(type)
30
+ to_s == type.to_s
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ require 'ethereum-contract-abi/encoders/int_encoder'
2
+ require 'ethereum-contract-abi/contract/abi_types/base_type'
3
+
4
+ include EthereumContractABI::Encoders
5
+
6
+ module EthereumContractABI
7
+ module ContractInterface
8
+ module AbiTypes
9
+ class Bool < BaseType
10
+ def to_s
11
+ "bool"
12
+ end
13
+
14
+ def is_dynamic
15
+ false
16
+ end
17
+
18
+ def valid_value?(bool)
19
+ [true, false].include? bool
20
+ end
21
+
22
+ def encode_value(bool)
23
+ raise ArgumentError unless valid_value?(bool)
24
+ IntEncoder.encode(bool ? 1 : 0)
25
+ end
26
+
27
+ def self.from_string(string_type)
28
+ string_type === 'bool' ? self.new : nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ require 'ethereum-contract-abi/encoders/bytes_encoder'
2
+ require 'ethereum-contract-abi/contract/abi_types/base_type'
3
+
4
+ include EthereumContractABI::Encoders
5
+
6
+ module EthereumContractABI
7
+ module ContractInterface
8
+ module AbiTypes
9
+ class Bytes < BaseType
10
+ def initialize(num_bytes = nil)
11
+ @num_bytes = num_bytes
12
+ end
13
+
14
+ def is_dynamic
15
+ @num_bytes.nil?
16
+ end
17
+
18
+ def bytesize
19
+ @num_bytes.nil? ? nil : (@num_bytes.to_f/32.0).ceil
20
+ end
21
+
22
+ def to_s
23
+ "bytes#{@num_bytes}"
24
+ end
25
+
26
+ def valid_value?(value)
27
+ true
28
+ end
29
+
30
+ def encode_value(bytes)
31
+ BytesEncoder.encode(bytes, @num_bytes)
32
+ end
33
+
34
+ def self.from_string(string_type)
35
+ /(?<is_bytes>bytes)(?<bytes>\d+)?/ =~ string_type
36
+ return nil unless is_bytes
37
+ bytes ? self.new(bytes.to_i) : self.new
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ require 'ethereum-contract-abi/encoders/decimal_encoder'
2
+ require 'ethereum-contract-abi/contract/abi_types/base_type'
3
+
4
+ include EthereumContractABI::Encoders
5
+
6
+ module EthereumContractABI
7
+ module ContractInterface
8
+ module AbiTypes
9
+ class Fixed < BaseType
10
+ def initialize(bits = 128, precision = 18)
11
+ raise ArgumentError.new("8 must be a factor of bits") unless bits % 8 === 0
12
+ raise ArgumentError.new("bits must be: 8 <= bits <= 256") unless 8 <= bits && bits <= 256
13
+ raise ArgumentError.new("precision must be: 0 < precision <= 80") unless 0 < precision && precision <= 80
14
+ @bits = bits
15
+ @precision = precision
16
+ end
17
+
18
+ def is_dynamic
19
+ false
20
+ end
21
+
22
+ def to_s
23
+ "fixed#{@bits}x#{@precision}"
24
+ end
25
+
26
+ def valid_value?(number)
27
+ return false unless number.is_a? Numeric
28
+ return false unless number.round(0).bit_length <= @bits
29
+ true
30
+ end
31
+
32
+ def encode_value(number)
33
+ raise ArgumentError unless valid_value?(number)
34
+ DecimalEncoder.encode_value(number, @precision)
35
+ end
36
+
37
+ def self.from_string(string_type)
38
+ /(?<type>fixed)((?<bits>\d+)x(?<precision>\d+))?/ =~ string_type
39
+ return nil unless type
40
+
41
+ bits && precision ? self.new(bits.to_i, precision.to_i) : self.new
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ require 'ethereum-contract-abi/encoders/int_encoder'
2
+ require 'ethereum-contract-abi/decoders/int_decoder'
3
+ require 'ethereum-contract-abi/contract/abi_types/base_type'
4
+
5
+ include EthereumContractABI::Encoders
6
+ include EthereumContractABI::Decoders
7
+
8
+ module EthereumContractABI
9
+ module ContractInterface
10
+ module AbiTypes
11
+ class Int < BaseType
12
+ def initialize(bits = 256)
13
+ raise ArgumentError.new("8 must be a factor of bits") unless bits % 8 === 0
14
+ @bits = bits
15
+ end
16
+
17
+ def is_dynamic
18
+ false
19
+ end
20
+
21
+ def to_s
22
+ "int#{@bits}"
23
+ end
24
+
25
+ def valid_value?(number)
26
+ return false unless number.is_a? Numeric
27
+ return false unless number.bit_length <= @bits
28
+ true
29
+ end
30
+
31
+ def encode_value(number)
32
+ raise ArgumentError unless valid_value?(number)
33
+ IntEncoder.encode(number)
34
+ end
35
+
36
+ def decode_value(value)
37
+ IntDecoder.decode(value)
38
+ end
39
+
40
+ def self.from_string(string_type)
41
+ /(?<is_int>^int)(?<bits>\d+)?/ =~ string_type
42
+ return nil unless is_int
43
+ bits ? self.new(bits.to_i) : self.new
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ require 'ethereum-contract-abi/encoders/bytes_encoder'
2
+ require 'ethereum-contract-abi/decoders/string_decoder'
3
+ require 'ethereum-contract-abi/contract/abi_types/base_type'
4
+
5
+ include EthereumContractABI::Encoders
6
+ include EthereumContractABI::Decoders
7
+
8
+ module EthereumContractABI
9
+ module ContractInterface
10
+ module AbiTypes
11
+ class String < BaseType
12
+ def to_s
13
+ "string"
14
+ end
15
+
16
+ def is_dynamic
17
+ true
18
+ end
19
+
20
+ def bytesize
21
+ nil
22
+ end
23
+
24
+ def valid_value?(value)
25
+ return false unless value.is_a? String
26
+ true
27
+ end
28
+
29
+ def encode_value(value)
30
+ raise ArgumentError unless valid_value?(value)
31
+ BytesEncoder.encode(value)
32
+ end
33
+
34
+ def decode_value(value)
35
+ StringDecoder.decode(value)
36
+ end
37
+
38
+ def self.from_string(string_type)
39
+ string_type === 'string' ? self.new : nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ require 'ethereum-contract-abi/encoders/int_encoder'
2
+ require 'ethereum-contract-abi/contract/abi_types/int'
3
+
4
+ include EthereumContractABI::Encoders
5
+
6
+ module EthereumContractABI
7
+ module ContractInterface
8
+ module AbiTypes
9
+ class Uint < Int
10
+ def to_s
11
+ "uint#{@bits}"
12
+ end
13
+
14
+ def is_dynamic
15
+ false
16
+ end
17
+
18
+ def valid_value?(number)
19
+ super(number) && number >= 0
20
+ end
21
+
22
+ def self.from_string(string_type)
23
+ /(?<is_uint>uint)(?<bits>\d+)?/ =~ string_type
24
+ return nil unless is_uint
25
+ bits ? self.new(bits.to_i) : self.new
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module EthereumContractABI
2
+ module ContractInterface
3
+ module EIP
4
+ ERC721_METADATA_ID = '0x5b5e139f'
5
+ ERC721_ENUMERABLE_ID = '0x780e9d63'
6
+ ERC1155_METADATA_ID = '0x0e89341c'
7
+ ERC165_ID = 'ERC165'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ require 'ethereum-contract-abi/contract/abi_types/string'
2
+ require 'ethereum-contract-abi/contract/abi_types/uint'
3
+ require 'ethereum-contract-abi/contract/output'
4
+ require 'ethereum-contract-abi/contract/function'
5
+
6
+ include EthereumContractABI::ContractInterface
7
+
8
+ module EthereumContractABI
9
+ module ContractInterface
10
+ module EIP
11
+ class ERC1155MetadataInterface
12
+
13
+ def self.is_implemented_by?(contract)
14
+ contract.has_function?(self.uri)
15
+ end
16
+
17
+ def self.functions
18
+ [self.uri]
19
+ end
20
+
21
+ def self.uri
22
+ function_name = 'uri'
23
+ inputs = [Input.new('_id', AbiTypes::Uint.new)]
24
+ outputs = [Output.new(AbiTypes::String.new)]
25
+ Function.new(function_name, inputs, outputs)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'ethereum-contract-abi/contract/abi_types/bytes'
2
+ require 'ethereum-contract-abi/contract/abi_types/bool'
3
+ require 'ethereum-contract-abi/contract/output'
4
+ require 'ethereum-contract-abi/contract/function'
5
+
6
+ include EthereumContractABI::ContractInterface
7
+
8
+ module EthereumContractABI
9
+ module ContractInterface
10
+ module EIP
11
+ class ERC165Interface
12
+
13
+ def self.is_implemented_by?(contract)
14
+ contract.has_function?(self.supportsInterface)
15
+ end
16
+
17
+ def self.functions
18
+ [self.supportsInterface]
19
+ end
20
+
21
+ def self.supportsInterface
22
+ function_name = 'supportsInterface'
23
+ inputs = [Input.new( 'interfaceID', AbiTypes::Bytes.new(4))]
24
+ outputs = [Output.new(AbiTypes::Bool.new)]
25
+ Function.new(function_name, inputs, outputs)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ require 'ethereum-contract-abi/contract/abi_types/address'
2
+ require 'ethereum-contract-abi/contract/abi_types/uint'
3
+ require 'ethereum-contract-abi/contract/output'
4
+ require 'ethereum-contract-abi/contract/function'
5
+
6
+ include EthereumContractABI::ContractInterface
7
+
8
+ module EthereumContractABI
9
+ module ContractInterface
10
+ module EIP
11
+ class ERC721EnumerableInterface
12
+
13
+ def self.is_implemented_by?(contract)
14
+ contract.has_function?(self.totalSupply) &&
15
+ contract.has_function?(self.tokenByIndex) &&
16
+ contract.has_function?(self.tokenOfOwnerByIndex)
17
+ end
18
+
19
+ def self.totalSupply
20
+ function_name = 'totalSupply'
21
+ inputs = []
22
+ outputs = [Output.new(AbiTypes::Uint.new)]
23
+ Function.new(function_name, inputs, outputs)
24
+ end
25
+
26
+ def self.tokenByIndex
27
+ function_name = 'tokenByIndex'
28
+ inputs = [Input.new('_index', AbiTypes::Uint.new)]
29
+ outputs = [Output.new(AbiTypes::Uint.new)]
30
+ Function.new(function_name, inputs, outputs)
31
+ end
32
+
33
+ def self.tokenOfOwnerByIndex
34
+ function_name = 'tokenOfOwnerByIndex'
35
+ inputs = [Input.new('_owner', AbiTypes::Address.new), Input.new('_index', AbiTypes::Uint.new)]
36
+ outputs = [Output.new(AbiTypes::Uint.new)]
37
+ Function.new(function_name, inputs, outputs)
38
+ end
39
+
40
+ def self.functions
41
+ [self.totalSupply, self.tokenByIndex, self.tokenOfOwnerByIndex]
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require 'ethereum-contract-abi/contract/abi_types/string'
2
+ require 'ethereum-contract-abi/contract/abi_types/uint'
3
+ require 'ethereum-contract-abi/contract/output'
4
+ require 'ethereum-contract-abi/contract/function'
5
+
6
+ include EthereumContractABI::ContractInterface
7
+
8
+ module EthereumContractABI
9
+ module ContractInterface
10
+ module EIP
11
+ class ERC721MetadataInterface
12
+
13
+ def self.is_implemented_by?(contract)
14
+ contract.has_function?(self.name) &&
15
+ contract.has_function?(self.symbol) &&
16
+ contract.has_function?(self.tokenURI)
17
+ end
18
+
19
+ def self.name
20
+ function_name = 'name'
21
+ inputs = []
22
+ outputs = [Output.new(AbiTypes::String.new, '_name')]
23
+ Function.new(function_name, inputs, outputs)
24
+ end
25
+
26
+ def self.symbol
27
+ function_name = 'symbol'
28
+ inputs = []
29
+ outputs = [Output.new(AbiTypes::String.new, '_symbol')]
30
+ Function.new(function_name, inputs, outputs)
31
+ end
32
+
33
+ def self.tokenURI
34
+ function_name = 'tokenURI'
35
+ inputs = [Input.new('_tokenId', AbiTypes::Uint.new)]
36
+ outputs = [Output.new(AbiTypes::String.new)]
37
+ Function.new(function_name, inputs, outputs)
38
+ end
39
+
40
+ def self.functions
41
+ [self.name, self.symbol, self.tokenURI]
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ require 'digest'
2
+ require 'openssl'
3
+ require 'sha3-pure-ruby'
4
+ require 'ethereum-contract-abi/util'
5
+ require 'ethereum-contract-abi/decoders/function_decoder'
6
+
7
+ include EthereumContractABI::Decoders
8
+
9
+ module EthereumContractABI
10
+ module ContractInterface
11
+ class Function
12
+ attr_reader :name, :inputs, :outputs
13
+
14
+ def initialize(name, inputs, outputs)
15
+ @name = name
16
+ @inputs = inputs
17
+ @outputs = outputs
18
+ @decoder = FunctionDecoder.new(outputs)
19
+ end
20
+
21
+ def signature
22
+ params = @inputs.map { |i| i.type.to_s }
23
+ "#{@name}(#{params.join(",")})"
24
+ end
25
+
26
+ def method_id
27
+ hash = [Digest::SHA3.new(256, true).hexdigest(signature)].pack("H*")
28
+ hash.slice(0..3)
29
+ end
30
+
31
+ def valid_args?(args)
32
+ return false unless args.size === @inputs.size
33
+ true
34
+ end
35
+
36
+ def encode_call(*args)
37
+ raise ArgumentError.new("Invalid function arguments") unless valid_args?(args)
38
+ encoded_args = @inputs.zip(args).map do |input, arg|
39
+ input.encode_value(arg)
40
+ end
41
+ EthereumContractABI::Util.fromHexByteString(method_id + encoded_args.join(''))
42
+ end
43
+
44
+ def decode_output(output_string)
45
+ output_hex_string = EthereumContractABI::Util.remove_hex_prefix(output_string)
46
+ if has_any_dynamic_outputs
47
+ @decoder.decode_dynamic_output(output_hex_string)
48
+ else
49
+ @decoder.decode_static_output(output_hex_string)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def has_any_dynamic_outputs
56
+ @outputs.find { |o| o.type.is_dynamic }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ module EthereumContractABI
2
+ module ContractInterface
3
+ class Input
4
+ attr_reader :type
5
+
6
+ def initialize(name, type)
7
+ @name = name
8
+ @type = type
9
+ end
10
+
11
+ def encode_value(value)
12
+ @type.encode_value(value)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module EthereumContractABI
2
+ module ContractInterface
3
+ class Output
4
+ attr_reader :type
5
+
6
+ def initialize(type, name = nil)
7
+ @name = name
8
+ @type = type
9
+ end
10
+
11
+ def decode_value(value)
12
+ @type.decode_value(value)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ require 'json'
2
+ require 'ethereum-contract-abi/contract'
3
+ require 'ethereum-contract-abi/contract/abi_types/uint'
4
+ require 'ethereum-contract-abi/contract/abi_types/bool'
5
+ require 'ethereum-contract-abi/contract/abi_types/string'
6
+ require 'ethereum-contract-abi/contract/abi_types/fixed'
7
+ require 'ethereum-contract-abi/contract/abi_types/bytes'
8
+ require 'ethereum-contract-abi/contract/abi_types/address'
9
+
10
+ include EthereumContractABI::ContractInterface::AbiTypes
11
+
12
+ module EthereumContractABI
13
+ module ContractInterface
14
+ module Parsers
15
+ class AbiTypeParser
16
+ def self.from_string(string_type)
17
+ uint = Uint.from_string(string_type)
18
+ return uint unless uint.nil?
19
+
20
+ bool = Bool.from_string(string_type)
21
+ return bool unless bool.nil?
22
+
23
+ decimal = Fixed.from_string(string_type)
24
+ return decimal unless decimal.nil?
25
+
26
+ str = EthereumContractABI::ContractInterface::AbiTypes::String.from_string(string_type)
27
+ return str unless str.nil?
28
+
29
+ bytes = Bytes.from_string(string_type)
30
+ return bytes unless bytes.nil?
31
+
32
+ address = Address.from_string(string_type)
33
+ return address unless address.nil?
34
+
35
+ raise ArgumentError.new('Unknown type')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+ require 'ethereum-contract-abi/contract'
3
+ require 'ethereum-contract-abi/contract/parsers/function_parser'
4
+
5
+ include EthereumContractABI::ContractInterface::Parsers
6
+
7
+ module EthereumContractABI
8
+ module ContractInterface
9
+ module Parsers
10
+ class ContractParser
11
+ def self.from_json(json_string)
12
+ parsed = JSON.parse(json_string)
13
+ functions = parsed.select { |interface| interface["type"] === "function"}
14
+ .map { |f_hash| f_hash.transform_keys(&:to_sym) }
15
+ .filter_map do |f_hash|
16
+ begin
17
+ FunctionParser.from_hash(f_hash)
18
+ rescue ArgumentError
19
+ # Error parsing contract function, all ABI types are not yet implemented
20
+ nil
21
+ end
22
+ end
23
+ events = parsed.select { |interface| interface["type"] === "event"}
24
+ EthereumContractABI::Contract.new(functions, events)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require 'ethereum-contract-abi/contract'
3
+ require 'ethereum-contract-abi/contract/input'
4
+ require 'ethereum-contract-abi/contract/output'
5
+ require 'ethereum-contract-abi/contract/parsers/abi_type_parser'
6
+ require 'ethereum-contract-abi/contract/function'
7
+
8
+ include EthereumContractABI::ContractInterface
9
+
10
+ module EthereumContractABI
11
+ module ContractInterface
12
+ module Parsers
13
+ class FunctionParser
14
+ def self.from_hash(function_hash)
15
+ function_hash = function_hash.transform_keys(&:to_sym)
16
+ name = function_hash[:name]
17
+ inputs = self.get_inputs(function_hash[:inputs])
18
+ outputs = self.get_inputs(function_hash[:outputs])
19
+ Function.new(name, inputs, outputs)
20
+ end
21
+
22
+ private
23
+
24
+ def self.get_inputs(inputs)
25
+ inputs.map do |input_hash|
26
+ hash = input_hash.transform_keys(&:to_sym)
27
+ type = AbiTypeParser.from_string(hash[:type])
28
+ Input.new(name, type)
29
+ end
30
+ end
31
+
32
+ def self.get_outputs(outputs)
33
+ outputs.map do |input_hash|
34
+ hash = input_hash.transform_keys(&:to_sym)
35
+ type = AbiTypeParser.from_string(hash[:type])
36
+ Output.new(name, type)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ require 'ethereum-contract-abi/util'
2
+ require 'ethereum-contract-abi/decoders/int_decoder'
3
+ require 'ethereum-contract-abi/decoders/string_decoder'
4
+
5
+ module EthereumContractABI
6
+ module Decoders
7
+ class FunctionDecoder
8
+ def initialize(function_outputs)
9
+ @function_outputs = function_outputs
10
+ end
11
+
12
+ def decode_dynamic_output(encoded_output)
13
+ hex = Util.toHexByteString(encoded_output)
14
+ @function_outputs.map.with_index do |output, index|
15
+ head = get_head_by_index(encoded_output, index)
16
+ head_offset = IntDecoder.decode(head)
17
+ output_bytes = hex.byteslice(head_offset, hex.bytesize)
18
+ output.type.decode_value(Util.fromHexByteString(output_bytes, with_prefix: false))
19
+ end
20
+ end
21
+
22
+ def decode_static_output(encoded_output)
23
+ @function_outputs.map.with_index do |output, index|
24
+ bytes = encoded_output.slice!(0, output.type.bytesize * 2)
25
+ output.type.decode_value(bytes)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def get_head_by_index(encoded_output, index)
32
+ first_byte = index * 64
33
+ encoded_output.byteslice(first_byte, 64)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ require 'ethereum-contract-abi/util'
2
+
3
+ module EthereumContractABI
4
+ module Decoders
5
+ class IntDecoder
6
+ def self.decode(encoded_value)
7
+ encoded_value.slice(0, 64).to_i(16)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ require 'ethereum-contract-abi/util'
2
+ require 'ethereum-contract-abi/decoders/int_decoder'
3
+
4
+ include EthereumContractABI::Decoders
5
+
6
+ module EthereumContractABI
7
+ module Decoders
8
+ class StringDecoder
9
+ def self.decode(string, num_bytes = nil)
10
+ hex = [string].pack('H*')
11
+ length = IntDecoder.decode(hex.byteslice(0, 32).unpack("H*").first)
12
+ hex.byteslice(32..hex.bytesize).byteslice(0, length).strip
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ require 'ethereum-contract-abi/constants'
2
+ require 'ethereum-contract-abi/encoders/int_encoder'
3
+
4
+ include EthereumContractABI::Encoders
5
+
6
+ module EthereumContractABI
7
+ module Encoders
8
+ class BytesEncoder
9
+ def self.encode(bytes_to_encode, num_bytes = nil)
10
+ if bytes_to_encode.methods.include?(:start_with?) && bytes_to_encode.start_with?("0x")
11
+ bytes_to_encode = Util.toHexByteString(bytes_to_encode.slice(2, bytes_to_encode.length))
12
+ end
13
+
14
+ unless num_bytes.nil?
15
+ raise ArgumentError.new("Too many bytes to encode") unless bytes_to_encode.bytesize <= num_bytes
16
+ return self.encode_bytes(bytes_to_encode)
17
+ end
18
+
19
+ num_bytes_encoded = IntEncoder.encode(bytes_to_encode.bytesize)
20
+ num_bytes_encoded + self.encode_bytes(bytes_to_encode)
21
+ end
22
+
23
+ private
24
+
25
+ def self.encode_bytes(bytes_to_encode)
26
+ final_length = (bytes_to_encode.bytesize / 32.to_f).ceil * 32
27
+ bytes_to_encode + Constants::BYTE_ZERO * (final_length - bytes_to_encode.bytesize)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ require 'ethereum-contract-abi/constants'
2
+ require 'ethereum-contract-abi/encoders/int_encoder'
3
+
4
+ include EthereumContractABI::Encoders
5
+
6
+ module EthereumContractABI
7
+ module Encoders
8
+ class DecimalEncoder
9
+ def self.encode_value(decimal_number, precision)
10
+ IntEncoder.encode((decimal_number * 10**precision).to_i)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'ethereum-contract-abi/constants'
2
+
3
+ module EthereumContractABI
4
+ module Encoders
5
+ class IntEncoder
6
+ def self.encode(int)
7
+ hex = int < 0 ? (int & 0xffff).to_s(16).rjust(2, "0") : int.to_s(16).rjust(2, "0")
8
+ # converting to byte string requires each byte is represented by exactly 2 hex characters
9
+ adjusted_hex = hex.length % 2 != 0 ? "0" + hex : hex
10
+ int_hex_bytes = Util.toHexByteString(adjusted_hex)
11
+ pad_byte = int < 0 ? Constants::BYTE_ONE : Constants::BYTE_ZERO
12
+ pad_byte * (32 - int_hex_bytes.bytesize) + int_hex_bytes
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ module EthereumContractABI
2
+ class Util
3
+
4
+ def self.toHexByteString(str)
5
+ if str.start_with?("0x")
6
+ return [str.slice(2, str.length)].pack("H*")
7
+ end
8
+ [str].pack("H*")
9
+ end
10
+
11
+ def self.fromHexByteString(str, with_prefix: true)
12
+ prefix = with_prefix ? "0x" : ""
13
+ prefix + str.unpack("H*").first
14
+ end
15
+
16
+ def self.remove_hex_prefix(str)
17
+ str.slice(2, str.length)
18
+ end
19
+
20
+ def self. keccak_hash(str)
21
+ [Digest::SHA3.new(256, true ).hexdigest(signature)].pack("H*")
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ethereum-contract-abi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Evan Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A library for interacting with Ethereum smart contracts via the Contract
14
+ Application Binary Interface (ABI)
15
+ email: evan@evantaylor.ca
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - '3.0'
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - LICENSE
26
+ - README.md
27
+ - Rakefile
28
+ - bin/console
29
+ - ethereum_contract_abi.gemspec
30
+ - lib/ethereum-contract-abi.rb
31
+ - lib/ethereum-contract-abi/constants.rb
32
+ - lib/ethereum-contract-abi/contract.rb
33
+ - lib/ethereum-contract-abi/contract/abi_types/address.rb
34
+ - lib/ethereum-contract-abi/contract/abi_types/base_type.rb
35
+ - lib/ethereum-contract-abi/contract/abi_types/bool.rb
36
+ - lib/ethereum-contract-abi/contract/abi_types/bytes.rb
37
+ - lib/ethereum-contract-abi/contract/abi_types/fixed.rb
38
+ - lib/ethereum-contract-abi/contract/abi_types/int.rb
39
+ - lib/ethereum-contract-abi/contract/abi_types/string.rb
40
+ - lib/ethereum-contract-abi/contract/abi_types/uint.rb
41
+ - lib/ethereum-contract-abi/contract/eip/constants.rb
42
+ - lib/ethereum-contract-abi/contract/eip/erc1155_metadata_interface.rb
43
+ - lib/ethereum-contract-abi/contract/eip/erc165_interface.rb
44
+ - lib/ethereum-contract-abi/contract/eip/erc721_enumerable_interface.rb
45
+ - lib/ethereum-contract-abi/contract/eip/erc721_metadata_interface.rb
46
+ - lib/ethereum-contract-abi/contract/function.rb
47
+ - lib/ethereum-contract-abi/contract/input.rb
48
+ - lib/ethereum-contract-abi/contract/output.rb
49
+ - lib/ethereum-contract-abi/contract/parsers/abi_type_parser.rb
50
+ - lib/ethereum-contract-abi/contract/parsers/contract_parser.rb
51
+ - lib/ethereum-contract-abi/contract/parsers/function_parser.rb
52
+ - lib/ethereum-contract-abi/decoders/function_decoder.rb
53
+ - lib/ethereum-contract-abi/decoders/int_decoder.rb
54
+ - lib/ethereum-contract-abi/decoders/string_decoder.rb
55
+ - lib/ethereum-contract-abi/encoders/bytes_encoder.rb
56
+ - lib/ethereum-contract-abi/encoders/decimal_encoder.rb
57
+ - lib/ethereum-contract-abi/encoders/int_encoder.rb
58
+ - lib/ethereum-contract-abi/util.rb
59
+ homepage: https://evantaylor.ca
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.2.3
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Ethereum contract ABI encoder and decoder
82
+ test_files: []