Skip to content

Commit bf789f3

Browse files
authored
Assign owner correctly when there are multiple transfers (#1956)
1 parent 9432bee commit bf789f3

File tree

2 files changed

+118
-26
lines changed

2 files changed

+118
-26
lines changed

app/receipt.go

+49-26
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ type AllowanceResponse struct {
3232
Expires json.RawMessage `json:"expires"`
3333
}
3434

35+
func getOwnerEventKey(contractAddr string, tokenID string) string {
36+
return fmt.Sprintf("%s-%s", contractAddr, tokenID)
37+
}
38+
3539
func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.Tx, checksum [32]byte, response sdk.DeliverTxHookInput) {
3640
// hooks will only be called if DeliverTx is successful
3741
wasmEvents := GetEventsOfType(response, wasmtypes.WasmModuleEventType)
@@ -43,6 +47,26 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
4347
// additional gas consumption from EVM receipt generation and event translation
4448
wasmToEvmEventGasLimit := app.EvmKeeper.GetDeliverTxHookWasmGasLimit(ctx.WithGasMeter(sdk.NewInfiniteGasMeter(1, 1)))
4549
wasmToEvmEventCtx := ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, wasmToEvmEventGasLimit))
50+
// unfortunately CW721 transfer events differ from ERC721 transfer events
51+
// in that CW721 include sender (which can be different than owner) whereas
52+
// ERC721 always include owner. The following logic refer to the owner
53+
// event emitted before the transfer and use that instead to populate the
54+
// synthetic ERC721 event.
55+
ownerEvents := GetEventsOfType(response, wasmtypes.EventTypeCW721PreTransferOwner)
56+
ownerEventsMap := map[string][]abci.Event{}
57+
for _, ownerEvent := range ownerEvents {
58+
if len(ownerEvent.Attributes) != 3 {
59+
ctx.Logger().Error("received owner event with number of attributes != 3")
60+
continue
61+
}
62+
ownerEventKey := getOwnerEventKey(string(ownerEvent.Attributes[0].Value), string(ownerEvent.Attributes[1].Value))
63+
if events, ok := ownerEventsMap[ownerEventKey]; ok {
64+
ownerEventsMap[ownerEventKey] = append(events, ownerEvent)
65+
} else {
66+
ownerEventsMap[ownerEventKey] = []abci.Event{ownerEvent}
67+
}
68+
}
69+
cw721TransferCounterMap := map[string]int{}
4670
for _, wasmEvent := range wasmEvents {
4771
contractAddr, found := GetAttributeValue(wasmEvent, wasmtypes.AttributeKeyContractAddr)
4872
if !found {
@@ -60,7 +84,7 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
6084
// check if there is a ERC721 pointer to contract Addr
6185
pointerAddr, _, exists = app.EvmKeeper.GetERC721CW721Pointer(wasmToEvmEventCtx, contractAddr)
6286
if exists {
63-
log, eligible := app.translateCW721Event(wasmToEvmEventCtx, wasmEvent, pointerAddr, contractAddr, response)
87+
log, eligible := app.translateCW721Event(wasmToEvmEventCtx, wasmEvent, pointerAddr, contractAddr, ownerEventsMap, cw721TransferCounterMap)
6488
if eligible {
6589
log.Index = uint(len(logs))
6690
logs = append(logs, log)
@@ -173,7 +197,8 @@ func (app *App) translateCW20Event(ctx sdk.Context, wasmEvent abci.Event, pointe
173197
return nil, false
174198
}
175199

176-
func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string, response sdk.DeliverTxHookInput) (*ethtypes.Log, bool) {
200+
func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string,
201+
ownerEventsMap map[string][]abci.Event, cw721TransferCounterMap map[string]int) (*ethtypes.Log, bool) {
177202
action, found := GetAttributeValue(wasmEvent, "action")
178203
if !found {
179204
return nil, false
@@ -184,32 +209,30 @@ func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, point
184209
tokenID := GetTokenIDAttribute(wasmEvent)
185210
if tokenID == nil {
186211
return nil, false
212+
} else {
213+
ctx.Logger().Error("Translate CW721 error: null token ID")
187214
}
188-
sender := common.Hash{}
189-
// unfortunately CW721 transfer events differ from ERC721 transfer events
190-
// in that CW721 include sender (which can be different than owner) whereas
191-
// ERC721 always include owner. The following logic refer to the owner
192-
// event emitted before the transfer and use that instead to populate the
193-
// synthetic ERC721 event.
194-
ownerEvents := GetEventsOfType(response, wasmtypes.EventTypeCW721PreTransferOwner)
195-
for _, ownerEvent := range ownerEvents {
196-
if len(ownerEvent.Attributes) != 3 ||
197-
string(ownerEvent.Attributes[0].Key) != wasmtypes.AttributeKeyContractAddr ||
198-
string(ownerEvent.Attributes[0].Value) != contractAddr {
199-
continue
200-
}
201-
tokenIDStr, _ := GetAttributeValue(wasmEvent, "token_id")
202-
if string(ownerEvent.Attributes[1].Key) != wasmtypes.AttributeKeyTokenId ||
203-
string(ownerEvent.Attributes[1].Value) != tokenIDStr ||
204-
string(ownerEvent.Attributes[2].Key) != wasmtypes.AttributeKeyOwner {
205-
continue
206-
}
207-
ownerAcc, err := sdk.AccAddressFromBech32(string(ownerEvent.Attributes[2].Value))
208-
if err != nil {
209-
continue
215+
sender := app.GetEvmAddressAttribute(ctx, wasmEvent, "sender")
216+
ownerEventKey := getOwnerEventKey(contractAddr, tokenID.String())
217+
var currentCounter int
218+
if c, ok := cw721TransferCounterMap[ownerEventKey]; ok {
219+
currentCounter = c
220+
}
221+
cw721TransferCounterMap[ownerEventKey] = currentCounter + 1
222+
if ownerEvents, ok := ownerEventsMap[ownerEventKey]; ok {
223+
if len(ownerEvents) > currentCounter {
224+
ownerSeiAddrStr := string(ownerEvents[currentCounter].Attributes[2].Value)
225+
if ownerSeiAddr, err := sdk.AccAddressFromBech32(ownerSeiAddrStr); err == nil {
226+
ownerEvmAddr := app.EvmKeeper.GetEVMAddressOrDefault(ctx, ownerSeiAddr)
227+
sender = common.BytesToHash(ownerEvmAddr[:])
228+
} else {
229+
ctx.Logger().Error("Translate CW721 error: invalid bech32 owner", "error", err, "address", ownerSeiAddrStr)
230+
}
231+
} else {
232+
ctx.Logger().Error("Translate CW721 error: insufficient owner events", "key", ownerEventKey, "counter", currentCounter, "events", len(ownerEvents))
210233
}
211-
owner := app.EvmKeeper.GetEVMAddressOrDefault(ctx, ownerAcc)
212-
sender = common.BytesToHash(owner[:])
234+
} else {
235+
ctx.Logger().Error("Translate CW721 error: owner event not found", "key", ownerEventKey)
213236
}
214237
topics = []common.Hash{
215238
ERC721TransferTopic,

app/receipt_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,41 @@ func TestEvmEventsForCw721(t *testing.T) {
372372
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
373373
require.Equal(t, uint32(0), res.Code)
374374

375+
// acct2 transfer on behalf of acct1 to acct2, acct2 approve acct1, acct1 transfer on behalf of acct2 to acct1
376+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
377+
msg1 := &wasmtypes.MsgExecuteContract{
378+
Sender: recipient.String(),
379+
Contract: contractAddr.String(),
380+
Msg: []byte(fmt.Sprintf("{\"transfer_nft\":{\"token_id\":\"2\",\"recipient\":\"%s\"}}", recipient.String())),
381+
}
382+
msg2 := &wasmtypes.MsgExecuteContract{
383+
Sender: recipient.String(),
384+
Contract: contractAddr.String(),
385+
Msg: []byte(fmt.Sprintf("{\"approve_all\":{\"operator\":\"%s\"}}", creator.String())),
386+
}
387+
msg3 := &wasmtypes.MsgExecuteContract{
388+
Sender: creator.String(),
389+
Contract: contractAddr.String(),
390+
Msg: []byte(fmt.Sprintf("{\"transfer_nft\":{\"token_id\":\"2\",\"recipient\":\"%s\"}}", creator.String())),
391+
}
392+
txBuilder.SetMsgs(msg1, msg2, msg3)
393+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
394+
txBuilder.SetGasLimit(1000000)
395+
tx = signTxMultiple(txBuilder, []cryptotypes.PrivKey{privKeyRecipient, privKey}, []authtypes.AccountI{k.AccountKeeper().GetAccount(ctx, recipient), k.AccountKeeper().GetAccount(ctx, creator)})
396+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
397+
require.Nil(t, err)
398+
sum = sha256.Sum256(txbz)
399+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
400+
require.Equal(t, uint32(0), res.Code)
401+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
402+
require.Nil(t, err)
403+
require.Equal(t, 3, len(receipt.Logs))
404+
// make sure that the owner is set correctly to the creator, not the spender.
405+
require.Equal(t, common.BytesToHash(creatorEvmAddr[:]).Hex(), receipt.Logs[0].Topics[1])
406+
// the second log is the approval log, which doesn't affect ownership thus not checking here.
407+
recipientEvmAddr := testkeeper.EVMTestApp.EvmKeeper.GetEVMAddressOrDefault(ctx, recipient)
408+
require.Equal(t, common.BytesToHash(recipientEvmAddr[:]).Hex(), receipt.Logs[2].Topics[1])
409+
375410
// burn on behalf
376411
payload = []byte("{\"burn\":{\"token_id\":\"2\"}}")
377412
msg = &wasmtypes.MsgExecuteContract{
@@ -460,3 +495,37 @@ func signTx(txBuilder client.TxBuilder, privKey cryptotypes.PrivKey, acc authtyp
460495
_ = txBuilder.SetSignatures(sigsV2...)
461496
return txBuilder.GetTx()
462497
}
498+
499+
func signTxMultiple(txBuilder client.TxBuilder, privKeys []cryptotypes.PrivKey, accs []authtypes.AccountI) sdk.Tx {
500+
var sigsV2 []signing.SignatureV2
501+
for i, privKey := range privKeys {
502+
sigsV2 = append(sigsV2, signing.SignatureV2{
503+
PubKey: privKey.PubKey(),
504+
Data: &signing.SingleSignatureData{
505+
SignMode: testkeeper.EVMTestApp.GetTxConfig().SignModeHandler().DefaultMode(),
506+
Signature: nil,
507+
},
508+
Sequence: accs[i].GetSequence(),
509+
})
510+
}
511+
_ = txBuilder.SetSignatures(sigsV2...)
512+
sigsV2 = []signing.SignatureV2{}
513+
for i, privKey := range privKeys {
514+
signerData := xauthsigning.SignerData{
515+
ChainID: "sei-test",
516+
AccountNumber: accs[i].GetAccountNumber(),
517+
Sequence: accs[i].GetSequence(),
518+
}
519+
sigV2, _ := clienttx.SignWithPrivKey(
520+
testkeeper.EVMTestApp.GetTxConfig().SignModeHandler().DefaultMode(),
521+
signerData,
522+
txBuilder,
523+
privKey,
524+
testkeeper.EVMTestApp.GetTxConfig(),
525+
accs[i].GetSequence(),
526+
)
527+
sigsV2 = append(sigsV2, sigV2)
528+
}
529+
_ = txBuilder.SetSignatures(sigsV2...)
530+
return txBuilder.GetTx()
531+
}

0 commit comments

Comments
 (0)