diff --git a/contracts/v2/lib/PolygonRollupBaseEtrog.sol b/contracts/v2/lib/PolygonRollupBaseEtrog.sol index 09c59e003..00eb9e6c6 100644 --- a/contracts/v2/lib/PolygonRollupBaseEtrog.sol +++ b/contracts/v2/lib/PolygonRollupBaseEtrog.sol @@ -406,7 +406,7 @@ contract PolygonRollupBaseEtrog is /** * @notice Allows a sequencer to send multiple batches * @param batches Struct array which holds the necessary data to append new batches to the sequence - * @param l2Coinbase Address that will receive the fees from L2+ + * @param l2Coinbase Address that will receive the fees from L2 * note Pol is not a reentrant token */ function sequenceBatches( diff --git a/contracts/v2/mocks/BridgeReceiverMock.sol b/contracts/v2/mocks/BridgeReceiverMock.sol new file mode 100644 index 000000000..11364419b --- /dev/null +++ b/contracts/v2/mocks/BridgeReceiverMock.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: AGPL-3.0 + +import "../PolygonZkEVMBridgeV2.sol"; +pragma solidity 0.8.20; + +/** + * Contract for compressing and decompressing claim data + */ +contract BridgeReceiverMock { + uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; + + event FallbackEvent(bytes calldataBytes); + + /** + * @dev Emitted when bridge assets or messages to another network + */ + event ClaimAsset( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originTokenAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata + ); + + /** + * @dev Emitted when bridge assets or messages to another network + */ + event ClaimMessage( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originTokenAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata + ); + + /** + * @notice Verify merkle proof and withdraw tokens/ether + * @param smtProofLocalExitRoot Smt proof to proof the leaf against the network exit root + * @param smtProofRollupExitRoot Smt proof to proof the rollupLocalExitRoot against the rollups exit root + * @param globalIndex Global index is defined as: + * | 191 bits | 1 bit | 32 bits | 32 bits | + * | 0 | mainnetFlag | rollupIndex | localRootIndex | + * note that only the rollup index will be used only in case the mainnet flag is 0 + * note that global index do not assert the unused bits to 0. + * This means that when synching the events, the globalIndex must be decoded the same way that in the Smart contract + * to avoid possible synch attacks + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param originNetwork Origin network + * @param originTokenAddress Origin token address, 0 address is reserved for ether + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amount Amount of tokens + * @param metadata Abi encoded metadata if any, empty otherwise + */ + function claimAsset( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originTokenAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes calldata metadata + ) external { + emit ClaimAsset( + smtProofLocalExitRoot, + smtProofRollupExitRoot, + globalIndex, + mainnetExitRoot, + rollupExitRoot, + originNetwork, + originTokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadata + ); + } + + /** + * @notice Verify merkle proof and execute message + * If the receiving address is an EOA, the call will result as a success + * Which means that the amount of ether will be transferred correctly, but the message + * will not trigger any execution + * @param smtProofLocalExitRoot Smt proof to proof the leaf against the exit root + * @param smtProofRollupExitRoot Smt proof to proof the rollupLocalExitRoot against the rollups exit root + * @param globalIndex Global index is defined as: + * | 191 bits | 1 bit | 32 bits | 32 bits | + * | 0 | mainnetFlag | rollupIndex | localRootIndex | + * note that only the rollup index will be used only in case the mainnet flag is 0 + * note that global index do not assert the unused bits to 0. + * This means that when synching the events, the globalIndex must be decoded the same way that in the Smart contract + * to avoid possible synch attacks + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param originNetwork Origin network + * @param originAddress Origin address + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amount message value + * @param metadata Abi encoded metadata if any, empty otherwise + */ + function claimMessage( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes calldata metadata + ) external { + // assembly { + // let ptr := mload(0x40) + + // let size := add(calldatasize(), 28) // 4 bytes calldata + + // mstore(ptr, 0x20) // dataPos + + // mstore(add(ptr, 0x20), size) // data len + + // calldatacopy(add(ptr, 0x40), 0, size) // data + + // return(ptr, add(size, 0x40)) + // } + + emit ClaimMessage( + smtProofLocalExitRoot, + smtProofRollupExitRoot, + globalIndex, + mainnetExitRoot, + rollupExitRoot, + originNetwork, + originAddress, + destinationNetwork, + destinationAddress, + amount, + metadata + ); + } + + fallback() external { + emit FallbackEvent(msg.data); + } +} diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol new file mode 100644 index 000000000..f6dfc47f8 --- /dev/null +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: AGPL-3.0 + +import "../PolygonZkEVMBridgeV2.sol"; +import "hardhat/console.sol"; + +pragma solidity 0.8.20; + +/** + * Contract for compressing and decompressing claim data + */ +contract ClaimCompressor { + uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; + + // Indicate where's the mainnet flag bit in the global index + uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; + + bytes4 private constant _CLAIM_ASSET_SIGNATURE = + PolygonZkEVMBridgeV2.claimAsset.selector; + + bytes4 private constant _CLAIM_MESSAGE_SIGNATURE = + PolygonZkEVMBridgeV2.claimMessage.selector; + + // Bytes that will be added to the snark input for every rollup aggregated + // 4 bytes signature + // 32*32 bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot + // 32*32 bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot + // 32*8 Rest constant parameters + // 32 bytes position, 32 bytes length, + length bytes = 4 + 32*32*2 + 32*8 + 32*2 + length metadata = totalLen + uint256 internal constant _CONSTANT_BYTES_PER_CLAIM = + 4 + 32 * 32 * 2 + 8 * 32 + 32 * 2; + + // Bytes len of arrays of 32 positions, of 32 bytes bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] + uint256 internal constant _BYTE_LEN_CONSTANT_ARRAYS = 32 * 32; + + // The following parameters are constant in the encoded compressed claim call + // mainnetExitRoot, + // rollupExitRoot + uint256 internal constant _CONSTANT_VARIABLES_LENGTH = 32 * 2; + + // 32*32 bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot + // 32*32 bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot + // 32*8 Rest constant parameters + // 32 bytes position + uint256 internal constant _METADATA_OFSSET = 32 * 32 * 2 + 8 * 32 + 32; + + // PolygonZkEVMBridge address + address private immutable _bridgeAddress; + + // Mainnet identifier + uint32 private immutable _networkID; + + /** + * @param smtProofLocalExitRoot Smt proof + * @param smtProofRollupExitRoot Smt proof + * @param globalIndex Index of the leaf + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param originNetwork Origin network + * @param originAddress Origin address + * param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amount message value + * @param metadata Abi encoded metadata if any, empty otherwise + * @param isMessage Bool indicating if it's a message + */ + struct CompressClaimCallData { + bytes32[32] smtProofLocalExitRoot; + bytes32[32] smtProofRollupExitRoot; + uint256 globalIndex; + uint32 originNetwork; + address originAddress; + address destinationAddress; + uint256 amount; + bytes metadata; + bool isMessage; + } + + /** + * @param __bridgeAddress PolygonZkEVMBridge contract address + * @param __networkID Network ID + */ + constructor(address __bridgeAddress, uint32 __networkID) { + _bridgeAddress = __bridgeAddress; + _networkID = __networkID; + } + + /** + * @notice Foward all the claim parameters to compress them inside the contrat + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param compressClaimCalldata compress claim calldata + **/ + function compressClaimCall( + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + CompressClaimCallData[] calldata compressClaimCalldata + ) external pure returns (bytes memory) { + // common parameters for all the claims + bytes memory totalCompressedClaim = abi.encodePacked( + mainnetExitRoot, + rollupExitRoot + ); + bool isRollupSmtSet; + + // If the memory cost goes crazy, might need to do it in assembly D: + for (uint256 i = 0; i < compressClaimCalldata.length; i++) { + CompressClaimCallData + memory currentCompressClaimCalldata = compressClaimCalldata[i]; + + // Compute Local Root compressed + + // compare smt proof against the last one + uint256 lastDifferentLevel = 0; + if (i == 0) { + // compare against hashes of zeroes, TODO, set hashes zeroes on start + lastDifferentLevel = 32; + } else { + for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { + if ( + currentCompressClaimCalldata.smtProofLocalExitRoot[j] != + compressClaimCalldata[i - 1].smtProofLocalExitRoot[j] + ) { + lastDifferentLevel = j + 1; + } + } + } + bytes memory currentSmtCompressed = abi.encodePacked( + uint8(lastDifferentLevel) + ); + for (uint256 j = 0; j < lastDifferentLevel; j++) { + currentSmtCompressed = abi.encodePacked( + currentSmtCompressed, + currentCompressClaimCalldata.smtProofLocalExitRoot[j] + ); + } + + // Compute Rollup Root compressed + lastDifferentLevel = 0; + if ( + currentCompressClaimCalldata.smtProofRollupExitRoot[0] != + bytes32(0) + ) { + if (i == 0 || !isRollupSmtSet) { + // compare against hashes of zeroes, TODO + lastDifferentLevel = 32; + } else { + for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { + if ( + currentCompressClaimCalldata.smtProofRollupExitRoot[ + j + ] != + compressClaimCalldata[i - 1].smtProofRollupExitRoot[ + j + ] + ) { + lastDifferentLevel = j + 1; + } + } + } + isRollupSmtSet = true; + } + + currentSmtCompressed = abi.encodePacked( + currentSmtCompressed, + uint8(lastDifferentLevel) + ); + + for (uint256 j = 0; j < lastDifferentLevel; j++) { + currentSmtCompressed = abi.encodePacked( + currentSmtCompressed, + currentCompressClaimCalldata.smtProofRollupExitRoot[j] + ); + } + + // currentSmtCompressed: + // uint8(lastDifferentLevelLocal) + // smtProofCompressedLocal + // uint8(lastDifferentLevelRollup) + // smtProofCompressedRollup + + bytes memory compressedClaimCall = abi.encodePacked( + uint8(currentCompressClaimCalldata.isMessage ? 1 : 0), // define byte with all small values TODO + currentSmtCompressed, + uint8(currentCompressClaimCalldata.globalIndex >> 64), // define byte with all small values TODO + uint64(currentCompressClaimCalldata.globalIndex), + currentCompressClaimCalldata.originNetwork, + currentCompressClaimCalldata.originAddress, + currentCompressClaimCalldata.destinationAddress, + currentCompressClaimCalldata.amount, // could compress to 128 bits + uint32(currentCompressClaimCalldata.metadata.length), + currentCompressClaimCalldata.metadata + ); + + // Accumulate all claim calls + totalCompressedClaim = abi.encodePacked( + totalCompressedClaim, + compressedClaimCall + ); + } + return totalCompressedClaim; + } + + function sendCompressedClaims( + bytes calldata compressedClaimCalls + ) external { + // TODO get metadata tokens ( max len metadata) + // TODO first rollupExitRoot, instead of zeroes, could be zero hashes, Codecopy?¿ + + // Load "dynamic" constant and immutables since are not accesible from assembly + uint256 destinationNetwork = _networkID; + address bridgeAddress = _bridgeAddress; + + uint256 claimAssetSignature = uint32(_CLAIM_ASSET_SIGNATURE); + uint256 claimMessageSignature = uint32(_CLAIM_MESSAGE_SIGNATURE); + + // no need to be memory-safe, since the rest of the function will happen on assembly + assembly { + // Get the last free memory pointer + // let freeMemPointer := mload(0x40) + // no need to reserve memory since the rest of the funcion will happen on assembly + + let compressedClaimCallsOffset := compressedClaimCalls.offset + let compressedClaimCallsLen := compressedClaimCalls.length + + // Encoded compressed Data: + + // Constant parameters: + + // mainnetExitRoot, + // rollupExitRoot + + // Parameters per claim tx: + // [ + // uint8(currentCompressClaimCalldata.isMessage ? 1 : 0), + // uint8(lastDifferentLevel), + // smtProofCompressed: + // uint8(lastDifferentLevelLocal) + // smtProofCompressedLocal + // uint8(lastDifferentLevelRollup) + // smtProofCompressedRollup + // uint8(currentCompressClaimCalldata.globalIndex >> 64), + // uint64(currentCompressClaimCalldata.globalIndex), + // currentCompressClaimCalldata.originNetwork, + // currentCompressClaimCalldata.originAddress, + // currentCompressClaimCalldata.destinationAddress, + // currentCompressClaimCalldata.amount, // could compress to 128 bits + // uint32(currentCompressClaimCalldata.metadata.length), + // currentCompressClaimCalldata.metadata + // ] + + // Write the constant parameters for all claims in this call + + // TODO set both arrays to zero hashes + // Trick to emtpy the memory, copyng calldata out of bounds, + // set smtProofLocalExitRoot to all zeroes + // calldatacopy( + // 4, // Memory offset, signature = 4 bytes + // calldatasize(), // calldata size + // _BYTE_LEN_CONSTANT_ARRAYS // Copy smtProofRollupExitRoot len + // ) + + // // Copy smtProofRollupExitRoot + // calldatacopy( + // add(4, _BYTE_LEN_CONSTANT_ARRAYS), // Memory offset, signature + smtProofLocalExitRoot = 32 * 32 bytes + 4 bytes + // add(compressedClaimCallsOffset, _BYTE_LEN_CONSTANT_ARRAYS), // calldata offset + // _BYTE_LEN_CONSTANT_ARRAYS // Copy smtProofRollupExitRoot len + // ) + + // Copy mainnetExitRoot + calldatacopy( + add(4, mul(65, 32)), // Memory offset, signature + smtProofLocalExitRoot + smtProofRollupExitRoot + globalIndex = 65 * 32 bytes + 4 bytes + compressedClaimCallsOffset, // calldata offset + 32 // Copy mainnetExitRoot len + ) + + // Copy rollupExitRoot + calldatacopy( + add(4, mul(66, 32)), // Memory offset, signature + smtProofLocalExitRoot + smtProofRollupExitRoot + globalIndex + mainnetExitRoot = 66 * 32 bytes + 4 bytes + add(compressedClaimCallsOffset, 32), // calldata offset, mainnetExitRoot = 32 + 32 // Copy rollupExitRoot len + ) + + // Copy destinationNetwork + + // Memory offset, signature + smtProofLocalExitRoot + smtProofRollupExitRoot + + // globalIndex + mainnetExitRoot + rollupExitRoot + originNetwork + originAddress = 69 * 32 bytes + 4 bytes + mstore(add(4, mul(69, 32)), destinationNetwork) + + // Copy metadata offset + + // Memory offset, signature + smtProofLocalExitRoot + smtProofRollupExitRoot + + // globalIndex + mainnetExitRoot + rollupExitRoot + originNetwork + originAddress + + //destinationNetwork + destinationAddress + amount = 72 * 32 bytes + 4 bytes + mstore(add(4, mul(72, 32)), _METADATA_OFSSET) + + // Start the calldata pointer after the constant parameters + let currentCalldataPointer := add( + compressedClaimCallsOffset, + _CONSTANT_VARIABLES_LENGTH + ) + + for { + // initialization block, empty + } lt( + currentCalldataPointer, + add(compressedClaimCallsOffset, compressedClaimCallsLen) + ) { + // after iteration block, empty + } { + // loop block, non empty ;) + + // THe final calldata should be something like: + // function claimMessage/claimAsset( + // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, + // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, --> constant + // uint256 globalIndex, + // bytes32 mainnetExitRoot, --> constant + // bytes32 rollupExitRoot, --> constant + // uint32 originNetwork, + // address originAddress, + // uint32 destinationNetwork, --> constant + // address destinationAddress, + // uint256 amount, + // bytes calldata metadata + // ) + + // Read uint8 isMessageBool + switch shr(248, calldataload(currentCalldataPointer)) + case 0 { + // TODO optimization + // Write asset signature + mstore8(3, claimAssetSignature) + mstore8(2, shr(8, claimAssetSignature)) + mstore8(1, shr(16, claimAssetSignature)) + mstore8(0, shr(24, claimAssetSignature)) + } + case 1 { + mstore8(3, claimMessageSignature) + mstore8(2, shr(8, claimMessageSignature)) + mstore8(1, shr(16, claimMessageSignature)) + mstore8(0, shr(24, claimMessageSignature)) + } + + // Add 1 byte of isMessage TODO + currentCalldataPointer := add(currentCalldataPointer, 1) + + // Mem pointer where the current data must be written + let memPointer := 4 + + // load lastDifferentLevelLocal + let smtProofBytesToCopy := mul( + shr( + 248, // 256 - 8(lastDifferentLevelLocal) = 248 + calldataload(currentCalldataPointer) + ), + 32 + ) + + // Add 1 byte of lastDifferentLevelLocal + currentCalldataPointer := add(currentCalldataPointer, 1) + + calldatacopy( + memPointer, // Memory offset + currentCalldataPointer, // calldata offset + smtProofBytesToCopy // Copy smtProofBytesToCopy len + ) + + // Add smtProofBytesToCopy bits of smtProofCompressed + currentCalldataPointer := add( + currentCalldataPointer, + smtProofBytesToCopy + ) + // mem pointer, add smtProofLocalExitRoot(current) + memPointer := add(memPointer, mul(32, 32)) + + // load lastDifferentLevelRollup + smtProofBytesToCopy := mul( + shr( + 248, // 256 - 8(lastDifferentLevelRollup) = 248 + calldataload(currentCalldataPointer) + ), + 32 + ) + + // Add 1 byte of lastDifferentLevelRollup + currentCalldataPointer := add(currentCalldataPointer, 1) + + calldatacopy( + memPointer, // Memory offset + currentCalldataPointer, // calldata offset + smtProofBytesToCopy // Copy smtProofBytesToCopy len + ) + + // Add smtProofBytesToCopy bits of smtProofCompressed + currentCalldataPointer := add( + currentCalldataPointer, + smtProofBytesToCopy + ) + + // mem pointer, add smtProofRollupExitRoot(current) + memPointer := add(memPointer, mul(32, 32)) + + // Copy global index + // bool(globalIndex[i] & _GLOBAL_INDEX_MAINNET_FLAG != 0), // get isMainnet bool + // uint64(globalIndex[i]), + + // Since we cannot copy 65 bits, copy first mainnet flag + + // global exit root --> first 23 bytes to 0 + // | 191 bits | 1 bit | 32 bits | 32 bits | + // | 0 | mainnetFlag | rollupIndex | localRootIndex | + mstore8( + add(memPointer, 23), // 23 bytes globalIndex Offset + shr(248, calldataload(currentCalldataPointer)) // 256 - 8(lastDifferentLevel) = 248 + ) + + // Add 1 bytes of uint8(globalIndex[i] >> 64) + currentCalldataPointer := add(currentCalldataPointer, 1) + + // Copy the next 64 bits for the uint64(globalIndex[i]), + calldatacopy( + add(memPointer, 24), // 24 bytes globalIndex Offset + currentCalldataPointer, // calldata offset + 8 // Copy uint64(globalIndex[i]) + ) + currentCalldataPointer := add(currentCalldataPointer, 8) + + // mem pointer, add globalIndex(current) + mainnetExitRoot(constant) + rollupExitRoot(constant) = 32*3 bytes + memPointer := add(memPointer, 96) + + // Copy the next 4 bytes for the originNetwork[i] + calldatacopy( + add(memPointer, 28), // 28 uint32 offset + currentCalldataPointer, // calldata offset + 4 // Copy originNetwork[i] + ) + currentCalldataPointer := add(currentCalldataPointer, 4) + + // mem pointer, add originNetwork(current) + memPointer := add(memPointer, 32) + + // Copy the next 20 bytes for the originAddress[i] + calldatacopy( + add(memPointer, 12), // 12 address offset + currentCalldataPointer, // calldata offset + 20 // Copy originAddress[i] + ) + currentCalldataPointer := add(currentCalldataPointer, 20) + + // mem pointer, add originAddress(current) + destinationNetwork (constant) + memPointer := add(memPointer, 64) + + // amount[i], // could compress to 128 bits + // uint32(metadata[i].length), + // metadata[i] + + // Copy the next 20 bytes for the destinationAddress[i] + calldatacopy( + add(memPointer, 12), // 12 address offset + currentCalldataPointer, // calldata offset + 20 // Copy destinationAddress[i] + ) + currentCalldataPointer := add(currentCalldataPointer, 20) + + // mem pointer, add destinationAddress(current) + memPointer := add(memPointer, 32) + + // Copy the next 32 bytes for the amount[i] + calldatacopy( + memPointer, // 0 uint256 offset + currentCalldataPointer, // calldata offset + 32 // Copy amount[i] + ) + currentCalldataPointer := add(currentCalldataPointer, 32) + + // mem pointer, add amount(current), add metadataOffset (constant) + memPointer := add(memPointer, 64) + + // Copy the next 4 bytes for the uint32(metadata[i].length) + + // load metadataLen + let metadataLen := shr( + 224, // 256 - 32(uint32(metadata[i].length)) = 224 + calldataload(currentCalldataPointer) + ) + + mstore(memPointer, metadataLen) + + currentCalldataPointer := add(currentCalldataPointer, 4) + + // mem pointer, add metadata len + memPointer := add(memPointer, 32) + + // Write metadata + + // Copy the next metadataLen bytes for themetadata + calldatacopy( + memPointer, // mem offset + currentCalldataPointer, // calldata offset + metadataLen // Copy metadataLen bytes + ) + + currentCalldataPointer := add( + currentCalldataPointer, + metadataLen + ) + + memPointer := add(memPointer, metadataLen) + + // clean mem + mstore(memPointer, 0) + + // metadata len should be a multiple of 32 bytes + let totalLenCall := add( + _CONSTANT_BYTES_PER_CLAIM, + add(metadataLen, mod(sub(32, mod(metadataLen, 32)), 32)) + ) + + // SHould i limit the gas TODO of the call + let success := call( + gas(), // gas + bridgeAddress, // address + 0, // value + 0, // args offset + totalLenCall, // argsSize + 0, // retOffset + 0 // retSize + ) + } + } + } +} diff --git a/deployment/v2/4_createRollup.ts b/deployment/v2/4_createRollup.ts index 393c52934..daadd02fb 100644 --- a/deployment/v2/4_createRollup.ts +++ b/deployment/v2/4_createRollup.ts @@ -286,6 +286,8 @@ async function main() { // Setup data commitee to 0 await (await polygonDataCommittee?.setupCommittee(0, [], "0x")).wait(); + } else { + await (await polygonDataCommittee?.transferOwnership(adminZkEVM)).wait(); } outputJson.polygonDataCommittee = polygonDataCommittee?.target; diff --git a/deployment/v2/README.md b/deployment/v2/README.md index 4fee2f052..62e1c68ed 100644 --- a/deployment/v2/README.md +++ b/deployment/v2/README.md @@ -55,6 +55,7 @@ A new folder will be created witth the following name `deployments/${network}_$( ## deploy-parameters.json +- `test` : bool, Indicate if it's a test deployment, which will fund the deployer address with pre minted ether and will give more powers to the deployer address to make easier the flow. - `timelockAdminAddress`: address, Timelock owner address, able to send start an upgradability process via timelock - `minDelayTimelock`: number, Minimum timelock delay, - `salt`: bytes32, Salt used in `PolygonZkEVMDeployer` to deploy deterministic contracts, such as the PolygonZkEVMBridge diff --git a/deployment/v2/verifyContracts.js b/deployment/v2/verifyContracts.js index 8c5390ae3..6a583b0f1 100644 --- a/deployment/v2/verifyContracts.js +++ b/deployment/v2/verifyContracts.js @@ -168,7 +168,7 @@ async function main() { // verify zkEVM address or validium - if (createRollupOutputParameters.consensusContract == "PolygonZkEVMEtrog") { + if (createRollupOutputParameters.consensusContract === 'PolygonZkEVMEtrog') { try { await hre.run( 'verify:verify', @@ -186,28 +186,25 @@ async function main() { } catch (error) { // expect(error.message.toLowerCase().includes('proxyadmin')).to.be.equal(true); } - } else { - if(createRollupOutputParameters.consensusContract == "PolygonValidiumEtrog") { - try { - await hre.run( - 'verify:verify', - { - contract: 'contracts/v2/consensus/validium/PolygonValidiumEtrog.sol:PolygonValidiumEtrog', - address: createRollupOutputParameters.rollupAddress, - constructorArguments: [ - deployOutputParameters.polygonZkEVMGlobalExitRootAddress, - deployOutputParameters.polTokenAddress, - deployOutputParameters.polygonZkEVMBridgeAddress, - deployOutputParameters.polygonRollupManager, - ], - }, - ); - } catch (error) { - // expect(error.message.toLowerCase().includes('proxyadmin')).to.be.equal(true); - } + } else if (createRollupOutputParameters.consensusContract === 'PolygonValidiumEtrog') { + try { + await hre.run( + 'verify:verify', + { + contract: 'contracts/v2/consensus/validium/PolygonValidiumEtrog.sol:PolygonValidiumEtrog', + address: createRollupOutputParameters.rollupAddress, + constructorArguments: [ + deployOutputParameters.polygonZkEVMGlobalExitRootAddress, + deployOutputParameters.polTokenAddress, + deployOutputParameters.polygonZkEVMBridgeAddress, + deployOutputParameters.polygonRollupManager, + ], + }, + ); + } catch (error) { + // expect(error.message.toLowerCase().includes('proxyadmin')).to.be.equal(true); } } - } main() diff --git a/hardhat.config.ts b/hardhat.config.ts index 3a207099d..42b6c9a73 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -126,6 +126,17 @@ const config: HardhatUserConfig = { evmVersion: "shanghai", }, // try yul optimizer }, + "contracts/v2/utils/ClaimCompressor.sol": { + version: "0.8.20", + settings: { + optimizer: { + enabled: true, + runs: 999999, + }, + evmVersion: "shanghai", + //viaIR: true, + }, + }, }, }, networks: { diff --git a/package-lock.json b/package-lock.json index 8d7f98dd7..2fc51a05d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@0xpolygonhermez/zkevm-contracts", - "version": "2.0.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@0xpolygonhermez/zkevm-contracts", - "version": "2.0.0", + "version": "3.0.0", "license": "pending", "devDependencies": { "@0xpolygonhermez/zkevm-commonjs": "github:0xPolygonHermez/zkevm-commonjs#main", diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts new file mode 100644 index 000000000..29c79a0b3 --- /dev/null +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -0,0 +1,615 @@ +import {expect} from "chai"; +import {ethers, upgrades} from "hardhat"; +import { + VerifierRollupHelperMock, + ERC20PermitMock, + PolygonRollupManagerMock, + PolygonZkEVMGlobalExitRoot, + PolygonZkEVMBridgeV2, + PolygonZkEVMV2, + PolygonRollupBase, + TokenWrapped, + ClaimCompressor, + BridgeReceiverMock, +} from "../../typechain-types"; +import {takeSnapshot, time} from "@nomicfoundation/hardhat-network-helpers"; +import {processorUtils, contractUtils, MTBridge, mtBridgeUtils} from "@0xpolygonhermez/zkevm-commonjs"; +const {calculateSnarkInput, calculateAccInputHash, calculateBatchHashData} = contractUtils; +const MerkleTreeBridge = MTBridge; +const {verifyMerkleProof, getLeafValue} = mtBridgeUtils; +import {MemDB, ZkEVMDB, getPoseidon, smtUtils, processorUtils} from "@0xpolygonhermez/zkevm-commonjs"; +import {deploy} from "@openzeppelin/hardhat-upgrades/dist/utils"; +import {parse} from "yargs"; + +describe("PolygonZkEVMBridge Contract", () => { + upgrades.silenceWarnings(); + + let claimCompressor: ClaimCompressor; + let bridgeReceiverMock: BridgeReceiverMock; + let polygonZkEVMBridgeContract: PolygonZkEVMBridgeV2; + let polTokenContract: ERC20PermitMock; + let polygonZkEVMGlobalExitRoot: PolygonZkEVMGlobalExitRoot; + + const networkID = 1; + + const tokenName = "Matic Token"; + const tokenSymbol = "MATIC"; + const decimals = 18; + const tokenInitialBalance = ethers.parseEther("20000000"); + const metadataToken = ethers.AbiCoder.defaultAbiCoder().encode( + ["string", "string", "uint8"], + [tokenName, tokenSymbol, decimals] + ); + const networkIDMainnet = 0; + const networkIDRollup = 1; + + const LEAF_TYPE_ASSET = 0; + const LEAF_TYPE_MESSAGE = 1; + + let deployer: any; + let rollupManager: any; + let acc1: any; + let bridge: any; + + beforeEach("Deploy contracts", async () => { + // load signers + [deployer, rollupManager, acc1, bridge] = await ethers.getSigners(); + + // deploy receiver + const BridgeReceiverFactory = await ethers.getContractFactory("BridgeReceiverMock"); + bridgeReceiverMock = await BridgeReceiverFactory.deploy(); + await bridgeReceiverMock.waitForDeployment(); + + // deploy global exit root manager + const ClaimCompressorFactory = await ethers.getContractFactory("ClaimCompressor"); + claimCompressor = await ClaimCompressorFactory.deploy(bridgeReceiverMock.target, networkID); + await claimCompressor.waitForDeployment(); + + // Deploy bridge contracts + // deploy PolygonZkEVMBridge + const polygonZkEVMBridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridgeV2"); + polygonZkEVMBridgeContract = (await upgrades.deployProxy(polygonZkEVMBridgeFactory, [], { + initializer: false, + unsafeAllow: ["constructor"], + })) as unknown as PolygonZkEVMBridgeV2; + + // deploy global exit root manager + const PolygonZkEVMGlobalExitRootFactory = await ethers.getContractFactory("PolygonZkEVMGlobalExitRoot"); + polygonZkEVMGlobalExitRoot = await PolygonZkEVMGlobalExitRootFactory.deploy( + rollupManager.address, + polygonZkEVMBridgeContract.target + ); + + await polygonZkEVMBridgeContract.initialize( + networkID, + ethers.ZeroAddress, // zero for ether + ethers.ZeroAddress, // zero for ether + polygonZkEVMGlobalExitRoot.target, + rollupManager.address, + "0x" + ); + + // deploy token + const maticTokenFactory = await ethers.getContractFactory("ERC20PermitMock"); + polTokenContract = await maticTokenFactory.deploy( + tokenName, + tokenSymbol, + deployer.address, + tokenInitialBalance + ); + }); + + it("should check random values", async () => { + const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridgeV2"); + + // compute root merkle tree in Js + const height = 32; + const merkleTreeLocal = new MerkleTreeBridge(height); + + const totalLeafsMerkleTree = 20; + + const leafs = []; + for (let i = 0; i < totalLeafsMerkleTree; i++) { + // Create a random merkle tree + const originNetwork = ethers.hexlify(ethers.randomBytes(4)); + const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); + const amount = ethers.parseEther("10"); + const destinationNetwork = networkID; // fixed by contract + const destinationAddress = ethers.hexlify(ethers.randomBytes(20)); + const metadata = ethers.hexlify(ethers.randomBytes(Math.floor(Math.random() * 1000))); + const metadataHash = ethers.solidityPackedKeccak256(["bytes"], [metadata]); + const leafType = Math.floor(Math.random() * 2); + const leafValue = getLeafValue( + leafType, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadataHash + ); + merkleTreeLocal.add(leafValue); + leafs.push({ + leafType, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadata, + }); + } + + const mainnetExitRoot = merkleTreeLocal.getRoot(); + const rollupExitRoot = ethers.hexlify(ethers.randomBytes(32)); + + // Mock rollup root, not necessary now + const randomIndex = 10; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(randomIndex); + + // Compute calldatas + for (let i = 1; i < totalLeafsMerkleTree; i++) { + const sequenceForcedStructs = [] as any; + + for (let j = 0; j < i; j++) { + const index = j; + const currentLeaf = leafs[j]; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(j); + + const sequenceForced = { + smtProofRollupExitRoot: proofLocal, + smtProofLocalExitRoot: proofLocal, + globalIndex: index, + originNetwork: currentLeaf.originNetwork, + originAddress: currentLeaf.tokenAddress, + destinationAddress: currentLeaf.destinationAddress, + amount: currentLeaf.amount, + metadata: currentLeaf.metadata, + isMessage: currentLeaf.leafType, + } as any; + + sequenceForcedStructs.push(sequenceForced); + } + + const compressedMultipleBytes = await claimCompressor.compressClaimCall( + mainnetExitRoot, + rollupExitRoot, + sequenceForcedStructs + ); + + // ASsert correctness + const receipt = await (await claimCompressor.sendCompressedClaims(compressedMultipleBytes)).wait(); + for (let k = 0; k < receipt?.logs.length; k++) { + const currentLog = receipt?.logs[k]; + const currenSequenceForcedStructs = sequenceForcedStructs[k]; + + const decodeFunctionName = currenSequenceForcedStructs.isMessage ? "claimMessage" : "claimAsset"; + + const encodedCall = BridgeFactory.interface.encodeFunctionData(decodeFunctionName, [ + currenSequenceForcedStructs.smtProofLocalExitRoot, + proofLocal, + currenSequenceForcedStructs.globalIndex, + mainnetExitRoot, + rollupExitRoot, + currenSequenceForcedStructs.originNetwork, + currenSequenceForcedStructs.originAddress, + networkID, // constant + currenSequenceForcedStructs.destinationAddress, + currenSequenceForcedStructs.amount, + currenSequenceForcedStructs.metadata, + ]); + + const parsedLog = bridgeReceiverMock.interface.parseLog(currentLog); + expect(parsedLog?.args.smtProofLocalExitRoot).to.be.deep.equal( + currenSequenceForcedStructs.smtProofLocalExitRoot + ); + expect(parsedLog?.args.smtProofRollupExitRoot).to.be.deep.equal( + currenSequenceForcedStructs.smtProofRollupExitRoot + ); + expect(parsedLog?.args.globalIndex).to.be.equal(currenSequenceForcedStructs.globalIndex); + expect(parsedLog?.args.mainnetExitRoot).to.be.equal(mainnetExitRoot); + expect(parsedLog?.args.rollupExitRoot).to.be.equal(rollupExitRoot); + expect(parsedLog?.args.originNetwork).to.be.equal(currenSequenceForcedStructs.originNetwork); + expect(parsedLog?.args.originTokenAddress.toLowerCase()).to.be.equal( + currenSequenceForcedStructs.originAddress + ); + expect(parsedLog?.args.destinationNetwork).to.be.equal(networkID); + expect(parsedLog?.args.destinationAddress.toLowerCase()).to.be.equal( + currenSequenceForcedStructs.destinationAddress + ); + expect(parsedLog?.args.amount).to.be.equal(currenSequenceForcedStructs.amount); + expect(parsedLog?.args.metadata).to.be.equal(currenSequenceForcedStructs.metadata); + } + } + }); + + it("should test against bridge", async () => { + const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridgeV2"); + + const ClaimCompressorFactory = await ethers.getContractFactory("ClaimCompressor"); + const realClaimCompressor = await ClaimCompressorFactory.deploy(polygonZkEVMBridgeContract.target, networkID); + await realClaimCompressor.waitForDeployment(); + + // compute root merkle tree in Js + const height = 32; + const merkleTreeLocal = new MerkleTreeBridge(height); + + const totalLeafsMerkleTree = 10; + + const leafs = []; + for (let i = 0; i < totalLeafsMerkleTree; i++) { + // Create a random merkle tree + const leafType = Math.floor(Math.random() * 2); + const originNetwork = ethers.hexlify(ethers.randomBytes(4)); + const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); + const amount = leafType == 0 ? ethers.hexlify(ethers.randomBytes(32)) : 0; + const destinationNetwork = networkID; // fixed by contract + const destinationAddress = ethers.hexlify(ethers.randomBytes(20)); + const metadata = metadataToken; + const metadataHash = ethers.solidityPackedKeccak256(["bytes"], [metadata]); + + const leafValue = getLeafValue( + leafType, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadataHash + ); + merkleTreeLocal.add(leafValue); + leafs.push({ + leafType, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadata, + }); + } + + const rollupExitRoot = await polygonZkEVMGlobalExitRoot.lastRollupExitRoot(); + const mainnetExitRoot = merkleTreeLocal.getRoot(); + + // add rollup Merkle root + await ethers.provider.send("hardhat_impersonateAccount", [polygonZkEVMBridgeContract.target]); + const bridgemoCK = await ethers.getSigner(polygonZkEVMBridgeContract.target as any); + + await expect(polygonZkEVMGlobalExitRoot.connect(bridgemoCK).updateExitRoot(mainnetExitRoot, {gasPrice: 0})) + .to.emit(polygonZkEVMGlobalExitRoot, "UpdateGlobalExitRoot") + .withArgs(mainnetExitRoot, rollupExitRoot); + + // check roots + const rollupExitRootSC = await polygonZkEVMGlobalExitRoot.lastRollupExitRoot(); + expect(rollupExitRootSC).to.be.equal(rollupExitRoot); + const mainnetExitRootSC = await polygonZkEVMGlobalExitRoot.lastMainnetExitRoot(); + expect(mainnetExitRootSC).to.be.equal(mainnetExitRoot); + const computedGlobalExitRoot = calculateGlobalExitRoot(mainnetExitRoot, rollupExitRootSC); + expect(computedGlobalExitRoot).to.be.equal(await polygonZkEVMGlobalExitRoot.getLastGlobalExitRoot()); + + // Merkle proof local + const proofLocalFirst = merkleTreeLocal.getProofTreeByIndex(0); + + const snapshot = await takeSnapshot(); + + // Compute calldatas + for (let i = 1; i < totalLeafsMerkleTree; i++) { + await snapshot.restore(); + + const sequenceForcedStructs = [] as any; + + for (let j = 0; j < i; j++) { + const index = j; + const currentLeaf = leafs[j]; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(j); + + const globalIndex = computeGlobalIndex(index, 0, true); + + const sequenceForced = { + smtProofRollupExitRoot: proofLocal, + smtProofLocalExitRoot: proofLocal, + globalIndex: globalIndex, + originNetwork: currentLeaf.originNetwork, + originAddress: currentLeaf.tokenAddress, + destinationAddress: currentLeaf.destinationAddress, + amount: currentLeaf.amount, + metadata: currentLeaf.metadata, + isMessage: currentLeaf.leafType, + } as any; + + sequenceForcedStructs.push(sequenceForced); + + if (currentLeaf.leafType == 0) { + await polygonZkEVMBridgeContract.claimAsset.estimateGas( + proofLocal, + proofLocalFirst, + globalIndex, + mainnetExitRoot, + rollupExitRootSC, + currentLeaf.originNetwork, + currentLeaf.tokenAddress, + networkID, + currentLeaf.destinationAddress, + currentLeaf.amount, + currentLeaf.metadata + ); + } else { + await polygonZkEVMBridgeContract.claimMessage.estimateGas( + proofLocal, + proofLocalFirst, + globalIndex, + mainnetExitRoot, + rollupExitRootSC, + currentLeaf.originNetwork, + currentLeaf.tokenAddress, + networkID, + currentLeaf.destinationAddress, + currentLeaf.amount, + currentLeaf.metadata + ); + } + } + + const compressedMultipleBytes = await realClaimCompressor.compressClaimCall( + mainnetExitRoot, + rollupExitRootSC, + sequenceForcedStructs + ); + + // ASsert correctness + const receipt = await (await realClaimCompressor.sendCompressedClaims(compressedMultipleBytes)).wait(); + + console.log({ + numClaims: i, + gasUsed: receipt?.gasUsed, + }); + + let currentSequenceForcedStructs = 0; + for (let k = 0; k < receipt?.logs.length; k++) { + const currentLog = receipt?.logs[k]; + if (currentLog?.address != polygonZkEVMBridgeContract.target) { + continue; + } else { + const parsedLog = BridgeFactory.interface.parseLog(currentLog); + if (parsedLog.name == "NewWrappedToken") { + continue; + } + } + const currenSequenceForcedStructs = sequenceForcedStructs[currentSequenceForcedStructs]; + + const parsedLog = BridgeFactory.interface.parseLog(currentLog); + + expect(parsedLog?.args.globalIndex).to.be.deep.equal(currenSequenceForcedStructs.globalIndex); + expect(parsedLog?.args.originNetwork).to.be.equal(currenSequenceForcedStructs.originNetwork); + expect(parsedLog?.args.originAddress.toLowerCase()).to.be.equal( + currenSequenceForcedStructs.originAddress + ); + expect(parsedLog?.args.destinationAddress.toLowerCase()).to.be.equal( + currenSequenceForcedStructs.destinationAddress + ); + expect(parsedLog?.args.amount).to.be.equal(currenSequenceForcedStructs.amount); + currentSequenceForcedStructs++; + } + + expect(currentSequenceForcedStructs).to.be.equal(sequenceForcedStructs.length); + } + }).timeout(1000000); + it("should check Compression", async () => { + const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridgeV2"); + + const originNetwork = networkIDMainnet; + const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); + const amount = ethers.parseEther("10"); + const destinationNetwork = networkID; + const destinationAddress = acc1.address; + const metadata = metadataToken; + const metadataHash = ethers.solidityPackedKeccak256(["bytes"], [metadata]); + + // compute root merkle tree in Js + const height = 32; + const merkleTreeLocal = new MerkleTreeBridge(height); + const leafValue = getLeafValue( + LEAF_TYPE_ASSET, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadataHash + ); + for (let i = 0; i < 8; i++) { + merkleTreeLocal.add(leafValue); + } + + const mainnetExitRoot = merkleTreeLocal.getRoot(); + + const index = 0; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(index); + + const indexRandom = 3; + + const proofZeroes = new Array(32).fill(ethers.ZeroHash); + const encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ + proofLocal, + proofZeroes, + indexRandom, + mainnetExitRoot, + ethers.ZeroHash, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadata, + ]); + + const newWallet = ethers.HDNodeWallet.fromMnemonic( + ethers.Mnemonic.fromPhrase("test test test test test test test test test test test junk"), + `m/44'/60'/0'/0/0` + ); + + const tx = { + data: encodedCall, + to: bridge.address, + nonce: 1, + gasLimit: 200000, + gasPrice: ethers.parseUnits("10", "gwei"), + chainId: 5, + }; + + const txSigned = await newWallet.signTransaction(tx); + + // Get claim tx bytes calldata + const customSignedTx = processorUtils.rawTxToCustomRawTx(txSigned); + + // Compute calldatas + for (let i = 1; i < 20; i++) { + const sequenceForcedStructs = [] as any; + + for (let j = 0; j < i; j++) { + const index = i; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(i); + + const sequenceForced = { + smtProofLocalExitRoot: proofLocal, + smtProofRollupExitRoot: proofZeroes, + globalIndex: index, + originNetwork: originNetwork, + originAddress: tokenAddress, + destinationAddress: destinationAddress, + amount: amount, + metadata: metadata, + isMessage: false, + } as any; + + sequenceForcedStructs.push(sequenceForced); + } + + const compressedMultipleBytes = await claimCompressor.compressClaimCall( + mainnetExitRoot, + ethers.ZeroHash, + sequenceForcedStructs + ); + + // ASsert correctness + let lastSmtRollupCopied = new Array(32).fill(ethers.ZeroHash); // TODO could be set to zero hashes + + const receipt = await (await claimCompressor.sendCompressedClaims(compressedMultipleBytes)).wait(); + for (let k = 0; k < receipt?.logs.length; k++) { + const currentLog = receipt?.logs[k]; + const currenSequenceForcedStructs = sequenceForcedStructs[k]; + + const decodeFunctionName = currenSequenceForcedStructs.isMessage ? "claimMessage" : "claimAsset"; + + const encodedCall = BridgeFactory.interface.encodeFunctionData(decodeFunctionName, [ + currenSequenceForcedStructs.smtProofLocalExitRoot, + proofLocal, + currenSequenceForcedStructs.globalIndex, + mainnetExitRoot, + ethers.ZeroHash, + currenSequenceForcedStructs.originNetwork, + currenSequenceForcedStructs.originAddress, + destinationNetwork, // constant + currenSequenceForcedStructs.destinationAddress, + currenSequenceForcedStructs.amount, + currenSequenceForcedStructs.metadata, + ]); + + const parsedLog = bridgeReceiverMock.interface.parseLog(currentLog); + //expect(parsedLog?.args[0]).to.be.equal(encodedCall); + + expect(parsedLog?.args.smtProofLocalExitRoot).to.be.deep.equal( + currenSequenceForcedStructs.smtProofLocalExitRoot + ); + + let isZeroArray = true; + + for (const element of parsedLog?.args.smtProofRollupExitRoot) { + if (element != ethers.ZeroHash) { + isZeroArray = false; + } + } + + if (isZeroArray) { + expect(parsedLog?.args.smtProofRollupExitRoot).to.be.deep.equal(lastSmtRollupCopied); + } else { + expect(parsedLog?.args.smtProofRollupExitRoot).to.be.deep.equal( + currenSequenceForcedStructs.smtProofRollupExitRoot + ); + lastSmtRollupCopied = currenSequenceForcedStructs.smtProofRollupExitRoot; + } + + expect(parsedLog?.args.globalIndex).to.be.equal(currenSequenceForcedStructs.globalIndex); + expect(parsedLog?.args.mainnetExitRoot).to.be.equal(mainnetExitRoot); + expect(parsedLog?.args.rollupExitRoot).to.be.equal(ethers.ZeroHash); + expect(parsedLog?.args.originNetwork).to.be.equal(currenSequenceForcedStructs.originNetwork); + expect(parsedLog?.args.originTokenAddress.toLowerCase()).to.be.equal( + currenSequenceForcedStructs.originAddress + ); + expect(parsedLog?.args.destinationNetwork).to.be.equal(networkID); + expect(parsedLog?.args.destinationAddress.toLowerCase()).to.be.equal( + currenSequenceForcedStructs.destinationAddress.toLowerCase() + ); + expect(parsedLog?.args.amount).to.be.equal(currenSequenceForcedStructs.amount); + expect(parsedLog?.args.metadata).to.be.equal(currenSequenceForcedStructs.metadata); + } + + const txCompressedMultiple = { + data: compressedMultipleBytes, + to: bridge.address, + nonce: 1, + gasLimit: 200000, + gasPrice: ethers.parseUnits("10", "gwei"), + chainId: 5, + }; + + const txCompressedMultipleSigned = await newWallet.signTransaction(txCompressedMultiple); + const customtxCompressedMultipleSigned = processorUtils.rawTxToCustomRawTx(txCompressedMultipleSigned); + + const customSignedCost = calculateCallDataCost(customSignedTx); + const customCompressedMultipleCost = calculateCallDataCost(customtxCompressedMultipleSigned); + + console.log({ + numClaims: i, + gasUsed: receipt?.gasUsed, + dataClaimCall: encodedCall.length * i, + dataCompressedCall: compressedMultipleBytes.length, + ratioData: compressedMultipleBytes.length / (encodedCall.length * i), + dataTotalTxClaimCall: (customSignedTx.length / 2) * i, + costCalldataTxClaimCall: customSignedCost * i, + dataTotalTxCompressedCall: customtxCompressedMultipleSigned.length / 2, + calldataCostTxCompressed: customCompressedMultipleCost, + ratioTxData: customtxCompressedMultipleSigned.length / (customSignedTx.length * i), + ratioTxDataCost: customCompressedMultipleCost / (customSignedCost * i), + }); + } + }); +}); + +function calculateCallDataCost(calldataBytes: string): number { + const bytesArray = ethers.getBytes(calldataBytes); + let totalCost = 0; + for (const bytes of bytesArray) { + if (bytes == 0) { + totalCost += 4; + } else { + totalCost += 16; + } + } + + return totalCost; +} + +function calculateGlobalExitRoot(mainnetExitRoot: any, rollupExitRoot: any) { + return ethers.solidityPackedKeccak256(["bytes32", "bytes32"], [mainnetExitRoot, rollupExitRoot]); +} +const _GLOBAL_INDEX_MAINNET_FLAG = 2n ** 64n; + +function computeGlobalIndex(indexLocal: any, indexRollup: any, isMainnet: Boolean) { + if (isMainnet === true) { + return BigInt(indexLocal) + _GLOBAL_INDEX_MAINNET_FLAG; + } else { + return BigInt(indexLocal) + BigInt(indexRollup) * 2n ** 32n; + } +}