diff --git a/contracts/blade/BridgeStorage.sol b/contracts/blade/BridgeStorage.sol index 6e62f6a0..f792728f 100644 --- a/contracts/blade/BridgeStorage.sol +++ b/contracts/blade/BridgeStorage.sol @@ -48,25 +48,19 @@ contract BridgeStorage is ValidatorSetStorage { /** * @notice commits new batch * @param batch new batch - * @param signature aggregated signature of validators that signed the new batch - * @param bitmap bitmap of which validators signed the message */ - function commitBatch( - BridgeMessageBatch calldata batch, - uint256[2] calldata signature, - bytes calldata bitmap - ) external onlySystemCall { + function commitBatch(SignedBridgeMessageBatch calldata batch) external onlySystemCall { _verifyBatch(batch); bytes memory hash = abi.encode( - keccak256(abi.encode(batch.messages, batch.sourceChainId, batch.destinationChainId)) + keccak256( + abi.encode(batch.rootHash, batch.startId, batch.endId, batch.sourceChainId, batch.destinationChainId) + ) ); - verifySignature(bls.hashToPoint(DOMAIN_BRIDGE, hash), signature, bitmap); - SignedBridgeMessageBatch storage signedBatch = batches[batchCounter]; - signedBatch.batch = batch; - signedBatch.signature = signature; - signedBatch.bitmap = bitmap; + verifySignature(bls.hashToPoint(DOMAIN_BRIDGE, hash), batch.signature, batch.bitmap); + + batches[batchCounter] = batch; emit NewBatch(batchCounter); @@ -77,23 +71,15 @@ contract BridgeStorage is ValidatorSetStorage { * @notice Internal function that verifies the batch * @param batch batch to verify */ - function _verifyBatch(BridgeMessageBatch calldata batch) private { - require(batch.messages.length > 0, "EMPTY_BATCH"); - require(lastCommitted[batch.sourceChainId] + 1 == batch.messages[0].id, "INVALID_LAST_COMMITTED"); - - for (uint256 i = 0; i < batch.messages.length; ) { - BridgeMessage memory message = batch.messages[i]; - require(message.sourceChainId == batch.sourceChainId, "INVALID_SOURCE_CHAIN_ID"); - require(message.destinationChainId == batch.destinationChainId, "INVALID_DESTINATION_CHAIN_ID"); - unchecked { - ++i; - } - } + function _verifyBatch(SignedBridgeMessageBatch calldata batch) private { + require(batch.rootHash != bytes32(0), "EMPTY_BATCH"); if (batch.sourceChainId == block.chainid) { - lastCommittedInternal[batch.destinationChainId] = batch.messages[batch.messages.length - 1].id; + require(lastCommittedInternal[batch.destinationChainId] + 1 == batch.startId, "INVALID_LAST_COMMITTED"); + lastCommittedInternal[batch.destinationChainId] = batch.endId; } else { - lastCommitted[batch.sourceChainId] = batch.messages[batch.messages.length - 1].id; + require(lastCommitted[batch.sourceChainId] + 1 == batch.startId, "INVALID_LAST_COMMITTED"); + lastCommitted[batch.sourceChainId] = batch.endId; } } diff --git a/contracts/blade/Gateway.sol b/contracts/blade/Gateway.sol index a0b4e929..a1a1c30d 100644 --- a/contracts/blade/Gateway.sol +++ b/contracts/blade/Gateway.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import "./ValidatorSetStorage.sol"; import "../interfaces/IGateway.sol"; +import "../lib/Merkle.sol"; contract Gateway is ValidatorSetStorage, IGateway { uint256 public constant MAX_LENGTH = 2048; @@ -54,25 +55,32 @@ contract Gateway is ValidatorSetStorage, IGateway { /** * @notice receives the batch of messages and executes them * @param batch batch of messages - * @param signature the aggregated signature submitted by the proposer - * @param bitmap bitmap of which validators signed the message */ // slither-disable-next-line protected-vars function receiveBatch( - BridgeMessageBatch calldata batch, + BridgeMessage[] calldata batch, uint256[2] calldata signature, bytes calldata bitmap ) external { _verifyBatch(batch); bytes memory hash = abi.encode( - keccak256(abi.encode(batch.messages, batch.sourceChainId, batch.destinationChainId)) + keccak256( + abi.encode( + calculateMerkleRoot(batch), + batch[0].id, + batch[batch.length - 1].id, + batch[0].sourceChainId, + batch[0].destinationChainId + ) + ) ); + verifySignature(bls.hashToPoint(DOMAIN_BRIDGE, hash), signature, bitmap); - uint256 length = batch.messages.length; + uint256 length = batch.length; for (uint256 i = 0; i < length; ) { - _executeBridgeMessage(batch.messages[i]); + _executeBridgeMessage(batch[i]); unchecked { ++i; @@ -84,14 +92,15 @@ contract Gateway is ValidatorSetStorage, IGateway { * @notice Internal function that verifies the batch * @param batch batch to verify */ - function _verifyBatch(BridgeMessageBatch calldata batch) private view { - require(batch.messages.length > 0, "EMPTY_BATCH"); + function _verifyBatch(BridgeMessage[] calldata batch) private view { + require(batch.length > 0, "EMPTY_BATCH"); uint256 destinationChainId = block.chainid; - require(batch.destinationChainId == destinationChainId, "INVALID_DESTINATION_CHAIN_ID"); - for (uint256 i = 0; i < batch.messages.length; ) { - BridgeMessage memory message = batch.messages[i]; - require(message.sourceChainId == batch.sourceChainId, "INVALID_SOURCE_CHAIN_ID"); + uint256 sourceChainId = batch[0].sourceChainId; + + for (uint256 i = 0; i < batch.length; ) { + BridgeMessage memory message = batch[i]; + require(message.sourceChainId == sourceChainId, "INVALID_SOURCE_CHAIN_ID"); require(message.destinationChainId == destinationChainId, "INVALID_DESTINATION_CHAIN_ID"); unchecked { ++i; @@ -127,6 +136,21 @@ contract Gateway is ValidatorSetStorage, IGateway { emit BridgeMessageResult(message.id, success, message.sourceChainId, message.destinationChainId, returnData); } + // Function to calculate Merkle Root from an array of BridgeMessages + function calculateMerkleRoot(BridgeMessage[] memory messages) internal pure returns (bytes32) { + require(messages.length > 0, "No messages provided"); + + // Convert the BridgeMessages to their keccak256 hashes (this will be the actual leaves) + bytes32[] memory leaves = new bytes32[](messages.length); + + for (uint256 i = 0; i < messages.length; i++) { + leaves[i] = keccak256(abi.encode(messages[i])); + } + + // Pass the leaves to compute the Merkle root + return Merkle.computeMerkleRoot(leaves); + } + // slither-disable-next-line unused-state,naming-convention uint256[50] private __gap; } diff --git a/contracts/interfaces/blade/IValidatorSetStorage.sol b/contracts/interfaces/blade/IValidatorSetStorage.sol index 3dee27e5..cc84ce98 100644 --- a/contracts/interfaces/blade/IValidatorSetStorage.sol +++ b/contracts/interfaces/blade/IValidatorSetStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity ^0.8.19; bytes32 constant DOMAIN_VALIDATOR_SET = keccak256("DOMAIN_VALIDATOR_SET"); @@ -34,23 +34,20 @@ struct BridgeMessage { } /** - * @param messages list of all messages in batch - * @param sourceChainId id of chain which is source of batch - * @param destinationChainId id of chain which is destination of batch - */ -struct BridgeMessageBatch { - BridgeMessage[] messages; - uint256 sourceChainId; - uint256 destinationChainId; -} - -/** - * @param batch batch that is signed + * @param rootHash root hash of the batch + * @param startId start id of the batch + * @param endId end id of the batch + * @param sourceChainId id of source chain + * @param destinationChainId id of destination chain * @param signature aggregated signature of validators that signed the batch * @param bitmap bitmap of which validators signed the message */ struct SignedBridgeMessageBatch { - BridgeMessageBatch batch; + bytes32 rootHash; + uint256 startId; + uint256 endId; + uint256 sourceChainId; + uint256 destinationChainId; uint256[2] signature; bytes bitmap; } diff --git a/contracts/lib/Merkle.sol b/contracts/lib/Merkle.sol new file mode 100644 index 00000000..99d65f95 --- /dev/null +++ b/contracts/lib/Merkle.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +/** + * @title Merkle tree Lib + * @notice merkle tree helper functions + */ +library Merkle { + /** + * @notice helper function to compute the Merkle Root from an array of already hashed leaves + * @param leaves hashed leaves of the merkle tree + */ + function computeMerkleRoot(bytes32[] memory leaves) internal pure returns (bytes32) { + if (leaves.length == 1) { + return leaves[0]; // If there's only one leaf, it is the root + } + + // Continue hashing pairs of nodes until the root is obtained + while (leaves.length > 1) { + uint256 nextLevelSize = (leaves.length + 1) / 2; + bytes32[] memory nextLevel = new bytes32[](nextLevelSize); + + for (uint256 i = 0; i < leaves.length / 2; i++) { + nextLevel[i] = keccak256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1])); + } + + // If the number of leaves is odd, carry forward the last leaf + if (leaves.length % 2 == 1) { + nextLevel[nextLevel.length - 1] = leaves[leaves.length - 1]; + } + + leaves = nextLevel; + } + + return leaves[0]; + } +} diff --git a/docs/blade/BridgeStorage.md b/docs/blade/BridgeStorage.md index 81a3674d..d5e12e3c 100644 --- a/docs/blade/BridgeStorage.md +++ b/docs/blade/BridgeStorage.md @@ -149,7 +149,7 @@ function batchCounter() external view returns (uint256) ### batches ```solidity -function batches(uint256) external view returns (struct BridgeMessageBatch batch, bytes bitmap) +function batches(uint256) external view returns (bytes32 rootHash, uint256 startId, uint256 endId, uint256 sourceChainId, uint256 destinationChainId, bytes bitmap) ``` @@ -166,7 +166,11 @@ function batches(uint256) external view returns (struct BridgeMessageBatch batch | Name | Type | Description | |---|---|---| -| batch | BridgeMessageBatch | undefined | +| rootHash | bytes32 | undefined | +| startId | uint256 | undefined | +| endId | uint256 | undefined | +| sourceChainId | uint256 | undefined | +| destinationChainId | uint256 | undefined | | bitmap | bytes | undefined | ### bls @@ -206,7 +210,7 @@ function bn256G2() external view returns (contract IBN256G2) ### commitBatch ```solidity -function commitBatch(BridgeMessageBatch batch, uint256[2] signature, bytes bitmap) external nonpayable +function commitBatch(SignedBridgeMessageBatch batch) external nonpayable ``` @@ -217,9 +221,7 @@ function commitBatch(BridgeMessageBatch batch, uint256[2] signature, bytes bitma | Name | Type | Description | |---|---|---| -| batch | BridgeMessageBatch | undefined | -| signature | uint256[2] | undefined | -| bitmap | bytes | undefined | +| batch | SignedBridgeMessageBatch | undefined | ### commitValidatorSet diff --git a/docs/blade/Gateway.md b/docs/blade/Gateway.md index 8dc8bcbc..d1823528 100644 --- a/docs/blade/Gateway.md +++ b/docs/blade/Gateway.md @@ -315,7 +315,7 @@ function processedEvents(uint256) external view returns (bool) ### receiveBatch ```solidity -function receiveBatch(BridgeMessageBatch batch, uint256[2] signature, bytes bitmap) external nonpayable +function receiveBatch(BridgeMessage[] batch, uint256[2] signature, bytes bitmap) external nonpayable ``` @@ -326,7 +326,7 @@ function receiveBatch(BridgeMessageBatch batch, uint256[2] signature, bytes bitm | Name | Type | Description | |---|---|---| -| batch | BridgeMessageBatch | undefined | +| batch | BridgeMessage[] | undefined | | signature | uint256[2] | undefined | | bitmap | bytes | undefined | diff --git a/docs/lib/Merkle.md b/docs/lib/Merkle.md new file mode 100644 index 00000000..4dddfe60 --- /dev/null +++ b/docs/lib/Merkle.md @@ -0,0 +1,12 @@ +# Merkle + + + + + + + + + + + diff --git a/test/blade/BridgeStorage.test.ts b/test/blade/BridgeStorage.test.ts index 0d39cc6f..b4ffc106 100644 --- a/test/blade/BridgeStorage.test.ts +++ b/test/blade/BridgeStorage.test.ts @@ -3,7 +3,11 @@ import * as hre from "hardhat"; import { ethers } from "hardhat"; import { BLS, BN256G2, BridgeStorage } from "../../typechain-types"; import * as mcl from "../../ts/mcl"; -import { bridge } from "../../typechain-types/contracts"; +import { BigNumberish } from "ethers"; +import { + SignedBridgeMessageBatchStruct, + SignedBridgeMessageBatchStructOutput, +} from "../../typechain-types/contracts/blade/BridgeStorage"; const DOMAIN = ethers.utils.arrayify(ethers.utils.solidityKeccak256(["string"], ["DOMAIN_BRIDGE"])); const sourceChainId = 2; @@ -68,64 +72,34 @@ describe("BridgeStorage", () => { }); it("Bridge storage fail: no system call", async () => { - msgs = []; - - msgs = [ - { - id: 1, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - - let sign: [number, number]; - - sign = [1, 1]; - - const batch = { - messages: msgs, + const batch: SignedBridgeMessageBatchStruct = { + rootHash: "0x1555ad6149fc39abc7852aad5c3df6b9df7964ac90ffbbcf6206b1eda846c881", + startId: 1, + endId: 5, sourceChainId: sourceChainId, destinationChainId: destinationChainId, + signature: [100, 200], + bitmap: "0xffff", }; - await expect(bridgeStorage.commitBatch(batch, sign, ethers.constants.AddressZero)) + await expect(bridgeStorage.commitBatch(batch)) .to.be.revertedWithCustomError(bridgeStorage, "Unauthorized") .withArgs("SYSTEMCALL"); }); it("Bridge storage commitBatch fail: invalid signature", async () => { - msgs = []; - - msgs = [ - { - id: 1, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - { - id: 2, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - const bitmapStr = "ffff"; const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, + const batch: SignedBridgeMessageBatchStruct = { + rootHash: "0x1555ad6149fc39abc7852aad5c3df6b9df7964ac90ffbbcf6206b1eda846c881", + startId: 1, + endId: 5, sourceChainId: sourceChainId, destinationChainId: destinationChainId, + signature: [100, 200], + bitmap: bitmap, }; const message = ethers.utils.keccak256( @@ -156,51 +130,28 @@ describe("BridgeStorage", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - await expect(systemBridgeStorage.commitBatch(batch, aggMessagePoint, bitmap)).to.be.revertedWith( - "SIGNATURE_VERIFICATION_FAILED" - ); + await expect(systemBridgeStorage.commitBatch(batch)).to.be.revertedWith("SIGNATURE_VERIFICATION_FAILED"); }); it("Bridge storage commitBatch fail: empty bitmap", async () => { - msgs = []; - - msgs = [ - { - id: 1, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - { - id: 2, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - const bitmapStr = "00"; const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, + const batch: SignedBridgeMessageBatchStruct = { + rootHash: "0x1555ad6149fc39abc7852aad5c3df6b9df7964ac90ffbbcf6206b1eda846c881", + startId: 1, + endId: 5, sourceChainId: sourceChainId, destinationChainId: destinationChainId, + signature: [0, 0], + bitmap: bitmap, }; const messageOfBatch = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] + ["bytes32", "uint256", "uint256", "uint256", "uint256"], + [batch.rootHash, batch.startId, batch.endId, batch.sourceChainId, batch.destinationChainId] ) ); @@ -230,49 +181,30 @@ describe("BridgeStorage", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - await expect(systemBridgeStorage.commitBatch(batch, aggMessagePoint, bitmap)).to.be.revertedWith("BITMAP_IS_EMPTY"); + batch.signature = aggMessagePoint; + + await expect(systemBridgeStorage.commitBatch(batch)).to.be.revertedWith("BITMAP_IS_EMPTY"); }); it("Bridge storage commitBatch fail:not enough voting power", async () => { - msgs = []; - - msgs = [ - { - id: 1, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - { - id: 2, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - const bitmapStr = "01"; const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, + const batch: SignedBridgeMessageBatchStruct = { + rootHash: "0x1555ad6149fc39abc7852aad5c3df6b9df7964ac90ffbbcf6206b1eda846c881", + startId: 1, + endId: 5, sourceChainId: sourceChainId, destinationChainId: destinationChainId, + signature: [0, 0], + bitmap: bitmap, }; const messageOfBatch = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] + ["bytes32", "uint256", "uint256", "uint256", "uint256"], + [batch.rootHash, batch.startId, batch.endId, batch.sourceChainId, batch.destinationChainId] ) ); @@ -302,51 +234,30 @@ describe("BridgeStorage", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - await expect(systemBridgeStorage.commitBatch(batch, aggMessagePoint, bitmap)).to.be.revertedWith( - "INSUFFICIENT_VOTING_POWER" - ); + batch.signature = aggMessagePoint; + + await expect(systemBridgeStorage.commitBatch(batch)).to.be.revertedWith("INSUFFICIENT_VOTING_POWER"); }); it("Bridge storage commitBatch success", async () => { - msgs = []; - - msgs = [ - { - id: 1, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - { - id: 2, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - const bitmapStr = "ffff"; const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, + const batch: SignedBridgeMessageBatchStruct = { + rootHash: "0x1555ad6149fc39abc7852aad5c3df6b9df7964ac90ffbbcf6206b1eda846c881", + startId: 1, + endId: 5, sourceChainId: sourceChainId, destinationChainId: destinationChainId, + signature: [0, 0], + bitmap: bitmap, }; const messageOfBatch = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] + ["bytes32", "uint256", "uint256", "uint256", "uint256"], + [batch.rootHash, batch.startId, batch.endId, batch.sourceChainId, batch.destinationChainId] ) ); @@ -376,64 +287,11 @@ describe("BridgeStorage", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - const firstTx = await systemBridgeStorage.commitBatch(batch, aggMessagePoint, bitmap); + batch.signature = aggMessagePoint; + + const firstTx = await systemBridgeStorage.commitBatch(batch); const firstReceipt = await firstTx.wait(); const firstLogs = firstReceipt?.events?.filter((log) => log.event === "NewBatch") as any[]; expect(firstLogs).to.exist; }); - - it("Bridge storage commitBatch fail: zero messages in batch", async () => { - msgs = []; - - let sign: [number, number]; - - sign = [1, 1]; - - const batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - }; - - await expect(systemBridgeStorage.commitBatch(batch, sign, ethers.constants.AddressZero)).to.be.revertedWith( - "EMPTY_BATCH" - ); - }); - - it("Bridge storage bad commitBatch fail: bad source chain id", async () => { - msgs = []; - - msgs = [ - { - id: 3, - sourceChainId: 1, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - { - id: 4, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - - let sign: [number, number]; - - sign = [0, 0]; - - const batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - }; - - await expect(systemBridgeStorage.commitBatch(batch, sign, ethers.constants.AddressZero)).to.be.revertedWith( - "INVALID_SOURCE_CHAIN_ID" - ); - }); }); diff --git a/test/blade/Gateway.test.ts b/test/blade/Gateway.test.ts index 51afe30c..d03a1e21 100644 --- a/test/blade/Gateway.test.ts +++ b/test/blade/Gateway.test.ts @@ -117,26 +117,12 @@ describe("Gateway", () => { receiver: ethers.constants.AddressZero, payload: ethers.constants.HashZero, }, - { - id: 2, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, ]; const bitmapStr = "ffff"; const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - }; - const message = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode(["bytes32"], [ethers.utils.hexlify(ethers.utils.randomBytes(32))]) ); @@ -165,7 +151,7 @@ describe("Gateway", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - await expect(gateway.receiveBatch(batch, aggMessagePoint, bitmap)).to.be.revertedWith( + await expect(gateway.receiveBatch(msgs, aggMessagePoint, bitmap)).to.be.revertedWith( "SIGNATURE_VERIFICATION_FAILED" ); }); @@ -196,21 +182,27 @@ describe("Gateway", () => { const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - }; + const encodedMessage1 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[0]] + ); + const hash1 = ethers.utils.keccak256(encodedMessage1); + + const encodedMessage2 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[1]] + ); + const hash2 = ethers.utils.keccak256(encodedMessage2); + + const concatenatedHashes = ethers.utils.hexConcat([hash1, hash2]); + const root = ethers.utils.keccak256(concatenatedHashes); const messageOfBatch = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] - ) + ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "uint256"], [root, 1, 2, 2, 3]) ); const message = ethers.utils.defaultAbiCoder.encode(["bytes32"], [messageOfBatch]); @@ -239,7 +231,7 @@ describe("Gateway", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - await expect(gateway.receiveBatch(batch, aggMessagePoint, bitmap)).to.be.revertedWith("BITMAP_IS_EMPTY"); + await expect(gateway.receiveBatch(msgs, aggMessagePoint, bitmap)).to.be.revertedWith("BITMAP_IS_EMPTY"); }); it("Gateway receiveBatch fail:not enough voting power", async () => { @@ -268,21 +260,27 @@ describe("Gateway", () => { const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - }; + const encodedMessage1 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[0]] + ); + const hash1 = ethers.utils.keccak256(encodedMessage1); + + const encodedMessage2 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[1]] + ); + const hash2 = ethers.utils.keccak256(encodedMessage2); + + const concatenatedHashes = ethers.utils.hexConcat([hash1, hash2]); + const root = ethers.utils.keccak256(concatenatedHashes); const messageOfBatch = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] - ) + ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "uint256"], [root, 1, 2, 2, 3]) ); const message = ethers.utils.defaultAbiCoder.encode(["bytes32"], [messageOfBatch]); @@ -311,7 +309,7 @@ describe("Gateway", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - await expect(gateway.receiveBatch(batch, aggMessagePoint, bitmap)).to.be.revertedWith("INSUFFICIENT_VOTING_POWER"); + await expect(gateway.receiveBatch(msgs, aggMessagePoint, bitmap)).to.be.revertedWith("INSUFFICIENT_VOTING_POWER"); }); it("Gateway receiveBatch success", async () => { @@ -340,21 +338,27 @@ describe("Gateway", () => { const bitmap = `0x${bitmapStr}`; - const batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, - }; + const encodedMessage1 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[0]] + ); + const hash1 = ethers.utils.keccak256(encodedMessage1); + + const encodedMessage2 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[1]] + ); + const hash2 = ethers.utils.keccak256(encodedMessage2); + + const concatenatedHashes = ethers.utils.hexConcat([hash1, hash2]); + const root = ethers.utils.keccak256(concatenatedHashes); const messageOfBatch = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] - ) + ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "uint256"], [root, 1, 2, 2, 3]) ); const message = ethers.utils.defaultAbiCoder.encode(["bytes32"], [messageOfBatch]); @@ -383,54 +387,9 @@ describe("Gateway", () => { const aggMessagePoint: mcl.MessagePoint = mcl.g1ToHex(mcl.aggregateRaw(signatures)); - const firstTx = await gateway.receiveBatch(batch, aggMessagePoint, bitmap); + const firstTx = await gateway.receiveBatch(msgs, aggMessagePoint, bitmap); const firstReceipt = await firstTx.wait(); const firstLogs = firstReceipt?.events?.filter((log) => log.event === "BridgeMessageResult") as any[]; expect(firstLogs).to.exist; }); - - it("Gateway receiveBatch fail: zero messages in batch", async () => { - msgs = []; - - let sign: [number, number]; - - sign = [1, 1]; - - const batch = { - messages: msgs, - sourceChainId: 2, - destinationChainId: 3, - }; - - await expect(gateway.receiveBatch(batch, sign, ethers.constants.AddressZero)).to.be.revertedWith("EMPTY_BATCH"); - }); - - it("Gateway receiveBatch fail: bad source chain id", async () => { - msgs = []; - - msgs = [ - { - id: 1, - sourceChainId: 1, - destinationChainId: 3, - sender: ethers.constants.AddressZero, - receiver: ethers.constants.AddressZero, - payload: ethers.constants.HashZero, - }, - ]; - - let sign: [number, number]; - - sign = [0, 0]; - - const batch = { - messages: msgs, - sourceChainId: 2, - destinationChainId: 3, - }; - - await expect(gateway.receiveBatch(batch, sign, ethers.constants.AddressZero)).to.be.revertedWith( - "INVALID_SOURCE_CHAIN_ID" - ); - }); }); diff --git a/test/forge/blade/BridgeStorage.t.sol b/test/forge/blade/BridgeStorage.t.sol index 820cd9c8..ca3abcbc 100644 --- a/test/forge/blade/BridgeStorage.t.sol +++ b/test/forge/blade/BridgeStorage.t.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity ^0.8.19; import "@utils/Test.sol"; import {BridgeStorage} from "contracts/blade/BridgeStorage.sol"; -import {Validator, BridgeMessage, BridgeMessageBatch, DOMAIN_BRIDGE} from "contracts/interfaces/blade/IValidatorSetStorage.sol"; +import {Validator, BridgeMessage, SignedBridgeMessageBatch, DOMAIN_BRIDGE} from "contracts/interfaces/blade/IValidatorSetStorage.sol"; import {BLS} from "contracts/common/BLS.sol"; import {BN256G2} from "contracts/common/BN256G2.sol"; import {System} from "contracts/blade/System.sol"; +import "contracts/lib/Merkle.sol"; abstract contract BridgeStorageTest is Test, System, BridgeStorage { BridgeStorage bridgeStorage; @@ -17,6 +18,7 @@ abstract contract BridgeStorageTest is Test, System, BridgeStorage { bytes[] public bitmaps; uint256[2][] public aggMessagePoints; BridgeMessage[] public msgs; + bytes32 rootHash; function setUp() public virtual { bls = new BLS(); @@ -48,9 +50,13 @@ abstract contract BridgeStorageTest is Test, System, BridgeStorage { validatorSet.push(validatorTemp[i]); } + bytes32[] memory leaves = new bytes32[](messageTmp.length); for (uint256 i = 0; i < messageTmp.length; i++) { msgs.push(messageTmp[i]); + leaves[i] = keccak256(abi.encode(messageTmp[i])); } + + rootHash = Merkle.computeMerkleRoot(leaves); } } @@ -77,31 +83,63 @@ contract BridgeStorageUnitialized is BridgeStorageTest { contract BridgeStorageCommitBatchTests is BridgeStorageInitialized { function testCommitBatch_InvalidSignature() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); + SignedBridgeMessageBatch memory batch = SignedBridgeMessageBatch({ + rootHash: rootHash, + startId: msgs[0].id, + endId: msgs[msgs.length - 1].id, + sourceChainId: 2, + destinationChainId: 3, + signature: aggMessagePoints[0], + bitmap: bitmaps[0] + }); vm.expectRevert("SIGNATURE_VERIFICATION_FAILED"); - bridgeStorage.commitBatch(batch, aggMessagePoints[0], bitmaps[0]); + bridgeStorage.commitBatch(batch); } function testCommitBatch_EmptyBitmap() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); + SignedBridgeMessageBatch memory batch = SignedBridgeMessageBatch({ + rootHash: rootHash, + startId: msgs[0].id, + endId: msgs[msgs.length - 1].id, + sourceChainId: 2, + destinationChainId: 3, + signature: aggMessagePoints[1], + bitmap: bitmaps[1] + }); vm.expectRevert("BITMAP_IS_EMPTY"); - bridgeStorage.commitBatch(batch, aggMessagePoints[1], bitmaps[1]); + bridgeStorage.commitBatch(batch); } function testCommitBatch_NotEnoughPower() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); + SignedBridgeMessageBatch memory batch = SignedBridgeMessageBatch({ + rootHash: rootHash, + startId: msgs[0].id, + endId: msgs[msgs.length - 1].id, + sourceChainId: 2, + destinationChainId: 3, + signature: aggMessagePoints[2], + bitmap: bitmaps[2] + }); vm.expectRevert("INSUFFICIENT_VOTING_POWER"); - bridgeStorage.commitBatch(batch, aggMessagePoints[2], bitmaps[2]); + bridgeStorage.commitBatch(batch); } function testCommitBatch_Success() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); + SignedBridgeMessageBatch memory batch = SignedBridgeMessageBatch({ + rootHash: rootHash, + startId: msgs[0].id, + endId: msgs[msgs.length - 1].id, + sourceChainId: 2, + destinationChainId: 3, + signature: aggMessagePoints[3], + bitmap: bitmaps[3] + }); vm.expectEmit(); emit NewBatch(0); - bridgeStorage.commitBatch(batch, aggMessagePoints[3], bitmaps[3]); + bridgeStorage.commitBatch(batch); } } diff --git a/test/forge/blade/Gateway.t.sol b/test/forge/blade/Gateway.t.sol index 047c735c..fb88af9d 100644 --- a/test/forge/blade/Gateway.t.sol +++ b/test/forge/blade/Gateway.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity ^0.8.19; import "@utils/Test.sol"; import {Gateway} from "contracts/blade/Gateway.sol"; -import {Validator, BridgeMessage, BridgeMessageBatch, DOMAIN_BRIDGE} from "contracts/interfaces/blade/IValidatorSetStorage.sol"; +import {Validator, BridgeMessage, SignedBridgeMessageBatch, DOMAIN_BRIDGE} from "contracts/interfaces/blade/IValidatorSetStorage.sol"; import {BLS} from "contracts/common/BLS.sol"; import {BN256G2} from "contracts/common/BN256G2.sol"; import {System} from "contracts/blade/System.sol"; @@ -91,7 +91,7 @@ contract GatewayStateSyncTest is GatewayInitialized { gateway.sendBridgeMsg(receiver, moreThanMaxData, 1); } - function testCannotSyncState_InvalidDestinationChainId() public { + function testCannotSyncState_InvalidDestinationChainId() public { vm.expectRevert("INVALID_DESTINATION_CHAIN_ID"); gateway.sendBridgeMsg(receiver, maxData, 0); } @@ -117,33 +117,25 @@ contract GatewayStateSyncTest is GatewayInitialized { contract GatewayReceiveBatchTests is GatewayInitialized { function testReceiveBatch_InvalidSignature() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); - vm.expectRevert("SIGNATURE_VERIFICATION_FAILED"); - gateway.receiveBatch(batch, aggMessagePoints[0], bitmaps[0]); + gateway.receiveBatch(msgs, aggMessagePoints[0], bitmaps[0]); } function testReceiveBatch_EmptyBitmap() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); - vm.expectRevert("BITMAP_IS_EMPTY"); - gateway.receiveBatch(batch, aggMessagePoints[1], bitmaps[1]); + gateway.receiveBatch(msgs, aggMessagePoints[1], bitmaps[1]); } function testReceiveBatch_NotEnoughPower() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); - vm.expectRevert("INSUFFICIENT_VOTING_POWER"); - gateway.receiveBatch(batch, aggMessagePoints[2], bitmaps[2]); + gateway.receiveBatch(msgs, aggMessagePoints[2], bitmaps[2]); } function testReceiveBatch_Success() public { - BridgeMessageBatch memory batch = BridgeMessageBatch({messages: msgs, sourceChainId: 2, destinationChainId: 3}); - vm.expectEmit(); emit BridgeMessageResult(1, false, 2, 3, bytes("")); vm.expectEmit(); emit BridgeMessageResult(2, false, 2, 3, bytes("")); - gateway.receiveBatch(batch, aggMessagePoints[3], bitmaps[3]); + gateway.receiveBatch(msgs, aggMessagePoints[3], bitmaps[3]); } } diff --git a/test/forge/blade/generateMsgBridgeStorage.ts b/test/forge/blade/generateMsgBridgeStorage.ts index 79ee91a9..6f6939fa 100644 --- a/test/forge/blade/generateMsgBridgeStorage.ts +++ b/test/forge/blade/generateMsgBridgeStorage.ts @@ -38,11 +38,6 @@ let msgs = [ payload: ethers.utils.id("2233"), }, ]; -let batch = { - messages: msgs, - sourceChainId: sourceChainId, - destinationChainId: destinationChainId, -}; async function generateMsg() { const input = process.argv[2]; @@ -129,15 +124,28 @@ function generateSignature1() { const bitmapStr = "00"; const bitmap = `0x${bitmapStr}`; + + const encodedMessage1 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[0]] + ); + const hash1 = ethers.utils.keccak256(encodedMessage1); + + const encodedMessage2 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[1]] + ); + const hash2 = ethers.utils.keccak256(encodedMessage2); + + const concatenatedHashes = ethers.utils.hexConcat([hash1, hash2]); + const root = ethers.utils.keccak256(concatenatedHashes); + const message = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] - ) + ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "uint256"], [root, 1, 2, 2, 3]) ); const signatures: mcl.Signature[] = []; @@ -173,17 +181,29 @@ function generateSignature2() { const bitmapStr = "01"; const bitmap = `0x${bitmapStr}`; - const message = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] - ) + + const encodedMessage1 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[0]] ); + const hash1 = ethers.utils.keccak256(encodedMessage1); + const encodedMessage2 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[1]] + ); + const hash2 = ethers.utils.keccak256(encodedMessage2); + + const concatenatedHashes = ethers.utils.hexConcat([hash1, hash2]); + const root = ethers.utils.keccak256(concatenatedHashes); + + const message = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "uint256"], [root, 1, 2, 2, 3]) + ); const signatures: mcl.Signature[] = []; let flag = false; @@ -218,15 +238,27 @@ function generateSignature3() { const bitmap = `0x${bitmapStr}`; + const encodedMessage1 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[0]] + ); + const hash1 = ethers.utils.keccak256(encodedMessage1); + + const encodedMessage2 = ethers.utils.defaultAbiCoder.encode( + [ + "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)", + ], + [msgs[1]] + ); + const hash2 = ethers.utils.keccak256(encodedMessage2); + + const concatenatedHashes = ethers.utils.hexConcat([hash1, hash2]); + const root = ethers.utils.keccak256(concatenatedHashes); + const message = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - [ - "tuple(uint256 id, uint256 sourceChainId, uint256 destinationChainId, address sender, address receiver, bytes payload)[]", - "uint256", - "uint256", - ], - [batch.messages, batch.sourceChainId, batch.destinationChainId] - ) + ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "uint256"], [root, 1, 2, 2, 3]) ); const signatures: mcl.Signature[] = [];