Skip to content

Commit

Permalink
feat(evm-types): state encoders for Eth addr and hash
Browse files Browse the repository at this point in the history
  • Loading branch information
Unique-Divine committed Apr 15, 2024
1 parent dd6a100 commit 94b7fcc
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 0 deletions.
79 changes: 79 additions & 0 deletions eth/types/state_encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package types

import (
fmt "fmt"

"github.com/NibiruChain/collections"
ethcommon "github.com/ethereum/go-ethereum/common"
)

// BytesToHex converts a byte array to a hexadecimal string
func BytesToHex(bz []byte) string {
return fmt.Sprintf("%x", bz)
}

// EthAddr: (alias) 20 byte address of an Ethereum account.
type EthAddr = ethcommon.Address

// EthHash: (alias) 32 byte Keccak256 hash of arbitrary data.
type EthHash = ethcommon.Hash

var (
// Implements a `collections.ValueEncoder` for the `[]byte` type
ValueEncoderBytes collections.ValueEncoder[[]byte] = veBytes{}
KeyEncoderBytes collections.KeyEncoder[[]byte] = keBytes{}

// Implements a `collections.ValueEncoder` for an Ethereum address.
ValueEncoderEthAddr collections.ValueEncoder[EthAddr] = veEthAddr{}
// keEthHash: Implements a `collections.KeyEncoder` for an Ethereum address.
KeyEncoderEthAddr collections.KeyEncoder[EthAddr] = keEthAddr{}

// keEthHash: Implements a `collections.KeyEncoder` for an Ethereum hash.
KeyEncoderEthHash collections.KeyEncoder[EthHash] = keEthHash{}
)

// collections ValueEncoder[[]byte]
type veBytes struct{}

func (_ veBytes) Encode(value []byte) []byte { return value }
func (_ veBytes) Decode(bz []byte) []byte { return bz }
func (_ veBytes) Stringify(value []byte) string { return BytesToHex(value) }
func (_ veBytes) Name() string { return "[]byte" }

// veEthAddr: Implements a `collections.ValueEncoder` for an Ethereum address.
type veEthAddr struct{}

func (_ veEthAddr) Encode(value EthAddr) []byte { return value.Bytes() }
func (_ veEthAddr) Decode(bz []byte) EthAddr { return ethcommon.BytesToAddress(bz) }
func (_ veEthAddr) Stringify(value EthAddr) string { return value.Hex() }
func (_ veEthAddr) Name() string { return "EthAddr" }

type keBytes struct{}

// Encode encodes the type T into bytes.
func (_ keBytes) Encode(key []byte) []byte { return key }

// Decode decodes the given bytes back into T.
// And it also must return the bytes of the buffer which were read.
func (_ keBytes) Decode(bz []byte) (int, []byte) { return len(bz), bz }

// Stringify returns a string representation of T.
func (_ keBytes) Stringify(key []byte) string { return BytesToHex(key) }

// keEthAddr: Implements a `collections.KeyEncoder` for an Ethereum address.
type keEthAddr struct{}

func (_ keEthAddr) Encode(value EthAddr) []byte { return value.Bytes() }
func (_ keEthAddr) Decode(bz []byte) (int, EthAddr) {
return ethcommon.AddressLength, ethcommon.BytesToAddress(bz)
}
func (_ keEthAddr) Stringify(value EthAddr) string { return value.Hex() }

// keEthHash: Implements a `collections.KeyEncoder` for an Ethereum hash.
type keEthHash struct{}

func (_ keEthHash) Encode(value EthHash) []byte { return value.Bytes() }
func (_ keEthHash) Decode(bz []byte) (int, EthHash) {
return ethcommon.HashLength, ethcommon.BytesToHash(bz)
}
func (_ keEthHash) Stringify(value EthHash) string { return value.Hex() }
122 changes: 122 additions & 0 deletions eth/types/state_encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package types_test

