From dddd24ba5830808b9fc7af810ebe3caa5ee5beb6 Mon Sep 17 00:00:00 2001 From: codchen Date: Wed, 20 Nov 2024 16:30:46 +0800 Subject: [PATCH] Use owner event to populate ERC721 transfer topic (#1932) * Use owner event to populate ERC721 transfer topic * more test * rebase and revert owner replacement --- app/receipt.go | 32 +++++++++++++++++-- app/receipt_test.go | 75 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/app/receipt.go b/app/receipt.go index 02a987c8e..f41ead37c 100644 --- a/app/receipt.go +++ b/app/receipt.go @@ -60,7 +60,7 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk. // check if there is a ERC721 pointer to contract Addr pointerAddr, _, exists = app.EvmKeeper.GetERC721CW721Pointer(wasmToEvmEventCtx, contractAddr) if exists { - log, eligible := app.translateCW721Event(wasmToEvmEventCtx, wasmEvent, pointerAddr, contractAddr) + log, eligible := app.translateCW721Event(wasmToEvmEventCtx, wasmEvent, pointerAddr, contractAddr, response) if eligible { log.Index = uint(len(logs)) logs = append(logs, log) @@ -173,7 +173,7 @@ func (app *App) translateCW20Event(ctx sdk.Context, wasmEvent abci.Event, pointe return nil, false } -func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string) (*ethtypes.Log, bool) { +func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string, response sdk.DeliverTxHookInput) (*ethtypes.Log, bool) { action, found := GetAttributeValue(wasmEvent, "action") if !found { return nil, false @@ -185,9 +185,35 @@ func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, point if tokenID == nil { return nil, false } + sender := common.Hash{} + // unfortunately CW721 transfer events differ from ERC721 transfer events + // in that CW721 include sender (which can be different than owner) whereas + // ERC721 always include owner. The following logic refer to the owner + // event emitted before the transfer and use that instead to populate the + // synthetic ERC721 event. + ownerEvents := GetEventsOfType(response, wasmtypes.EventTypeCW721PreTransferOwner) + for _, ownerEvent := range ownerEvents { + if len(ownerEvent.Attributes) != 3 || + string(ownerEvent.Attributes[0].Key) != wasmtypes.AttributeKeyContractAddr || + string(ownerEvent.Attributes[0].Value) != contractAddr { + continue + } + tokenIDStr, _ := GetAttributeValue(wasmEvent, "token_id") + if string(ownerEvent.Attributes[1].Key) != wasmtypes.AttributeKeyTokenId || + string(ownerEvent.Attributes[1].Value) != tokenIDStr || + string(ownerEvent.Attributes[2].Key) != wasmtypes.AttributeKeyOwner { + continue + } + ownerAcc, err := sdk.AccAddressFromBech32(string(ownerEvent.Attributes[2].Value)) + if err != nil { + continue + } + owner := app.EvmKeeper.GetEVMAddressOrDefault(ctx, ownerAcc) + sender = common.BytesToHash(owner[:]) + } topics = []common.Hash{ ERC721TransferTopic, - app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"), + sender, app.GetEvmAddressAttribute(ctx, wasmEvent, "recipient"), common.BigToHash(tokenID), } diff --git a/app/receipt_test.go b/app/receipt_test.go index bfdff291b..326dd6426 100644 --- a/app/receipt_test.go +++ b/app/receipt_test.go @@ -168,7 +168,8 @@ func TestEvmEventsForCw721(t *testing.T) { amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000000000))) k.BankKeeper().MintCoins(ctx, "evm", amt) k.BankKeeper().SendCoinsFromModuleToAccount(ctx, "evm", creator, amt) - recipient, _ := testkeeper.MockAddressPair() + privKeyRecipient := testkeeper.MockPrivateKey() + recipient, _ := testkeeper.PrivateKeyToAddresses(privKeyRecipient) payload := []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"1\",\"owner\":\"%s\"}}", recipient.String())) msg := &wasmtypes.MsgExecuteContract{ Sender: creator.String(), @@ -320,10 +321,12 @@ func TestEvmEventsForCw721(t *testing.T) { require.True(t, found) require.Equal(t, common.HexToHash("0x1").Bytes(), receipt.Logs[0].Data) - // revoke all - payload = []byte(fmt.Sprintf("{\"revoke_all\":{\"operator\":\"%s\"}}", recipient.String())) + // transfer on behalf + k.BankKeeper().MintCoins(ctx, "evm", amt) + k.BankKeeper().SendCoinsFromModuleToAccount(ctx, "evm", recipient, amt) + payload = []byte(fmt.Sprintf("{\"transfer_nft\":{\"token_id\":\"2\",\"recipient\":\"%s\"}}", recipient.String())) msg = &wasmtypes.MsgExecuteContract{ - Sender: creator.String(), + Sender: recipient.String(), Contract: contractAddr.String(), Msg: payload, } @@ -331,7 +334,7 @@ func TestEvmEventsForCw721(t *testing.T) { txBuilder.SetMsgs(msg) txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) txBuilder.SetGasLimit(300000) - tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator)) + tx = signTx(txBuilder, privKeyRecipient, k.AccountKeeper().GetAccount(ctx, recipient)) txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) require.Nil(t, err) sum = sha256.Sum256(txbz) @@ -342,12 +345,66 @@ func TestEvmEventsForCw721(t *testing.T) { require.Equal(t, 1, len(receipt.Logs)) require.NotEmpty(t, receipt.LogsBloom) require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) - _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) - require.True(t, found) + require.Equal(t, uint32(0), receipt.Logs[0].Index) + ownerHash := receipt.Logs[0].Topics[1] + // make sure that the owner is set correctly to the creator, not the spender. + creatorEvmAddr := testkeeper.EVMTestApp.EvmKeeper.GetEVMAddressOrDefault(ctx, creator) + require.Equal(t, common.BytesToHash(creatorEvmAddr[:]).Hex(), ownerHash) + tokenIdHash = receipt.Logs[0].Topics[3] + require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000002", tokenIdHash) require.Equal(t, common.HexToHash("0x0").Bytes(), receipt.Logs[0].Data) - // burn + // transfer back + payload = []byte(fmt.Sprintf("{\"transfer_nft\":{\"token_id\":\"2\",\"recipient\":\"%s\"}}", creator.String())) + msg = &wasmtypes.MsgExecuteContract{ + Sender: recipient.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKeyRecipient, k.AccountKeeper().GetAccount(ctx, recipient)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + + // burn on behalf payload = []byte("{\"burn\":{\"token_id\":\"2\"}}") + msg = &wasmtypes.MsgExecuteContract{ + Sender: recipient.String(), + Contract: contractAddr.String(), + Msg: payload, + } + txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)))) + txBuilder.SetGasLimit(300000) + tx = signTx(txBuilder, privKeyRecipient, k.AccountKeeper().GetAccount(ctx, recipient)) + txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx) + require.Nil(t, err) + sum = sha256.Sum256(txbz) + res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum) + require.Equal(t, uint32(0), res.Code) + receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:])) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.NotEmpty(t, receipt.LogsBloom) + require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) + require.Equal(t, uint32(0), receipt.Logs[0].Index) + ownerHash = receipt.Logs[0].Topics[1] + // make sure that the owner is set correctly to the creator, not the spender. + creatorEvmAddr = testkeeper.EVMTestApp.EvmKeeper.GetEVMAddressOrDefault(ctx, creator) + require.Equal(t, common.BytesToHash(creatorEvmAddr[:]).Hex(), ownerHash) + tokenIdHash = receipt.Logs[0].Topics[3] + require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000002", tokenIdHash) + require.Equal(t, common.HexToHash("0x0").Bytes(), receipt.Logs[0].Data) + + // revoke all + payload = []byte(fmt.Sprintf("{\"revoke_all\":{\"operator\":\"%s\"}}", recipient.String())) msg = &wasmtypes.MsgExecuteContract{ Sender: creator.String(), Contract: contractAddr.String(), @@ -370,8 +427,6 @@ func TestEvmEventsForCw721(t *testing.T) { require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address) _, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx) require.True(t, found) - tokenIdHash = receipt.Logs[0].Topics[3] - require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000002", tokenIdHash) require.Equal(t, common.HexToHash("0x0").Bytes(), receipt.Logs[0].Data) }