Skip to content

Commit

Permalink
Merge pull request #55 from initia-labs/evm-nft
Browse files Browse the repository at this point in the history
feature: evm-nft indexer and bug fixes
  • Loading branch information
Vritra4 authored Aug 6, 2024
2 parents ab84e04 + b15102b commit fc557a1
Show file tree
Hide file tree
Showing 27 changed files with 4,248 additions and 75 deletions.
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

0 comments on commit fc557a1

Please sign in to comment.