Skip to content

Commit

Permalink
abi2: new package (#169)
Browse files Browse the repository at this point in the history
This is a new package for decoding ABI data. The main difference between
abi and abi2 is that abi2 is meant to get it's abi schema from a json
file and that json file's event inputs can be annotated with a column
field to indicate that they are to be written to a database table.

This means that the abi2 decoding function takes an ABI payload (which
can included nested tuples, arrays, etc.) and maps it onto a one or more
rows. Multiple rows are returned when an selected input is nested in an
array. If multiple inputs are selected from different arrays within the
ABI tree, then the number of rows returned is equal to the total number
of array elements. Finally, non-array based selected fields are combined
with the array based selected fields. For example:

	Tuple(
		selected(0, static)		//f1
		array(selected(1, static))	//f2
		array(selected(2, static))	//f3
	)

The number of rows would be: |f1| + |f2|
The number of columns would be: 2

Finally, a new Integration is also introduced. This means that code is
no longer requried to add new integratinos. You can simply provide an
annotated JSON ABI file.
  • Loading branch information
ryandotsmith authored Sep 28, 2023
1 parent b80208b commit 8274806
Show file tree
Hide file tree
Showing 8 changed files with 1,385 additions and 0 deletions.
927 changes: 927 additions & 0 deletions abi2/abi2.go

Large diffs are not rendered by default.

301 changes: 301 additions & 0 deletions abi2/abi2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
package abi2

import (
"encoding/hex"
"strings"
"testing"

"github.com/indexsupply/x/bint"

"kr.dev/diff"
)

func TestHasStatic(t *testing.T) {
cases := []struct {
t atype
want bool
}{
{
static(),
true,
},
{
dynamic(),
false,
},
{
array(static()),
false,
},
{
array(dynamic()),
false,
},
{
arrayK(2, static()),
true,
},
{
arrayK(3, arrayK(2, static())),
true,
},
}
for _, tc := range cases {
got := hasStatic(tc.t)
diff.Test(t, t.Errorf, got, tc.want)
}
}
func TestSizeof(t *testing.T) {
cases := []struct {
t atype
want int
}{
{
static(),
32,
},
{
dynamic(),
0,
},
{
array(static()),
0,
},
{
array(dynamic()),
0,
},
{
arrayK(2, static()),
64,
},
{
arrayK(3, arrayK(2, static())),
192,
},
}
for _, tc := range cases {
got := sizeof(tc.t)
diff.Test(t, t.Errorf, got, tc.want)
}
}

func TestHasKind(t *testing.T) {
cases := []struct {
t atype
k byte
want bool
}{
{
static(),
's',
false,
},
{
dynamic(),
's',
false,
},
{
array(dynamic()),
'd',
true,
},
{
array(array(dynamic())),
'd',
true,
},
{
array(array(dynamic())),
'a',
true,
},
{
tuple(array(dynamic())),
's',
false,
},
}
for _, tc := range cases {
got := tc.t.hasKind(tc.k)
diff.Test(t, t.Errorf, got, tc.want)
}
}

func hb(s string) []byte {
s = strings.Map(func(r rune) rune {
switch {
case r >= '0' && r <= '9':
return r
case r >= 'a' && r <= 'f':
return r
default:
return -1
}
}, strings.ToLower(s))
b, _ := hex.DecodeString(s)
return b
}

func n2b(x uint64) []byte {
var b [32]byte
bint.Encode(b[:], x)
return b[:]
}

func TestScan(t *testing.T) {
cases := []struct {
desc string
input []byte
at atype
want [][][]byte
}{
{
desc: "tuple of numbers",
input: hb(`
000000000000000000000000000000000000000000000000000000000000002a
000000000000000000000000000000000000000000000000000000000000002a
`),
at: tuple(sel(0, static()), sel(1, static())),
want: [][][]byte{
[][]byte{n2b(42), n2b(42)},
},
},
{
desc: "tuple of array with static types",
input: hb(`
000000000000000000000000000000000000000000000000000000000000002a
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000000000000000000000002b
000000000000000000000000000000000000000000000000000000000000002c
`),
at: tuple(sel(0, static()), array(sel(1, static()))),
want: [][][]byte{
[][]byte{n2b(42), n2b(43)},
[][]byte{n2b(42), n2b(44)},
},
},
{
desc: "tuple of array with dynamic types",
input: hb(`
000000000000000000000000000000000000000000000000000000000000002a
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000003
666f6f0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000003
6261720000000000000000000000000000000000000000000000000000000000
`),
at: tuple(sel(0, static()), array(sel(1, dynamic()))),
want: [][][]byte{
[][]byte{n2b(42), []byte("foo")},
[][]byte{n2b(42), []byte("bar")},
},
},
{
desc: "dynamic nested list of dynamic types",
input: hb(`
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000120
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000005
68656c6c6f000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000005
776f726c64000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000003
6279650000000000000000000000000000000000000000000000000000000000
`),
at: array(array(sel(0, dynamic()))),
want: [][][]byte{
[][]byte{[]byte("hello")},
[][]byte{[]byte("world")},
[][]byte{[]byte("bye")},
},
},
}
for _, tc := range cases {
res := NewResult(tc.at)
err := res.Scan(tc.input)
diff.Test(t, t.Fatalf, err, nil)
diff.Test(t, t.Errorf, res.Bytes(), tc.want)
}
}

func TestABIType(t *testing.T) {
cases := []struct {
input Input
want atype
n int
}{
{
Input{Name: "a", Type: "bytes32"},
static(),
0,
},
{
Input{Name: "a", Type: "bytes32[]"},
array(static()),
0,
},
{
Input{
Name: "a",
Type: "tuple[]",
Components: []Input{
Input{Name: "b", Type: "uint256"},
Input{Name: "c", Type: "bytes"},
},
},
array(tuple(static(), dynamic())),
0,
},
}
for _, tc := range cases {
_, got := tc.input.ABIType(0)
diff.Test(t, t.Errorf, tc.want, got)
}
}

func TestSelected(t *testing.T) {
event := Event{
Name: "test",
Inputs: []Input{
Input{Name: "z"},
Input{
Name: "a",
Column: "a",
Components: []Input{
Input{Name: "b", Column: "b"},
Input{Name: "c", Column: "c"},
},
},
Input{Name: "d", Column: "d"},
Input{Name: "e", Column: ""},
},
}
want := []Input{
Input{Name: "b", Column: "b"},
Input{Name: "c", Column: "c"},
Input{
Name: "a",
Column: "a",
Components: []Input{
Input{Name: "b", Column: "b"},
Input{Name: "c", Column: "c"},
},
},
Input{Name: "d", Column: "d"},
}
diff.Test(t, t.Errorf, want, event.Selected())
}
Loading

0 comments on commit 8274806

Please sign in to comment.