Skip to content

Commit 1c32f92

Browse files
authored
Translate CW721 events to ERC721 events (#1750)
* Use transient store for EVM deferred info * fix tests * hardhat tests * Translate CW721 events to ERC721 events * tests * rebase
1 parent b3204f9 commit 1c32f92

12 files changed

+475
-65
lines changed

app/receipt.go

+121-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ const ShellEVMTxType = math.MaxUint32
2121

2222
var ERC20ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")
2323
var ERC20TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
24+
var ERC721TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
25+
var ERC721ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")
26+
var ERC721ApproveAllTopic = common.HexToHash("0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31")
2427
var EmptyHash = common.HexToHash("0x0")
28+
var TrueHash = common.HexToHash("0x1")
2529

2630
type AllowanceResponse struct {
2731
Allowance sdk.Int `json:"allowance"`
@@ -49,6 +53,16 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
4953
}
5054
continue
5155
}
56+
// check if there is a ERC721 pointer to contract Addr
57+
pointerAddr, _, exists = app.EvmKeeper.GetERC721CW721Pointer(ctx, contractAddr)
58+
if exists {
59+
log, eligible := app.translateCW721Event(ctx, wasmEvent, pointerAddr, contractAddr)
60+
if eligible {
61+
log.Index = uint(len(logs))
62+
logs = append(logs, log)
63+
}
64+
continue
65+
}
5266
}
5367
if len(logs) == 0 {
5468
return
@@ -59,7 +73,7 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
5973
}
6074
var bloom ethtypes.Bloom
6175
if r, err := app.EvmKeeper.GetTransientReceipt(ctx, txHash); err == nil && r != nil {
62-
r.Logs = append(r.Logs, utils.Map(logs, evmkeeper.ConvertEthLog)...)
76+
r.Logs = append(r.Logs, utils.Map(logs, evmkeeper.ConvertSyntheticEthLog)...)
6377
bloom = ethtypes.CreateBloom(ethtypes.Receipts{&ethtypes.Receipt{Logs: evmkeeper.GetLogsForTx(r)}})
6478
r.LogsBloom = bloom[:]
6579
_ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, r)
@@ -71,7 +85,7 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
7185
GasUsed: ctx.GasMeter().GasConsumed(),
7286
BlockNumber: uint64(ctx.BlockHeight()),
7387
TransactionIndex: uint32(ctx.TxIndex()),
74-
Logs: utils.Map(logs, evmkeeper.ConvertEthLog),
88+
Logs: utils.Map(logs, evmkeeper.ConvertSyntheticEthLog),
7589
LogsBloom: bloom[:],
7690
Status: uint32(ethtypes.ReceiptStatusSuccessful), // we don't create shell receipt for failed Cosmos tx since there is no event anyway
7791
}
@@ -146,6 +160,99 @@ func (app *App) translateCW20Event(ctx sdk.Context, wasmEvent abci.Event, pointe
146160
return nil, false
147161
}
148162

