Skip to content

Commit

Permalink
Translate CW721 events to ERC721 events (#1750)
Browse files Browse the repository at this point in the history
* Use transient store for EVM deferred info

* fix tests

* hardhat tests

* Translate CW721 events to ERC721 events

* tests

* rebase
  • Loading branch information
codchen authored Jul 9, 2024
1 parent b3204f9 commit 1c32f92
Show file tree
Hide file tree
Showing 12 changed files with 475 additions and 65 deletions.
123 changes: 121 additions & 2 deletions app/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ const ShellEVMTxType = math.MaxUint32

var ERC20ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")
var ERC20TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
var ERC721TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
var ERC721ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")
var ERC721ApproveAllTopic = common.HexToHash("0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31")
var EmptyHash = common.HexToHash("0x0")
var TrueHash = common.HexToHash("0x1")

type AllowanceResponse struct {
Allowance sdk.Int `json:"allowance"`
Expand Down Expand Up @@ -49,6 +53,16 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
}
continue
}
// check if there is a ERC721 pointer to contract Addr
pointerAddr, _, exists = app.EvmKeeper.GetERC721CW721Pointer(ctx, contractAddr)
if exists {
log, eligible := app.translateCW721Event(ctx, wasmEvent, pointerAddr, contractAddr)
if eligible {
log.Index = uint(len(logs))
logs = append(logs, log)
}
continue
}
}
if len(logs) == 0 {
return
Expand All @@ -59,7 +73,7 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
}
var bloom ethtypes.Bloom
if r, err := app.EvmKeeper.GetTransientReceipt(ctx, txHash); err == nil && r != nil {
r.Logs = append(r.Logs, utils.Map(logs, evmkeeper.ConvertEthLog)...)
r.Logs = append(r.Logs, utils.Map(logs, evmkeeper.ConvertSyntheticEthLog)...)
bloom = ethtypes.CreateBloom(ethtypes.Receipts{&ethtypes.Receipt{Logs: evmkeeper.GetLogsForTx(r)}})
r.LogsBloom = bloom[:]
_ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, r)
Expand All @@ -71,7 +85,7 @@ func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.
GasUsed: ctx.GasMeter().GasConsumed(),
BlockNumber: uint64(ctx.BlockHeight()),
TransactionIndex: uint32(ctx.TxIndex()),
Logs: utils.Map(logs, evmkeeper.ConvertEthLog),
Logs: utils.Map(logs, evmkeeper.ConvertSyntheticEthLog),
LogsBloom: bloom[:],
Status: uint32(ethtypes.ReceiptStatusSuccessful), // we don't create shell receipt for failed Cosmos tx since there is no event anyway
}
Expand Down Expand Up @@ -146,6 +160,99 @@ 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) {
action, found := GetAttributeValue(wasmEvent, "action")
if !found {
return nil, false
}
var topics []common.Hash
switch action {
case "transfer_nft", "send_nft", "burn":
topics = []common.Hash{
ERC721TransferTopic,
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
app.GetEvmAddressAttribute(ctx, wasmEvent, "recipient"),
}
tokenID := GetTokenIDAttribute(wasmEvent)
if tokenID == nil {
return nil, false
}
return &ethtypes.Log{
Address: pointerAddr,
Topics: topics,
Data: common.BigToHash(tokenID).Bytes(),
}, true
case "mint":
topics = []common.Hash{
ERC721TransferTopic,
EmptyHash,
app.GetEvmAddressAttribute(ctx, wasmEvent, "owner"),
}
tokenID := GetTokenIDAttribute(wasmEvent)
if tokenID == nil {
return nil, false
}
return &ethtypes.Log{
Address: pointerAddr,
Topics: topics,
Data: common.BigToHash(tokenID).Bytes(),
}, true
case "approve":
topics = []common.Hash{
ERC721ApprovalTopic,
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
app.GetEvmAddressAttribute(ctx, wasmEvent, "spender"),
}
tokenID := GetTokenIDAttribute(wasmEvent)
if tokenID == nil {
return nil, false
}
return &ethtypes.Log{
Address: pointerAddr,
Topics: topics,
Data: common.BigToHash(tokenID).Bytes(),
}, true
case "revoke":
topics = []common.Hash{
ERC721ApprovalTopic,
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
EmptyHash,
}
tokenID := GetTokenIDAttribute(wasmEvent)
if tokenID == nil {
return nil, false
}
return &ethtypes.Log{
Address: pointerAddr,
Topics: topics,
Data: common.BigToHash(tokenID).Bytes(),
}, true
case "approve_all":
topics = []common.Hash{
ERC721ApproveAllTopic,
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
app.GetEvmAddressAttribute(ctx, wasmEvent, "operator"),
}
return &ethtypes.Log{
Address: pointerAddr,
Topics: topics,
Data: TrueHash.Bytes(),
}, true
case "revoke_all":
topics = []common.Hash{
ERC721ApproveAllTopic,
app.GetEvmAddressAttribute(ctx, wasmEvent, "sender"),
app.GetEvmAddressAttribute(ctx, wasmEvent, "operator"),
}
return &ethtypes.Log{
Address: pointerAddr,
Topics: topics,
Data: EmptyHash.Bytes(),
}, true
}
return nil, false
}

