Skip to content

Commit

Permalink
Merge branch 'feat/eth-encode'
Browse files Browse the repository at this point in the history
  • Loading branch information
loongy committed Sep 10, 2020
2 parents 4f3e2dc + f46207f commit 4eba89a
Show file tree
Hide file tree
Showing 4 changed files with 582 additions and 0 deletions.
141 changes: 141 additions & 0 deletions chain/ethereum/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package ethereum

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

"github.com/ethereum/go-ethereum/common"
"github.com/renproject/multichain/api/address"
"github.com/renproject/pack"
"github.com/renproject/surge"
)

// AddressEncodeDecoder implements the address.EncodeDecoder interface
type AddressEncodeDecoder struct {
AddressEncoder
AddressDecoder
}

// AddressEncoder implements the address.Encoder interface.
type AddressEncoder interface {
EncodeAddress(address.RawAddress) (address.Address, error)
}

type addressEncoder struct{}

// NewAddressEncodeDecoder constructs a new AddressEncodeDecoder.
func NewAddressEncodeDecoder() address.EncodeDecoder {
return AddressEncodeDecoder{
AddressEncoder: NewAddressEncoder(),
AddressDecoder: NewAddressDecoder(),
}
}

// AddressDecoder implements the address.Decoder interface.
type AddressDecoder interface {
DecodeAddress(address.Address) (address.RawAddress, error)
}

type addressDecoder struct{}

// NewAddressDecoder constructs a new AddressDecoder.
func NewAddressDecoder() AddressDecoder {
return addressDecoder{}
}

// NewAddressEncoder constructs a new AddressEncoder.
func NewAddressEncoder() AddressEncoder {
return addressEncoder{}
}

func (addressDecoder) DecodeAddress(encoded address.Address) (address.RawAddress, error) {
ethaddr, err := NewAddressFromHex(string(pack.String(encoded)))
if err != nil {
return nil, err
}
return address.RawAddress(pack.Bytes(ethaddr[:])), nil
}

func (addressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) {
encodedAddr := common.Bytes2Hex([]byte(rawAddr))
return address.Address(pack.NewString(encodedAddr)), nil
}

// An Address represents a public address on the Ethereum blockchain. It can be
// the address of an external account, or the address of a smart contract.
type Address common.Address

// NewAddressFromHex returns an Address decoded from a hex
// string.
func NewAddressFromHex(str string) (Address, error) {
if strings.HasPrefix(str, "0x") {
str = str[2:]
}
if len(str) != 40 {
return Address{}, fmt.Errorf("invalid ethaddress %v", str)
}
ethaddrData, err := hex.DecodeString(str)
if err != nil {
return Address{}, fmt.Errorf("invalid ethaddress %v: %v", str, err)
}
ethaddr := common.Address{}
copy(ethaddr[:], ethaddrData)
return Address(ethaddr), nil
}

// SizeHint returns the number of bytes needed to represent this address in
// binary.
func (Address) SizeHint() int {
return common.AddressLength
}

// Marshal the address to binary.
func (addr Address) Marshal(buf []byte, rem int) ([]byte, int, error) {
if len(buf) < common.AddressLength || rem < common.AddressLength {
return buf, rem, surge.ErrUnexpectedEndOfBuffer
}
copy(buf, addr[:])
return buf[common.AddressLength:], rem - common.AddressLength, nil
}

// Unmarshal the address from binary.
func (addr *Address) Unmarshal(buf []byte, rem int) ([]byte, int, error) {
if len(buf) < common.AddressLength || rem < common.AddressLength {
return buf, rem, surge.ErrUnexpectedEndOfBuffer
}
copy(addr[:], buf[:common.AddressLength])
return buf[common.AddressLength:], rem - common.AddressLength, nil
}

// MarshalJSON implements JSON marshaling by encoding the address as a hex
// string.
func (addr Address) MarshalJSON() ([]byte, error) {
return json.Marshal(common.Address(addr).Hex())
}

// UnmarshalJSON implements JSON unmarshaling by expected the data be a hex
// encoded string representation of an address.
func (addr *Address) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
ethaddr, err := NewAddressFromHex(str)
if err != nil {
return err
}
*addr = ethaddr
return nil
}

// String returns the address as a human-readable hex string.
func (addr Address) String() string {
return hex.EncodeToString(addr[:])
}

// Bytes returns the address as a slice of 20 bytes.
func (addr Address) Bytes() pack.Bytes {
return pack.Bytes(addr[:])
}
107 changes: 107 additions & 0 deletions chain/ethereum/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package ethereum_test

