-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(evm): added tx logs events to the funtoken related txs (#2161)
* fix: added tx logs emitting to the funtoken related txs * chore: changelog update * chore: lint * chore: debug failing integration test * fix: removed tx index updating for non eth txs * fix: tests --------- Co-authored-by: Unique Divine <[email protected]> Co-authored-by: Kevin Yang <[email protected]>
- Loading branch information
1 parent
ee5741b
commit 0ef4099
Showing
11 changed files
with
345 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
// 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), | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.