diff --git a/contracts/test/ERC20toCW20PointerTest.js b/contracts/test/ERC20toCW20PointerTest.js index 06d81fc4c..6d59cf770 100644 --- a/contracts/test/ERC20toCW20PointerTest.js +++ b/contracts/test/ERC20toCW20PointerTest.js @@ -93,9 +93,9 @@ describe("ERC20 to CW20 Pointer", function () { expect(await pointer.balanceOf(sender.evmAddress)).to.equal(balances.account0); expect(await pointer.balanceOf(recipient.evmAddress)).to.equal(balances.account1); - const blockNumber = await ethers.provider.getBlockNumber(); const tx = await pointer.transfer(recipient.evmAddress, 1); - await tx.wait(); + const receipt = await tx.wait(); + const blockNumber = receipt.blockNumber; expect(await pointer.balanceOf(sender.evmAddress)).to.equal(balances.account0-1); expect(await pointer.balanceOf(recipient.evmAddress)).to.equal(balances.account1+1); @@ -107,18 +107,38 @@ describe("ERC20 to CW20 Pointer", function () { address: await pointer.getAddress(), topics: [ethers.id("Transfer(address,address,uint256)")] }; - // send via eth_ endpoint - synthetic event doesn't show up + // send via eth_ endpoint - synthetic event should show up because we are using the + // synthetic event in place of a real EVM event const ethlogs = await ethers.provider.send('eth_getLogs', [filter]); - expect(ethlogs.length).to.equal(0); + expect(ethlogs.length).to.equal(1); // send via sei_ endpoint - synthetic event shows up const seilogs = await ethers.provider.send('sei_getLogs', [filter]); expect(seilogs.length).to.equal(1); - expect(seilogs.length).to.equal(1); - expect(seilogs[0]["address"].toLowerCase()).to.equal((await pointer.getAddress()).toLowerCase()); - expect(seilogs[0]["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); - expect(seilogs[0]["topics"][1].substring(26)).to.equal(sender.evmAddress.substring(2).toLowerCase()); - expect(seilogs[0]["topics"][2].substring(26)).to.equal(recipient.evmAddress.substring(2).toLowerCase()); + + const logs = [...ethlogs, ...seilogs]; + logs.forEach(async (log) => { + expect(log["address"].toLowerCase()).to.equal((await pointer.getAddress()).toLowerCase()); + expect(log["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); + expect(log["topics"][1].substring(26)).to.equal(sender.evmAddress.substring(2).toLowerCase()); + expect(log["topics"][2].substring(26)).to.equal(recipient.evmAddress.substring(2).toLowerCase()); + }); + + const ethBlock = await ethers.provider.send('eth_getBlockByNumber', ['0x' + blockNumber.toString(16), false]); + const seiBlock = await ethers.provider.send('sei_getBlockByNumber', ['0x' + blockNumber.toString(16), false]); + expect(ethBlock.transactions.length).to.equal(1); + expect(seiBlock.transactions.length).to.equal(1); + + const ethReceipts = await ethers.provider.send('eth_getBlockReceipts', ['0x' + blockNumber.toString(16)]); + const seiReceipts = await ethers.provider.send('sei_getBlockReceipts', ['0x' + blockNumber.toString(16)]); + expect(ethReceipts.length).to.equal(1); + expect(seiReceipts.length).to.equal(1); + expect(ethReceipts[0].transactionHash).to.equal(seiReceipts[0].transactionHash); + + const ethTx = await ethers.provider.send('eth_getTransactionReceipt', [receipt.hash]); + expect(ethTx.logs.length).to.equal(1); // check for transfer event + const ethTxByHash = await ethers.provider.send('eth_getTransactionByHash', [tx.hash]); + expect(ethTxByHash).to.not.be.null; const cleanupTx = await pointer.connect(recipient.signer).transfer(sender.evmAddress, 1); await cleanupTx.wait(); @@ -147,7 +167,7 @@ describe("ERC20 to CW20 Pointer", function () { const spender = accounts[1].evmAddress; const blockNumber = await ethers.provider.getBlockNumber(); const tx = await pointer.approve(spender, 1000000); - await tx.wait(); + const receipt = await tx.wait(); const allowance = await pointer.allowance(owner, spender); expect(Number(allowance)).to.equal(1000000); @@ -160,15 +180,15 @@ describe("ERC20 to CW20 Pointer", function () { }; // send via eth_ endpoint - synthetic event doesn't show up const ethlogs = await ethers.provider.send('eth_getLogs', [filter]); - expect(ethlogs.length).to.equal(0); + expect(ethlogs.length).to.equal(1); + expect(ethlogs[0]["address"].toLowerCase()).to.equal((await pointer.getAddress()).toLowerCase()); + expect(ethlogs[0]["topics"][0]).to.equal(ethers.id("Approval(address,address,uint256)")); + expect(ethlogs[0]["topics"][1].substring(26)).to.equal(owner.substring(2).toLowerCase()); + expect(ethlogs[0]["topics"][2].substring(26)).to.equal(spender.substring(2).toLowerCase()); // send via sei_ endpoint - synthetic event shows up const seilogs = await ethers.provider.send('sei_getLogs', [filter]); expect(seilogs.length).to.equal(1); - expect(seilogs[0]["address"].toLowerCase()).to.equal((await pointer.getAddress()).toLowerCase()); - expect(seilogs[0]["topics"][0]).to.equal(ethers.id("Approval(address,address,uint256)")); - expect(seilogs[0]["topics"][1].substring(26)).to.equal(owner.substring(2).toLowerCase()); - expect(seilogs[0]["topics"][2].substring(26)).to.equal(spender.substring(2).toLowerCase()); }); it("should lower approval", async function () { diff --git a/contracts/test/ERC721toCW721PointerTest.js b/contracts/test/ERC721toCW721PointerTest.js index 2048c69f0..c8026e8c7 100644 --- a/contracts/test/ERC721toCW721PointerTest.js +++ b/contracts/test/ERC721toCW721PointerTest.js @@ -94,17 +94,22 @@ describe("ERC721 to CW721 Pointer", function () { address: await pointerAcc1.getAddress(), topics: [ethers.id("Approval(address,address,uint256)")] }; - // send via eth_ endpoint - synthetic event doesn't show up + // send via eth_ endpoint - synthetic event should show up because we are using the + // synthetic event in place of a real EVM event const ethlogs = await ethers.provider.send('eth_getLogs', [filter]); - expect(ethlogs.length).to.equal(0); + expect(ethlogs.length).to.equal(1); // send via sei_ endpoint - synthetic event shows up const seilogs = await ethers.provider.send('sei_getLogs', [filter]); expect(seilogs.length).to.equal(1); - expect(seilogs[0]["address"].toLowerCase()).to.equal((await pointerAcc1.getAddress()).toLowerCase()); - expect(seilogs[0]["topics"][0]).to.equal(ethers.id("Approval(address,address,uint256)")); - expect(seilogs[0]["topics"][1].substring(26)).to.equal(accounts[0].evmAddress.substring(2).toLowerCase()); - expect(seilogs[0]["topics"][2].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); + + const logs = [...ethlogs, ...seilogs]; + logs.forEach(async (log) => { + expect(log["address"].toLowerCase()).to.equal((await pointer.getAddress()).toLowerCase()); + expect(log["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); + expect(log["topics"][1].substring(26)).to.equal(accounts[0].evmAddress.substring(2).toLowerCase()); + expect(log["topics"][2].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); + }); }); it("cannot approve token you don't own", async function () { @@ -116,7 +121,7 @@ describe("ERC721 to CW721 Pointer", function () { await mine(pointerAcc0.approve(accounts[1].evmAddress, 2)); const blockNumber = await ethers.provider.getBlockNumber(); transferTxResp = await pointerAcc1.transferFrom(accounts[0].evmAddress, accounts[1].evmAddress, 2); - await transferTxResp.wait(); + const receipt = await transferTxResp.wait(); const filter = { fromBlock: '0x' + blockNumber.toString(16), toBlock: 'latest', @@ -125,19 +130,33 @@ describe("ERC721 to CW721 Pointer", function () { }; // send via eth_ endpoint - synthetic event doesn't show up const ethlogs = await ethers.provider.send('eth_getLogs', [filter]); - expect(ethlogs.length).to.equal(0); - + expect(ethlogs.length).to.equal(1); const seilogs = await ethers.provider.send('sei_getLogs', [filter]); expect(seilogs.length).to.equal(1); - expect(seilogs[0]["address"].toLowerCase()).to.equal((await pointerAcc1.getAddress()).toLowerCase()); - expect(seilogs[0]["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); - expect(seilogs[0]["topics"][1].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); - expect(seilogs[0]["topics"][2].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); + const logs = [...ethlogs, ...seilogs]; + logs.forEach(async (log) => { + expect(log["address"].toLowerCase()).to.equal((await pointerAcc1.getAddress()).toLowerCase()); + expect(log["topics"][0]).to.equal(ethers.id("Transfer(address,address,uint256)")); + expect(log["topics"][1].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); + expect(log["topics"][2].substring(26)).to.equal(accounts[1].evmAddress.substring(2).toLowerCase()); + }); + const balance0 = await pointerAcc0.balanceOf(accounts[0].evmAddress); expect(balance0).to.equal(0); const balance1 = await pointerAcc0.balanceOf(accounts[1].evmAddress); expect(balance1).to.equal(2); + // do same for eth_getBlockReceipts and sei_getBlockReceipts + const ethBlockReceipts = await ethers.provider.send('eth_getBlockReceipts', ['0x' + blockNumber.toString(16)]); + expect(ethBlockReceipts.length).to.equal(1); + const seiBlockReceipts = await ethers.provider.send('sei_getBlockReceipts', ['0x' + blockNumber.toString(16)]); + expect(seiBlockReceipts.length).to.equal(1); + + const ethTx = await ethers.provider.send('eth_getTransactionReceipt', [receipt.hash]); + expect(ethTx.logs.length).to.equal(1); + const ethTxByHash = await ethers.provider.send('eth_getTransactionByHash', [receipt.hash]); + expect(ethTxByHash).to.not.be.null; + // return token id 2 back to accounts[0] using safe transfer from await mine(pointerAcc1.approve(accounts[0].evmAddress, 2)); await mine(pointerAcc1.safeTransferFrom(accounts[1].evmAddress, accounts[0].evmAddress, 2)); diff --git a/evmrpc/block.go b/evmrpc/block.go index bbcb9e411..8ff3ecd4b 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "math" "math/big" "strings" "sync" @@ -26,18 +27,20 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +const ShellEVMTxType = math.MaxUint32 + type BlockAPI struct { - tmClient rpcclient.Client - keeper *keeper.Keeper - ctxProvider func(int64) sdk.Context - txConfig client.TxConfig - connectionType ConnectionType - namespace string - includeSyntheticTxs bool + tmClient rpcclient.Client + keeper *keeper.Keeper + ctxProvider func(int64) sdk.Context + txConfig client.TxConfig + connectionType ConnectionType + namespace string + includeShellReceipts bool } func NewBlockAPI(tmClient rpcclient.Client, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, txConfig client.TxConfig, connectionType ConnectionType, namespace string) *BlockAPI { - return &BlockAPI{tmClient: tmClient, keeper: k, ctxProvider: ctxProvider, txConfig: txConfig, connectionType: connectionType, includeSyntheticTxs: shouldIncludeSynthetic(namespace)} + return &BlockAPI{tmClient: tmClient, keeper: k, ctxProvider: ctxProvider, txConfig: txConfig, connectionType: connectionType, includeShellReceipts: shouldIncludeSynthetic(namespace)} } func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number rpc.BlockNumber) (result *hexutil.Uint, returnErr error) { @@ -80,7 +83,7 @@ func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fu return nil, err } blockBloom := a.keeper.GetBlockBloom(a.ctxProvider(block.Block.Height)) - return EncodeTmBlock(a.ctxProvider(LatestCtxHeight), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeSyntheticTxs) + return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts) } func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) { @@ -128,7 +131,7 @@ func (a *BlockAPI) getBlockByNumber(ctx context.Context, number rpc.BlockNumber, return nil, err } blockBloom := a.keeper.GetBlockBloom(a.ctxProvider(block.Block.Height)) - return EncodeTmBlock(a.ctxProvider(LatestCtxHeight), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeSyntheticTxs) + return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts) } func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]interface{}, returnErr error) { @@ -169,7 +172,8 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block mtx.Unlock() } } else { - if !a.includeSyntheticTxs && len(receipt.Logs) > 0 && receipt.Logs[0].Synthetic { + // If the receipt has synthetic logs, we actually want to include them in the response. + if !a.includeShellReceipts && receipt.TxType == ShellEVMTxType { return } encodedReceipt, err := encodeReceipt(receipt, a.txConfig.TxDecoder(), block, func(h common.Hash) bool { @@ -242,6 +246,9 @@ func EncodeTmBlock( if err != nil { continue } + if !includeSyntheticTxs && receipt.TxType == ShellEVMTxType { + continue + } newTx := ethapi.NewRPCTransaction(ethtx, blockhash, number.Uint64(), uint64(blockTime.Second()), uint64(receipt.TransactionIndex), baseFeePerGas, chainConfig) transactions = append(transactions, newTx) } diff --git a/evmrpc/block_test.go b/evmrpc/block_test.go index 1a4c76d0f..5b4959d1a 100644 --- a/evmrpc/block_test.go +++ b/evmrpc/block_test.go @@ -114,11 +114,18 @@ func TestGetBlockReceipts(t *testing.T) { // Query by tag latest => retrieves block 8 resObj3 := sendRequestGood(t, "getBlockReceipts", "latest") result = resObj3["result"].([]interface{}) - require.Equal(t, 1, len(result)) + require.Equal(t, 2, len(result)) receipt1 = result[0].(map[string]interface{}) require.Equal(t, "0x8", receipt1["blockNumber"]) require.Equal(t, "0x0", receipt1["transactionIndex"]) require.Equal(t, multiTxBlockTx4.Hash().Hex(), receipt1["transactionHash"]) + receiptWithSyntheticLog := result[1].(map[string]interface{}) + require.Equal(t, "0x8", receiptWithSyntheticLog["blockNumber"]) + logs := receiptWithSyntheticLog["logs"].([]interface{}) + firstLog := logs[0].(map[string]interface{}) + topics := firstLog["topics"].([]interface{}) + syntheticLogFirstTopic := "0x0000000000000000000000000000000000000000000000000000000000000234" + require.Equal(t, syntheticLogFirstTopic, topics[0].(string)) } func verifyGenesisBlockResult(t *testing.T, resObj map[string]interface{}) { diff --git a/evmrpc/filter.go b/evmrpc/filter.go index 29be263ed..5f06d8ca2 100644 --- a/evmrpc/filter.go +++ b/evmrpc/filter.go @@ -64,7 +64,7 @@ type EventItemDataWrapper struct { } func NewFilterAPI(tmClient rpcclient.Client, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, filterConfig *FilterConfig, connectionType ConnectionType, namespace string) *FilterAPI { - logFetcher := &LogFetcher{tmClient: tmClient, k: k, ctxProvider: ctxProvider, filterConfig: filterConfig, includeSynthetic: shouldIncludeSynthetic(namespace)} + logFetcher := &LogFetcher{tmClient: tmClient, k: k, ctxProvider: ctxProvider, filterConfig: filterConfig, includeSyntheticReceipts: shouldIncludeSynthetic(namespace)} filters := make(map[ethrpc.ID]filter) api := &FilterAPI{ namespace: namespace, @@ -275,11 +275,11 @@ func (a *FilterAPI) UninstallFilter( } type LogFetcher struct { - tmClient rpcclient.Client - k *keeper.Keeper - ctxProvider func(int64) sdk.Context - filterConfig *FilterConfig - includeSynthetic bool + tmClient rpcclient.Client + k *keeper.Keeper + ctxProvider func(int64) sdk.Context + filterConfig *FilterConfig + includeSyntheticReceipts bool } func (f *LogFetcher) GetLogsByFilters(ctx context.Context, crit filters.FilterCriteria, lastToHeight int64) ([]*ethtypes.Log, int64, error) { @@ -366,10 +366,11 @@ func (f *LogFetcher) FindLogsByBloom(height int64, filters [][]bloomIndexes) (re ctx.Logger().Error(fmt.Sprintf("FindLogsByBloom: unable to find receipt for hash %s", hash.Hex())) continue } - if !f.includeSynthetic && len(receipt.Logs) > 0 && receipt.Logs[0].Synthetic { + // if includeShellReceipts is false, include receipts with synthetic logs but exclude shell tx receipts + if !f.includeSyntheticReceipts && receipt.TxType == ShellEVMTxType { continue } - if !f.includeSynthetic && receipt.EffectiveGasPrice == 0 { + if !f.includeSyntheticReceipts && receipt.EffectiveGasPrice == 0 { return } if len(receipt.LogsBloom) > 0 && MatchFilters(ethtypes.Bloom(receipt.LogsBloom), filters) { diff --git a/evmrpc/filter_test.go b/evmrpc/filter_test.go index 4f589d95d..fa0a4f145 100644 --- a/evmrpc/filter_test.go +++ b/evmrpc/filter_test.go @@ -221,7 +221,7 @@ func TestSeiFilterGetLogs(t *testing.T) { }) } -func TestEthEndpointShouldNotReturnSyntheticLogs(t *testing.T) { +func TestEthEndpointCanReturnSyntheticLogs(t *testing.T) { testFilterGetLogs(t, "eth", []GetFilterLogTests{ { name: "filter by single topic with default range, exclude synethetic logs", @@ -230,7 +230,7 @@ func TestEthEndpointShouldNotReturnSyntheticLogs(t *testing.T) { check: func(t *testing.T, log map[string]interface{}) { require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000234", log["topics"].([]interface{})[0].(string)) }, - wantLen: 0, + wantLen: 1, }, }) }