Skip to content
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

fix(evm): added tx logs events to the funtoken related txs #2161

Merged
merged 10 commits into from
Jan 20, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ needed to include double quotes around the hexadecimal string.
- [#2156](https://github.com/NibiruChain/nibiru/pull/2156) - test(evm-e2e): add E2E test using the Nibiru Oracle's ChainLink impl
- [#2157](https://github.com/NibiruChain/nibiru/pull/2157) - fix(evm): Fix unit inconsistency related to AuthInfo.Fee and txData.Fee using effective fee
- [#2160](https://github.com/NibiruChain/nibiru/pull/2160) - fix(evm-precompile): use bank.MsgServer Send in precompile IFunToken.bankMsgSend
- [#2161](https://github.com/NibiruChain/nibiru/pull/2161) - fix(evm): added tx logs events to the funtoken related txs
- [#2162](https://github.com/NibiruChain/nibiru/pull/2162) - test(testutil): try retrying for 'panic: pebbledb: closed'
- [#2167](https://github.com/NibiruChain/nibiru/pull/2167) - refactor(evm): removed blockGasUsed transient variable
- [#2168](https://github.com/NibiruChain/nibiru/pull/2168) - chore(evm-solidity): Move unrelated docs, gen-embeds, and add Solidity docs
Expand Down
2 changes: 1 addition & 1 deletion eth/rpc/backend/backend_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func (s *BackendSuite) buildContractCallTx(
nonce uint64,
gasLimit uint64,
) gethcore.Transaction {
// recipient := crypto.CreateAddress(s.fundedAccEthAddr, 29381)
//recipient := crypto.CreateAddress(s.fundedAccEthAddr, 29381)
transferAmount := big.NewInt(100)

packedArgs, err := embeds.SmartContract_TestERC20.ABI.Pack(
Expand Down
18 changes: 7 additions & 11 deletions eth/rpc/backend/gas_used_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ func (s *BackendSuite) TestGasUsedFunTokens() {
erc20Addr, err := eth.NewEIP55AddrFromStr(testContractAddress.String())
s.Require().NoError(err)

_, err = s.backend.GetTransactionCount(s.fundedAccEthAddr, rpc.EthPendingBlockNumber)
s.Require().NoError(err)
nonce := s.getCurrentNonce(s.node.EthAddress)
balanceBefore := s.getUnibiBalance(s.fundedAccEthAddr)

txResp, err := s.network.BroadcastMsgs(s.node.Address, &evm.MsgCreateFunToken{
txResp, err := s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgCreateFunToken{
Sender: s.node.Address.String(),
FromErc20: &erc20Addr,
})
Expand All @@ -96,15 +96,11 @@ func (s *BackendSuite) TestGasUsedFunTokens() {
)
s.Require().NoError(err)

nonce, err := s.backend.GetTransactionCount(s.fundedAccEthAddr, rpc.EthPendingBlockNumber)
s.Require().NoError(err)

balanceBefore := s.getUnibiBalance(s.fundedAccEthAddr)

nonce = s.getCurrentNonce(s.fundedAccEthAddr)
txHash1 := SendTransaction(
s,
&gethcore.LegacyTx{
Nonce: uint64(*nonce),
Nonce: nonce,
To: &precompile.PrecompileAddr_FunToken,
Data: packedArgsPass,
Gas: 1_500_000,
Expand All @@ -123,7 +119,7 @@ func (s *BackendSuite) TestGasUsedFunTokens() {
txHash2 := SendTransaction( // should fail due to invalid recipient address
s,
&gethcore.LegacyTx{
Nonce: uint64(*nonce + 1),
Nonce: nonce + 1,
To: &precompile.PrecompileAddr_FunToken,
Data: packedArgsFail,
Gas: 1_500_000,
Expand All @@ -134,7 +130,7 @@ func (s *BackendSuite) TestGasUsedFunTokens() {
txHash3 := SendTransaction(
s,
&gethcore.LegacyTx{
Nonce: uint64(*nonce + 2),
Nonce: nonce + 2,
To: &precompile.PrecompileAddr_FunToken,
Data: packedArgsPass,
Gas: 1_500_000,
Expand Down
299 changes: 299 additions & 0 deletions eth/rpc/backend/tx_logs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
package backend_test

import (
"fmt"
"math/big"

tmrpctypes "github.com/cometbft/cometbft/rpc/core/types"
sdk "github.com/cosmos/cosmos-sdk/types"
gethcommon "github.com/ethereum/go-ethereum/common"
gethcore "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"

"github.com/NibiruChain/nibiru/v2/eth"
"github.com/NibiruChain/nibiru/v2/eth/rpc/backend"
"github.com/NibiruChain/nibiru/v2/x/common/testutil"
"github.com/NibiruChain/nibiru/v2/x/evm"
"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
"github.com/NibiruChain/nibiru/v2/x/evm/evmtest"
"github.com/NibiruChain/nibiru/v2/x/evm/precompile"
)

// TestEthLogs checks that eth txs as well as funtoken txs produce tx_logs events and update tx index properly.
// To check that, we send a series of transactions:
// - 1: simple eth transfer
// - 2: deploying erc20 contract
// - 3. creating funtoken from erc20
// - 4: creating funtoken from coin
// - 5. converting coin to erc20
// - 6. converting erc20 born token to coin via precompile
// Each tx should emit some tx logs and emit proper tx index within ethereum tx event.
func (s *BackendSuite) TestLogs() {
// Test is broadcasting txs. Lock to avoid nonce conflicts.
testMutex.Lock()
defer testMutex.Unlock()

// Start with fresh block
s.Require().NoError(s.network.WaitForNextBlock())

s.T().Log("TX1: Send simple nibi transfer")
randomEthAddr := evmtest.NewEthPrivAcc().EthAddr
txHashFirst := s.SendNibiViaEthTransfer(randomEthAddr, amountToSend, false)

s.T().Log("TX2: Deploy ERC20 contract")
_, erc20ContractAddr := s.DeployTestContract(false)
erc20Addr, _ := eth.NewEIP55AddrFromStr(erc20ContractAddr.String())

s.T().Log("TX3: Create FunToken from ERC20")
nonce := s.getCurrentNonce(eth.NibiruAddrToEthAddr(s.node.Address))
txResp, err := s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgCreateFunToken{
Sender: s.node.Address.String(),
FromErc20: &erc20Addr,
})
s.Require().NoError(err)
s.Require().NotNil(txResp)
s.Require().Equal(
uint32(0),
txResp.Code,
fmt.Sprintf("Failed to create FunToken from ERC20. RawLog: %s", txResp.RawLog),
)

s.T().Log("TX4: Create FunToken from unibi coin")
nonce++
erc20FromCoinAddr := crypto.CreateAddress(evm.EVM_MODULE_ADDRESS, s.getCurrentNonce(evm.EVM_MODULE_ADDRESS)+1)

txResp, err = s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgCreateFunToken{
Sender: s.node.Address.String(),
FromBankDenom: evm.EVMBankDenom,
})
s.Require().NoError(err)
s.Require().NotNil(txResp)
s.Require().Equal(
uint32(0),
txResp.Code,
fmt.Sprintf("Failed to create FunToken from unibi coin. RawLog: %s", txResp.RawLog),
)

s.T().Log("TX5: Convert coin to EVM")
nonce++
txResp, err = s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgConvertCoinToEvm{
Sender: s.node.Address.String(),
BankCoin: sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(1)),
ToEthAddr: eth.EIP55Addr{
Address: s.fundedAccEthAddr,
},
})
s.Require().NoError(err)
s.Require().NotNil(txResp)
s.Require().Equal(
uint32(0),
txResp.Code,
fmt.Sprintf("Failed converting coin to evm. RawLog: %s", txResp.RawLog),
)

s.T().Log("TX6: Send erc20 token to coin using precompile")
randomNibiAddress := testutil.AccAddress()
packedArgsPass, err := embeds.SmartContract_FunToken.ABI.Pack(
"sendToBank",
erc20Addr.Address,
big.NewInt(1),
randomNibiAddress.String(),
)
s.Require().NoError(err)
txHashLast := SendTransaction(
s,
&gethcore.LegacyTx{
Nonce: s.getCurrentNonce(s.fundedAccEthAddr),
To: &precompile.PrecompileAddr_FunToken,
Data: packedArgsPass,
Gas: 1_500_000,
GasPrice: big.NewInt(1),
},
false,
)

// Wait for all txs to be included in a block
blockNumFirstTx, _, _ := WaitForReceipt(s, txHashFirst)
blockNumLastTx, _, _ := WaitForReceipt(s, txHashLast)
s.Require().NotNil(blockNumFirstTx)
s.Require().NotNil(blockNumLastTx)

Comment on lines +115 to +120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add timeout handling for transaction receipt waiting.

The WaitForReceipt calls could potentially hang if transactions are never mined. Consider adding a timeout mechanism to fail fast in such scenarios.

// Check tx logs for each tx
type logsCheck struct {
txInfo string
logs []*gethcore.Log
expectEthTx bool
}
checks := []logsCheck{
{
txInfo: "TX1 - simple eth transfer, should have empty logs",
logs: nil,
expectEthTx: true,
},
{
txInfo: "TX2 - deploying erc20 contract, should have logs",
logs: []*gethcore.Log{
// minting initial balance to the account
{
Address: erc20ContractAddr,
Topics: []gethcommon.Hash{
crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")),
gethcommon.Address{}.Hash(),
s.fundedAccEthAddr.Hash(),
},
},
},
expectEthTx: true,
},
{
txInfo: "TX3 - create FunToken from ERC20, no eth tx, no logs",
logs: nil,
expectEthTx: false,
},
{
txInfo: "TX4 - create FunToken from unibi coin, no eth tx, logs for contract deployment",
logs: []*gethcore.Log{
// contract ownership to evm module
{
Address: erc20FromCoinAddr,
Topics: []gethcommon.Hash{
crypto.Keccak256Hash([]byte("OwnershipTransferred(address,address)")),
gethcommon.Address{}.Hash(),
evm.EVM_MODULE_ADDRESS.Hash(),
},
},
},
expectEthTx: false,
},
{
txInfo: "TX5 - Convert coin to EVM, no eth tx, logs for minting tokens to the account",
logs: []*gethcore.Log{
// minting to the account
{
Address: erc20FromCoinAddr,
Topics: []gethcommon.Hash{
crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")),
gethcommon.Address{}.Hash(),
s.fundedAccEthAddr.Hash(),
},
},
},
expectEthTx: false,
},
{
txInfo: "TX6 - Send erc20 token to coin using precompile, eth tx, logs for transferring tokens to evm module",
logs: []*gethcore.Log{
// transfer from account to evm module
{
Address: erc20ContractAddr,
Topics: []gethcommon.Hash{
crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")),
s.fundedAccEthAddr.Hash(),
evm.EVM_MODULE_ADDRESS.Hash(),
},
},
},
expectEthTx: true,
},
}

// Getting block results. Note: txs could be included in more than one block
blockNumber := blockNumFirstTx.Int64()
blockRes, err := s.backend.TendermintBlockResultByNumber(&blockNumber)
s.Require().NoError(err)
s.Require().NotNil(blockRes)
txIndex := 0
ethTxIndex := 0
for idx, check := range checks {
if txIndex+1 > len(blockRes.TxsResults) {
blockNumber++
if blockNumber > blockNumLastTx.Int64() {
s.Fail("TX %d not found in block results", idx)
}
txIndex = 0
ethTxIndex = 0
blockRes, err = s.backend.TendermintBlockResultByNumber(&blockNumber)
s.Require().NoError(err)
s.Require().NotNil(blockRes)
}
s.assertTxLogsAndTxIndex(blockRes, txIndex, ethTxIndex, check.logs, check.expectEthTx, check.txInfo)
txIndex++
if check.expectEthTx {
ethTxIndex++
}
}
}

// assertTxLogsAndTxIndex gets tx results from the block and checks tx logs and tx index.
func (s *BackendSuite) assertTxLogsAndTxIndex(
blockRes *tmrpctypes.ResultBlockResults,
txIndex int,
ethTxIndex int,
expectedTxLogs []*gethcore.Log,
expectedEthTx bool,
txInfo string,
) {
txResults := blockRes.TxsResults[txIndex]
s.Require().Equal(uint32(0x0), txResults.Code, "tx failed, %s. RawLog: %s", txInfo, txResults.Log)

events := blockRes.TxsResults[txIndex].Events

foundEthTx := false
for _, event := range events {
if event.Type == evm.TypeUrlEventTxLog {
logs, err := backend.ParseTxLogsFromEvent(event)
s.Require().NoError(err)
if len(expectedTxLogs) > 0 {
s.Require().GreaterOrEqual(len(logs), len(expectedTxLogs))
s.assertTxLogsMatch(expectedTxLogs, logs, txInfo)
} else {
s.Require().Nil(logs)
}
}
if event.Type == evm.TypeUrlEventEthereumTx {
foundEthTx = true
if !expectedEthTx {
s.Fail("unexpected EventEthereumTx event for non-eth tx, %s", txInfo)
}
ethereumTx, err := evm.EventEthereumTxFromABCIEvent(event)
s.Require().NoError(err)
s.Require().Equal(
fmt.Sprintf("%d", ethTxIndex),
ethereumTx.Index,
"tx index mismatch, %s", txInfo,
)
}
}
if expectedEthTx && !foundEthTx {
s.Fail("expected EventEthereumTx event not found, %s", txInfo)
}
}

// assertTxLogsMatch checks that actual tx logs include the expected logs
func (s *BackendSuite) assertTxLogsMatch(
expectedLogs []*gethcore.Log,
actualLogs []*gethcore.Log,
txInfo string,
) {
for idx, expectedLog := range expectedLogs {
actualLog := actualLogs[idx]
s.Require().Equal(
expectedLog.Address,
actualLog.Address, fmt.Sprintf("log contract address mismatch, log index %d, %s", idx, txInfo),
)

s.Require().Equal(
len(expectedLog.Topics),
len(actualLog.Topics),
fmt.Sprintf("topics length mismatch, log index %d, %s", idx, txInfo),
)

for idx, topic := range expectedLog.Topics {
s.Require().Equal(
topic,
actualLog.Topics[idx],
fmt.Sprintf("topic mismatch, log index %d, %s", idx, txInfo),
)
}
}
}
Loading
Loading