diff --git a/contracts/examples/ERC20Messaging.sol b/contracts/examples/ERC20Messaging.sol index 3cfae43..ba3d7c1 100644 --- a/contracts/examples/ERC20Messaging.sol +++ b/contracts/examples/ERC20Messaging.sol @@ -91,12 +91,13 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging { /// @notice Entry point for sending a cross-subnet asset transfer /// @dev The input data is sent to the target subnet externally /// @param targetSubnetId Target subnet ID - /// @param /*receiver*/ Receiver's address (avoiding unused local variable warning) /// @param tokenAddress Address of target token contract + /// @param receiver Receiver's address /// @param amount Amount of token to send - function sendToken(SubnetId targetSubnetId, address /*receiver*/, address tokenAddress, uint256 amount) external { + function sendToken(SubnetId targetSubnetId, address tokenAddress, address receiver, uint256 amount) external { if (_toposCoreAddr.code.length == uint256(0)) revert InvalidToposCore(); _burnTokenFrom(msg.sender, tokenAddress, amount); + emit TokenSent(targetSubnetId, tokenAddress, receiver, amount); _emitMessageSentEvent(targetSubnetId); } @@ -136,16 +137,30 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging { } /// @notice Execute a cross-subnet asset transfer - /// @param indexOfDataInTxRaw Index of data in txRaw - /// @param txRaw Raw transaction data - function _execute(uint256 indexOfDataInTxRaw, bytes calldata txRaw) internal override { - (SubnetId targetSubnetId, address receiver, address tokenAddress, uint256 amount) = abi.decode( - txRaw[indexOfDataInTxRaw + 4:], // omit the 4 bytes function selector - (SubnetId, address, address, uint256) + /// @param logIndexes Array of indexes of the logs to use + /// @param logsAddress Array of addresses of the logs + /// @param logsData Array of data of the logs + /// @param logsTopics Array of topics of the logs + function _execute( + uint256[] memory logIndexes, + address[] memory logsAddress, + bytes[] memory logsData, + bytes32[][] memory logsTopics, + SubnetId networkSubnetId + ) internal override { + // verify that the event was emitted by this contract on the source subnet + uint256 tokenSentEventIndex = logIndexes[0]; + if (logsAddress[tokenSentEventIndex] != address(this)) revert InvalidOriginAddress(); + + // implication on the application contract to verify the target subnet id + // first topic is the event signature & second topic is the target subnet id + bytes32 targetSubnetId = logsTopics[tokenSentEventIndex][1]; + if (SubnetId.unwrap(networkSubnetId) != targetSubnetId) revert InvalidSubnetId(); + + (address tokenAddress, address receiver, uint256 amount) = abi.decode( + logsData[tokenSentEventIndex], + (address, address, uint256) ); - if (!_validateTargetSubnetId(targetSubnetId)) revert InvalidSubnetId(); - - // prevent reentrancy _mintToken(tokenAddress, receiver, amount); } diff --git a/contracts/interfaces/IERC20Messaging.sol b/contracts/interfaces/IERC20Messaging.sol index 9e85f05..6523927 100644 --- a/contracts/interfaces/IERC20Messaging.sol +++ b/contracts/interfaces/IERC20Messaging.sol @@ -18,10 +18,14 @@ interface IERC20Messaging is IToposMessaging { event TokenDeployed(string symbol, address tokenAddress); + event TokenSent(SubnetId indexed targetSubnetId, address tokenAddress, address receiver, uint256 amount); + error BurnFailed(address tokenAddress); error ExceedDailyMintLimit(address tokenAddress); error InvalidAmount(); + error InvalidOriginAddress(); error InvalidSetDailyMintLimitsParams(); + error InvalidSubnetId(); error InvalidTokenDeployer(); error TokenAlreadyExists(address tokenAddress); error TokenDeployFailed(); diff --git a/contracts/interfaces/IToposCore.sol b/contracts/interfaces/IToposCore.sol index f6bd48d..80e73f2 100644 --- a/contracts/interfaces/IToposCore.sol +++ b/contracts/interfaces/IToposCore.sol @@ -10,6 +10,7 @@ interface IToposCore { SubnetId sourceSubnetId; bytes32 stateRoot; bytes32 txRoot; + bytes32 receiptRoot; SubnetId[] targetSubnets; uint32 verifier; CertificateId certId; @@ -22,9 +23,9 @@ interface IToposCore { SubnetId sourceSubnetId; } - event CertStored(CertificateId certId, bytes32 txRoot); + event CertStored(CertificateId certId, bytes32 receiptRoot); - event CrossSubnetMessageSent(SubnetId targetSubnetId); + event CrossSubnetMessageSent(SubnetId indexed targetSubnetId); event Upgraded(address indexed implementation); @@ -84,5 +85,5 @@ interface IToposCore { function sourceSubnetIdExists(SubnetId subnetId) external view returns (bool); - function txRootToCertId(bytes32 txRoot) external view returns (CertificateId); + function receiptRootToCertId(bytes32 receiptRoot) external view returns (CertificateId); } diff --git a/contracts/interfaces/IToposMessaging.sol b/contracts/interfaces/IToposMessaging.sol index 93216b4..f70e408 100644 --- a/contracts/interfaces/IToposMessaging.sol +++ b/contracts/interfaces/IToposMessaging.sol @@ -15,14 +15,19 @@ interface IToposMessaging { } error CertNotPresent(); - error IllegalMemoryAccess(); error InvalidMerkleProof(); - error InvalidSubnetId(); + error InvalidTransactionStatus(); error InvalidToposCore(); + error LogIndexOutOfRange(); error TransactionAlreadyExecuted(); error UnsupportedProofKind(); - function validateMerkleProof(bytes memory proofBlob, bytes32 txHash, bytes32 txRoot) external returns (bool); + function execute(uint256[] calldata logIndexes, bytes calldata proofBlob, bytes32 receiptRoot) external; + + function validateMerkleProof( + bytes memory proofBlob, + bytes32 receiptRoot + ) external returns (bytes memory receiptRaw); function toposCore() external view returns (address); } diff --git a/contracts/topos-core/ToposCore.sol b/contracts/topos-core/ToposCore.sol index 83136dd..795d14d 100644 --- a/contracts/topos-core/ToposCore.sol +++ b/contracts/topos-core/ToposCore.sol @@ -30,8 +30,8 @@ contract ToposCore is IToposCore, AdminMultisigBase, Initializable { /// @notice Mapping to store the last seen certificate for a subnet mapping(SubnetId => IToposCore.StreamPosition) checkpoint; - /// @notice Mapping of transactions root to the certificate ID - mapping(bytes32 => CertificateId) public txRootToCertId; + /// @notice Mapping of receipts root to the certificate ID + mapping(bytes32 => CertificateId) public receiptRootToCertId; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -63,6 +63,7 @@ contract ToposCore is IToposCore, AdminMultisigBase, Initializable { SubnetId sourceSubnetId, bytes32 stateRoot, bytes32 txRoot, + bytes32 receiptRoot, SubnetId[] memory targetSubnets, uint32 verifier, CertificateId certId, @@ -70,7 +71,7 @@ contract ToposCore is IToposCore, AdminMultisigBase, Initializable { bytes memory signature ) = abi.decode( certBytes, - (CertificateId, SubnetId, bytes32, bytes32, SubnetId[], uint32, CertificateId, bytes, bytes) + (CertificateId, SubnetId, bytes32, bytes32, bytes32, SubnetId[], uint32, CertificateId, bytes, bytes) ); certificateSet.insert(CertificateId.unwrap(certId)); // add certificate ID to the CRUD storage set @@ -79,6 +80,7 @@ contract ToposCore is IToposCore, AdminMultisigBase, Initializable { newCert.sourceSubnetId = sourceSubnetId; newCert.stateRoot = stateRoot; newCert.txRoot = txRoot; + newCert.receiptRoot = receiptRoot; newCert.targetSubnets = targetSubnets; newCert.verifier = verifier; newCert.certId = certId; @@ -93,8 +95,8 @@ contract ToposCore is IToposCore, AdminMultisigBase, Initializable { newStreamPosition.position = position; newStreamPosition.sourceSubnetId = sourceSubnetId; - txRootToCertId[txRoot] = certId; // add certificate ID to the transaction root mapping - emit CertStored(certId, txRoot); + receiptRootToCertId[receiptRoot] = certId; // add certificate ID to the receipt root mapping + emit CertStored(certId, receiptRoot); } /// @notice Emits an event to signal a cross subnet message has been sent @@ -198,7 +200,7 @@ contract ToposCore is IToposCore, AdminMultisigBase, Initializable { storedCert.prevId, storedCert.sourceSubnetId, storedCert.stateRoot, - storedCert.txRoot, + storedCert.receiptRoot, storedCert.targetSubnets, storedCert.verifier, storedCert.certId, diff --git a/contracts/topos-core/ToposMessaging.sol b/contracts/topos-core/ToposMessaging.sol index 0b90aec..8bf75c7 100644 --- a/contracts/topos-core/ToposMessaging.sol +++ b/contracts/topos-core/ToposMessaging.sol @@ -26,32 +26,43 @@ contract ToposMessaging is IToposMessaging, EternalStorage { } /// @notice Entry point for executing any message on a target subnet - /// @param indexOfDataInTxRaw Index of tx.data in raw transaction hex - /// @param txRaw RLP encoded raw transaction hex + /// @param logIndexes Indexes of the logs to process /// @param proofBlob RLP encoded proof blob - /// @param txRoot Transactions root - function execute( - uint256 indexOfDataInTxRaw, - bytes calldata proofBlob, - bytes calldata txRaw, - bytes32 txRoot - ) external { + /// @param receiptRoot Receipts root of the block + function execute(uint256[] calldata logIndexes, bytes calldata proofBlob, bytes32 receiptRoot) external { if (_toposCoreAddr.code.length == uint256(0)) revert InvalidToposCore(); - if (txRaw.length < indexOfDataInTxRaw + 4) revert IllegalMemoryAccess(); + if (logIndexes.length < 1) revert LogIndexOutOfRange(); - CertificateId certId = IToposCore(_toposCoreAddr).txRootToCertId(txRoot); + CertificateId certId = IToposCore(_toposCoreAddr).receiptRootToCertId(receiptRoot); if (!IToposCore(_toposCoreAddr).certificateExists(certId)) revert CertNotPresent(); - // In order to validate the transaction pass the entire transaction bytes which is then hashed. - // The transaction hash is used as a leaf to validate the inclusion proof. - bytes32 txHash = keccak256(abi.encodePacked(txRaw)); - if (!validateMerkleProof(proofBlob, txHash, txRoot)) revert InvalidMerkleProof(); + // the raw receipt bytes are taken out of the proof + bytes memory receiptRaw = validateMerkleProof(proofBlob, receiptRoot); + if (receiptRaw.length == uint256(0)) revert InvalidMerkleProof(); + + bytes32 receiptHash = keccak256(abi.encodePacked(receiptRaw)); + if (_isTxExecuted(receiptHash, receiptRoot)) revert TransactionAlreadyExecuted(); + + ( + uint256 status, // uint256 cumulativeGasUsed // bytes memory logsBloom + , + , + address[] memory logsAddress, + bytes32[][] memory logsTopics, + bytes[] memory logsData + ) = _decodeReceipt(receiptRaw); + if (status != 1) revert InvalidTransactionStatus(); + + // verify that provided indexes are within the range of the number of event logs + for (uint256 i = 0; i < logIndexes.length; i++) { + if (logIndexes[i] >= logsAddress.length) revert LogIndexOutOfRange(); + } - if (_isTxExecuted(txHash)) revert TransactionAlreadyExecuted(); + SubnetId networkSubnetId = IToposCore(_toposCoreAddr).networkSubnetId(); // prevent re-entrancy - _setTxExecuted(txHash); - _execute(indexOfDataInTxRaw, txRaw); + _setTxExecuted(receiptHash, receiptRoot); + _execute(logIndexes, logsAddress, logsData, logsTopics, networkSubnetId); } /// @notice Get the address of topos core contract @@ -59,34 +70,32 @@ contract ToposMessaging is IToposMessaging, EternalStorage { return _toposCoreAddr; } - /// @notice Validate a Merkle proof for an external transaction + /// @notice Validate a Merkle proof for an external transaction receipt /// @param proofBlob RLP encoded proof blob - /// @param txHash Transaction hash - /// @param txRoot Transactions root + /// @param receiptRoot Receipts root of the block function validateMerkleProof( bytes memory proofBlob, - bytes32 txHash, - bytes32 txRoot - ) public pure override returns (bool) { + bytes32 receiptRoot + ) public pure override returns (bytes memory receiptRaw) { Proof memory proof = _decodeProofBlob(proofBlob); - if (proof.kind != 1) revert UnsupportedProofKind(); - - bytes memory txRawFromProof = MerklePatriciaProofVerifier.extractProofValue(txRoot, proof.mptKey, proof.stack); - if (txRawFromProof.length == 0) { - // Empty return value for proof of exclusion - return false; - } else { - bytes32 txHashFromProof = keccak256(abi.encodePacked(txRawFromProof)); - return txHash == txHashFromProof; - } + receiptRaw = MerklePatriciaProofVerifier.extractProofValue(receiptRoot, proof.mptKey, proof.stack); } /// @notice Execute the message on a target subnet /// @dev This function should be implemented by the child contract - /// @param indexOfDataInTxRaw Index of tx.data in raw transaction hex - /// @param txRaw RLP encoded raw transaction hex - function _execute(uint256 indexOfDataInTxRaw, bytes calldata txRaw) internal virtual {} + /// @param logIndexes Array of indexes of the logs to use + /// @param logsAddress Array of addresses of the logs + /// @param logsData Array of data of the logs + /// @param logsTopics Array of topics of the logs + /// @param networkSubnetId Subnet id of the network + function _execute( + uint256[] memory logIndexes, + address[] memory logsAddress, + bytes[] memory logsData, + bytes32[][] memory logsTopics, + SubnetId networkSubnetId + ) internal virtual {} /// @notice emit a message sent event from the ToposCore contract function _emitMessageSentEvent(SubnetId targetSubnetId) internal { @@ -94,15 +103,19 @@ contract ToposMessaging is IToposMessaging, EternalStorage { } /// @notice Set a flag to indicate that the asset transfer transaction has been executed - /// @param txHash Hash of asset transfer transaction - function _setTxExecuted(bytes32 txHash) internal { - _setBool(_getTxExecutedKey(txHash), true); + /// @param receiptHash receipt hash + /// @param receiptRoot receipt root + function _setTxExecuted(bytes32 receiptHash, bytes32 receiptRoot) internal { + bytes32 suffix = keccak256(abi.encodePacked(receiptHash, receiptRoot)); + _setBool(_getTxExecutedKey(suffix), true); } /// @notice Get the flag to indicate that the transaction has been executed - /// @param txHash transaction hash - function _isTxExecuted(bytes32 txHash) internal view returns (bool) { - return getBool(_getTxExecutedKey(txHash)); + /// @param receiptHash receipt hash + /// @param receiptRoot receipt root + function _isTxExecuted(bytes32 receiptHash, bytes32 receiptRoot) internal view returns (bool) { + bytes32 suffix = keccak256(abi.encodePacked(receiptHash, receiptRoot)); + return getBool(_getTxExecutedKey(suffix)); } /// @notice Validate that the target subnet id is the same as the subnet id of the topos core @@ -113,9 +126,9 @@ contract ToposMessaging is IToposMessaging, EternalStorage { } /// @notice Get the key for the flag to indicate that the transaction has been executed - /// @param txHash transaction hash - function _getTxExecutedKey(bytes32 txHash) internal pure returns (bytes32) { - return keccak256(abi.encode(PREFIX_EXECUTED, txHash)); + /// @param suffix suffix of the key + function _getTxExecutedKey(bytes32 suffix) internal pure returns (bytes32) { + return keccak256(abi.encode(PREFIX_EXECUTED, suffix)); } /// @notice Decode the proof blob into Proof struct @@ -156,4 +169,46 @@ contract ToposMessaging is IToposMessaging, EternalStorage { assert(nibblesLength == nibbles.length); } + + /// @notice Decode the receipt into its components + /// @param receiptRaw RLP encoded receipt + function _decodeReceipt( + bytes memory receiptRaw + ) + internal + pure + returns ( + uint256 status, + uint256 cumulativeGasUsed, + bytes memory logsBloom, + address[] memory logsAddress, + bytes32[][] memory logsTopics, + bytes[] memory logsData + ) + { + RLPReader.RLPItem[] memory receipt = receiptRaw.toRlpItem().toList(); + + status = receipt[0].toUint(); + cumulativeGasUsed = receipt[1].toUint(); + logsBloom = receipt[2].toBytes(); + + RLPReader.RLPItem[] memory logs = receipt[3].toList(); + logsAddress = new address[](logs.length); + logsTopics = new bytes32[][](logs.length); + logsData = new bytes[](logs.length); + + for (uint256 i = 0; i < logs.length; i++) { + RLPReader.RLPItem[] memory log = logs[i].toList(); + logsAddress[i] = log[0].toAddress(); + + RLPReader.RLPItem[] memory topics = log[1].toList(); + bytes32[] memory topicArray = new bytes32[](topics.length); + for (uint256 j = 0; j < topics.length; j++) { + topicArray[j] = bytes32(topics[j].toUint()); + } + logsTopics[i] = topicArray; + + logsData[i] = log[2].toBytes(); + } + } } diff --git a/package.json b/package.json index a0370b4..680661e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@topos-protocol/topos-smart-contracts", - "version": "1.1.4", + "version": "1.2.0", "description": "Topos Smart Contracts", "repository": { "type": "git", diff --git a/scripts/test/send-token.ts b/scripts/test/send-token.ts index 1845982..735d122 100644 --- a/scripts/test/send-token.ts +++ b/scripts/test/send-token.ts @@ -122,8 +122,8 @@ const main = async function (...args: string[]) { ) const tx = await erc20Messaging.sendToken( cc.TARGET_SUBNET_ID_4, - receiverAddress, tokenAddress, + receiverAddress, amount, { gasLimit: 5_000_000, @@ -145,6 +145,9 @@ const main = async function (...args: string[]) { ' amount:', amount ) + + const remainingBalance = await erc20.balanceOf(wallet.address) + console.log('Remaining balance:', remainingBalance.toString()) } else { console.log('Missing CrossSubnetMessageSent event') } diff --git a/test/topos-core/ToposCore.test.ts b/test/topos-core/ToposCore.test.ts index 06b6230..2d25b2b 100644 --- a/test/topos-core/ToposCore.test.ts +++ b/test/topos-core/ToposCore.test.ts @@ -12,6 +12,7 @@ describe('ToposCore', () => { cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, cc.TX_ROOT_MAX, + cc.RECEIPT_ROOT_MAX, [cc.TARGET_SUBNET_ID_4, cc.TARGET_SUBNET_ID_5], cc.VERIFIER, cc.CERT_ID_1, @@ -142,6 +143,7 @@ describe('ToposCore', () => { checkpoint[2].toString(), cc.STATE_ROOT_MAX, cc.TX_ROOT_MAX, + cc.RECEIPT_ROOT_MAX, [cc.TARGET_SUBNET_ID_4], cc.VERIFIER, checkpoint[0].toString(), @@ -176,6 +178,7 @@ describe('ToposCore', () => { checkpoint[2].toString(), cc.STATE_ROOT_MAX, cc.TX_ROOT_MAX, + cc.RECEIPT_ROOT_MAX, [cc.TARGET_SUBNET_ID_4], cc.VERIFIER, checkpoint[0].toString(), @@ -202,6 +205,7 @@ describe('ToposCore', () => { updatedTestCheckpoint[2].toString(), cc.STATE_ROOT_MAX, cc.TX_ROOT_MAX, + cc.RECEIPT_ROOT_MAX, [cc.TARGET_SUBNET_ID_4], cc.VERIFIER, updatedTestCheckpoint[0].toString(), @@ -231,7 +235,7 @@ describe('ToposCore', () => { const tx = await toposCore.pushCertificate(defaultCert, cc.CERT_POS_1) await expect(tx) .to.emit(toposCore, 'CertStored') - .withArgs(cc.CERT_ID_1, cc.TX_ROOT_MAX) + .withArgs(cc.CERT_ID_1, cc.RECEIPT_ROOT_MAX) }) }) diff --git a/test/topos-core/ToposMessaging.test.ts b/test/topos-core/ToposMessaging.test.ts index f651178..9eac988 100644 --- a/test/topos-core/ToposMessaging.test.ts +++ b/test/topos-core/ToposMessaging.test.ts @@ -2,7 +2,7 @@ import { Contract } from 'ethers' import { deployContractConstant } from '../../scripts/const-addr-deployer' import { ethers, network } from 'hardhat' import { expect } from 'chai' -import { getMptProof } from './shared/utils/mpt_proof' +import { getReceiptMptProof } from './shared/utils/mpt_proof' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { JsonRpcProvider } from '@ethersproject/providers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -31,6 +31,7 @@ describe('ToposMessaging', () => { cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, cc.TX_ROOT_MAX, + cc.RECEIPT_ROOT_MAX, [cc.TARGET_SUBNET_ID_4, cc.TARGET_SUBNET_ID_5], cc.VERIFIER, cc.CERT_ID_1, @@ -80,10 +81,9 @@ describe('ToposMessaging', () => { tokenDeployer.address, toposCore.address ) - const erc20MessagingContract = new ethers.Contract( - erc20Messaging.address, - ERC20Messaging.interface, - admin + const erc20Messaging2 = await ERC20Messaging.deploy( + tokenDeployer.address, + toposCore.address ) return { @@ -94,7 +94,7 @@ describe('ToposMessaging', () => { ERC20, toposCore, erc20Messaging, - erc20MessagingContract, + erc20Messaging2, } } @@ -226,14 +226,8 @@ describe('ToposMessaging', () => { describe('execute', () => { it('deploys a token with zero mint limit', async () => { - const { - admin, - receiver, - ERC20, - toposCore, - erc20Messaging, - erc20MessagingContract, - } = await loadFixture(deployERC20MessagingFixture) + const { admin, receiver, ERC20, toposCore, erc20Messaging } = + await loadFixture(deployERC20MessagingFixture) await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_2) const token = testUtils.encodeTokenParam( tc.TOKEN_NAME, @@ -251,7 +245,7 @@ describe('ToposMessaging', () => { await erc20.approve(erc20Messaging.address, tc.SEND_AMOUNT_50) const sendToken = await sendTokenTx( - erc20MessagingContract, + erc20Messaging, ethers.provider, receiver.address, admin, @@ -259,14 +253,17 @@ describe('ToposMessaging', () => { tokenAddress ) - const { indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await getMptProof(sendToken, ethers.provider) + const { proofBlob, receiptsRoot } = await getReceiptMptProof( + sendToken, + ethers.provider + ) const certificate = testUtils.encodeCertParam( cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - transactionsRoot, + cc.TX_ROOT_MAX, + receiptsRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -275,12 +272,7 @@ describe('ToposMessaging', () => { ) await toposCore.pushCertificate(certificate, cc.CERT_POS_1) await expect( - erc20Messaging.execute( - indexOfTxData, - proofBlob, - txRaw, - transactionsRoot - ) + erc20Messaging.execute([tc.TOKEN_SENT_INDEX_2], proofBlob, receiptsRoot) ) .to.emit(erc20, 'Transfer') .withArgs( @@ -290,62 +282,34 @@ describe('ToposMessaging', () => { ) }) - it('reverts if the index of tx data is out of range', async () => { - const { + it('reverts if the log index is out of range', async () => { + const { admin, receiver, defaultToken, ERC20, erc20Messaging } = + await loadFixture(deployERC20MessagingFixture) + const { proofBlob, receiptsRoot } = await deployDefaultToken( admin, receiver, - defaultToken, ERC20, - erc20Messaging, - erc20MessagingContract, - } = await loadFixture(deployERC20MessagingFixture) - const { indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await deployDefaultToken( - admin, - receiver, - ERC20, - defaultToken, - erc20Messaging, - erc20MessagingContract - ) - - const outOfBoundsMax = 300 + defaultToken, + erc20Messaging + ) await expect( - erc20Messaging.execute( - indexOfTxData + outOfBoundsMax, - proofBlob, - txRaw, - transactionsRoot - ) - ).to.be.revertedWithCustomError(erc20Messaging, 'IllegalMemoryAccess') + erc20Messaging.execute([], proofBlob, receiptsRoot) + ).to.be.revertedWithCustomError(erc20Messaging, 'LogIndexOutOfRange') }) it('reverts if the certificate is not present', async () => { - const { + const { admin, receiver, defaultToken, ERC20, erc20Messaging } = + await loadFixture(deployERC20MessagingFixture) + const { proofBlob, receiptsRoot } = await deployDefaultToken( admin, receiver, - defaultToken, ERC20, - erc20Messaging, - erc20MessagingContract, - } = await loadFixture(deployERC20MessagingFixture) - const { indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await deployDefaultToken( - admin, - receiver, - ERC20, - defaultToken, - erc20Messaging, - erc20MessagingContract - ) + defaultToken, + erc20Messaging + ) await expect( - erc20Messaging.execute( - indexOfTxData, - proofBlob, - txRaw, - transactionsRoot - ) + erc20Messaging.execute([tc.TOKEN_SENT_INDEX_2], proofBlob, receiptsRoot) ).to.be.revertedWithCustomError(erc20Messaging, 'CertNotPresent') }) @@ -357,24 +321,22 @@ describe('ToposMessaging', () => { ERC20, toposCore, erc20Messaging, - erc20MessagingContract, } = await loadFixture(deployERC20MessagingFixture) - const { indexOfTxData, transactionsRoot, txRaw } = - await deployDefaultToken( - admin, - receiver, - ERC20, - defaultToken, - erc20Messaging, - erc20MessagingContract - ) + const { receiptsRoot } = await deployDefaultToken( + admin, + receiver, + ERC20, + defaultToken, + erc20Messaging + ) await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_2) const certificate = testUtils.encodeCertParam( cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - transactionsRoot, + cc.TX_ROOT_MAX, + receiptsRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -385,15 +347,14 @@ describe('ToposMessaging', () => { const fakeProofBlob = '0x01' await expect( erc20Messaging.execute( - indexOfTxData, + [tc.TOKEN_SENT_INDEX_2], fakeProofBlob, - txRaw, - transactionsRoot + receiptsRoot ) ).to.be.reverted }) - it('reverts if the target subnet id is mismatched', async () => { + it('reverts if the transaction is already executed', async () => { const { admin, receiver, @@ -401,24 +362,53 @@ describe('ToposMessaging', () => { ERC20, toposCore, erc20Messaging, - erc20MessagingContract, } = await loadFixture(deployERC20MessagingFixture) - const { indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await deployDefaultToken( - admin, - receiver, - ERC20, - defaultToken, - erc20Messaging, - erc20MessagingContract - ) + const { proofBlob, receiptsRoot } = await deployDefaultToken( + admin, + receiver, + ERC20, + defaultToken, + erc20Messaging + ) - await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_1) // target subnet id = SOURCE_SUBNET_ID_2 + await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_2) + const certificate = testUtils.encodeCertParam( + cc.PREV_CERT_ID_0, + cc.SOURCE_SUBNET_ID_1, + cc.STATE_ROOT_MAX, + cc.TX_ROOT_MAX, + receiptsRoot, + [cc.SOURCE_SUBNET_ID_2], + cc.VERIFIER, + cc.CERT_ID_1, + cc.DUMMY_STARK_PROOF, + cc.DUMMY_SIGNATURE + ) + await toposCore.pushCertificate(certificate, cc.CERT_POS_1) + await erc20Messaging.execute( + [tc.TOKEN_SENT_INDEX_2], + proofBlob, + receiptsRoot + ) + await expect( + erc20Messaging.execute([tc.TOKEN_SENT_INDEX_2], proofBlob, receiptsRoot) + ).to.be.revertedWithCustomError( + erc20Messaging, + 'TransactionAlreadyExecuted' + ) + }) + + it('reverts if the transaction status is invalid', async () => { + const { toposCore, erc20Messaging } = await loadFixture( + deployERC20MessagingFixture + ) + await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_2) const certificate = testUtils.encodeCertParam( cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - transactionsRoot, + cc.TX_ROOT_MAX, + txc.INVALID_STATUS_TRANSACTION.receiptRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -428,15 +418,17 @@ describe('ToposMessaging', () => { await toposCore.pushCertificate(certificate, cc.CERT_POS_1) await expect( erc20Messaging.execute( - indexOfTxData, - proofBlob, - txRaw, - transactionsRoot + [tc.TOKEN_SENT_INDEX_2], + txc.INVALID_STATUS_TRANSACTION.proofBlob, + txc.INVALID_STATUS_TRANSACTION.receiptRoot ) - ).to.be.revertedWithCustomError(erc20Messaging, 'InvalidSubnetId') + ).to.be.revertedWithCustomError( + erc20Messaging, + 'InvalidTransactionStatus' + ) }) - it('reverts if the transaction is already executed', async () => { + it('reverts if the tx is not coming from the same sending contract', async () => { const { admin, receiver, @@ -444,24 +436,23 @@ describe('ToposMessaging', () => { ERC20, toposCore, erc20Messaging, - erc20MessagingContract, + erc20Messaging2, // different sending contract } = await loadFixture(deployERC20MessagingFixture) - const { indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await deployDefaultToken( - admin, - receiver, - ERC20, - defaultToken, - erc20Messaging, - erc20MessagingContract - ) + const { proofBlob, receiptsRoot } = await deployDefaultToken( + admin, + receiver, + ERC20, + defaultToken, + erc20Messaging2 + ) await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_2) const certificate = testUtils.encodeCertParam( cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - transactionsRoot, + cc.TX_ROOT_MAX, + receiptsRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -469,23 +460,45 @@ describe('ToposMessaging', () => { cc.DUMMY_SIGNATURE ) await toposCore.pushCertificate(certificate, cc.CERT_POS_1) - await erc20Messaging.execute( - indexOfTxData, - proofBlob, - txRaw, - transactionsRoot - ) await expect( - erc20Messaging.execute( - indexOfTxData, - proofBlob, - txRaw, - transactionsRoot - ) - ).to.be.revertedWithCustomError( + erc20Messaging.execute([tc.TOKEN_SENT_INDEX_2], proofBlob, receiptsRoot) + ).to.be.revertedWithCustomError(erc20Messaging, 'InvalidOriginAddress') + }) + + it('reverts if the target subnet id is mismatched', async () => { + const { + admin, + receiver, + defaultToken, + ERC20, + toposCore, erc20Messaging, - 'TransactionAlreadyExecuted' + } = await loadFixture(deployERC20MessagingFixture) + const { proofBlob, receiptsRoot } = await deployDefaultToken( + admin, + receiver, + ERC20, + defaultToken, + erc20Messaging + ) + + await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_1) // target subnet id = SOURCE_SUBNET_ID_2 + const certificate = testUtils.encodeCertParam( + cc.PREV_CERT_ID_0, + cc.SOURCE_SUBNET_ID_1, + cc.STATE_ROOT_MAX, + cc.TX_ROOT_MAX, + receiptsRoot, + [cc.SOURCE_SUBNET_ID_2], + cc.VERIFIER, + cc.CERT_ID_1, + cc.DUMMY_STARK_PROOF, + cc.DUMMY_SIGNATURE ) + await toposCore.pushCertificate(certificate, cc.CERT_POS_1) + await expect( + erc20Messaging.execute([tc.TOKEN_SENT_INDEX_2], proofBlob, receiptsRoot) + ).to.be.revertedWithCustomError(erc20Messaging, 'InvalidSubnetId') }) it('reverts if the token is not deployed yet', async () => { @@ -497,7 +510,8 @@ describe('ToposMessaging', () => { cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - txc.UNKNOWN_TOKEN_TRANSACTION.txRoot, + cc.TX_ROOT_MAX, + txc.UNKNOWN_TOKEN_TRANSACTION.receiptRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -507,10 +521,9 @@ describe('ToposMessaging', () => { await toposCore.pushCertificate(certificate, cc.CERT_POS_1) await expect( erc20Messaging.execute( - txc.INDEX_OF_TX_DATA_36, + [tc.TOKEN_SENT_INDEX_0], txc.UNKNOWN_TOKEN_TRANSACTION.proofBlob, - txc.UNKNOWN_TOKEN_TRANSACTION.txRaw, - txc.UNKNOWN_TOKEN_TRANSACTION.txRoot + txc.UNKNOWN_TOKEN_TRANSACTION.receiptRoot ) ).to.be.revertedWithCustomError(erc20Messaging, 'TokenDoesNotExist') }) @@ -525,7 +538,8 @@ describe('ToposMessaging', () => { cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - txc.MINT_EXCEED_TRANSACTION.txRoot, + cc.TX_ROOT_MAX, + txc.MINT_EXCEED_TRANSACTION.receiptRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -535,10 +549,9 @@ describe('ToposMessaging', () => { await toposCore.pushCertificate(certificate, cc.CERT_POS_1) await expect( erc20Messaging.execute( - txc.INDEX_OF_TX_DATA_36, + [tc.TOKEN_SENT_INDEX_2], txc.MINT_EXCEED_TRANSACTION.proofBlob, - txc.MINT_EXCEED_TRANSACTION.txRaw, - txc.MINT_EXCEED_TRANSACTION.txRoot + txc.MINT_EXCEED_TRANSACTION.receiptRoot ) ).to.be.revertedWithCustomError(erc20Messaging, 'ExceedDailyMintLimit') }) @@ -553,7 +566,8 @@ describe('ToposMessaging', () => { cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - txc.ZERO_ADDRESS_TRANSACTION.txRoot, + cc.TX_ROOT_MAX, + txc.ZERO_ADDRESS_TRANSACTION.receiptRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -563,10 +577,9 @@ describe('ToposMessaging', () => { await toposCore.pushCertificate(certificate, cc.CERT_POS_1) await expect( erc20Messaging.execute( - txc.INDEX_OF_TX_DATA_36, + [tc.TOKEN_SENT_INDEX_2], txc.ZERO_ADDRESS_TRANSACTION.proofBlob, - txc.ZERO_ADDRESS_TRANSACTION.txRaw, - txc.ZERO_ADDRESS_TRANSACTION.txRoot + txc.ZERO_ADDRESS_TRANSACTION.receiptRoot ) ).to.be.revertedWith('ERC20: mint to the zero address') }) @@ -579,23 +592,21 @@ describe('ToposMessaging', () => { ERC20, toposCore, erc20Messaging, - erc20MessagingContract, } = await loadFixture(deployERC20MessagingFixture) - const { erc20, indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await deployDefaultToken( - admin, - receiver, - ERC20, - defaultToken, - erc20Messaging, - erc20MessagingContract - ) + const { erc20, proofBlob, receiptsRoot } = await deployDefaultToken( + admin, + receiver, + ERC20, + defaultToken, + erc20Messaging + ) await toposCore.setNetworkSubnetId(cc.SOURCE_SUBNET_ID_2) const certificate = testUtils.encodeCertParam( cc.PREV_CERT_ID_0, cc.SOURCE_SUBNET_ID_1, cc.STATE_ROOT_MAX, - transactionsRoot, + cc.TX_ROOT_MAX, + receiptsRoot, [cc.SOURCE_SUBNET_ID_2], cc.VERIFIER, cc.CERT_ID_1, @@ -604,12 +615,7 @@ describe('ToposMessaging', () => { ) await toposCore.pushCertificate(certificate, cc.CERT_POS_1) await expect( - erc20Messaging.execute( - indexOfTxData, - proofBlob, - txRaw, - transactionsRoot - ) + erc20Messaging.execute([tc.TOKEN_SENT_INDEX_2], proofBlob, receiptsRoot) ) .to.emit(erc20, 'Transfer') .withArgs( @@ -630,8 +636,8 @@ describe('ToposMessaging', () => { await expect( erc20Messaging.sendToken( cc.TARGET_SUBNET_ID_4, - tc.RECIPIENT_ADDRESS, token.address, + tc.RECIPIENT_ADDRESS, tc.SEND_AMOUNT_50 ) ) @@ -650,8 +656,8 @@ describe('ToposMessaging', () => { await expect( erc20Messaging.sendToken( cc.TARGET_SUBNET_ID_4, - tc.RECIPIENT_ADDRESS, tokenAddress, + tc.RECIPIENT_ADDRESS, 0 ) ).to.be.revertedWithCustomError(erc20Messaging, 'InvalidAmount') @@ -669,8 +675,8 @@ describe('ToposMessaging', () => { await expect( erc20Messaging.sendToken( cc.TARGET_SUBNET_ID_4, - tc.RECIPIENT_ADDRESS, tokenAddress, + tc.RECIPIENT_ADDRESS, tc.SEND_AMOUNT_50 ) ) @@ -688,11 +694,12 @@ describe('ToposMessaging', () => { const tokenAddress = logs?.args?.tokenAddress const erc20 = ERC20.attach(tokenAddress) await erc20.approve(erc20Messaging.address, tc.SEND_AMOUNT_50) + await expect( erc20Messaging.sendToken( cc.TARGET_SUBNET_ID_4, - tc.RECIPIENT_ADDRESS, tokenAddress, + tc.RECIPIENT_ADDRESS, tc.SEND_AMOUNT_50 ) ) @@ -702,6 +709,13 @@ describe('ToposMessaging', () => { ethers.constants.AddressZero, tc.SEND_AMOUNT_50 ) + .to.emit(erc20Messaging, 'TokenSent') + .withArgs( + cc.TARGET_SUBNET_ID_4, + tokenAddress, + tc.RECIPIENT_ADDRESS, + tc.SEND_AMOUNT_50 + ) .to.emit(toposCore, 'CrossSubnetMessageSent') .withArgs(cc.TARGET_SUBNET_ID_4) }) @@ -712,8 +726,7 @@ describe('ToposMessaging', () => { receiver: SignerWithAddress, ERC20: ERC20__factory, defaultToken: string, - erc20Messaging: ERC20Messaging, - erc20MessagingContract: Contract + erc20Messaging: ERC20Messaging ) { const tx = await erc20Messaging.deployToken(defaultToken) const txReceipt = await tx.wait() @@ -723,7 +736,7 @@ describe('ToposMessaging', () => { await erc20.approve(erc20Messaging.address, tc.SEND_AMOUNT_50) const sendToken = await sendTokenTx( - erc20MessagingContract, + erc20Messaging, ethers.provider, receiver.address, admin, @@ -731,9 +744,11 @@ describe('ToposMessaging', () => { tokenAddress ) - const { indexOfTxData, proofBlob, transactionsRoot, txRaw } = - await getMptProof(sendToken, ethers.provider) - return { erc20, indexOfTxData, proofBlob, transactionsRoot, txRaw } + const { proofBlob, receiptsRoot } = await getReceiptMptProof( + sendToken, + ethers.provider + ) + return { erc20, proofBlob, receiptsRoot } } async function sendTokenTx( @@ -746,15 +761,15 @@ describe('ToposMessaging', () => { ) { const estimatedGasLimit = await contractInstance.estimateGas.sendToken( targetSubnetId, - receiver, tokenAddress, + receiver, tc.SEND_AMOUNT_50, { gasLimit: 4_000_000 } ) const TxUnsigned = await contractInstance.populateTransaction.sendToken( targetSubnetId, - receiver, tokenAddress, + receiver, tc.SEND_AMOUNT_50, { gasLimit: 4_000_000 } ) diff --git a/test/topos-core/shared/constants/certificates.ts b/test/topos-core/shared/constants/certificates.ts index 4b62b68..bb41fad 100644 --- a/test/topos-core/shared/constants/certificates.ts +++ b/test/topos-core/shared/constants/certificates.ts @@ -16,6 +16,8 @@ export const DUMMY_STARK_PROOF = ethers.utils.formatBytes32String('starkProof') export const DUMMY_SIGNATURE = ethers.utils.formatBytes32String('signature') export const PREV_CERT_ID_0 = '0x0000000000000000000000000000000000000000000000000000000000000000' +export const RECEIPT_ROOT_MAX = + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' export const SOURCE_SUBNET_ID_1 = '0x0000000000000000000000000000000000000000000000000000000000000001' export const SOURCE_SUBNET_ID_2 = diff --git a/test/topos-core/shared/constants/tokens.ts b/test/topos-core/shared/constants/tokens.ts index f5f2c26..9734744 100644 --- a/test/topos-core/shared/constants/tokens.ts +++ b/test/topos-core/shared/constants/tokens.ts @@ -5,5 +5,7 @@ export const MINT_CAP_100_000_000 = 100_000_000 export const RECIPIENT_ADDRESS = '0x0063046686E46Dc6F15918b61AE2B121458534a5' export const SEND_AMOUNT_50 = 50 export const TOKEN_NAME = 'Test Token' +export const TOKEN_SENT_INDEX_0 = 0 +export const TOKEN_SENT_INDEX_2 = 2 export const TOKEN_SYMBOL_X = 'TKX' export const TOKEN_SYMBOL_Y = 'TKY' diff --git a/test/topos-core/shared/constants/transactions.ts b/test/topos-core/shared/constants/transactions.ts index 0fe989e..c43dfc8 100644 --- a/test/topos-core/shared/constants/transactions.ts +++ b/test/topos-core/shared/constants/transactions.ts @@ -1,38 +1,31 @@ -export const INDEX_OF_TX_DATA_36 = 36 - class TransactionData { proofBlob: string - txRaw: string - txRoot: string + receiptRoot: string optionalData?: string[] - constructor( - proofBlob: string, - txRaw: string, - txRoot: string, - optionalData?: string[] - ) { + constructor(proofBlob: string, receiptRoot: string, optionalData?: string[]) { this.proofBlob = proofBlob - this.txRaw = txRaw - this.txRoot = txRoot + this.receiptRoot = receiptRoot this.optionalData = optionalData } } export const MINT_EXCEED_TRANSACTION: TransactionData = new TransactionData( - '0xf8f80180f8f4f8f2822080b8edf8eb08844f22ad6983010e80945fc8d32690cc91d4c39d9d3abcbd16989f87570780b8845c914ec6000000000000000000000000000000000000000000000000000000000000000200000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000080470aae1435c000e381896adce853000cc58c13000000000000000000000000000000000000000000000000000000000000006d82f4f5a0687e37d19b2f7a66e3e0d0ace96ac65fb6b94f195ace9dbd019d87a02a46f301a06df601ef6b564443efbc14d0c5662d685e6c66d0850953a2db143a4167f4e84c', - '0xf8eb08844f22ad6983010e80945fc8d32690cc91d4c39d9d3abcbd16989f87570780b8845c914ec6000000000000000000000000000000000000000000000000000000000000000200000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000080470aae1435c000e381896adce853000cc58c13000000000000000000000000000000000000000000000000000000000000006d82f4f5a0687e37d19b2f7a66e3e0d0ace96ac65fb6b94f195ace9dbd019d87a02a46f301a06df601ef6b564443efbc14d0c5662d685e6c66d0850953a2db143a4167f4e84c', - '0x389cb8f507f0b3a0fdd25f04dcfe33e60b338e37e13aac6cc224c0bb12f322f8' + '0xf9036f0180f9036af90367822080b90361f9035e0183010343b9010004000000000000000000000000000400000000000400000000000000000000000000000000000000000000000000080040000000000000000000000000200001000000000000000000000008000000000000000240000000000000000000000000000000020000000001000100000800000200000000000000000010000000800000000008000000000000000000000000000000000080000000000000000000120000000000001000000100000020000000000000000000000000000000000000000002000000200000000040000000000000002000001000000001000020000010000000000000000000000000000000000000008000000000000000000000f90253f89b9480470aae1435c000e381896adce853000cc58c13f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266a00000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707a00000000000000000000000000000000000000000000000000000000000000000f89b9480470aae1435c000e381896adce853000cc58c13f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000006df8bb945fc8d32690cc91d4c39d9d3abcbd16989f875707f842a0920718295e8e03a7fda1fe1d8d41ba008f378b1d679ea1a5f70eca3a389a2578a00000000000000000000000000000000000000000000000000000000000000002b86000000000000000000000000080470aae1435c000e381896adce853000cc58c1300000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8000000000000000000000000000000000000000000000000000000000000006df85a94cf7ed3acca5a467e9e704c703e8d87f634fb0fc9f842a0f27439dc0ef741d8be6e7efbd4f85c0995f73cc11fc82ad08ede2b7bf735a640a0000000000000000000000000000000000000000000000000000000000000000280', + '0x03bfbb51c66be456dcb0821b03c1ce7d95f0270982de7672d6f9af83ff38aa3a' ) export const UNKNOWN_TOKEN_TRANSACTION: TransactionData = new TransactionData( - '0xf8f80180f8f4f8f2822080b8edf8eb078451d6ca388301141c94dc64a140aa3e981100a9beca4e685f962f0cf6c980b8845c914ec6000000000000000000000000000000000000000000000000000000000000000200000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000084ca534d36b5d544251db6722e245409d7293bb1000000000000000000000000000000000000000000000000000000000000003282f4f6a06704fe2ef8c355cb750faddb21051d58f3198287ea54b2320c1cf2fcf0f2d4a1a025ffe4da9059821e8944ca10c4f6caaa3a8938ccb98f5c0698ac1d6a55c75030', - '0xf8eb078451d6ca388301141c94dc64a140aa3e981100a9beca4e685f962f0cf6c980b8845c914ec6000000000000000000000000000000000000000000000000000000000000000200000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000084ca534d36b5d544251db6722e245409d7293bb1000000000000000000000000000000000000000000000000000000000000003282f4f6a06704fe2ef8c355cb750faddb21051d58f3198287ea54b2320c1cf2fcf0f2d4a1a025ffe4da9059821e8944ca10c4f6caaa3a8938ccb98f5c0698ac1d6a55c75030', - '0xe62585388b2c91456092920d65cfdf960460198ab64024f91b946f422d6eb401' + '0xf902340180f9022ff9022c822080b90226f9022301828425b9010004000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000080000000000000000000000000000000001000000000000000000000000000000000000000240000000000000000000000000000000000000000001000000000000000200000000000000000000000000000000000008000000000000000000000000000000000080000000000000000000000000000000001000000100000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000001000000000000000000000000000000000000000000000000008000000000000000000000f90119f8bb945fc8d32690cc91d4c39d9d3abcbd16989f875707f842a0920718295e8e03a7fda1fe1d8d41ba008f378b1d679ea1a5f70eca3a389a2578a00000000000000000000000000000000000000000000000000000000000000002b860000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000000000032f85a94cf7ed3acca5a467e9e704c703e8d87f634fb0fc9f842a0f27439dc0ef741d8be6e7efbd4f85c0995f73cc11fc82ad08ede2b7bf735a640a0000000000000000000000000000000000000000000000000000000000000000280', + '0xcb65f89f072a5ad7d55d01ea47ff273d723ae875ef41b4733dfb6727d1e042c0' ) export const ZERO_ADDRESS_TRANSACTION: TransactionData = new TransactionData( - '0xf8f80180f8f4f8f2822080b8edf8eb08844f1d2bb483010d7d945fc8d32690cc91d4c39d9d3abcbd16989f87570780b8845c914ec60000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080470aae1435c000e381896adce853000cc58c13000000000000000000000000000000000000000000000000000000000000003282f4f5a08ba005948a0f9028543602166ec2316e5faaa2f118b507a0658434cd190632a2a013d153ce46c595608a626f479f41730f9b3ec3152ecefa176ac3220a7122c3b7', - '0xf8eb08844f1d2bb483010d7d945fc8d32690cc91d4c39d9d3abcbd16989f87570780b8845c914ec60000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080470aae1435c000e381896adce853000cc58c13000000000000000000000000000000000000000000000000000000000000003282f4f5a08ba005948a0f9028543602166ec2316e5faaa2f118b507a0658434cd190632a2a013d153ce46c595608a626f479f41730f9b3ec3152ecefa176ac3220a7122c3b7', - '0xf0e029e097fc35ca535c5da1e2e399bad4a8b841eb4052dd891cc5adb932116c' + '0xf9036f0180f9036af90367822080b90361f9035e0183010253b9010004000000000000000000000000000400000000000400000000000000000000000000000000000000000000000000080040000000000000000000000000200001000000000000000000000008000000000000000240000000000000000000000000000000020000000001000100000800000200000000000000000010000000800000000008000000000000000000000000000000000080000000000000000000120000000000001000000100000020000000000000000000000000000000000000000002000000200000000040000000000000002000001000000001000020000010000000000000000000000000000000000000008000000000000000000000f90253f89b9480470aae1435c000e381896adce853000cc58c13f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266a00000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707a00000000000000000000000000000000000000000000000000000000000000000f89b9480470aae1435c000e381896adce853000cc58c13f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000032f8bb945fc8d32690cc91d4c39d9d3abcbd16989f875707f842a0920718295e8e03a7fda1fe1d8d41ba008f378b1d679ea1a5f70eca3a389a2578a00000000000000000000000000000000000000000000000000000000000000002b86000000000000000000000000080470aae1435c000e381896adce853000cc58c1300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000032f85a94cf7ed3acca5a467e9e704c703e8d87f634fb0fc9f842a0f27439dc0ef741d8be6e7efbd4f85c0995f73cc11fc82ad08ede2b7bf735a640a0000000000000000000000000000000000000000000000000000000000000000280', + '0x3f10bcaa9ca2be4b5fa3493b2f2cb17c55a6494719335fb4139131e643f0d03f' +) + +export const INVALID_STATUS_TRANSACTION: TransactionData = new TransactionData( + '0xf901190180f90114f90111822080b9010bf9010880827d98b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0', + '0x8619f1112d159672b84da1e919bfbbcd4935bd897a5b1274bd2a03dae431fa02' ) diff --git a/test/topos-core/shared/utils/common.ts b/test/topos-core/shared/utils/common.ts index 4ec91cf..9b16922 100644 --- a/test/topos-core/shared/utils/common.ts +++ b/test/topos-core/shared/utils/common.ts @@ -5,6 +5,7 @@ function encodeCertParam( sourceSubnetId: string, stateRoot: string, txRoot: string, + receiptRoot: string, targetSubnets: string[], verifier: number, certId: string, @@ -17,6 +18,7 @@ function encodeCertParam( 'bytes32', 'bytes32', 'bytes32', + 'bytes32', 'bytes32[]', 'uint32', 'bytes32', @@ -28,6 +30,7 @@ function encodeCertParam( sourceSubnetId, stateRoot, txRoot, + receiptRoot, targetSubnets, verifier, certId, diff --git a/test/topos-core/shared/utils/mpt_proof.ts b/test/topos-core/shared/utils/mpt_proof.ts index cc3f612..4f5e642 100644 --- a/test/topos-core/shared/utils/mpt_proof.ts +++ b/test/topos-core/shared/utils/mpt_proof.ts @@ -3,7 +3,7 @@ import { RLP } from '@ethereumjs/rlp' import { Trie } from '@ethereumjs/trie' import { BlockWithTransactions } from '@ethersproject/abstract-provider' -export async function getMptProof( +export async function getReceiptMptProof( tx: ethers.providers.TransactionResponse, provider: ethers.providers.JsonRpcProvider ) { @@ -13,44 +13,49 @@ export async function getMptProof( receipt.blockHash, true, ]) - const transactionsRoot = rawBlock.transactionsRoot - const trie = await createTrie(block) + const receiptsRoot = rawBlock.receiptsRoot + const trie = await createTrie(block, provider) const trieRoot = trie.root() - if ('0x' + trieRoot.toString('hex') !== transactionsRoot) { - throw new Error('Transactions root mismatch') + if ('0x' + trieRoot.toString('hex') !== receiptsRoot) { + throw new Error( + 'Receipts root does not match trie root' + + '\n' + + 'trieRoot: ' + + '0x' + + trieRoot.toString('hex') + + '\n' + + 'receiptsRoot: ' + + receiptsRoot + ) } const indexOfTx = block.transactions.findIndex((_tx) => _tx.hash === tx.hash) const key = Buffer.from(RLP.encode(indexOfTx)) - const txRaw = getRawTransaction(tx) - const indexOfTxData = txRaw.substring(2).indexOf(tx.data.substring(2)) / 2 - const { stack: _stack } = await trie.findPath(key) const stack = _stack.map((node) => node.raw()) const proofBlob = ethers.utils.hexlify(RLP.encode([1, indexOfTx, stack])) - return { indexOfTxData, proofBlob, transactionsRoot, txRaw } + return { proofBlob, receiptsRoot } } -async function createTrie(block: BlockWithTransactions) { +async function createTrie( + block: BlockWithTransactions, + provider: ethers.providers.JsonRpcProvider +) { const trie = new Trie() await Promise.all( block.transactions.map(async (tx, index) => { - const { nonce, gasPrice, gasLimit, to, value, data, v, r, s } = tx + const { /*type,*/ cumulativeGasUsed, logs, logsBloom, status } = + await provider.getTransactionReceipt(tx.hash) return trie.put( Buffer.from(RLP.encode(index)), Buffer.from( RLP.encode([ - nonce, - gasPrice?.toNumber(), - gasLimit.toNumber(), - to, - value.toNumber(), - data, - v, - r, - s, + status, + cumulativeGasUsed.toNumber(), + logsBloom, + logs.map((l) => [l.address, l.topics, l.data]), ]) ) ) @@ -58,25 +63,3 @@ async function createTrie(block: BlockWithTransactions) { ) return trie } - -function getRawTransaction(tx: ethers.providers.TransactionResponse): string { - function addKey(accum, key) { - if (tx[key as keyof typeof tx]) { - accum[key] = tx[key as keyof typeof tx] - } - return accum - } - const txFields = - 'accessList chainId data gasPrice gasLimit maxFeePerGas maxPriorityFeePerGas nonce to type value'.split( - ' ' - ) - const sigFields = 'v r s'.split(' ') - const raw = ethers.utils.serializeTransaction( - txFields.reduce(addKey, {}), - sigFields.reduce(addKey, {}) - ) - if (ethers.utils.keccak256(raw) !== tx.hash) { - throw new Error('serializing failed!') - } - return raw -}