-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(evm-types): state encoders for Eth addr and hash
- Loading branch information
1 parent
dd6a100
commit 94b7fcc
Showing
2 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |