From be37bf21bb36e442fb98dbdf5fd1876c772bf6bf Mon Sep 17 00:00:00 2001 From: Matthieu Vachon Date: Fri, 26 Jul 2024 15:59:09 -0400 Subject: [PATCH] Integration tests showcasing broken duplicated `log.index` --- contracts/src/ERC20PreTransferFromWrapper.sol | 28 +++++ contracts/test/ERC20toCW20PointerTest.js | 107 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 contracts/src/ERC20PreTransferFromWrapper.sol diff --git a/contracts/src/ERC20PreTransferFromWrapper.sol b/contracts/src/ERC20PreTransferFromWrapper.sol new file mode 100644 index 000000000..594a0ba6e --- /dev/null +++ b/contracts/src/ERC20PreTransferFromWrapper.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract ERC20PreTransferFromWrapper { + IERC20 public wrapped; + + event PreTransferFrom( + address indexed from, + address indexed to, + uint256 amount + ); + + constructor(address wrapped_) { + wrapped = IERC20(wrapped_); + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public returns (bool) { + emit PreTransferFrom(from, to, amount); + require(wrapped.transferFrom(from, to, amount), "Transfer from failed"); + return true; + } +} diff --git a/contracts/test/ERC20toCW20PointerTest.js b/contracts/test/ERC20toCW20PointerTest.js index 7f6e84b6e..860cb879b 100644 --- a/contracts/test/ERC20toCW20PointerTest.js +++ b/contracts/test/ERC20toCW20PointerTest.js @@ -257,6 +257,113 @@ describe("ERC20 to CW20 Pointer", function () { await (await pointer.approve(spender.evmAddress, 0)).wait() }); }); + + describe("CW20 event logs", function () { + it("correctly handle synthetic log indexes", async function () { + const ERC20PreTransferFromWrapper = await ethers.getContractFactory("ERC20PreTransferFromWrapper") + const wrappedPointer = await ERC20PreTransferFromWrapper.deploy(await pointer.getAddress()); + + await wrappedPointer.waitForDeployment() + + let sender = accounts[0]; + let recipient = accounts[1]; + + expect(await pointer.balanceOf(sender.evmAddress)).to.equal( + balances.account0 + ); + expect(await pointer.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + ); + + const txCount = 15; + + const tx = await pointer.approve(await wrappedPointer.getAddress(), txCount); + const receipt = await tx.wait(); + + const nonce = await ethers.provider.getTransactionCount( + sender.evmAddress + ); + + const transfer = async (index) => { + let tx + try { + tx = await wrappedPointer.transferFrom(sender.evmAddress, recipient.evmAddress, 1, { + nonce: nonce + (index - 1), + }); + } catch (error) { + console.log(`Transfer ${index} send transaction failed`, error); + throw error; + } + + let receipt + try { + receipt = await tx.wait(); + } catch (error) { + console.log(`Transfer ${index} send transaction failed`, error); + throw error; + } + }; + + let promises = []; + for (let i = 1; i <= txCount; i++) { + promises.push(transfer(i)); + } + + const blockNumber = await ethers.provider.getBlockNumber(); + console.log("Fetch block number", blockNumber, typeof blockNumber); + + await Promise.all(promises); + + expect(await pointer.balanceOf(sender.evmAddress)).to.equal( + balances.account0 - txCount + ); + expect(await pointer.balanceOf(recipient.evmAddress)).to.equal( + balances.account1 + txCount + ); + + // check logs + const filter = { + fromBlock: blockNumber, + toBlock: "latest", + address: await pointer.getAddress(), + topics: [ethers.id("Transfer(address,address,uint256)")], + }; + + const logs = await ethers.provider.getLogs(filter); + expect(logs.length).to.equal(txCount); + + const byBlock = {}; + logs.forEach((log) => { + if (!byBlock[log.blockNumber]) { + byBlock[log.blockNumber] = []; + } + + byBlock[log.blockNumber].push(log); + }); + + // Sanity check to ensure we were able to generate a block with multiple logs + expect( + Object.entries(byBlock).some( + ([blockNumber, logs]) => logs.length > 1 + ) + ).to.be.true; + + Object.entries(byBlock).forEach( + ([blockNumber, logs]) => { + const logIndexes = {} + logs.forEach((log, index) => { + expect(logIndexes[log.index], `all log indexes in block #${blockNumber} should be unique but log's Index value ${log.index} for log at position ${index} has already been seen`).to.be.undefined; + logIndexes[log.index] = index + }) + } + ) + + const cleanupTx = await wrappedPointer + .connect(recipient.signer) + .transfer(sender.evmAddress, txCount); + await cleanupTx.wait(); + }); + }) }); }