import (
"testing"

"github.com/NibiruChain/collections"
ethtypes "github.com/NibiruChain/nibiru/eth/types"
cmtdb "github.com/cometbft/cometbft-db"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

sdkcodec "github.com/cosmos/cosmos-sdk/codec"
sdkcodectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdkstore "github.com/cosmos/cosmos-sdk/store"
sdkstoretypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// encoderDeps: Initializes a database and KV store useful for testing
// abstractions over merklized storage like the `collections.Map` and
// `collections.Item`.
func encoderDeps() (sdkstoretypes.StoreKey, sdk.Context, sdkcodec.BinaryCodec) {
sk := sdk.NewKVStoreKey("mock")
dbm := cmtdb.NewMemDB()
ms := sdkstore.NewCommitMultiStore(dbm)
ms.MountStoreWithDB(sk, sdkstoretypes.StoreTypeIAVL, dbm)
if err := ms.LoadLatestVersion(); err != nil {
panic(err)
}

return sk,
sdk.Context{}.
WithMultiStore(ms).
WithGasMeter(sdk.NewGasMeter(1_000_000_000)),
sdkcodec.NewProtoCodec(sdkcodectypes.NewInterfaceRegistry())
}

func assertBijectiveKey[T any](t *testing.T, encoder collections.KeyEncoder[T], key T) {
encodedKey := encoder.Encode(key)
readLen, decodedKey := encoder.Decode(encodedKey)
require.Equal(t, len(encodedKey), readLen, "encoded key and read bytes must have same size")
require.Equal(t, key, decodedKey, "encoding and decoding produces different keys")
wantStr := encoder.Stringify(key)
gotStr := encoder.Stringify(decodedKey)
require.Equal(t, wantStr, gotStr,
"encoding and decoding produce different string representations")
}

func assertBijectiveValue[T any](t *testing.T, encoder collections.ValueEncoder[T], value T) {
encodedValue := encoder.Encode(value)
decodedValue := encoder.Decode(encodedValue)
require.Equal(t, value, decodedValue, "encoding and decoding produces different values")

wantStr := encoder.Stringify(value)
gotStr := encoder.Stringify(decodedValue)
require.Equal(t, wantStr, gotStr,
"encoding and decoding produce different string representations")
require.NotEmpty(t, encoder.Name())
}

type SuiteEncoders struct {
suite.Suite
}

func TestSuiteEncoders_RunAll(t *testing.T) {
suite.Run(t, new(SuiteEncoders))
}

func (s *SuiteEncoders) TestEncoderBytes() {
testCases := []struct {
name string
value string
}{
{"dec-like number", "-1000.5858"},
{"Nibiru bech32 addr", "nibi1rlvdjfmxkyfj4tzu73p8m4g2h4y89xccf9622l"},
{"Nibiru EVM addr", "0xA52c829E935C30F4C7dcD66739Cf91BF79dD9253"},
{"normal text with special symbols", "abc123日本語!!??foobar"},
}
for _, tc := range testCases {
s.Run("bijectivity: []byte encoders "+tc.name, func() {
given := []byte(tc.value)
assertBijectiveKey(s.T(), ethtypes.KeyEncoderBytes, given)
assertBijectiveValue(s.T(), ethtypes.ValueEncoderBytes, given)
})
}
}

func (s *SuiteEncoders) TestEncoderEthAddr() {
testCases := []struct {
name string
given ethtypes.EthAddr
wantPanic bool
}{
{
name: "Nibiru EVM addr",
given: ethcommon.BytesToAddress([]byte("0xA52c829E935C30F4C7dcD66739Cf91BF79dD9253")),
},
{
name: "Nibiru EVM addr length above 20 bytes",
given: ethcommon.BytesToAddress([]byte("0xA52c829E935C30F4C7dcD66739Cf91BF79dD92532456BF123421")),
},
{
name: "Nibiru Bech 32 addr (hypothetically)",
given: ethtypes.EthAddr([]byte("nibi1rlvdjfmxkyfj4tzu73p8m4g2h4y89xccf9622l")),
},
}
for _, tc := range testCases {
s.Run("bijectivity: []byte encoders "+tc.name, func() {
given := tc.given
runTest := func() {
assertBijectiveKey(s.T(), ethtypes.KeyEncoderEthAddr, given)
assertBijectiveValue(s.T(), ethtypes.ValueEncoderEthAddr, given)
}
if tc.wantPanic {
s.Require().Panics(runTest)
} else {
s.Require().NotPanics(runTest)
}
})
}
}

0 comments on commit 94b7fcc

Please sign in to comment.