diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 47d94b4..e4f3c57 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "encoding/json" "fmt" "math" "path/filepath" @@ -371,6 +372,7 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller // prepare querier querier := k.newQueryHandler(ctx, contractAddress) gas := k.runtimeGasForContract(ctx) + k.emitCW721OwnerBeforeTransferIfApplicable(ctx, contractAddress, msg) res, gasUsed, execErr := k.wasmVM.Execute(codeInfo.CodeHash, env, info, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization) k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { @@ -1072,6 +1074,53 @@ func (k Keeper) newQueryHandler(ctx sdk.Context, contractAddress sdk.AccAddress) return NewQueryHandler(ctx, k.wasmVMQueryHandler, contractAddress, k.gasRegister) } +func (k Keeper) emitCW721OwnerBeforeTransferIfApplicable(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) { + parsedMsg := map[string]json.RawMessage{} + if err := json.Unmarshal(msg, &parsedMsg); err != nil { + return + } + subMsg, ok := parsedMsg["transfer_nft"] + if !ok { + subMsg, ok = parsedMsg["send_nft"] + } + if !ok { + subMsg, ok = parsedMsg["burn"] + } + if !ok { + return + } + parsedSubMsg := map[string]json.RawMessage{} + if err := json.Unmarshal(subMsg, &parsedSubMsg); err != nil { + return + } + if tokenBz, ok := parsedSubMsg["token_id"]; ok && len(tokenBz) > 2 { + tokenID := string(tokenBz[1 : len(tokenBz)-1]) + ownerQuery := map[string]interface{}{"owner_of": map[string]string{ + "token_id": tokenID, + }} + ownerQueryBz, err := json.Marshal(ownerQuery) + if err != nil { + return + } + resBz, err := k.QuerySmart(ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)), contractAddress, ownerQueryBz) + if err != nil { + return + } + res := map[string]json.RawMessage{} + if err := json.Unmarshal(resBz, &res); err != nil { + return + } + if ownerBz, ok := res["owner"]; ok && len(ownerBz) > 2 { + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypeCW721PreTransferOwner, + sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), + sdk.NewAttribute(types.AttributeKeyTokenId, tokenID), + sdk.NewAttribute(types.AttributeKeyOwner, string(ownerBz[1:len(ownerBz)-1])), + )) + } + } +} + // MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier type MultipliedGasMeter struct { originalMeter sdk.GasMeter diff --git a/x/wasm/types/events.go b/x/wasm/types/events.go index 0c32476..2bdd6a2 100644 --- a/x/wasm/types/events.go +++ b/x/wasm/types/events.go @@ -6,15 +6,16 @@ const ( // CustomContractEventPrefix contracts can create custom events. To not mix them with other system events they got the `wasm-` prefix. CustomContractEventPrefix = "wasm-" - EventTypeStoreCode = "store_code" - EventTypeInstantiate = "instantiate" - EventTypeExecute = "execute" - EventTypeMigrate = "migrate" - EventTypePinCode = "pin_code" - EventTypeUnpinCode = "unpin_code" - EventTypeSudo = "sudo" - EventTypeReply = "reply" - EventTypeGovContractResult = "gov_contract_result" + EventTypeStoreCode = "store_code" + EventTypeInstantiate = "instantiate" + EventTypeExecute = "execute" + EventTypeMigrate = "migrate" + EventTypePinCode = "pin_code" + EventTypeUnpinCode = "unpin_code" + EventTypeSudo = "sudo" + EventTypeReply = "reply" + EventTypeGovContractResult = "gov_contract_result" + EventTypeCW721PreTransferOwner = "cw721_pretransfer_owner" ) // event attributes returned from contract execution @@ -25,4 +26,6 @@ const ( AttributeKeyCodeID = "code_id" AttributeKeyResultDataHex = "result" AttributeKeyFeature = "feature" + AttributeKeyTokenId = "token_id" + AttributeKeyOwner = "owner" )