Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: evm-nft indexer and bug fixes #55

Merged
merged 24 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ linters-settings:
# suggest-new: true
misspell:
locale: US

run:
exclude:
- "*.pb.go"
- "*.pb.gw.go"
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ Registered submodules get abci Events(i.e. FinalizeBlock and Commit) and are all
- tx
- move-nft
- wasm-nft
- ~~pair~~ deprecated
- ~~wasmpair~~ deprecated
- evm-nft
- pair: common for move/evm
- wasm-pair: only for wasm
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ go 1.22
use (
.
./submodules/block
./submodules/evm-nft
./submodules/move-nft
./submodules/pair
./submodules/wasm-nft
Expand Down
1,783 changes: 1,778 additions & 5 deletions go.work.sum

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions nft/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package types

import (
"errors"
"strconv"
)

func (m *TokenHandle) AdjustLength(delta int64) error {
if m == nil {
return errors.New("TokenHandle is nil")
}

i, err := strconv.ParseInt(m.Length, 10, 64)
if err != nil && m.Length != "" {
return err
}
i += delta
if i < 0 {
return errors.New("TokenHandle length cannot be negative")
}

m.Length = strconv.FormatInt(i, 10)
return nil
}

func (m *Collection) AdjustLength(delta int64) error {
if m == nil {
return errors.New("Collection is nil")
}
if m.Nfts == nil {
m.Nfts = &TokenHandle{}
}
return m.Nfts.AdjustLength(delta)
}

func (m *IndexedCollection) AdjustLength(delta int64) error {
if m == nil {
return errors.New("IndexedCollection is nil")
}
if m.Collection == nil {
m.Collection = &Collection{}
}
return m.Collection.AdjustLength(delta)
}
7 changes: 3 additions & 4 deletions store/cache_kvstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ func (c CacheStore) Set(key, value []byte) error {
}

func (c CacheStore) Delete(key []byte) error {
err := c.cache.Delete(string(key))
if err != nil && errors.IsOf(err, bigcache.ErrEntryNotFound) {
return errors.Wrap(err, "failed to delete cache")
}
types.AssertValidKey(key)

_ = c.cache.Delete(string(key))
c.store.Delete(key)

return nil
Expand Down
245 changes: 245 additions & 0 deletions submodules/evm-nft/collect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package evm_nft

import (
"context"

"github.com/pkg/errors"

"cosmossdk.io/collections"
cosmoserr "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

abci "github.com/cometbft/cometbft/abci/types"

"github.com/initia-labs/kvindexer/submodules/evm-nft/types"
evmtypes "github.com/initia-labs/minievm/x/evm/types"
)

func (sm EvmNFTSubmodule) finalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error {
sm.Logger(ctx).Debug("finalizeBlock", "submodule", types.SubmoduleName, "txs", len(req.Txs), "height", req.Height)

for _, txResult := range res.TxResults {
events := filterAndParseEvent(txResult.Events, eventTypes)
err := sm.processEvents(ctx, events)
if err != nil {
sm.Logger(ctx).Debug("processEvents", "error", err)
}
}

return nil
}

func (sm EvmNFTSubmodule) processEvents(ctx context.Context, events []types.EventWithAttributeMap) error {

for _, event := range events {
log, ok := event.AttributesMap[evmtypes.AttributeKeyLog]
if !ok {
continue // no log means it's not evm-related event
}

transferLog, err := types.ParseERC721TransferLog(sm.ac, log)
if err != nil && !errors.Is(err, types.ErrNotERC721) {
sm.Logger(ctx).Info("failed parse attribute", "error", err)
continue
}

var fn func(context.Context, *types.ParsedTransfer) error
switch transferLog.GetAction() {
case types.NftActionMint:
fn = sm.handleMintEvent
case types.NftActionTransfer:
fn = sm.handlerTransferEvent
case types.NftActionBurn:
fn = sm.handleBurnEvent
default:
sm.Logger(ctx).Info("unknown nft action", "action", transferLog.GetAction())
continue
}

if err := fn(ctx, transferLog); err != nil {
sm.Logger(ctx).Info("failed to handle nft-related event", "error", err.Error())
}
}
return nil
}

func (sm EvmNFTSubmodule) handleMintEvent(ctx context.Context, event *types.ParsedTransfer) error {
sm.Logger(ctx).Debug("minted", "event", event)

classId, err := evmtypes.ClassIdFromCollectionAddress(ctx, sm.vmKeeper, event.Address)
if err != nil {
return cosmoserr.Wrap(err, "failed to get classId from collection address")
}
contractSdkAddr := getCosmosAddress(event.Address)

collection, err := sm.collectionMap.Get(ctx, contractSdkAddr)
if err != nil {
if !cosmoserr.IsOf(err, collections.ErrNotFound) {
return cosmoserr.Wrap(err, "failed to check collection existence")
}
// if not found, it means this is the first minting of the collection, so we need to set into collectionMap
coll, err := sm.getIndexedCollectionFromVMStore(ctx, event.Address, classId)
if err != nil {
return cosmoserr.Wrap(err, "failed to get collection contract info")
}

collection = *coll

err = sm.collectionMap.Set(ctx, contractSdkAddr, collection)
if err != nil {
return cosmoserr.Wrap(err, "failed to set collection")
}
}

if err := collection.AdjustLength(1); err != nil {
return cosmoserr.Wrap(err, "failed to adjust collection length")
}
err = sm.collectionMap.Set(ctx, contractSdkAddr, collection)
if err != nil {
return cosmoserr.Wrap(err, "failed to set collection")
}

err = sm.applyCollectionOwnerMap(ctx, contractSdkAddr, event.To, true)
if err != nil {
return cosmoserr.Wrap(err, "failed to insert collection into collectionOwnersMap")
}

ownerSdkAddr, err := getCosmosAddressFromString(sm.ac, event.To.String())
if err != nil {
return cosmoserr.Wrap(err, "failed to parse new owner address from topic")
}

token, err := sm.getIndexedNftFromVMStore(ctx, event.Address, classId, event.TokenId, &ownerSdkAddr)
if err != nil {
return cosmoserr.Wrap(err, "failed to get token info")
}
token.CollectionName = collection.Collection.Name

err = sm.tokenMap.Set(ctx, collections.Join(contractSdkAddr, event.TokenId), *token)
if err != nil {
return cosmoserr.Wrap(err, "failed to set token")
}

err = sm.tokenOwnerMap.Set(ctx, collections.Join3(event.To, contractSdkAddr, event.TokenId), true)
if err != nil {
sm.Logger(ctx).Error("failed to insert into tokenOwnerSet", "event", event, "error", err)
return cosmoserr.Wrap(err, "failed to insert into tokenOwnerSet")
}

sm.Logger(ctx).Warn("nft minted", "collection", collection, "token", token)
return nil
}

func (sm EvmNFTSubmodule) handlerTransferEvent(ctx context.Context, event *types.ParsedTransfer) (err error) {
sm.Logger(ctx).Info("sent/transferred", "event", event)
contractSdkAddr := getCosmosAddress(event.Address)

tpk := collections.Join[sdk.AccAddress, string](contractSdkAddr, event.TokenId)

token, err := sm.tokenMap.Get(ctx, tpk)
if err != nil {
sm.Logger(ctx).Debug("failed to get nft from prev owner and object addres", "collection-addr", event.Address, "token-id", event.TokenId, "prevOwner", event.From, "error", err)
return cosmoserr.Wrap(err, "failed to get nft from tokenMap")
}
token.OwnerAddr = event.To.String()

if err = sm.tokenMap.Set(ctx, tpk, token); err != nil {
return errors.New("failed to delete nft from sender's collection")
}

err = sm.applyCollectionOwnerMap(ctx, tpk.K1(), event.From, false)
if err != nil {
return errors.New("failed to decrease collection count from prev owner")

}
err = sm.applyCollectionOwnerMap(ctx, tpk.K1(), event.To, true)
if err != nil {
return errors.New("failed to increase collection count from new owner")
}

err = sm.tokenOwnerMap.Remove(ctx, collections.Join3(event.From, tpk.K1(), tpk.K2()))
if err != nil {
sm.Logger(ctx).Error("failed to remove from tokenOwnerSet", "to", event.To, "collection-addr", tpk.K1(), "token-id", tpk.K2(), "error", err)
return errors.New("failed to insert token into tokenOwnerSet")
}
err = sm.tokenOwnerMap.Set(ctx, collections.Join3(event.To, tpk.K1(), tpk.K2()), true)
if err != nil {
sm.Logger(ctx).Error("failed to insert into tokenOwnerSet", "to", event.To, "collection-addr", tpk.K1(), "token-id", tpk.K2(), "error", err)
return errors.New("failed to insert token into tokenOwnerSet")
}

sm.Logger(ctx).Info("nft sent/transferred", "objectKey", tpk, "token", token, "prevOwner", event.From, "newOwner", event.To)
return nil
}

func (sm EvmNFTSubmodule) handleBurnEvent(ctx context.Context, event *types.ParsedTransfer) error {
sm.Logger(ctx).Info("burnt", "event", event)
contractSdkAddr := getCosmosAddress(event.Address)

// remove from tokensOwnersMap
tpk := collections.Join[sdk.AccAddress, string](contractSdkAddr, event.TokenId)
token, err := sm.tokenMap.Get(ctx, tpk)
if err != nil {
return cosmoserr.Wrap(err, "failed to get nft from tokenMap")
}

err = sm.tokenMap.Remove(ctx, tpk)
if err != nil {
return cosmoserr.Wrap(err, "failed to delete nft from tokenMap")
}

ownerSdkAddr, err := getCosmosAddressFromString(sm.ac, token.OwnerAddr)
if err != nil {
return cosmoserr.Wrap(err, "failed to get owner address from token")
}

err = sm.tokenOwnerMap.Remove(ctx, collections.Join3(ownerSdkAddr, tpk.K1(), tpk.K2()))
if err != nil {
sm.Logger(ctx).Error("failed to remove from tokenOwnerSet", "owner", ownerSdkAddr, "collection-addr", tpk.K1(), "token-id", tpk.K2(), "error", err)
return cosmoserr.Wrap(err, "failed to insert token into tokenOwnerSet")
}
collection, err := sm.collectionMap.Get(ctx, contractSdkAddr)
if err != nil {
return cosmoserr.Wrap(err, "failed to get collection from collectionMap")
}
if err := collection.AdjustLength(-1); err != nil {
return cosmoserr.Wrap(err, "failed to adjust collection length")
}
err = sm.collectionMap.Set(ctx, contractSdkAddr, collection)
if err != nil {
return cosmoserr.Wrap(err, "failed to set collection")
}

err = sm.applyCollectionOwnerMap(ctx, contractSdkAddr, ownerSdkAddr, false)
if err != nil {
return err // just return err, no wrap
}

sm.Logger(ctx).Info("nft burnt", "event", event)

return nil
}

func (sm EvmNFTSubmodule) applyCollectionOwnerMap(ctx context.Context, collectionAddr, ownerAddr sdk.AccAddress, isIncrease bool) error {
count, err := sm.collectionOwnerMap.Get(ctx, collections.Join(ownerAddr, collectionAddr))
if err != nil {
if !isIncrease || (isIncrease && !cosmoserr.IsOf(err, collections.ErrNotFound)) {
return cosmoserr.Wrap(err, "failed to get collection count from collectionOwnersMap")
}
}
if isIncrease {
count++
} else {
count--
}

if count == 0 {
err = sm.collectionOwnerMap.Remove(ctx, collections.Join(ownerAddr, collectionAddr))
} else {
err = sm.collectionOwnerMap.Set(ctx, collections.Join(ownerAddr, collectionAddr), count)
}
if err != nil {
return cosmoserr.Wrap(err, "failed to update collection count in collectionOwnersMap")
}
return nil
}
Loading
Loading