Skip to content

Commit fc557a1

Browse files
authored
Merge pull request #55 from initia-labs/evm-nft
feature: evm-nft indexer and bug fixes
2 parents ab84e04 + b15102b commit fc557a1

27 files changed

+4248
-75
lines changed

.golangci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,8 @@ linters-settings:
4242
# suggest-new: true
4343
misspell:
4444
locale: US
45+
46+
run:
47+
exclude:
48+
- "*.pb.go"
49+
- "*.pb.gw.go"

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ Registered submodules get abci Events(i.e. FinalizeBlock and Commit) and are all
1010
- tx
1111
- move-nft
1212
- wasm-nft
13-
- ~~pair~~ deprecated
14-
- ~~wasmpair~~ deprecated
13+
- evm-nft
14+
- pair: common for move/evm
15+
- wasm-pair: only for wasm

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ go 1.22
33
use (
44
.
55
./submodules/block
6+
./submodules/evm-nft
67
./submodules/move-nft
78
./submodules/pair
89
./submodules/wasm-nft

go.work.sum

Lines changed: 1778 additions & 5 deletions
Large diffs are not rendered by default.

nft/types/types.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package types
2+
3+
import (
4+
"errors"
5+
"strconv"
6+
)
7+
8+
func (m *TokenHandle) AdjustLength(delta int64) error {
9+
if m == nil {
10+
return errors.New("TokenHandle is nil")
11+
}
12+
13+
i, err := strconv.ParseInt(m.Length, 10, 64)
14+
if err != nil && m.Length != "" {
15+
return err
16+
}
17+
i += delta
18+
if i < 0 {
19+
return errors.New("TokenHandle length cannot be negative")
20+
}
21+
22+
m.Length = strconv.FormatInt(i, 10)
23+
return nil
24+
}
25+
26+
func (m *Collection) AdjustLength(delta int64) error {
27+
if m == nil {
28+
return errors.New("Collection is nil")
29+
}
30+
if m.Nfts == nil {
31+
m.Nfts = &TokenHandle{}
32+
}
33+
return m.Nfts.AdjustLength(delta)
34+
}
35+
36+
func (m *IndexedCollection) AdjustLength(delta int64) error {
37+
if m == nil {
38+
return errors.New("IndexedCollection is nil")
39+
}
40+
if m.Collection == nil {
41+
m.Collection = &Collection{}
42+
}
43+
return m.Collection.AdjustLength(delta)
44+
}

store/cache_kvstore.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,9 @@ func (c CacheStore) Set(key, value []byte) error {
6969
}
7070

7171
func (c CacheStore) Delete(key []byte) error {
72-
err := c.cache.Delete(string(key))
73-
if err != nil && errors.IsOf(err, bigcache.ErrEntryNotFound) {
74-
return errors.Wrap(err, "failed to delete cache")
75-
}
72+
types.AssertValidKey(key)
73+
74+
_ = c.cache.Delete(string(key))
7675
c.store.Delete(key)
7776

7877
return nil

submodules/evm-nft/collect.go

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package evm_nft
2+
3+
import (
4+
"context"
5+
6+
"github.com/pkg/errors"
7+
8+
"cosmossdk.io/collections"
9+
cosmoserr "cosmossdk.io/errors"
10+
sdk "github.com/cosmos/cosmos-sdk/types"
11+
12+
abci "github.com/cometbft/cometbft/abci/types"
13+
14+
"github.com/initia-labs/kvindexer/submodules/evm-nft/types"
15+
evmtypes "github.com/initia-labs/minievm/x/evm/types"
16+
)
17+
18+
func (sm EvmNFTSubmodule) finalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error {
19+
sm.Logger(ctx).Debug("finalizeBlock", "submodule", types.SubmoduleName, "txs", len(req.Txs), "height", req.Height)
20+
21+
for _, txResult := range res.TxResults {
22+
events := filterAndParseEvent(txResult.Events, eventTypes)
23+
err := sm.processEvents(ctx, events)
24+
if err != nil {
25+
sm.Logger(ctx).Debug("processEvents", "error", err)
26+
}
27+
}
28+
29+
return nil
30+
}
31+
32+
func (sm EvmNFTSubmodule) processEvents(ctx context.Context, events []types.EventWithAttributeMap) error {
33+
34+
for _, event := range events {
35+
log, ok := event.AttributesMap[evmtypes.AttributeKeyLog]
36+
if !ok {
37+
continue // no log means it's not evm-related event
38+
}
39+
40+
transferLog, err := types.ParseERC721TransferLog(sm.ac, log)
41+
if err != nil && !errors.Is(err, types.ErrNotERC721) {
42+
sm.Logger(ctx).Info("failed parse attribute", "error", err)
43+
continue
44+
}
45+
46+
var fn func(context.Context, *types.ParsedTransfer) error
47+
switch transferLog.GetAction() {
48+
case types.NftActionMint:
49+
fn = sm.handleMintEvent
50+
case types.NftActionTransfer:
51+
fn = sm.handlerTransferEvent
52+
case types.NftActionBurn:
53+
fn = sm.handleBurnEvent
54+
default:
55+
sm.Logger(ctx).Info("unknown nft action", "action", transferLog.GetAction())
56+
continue
57+
}
58+
59+
if err := fn(ctx, transferLog); err != nil {
60+
sm.Logger(ctx).Info("failed to handle nft-related event", "error", err.Error())
61+
}
62+
}
63+
return nil
64+
}
65+
66+
func (sm EvmNFTSubmodule) handleMintEvent(ctx context.Context, event *types.ParsedTransfer) error {
67+
sm.Logger(ctx).Debug("minted", "event", event)
68+
69+
classId, err := evmtypes.ClassIdFromCollectionAddress(ctx, sm.vmKeeper, event.Address)
70+
if err != nil {
71+
return cosmoserr.Wrap(err, "failed to get classId from collection address")
72+
}
73+
contractSdkAddr := getCosmosAddress(event.Address)
74+
75+
collection, err := sm.collectionMap.Get(ctx, contractSdkAddr)
76+
if err != nil {
77+
if !cosmoserr.IsOf(err, collections.ErrNotFound) {
78+
return cosmoserr.Wrap(err, "failed to check collection existence")
79+
}
80+
// if not found, it means this is the first minting of the collection, so we need to set into collectionMap
81+
coll, err := sm.getIndexedCollectionFromVMStore(ctx, event.Address, classId)
82+
if err != nil {
83+
return cosmoserr.Wrap(err, "failed to get collection contract info")
84+
}
85+
86+
collection = *coll
87+
88+
err = sm.collectionMap.Set(ctx, contractSdkAddr, collection)
89+
if err != nil {
90+
return cosmoserr.Wrap(err, "failed to set collection")
91+
}
92+
}
93+
94+
if err := collection.AdjustLength(1); err != nil {
95+
return cosmoserr.Wrap(err, "failed to adjust collection length")
96+
}
97+
err = sm.collectionMap.Set(ctx, contractSdkAddr, collection)
98+
if err != nil {
99+
return cosmoserr.Wrap(err, "failed to set collection")
100+
}
101+
102+
err = sm.applyCollectionOwnerMap(ctx, contractSdkAddr, event.To, true)
103+
if err != nil {
104+
return cosmoserr.Wrap(err, "failed to insert collection into collectionOwnersMap")
105+
}
106+
107+
ownerSdkAddr, err := getCosmosAddressFromString(sm.ac, event.To.String())
108+
if err != nil {
109+
return cosmoserr.Wrap(err, "failed to parse new owner address from topic")
110+
}
111+
112+
token, err := sm.getIndexedNftFromVMStore(ctx, event.Address, classId, event.TokenId, &ownerSdkAddr)
113+
if err != nil {
114+
return cosmoserr.Wrap(err, "failed to get token info")
115+
}
116+
token.CollectionName = collection.Collection.Name
117+
118+
err = sm.tokenMap.Set(ctx, collections.Join(contractSdkAddr, event.TokenId), *token)
119+
if err != nil {
120+
return cosmoserr.Wrap(err, "failed to set token")
121+
}
122+
123+
err = sm.tokenOwnerMap.Set(ctx, collections.Join3(event.To, contractSdkAddr, event.TokenId), true)
124+
if err != nil {
125+
sm.Logger(ctx).Error("failed to insert into tokenOwnerSet", "event", event, "error", err)
126+
return cosmoserr.Wrap(err, "failed to insert into tokenOwnerSet")
127+
}
128+
129+
sm.Logger(ctx).Warn("nft minted", "collection", collection, "token", token)
130+
return nil
131+
}
132+
133+
func (sm EvmNFTSubmodule) handlerTransferEvent(ctx context.Context, event *types.ParsedTransfer) (err error) {
134+
sm.Logger(ctx).Info("sent/transferred", "event", event)
135+
contractSdkAddr := getCosmosAddress(event.Address)
136+
137+
tpk := collections.Join[sdk.AccAddress, string](contractSdkAddr, event.TokenId)
138+
139+
token, err := sm.tokenMap.Get(ctx, tpk)
140+
if err != nil {
141+
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)
142+
return cosmoserr.Wrap(err, "failed to get nft from tokenMap")
143+
}
144+
token.OwnerAddr = event.To.String()
145+
146+
if err = sm.tokenMap.Set(ctx, tpk, token); err != nil {
147+
return errors.New("failed to delete nft from sender's collection")
148+
}
149+
150+
err = sm.applyCollectionOwnerMap(ctx, tpk.K1(), event.From, false)
151+
if err != nil {
152+
return errors.New("failed to decrease collection count from prev owner")
153+
154+
}
155+
err = sm.applyCollectionOwnerMap(ctx, tpk.K1(), event.To, true)
156+
if err != nil {
157+
return errors.New("failed to increase collection count from new owner")
158+
}
159+
160+
err = sm.tokenOwnerMap.Remove(ctx, collections.Join3(event.From, tpk.K1(), tpk.K2()))
161+
if err != nil {
162+
sm.Logger(ctx).Error("failed to remove from tokenOwnerSet", "to", event.To, "collection-addr", tpk.K1(), "token-id", tpk.K2(), "error", err)
163+
return errors.New("failed to insert token into tokenOwnerSet")
164+
}
165+
err = sm.tokenOwnerMap.Set(ctx, collections.Join3(event.To, tpk.K1(), tpk.K2()), true)
166+
if err != nil {
167+
sm.Logger(ctx).Error("failed to insert into tokenOwnerSet", "to", event.To, "collection-addr", tpk.K1(), "token-id", tpk.K2(), "error", err)
168+
return errors.New("failed to insert token into tokenOwnerSet")
169+
}
170+
171+
sm.Logger(ctx).Info("nft sent/transferred", "objectKey", tpk, "token", token, "prevOwner", event.From, "newOwner", event.To)
172+
return nil
173+
}
174+
175+
func (sm EvmNFTSubmodule) handleBurnEvent(ctx context.Context, event *types.ParsedTransfer) error {
176+
sm.Logger(ctx).Info("burnt", "event", event)
177+
contractSdkAddr := getCosmosAddress(event.Address)
178+
179+
// remove from tokensOwnersMap
180+
tpk := collections.Join[sdk.AccAddress, string](contractSdkAddr, event.TokenId)
181+
token, err := sm.tokenMap.Get(ctx, tpk)
182+
if err != nil {
183+
return cosmoserr.Wrap(err, "failed to get nft from tokenMap")
184+
}
185+
186+
err = sm.tokenMap.Remove(ctx, tpk)
187+
if err != nil {
188+
return cosmoserr.Wrap(err, "failed to delete nft from tokenMap")
189+
}
190+
191+
ownerSdkAddr, err := getCosmosAddressFromString(sm.ac, token.OwnerAddr)
192+
if err != nil {
193+
return cosmoserr.Wrap(err, "failed to get owner address from token")
194+
}
195+
196+
err = sm.tokenOwnerMap.Remove(ctx, collections.Join3(ownerSdkAddr, tpk.K1(), tpk.K2()))
197+
if err != nil {
198+
sm.Logger(ctx).Error("failed to remove from tokenOwnerSet", "owner", ownerSdkAddr, "collection-addr", tpk.K1(), "token-id", tpk.K2(), "error", err)
199+
return cosmoserr.Wrap(err, "failed to insert token into tokenOwnerSet")
200+
}
201+
collection, err := sm.collectionMap.Get(ctx, contractSdkAddr)
202+
if err != nil {
203+
return cosmoserr.Wrap(err, "failed to get collection from collectionMap")
204+
}
205+
if err := collection.AdjustLength(-1); err != nil {
206+
return cosmoserr.Wrap(err, "failed to adjust collection length")
207+
}
208+
err = sm.collectionMap.Set(ctx, contractSdkAddr, collection)
209+
if err != nil {
210+
return cosmoserr.Wrap(err, "failed to set collection")
211+
}
212+
213+
err = sm.applyCollectionOwnerMap(ctx, contractSdkAddr, ownerSdkAddr, false)
214+
if err != nil {
215+
return err // just return err, no wrap
216+
}
217+
218+
sm.Logger(ctx).Info("nft burnt", "event", event)
219+
220+
return nil
221+
}
222+
223+
func (sm EvmNFTSubmodule) applyCollectionOwnerMap(ctx context.Context, collectionAddr, ownerAddr sdk.AccAddress, isIncrease bool) error {
224+
count, err := sm.collectionOwnerMap.Get(ctx, collections.Join(ownerAddr, collectionAddr))
225+
if err != nil {
226+
if !isIncrease || (isIncrease && !cosmoserr.IsOf(err, collections.ErrNotFound)) {
227+
return cosmoserr.Wrap(err, "failed to get collection count from collectionOwnersMap")
228+
}
229+
}
230+
if isIncrease {
231+
count++
232+
} else {
233+
count--
234+
}
235+
236+
if count == 0 {
237+
err = sm.collectionOwnerMap.Remove(ctx, collections.Join(ownerAddr, collectionAddr))
238+
} else {
239+
err = sm.collectionOwnerMap.Set(ctx, collections.Join(ownerAddr, collectionAddr), count)
240+
}
241+
if err != nil {
242+
return cosmoserr.Wrap(err, "failed to update collection count in collectionOwnersMap")
243+
}
244+
return nil
245+
}

0 commit comments

Comments
 (0)