import (
"encoding/hex"
"encoding/json"
"testing/quick"

"github.com/renproject/multichain/chain/ethereum"
"github.com/renproject/surge"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Address", func() {
Context("when unmarshaling and unmarshaling", func() {
It("should equal itself", func() {
f := func(x [20]byte) bool {
addr := ethereum.Address(x)
Expect(addr.SizeHint()).To(Equal(20))

bytes, err := surge.ToBinary(addr)
Expect(err).ToNot(HaveOccurred())

var newAddr ethereum.Address
err = surge.FromBinary(&newAddr, bytes)
Expect(err).ToNot(HaveOccurred())

Expect(addr).To(Equal(newAddr))
return true
}

err := quick.Check(f, nil)
Expect(err).ToNot(HaveOccurred())
})
})

Context("when unmarshaling and unmarshaling to/from JSON", func() {
It("should equal itself", func() {
f := func(x [20]byte) bool {
addr := ethereum.Address(x)

bytes, err := json.Marshal(addr)
Expect(err).ToNot(HaveOccurred())

var newAddr ethereum.Address
err = json.Unmarshal(bytes, &newAddr)
Expect(err).ToNot(HaveOccurred())

Expect(addr).To(Equal(newAddr))
return true
}

err := quick.Check(f, nil)
Expect(err).ToNot(HaveOccurred())
})

Context("when the address is invalid hex", func() {
It("should return an error", func() {
f := func(x [40]byte) bool {
bytes, err := json.Marshal(string(x[:]))
Expect(err).ToNot(HaveOccurred())

var newAddr ethereum.Address
err = json.Unmarshal(bytes, &newAddr)
Expect(err).To(HaveOccurred())
return true
}

err := quick.Check(f, nil)
Expect(err).ToNot(HaveOccurred())
})
})

Context("when the address is invalid length", func() {
It("should return an error", func() {
f := func(x [10]byte) bool {
addr := hex.EncodeToString(x[:])
bytes, err := json.Marshal(addr)
Expect(err).ToNot(HaveOccurred())

var newAddr ethereum.Address
err = json.Unmarshal(bytes, &newAddr)
Expect(err).To(HaveOccurred())
return true
}

err := quick.Check(f, nil)
Expect(err).ToNot(HaveOccurred())
})
})
})

Context("when unmarshalling random data", func() {
It("should not panic", func() {
f := func(x []byte) bool {
var addr ethereum.Address
Expect(func() { addr.Unmarshal(x, surge.MaxBytes) }).ToNot(Panic())
Expect(func() { json.Unmarshal(x, &addr) }).ToNot(Panic())
return true
}

err := quick.Check(f, nil)
Expect(err).ToNot(HaveOccurred())
})
})
})
79 changes: 79 additions & 0 deletions chain/ethereum/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ethereum

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/renproject/pack"
)

// A Payload is an Ethereum encoded function call. It includes an ABI, the
// function being called from the ABI, and the data being passed to the
// function.
type Payload struct {
ABI pack.Bytes `json:"abi"`
Fn pack.Bytes `json:"fn"`
Data pack.Bytes `json:"data"`
}

// Encode values into an Ethereum ABI compatible byte slice.
func Encode(vals ...interface{}) []byte {
ethargs := make(abi.Arguments, 0, len(vals))
ethvals := make([]interface{}, 0, len(vals))

for _, val := range vals {
var ethval interface{}
var ty abi.Type
var err error

switch val := val.(type) {
case pack.Bytes:
ethval = val
ty, err = abi.NewType("bytes", "", nil)
case pack.Bytes32:
ethval = val
ty, err = abi.NewType("bytes32", "", nil)

case pack.U8:
ethval = big.NewInt(0).SetUint64(uint64(val.Uint8()))
ty, err = abi.NewType("uint256", "", nil)
case pack.U16:
ethval = big.NewInt(0).SetUint64(uint64(val.Uint16()))
ty, err = abi.NewType("uint256", "", nil)
case pack.U32:
ethval = big.NewInt(0).SetUint64(uint64(val.Uint32()))
ty, err = abi.NewType("uint256", "", nil)
case pack.U64:
ethval = big.NewInt(0).SetUint64(uint64(val.Uint64()))
ty, err = abi.NewType("uint256", "", nil)
case pack.U128:
ethval = val.Int()
ty, err = abi.NewType("uint256", "", nil)
case pack.U256:
ethval = val.Int()
ty, err = abi.NewType("uint256", "", nil)

case Address:
ethval = val
ty, err = abi.NewType("address", "", nil)

default:
panic(fmt.Errorf("non-exhaustive pattern: %T", val))
}

if err != nil {
panic(fmt.Errorf("error encoding: %v", err))
}
ethargs = append(ethargs, abi.Argument{
Type: ty,
})
ethvals = append(ethvals, ethval)
}

packed, err := ethargs.Pack(ethvals...)
if err != nil {
panic(fmt.Errorf("error packing: %v", err))
}
return packed
}
Loading

0 comments on commit 4eba89a

Please sign in to comment.