Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func TestEvmAnteErrorHandler(t *testing.T) {
Log: "nonce too high",
}})
testkeeper.EVMTestApp.EvmKeeper.SetMsgs([]*evmtypes.MsgEVMTransaction{req})
deferredInfo := testkeeper.EVMTestApp.EvmKeeper.GetEVMTxDeferredInfo(ctx)
deferredInfo := testkeeper.EVMTestApp.EvmKeeper.GetAllEVMTxDeferredInfo(ctx)
require.Equal(t, 1, len(deferredInfo))
require.Contains(t, deferredInfo[0].Error, "nonce too high")
}
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,8 @@ func New(
app.HardForkManager = upgrades.NewHardForkManager(app.ChainID)
app.HardForkManager.RegisterHandler(v0upgrade.NewHardForkUpgradeHandler(100_000, upgrades.ChainIDSeiHardForkTest, app.WasmKeeper))

app.RegisterDeliverTxHook(app.AddCosmosEventsToEVMReceiptIfApplicable)

return app
}

Expand Down
188 changes: 188 additions & 0 deletions app/receipt.go
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 {
Copy link
Copy Markdown
Contributor

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 indentation

Copy link
Copy Markdown
Collaborator Author

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

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{&ethtypes.Receipt{Logs: evmkeeper.GetLogsForTx(r)}})
r.LogsBloom = bloom[:]
_ = app.EvmKeeper.SetTransientReceipt(ctx, txHash, r)
} else {
bloom = ethtypes.CreateBloom(ethtypes.Receipts{&ethtypes.Receipt{Logs: logs}})
receipt := &evmtypes.Receipt{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 &ethtypes.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 &ethtypes.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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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
}
175 changes: 175 additions & 0 deletions app/receipt_test.go
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()
}
Loading