From 2c1198d90ba8c051b9dbe5c4eedf906775bd8299 Mon Sep 17 00:00:00 2001 From: Kevin Yang <5478483+k-yang@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:16:13 -0500 Subject: [PATCH] fix: fun token state marshalling (#1947) * fix: HexAddr marshalling issue * add hex addr string test * refactor: remove EthAddr alias type * refactor: remove EthHash alias type * refactor: remove extra types * refactor: FunToken ID generation * fix: test fun token state * Update CHANGELOG.md --- CHANGELOG.md | 1 + eth/hex.go | 6 +-- eth/hex_test.go | 9 ++++ eth/state_encoder.go | 32 +++++------ eth/state_encoder_test.go | 4 +- x/evm/evm.go | 19 +++---- x/evm/keeper/evm_state.go | 4 +- x/evm/keeper/funtoken_state.go | 18 +++---- x/evm/keeper/funtoken_state_test.go | 82 +++++++++++++++++++++++++++++ 9 files changed, 125 insertions(+), 50 deletions(-) create mode 100644 x/evm/keeper/funtoken_state_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5694d65ed..1903de5ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1917](https://github.com/NibiruChain/nibiru/pull/1917) - test(e2e-evm): TypeScript support. Type generation from compiled contracts. Formatter for TS code. - [#1922](https://github.com/NibiruChain/nibiru/pull/1922) - feat(evm): tracer option is read from the config. - [#1936](https://github.com/NibiruChain/nibiru/pull/1936) - feat(evm): EVM fungible token protobufs and encoding tests +- [#1947](https://github.com/NibiruChain/nibiru/pull/1947) - fix(evm): fix FunToken state marshalling #### Dapp modules: perp, spot, oracle, etc diff --git a/eth/hex.go b/eth/hex.go index 3e6cf0cde..4f4017bc5 100644 --- a/eth/hex.go +++ b/eth/hex.go @@ -16,7 +16,7 @@ type HexAddr string var _ sdk.CustomProtobufType = (*HexAddr)(nil) -func NewHexAddr(addr EthAddr) HexAddr { +func NewHexAddr(addr gethcommon.Address) HexAddr { return HexAddr(addr.Hex()) } @@ -58,7 +58,7 @@ func (h HexAddr) Valid() error { return nil } -func (h HexAddr) ToAddr() EthAddr { +func (h HexAddr) ToAddr() gethcommon.Address { return gethcommon.HexToAddress(string(h)) } @@ -88,7 +88,7 @@ func (h HexAddr) MarshalJSON() ([]byte, error) { // Implements the gogo proto custom type interface. // Ref: https://github.com/cosmos/gogoproto/blob/v1.5.0/custom_types.md func (h *HexAddr) MarshalTo(data []byte) (n int, err error) { - bz := []byte{} + bz := []byte(*h) copy(data, bz) hexAddr, err := NewHexAddrFromStr(string(bz)) *h = hexAddr diff --git a/eth/hex_test.go b/eth/hex_test.go index 8d11fafce..0fef9f855 100644 --- a/eth/hex_test.go +++ b/eth/hex_test.go @@ -270,3 +270,12 @@ func (s *Suite) TestProtobufEncoding() { }) } } + +func (s *Suite) TestHexAddrToString() { + hexAddr := eth.HexAddr("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") + s.Equal("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", hexAddr.String()) + s.Equal("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", string(hexAddr)) + + ethAddr := gethcommon.HexToAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") + s.Equal("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", ethAddr.String()) +} diff --git a/eth/state_encoder.go b/eth/state_encoder.go index 205ab7097..bb3fbaef6 100644 --- a/eth/state_encoder.go +++ b/eth/state_encoder.go @@ -12,24 +12,18 @@ func BytesToHex(bz []byte) string { return fmt.Sprintf("%x", bz) } -// EthAddr: (alias) 20 byte address of an Ethereum account. -type EthAddr = gethcommon.Address - -// EthHash: (alias) 32 byte Keccak256 hash of arbitrary data. -type EthHash = gethcommon.Hash //revive:disable-line:exported - 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{} + ValueEncoderEthAddr collections.ValueEncoder[gethcommon.Address] = veEthAddr{} // keEthHash: Implements a `collections.KeyEncoder` for an Ethereum address. - KeyEncoderEthAddr collections.KeyEncoder[EthAddr] = keEthAddr{} + KeyEncoderEthAddr collections.KeyEncoder[gethcommon.Address] = keEthAddr{} // keEthHash: Implements a `collections.KeyEncoder` for an Ethereum hash. - KeyEncoderEthHash collections.KeyEncoder[EthHash] = keEthHash{} + KeyEncoderEthHash collections.KeyEncoder[gethcommon.Hash] = keEthHash{} ) // collections ValueEncoder[[]byte] @@ -43,10 +37,10 @@ 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 gethcommon.BytesToAddress(bz) } -func (_ veEthAddr) Stringify(value EthAddr) string { return value.Hex() } -func (_ veEthAddr) Name() string { return "EthAddr" } +func (_ veEthAddr) Encode(value gethcommon.Address) []byte { return value.Bytes() } +func (_ veEthAddr) Decode(bz []byte) gethcommon.Address { return gethcommon.BytesToAddress(bz) } +func (_ veEthAddr) Stringify(value gethcommon.Address) string { return value.Hex() } +func (_ veEthAddr) Name() string { return "gethcommon.Address" } type keBytes struct{} @@ -63,17 +57,17 @@ 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) { +func (_ keEthAddr) Encode(value gethcommon.Address) []byte { return value.Bytes() } +func (_ keEthAddr) Decode(bz []byte) (int, gethcommon.Address) { return gethcommon.AddressLength, gethcommon.BytesToAddress(bz) } -func (_ keEthAddr) Stringify(value EthAddr) string { return value.Hex() } +func (_ keEthAddr) Stringify(value gethcommon.Address) 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) { +func (_ keEthHash) Encode(value gethcommon.Hash) []byte { return value.Bytes() } +func (_ keEthHash) Decode(bz []byte) (int, gethcommon.Hash) { return gethcommon.HashLength, gethcommon.BytesToHash(bz) } -func (_ keEthHash) Stringify(value EthHash) string { return value.Hex() } +func (_ keEthHash) Stringify(value gethcommon.Hash) string { return value.Hex() } diff --git a/eth/state_encoder_test.go b/eth/state_encoder_test.go index 02d5a8f89..03eff2ac6 100644 --- a/eth/state_encoder_test.go +++ b/eth/state_encoder_test.go @@ -64,7 +64,7 @@ func (s *Suite) TestEncoderBytes() { func (s *Suite) TestEncoderEthAddr() { testCases := []struct { name string - given eth.EthAddr + given gethcommon.Address wantPanic bool }{ { @@ -77,7 +77,7 @@ func (s *Suite) TestEncoderEthAddr() { }, { name: "Nibiru Bech 32 addr (hypothetically)", - given: eth.EthAddr([]byte("nibi1rlvdjfmxkyfj4tzu73p8m4g2h4y89xccf9622l")), + given: gethcommon.Address([]byte("nibi1rlvdjfmxkyfj4tzu73p8m4g2h4y89xccf9622l")), }, } for _, tc := range testCases { diff --git a/x/evm/evm.go b/x/evm/evm.go index 08612c9d9..daaf2c059 100644 --- a/x/evm/evm.go +++ b/x/evm/evm.go @@ -15,29 +15,24 @@ import ( // tokens that are valid ERC20s with the same address. // https://github.com/NibiruChain/nibiru/issues/1933 func (fun FunToken) ID() []byte { - return newFunTokenIDFromStr(fun.Erc20Addr.String(), fun.BankDenom) + return NewFunTokenID(fun.Erc20Addr, fun.BankDenom) } -func NewFunTokenID(erc20 gethcommon.Address, bankDenom string) []byte { - erc20Addr := erc20.Hex() - return newFunTokenIDFromStr(erc20Addr, bankDenom) +func NewFunTokenID(erc20 eth.HexAddr, bankDenom string) []byte { + return tmhash.Sum([]byte(erc20.String() + "|" + bankDenom)) } -func newFunTokenIDFromStr(erc20AddrHex string, bankDenom string) []byte { - return tmhash.Sum([]byte(erc20AddrHex + "|" + bankDenom)) -} - -func errValidateFunToken(errMsg string) error { - return fmt.Errorf("FunTokenError: %s", errMsg) +func funTokenValidationError(err error) error { + return fmt.Errorf("FunTokenError: %s", err.Error()) } func (fun FunToken) Validate() error { if err := sdk.ValidateDenom(fun.BankDenom); err != nil { - return errValidateFunToken(err.Error()) + return funTokenValidationError(err) } if err := fun.Erc20Addr.Valid(); err != nil { - return errValidateFunToken(err.Error()) + return funTokenValidationError(err) } return nil diff --git a/x/evm/keeper/evm_state.go b/x/evm/keeper/evm_state.go index 0f0013f1c..318beb4d8 100644 --- a/x/evm/keeper/evm_state.go +++ b/x/evm/keeper/evm_state.go @@ -130,7 +130,7 @@ func (k Keeper) SetParams(ctx sdk.Context, params evm.Params) { // SetState updates contract storage and deletes if the value is empty. func (state EvmState) SetAccState( - ctx sdk.Context, addr eth.EthAddr, stateKey eth.EthHash, stateValue []byte, + ctx sdk.Context, addr gethcommon.Address, stateKey gethcommon.Hash, stateValue []byte, ) { if len(stateValue) != 0 { state.AccState.Insert(ctx, collections.Join(addr, stateKey), stateValue) @@ -140,7 +140,7 @@ func (state EvmState) SetAccState( } // GetState: Implements `statedb.Keeper` interface: Loads smart contract state. -func (k *Keeper) GetState(ctx sdk.Context, addr eth.EthAddr, stateKey eth.EthHash) eth.EthHash { +func (k *Keeper) GetState(ctx sdk.Context, addr gethcommon.Address, stateKey gethcommon.Hash) gethcommon.Hash { return gethcommon.BytesToHash(k.EvmState.AccState.GetOr( ctx, collections.Join(addr, stateKey), diff --git a/x/evm/keeper/funtoken_state.go b/x/evm/keeper/funtoken_state.go index c713c1fc3..2833eb597 100644 --- a/x/evm/keeper/funtoken_state.go +++ b/x/evm/keeper/funtoken_state.go @@ -10,19 +10,13 @@ import ( "github.com/NibiruChain/nibiru/eth" "github.com/NibiruChain/nibiru/x/evm" - funtoken "github.com/NibiruChain/nibiru/x/evm" -) - -type ( - funtokenPrimaryKeyType = []byte - funtokenValueType = funtoken.FunToken ) // FunTokenState isolates the key-value stores (collections) for fungible token // mappings. This struct is written as an extension of the default indexed map to // add utility functions. type FunTokenState struct { - collections.IndexedMap[funtokenPrimaryKeyType, funtokenValueType, IndexesFunToken] + collections.IndexedMap[[]byte, evm.FunToken, IndexesFunToken] } func NewFunTokenState( @@ -30,7 +24,7 @@ func NewFunTokenState( storeKey storetypes.StoreKey, ) FunTokenState { primaryKeyEncoder := eth.KeyEncoderBytes - valueEncoder := collections.ProtoValueEncoder[funtokenValueType](cdc) + valueEncoder := collections.ProtoValueEncoder[evm.FunToken](cdc) idxMap := collections.NewIndexedMap( storeKey, evm.KeyPrefixFunTokens, primaryKeyEncoder, valueEncoder, IndexesFunToken{ @@ -53,8 +47,8 @@ func NewFunTokenState( return FunTokenState{IndexedMap: idxMap} } -func (idxs IndexesFunToken) IndexerList() []collections.Indexer[funtokenPrimaryKeyType, funtokenValueType] { - return []collections.Indexer[funtokenPrimaryKeyType, funtokenValueType]{ +func (idxs IndexesFunToken) IndexerList() []collections.Indexer[[]byte, evm.FunToken] { + return []collections.Indexer[[]byte, evm.FunToken]{ idxs.ERC20Addr, idxs.BankDenom, } @@ -66,13 +60,13 @@ type IndexesFunToken struct { // - indexing key (IK): ERC-20 addr // - primary key (PK): FunToken ID // - value (V): FunToken value - ERC20Addr collections.MultiIndex[gethcommon.Address, funtokenPrimaryKeyType, funtokenValueType] + ERC20Addr collections.MultiIndex[gethcommon.Address, []byte, evm.FunToken] // BankDenom (MultiIndex): Index FunToken by coin denomination // - indexing key (IK): Coin denom // - primary key (PK): FunToken ID // - value (V): FunToken value - BankDenom collections.MultiIndex[string, funtokenPrimaryKeyType, funtokenValueType] + BankDenom collections.MultiIndex[string, []byte, evm.FunToken] } // Insert adds an [evm.FunToken] to state with defensive validation. Errors if diff --git a/x/evm/keeper/funtoken_state_test.go b/x/evm/keeper/funtoken_state_test.go new file mode 100644 index 000000000..122e3f409 --- /dev/null +++ b/x/evm/keeper/funtoken_state_test.go @@ -0,0 +1,82 @@ +package keeper_test + +import ( + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/evm" + "github.com/NibiruChain/nibiru/x/evm/evmtest" +) + +func (s *KeeperSuite) TestInsertAndGet() { + deps := evmtest.NewTestDeps() + + erc20Addr := gethcommon.HexToAddress("0xAEf9437FF23D48D73271a41a8A094DEc9ac71477") + err := deps.K.FunTokens.SafeInsert( + deps.Ctx, + erc20Addr, + "unibi", + true, + ) + s.Require().NoError(err) + + // test Get + funToken, err := deps.K.FunTokens.Get(deps.Ctx, evm.NewFunTokenID(eth.NewHexAddr(erc20Addr), "unibi")) + s.Require().NoError(err) + s.Require().Equal(eth.HexAddr("0xAEf9437FF23D48D73271a41a8A094DEc9ac71477"), funToken.Erc20Addr) + s.Require().Equal("unibi", funToken.BankDenom) + s.Require().True(funToken.IsMadeFromCoin) + + // iter := deps.K.FunTokens.Indexes.BankDenom.ExactMatch(deps.Ctx, "unibi") + // deps.K.FunTokens.Collect(ctx) +} + +func (s *KeeperSuite) TestCollect() { + deps := evmtest.NewTestDeps() + + erc20Addr := gethcommon.HexToAddress("0xAEf9437FF23D48D73271a41a8A094DEc9ac71477") + err := deps.K.FunTokens.SafeInsert( + deps.Ctx, + erc20Addr, + "unibi", + true, + ) + s.Require().NoError(err) + + // test Collect by bank denom + iter := deps.K.FunTokens.Indexes.BankDenom.ExactMatch(deps.Ctx, "unibi") + funTokens := deps.K.FunTokens.Collect(deps.Ctx, iter) + s.Require().Len(funTokens, 1) + s.Require().Equal(eth.HexAddr("0xAEf9437FF23D48D73271a41a8A094DEc9ac71477"), funTokens[0].Erc20Addr) + s.Require().Equal("unibi", funTokens[0].BankDenom) + s.Require().True(funTokens[0].IsMadeFromCoin) + + // test Collect by erc20 addr + iter2 := deps.K.FunTokens.Indexes.ERC20Addr.ExactMatch(deps.Ctx, erc20Addr) + funTokens = deps.K.FunTokens.Collect(deps.Ctx, iter2) + s.Require().Len(funTokens, 1) + s.Require().Equal(eth.HexAddr("0xAEf9437FF23D48D73271a41a8A094DEc9ac71477"), funTokens[0].Erc20Addr) + s.Require().Equal("unibi", funTokens[0].BankDenom) + s.Require().True(funTokens[0].IsMadeFromCoin) +} + +func (s *KeeperSuite) TestDelete() { + deps := evmtest.NewTestDeps() + + erc20Addr := gethcommon.HexToAddress("0xAEf9437FF23D48D73271a41a8A094DEc9ac71477") + err := deps.K.FunTokens.SafeInsert( + deps.Ctx, + erc20Addr, + "unibi", + true, + ) + s.Require().NoError(err) + + // test Delete + err = deps.K.FunTokens.Delete(deps.Ctx, evm.NewFunTokenID(eth.NewHexAddr(erc20Addr), "unibi")) + s.Require().NoError(err) + + // test Get + _, err = deps.K.FunTokens.Get(deps.Ctx, evm.NewFunTokenID(eth.NewHexAddr(erc20Addr), "unibi")) + s.Require().Error(err) +}