-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from initia-labs/evm-nft
feature: evm-nft indexer and bug fixes
- Loading branch information
Showing
27 changed files
with
4,248 additions
and
75 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 |
---|---|---|
|
@@ -42,3 +42,8 @@ linters-settings: | |
# suggest-new: true | ||
misspell: | ||
locale: US | ||
|
||
run: | ||
exclude: | ||
- "*.pb.go" | ||
- "*.pb.gw.go" |
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
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
Large diffs are not rendered by default.
Oops, something went wrong.
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,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) | ||
} |
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
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,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 | ||
} |
Oops, something went wrong.