Skip to content

Commit

Permalink
up
Browse files Browse the repository at this point in the history
  • Loading branch information
geraldb committed Dec 27, 2022
1 parent 6256150 commit b1a33dc
Show file tree
Hide file tree
Showing 15 changed files with 554 additions and 169 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
# do NOT check in dev/test enviroment variables in .env (dot env files)
.env


##
# ignore tmp directories

tmp/
tmp2/


2 changes: 1 addition & 1 deletion abiparser/Manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ README.md
Rakefile
lib/abiparser.rb
lib/abiparser/constructor.rb
lib/abiparser/contract.rb
lib/abiparser/export/interface.rb
lib/abiparser/function.rb
lib/abiparser/interface.rb
lib/abiparser/param.rb
lib/abiparser/version.rb
127 changes: 127 additions & 0 deletions abiparser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,133 @@ abiparser - application binary interface (abi) parser machinery / helper for Eth
## Usage


### Functions Signature Hashes / Selectors & Interface (Type) Ids


You can calculate the function selectors (or "sighash",
that is, signature hash)
by hashing the function signature
e.g. `supportsInterface(bytes4)` with the Keccak 256-Bit algorithm
and than use the first 4 bytes, that is, `0x01ffc9a7` (out of 32 bytes),
that is, `0x01ffc9a7a5cef8baa21ed3c5c0d7e23accb804b619e9333b597f47a0d84076e2`. Example:


``` ruby
require 'abiparser'

sig = 'supportsInterface(bytes4)'
pp keccak256( sig )[0,4].hexdigest
#=> "0x01ffc9a7"
```

Note: The `String#hexdigest` (also known as `String#bin_to_hex`) helper
converts a binary string (with `BINARY`/`ASCII-8BIT` encoding)
into a hex(adecimal) string.


You can calcuate interface (type) ids
by xor-ing (`^`) together the sighashes.
If the interface only has one function than
the interface (type) id equals the function sighash (by definition).


``` solidity
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
// The interface identifier for this interface is 0x01ffc9a7.
```

If you check the sighash for `supportsInterface(bytes4)`,
that is, `0x01ffc9a7` (see above)
than - bingo! - the interface id for ERC165 matches up.


Let's try to calculate the ERC20 standard (fungible) token interface
where the official id is `0x36372b07` by xor-ing (`^`) together all function sighashes:

``` ruby
pp (keccak256('totalSupply()')[0,4] ^
keccak256('balanceOf(address)')[0,4] ^
keccak256('allowance(address,address)')[0,4] ^
keccak256('transfer(address,uint256)')[0,4] ^
keccak256('approve(address,uint256)')[0,4] ^
keccak256('transferFrom(address,address,uint256)')[0,4]).hexdigest
#=> "0x36372b07"

# or where def sig(bin) = keccak256(bin)[0,4])

pp (sig('totalSupply()') ^
sig('balanceOf(address)') ^
sig('allowance(address,address)') ^
sig('transfer(address,uint256)') ^
sig('approve(address,uint256)') ^
sig('transferFrom(address,address,uint256)')).hexdigest
#=> "0x36372b07"
```

Voila!
Or re(use) the builtin pre-defined interfaces. Example:

``` ruby
pp IERC165.inteface_id #=> "0x01ffc9a7"
pp IERC20.interface_id #=> "0x36372b07"
pp IERC721.interface_id #=> "0x80ac58cd"
pp IERC721Metadata.interface_id #=> "0x5b5e139f"
pp IERC721Enumerable.interface_id #=> "0x780e9d63"
```

Yes, you can. Define your own interface. Let's have a looksie
at the built-ins. Example:

``` ruby
IERC165 = ABI::Interface.new(
'supportsInterface(bytes4)'
)

IERC20 = ABI::Interface.new(
'totalSupply()',
'balanceOf(address)',
'allowance(address,address)',
'transfer(address,uint256)',
'approve(address,uint256)',
'transferFrom(address,address,uint256)'
)

IERC721 = ABI::Interface.new(
'balanceOf(address)',
'ownerOf(uint256)',
'approve(address,uint256)',
'getApproved(uint256)',
'setApprovalForAll(address,bool)',
'isApprovedForAll(address,address)',
'transferFrom(address,address,uint256)',
'safeTransferFrom(address,address,uint256)',
'safeTransferFrom(address,address,uint256,bytes)' )

IERC721Metadata = ABI::Interface.new(
'name()',
'symbol()',
'tokenURI(uint256)' )

IERC721Enumerable = ABI::Interface.new(
'tokenOfOwnerByIndex(address,uint256)',
'totalSupply()',
'tokenByIndex(uint256)' )

...
```


To be continued...





## License
Expand Down
39 changes: 29 additions & 10 deletions abiparser/lib/abiparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class String

