From 94b7fccd1273ae2e9f0d51c1fea4f912a9709e2f Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Tue, 16 Apr 2024 01:07:59 +0400 Subject: [PATCH 1/4] feat(evm-types): state encoders for Eth addr and hash --- eth/types/state_encoder.go | 79 +++++++++++++++++++++ eth/types/state_encoder_test.go | 122 ++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 eth/types/state_encoder.go create mode 100644 eth/types/state_encoder_test.go diff --git a/eth/types/state_encoder.go b/eth/types/state_encoder.go new file mode 100644 index 000000000..29c316dfd --- /dev/null +++ b/eth/types/state_encoder.go @@ -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() } diff --git a/eth/types/state_encoder_test.go b/eth/types/state_encoder_test.go new file mode 100644 index 000000000..ae4234db3 --- /dev/null +++ b/eth/types/state_encoder_test.go @@ -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) + } + }) + } +} From 410bc14f3aa39c840825b885f33016ca2de261f0 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Tue, 16 Apr 2024 01:09:29 +0400 Subject: [PATCH 2/4] chore: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c1e2f457..bda88cc17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1837](https://github.com/NibiruChain/nibiru/pull/1837) - feat(eth): protos, eth types, and evm module types - [#1838](https://github.com/NibiruChain/nibiru/pull/1838) - feat(eth): Go-ethereum, crypto, encoding, and unit tests for evm/types +- [#1841](https://github.com/NibiruChain/nibiru/pull/1841) - feat(eth): Collections encoders for bytes, Ethereum addresses, and Ethereum hashes #### Dapp modules: perp, spot, etc From 7dd593afb8fe6129c9dacfa772c48945e5415a46 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Mon, 15 Apr 2024 16:26:00 +0400 Subject: [PATCH 3/4] linter --- eth/types/state_encoder_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth/types/state_encoder_test.go b/eth/types/state_encoder_test.go index ae4234db3..94779de12 100644 --- a/eth/types/state_encoder_test.go +++ b/eth/types/state_encoder_test.go @@ -4,12 +4,13 @@ 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" + ethtypes "github.com/NibiruChain/nibiru/eth/types" + sdkcodec "github.com/cosmos/cosmos-sdk/codec" sdkcodectypes "github.com/cosmos/cosmos-sdk/codec/types" sdkstore "github.com/cosmos/cosmos-sdk/store" From 045e54734ad51136162dc20674e577e653e2c240 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Tue, 16 Apr 2024 01:38:55 +0400 Subject: [PATCH 4/4] unused fn --- eth/types/state_encoder_test.go | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/eth/types/state_encoder_test.go b/eth/types/state_encoder_test.go index 94779de12..6ae627c43 100644 --- a/eth/types/state_encoder_test.go +++ b/eth/types/state_encoder_test.go @@ -4,39 +4,13 @@ import ( "testing" "github.com/NibiruChain/collections" - cmtdb "github.com/cometbft/cometbft-db" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ethtypes "github.com/NibiruChain/nibiru/eth/types" - - 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)