ethereum-contract-abi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []