Skip to content

Commit

Permalink
Merge pull request #3 from bugout-dev/annotations
Browse files Browse the repository at this point in the history
Optionally generate annotations for each interface
  • Loading branch information
zomglings authored Apr 11, 2023
2 parents d7ada9c + bf7708c commit 066778f
Show file tree
Hide file tree
Showing 9 changed files with 606 additions and 15 deletions.
36 changes: 36 additions & 0 deletions abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package main

import (
"encoding/json"
"fmt"
"strings"

"github.com/ethereum/go-ethereum/crypto"
)

/**
Expand Down Expand Up @@ -53,6 +57,11 @@ type DecodedABI struct {
Errors []ErrorItem
}

type Annotations struct {
InterfaceID []byte
FunctionSelectors [][]byte
}

func Decode(rawJSON []byte) (DecodedABI, error) {
var typeDeclarations []TypeDeclaration
var rawMessages []json.RawMessage
Expand Down Expand Up @@ -120,6 +129,33 @@ func Decode(rawJSON []byte) (DecodedABI, error) {
return decodedABI, nil
}

func MethodSelector(function FunctionItem) []byte {
argumentTypes := make([]string, len(function.Inputs))
for i, input := range function.Inputs {
argumentTypes[i] = input.Type
}
argumentTypesString := strings.Join(argumentTypes, ",")
signature := fmt.Sprintf("%s(%s)", function.Name, argumentTypesString)
return crypto.Keccak256([]byte(signature))[:4]
}

func Annotate(decodedABI DecodedABI) (Annotations, error) {
var annotations Annotations
annotations.InterfaceID = []byte{0x0, 0x0, 0x0, 0x0}
annotations.FunctionSelectors = make([][]byte, len(decodedABI.Functions))
for i, functionItem := range decodedABI.Functions {
selector := MethodSelector(functionItem)
annotations.FunctionSelectors[i] = selector

// XOR into InterfaceID byte by byte
annotations.InterfaceID[0] ^= selector[0]
annotations.InterfaceID[1] ^= selector[1]
annotations.InterfaceID[2] ^= selector[2]
annotations.InterfaceID[3] ^= selector[3]
}
return annotations, nil
}

func (v Value) IsCompoundType() bool {
return len(v.Components) > 0
}
57 changes: 57 additions & 0 deletions abi_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
package main

import (
"encoding/hex"
"os"
"testing"
)

func TestMethodSelectorOnERC721SafeTransferFromWithoutCalldata(t *testing.T) {
functionItem := FunctionItem{Type: "function", Name: "safeTransferFrom", Inputs: []Value{
{Name: "from", Type: "address"},
{Name: "to", Type: "address"},
{Name: "tokenId", Type: "uint256"},
}}

selector := MethodSelector(functionItem)

expectedSelectorString := "42842e0e"
selectorString := hex.EncodeToString(selector)
if selectorString != expectedSelectorString {
t.Fatalf("Incorrect method selector for safeTransferFrom(address,address,uint256). Expected: %s, actual: %s", expectedSelectorString, selectorString)
}
}

func TestMethodSelectorOnERC721SafeTransferFromWithCalldata(t *testing.T) {
functionItem := FunctionItem{Type: "function", Name: "safeTransferFrom", Inputs: []Value{
{Name: "from", Type: "address"},
{Name: "to", Type: "address"},
{Name: "tokenId", Type: "uint256"},
{Name: "data", Type: "bytes"},
}}

selector := MethodSelector(functionItem)

expectedSelectorString := "b88d4fde"
selectorString := hex.EncodeToString(selector)
if selectorString != expectedSelectorString {
t.Fatalf("Incorrect method selector for safeTransferFrom(address,address,uint256,bytes). Expected: %s, actual: %s", expectedSelectorString, selectorString)
}
}

func TestDecodeOwnableERC20(t *testing.T) {
contents, readErr := os.ReadFile("fixtures/abis/OwnableERC20.json")
if readErr != nil {
Expand Down Expand Up @@ -214,3 +248,26 @@ func TestSingleFunction(t *testing.T) {
}
}
}

func TestERC20InterfaceID(t *testing.T) {
contents, readErr := os.ReadFile("fixtures/abis/ERC20.json")
if readErr != nil {
t.Fatal("Could not read file containing ABI")
}

decodedABI, decodeErr := Decode(contents)
if decodeErr != nil {
t.Fatalf("Could not decode ABI: %s", decodeErr.Error())
}

annotations, err := Annotate(decodedABI)
if err != nil {
t.Fatalf("Could not generate annotations: %s", err.Error())
}

expectedInterfaceID := "36372b07"
interfaceId := hex.EncodeToString(annotations.InterfaceID)
if interfaceId != expectedInterfaceID {
t.Fatalf("Incorrect interface ID generated: expected: %s, actual: %s", expectedInterfaceID, interfaceId)
}
}
185 changes: 185 additions & 0 deletions fixtures/abis/ERC20.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
Loading

0 comments on commit 066778f

Please sign in to comment.