-
Notifications
You must be signed in to change notification settings - Fork 874
Translate CW20 events into EVM events in shell receipts #1748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1a8f042
c6b7133
a8d401b
3042e1c
b551d45
a1a975a
077d09f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| package app | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "math" | ||
| "math/big" | ||
|
|
||
| wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| ethtypes "github.com/ethereum/go-ethereum/core/types" | ||
| "github.com/sei-protocol/sei-chain/utils" | ||
| evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" | ||
| evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" | ||
| abci "github.com/tendermint/tendermint/abci/types" | ||
| ) | ||
|
|
||
| const ShellEVMTxType = math.MaxUint32 | ||
|
|
||
| var ERC20ApprovalTopic = common.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925") | ||
| var ERC20TransferTopic = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") | ||
| var EmptyHash = common.HexToHash("0x0") | ||
|
|
||
| type AllowanceResponse struct { | ||
| Allowance sdk.Int `json:"allowance"` | ||
| Expires json.RawMessage `json:"expires"` | ||
| } | ||
|
|
||
| func (app *App) AddCosmosEventsToEVMReceiptIfApplicable(ctx sdk.Context, tx sdk.Tx, checksum [32]byte, response abci.ResponseDeliverTx) { | ||
| if response.Code > 0 { | ||
| return | ||
| } | ||
| wasmEvents := GetEventsOfType(response, wasmtypes.WasmModuleEventType) | ||
| logs := []*ethtypes.Log{} | ||
| for _, wasmEvent := range wasmEvents { | ||
| contractAddr, found := GetAttributeValue(wasmEvent, wasmtypes.AttributeKeyContractAddr) | ||
| if !found { | ||
| continue | ||
| } | ||
| // check if there is a ERC20 pointer to contractAddr | ||
| pointerAddr, _, exists := app.EvmKeeper.GetERC20CW20Pointer(ctx, contractAddr) | ||
| if exists { | ||
| log, eligible := app.translateCW20Event(ctx, wasmEvent, pointerAddr, contractAddr) | ||
| if eligible { | ||
| log.Index = uint(len(logs)) | ||
| logs = append(logs, log) | ||
| } | ||
| continue | ||
| } | ||
| } | ||
| if len(logs) == 0 { | ||
| return | ||
| } | ||
| txHash := common.BytesToHash(checksum[:]) | ||
| if response.EvmTxInfo != nil { | ||
| txHash = common.HexToHash(response.EvmTxInfo.TxHash) | ||
| } | ||
| 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)...) | ||
| bloom = ethtypes.CreateBloom(ethtypes.Receipts{ðtypes.Receipt{Logs: evmkeeper.GetLogsForTx(r)}}) | ||
| r.LogsBloom = bloom[:] | ||
| _ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, r) | ||
| } else { | ||
| bloom = ethtypes.CreateBloom(ethtypes.Receipts{ðtypes.Receipt{Logs: logs}}) | ||
| receipt := &evmtypes.Receipt{ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there some indicator or flag so that we can somehow optionally include/exclude these events from a result? |
||
| TxType: ShellEVMTxType, | ||
| TxHashHex: txHash.Hex(), | ||
| GasUsed: ctx.GasMeter().GasConsumed(), | ||
| BlockNumber: uint64(ctx.BlockHeight()), | ||
| TransactionIndex: uint32(ctx.TxIndex()), | ||
| Logs: utils.Map(logs, evmkeeper.ConvertEthLog), | ||
| LogsBloom: bloom[:], | ||
| Status: uint32(ethtypes.ReceiptStatusSuccessful), // we don't create shell receipt for failed Cosmos tx since there is no event anyway | ||
| } | ||
| sigTx, ok := tx.(authsigning.SigVerifiableTx) | ||
| if ok && len(sigTx.GetSigners()) > 0 { | ||
| // use the first signer as the `from` | ||
| receipt.From = app.EvmKeeper.GetEVMAddressOrDefault(ctx, sigTx.GetSigners()[0]).Hex() | ||
| } | ||
| _ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, receipt) | ||
| } | ||
| if d, found := app.EvmKeeper.GetEVMTxDeferredInfo(ctx); found { | ||
| app.EvmKeeper.AppendToEvmTxDeferredInfo(ctx, bloom, txHash, d.Surplus) | ||
| } else { | ||
| app.EvmKeeper.AppendToEvmTxDeferredInfo(ctx, bloom, txHash, sdk.ZeroInt()) | ||
| } | ||
| } | ||
|
|
||
| func (app *App) translateCW20Event(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 "mint", "burn", "send", "transfer", "transfer_from", "send_from", "burn_from": | ||
| topics = []common.Hash{ | ||
| ERC20TransferTopic, | ||
| app.GetEvmAddressAttribute(ctx, wasmEvent, "from"), | ||
| app.GetEvmAddressAttribute(ctx, wasmEvent, "to"), | ||
| } | ||
| amount, found := GetAmountAttribute(wasmEvent) | ||
| if !found { | ||
| return nil, false | ||
| } | ||
| return ðtypes.Log{ | ||
| Address: pointerAddr, | ||
| Topics: topics, | ||
| Data: common.BigToHash(amount).Bytes(), | ||
| }, true | ||
| case "increase_allowance", "decrease_allowance": | ||
| ownerStr, found := GetAttributeValue(wasmEvent, "owner") | ||
| if !found { | ||
| return nil, false | ||
| } | ||
| spenderStr, found := GetAttributeValue(wasmEvent, "spender") | ||
| if !found { | ||
| return nil, false | ||
| } | ||
| topics := []common.Hash{ | ||
| ERC20ApprovalTopic, | ||
| app.GetEvmAddressAttribute(ctx, wasmEvent, "owner"), | ||
| app.GetEvmAddressAttribute(ctx, wasmEvent, "spender"), | ||
| } | ||
| res, err := app.WasmKeeper.QuerySmart( | ||
| ctx, | ||
| sdk.MustAccAddressFromBech32(contractAddr), | ||
| []byte(fmt.Sprintf("{\"allowance\":{\"owner\":\"%s\",\"spender\":\"%s\"}}", ownerStr, spenderStr)), | ||
| ) | ||
| if err != nil { | ||
| return nil, false | ||
| } | ||
| allowanceResponse := &AllowanceResponse{} | ||
| if err := json.Unmarshal(res, allowanceResponse); err != nil { | ||
| return nil, false | ||
| } | ||
| return ðtypes.Log{ | ||
| Address: pointerAddr, | ||
| Topics: topics, | ||
| Data: common.BigToHash(allowanceResponse.Allowance.BigInt()).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 { | ||
| seiAddr, err := sdk.AccAddressFromBech32(addrStr) | ||
| if err == nil { | ||
| evmAddr := app.EvmKeeper.GetEVMAddressOrDefault(ctx, seiAddr) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it will, but just confirming: this would work on a past block as well (ctx's height is honored here)?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah all the states are accessed via ctx which will be versioned |
||
| return common.BytesToHash(evmAddr[:]) | ||
| } | ||
| } | ||
| return EmptyHash | ||
| } | ||
|
|
||
| func GetEventsOfType(rdtx abci.ResponseDeliverTx, ty string) (res []abci.Event) { | ||
| for _, event := range rdtx.Events { | ||
| if event.Type == ty { | ||
| res = append(res, event) | ||
| } | ||
| } | ||
| return | ||
| } | ||
|
|
||
| func GetAttributeValue(event abci.Event, attribute string) (string, bool) { | ||
| for _, attr := range event.Attributes { | ||
| if string(attr.Key) == attribute { | ||
| return string(attr.Value), true | ||
| } | ||
| } | ||
| return "", false | ||
| } | ||
|
|
||
| func GetAmountAttribute(event abci.Event) (*big.Int, bool) { | ||
| amount, found := GetAttributeValue(event, "amount") | ||
| if found { | ||
| amountInt, ok := sdk.NewIntFromString(amount) | ||
| if ok { | ||
| return amountInt.BigInt(), true | ||
| } | ||
| } | ||
| return nil, false | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| package app_test | ||
|
|
||
| import ( | ||
| "crypto/sha256" | ||
| "encoding/hex" | ||
| "fmt" | ||
| "math/big" | ||
| "os" | ||
| "testing" | ||
| "time" | ||
|
|
||
| wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" | ||
| "github.com/cosmos/cosmos-sdk/client" | ||
| clienttx "github.com/cosmos/cosmos-sdk/client/tx" | ||
| cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
| sdk "github.com/cosmos/cosmos-sdk/types" | ||
| "github.com/cosmos/cosmos-sdk/types/tx/signing" | ||
| xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" | ||
| authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| ethtypes "github.com/ethereum/go-ethereum/core/types" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
| "github.com/sei-protocol/sei-chain/precompiles/wasmd" | ||
| testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" | ||
| evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" | ||
| "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" | ||
| "github.com/stretchr/testify/require" | ||
| abci "github.com/tendermint/tendermint/abci/types" | ||
| ) | ||
|
|
||
| func TestEvmEventsForCw20(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/cw20_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\",\"decimals\":6,\"initial_balances\":[{\"address\":\"%s\",\"amount\":\"1000000000\"}]}", creator.String())), "test", sdk.NewCoins()) | ||
| require.Nil(t, err) | ||
|
|
||
| _, mockPointerAddr := testkeeper.MockAddressPair() | ||
| k.SetERC20CW20Pointer(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("{\"transfer\":{\"recipient\":\"%s\",\"amount\":\"100\"}}", 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) | ||
| 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("{\"increase_allowance\":{\"spender\":\"%s\",\"amount\":\"100\"}}", 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("0x64").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{ | ||
| PubKey: privKey.PubKey(), | ||
| Data: &signing.SingleSignatureData{ | ||
| SignMode: testkeeper.EVMTestApp.GetTxConfig().SignModeHandler().DefaultMode(), | ||
| Signature: nil, | ||
| }, | ||
| Sequence: acc.GetSequence(), | ||
| } | ||
| sigsV2 = append(sigsV2, sigV2) | ||
| _ = txBuilder.SetSignatures(sigsV2...) | ||
| sigsV2 = []signing.SignatureV2{} | ||
| signerData := xauthsigning.SignerData{ | ||
| ChainID: "sei-test", | ||
| AccountNumber: acc.GetAccountNumber(), | ||
| Sequence: acc.GetSequence(), | ||
| } | ||
| sigV2, _ = clienttx.SignWithPrivKey( | ||
| testkeeper.EVMTestApp.GetTxConfig().SignModeHandler().DefaultMode(), | ||
| signerData, | ||
| txBuilder, | ||
| privKey, | ||
| testkeeper.EVMTestApp.GetTxConfig(), | ||
| acc.GetSequence(), | ||
| ) | ||
| sigsV2 = append(sigsV2, sigV2) | ||
| _ = txBuilder.SetSignatures(sigsV2...) | ||
| return txBuilder.GetTx() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd suggest doing a
if !exists { continue }to avoid the indentationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't continue here because we'd have another branch for 721 https://github.com/sei-protocol/sei-chain/pull/1750/files#diff-8810d2793d992ba6de31afd91265ef3facc0612669de567b51dc1d8d029cf22fR104 I refactored this part into a func to avoid the big indentation instead