# given two numeric strings,
# returns the bitwise xor string
def ^(other)
def xor(other)
a = self.bytes
b = other.bytes
## todo/check: cut-off on lower count (lc) - why? why not?
Expand All @@ -22,9 +22,11 @@ def ^(other)
puts "#{self.bin_to_hex} ^ #{other.bin_to_hex} = #{c.bin_to_hex}<"
c
end
alias_method :^, :xor
end # class String



def keccak256( bin )
Digest::KeccakLite.new( 256 ).digest( bin )
end
Expand All @@ -40,7 +42,7 @@ def sig( bin )
require_relative 'abiparser/param'
require_relative 'abiparser/constructor'
require_relative 'abiparser/function'
require_relative 'abiparser/interface'
require_relative 'abiparser/contract'

require_relative 'abiparser/export/interface.rb'

Expand All @@ -50,10 +52,9 @@ def sig( bin )
module ABI



## rename to QueryInterface or SupportInterface
## or InterfaceType or such - why? why not?
class InterfaceId
## or InterfaceType or InterfaceId or such - why? why not?
class Interface

attr_reader :interface_id

Expand Down Expand Up @@ -113,7 +114,7 @@ def support?( sig )
end
alias_method :supports?, :support? ## add alternate spelling - why? why not?

end ## class InterfaceId
end ## class Interface



Expand All @@ -125,7 +126,7 @@ def support?( sig )
## note: make "global" constants - why? why not?

## IERC20 0x36372b07
IERC20 = ABI::InterfaceId.new(
IERC20 = ABI::Interface.new(
'totalSupply()',
'balanceOf(address)',
'allowance(address,address)',
Expand All @@ -135,7 +136,7 @@ def support?( sig )


## IERC721 0x80ac58cd
IERC721 = ABI::InterfaceId.new(
IERC721 = ABI::Interface.new(
'balanceOf(address)',
'ownerOf(uint256)',
'approve(address,uint256)',
Expand All @@ -146,15 +147,33 @@ def support?( sig )
'safeTransferFrom(address,address,uint256)',
'safeTransferFrom(address,address,uint256,bytes)' )

## IERC165 0x01ffc9a7
IERC165 = ABI::Interface.new(
'supportsInterface(bytes4)',
)

## IERC721_METADATA 0x5b5e139f
IERC721Metadata = ABI::Interface.new(
'name()',
'symbol()',
'tokenURI(uint256)',
)

## ERC721_ENUMERABLE 0x780e9d63
IERC721Enumerable = ABI::Interface.new(
'tokenOfOwnerByIndex(address,uint256)',
'totalSupply()',
'tokenByIndex(uint256)',
)





module ABI
def self.read( path ) Interface.read( path ); end
def self.read( path ) Contract.read( path ); end

def self.parse( data ) Interface.parse( data ); end
def self.parse( data ) Contract.parse( data ); end
end # module ABI


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module ABI
class Interface
class Contract

def self.read( path )
data = read_json( path )
Expand Down Expand Up @@ -68,7 +68,7 @@ def initialize( constructor: nil,

## auto-add selectors (hashed signatures)
@funcs.each do |func|
sighash = keccak256( func.sig )[0,4].hexdigest
sighash = func.sighash
puts "0x#{sighash} => #{func.sig}"

## assert - no duplicates allowed
Expand All @@ -81,6 +81,7 @@ def initialize( constructor: nil,
end
end


## return hexstrings of sig(natures) - why? why not?
## rename to sighashes - why? why not?
def selectors() @selectors.keys; end
Expand Down Expand Up @@ -132,5 +133,5 @@ def helper_functions ## add pure ?? funcs alias - why? why not?
@funcs.select { |func| func.pure? }
end

end # class Interface
end # class Contract
end # module ABI
17 changes: 10 additions & 7 deletions abiparser/lib/abiparser/export/interface.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
module ABI
class Interface
class Contract


def generate_interface( name: ) ## interface declarations
buf = ''
buf << "interface #{name} {"

if @ctor
buf << "\n"
buf << "// Constructor\n"
buf << "#{@ctor.decl}\n"
end

# include constructor - why? why not?
#
# if @ctor
# buf << "\n"
# buf << "// Constructor\n"
# buf << "#{@ctor.decl}\n"
# end

if payable_functions.size > 0
buf << "\n"
Expand Down Expand Up @@ -49,5 +52,5 @@ def generate_interface( name: ) ## interface declarations
end


end ## class Interface
end ## class Contract
end ## module ABI
7 changes: 6 additions & 1 deletion abiparser/lib/abiparser/function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,14 @@ def sig
buf
end

def sighash
keccak256( sig )[0,4].hexdigest
end


def doc
buf = "function #{@name}"
## note: text with markdown formatting
buf = "function **#{@name}**"
if @inputs.empty?
buf << "()"
else
Expand Down
Loading

0 comments on commit b1a33dc

Please sign in to comment.