func (app *App) GetEvmAddressAttribute(ctx sdk.Context, event abci.Event, attribute string) common.Hash {
addrStr, found := GetAttributeValue(event, attribute)
if found {
Expand Down Expand Up @@ -186,3 +293,15 @@ func GetAmountAttribute(event abci.Event) (*big.Int, bool) {
}
return nil, false
}

func GetTokenIDAttribute(event abci.Event) *big.Int {
tokenID, found := GetAttributeValue(event, "token_id")
if !found {
return nil
}
tokenIDInt, ok := sdk.NewIntFromString(tokenID)
if !ok {
return nil
}
return tokenIDInt.BigInt()
}
220 changes: 220 additions & 0 deletions app/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,226 @@ func TestEvmEventsForCw20(t *testing.T) {
require.Equal(t, common.HexToHash("0x64").Bytes(), receipt.Logs[0].Data)
}

func TestEvmEventsForCw721(t *testing.T) {
k := testkeeper.EVMTestApp.EvmKeeper
wasmKeeper := k.WasmKeeper()
ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()).WithChainID("sei-test").WithBlockHeight(1)
code, err := os.ReadFile("../contracts/wasm/cw721_base.wasm")
require.Nil(t, err)
privKey := testkeeper.MockPrivateKey()
creator, _ := testkeeper.PrivateKeyToAddresses(privKey)
codeID, err := wasmKeeper.Create(ctx, creator, code, nil)
require.Nil(t, err)
contractAddr, _, err := wasmKeeper.Instantiate(ctx, codeID, creator, creator, []byte(fmt.Sprintf("{\"name\":\"test\",\"symbol\":\"test\",\"minter\":\"%s\"}", creator.String())), "test", sdk.NewCoins())
require.Nil(t, err)

_, mockPointerAddr := testkeeper.MockAddressPair()
k.SetERC721CW721Pointer(ctx, contractAddr.String(), mockPointerAddr)

// calling CW contract directly
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()
payload := []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"1\",\"owner\":\"%s\"}}", recipient.String()))
msg := &wasmtypes.MsgExecuteContract{
Sender: creator.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, privKey, k.AccountKeeper().GetAccount(ctx, creator))
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)
_, found := testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
require.True(t, found)

