|
| 1 | +package backend_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "math/big" |
| 6 | + |
| 7 | + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" |
| 8 | + sdk "github.com/cosmos/cosmos-sdk/types" |
| 9 | + gethcommon "github.com/ethereum/go-ethereum/common" |
| 10 | + gethcore "github.com/ethereum/go-ethereum/core/types" |
| 11 | + "github.com/ethereum/go-ethereum/crypto" |
| 12 | + |
| 13 | + "github.com/NibiruChain/nibiru/v2/eth" |
| 14 | + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" |
| 15 | + "github.com/NibiruChain/nibiru/v2/x/common/testutil" |
| 16 | + "github.com/NibiruChain/nibiru/v2/x/evm" |
| 17 | + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" |
| 18 | + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" |
| 19 | + "github.com/NibiruChain/nibiru/v2/x/evm/precompile" |
| 20 | +) |
| 21 | + |
| 22 | +// TestEthLogs checks that eth txs as well as funtoken txs produce tx_logs events and update tx index properly. |
| 23 | +// To check that, we send a series of transactions: |
| 24 | +// - 1: simple eth transfer |
| 25 | +// - 2: deploying erc20 contract |
| 26 | +// - 3. creating funtoken from erc20 |
| 27 | +// - 4: creating funtoken from coin |
| 28 | +// - 5. converting coin to erc20 |
| 29 | +// - 6. converting erc20 born token to coin via precompile |
| 30 | +// Each tx should emit some tx logs and emit proper tx index within ethereum tx event. |
| 31 | +func (s *BackendSuite) TestLogs() { |
| 32 | + // Test is broadcasting txs. Lock to avoid nonce conflicts. |
| 33 | + testMutex.Lock() |
| 34 | + defer testMutex.Unlock() |
| 35 | + |
| 36 | + // Start with fresh block |
| 37 | + s.Require().NoError(s.network.WaitForNextBlock()) |
| 38 | + |
| 39 | + s.T().Log("TX1: Send simple nibi transfer") |
| 40 | + randomEthAddr := evmtest.NewEthPrivAcc().EthAddr |
| 41 | + txHashFirst := s.SendNibiViaEthTransfer(randomEthAddr, amountToSend, false) |
| 42 | + |
| 43 | + s.T().Log("TX2: Deploy ERC20 contract") |
| 44 | + _, erc20ContractAddr := s.DeployTestContract(false) |
| 45 | + erc20Addr, _ := eth.NewEIP55AddrFromStr(erc20ContractAddr.String()) |
| 46 | + |
| 47 | + s.T().Log("TX3: Create FunToken from ERC20") |
| 48 | + nonce := s.getCurrentNonce(eth.NibiruAddrToEthAddr(s.node.Address)) |
| 49 | + txResp, err := s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgCreateFunToken{ |
| 50 | + Sender: s.node.Address.String(), |
| 51 | + FromErc20: &erc20Addr, |
| 52 | + }) |
| 53 | + s.Require().NoError(err) |
| 54 | + s.Require().NotNil(txResp) |
| 55 | + s.Require().Equal( |
| 56 | + uint32(0), |
| 57 | + txResp.Code, |
| 58 | + fmt.Sprintf("Failed to create FunToken from ERC20. RawLog: %s", txResp.RawLog), |
| 59 | + ) |
| 60 | + |
| 61 | + s.T().Log("TX4: Create FunToken from unibi coin") |
| 62 | + nonce++ |
| 63 | + erc20FromCoinAddr := crypto.CreateAddress(evm.EVM_MODULE_ADDRESS, s.getCurrentNonce(evm.EVM_MODULE_ADDRESS)+1) |
| 64 | + |
| 65 | + txResp, err = s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgCreateFunToken{ |
| 66 | + Sender: s.node.Address.String(), |
| 67 | + FromBankDenom: evm.EVMBankDenom, |
| 68 | + }) |
| 69 | + s.Require().NoError(err) |
| 70 | + s.Require().NotNil(txResp) |
| 71 | + s.Require().Equal( |
| 72 | + uint32(0), |
| 73 | + txResp.Code, |
| 74 | + fmt.Sprintf("Failed to create FunToken from unibi coin. RawLog: %s", txResp.RawLog), |
| 75 | + ) |
| 76 | + |
| 77 | + s.T().Log("TX5: Convert coin to EVM") |
| 78 | + nonce++ |
| 79 | + txResp, err = s.network.BroadcastMsgs(s.node.Address, &nonce, &evm.MsgConvertCoinToEvm{ |
| 80 | + Sender: s.node.Address.String(), |
| 81 | + BankCoin: sdk.NewCoin(evm.EVMBankDenom, sdk.NewInt(1)), |
| 82 | + ToEthAddr: eth.EIP55Addr{ |
| 83 | + Address: s.fundedAccEthAddr, |
| 84 | + }, |
| 85 | + }) |
| 86 | + s.Require().NoError(err) |
| 87 | + s.Require().NotNil(txResp) |
| 88 | + s.Require().Equal( |
| 89 | + uint32(0), |
| 90 | + txResp.Code, |
| 91 | + fmt.Sprintf("Failed converting coin to evm. RawLog: %s", txResp.RawLog), |
| 92 | + ) |
| 93 | + |
| 94 | + s.T().Log("TX6: Send erc20 token to coin using precompile") |
| 95 | + randomNibiAddress := testutil.AccAddress() |
| 96 | + packedArgsPass, err := embeds.SmartContract_FunToken.ABI.Pack( |
| 97 | + "sendToBank", |
| 98 | + erc20Addr.Address, |
| 99 | + big.NewInt(1), |
| 100 | + randomNibiAddress.String(), |
| 101 | + ) |
| 102 | + s.Require().NoError(err) |
| 103 | + txHashLast := SendTransaction( |
| 104 | + s, |
| 105 | + &gethcore.LegacyTx{ |
| 106 | + Nonce: s.getCurrentNonce(s.fundedAccEthAddr), |
| 107 | + To: &precompile.PrecompileAddr_FunToken, |
| 108 | + Data: packedArgsPass, |
| 109 | + Gas: 1_500_000, |
| 110 | + GasPrice: big.NewInt(1), |
| 111 | + }, |
| 112 | + false, |
| 113 | + ) |
| 114 | + |
| 115 | + // Wait for all txs to be included in a block |
| 116 | + blockNumFirstTx, _, _ := WaitForReceipt(s, txHashFirst) |
| 117 | + blockNumLastTx, _, _ := WaitForReceipt(s, txHashLast) |
| 118 | + s.Require().NotNil(blockNumFirstTx) |
| 119 | + s.Require().NotNil(blockNumLastTx) |
| 120 | + |
| 121 | + // Check tx logs for each tx |
| 122 | + type logsCheck struct { |
| 123 | + txInfo string |
| 124 | + logs []*gethcore.Log |
| 125 | + expectEthTx bool |
| 126 | + } |
| 127 | + checks := []logsCheck{ |
| 128 | + { |
| 129 | + txInfo: "TX1 - simple eth transfer, should have empty logs", |
| 130 | + logs: nil, |
| 131 | + expectEthTx: true, |
| 132 | + }, |
| 133 | + { |
| 134 | + txInfo: "TX2 - deploying erc20 contract, should have logs", |
| 135 | + logs: []*gethcore.Log{ |
| 136 | + // minting initial balance to the account |
| 137 | + { |
| 138 | + Address: erc20ContractAddr, |
| 139 | + Topics: []gethcommon.Hash{ |
| 140 | + crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")), |
| 141 | + gethcommon.Address{}.Hash(), |
| 142 | + s.fundedAccEthAddr.Hash(), |
| 143 | + }, |
| 144 | + }, |
| 145 | + }, |
| 146 | + expectEthTx: true, |
| 147 | + }, |
| 148 | + { |
| 149 | + txInfo: "TX3 - create FunToken from ERC20, no eth tx, no logs", |
| 150 | + logs: nil, |
| 151 | + expectEthTx: false, |
| 152 | + }, |
| 153 | + { |
| 154 | + txInfo: "TX4 - create FunToken from unibi coin, no eth tx, logs for contract deployment", |
| 155 | + logs: []*gethcore.Log{ |
| 156 | + // contract ownership to evm module |
| 157 | + { |
| 158 | + Address: erc20FromCoinAddr, |
| 159 | + Topics: []gethcommon.Hash{ |
| 160 | + crypto.Keccak256Hash([]byte("OwnershipTransferred(address,address)")), |
| 161 | + gethcommon.Address{}.Hash(), |
| 162 | + evm.EVM_MODULE_ADDRESS.Hash(), |
| 163 | + }, |
| 164 | + }, |
| 165 | + }, |
| 166 | + expectEthTx: false, |
| 167 | + }, |
| 168 | + { |
| 169 | + txInfo: "TX5 - Convert coin to EVM, no eth tx, logs for minting tokens to the account", |
| 170 | + logs: []*gethcore.Log{ |
| 171 | + // minting to the account |
| 172 | + { |
| 173 | + Address: erc20FromCoinAddr, |
| 174 | + Topics: []gethcommon.Hash{ |
| 175 | + crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")), |
| 176 | + gethcommon.Address{}.Hash(), |
| 177 | + s.fundedAccEthAddr.Hash(), |
| 178 | + }, |
| 179 | + }, |
| 180 | + }, |
| 181 | + expectEthTx: false, |
| 182 | + }, |
| 183 | + { |
| 184 | + txInfo: "TX6 - Send erc20 token to coin using precompile, eth tx, logs for transferring tokens to evm module", |
| 185 | + logs: []*gethcore.Log{ |
| 186 | + // transfer from account to evm module |
| 187 | + { |
| 188 | + Address: erc20ContractAddr, |
| 189 | + Topics: []gethcommon.Hash{ |
| 190 | + crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")), |
| 191 | + s.fundedAccEthAddr.Hash(), |
| 192 | + evm.EVM_MODULE_ADDRESS.Hash(), |
| 193 | + }, |
| 194 | + }, |
| 195 | + }, |
| 196 | + expectEthTx: true, |
| 197 | + }, |
| 198 | + } |
| 199 | + |
| 200 | + // Getting block results. Note: txs could be included in more than one block |
| 201 | + blockNumber := blockNumFirstTx.Int64() |
| 202 | + blockRes, err := s.backend.TendermintBlockResultByNumber(&blockNumber) |
| 203 | + s.Require().NoError(err) |
| 204 | + s.Require().NotNil(blockRes) |
| 205 | + txIndex := 0 |
| 206 | + ethTxIndex := 0 |
| 207 | + for idx, check := range checks { |
| 208 | + if txIndex+1 > len(blockRes.TxsResults) { |
| 209 | + blockNumber++ |
| 210 | + if blockNumber > blockNumLastTx.Int64() { |
| 211 | + s.Fail("TX %d not found in block results", idx) |
| 212 | + } |
| 213 | + txIndex = 0 |
| 214 | + ethTxIndex = 0 |
| 215 | + blockRes, err = s.backend.TendermintBlockResultByNumber(&blockNumber) |
| 216 | + s.Require().NoError(err) |
| 217 | + s.Require().NotNil(blockRes) |
| 218 | + } |
| 219 | + s.assertTxLogsAndTxIndex(blockRes, txIndex, ethTxIndex, check.logs, check.expectEthTx, check.txInfo) |
| 220 | + txIndex++ |
| 221 | + if check.expectEthTx { |
| 222 | + ethTxIndex++ |
| 223 | + } |
| 224 | + } |
| 225 | +} |
| 226 | + |
| 227 | +// assertTxLogsAndTxIndex gets tx results from the block and checks tx logs and tx index. |
| 228 | +func (s *BackendSuite) assertTxLogsAndTxIndex( |
| 229 | + blockRes *tmrpctypes.ResultBlockResults, |
| 230 | + txIndex int, |
| 231 | + ethTxIndex int, |
| 232 | + expectedTxLogs []*gethcore.Log, |
| 233 | + expectedEthTx bool, |
| 234 | + txInfo string, |
| 235 | +) { |
| 236 | + txResults := blockRes.TxsResults[txIndex] |
| 237 | + s.Require().Equal(uint32(0x0), txResults.Code, "tx failed, %s. RawLog: %s", txInfo, txResults.Log) |
| 238 | + |
| 239 | + events := blockRes.TxsResults[txIndex].Events |
| 240 | + |
| 241 | + foundEthTx := false |
| 242 | + for _, event := range events { |
| 243 | + if event.Type == evm.TypeUrlEventTxLog { |
| 244 | + logs, err := backend.ParseTxLogsFromEvent(event) |
| 245 | + s.Require().NoError(err) |
| 246 | + if len(expectedTxLogs) > 0 { |
| 247 | + s.Require().GreaterOrEqual(len(logs), len(expectedTxLogs)) |
| 248 | + s.assertTxLogsMatch(expectedTxLogs, logs, txInfo) |
| 249 | + } else { |
| 250 | + s.Require().Nil(logs) |
| 251 | + } |
| 252 | + } |
| 253 | + if event.Type == evm.TypeUrlEventEthereumTx { |
| 254 | + foundEthTx = true |
| 255 | + if !expectedEthTx { |
| 256 | + s.Fail("unexpected EventEthereumTx event for non-eth tx, %s", txInfo) |
| 257 | + } |
| 258 | + ethereumTx, err := evm.EventEthereumTxFromABCIEvent(event) |
| 259 | + s.Require().NoError(err) |
| 260 | + s.Require().Equal( |
| 261 | + fmt.Sprintf("%d", ethTxIndex), |
| 262 | + ethereumTx.Index, |
| 263 | + "tx index mismatch, %s", txInfo, |
| 264 | + ) |
| 265 | + } |
| 266 | + } |
| 267 | + if expectedEthTx && !foundEthTx { |
| 268 | + s.Fail("expected EventEthereumTx event not found, %s", txInfo) |
| 269 | + } |
| 270 | +} |
| 271 | + |
| 272 | +// assertTxLogsMatch checks that actual tx logs include the expected logs |
| 273 | +func (s *BackendSuite) assertTxLogsMatch( |
| 274 | + expectedLogs []*gethcore.Log, |
| 275 | + actualLogs []*gethcore.Log, |
| 276 | + txInfo string, |
| 277 | +) { |
| 278 | + for idx, expectedLog := range expectedLogs { |
| 279 | + actualLog := actualLogs[idx] |
| 280 | + s.Require().Equal( |
| 281 | + expectedLog.Address, |
| 282 | + actualLog.Address, fmt.Sprintf("log contract address mismatch, log index %d, %s", idx, txInfo), |
| 283 | + ) |
| 284 | + |
| 285 | + s.Require().Equal( |
| 286 | + len(expectedLog.Topics), |
| 287 | + len(actualLog.Topics), |
| 288 | + fmt.Sprintf("topics length mismatch, log index %d, %s", idx, txInfo), |
| 289 | + ) |
| 290 | + |
| 291 | + for idx, topic := range expectedLog.Topics { |
| 292 | + s.Require().Equal( |
| 293 | + topic, |
| 294 | + actualLog.Topics[idx], |
| 295 | + fmt.Sprintf("topic mismatch, log index %d, %s", idx, txInfo), |
| 296 | + ) |
| 297 | + } |
| 298 | + } |
| 299 | +} |
0 commit comments