Skip to content

Commit

Permalink
Merge pull request #17 from Kava-Labs/yevhenii/ibc-precompile-rebase-…
Browse files Browse the repository at this point in the history
…on-master

Initial implementation of IBC Precompile
  • Loading branch information
evgeniy-scherbina authored Mar 8, 2024
2 parents 3ffaf10 + 345a094 commit 2daf8ea
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 1 deletion.
59 changes: 58 additions & 1 deletion accounts/abi/abi.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Copyright 2024 Kava Labs, Inc.
// Copyright 2024 Ava Labs, Inc.
//
// Derived from https://github.com/ava-labs/subnet-evm@49b0e31
//
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
Expand Down Expand Up @@ -82,6 +87,32 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
return append(method.ID, arguments...), nil
}

// Upstream implementation provides API for `pack inputs` and `unpack outputs`.
// We extend API with `unpack inputs` functionality by adding getInputs and UnpackInput methods.
// Perhaps in the future we extend API with `pack outputs` functionality.

// getInputs gets method's inputs metadata by method name according to the ABI specification.
// It can be used to decode encoded method's inputs.
// getInputs and getArguments have similar implementations
func (abi ABI) getInputs(name string) (Arguments, error) {
// since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event
var args Arguments
if method, ok := abi.Methods[name]; ok {
args = method.Inputs
}
if event, ok := abi.Events[name]; ok {
args = event.Inputs
}
if args == nil {
return nil, fmt.Errorf("abi: could not locate named method or event: %s", name)
}
return args, nil
}

// getArguments gets method's outputs metadata by method name according to the ABI specification.
// It can be used to decode encoded method's outputs.
// getInputs and getArguments have similar implementations.
func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {
// since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event
Expand All @@ -101,7 +132,18 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {
return args, nil
}

// Unpack unpacks the output according to the abi specification.
// UnpackInput unpacks the input according to the ABI specification.
// UnpackInput and Unpack have similar implementations.
func (abi ABI) UnpackInput(name string, data []byte) ([]interface{}, error) {
args, err := abi.getInputs(name)
if err != nil {
return nil, err
}
return args.Unpack(data)
}

// Unpack unpacks the output according to the ABI specification.
// UnpackInput and Unpack have similar implementations.
func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) {
args, err := abi.getArguments(name, data)
if err != nil {
Expand All @@ -110,6 +152,21 @@ func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) {
return args.Unpack(data)
}

// UnpackInputIntoInterface unpacks the input in v according to the ABI specification.
// It performs an additional copy. Please only use, if you want to unpack into a
// structure that does not strictly conform to the ABI structure (e.g. has additional arguments)
func (abi ABI) UnpackInputIntoInterface(v interface{}, name string, data []byte) error {
args, err := abi.getInputs(name)
if err != nil {
return err
}
unpacked, err := args.Unpack(data)
if err != nil {
return err
}
return args.Copy(v, unpacked)
}

// UnpackIntoInterface unpacks the output in v according to the abi specification.
// It performs an additional copy. Please only use, if you want to unpack into a
// structure that does not strictly conform to the abi structure (e.g. has additional arguments)
Expand Down
159 changes: 159 additions & 0 deletions accounts/abi/abi_extra_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright 2024 Kava Labs, Inc.
// Copyright 2024 Ava Labs, Inc.
//
// Derived from https://github.com/ava-labs/subnet-evm@49b0e31

package abi

import (
"bytes"
"math/big"
"strings"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

// Note: This file contains tests in addition to those found in go-ethereum.

const TEST_ABI = `[{"type":"function","name":"receive","inputs":[{"name":"sender","type":"address"},{"name":"amount","type":"uint256"},{"name":"memo","type":"bytes"}],"outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}]}]`

func TestUnpackInput(t *testing.T) {
abi, err := JSON(strings.NewReader(TEST_ABI))
require.NoError(t, err)

type inputType struct {
Sender common.Address
Amount *big.Int
Memo []byte
}
input := inputType{
Sender: common.HexToAddress("0x02"),
Amount: big.NewInt(100),
Memo: []byte("hello"),
}

rawData, err := abi.Pack("receive", input.Sender, input.Amount, input.Memo)
require.NoError(t, err)

abi, err = JSON(strings.NewReader(TEST_ABI))
require.NoError(t, err)

for _, test := range []struct {
name string
extraPaddingBytes int
expectedErrorSubstring string
}{
{
name: "No extra padding to input data",
},
{
name: "Valid input data with 32 extra padding(%32) ",
extraPaddingBytes: 32,
},
{
name: "Valid input data with 64 extra padding(%32)",
extraPaddingBytes: 64,
},
{
name: "Valid input data with extra padding indivisible by 32",
extraPaddingBytes: 33,
},
} {
{
t.Run(test.name, func(t *testing.T) {
// skip 4 byte selector
data := rawData[4:]
// Add extra padding to data
data = append(data, make([]byte, test.extraPaddingBytes)...)

args, err := abi.UnpackInput("receive", data) // skips 4 byte selector
v := inputType{
Sender: *ConvertType(args[0], new(common.Address)).(*common.Address),
Amount: ConvertType(args[1], new(big.Int)).(*big.Int),
Memo: *ConvertType(args[2], new([]byte)).(*[]byte),
}

if test.expectedErrorSubstring != "" {
require.Error(t, err)
require.ErrorContains(t, err, test.expectedErrorSubstring)
} else {
require.NoError(t, err)
// Verify unpacked values match input
require.Equal(t, v.Amount, input.Amount)
require.EqualValues(t, v.Amount, input.Amount)
require.True(t, bytes.Equal(v.Memo, input.Memo))
}
})
}
}
}

func TestUnpackInputIntoInterface(t *testing.T) {
abi, err := JSON(strings.NewReader(TEST_ABI))
require.NoError(t, err)

type inputType struct {
Sender common.Address
Amount *big.Int
Memo []byte
}
input := inputType{
Sender: common.HexToAddress("0x02"),
Amount: big.NewInt(100),
Memo: []byte("hello"),
}

rawData, err := abi.Pack("receive", input.Sender, input.Amount, input.Memo)
require.NoError(t, err)

abi, err = JSON(strings.NewReader(TEST_ABI))
require.NoError(t, err)

for _, test := range []struct {
name string
extraPaddingBytes int
expectedErrorSubstring string
}{
{
name: "No extra padding to input data",
},
{
name: "Valid input data with 32 extra padding(%32) ",
extraPaddingBytes: 32,
},
{
name: "Valid input data with 64 extra padding(%32)",
extraPaddingBytes: 64,
},
{
name: "Valid input data with extra padding indivisible by 32",
extraPaddingBytes: 33,
},
} {
{
t.Run(test.name, func(t *testing.T) {
// skip 4 byte selector
data := rawData[4:]
// Add extra padding to data
data = append(data, make([]byte, test.extraPaddingBytes)...)

// Unpack into interface
var v inputType
err = abi.UnpackInputIntoInterface(&v, "receive", data) // skips 4 byte selector

if test.expectedErrorSubstring != "" {
require.Error(t, err)
require.ErrorContains(t, err, test.expectedErrorSubstring)
} else {
require.NoError(t, err)
// Verify unpacked values match input
require.Equal(t, v.Amount, input.Amount)
require.EqualValues(t, v.Amount, input.Amount)
require.True(t, bytes.Equal(v.Memo, input.Memo))
}
})
}
}
}

0 comments on commit 2daf8ea

Please sign in to comment.