// calling from wasmd precompile
abi, err := wasmd.GetABI()
require.Nil(t, err)
emptyCoins, err := sdk.NewCoins().MarshalJSON()
require.Nil(t, err)
payload = []byte(fmt.Sprintf("{\"mint\":{\"token_id\":\"2\",\"owner\":\"%s\"}}", creator.String()))
data, err := abi.Pack("execute", contractAddr.String(), payload, emptyCoins)
require.Nil(t, err)
wasmAddr := common.HexToAddress(wasmd.WasmdAddress)
txData := ethtypes.LegacyTx{
Nonce: 0,
GasPrice: big.NewInt(1000000000),
Gas: 1000000,
To: &wasmAddr,
Data: data,
}
chainID := k.ChainID(ctx)
chainCfg := evmtypes.DefaultChainConfig()
ethCfg := chainCfg.EthereumConfig(chainID)
blockNum := big.NewInt(ctx.BlockHeight())
signer := ethtypes.MakeSigner(ethCfg, blockNum, uint64(ctx.BlockTime().Unix()))
testPrivHex := hex.EncodeToString(privKey.Bytes())
key, _ := crypto.HexToECDSA(testPrivHex)
signedTx, err := ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key)
require.Nil(t, err)
typedTx, err := ethtx.NewLegacyTx(signedTx)
require.Nil(t, err)
emsg, err := evmtypes.NewMsgEVMTransaction(typedTx)
require.Nil(t, err)
txBuilder = testkeeper.EVMTestApp.GetTxConfig().NewTxBuilder()
txBuilder.SetMsgs(emsg)
tx = txBuilder.GetTx()
txbz, err = testkeeper.EVMTestApp.GetTxConfig().TxEncoder()(tx)
require.Nil(t, err)
res = testkeeper.EVMTestApp.DeliverTx(ctx.WithEventManager(sdk.NewEventManager()).WithTxIndex(1), abci.RequestDeliverTx{Tx: txbz}, tx, sum)
require.Equal(t, uint32(0), res.Code)
receipt, err = testkeeper.EVMTestApp.EvmKeeper.GetTransientReceipt(ctx, signedTx.Hash())
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)
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
require.True(t, found)

// test approval message
payload = []byte(fmt.Sprintf("{\"approve\":{\"spender\":\"%s\",\"token_id\":\"2\"}}", recipient.String()))
msg = &wasmtypes.MsgExecuteContract{
Sender: creator.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, privKey, k.AccountKeeper().GetAccount(ctx, creator))
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)
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
require.True(t, found)
require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data)

// revoke
payload = []byte(fmt.Sprintf("{\"revoke\":{\"spender\":\"%s\",\"token_id\":\"2\"}}", recipient.String()))
msg = &wasmtypes.MsgExecuteContract{
Sender: creator.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, privKey, k.AccountKeeper().GetAccount(ctx, creator))
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)
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
require.True(t, found)
require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data)

// approve all
payload = []byte(fmt.Sprintf("{\"approve_all\":{\"operator\":\"%s\"}}", recipient.String()))
msg = &wasmtypes.MsgExecuteContract{
Sender: creator.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, privKey, k.AccountKeeper().GetAccount(ctx, creator))
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)
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
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()))
msg = &wasmtypes.MsgExecuteContract{
Sender: creator.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, privKey, k.AccountKeeper().GetAccount(ctx, creator))
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)
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
require.True(t, found)
require.Equal(t, common.HexToHash("0x0").Bytes(), receipt.Logs[0].Data)

// burn
payload = []byte("{\"burn\":{\"token_id\":\"2\"}}")
msg = &wasmtypes.MsgExecuteContract{
Sender: creator.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, privKey, k.AccountKeeper().GetAccount(ctx, creator))
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)
_, found = testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
require.True(t, found)
require.Equal(t, common.HexToHash("0x2").Bytes(), receipt.Logs[0].Data)
}

func signTx(txBuilder client.TxBuilder, privKey cryptotypes.PrivKey, acc authtypes.AccountI) sdk.Tx {
var sigsV2 []signing.SignatureV2
sigV2 := signing.SignatureV2{
Expand Down
Loading

0 comments on commit 1c32f92

Please sign in to comment.