163+
func (app *App) translateCW721Event(ctx sdk.Context, wasmEvent abci.Event, pointerAddr common.Address, contractAddr string) (*ethtypes.Log, bool) {
164+
action, found := GetAttributeValue(wasmEvent, "action")
165+
if !found {
166+
return nil, false
167+
}
168+
var topics []common.Hash
169+
switch action {
170+
case "transfer_nft", "send_nft", "burn":
171+
topics = []common.Hash{
172+
ERC721TransferTopic,
173+
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
174+
app.GetEvmAddressAttribute(ctx, wasmEvent, "recipient"),
175+
}
176+
tokenID := GetTokenIDAttribute(wasmEvent)
177+
if tokenID == nil {
178+
return nil, false
179+
}
180+
return &ethtypes.Log{
181+
Address: pointerAddr,
182+
Topics: topics,
183+
Data: common.BigToHash(tokenID).Bytes(),
184+
}, true
185+
case "mint":
186+
topics = []common.Hash{
187+
ERC721TransferTopic,
188+
EmptyHash,
189+
app.GetEvmAddressAttribute(ctx, wasmEvent, "owner"),
190+
}
191+
tokenID := GetTokenIDAttribute(wasmEvent)
192+
if tokenID == nil {
193+
return nil, false
194+
}
195+
return &ethtypes.Log{
196+
Address: pointerAddr,
197+
Topics: topics,
198+
Data: common.BigToHash(tokenID).Bytes(),
199+
}, true
200+
case "approve":
201+
topics = []common.Hash{
202+
ERC721ApprovalTopic,
203+
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
204+
app.GetEvmAddressAttribute(ctx, wasmEvent, "spender"),
205+
}
206+
tokenID := GetTokenIDAttribute(wasmEvent)
207+
if tokenID == nil {
208+
return nil, false
209+
}
210+
return &ethtypes.Log{
211+
Address: pointerAddr,
212+
Topics: topics,
213+
Data: common.BigToHash(tokenID).Bytes(),
214+
}, true
215+
case "revoke":
216+
topics = []common.Hash{
217+
ERC721ApprovalTopic,
218+
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
219+
EmptyHash,
220+
}
221+
tokenID := GetTokenIDAttribute(wasmEvent)
222+
if tokenID == nil {
223+
return nil, false
224+
}
225+
return &ethtypes.Log{
226+
Address: pointerAddr,
227+
Topics: topics,
228+
Data: common.BigToHash(tokenID).Bytes(),
229+
}, true
230+
case "approve_all":
231+
topics = []common.Hash{
232+
ERC721ApproveAllTopic,
233+
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
234+
app.GetEvmAddressAttribute(ctx, wasmEvent, "operator"),
235+
}
236+
return &ethtypes.Log{
237+
Address: pointerAddr,
238+
Topics: topics,
239+
Data: TrueHash.Bytes(),
240+
}, true
241+
case "revoke_all":
242+
topics = []common.Hash{
243+
ERC721ApproveAllTopic,
244+
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
245+
app.GetEvmAddressAttribute(ctx, wasmEvent, "operator"),
246+
}
247+
return &ethtypes.Log{
248+
Address: pointerAddr,
249+
Topics: topics,
250+
Data: EmptyHash.Bytes(),
251+
}, true
252+
}
253+
return nil, false
254+
}
255+
149256
func (app *App) GetEvmAddressAttribute(ctx sdk.Context, event abci.Event, attribute string) common.Hash {
150257
addrStr, found := GetAttributeValue(event, attribute)
151258
if found {
@@ -186,3 +293,15 @@ func GetAmountAttribute(event abci.Event) (*big.Int, bool) {
186293
}
187294
return nil, false
188295
}
296+
297+
func GetTokenIDAttribute(event abci.Event) *big.Int {
298+
tokenID, found := GetAttributeValue(event, "token_id")
299+
if !found {
300+
return nil
301+
}
302+
tokenIDInt, ok := sdk.NewIntFromString(tokenID)
303+
if !ok {
304+
return nil
305+
}
306+
return tokenIDInt.BigInt()
307+
}

app/receipt_test.go

+220
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,226 @@ func TestEvmEventsForCw20(t *testing.T) {
143143
require.Equal(t, common.HexToHash("0x64").Bytes(), receipt.Logs[0].Data)
144144
}
145145

146+
func TestEvmEventsForCw721(t *testing.T) {
147+
k := testkeeper.EVMTestApp.EvmKeeper
148+
wasmKeeper := k.WasmKeeper()
149+
ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()).WithChainID("sei-test").WithBlockHeight(1)
150+
code, err := os.ReadFile("../contracts/wasm/cw721_base.wasm")
151+
require.Nil(t, err)
152+
privKey := testkeeper.MockPrivateKey()
153+
creator, _ := testkeeper.PrivateKeyToAddresses(privKey)
154+
codeID, err := wasmKeeper.Create(ctx, creator, code, nil)
155+
require.Nil(t, err)
156+
contractAddr, _, err := wasmKeeper.Instantiate(ctx, codeID, creator, creator, []byte(fmt.Sprintf("{\"name\":\"test\",\"symbol\":\"test\",\"minter\":\"%s\"}", creator.String())), "test", sdk.NewCoins())
157+
require.Nil(t, err)
158+
159+
_, mockPointerAddr := testkeeper.MockAddressPair()
160+
k.SetERC721CW721Pointer(ctx, contractAddr.String(), mockPointerAddr)
161+
162+
// calling CW contract directly
163+
amt := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000000000)))
164+
k.BankKeeper().MintCoins(ctx, "evm", amt)
165+
k.BankKeeper().SendCoinsFromModuleToAccount(ctx, "evm", creator, amt)
166+
recipient, _ := testkeeper.MockAddressPair()
167+
payload := []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"1\",\"owner\":\"%s\"}}", recipient.String()))
168+
msg := &wasmtypes.MsgExecuteContract{
169+
Sender: creator.String(),
170+
Contract: contractAddr.String(),
171+
Msg: payload,
172+
}
173+
txBuilder := testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
174+
txBuilder.SetMsgs(msg)
175+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
176+
txBuilder.SetGasLimit(300000)
177+
tx := signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator))
178+
txbz, err := testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
179+
require.Nil(t, err)
180+
sum := sha256.Sum256(txbz)
181+
res := testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
182+
require.Equal(t, uint32(0), res.Code)
183+
receipt, err := testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
184+
require.Nil(t, err)
185+
require.Equal(t, 1, len(receipt.Logs))
186+
require.NotEmpty(t, receipt.LogsBloom)
187+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
188+
_, found := testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
189+
require.True(t, found)
190+
191+
// calling from wasmd precompile
192+
abi, err := wasmd.GetABI()
193+
require.Nil(t, err)
194+
emptyCoins, err := sdk.NewCoins().MarshalJSON()
195+
require.Nil(t, err)
196+
payload = []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"2\",\"owner\":\"%s\"}}", creator.String()))
197+
data, err := abi.Pack("execute", contractAddr.String(), payload, emptyCoins)
198+
require.Nil(t, err)
199+
wasmAddr := common.HexToAddress(wasmd.WasmdAddress)
200+
txData := ethtypes.LegacyTx{
201+
Nonce: 0,
202+
GasPrice: big.NewInt(1000000000),
203+
Gas: 1000000,
204+
To: &wasmAddr,
205+
Data: data,
206+
}
207+
chainID := k.ChainID(ctx)
208+
chainCfg := evmtypes.DefaultChainConfig()
209+
ethCfg := chainCfg.EthereumConfig(chainID)
210+
blockNum := big.NewInt(ctx.BlockHeight())
211+
signer := ethtypes.MakeSigner(ethCfg, blockNum, uint64(ctx.BlockTime().Unix()))
212+
testPrivHex := hex.EncodeToString(privKey.Bytes())
213+
key, _ := crypto.HexToECDSA(testPrivHex)
214+
signedTx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key)
215+
require.Nil(t, err)
216+
typedTx, err := ethtx.NewLegacyTx(signedTx)
217+
require.Nil(t, err)
218+
emsg, err := evmtypes.NewMsgEVMTransaction(typedTx)
219+
require.Nil(t, err)
220+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
221+
txBuilder.SetMsgs(emsg)
222+
tx = txBuilder.GetTx()
223+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
224+
require.Nil(t, err)
225+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()).WithTxIndex(1), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
226+
require.Equal(t, uint32(0), res.Code)
227+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, signedTx.Hash())
228+
require.Nil(t, err)
229+
require.Equal(t, 1, len(receipt.Logs))
230+
require.NotEmpty(t, receipt.LogsBloom)
231+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
232+
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
233+
require.True(t, found)
234+
235+
// test approval message
236+
payload = []byte(fmt.Sprintf("{\"approve\":{\"spender\":\"%s\",\"token_id\":\"2\"}}", recipient.String()))
237+
msg = &wasmtypes.MsgExecuteContract{
238+
Sender: creator.String(),
239+
Contract: contractAddr.String(),
240+
Msg: payload,
241+
}
242+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
243+
txBuilder.SetMsgs(msg)
244+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
245+
txBuilder.SetGasLimit(300000)
246+
tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator))
247+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
248+
require.Nil(t, err)
249+
sum = sha256.Sum256(txbz)
250+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
251+
require.Equal(t, uint32(0), res.Code)
252+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
253+
require.Nil(t, err)
254+
require.Equal(t, 1, len(receipt.Logs))
255+
require.NotEmpty(t, receipt.LogsBloom)
256+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
257+
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
258+
require.True(t, found)
259+
require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data)
260+
261+
// revoke
262+
payload = []byte(fmt.Sprintf("{\"revoke\":{\"spender\":\"%s\",\"token_id\":\"2\"}}", recipient.String()))
263+
msg = &wasmtypes.MsgExecuteContract{
264+
Sender: creator.String(),
265+
Contract: contractAddr.String(),
266+
Msg: payload,
267+
}
268+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
269+
txBuilder.SetMsgs(msg)
270+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
271+
txBuilder.SetGasLimit(300000)
272+
tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator))
273+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
274+
require.Nil(t, err)
275+
sum = sha256.Sum256(txbz)
276+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
277+
require.Equal(t, uint32(0), res.Code)
278+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
279+
require.Nil(t, err)
280+
require.Equal(t, 1, len(receipt.Logs))
281+
require.NotEmpty(t, receipt.LogsBloom)
282+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
283+
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
284+
require.True(t, found)
285+
require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data)
286+
287+
// approve all
288+
payload = []byte(fmt.Sprintf("{\"approve_all\":{\"operator\":\"%s\"}}", recipient.String()))
289+
msg = &wasmtypes.MsgExecuteContract{
290+
Sender: creator.String(),
291+
Contract: contractAddr.String(),
292+
Msg: payload,
293+
}
294+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
295+
txBuilder.SetMsgs(msg)
296+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
297+
txBuilder.SetGasLimit(300000)
298+
tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator))
299+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
300+
require.Nil(t, err)
301+
sum = sha256.Sum256(txbz)
302+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
303+
require.Equal(t, uint32(0), res.Code)
304+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
305+
require.Nil(t, err)
306+
require.Equal(t, 1, len(receipt.Logs))
307+
require.NotEmpty(t, receipt.LogsBloom)
308+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
309+
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
310+
require.True(t, found)
311+
require.Equal(t, common.HexToHash("0x1").Bytes(), receipt.Logs[0].Data)
312+
313+
// revoke all
314+
payload = []byte(fmt.Sprintf("{\"revoke_all\":{\"operator\":\"%s\"}}", recipient.String()))
315+
msg = &wasmtypes.MsgExecuteContract{
316+
Sender: creator.String(),
317+
Contract: contractAddr.String(),
318+
Msg: payload,
319+
}
320+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
321+
txBuilder.SetMsgs(msg)
322+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
323+
txBuilder.SetGasLimit(300000)
324+
tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator))
325+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
326+
require.Nil(t, err)
327+
sum = sha256.Sum256(txbz)
328+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
329+
require.Equal(t, uint32(0), res.Code)
330+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
331+
require.Nil(t, err)
332+
require.Equal(t, 1, len(receipt.Logs))
333+
require.NotEmpty(t, receipt.LogsBloom)
334+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
335+
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
336+
require.True(t, found)
337+
require.Equal(t, common.HexToHash("0x0").Bytes(), receipt.Logs[0].Data)
338+
339+
// burn
340+
payload = []byte("{\"burn\":{\"token_id\":\"2\"}}")
341+
msg = &wasmtypes.MsgExecuteContract{
342+
Sender: creator.String(),
343+
Contract: contractAddr.String(),
344+
Msg: payload,
345+
}
346+
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
347+
txBuilder.SetMsgs(msg)
348+
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000))))
349+
txBuilder.SetGasLimit(300000)
350+
tx = signTx(txBuilder, privKey, k.AccountKeeper().GetAccount(ctx, creator))
351+
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
352+
require.Nil(t, err)
353+
sum = sha256.Sum256(txbz)
354+
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
355+
require.Equal(t, uint32(0), res.Code)
356+
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, common.BytesToHash(sum[:]))
357+
require.Nil(t, err)
358+
require.Equal(t, 1, len(receipt.Logs))
359+
require.NotEmpty(t, receipt.LogsBloom)
360+
require.Equal(t, mockPointerAddr.Hex(), receipt.Logs[0].Address)
361+
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
362+
require.True(t, found)
363+
require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data)
364+
}
365+
146366
func signTx(txBuilder client.TxBuilder, privKey cryptotypes.PrivKey, acc authtypes.AccountI) sdk.Tx {
147367
var sigsV2 []signing.SignatureV2
148368
sigV2 := signing.SignatureV2{

0 commit comments

Comments
 (0)