From 018a87da2e0f3e9df79a4981a03611350c8ff8c6 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Mon, 22 Jan 2024 12:57:24 +0100 Subject: [PATCH 01/10] first approach claim comrpessor --- contracts/utils/ClaimCompressor.sol | 159 ++++++++++++++++++ hardhat.config.ts | 10 ++ test/contractsv2/ClaimCompressor.test.ts | 199 +++++++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 contracts/utils/ClaimCompressor.sol create mode 100644 test/contractsv2/ClaimCompressor.test.ts diff --git a/contracts/utils/ClaimCompressor.sol b/contracts/utils/ClaimCompressor.sol new file mode 100644 index 000000000..4f4d50b0b --- /dev/null +++ b/contracts/utils/ClaimCompressor.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: AGPL-3.0 + +//import "../PolygonZkEVMBridge.sol"; + +pragma solidity 0.8.23; + +/** + * Contract for compressing and decompressing claim data + */ +contract ClaimCompressor { + uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; + + // Leaf type asset + uint8 private constant _LEAF_TYPE_ASSET = 0; + + // Leaf type message + uint8 private constant _LEAF_TYPE_MESSAGE = 1; + + // Mainnet identifier + uint32 private constant _MAINNET_NETWORK_ID = 0; + + // // Bytes that will be added to the snark input for every rollup aggregated + // // 32*32 bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof + // // 32*8 REst constant parameters + // // 32 bytes position, 32 bytes length, + length bytes = metadata + // uint256 internal constant BYTES_PER_CLAIM = + // 32*32 + 8*32 + + + // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof, + // uint32 index, + // bytes32 mainnetExitRoot, + // bytes32 rollupExitRoot, + // uint32 originNetwork, + // address originAddress, + // uint32 destinationNetwork, + // address destinationAddress, + // uint256 amount, + // bytes calldata metadata + + // PolygonZkEVMBridge address + address public immutable bridgeAddress; + + // Mainnet identifier + uint32 private immutable networkID; + + /** + * @param _bridgeAddress PolygonZkEVMBridge contract address + */ + constructor(address _bridgeAddress, uint32 _networkID) { + bridgeAddress = _bridgeAddress; + networkID = _networkID; + } + + /** + * @notice Foward all the claim parameters to compress them inside the contrat + * @param smtProof Smt proof + * @param index 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 + */ + function compressClaimCall( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH][] calldata smtProof, + uint32[] calldata index, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + //uint32 calldata originNetwork, + address[] calldata originAddress, + //uint32[] calldata destinationNetwork, + address[] calldata destinationAddress, + uint256[] calldata amount, + bytes[] calldata metadata, + bool[] calldata isMessage + ) public view returns (bytes memory) { + // common parameters for all the claims + bytes memory totalCompressedClaim = abi.encodePacked( + smtProof[0], + mainnetExitRoot, + rollupExitRoot + ); + + // If the memory cost goes crazy, might need to do it in assembly D: + for (uint256 i = 0; i < isMessage.length; i++) { + // Byte array that will be returned + + // compare smt proof against the first one + uint256 lastDifferentLevel = 0; + for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { + if (smtProof[i][j] != smtProof[0][j]) { + lastDifferentLevel = j; + } + } + + bytes memory smtProofCompressed; + + for (uint256 j = 0; j < lastDifferentLevel; j++) { + smtProofCompressed = abi.encodePacked( + smtProofCompressed, + smtProof[0][i] + ); + } + + bytes memory compressedClaimCall = abi.encodePacked( + isMessage[i], + uint8(lastDifferentLevel), + smtProofCompressed, + index[i], + // mainnetExitRoot, + // rollupExitRoot, + // originNetwork, // for first version this is ok + originAddress[i], + // destinationNetwork + destinationAddress[i], + amount[i], // could compress to 128 bits + uint32(metadata[i].length), + metadata[i] + ); + + // Accumulate all claim calls + totalCompressedClaim = abi.encodePacked( + totalCompressedClaim, + compressedClaimCall + ); + } + return totalCompressedClaim; + } + + function decompressClaimCall(bytes calldata compressedClaimCalls) public { + // // This pointer will be the current position to write on accumulateSnarkBytes + // uint256 ptrCompressedClaimCall; + // // Total length of the accumulateSnarkBytes, ByesPerRollup * rollupToVerify + 20 bytes (msg.sender) + // uint256 totalSnarkLength = _SNARK_BYTES_PER_ROLLUP_AGGREGATED * + // verifyBatchesData.length + + // 20; + // // Use assembly to rever memory and get the memory pointer + // assembly { + // // Set accumulateSnarkBytes to the next free memory space + // accumulateSnarkBytes := mload(0x40) + // // Reserve the memory: 32 bytes for the byte array length + 32 bytes extra for byte manipulation (0x40) + + // // the length of the input snark bytes + // mstore(0x40, add(add(accumulateSnarkBytes, 0x40), totalSnarkLength)) + // // Set the length of the input bytes + // mstore(accumulateSnarkBytes, totalSnarkLength) + // // Set the pointer on the start of the actual byte array + // ptrAccumulateInputSnarkBytes := add(accumulateSnarkBytes, 0x20) + // } + // assembly { + // // store oldStateRoot + // mstore(ptr, oldStateRoot) + // ptr := add(ptr, 32) + // } + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 3a207099d..9134169f1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -27,6 +27,16 @@ const config: HardhatUserConfig = { }, solidity: { compilers: [ + { + version: "0.8.23", + settings: { + optimizer: { + enabled: true, + runs: 999999, + }, + viaIR: true, + }, + }, { version: "0.8.17", settings: { diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts new file mode 100644 index 000000000..c2defe9fa --- /dev/null +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -0,0 +1,199 @@ +import {expect} from "chai"; +import {ethers, upgrades} from "hardhat"; +import { + VerifierRollupHelperMock, + ERC20PermitMock, + PolygonRollupManagerMock, + PolygonZkEVMGlobalExitRoot, + PolygonZkEVMBridgeV2, + PolygonZkEVMV2, + PolygonRollupBase, + TokenWrapped, + ClaimCompressor, +} 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"; + +describe("PolygonZkEVMBridge Contract", () => { + upgrades.silenceWarnings(); + + let claimCompressor: ClaimCompressor; + + 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 global exit root manager + const ClaimCompressorFactory = await ethers.getContractFactory("ClaimCompressor"); + claimCompressor = await ClaimCompressorFactory.deploy(bridge.address, networkID); + await claimCompressor.waitForDeployment(); + }); + + it("should check Compression", async () => { + const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridge"); + + const originNetwork = networkIDMainnet; + const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); + const amount = ethers.parseEther("10"); + const destinationNetwork = networkIDMainnet; + 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 encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ + proofLocal, + index, + 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 proofs = [] as any; + const indexes = [] as any; + const tokenAddresses = []; + const destinationAddresses = []; + const amounts = []; + const metadatas = []; + const isClaimeds = []; + + for (let j = 0; j < i; j++) { + const index = i; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(i); + + proofs.push(proofLocal); + indexes.push(index); + tokenAddresses.push(tokenAddress); + destinationAddresses.push(destinationAddress); + amounts.push(amount); + metadatas.push(metadata); + isClaimeds.push(false); + } + + const compressedMultipleBytes = await claimCompressor.compressClaimCall( + proofs, + indexes, + mainnetExitRoot, + ethers.ZeroHash, + tokenAddresses, + destinationAddresses, + amounts, + metadatas, + isClaimeds + ); + + 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, + dataClaimCall: encodedCall.length * i, + dataCompressedCall: compressedMultipleBytes.length, + ratioData: compressedMultipleBytes.length / (encodedCall.length * i), + dataTotalTxClaimCall: customSignedTx.length * i, + costCalldataTxClaimCall: customSignedCost * i, + dataTotalTxCompressedCall: customtxCompressedMultipleSigned.length, + 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; +} From 822f133fc5a0afbab3019717d12c38534bbca77e Mon Sep 17 00:00:00 2001 From: invocamanman Date: Wed, 31 Jan 2024 10:48:06 +0100 Subject: [PATCH 02/10] claimCompressor ongoing --- contracts/utils/ClaimCompressor.sol | 159 ------------- contracts/v2/utils/ClaimCompressor.sol | 304 +++++++++++++++++++++++++ hardhat.config.ts | 21 +- 3 files changed, 315 insertions(+), 169 deletions(-) delete mode 100644 contracts/utils/ClaimCompressor.sol create mode 100644 contracts/v2/utils/ClaimCompressor.sol diff --git a/contracts/utils/ClaimCompressor.sol b/contracts/utils/ClaimCompressor.sol deleted file mode 100644 index 4f4d50b0b..000000000 --- a/contracts/utils/ClaimCompressor.sol +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 - -//import "../PolygonZkEVMBridge.sol"; - -pragma solidity 0.8.23; - -/** - * Contract for compressing and decompressing claim data - */ -contract ClaimCompressor { - uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; - - // Leaf type asset - uint8 private constant _LEAF_TYPE_ASSET = 0; - - // Leaf type message - uint8 private constant _LEAF_TYPE_MESSAGE = 1; - - // Mainnet identifier - uint32 private constant _MAINNET_NETWORK_ID = 0; - - // // Bytes that will be added to the snark input for every rollup aggregated - // // 32*32 bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof - // // 32*8 REst constant parameters - // // 32 bytes position, 32 bytes length, + length bytes = metadata - // uint256 internal constant BYTES_PER_CLAIM = - // 32*32 + 8*32 + - - // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof, - // uint32 index, - // bytes32 mainnetExitRoot, - // bytes32 rollupExitRoot, - // uint32 originNetwork, - // address originAddress, - // uint32 destinationNetwork, - // address destinationAddress, - // uint256 amount, - // bytes calldata metadata - - // PolygonZkEVMBridge address - address public immutable bridgeAddress; - - // Mainnet identifier - uint32 private immutable networkID; - - /** - * @param _bridgeAddress PolygonZkEVMBridge contract address - */ - constructor(address _bridgeAddress, uint32 _networkID) { - bridgeAddress = _bridgeAddress; - networkID = _networkID; - } - - /** - * @notice Foward all the claim parameters to compress them inside the contrat - * @param smtProof Smt proof - * @param index 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 - */ - function compressClaimCall( - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH][] calldata smtProof, - uint32[] calldata index, - bytes32 mainnetExitRoot, - bytes32 rollupExitRoot, - //uint32 calldata originNetwork, - address[] calldata originAddress, - //uint32[] calldata destinationNetwork, - address[] calldata destinationAddress, - uint256[] calldata amount, - bytes[] calldata metadata, - bool[] calldata isMessage - ) public view returns (bytes memory) { - // common parameters for all the claims - bytes memory totalCompressedClaim = abi.encodePacked( - smtProof[0], - mainnetExitRoot, - rollupExitRoot - ); - - // If the memory cost goes crazy, might need to do it in assembly D: - for (uint256 i = 0; i < isMessage.length; i++) { - // Byte array that will be returned - - // compare smt proof against the first one - uint256 lastDifferentLevel = 0; - for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { - if (smtProof[i][j] != smtProof[0][j]) { - lastDifferentLevel = j; - } - } - - bytes memory smtProofCompressed; - - for (uint256 j = 0; j < lastDifferentLevel; j++) { - smtProofCompressed = abi.encodePacked( - smtProofCompressed, - smtProof[0][i] - ); - } - - bytes memory compressedClaimCall = abi.encodePacked( - isMessage[i], - uint8(lastDifferentLevel), - smtProofCompressed, - index[i], - // mainnetExitRoot, - // rollupExitRoot, - // originNetwork, // for first version this is ok - originAddress[i], - // destinationNetwork - destinationAddress[i], - amount[i], // could compress to 128 bits - uint32(metadata[i].length), - metadata[i] - ); - - // Accumulate all claim calls - totalCompressedClaim = abi.encodePacked( - totalCompressedClaim, - compressedClaimCall - ); - } - return totalCompressedClaim; - } - - function decompressClaimCall(bytes calldata compressedClaimCalls) public { - // // This pointer will be the current position to write on accumulateSnarkBytes - // uint256 ptrCompressedClaimCall; - // // Total length of the accumulateSnarkBytes, ByesPerRollup * rollupToVerify + 20 bytes (msg.sender) - // uint256 totalSnarkLength = _SNARK_BYTES_PER_ROLLUP_AGGREGATED * - // verifyBatchesData.length + - // 20; - // // Use assembly to rever memory and get the memory pointer - // assembly { - // // Set accumulateSnarkBytes to the next free memory space - // accumulateSnarkBytes := mload(0x40) - // // Reserve the memory: 32 bytes for the byte array length + 32 bytes extra for byte manipulation (0x40) + - // // the length of the input snark bytes - // mstore(0x40, add(add(accumulateSnarkBytes, 0x40), totalSnarkLength)) - // // Set the length of the input bytes - // mstore(accumulateSnarkBytes, totalSnarkLength) - // // Set the pointer on the start of the actual byte array - // ptrAccumulateInputSnarkBytes := add(accumulateSnarkBytes, 0x20) - // } - // assembly { - // // store oldStateRoot - // mstore(ptr, oldStateRoot) - // ptr := add(ptr, 32) - // } - } -} diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol new file mode 100644 index 000000000..9d3bcc07f --- /dev/null +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: AGPL-3.0 + +import "../PolygonZkEVMBridgeV2.sol"; + +pragma solidity 0.8.20; + +/** + * Contract for compressing and decompressing claim data + */ +contract ClaimCompressor { + uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; + + // Leaf type asset + uint8 private constant _LEAF_TYPE_ASSET = 0; + + // Leaf type message + uint8 private constant _LEAF_TYPE_MESSAGE = 1; + + // Mainnet identifier + uint32 private constant _MAINNET_NETWORK_ID = 0; + + 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 + // 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 = 32*32*2 + 32*8 + 32*2 + length metadata = totalLen + uint256 internal constant _CONSTANT_BYTES_PER_CLAIM = + 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 + // smtProofLocalExitRoots[0], + // smtProofRollupExitRoots, + // mainnetExitRoot, + // rollupExitRoot + uint256 internal constant _CONSTANT_VARIABLES_LENGTH = 32 * 32 * 2 + 32 * 2; + + // 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 + // ) + + // 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 + // ) + + // PolygonZkEVMBridge address + address private immutable _bridgeAddress; + + // Mainnet identifier + uint32 private immutable _networkID; + + /** + * @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 smtProofLocalExitRoots Smt proof + * @param smtProofRollupExitRoots 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 + */ + function compressClaimCall( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoots, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH][] calldata smtProofLocalExitRoots, // struct + uint256[] calldata globalIndex, + uint32[] calldata originNetwork, + address[] calldata originAddress, + address[] calldata destinationAddress, + uint256[] calldata amount, + bytes[] calldata metadata, + bool[] calldata isMessage + ) external pure returns (bytes memory) { + // common parameters for all the claims + bytes memory totalCompressedClaim = abi.encodePacked( + smtProofLocalExitRoots[0], + smtProofRollupExitRoots, + mainnetExitRoot, + rollupExitRoot + ); + + // If the memory cost goes crazy, might need to do it in assembly D: + for (uint256 i = 0; i < isMessage.length; i++) { + // Byte array that will be returned + + // compare smt proof against the first one + uint256 lastDifferentLevel = 0; + for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { + if ( + smtProofLocalExitRoots[i][j] != smtProofLocalExitRoots[0][j] + ) { + lastDifferentLevel = j; + } + } + + bytes memory smtProofCompressed; + + for (uint256 j = 0; j < lastDifferentLevel; j++) { + smtProofCompressed = abi.encodePacked( + smtProofCompressed, + smtProofLocalExitRoots[0][i] + ); + } + + bytes memory compressedClaimCall = abi.encodePacked( + isMessage[i], + uint8(lastDifferentLevel), + smtProofCompressed, + bytes1(bytes32(globalIndex[i] << 191)), // get the 65th bit, so 256 - 65 = 191 + uint64(globalIndex[i]), + // mainnetExitRoot, + // rollupExitRoot, + originNetwork[i], // for first version this is ok + originAddress[i], + // destinationNetwork + destinationAddress[i], + amount[i], // could compress to 128 bits + uint32(metadata[i].length), + metadata[i] + ); + + // Accumulate all claim calls + totalCompressedClaim = abi.encodePacked( + totalCompressedClaim, + compressedClaimCall + ); + } + return totalCompressedClaim; + } + + function decompressClaimCall(bytes calldata compressedClaimCalls) external { + // Starts with the common parameters for all the claims: + + // smtProofLocalExitRoots bytes32[32] + // smtProofRollupExitRoots bytes32[32] + // mainnetExitRoot bytes32 + // rollupExitRoot bytes32 + + // will copy them afterwards when needed, the idea will be to reacraete the call in assembly + + // // This pointer will be the current position to write on accumulateSnarkBytes + // uint256 ptrCompressedClaimCall; + // // Total length of the accumulateSnarkBytes, ByesPerRollup * rollupToVerify + 20 bytes (msg.sender) + // uint256 totalSnarkLength = _SNARK_BYTES_PER_ROLLUP_AGGREGATED * + // verifyBatchesData.length + + // 20; + + uint256 destinationAddress = _networkID; + + // no need to be memory-safe, since the rest of the function will happen on assembly ¿? + assembly ("memory-safe") { + // Get the last free memory pointer ( i might use 0 aswell) + //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 + + // Calldata claimMessage + // function claimMessage( + // 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 + // ) + + // Encoded compressed Data: + + // Constant parameters: + // smtProofLocalExitRoots[0], + // smtProofRollupExitRoots, + // mainnetExitRoot, + // rollupExitRoot + + // Parameters per claim tx + // [ + // isMessage[i], + // uint8(lastDifferentLevel), + // smtProofCompressed, + // bytes1(bytes32(globalIndex[i] << 191)), // get the 65th bit, so 256 - 65 = 191 + // uint64(globalIndex[i]), + // originNetwork[i], // for first version this is ok + // originAddress[i], + // destinationAddress[i], + // amount[i], // could compress to 128 bits + // uint32(metadata[i].length), + // metadata[i] + // ] + // Write the constant parameters for all claims in this call + + // 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 + add(compressedClaimCallsOffset, mul(64, 32)), // calldata offset, smtProofLocalExitRoots[0] + smtProofRollupExitRoots = 64*32 + 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, mul(65, 32)), // calldata offset, smtProofLocalExitRoots[0] + smtProofRollupExitRoots + mainnetExitRoot = 65*32 + 32 // Copy rollupExitRoot len + ) + + // Copy destinationAddress, since is constant, just use mstore + + // Memory offset, signature + smtProofLocalExitRoot + smtProofRollupExitRoot + + // globalIndex + mainnetExitRoot + rollupExitRoot + originNetwork + originAddress = 69 * 32 bytes + 4 bytes + mstore(add(4, mul(69, 32)), destinationAddress) + + // Skip constant parameters + + // smtProofLocalExitRoots[0], + // smtProofRollupExitRoots, + // mainnetExitRoot, + // rollupExitRoot + let currentCalldataPointer := _CONSTANT_VARIABLES_LENGTH + + for { + // initialization block, empty + } lt(currentCalldataPointer, compressedClaimCallsLen) { + // after iteration block, empty + } { + // loop block, non empty ;) + // x := add(x, mload(i)) + // i := add(i, 0x20) + } + + // Set the pointer on the start of the actual byte array + //ptrAccumulateInputSnarkBytes := add(accumulateSnarkBytes, 0x20) + + //call(gas(), bridgeAddress, 0, 0, add(_CONSTANT_BYTES_PER_CLAIM, metadataLen), 0, 0) + } + // gas + // address + // value + // argsOffset + // argsSize + // retOffset + // retSize + // assembly { + // // store oldStateRoot + // mstore(ptr, oldStateRoot) + // ptr := add(ptr, 32) + // } + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 9134169f1..43062876b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -27,16 +27,6 @@ const config: HardhatUserConfig = { }, solidity: { compilers: [ - { - version: "0.8.23", - settings: { - optimizer: { - enabled: true, - runs: 999999, - }, - viaIR: true, - }, - }, { version: "0.8.17", settings: { @@ -136,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: { From 9f4be2272fd308b0b5f38388265662ffa30fc60d Mon Sep 17 00:00:00 2001 From: invocamanman Date: Thu, 1 Feb 2024 18:52:24 +0100 Subject: [PATCH 03/10] small changes --- contracts/v2/lib/PolygonRollupBaseEtrog.sol | 2 +- contracts/v2/utils/ClaimCompressor.sol | 2 +- deployment/v2/4_createRollup.ts | 2 ++ deployment/v2/README.md | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) 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/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 9d3bcc07f..6515334b9 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -153,7 +153,7 @@ contract ClaimCompressor { uint64(globalIndex[i]), // mainnetExitRoot, // rollupExitRoot, - originNetwork[i], // for first version this is ok + originNetwork[i], originAddress[i], // destinationNetwork destinationAddress[i], 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 From 8bc56084523a7879d6809f92e1c46acd51be4b8a Mon Sep 17 00:00:00 2001 From: invocamanman Date: Fri, 2 Feb 2024 19:12:10 +0100 Subject: [PATCH 04/10] steps --- contracts/v2/mocks/BridgeReceiverMock.sol | 165 ++++++++++++++ contracts/v2/utils/ClaimCompressor.sol | 260 ++++++++++++++++++---- test/contractsv2/ClaimCompressor.test.ts | 146 +++++++++++- 3 files changed, 524 insertions(+), 47 deletions(-) create mode 100644 contracts/v2/mocks/BridgeReceiverMock.sol diff --git a/contracts/v2/mocks/BridgeReceiverMock.sol b/contracts/v2/mocks/BridgeReceiverMock.sol new file mode 100644 index 000000000..0ab52dc66 --- /dev/null +++ b/contracts/v2/mocks/BridgeReceiverMock.sol @@ -0,0 +1,165 @@ +// 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 claimAssetAA( + 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 claimMessageBB( + 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(bytes calldata input) external returns (bytes memory) { + emit FallbackEvent(input); + } +} diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 6515334b9..5a17a1556 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -19,10 +19,13 @@ contract ClaimCompressor { // Mainnet identifier uint32 private constant _MAINNET_NETWORK_ID = 0; - bytes4 private constant _CLAIM_ASSET_SIGNATURE = + // Indicate where's the mainnet flag bit in the global index + uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; + + bytes32 private constant _CLAIM_ASSET_SIGNATURE = PolygonZkEVMBridgeV2.claimAsset.selector; - bytes4 private constant _CLAIM_MESSAGE_SIGNATURE = + bytes32 private constant _CLAIM_MESSAGE_SIGNATURE = PolygonZkEVMBridgeV2.claimMessage.selector; // Bytes that will be added to the snark input for every rollup aggregated @@ -43,6 +46,12 @@ contract ClaimCompressor { // rollupExitRoot uint256 internal constant _CONSTANT_VARIABLES_LENGTH = 32 * 32 * 2 + 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; + // function claimAsset( // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, @@ -77,6 +86,22 @@ contract ClaimCompressor { // Mainnet identifier uint32 private immutable _networkID; + /** + * @notice Struct which will be used to call sequenceBatches + * @param transactions L2 ethereum transactions EIP-155 or pre-EIP-155 with signature: + * EIP-155: rlp(nonce, gasprice, gasLimit, to, value, data, chainid, 0, 0,) || v || r || s + * pre-EIP-155: rlp(nonce, gasprice, gasLimit, to, value, data) || v || r || s + * @param globalExitRoot Global exit root of the batch + * @param timestamp Sequenced timestamp of the batch + * @param minForcedTimestamp Minimum timestamp of the force batch data, empty when non forced batch + */ + struct BatchData { + bytes transactions; + bytes32 globalExitRoot; + uint64 timestamp; + uint64 minForcedTimestamp; + } + /** * @param __bridgeAddress PolygonZkEVMBridge contract address * @param __networkID Network ID @@ -117,7 +142,7 @@ contract ClaimCompressor { // common parameters for all the claims bytes memory totalCompressedClaim = abi.encodePacked( smtProofLocalExitRoots[0], - smtProofRollupExitRoots, + smtProofRollupExitRoots, // coudl be encoded like (uint8 len, first len []) mainnetExitRoot, rollupExitRoot ); @@ -146,10 +171,11 @@ contract ClaimCompressor { } bytes memory compressedClaimCall = abi.encodePacked( - isMessage[i], + uint8(isMessage[i] ? 1 : 0), // define byte with all small values TODO uint8(lastDifferentLevel), smtProofCompressed, - bytes1(bytes32(globalIndex[i] << 191)), // get the 65th bit, so 256 - 65 = 191 + uint8(globalIndex[i] >> 64), // define byte with all small values TODO + //bool(globalIndex[i] & _GLOBAL_INDEX_MAINNET_FLAG != 0), // get isMainnet bool uint64(globalIndex[i]), // mainnetExitRoot, // rollupExitRoot, @@ -179,27 +205,24 @@ contract ClaimCompressor { // mainnetExitRoot bytes32 // rollupExitRoot bytes32 - // will copy them afterwards when needed, the idea will be to reacraete the call in assembly + // Load "dynamic" constant and immutables since are not accesible from assembly + uint256 destinationNetwork = _networkID; + address bridgeAddress = _bridgeAddress; - // // This pointer will be the current position to write on accumulateSnarkBytes - // uint256 ptrCompressedClaimCall; - // // Total length of the accumulateSnarkBytes, ByesPerRollup * rollupToVerify + 20 bytes (msg.sender) - // uint256 totalSnarkLength = _SNARK_BYTES_PER_ROLLUP_AGGREGATED * - // verifyBatchesData.length + - // 20; - - uint256 destinationAddress = _networkID; + bytes32 claimAssetSignature = _CLAIM_ASSET_SIGNATURE; + bytes32 claimMessageSignature = _CLAIM_MESSAGE_SIGNATURE; // no need to be memory-safe, since the rest of the function will happen on assembly ¿? assembly ("memory-safe") { // Get the last free memory pointer ( i might use 0 aswell) - //let freeMemPointer := mload(0x40) + 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 - // Calldata claimMessage + // THe final calldata should be something like: + // Calldata claimMessage /claimAsset // function claimMessage( // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, // bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, --> constant @@ -217,6 +240,7 @@ contract ClaimCompressor { // Encoded compressed Data: // Constant parameters: + // smtProofLocalExitRoots[0], // smtProofRollupExitRoots, // mainnetExitRoot, @@ -224,18 +248,19 @@ contract ClaimCompressor { // Parameters per claim tx // [ - // isMessage[i], + // uint8(isMessage[i] ? 1 : 0), // define byte with all small values TODO // uint8(lastDifferentLevel), // smtProofCompressed, - // bytes1(bytes32(globalIndex[i] << 191)), // get the 65th bit, so 256 - 65 = 191 + // uint8(globalIndex[i] >> 64), // define byte with all small values TODO // uint64(globalIndex[i]), - // originNetwork[i], // for first version this is ok + // originNetwork[i], // originAddress[i], // destinationAddress[i], // amount[i], // could compress to 128 bits // uint32(metadata[i].length), // metadata[i] // ] + // Write the constant parameters for all claims in this call // Copy smtProofRollupExitRoot @@ -263,7 +288,13 @@ contract ClaimCompressor { // Memory offset, signature + smtProofLocalExitRoot + smtProofRollupExitRoot + // globalIndex + mainnetExitRoot + rollupExitRoot + originNetwork + originAddress = 69 * 32 bytes + 4 bytes - mstore(add(4, mul(69, 32)), destinationAddress) + 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) // Skip constant parameters @@ -279,26 +310,179 @@ contract ClaimCompressor { // after iteration block, empty } { // loop block, non empty ;) - // x := add(x, mload(i)) - // i := add(i, 0x20) - } - // Set the pointer on the start of the actual byte array - //ptrAccumulateInputSnarkBytes := add(accumulateSnarkBytes, 0x20) + // Read uint8 isMessageBool + switch shr(248, calldataload(currentCalldataPointer)) + case 0 { + // Write asset signature + mstore8(0, claimAssetSignature) + mstore8(1, shr(1, claimAssetSignature)) + mstore8(2, shr(2, claimAssetSignature)) + mstore8(3, shr(3, claimAssetSignature)) + } + case 1 { + mstore8(0, claimMessageSignature) + mstore8(1, shr(1, claimMessageSignature)) + mstore8(2, shr(2, claimMessageSignature)) + mstore8(3, shr(3, claimMessageSignature)) + } - //call(gas(), bridgeAddress, 0, 0, add(_CONSTANT_BYTES_PER_CLAIM, metadataLen), 0, 0) + // Add 1 byte of isMessage TODO + currentCalldataPointer := add(currentCalldataPointer, 1) + + // Mem pointer where the current data must be written + let memPointer := 4 + + // load lastDifferentLevel + let smtProofBytesToCopy := mul( + shr( + 248, // 256 - 8(lastDifferentLevel) = 248 + calldataload(currentCalldataPointer) + ), + 32 + ) + + // Add 1 byte of lastDifferentLevel + currentCalldataPointer := add(currentCalldataPointer, 1) + + calldatacopy( + 4, // Memory offset = 4 bytes + currentCalldataPointer, // calldata offset + smtProofBytesToCopy // Copy smtProofBytesToCopy len + ) + + // Add smtProofBytesToCopy bits of smtProofCompressed + currentCalldataPointer := add( + currentCalldataPointer, + smtProofBytesToCopy + ) + // mem pointer, add smtProofLocalExitRoot(current) + smtProofRollupExitRoot(constant) + memPointer := add(memPointer, mul(32, 64)) + + // 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 | + // | mainnetFlag | rollupIndex | localRootIndex | + mstore8( + add(memPointer, 23), // 23 bytes globalIndex Offset + shr(255, calldataload(currentCalldataPointer)) + ) + + // Add 1 bytes of uint8(globalIndex[i] >> 64 + currentCalldataPointer := add(currentCalldataPointer, 1) + + // Copy the next 64 bytes for the uint64(globalIndex[i]), + calldatacopy( + add(memPointer, 23), // 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( + 248, // 256 - 32(uint32(metadata[i].length)) = 224 + calldataload(currentCalldataPointer) + ) + + mstore(memPointer, metadataLen) + + currentCalldataPointer := add(currentCalldataPointer, 4) + + // mem pointer, add originNetwork(current) + 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 just in case + mstore(memPointer, 0) + + // len args should be a multiple of 32 bytes + metadataLen := add(metadataLen, mod(metadataLen, 32)) + + // SHould i limit the gas TODO or the call + let success := call( + gas(), // gas + bridgeAddress, // address + 0, // value + 0, // args offset + add(_CONSTANT_BYTES_PER_CLAIM, metadataLen), // argsSize + 0, // retOffset + 0 // retSize + ) + } } - // gas - // address - // value - // argsOffset - // argsSize - // retOffset - // retSize - // assembly { - // // store oldStateRoot - // mstore(ptr, oldStateRoot) - // ptr := add(ptr, 32) - // } } } diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts index c2defe9fa..04624eb57 100644 --- a/test/contractsv2/ClaimCompressor.test.ts +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -10,6 +10,7 @@ import { PolygonRollupBase, TokenWrapped, ClaimCompressor, + BridgeReceiverMock, } from "../../typechain-types"; import {takeSnapshot, time} from "@nomicfoundation/hardhat-network-helpers"; import {processorUtils, contractUtils, MTBridge, mtBridgeUtils} from "@0xpolygonhermez/zkevm-commonjs"; @@ -22,6 +23,7 @@ describe("PolygonZkEVMBridge Contract", () => { upgrades.silenceWarnings(); let claimCompressor: ClaimCompressor; + let bridgeReceiverMock: BridgeReceiverMock; const networkID = 1; @@ -48,14 +50,133 @@ describe("PolygonZkEVMBridge Contract", () => { // 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(bridge.address, networkID); + claimCompressor = await ClaimCompressorFactory.deploy(bridgeReceiverMock.target, networkID); await claimCompressor.waitForDeployment(); }); + it("should check it works", async () => { + const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridgeV2"); + + const originNetwork = networkIDMainnet; + const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); + const amount = ethers.parseEther("10"); + const destinationNetwork = networkIDMainnet; + 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 encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ + proofLocal, + proofLocal, + 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); + + const proofs = [proofLocal]; + const indexes = [index]; + const originNetworks = [originNetwork]; + const tokenAddresses = [tokenAddress]; + const destinationAddresses = [destinationAddress]; + const amounts = [amount]; + const metadatas = [metadata]; + const isMessage = [false]; + + const compressedMultipleBytes = await claimCompressor.compressClaimCall( + proofs[0], + mainnetExitRoot, + ethers.ZeroHash, + proofs, + indexes, + originNetworks, + tokenAddresses, + destinationAddresses, + amounts, + metadatas, + isMessage + ); + + const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); + + for (const log of receipt?.logs) { + const parsedLog = bridgeReceiverMock.interface.parseLog(log); + console.log({parsedLog}); + } + await expect(claimCompressor.decompressClaimCall(compressedMultipleBytes)) + .to.emit(bridgeReceiverMock, "FallbackEvent") + .withArgs("0x"); + // .to.emit(bridgeReceiverMock, "ClaimAsset") + // .withArgs( + // proofs[0], + // mainnetExitRoot, + // ethers.ZeroHash, + // proofs[0], + // indexes[0], + // originNetworks[0], + // tokenAddresses[0], + // destinationAddresses[0], + // amounts[0], + // metadatas[0], + // isMessage[0] + // ); + }); it("should check Compression", async () => { - const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridge"); + const BridgeFactory = await ethers.getContractFactory("PolygonZkEVMBridgeV2"); const originNetwork = networkIDMainnet; const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); @@ -86,9 +207,12 @@ describe("PolygonZkEVMBridge Contract", () => { const index = 0; const proofLocal = merkleTreeLocal.getProofTreeByIndex(index); + const indexRandom = 3; + const encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ proofLocal, - index, + proofLocal, + indexRandom, mainnetExitRoot, ethers.ZeroHash, originNetwork, @@ -119,14 +243,15 @@ describe("PolygonZkEVMBridge Contract", () => { const customSignedTx = processorUtils.rawTxToCustomRawTx(txSigned); // Compute calldatas - for (let i = 1; i < 20; i++) { + for (let i = 1; i < 0; i++) { const proofs = [] as any; const indexes = [] as any; + const originNetworks = []; const tokenAddresses = []; const destinationAddresses = []; const amounts = []; const metadatas = []; - const isClaimeds = []; + const isMessage = []; for (let j = 0; j < i; j++) { const index = i; @@ -134,23 +259,26 @@ describe("PolygonZkEVMBridge Contract", () => { proofs.push(proofLocal); indexes.push(index); + originNetworks.push(originNetwork); tokenAddresses.push(tokenAddress); destinationAddresses.push(destinationAddress); amounts.push(amount); metadatas.push(metadata); - isClaimeds.push(false); + isMessage.push(false); } const compressedMultipleBytes = await claimCompressor.compressClaimCall( - proofs, - indexes, + proofs[0], mainnetExitRoot, ethers.ZeroHash, + proofs, + indexes, + originNetworks, tokenAddresses, destinationAddresses, amounts, metadatas, - isClaimeds + isMessage ); const txCompressedMultiple = { From 0e41f8dd3ea805c9ac5211548301072fd7133ba1 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Fri, 2 Feb 2024 19:12:44 +0100 Subject: [PATCH 05/10] another step --- contracts/v2/mocks/BridgeReceiverMock.sol | 5 +- contracts/v2/utils/ClaimCompressor.sol | 106 +++++++++++----------- hardhat.config.ts | 2 +- test/contractsv2/ClaimCompressor.test.ts | 22 +++-- 4 files changed, 68 insertions(+), 67 deletions(-) diff --git a/contracts/v2/mocks/BridgeReceiverMock.sol b/contracts/v2/mocks/BridgeReceiverMock.sol index 0ab52dc66..cf1954fa3 100644 --- a/contracts/v2/mocks/BridgeReceiverMock.sol +++ b/contracts/v2/mocks/BridgeReceiverMock.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 import "../PolygonZkEVMBridgeV2.sol"; - pragma solidity 0.8.20; /** @@ -159,7 +158,7 @@ contract BridgeReceiverMock { ); } - fallback(bytes calldata input) external returns (bytes memory) { - emit FallbackEvent(input); + fallback() external { + emit FallbackEvent(msg.data); } } diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 5a17a1556..3b51111bd 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 import "../PolygonZkEVMBridgeV2.sol"; +import "hardhat/console.sol"; pragma solidity 0.8.20; @@ -22,10 +23,10 @@ contract ClaimCompressor { // Indicate where's the mainnet flag bit in the global index uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; - bytes32 private constant _CLAIM_ASSET_SIGNATURE = + bytes4 private constant _CLAIM_ASSET_SIGNATURE = PolygonZkEVMBridgeV2.claimAsset.selector; - bytes32 private constant _CLAIM_MESSAGE_SIGNATURE = + bytes4 private constant _CLAIM_MESSAGE_SIGNATURE = PolygonZkEVMBridgeV2.claimMessage.selector; // Bytes that will be added to the snark input for every rollup aggregated @@ -87,19 +88,27 @@ contract ClaimCompressor { uint32 private immutable _networkID; /** - * @notice Struct which will be used to call sequenceBatches - * @param transactions L2 ethereum transactions EIP-155 or pre-EIP-155 with signature: - * EIP-155: rlp(nonce, gasprice, gasLimit, to, value, data, chainid, 0, 0,) || v || r || s - * pre-EIP-155: rlp(nonce, gasprice, gasLimit, to, value, data) || v || r || s - * @param globalExitRoot Global exit root of the batch - * @param timestamp Sequenced timestamp of the batch - * @param minForcedTimestamp Minimum timestamp of the force batch data, empty when non forced batch + * @param smtProofRollupExitRoots 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 BatchData { - bytes transactions; - bytes32 globalExitRoot; - uint64 timestamp; - uint64 minForcedTimestamp; + struct CompressClaimCallData { + bytes32[32] smtProofLocalExitRoot; + uint256 globalIndex; + uint32 originNetwork; + address originAddress; + address destinationAddress; + uint256 amount; + bytes metadata; + bool isMessage; } /** @@ -113,49 +122,36 @@ contract ClaimCompressor { /** * @notice Foward all the claim parameters to compress them inside the contrat - * @param smtProofLocalExitRoots Smt proof - * @param smtProofRollupExitRoots Smt proof - * @param globalIndex Index of the leaf + * @param smtProofRollupExitRoot Smt proof * @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 - */ + * @param compressClaimCalldata compress claim calldata + **/ function compressClaimCall( - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoots, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, bytes32 mainnetExitRoot, bytes32 rollupExitRoot, - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH][] calldata smtProofLocalExitRoots, // struct - uint256[] calldata globalIndex, - uint32[] calldata originNetwork, - address[] calldata originAddress, - address[] calldata destinationAddress, - uint256[] calldata amount, - bytes[] calldata metadata, - bool[] calldata isMessage + CompressClaimCallData[] calldata compressClaimCalldata ) external pure returns (bytes memory) { // common parameters for all the claims bytes memory totalCompressedClaim = abi.encodePacked( - smtProofLocalExitRoots[0], - smtProofRollupExitRoots, // coudl be encoded like (uint8 len, first len []) + compressClaimCalldata[0].smtProofLocalExitRoot, + smtProofRollupExitRoot, // coudl be encoded like (uint8 len, first len []) mainnetExitRoot, rollupExitRoot ); // If the memory cost goes crazy, might need to do it in assembly D: - for (uint256 i = 0; i < isMessage.length; i++) { + for (uint256 i = 0; i < compressClaimCalldata.length; i++) { // Byte array that will be returned - + CompressClaimCallData + memory currentCompressClaimCalldata = compressClaimCalldata[i]; // compare smt proof against the first one uint256 lastDifferentLevel = 0; for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { if ( - smtProofLocalExitRoots[i][j] != smtProofLocalExitRoots[0][j] + currentCompressClaimCalldata.smtProofLocalExitRoot[j] != + compressClaimCalldata[0].smtProofLocalExitRoot[j] ) { lastDifferentLevel = j; } @@ -166,26 +162,26 @@ contract ClaimCompressor { for (uint256 j = 0; j < lastDifferentLevel; j++) { smtProofCompressed = abi.encodePacked( smtProofCompressed, - smtProofLocalExitRoots[0][i] + currentCompressClaimCalldata.smtProofLocalExitRoot[i] ); } bytes memory compressedClaimCall = abi.encodePacked( - uint8(isMessage[i] ? 1 : 0), // define byte with all small values TODO + uint8(currentCompressClaimCalldata.isMessage ? 1 : 0), // define byte with all small values TODO uint8(lastDifferentLevel), smtProofCompressed, - uint8(globalIndex[i] >> 64), // define byte with all small values TODO - //bool(globalIndex[i] & _GLOBAL_INDEX_MAINNET_FLAG != 0), // get isMainnet bool - uint64(globalIndex[i]), + uint8(currentCompressClaimCalldata.globalIndex >> 64), // define byte with all small values TODO + //bool(globalIndex & _GLOBAL_INDEX_MAINNET_FLAG != 0), // get isMainnet bool + uint64(currentCompressClaimCalldata.globalIndex), // mainnetExitRoot, // rollupExitRoot, - originNetwork[i], - originAddress[i], + currentCompressClaimCalldata.originNetwork, + currentCompressClaimCalldata.originAddress, // destinationNetwork - destinationAddress[i], - amount[i], // could compress to 128 bits - uint32(metadata[i].length), - metadata[i] + currentCompressClaimCalldata.destinationAddress, + currentCompressClaimCalldata.amount, // could compress to 128 bits + uint32(currentCompressClaimCalldata.metadata.length), + currentCompressClaimCalldata.metadata ); // Accumulate all claim calls @@ -201,7 +197,7 @@ contract ClaimCompressor { // Starts with the common parameters for all the claims: // smtProofLocalExitRoots bytes32[32] - // smtProofRollupExitRoots bytes32[32] + // smtProofRollupExitRoot bytes32[32] // mainnetExitRoot bytes32 // rollupExitRoot bytes32 @@ -209,13 +205,13 @@ contract ClaimCompressor { uint256 destinationNetwork = _networkID; address bridgeAddress = _bridgeAddress; - bytes32 claimAssetSignature = _CLAIM_ASSET_SIGNATURE; - bytes32 claimMessageSignature = _CLAIM_MESSAGE_SIGNATURE; + 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 ("memory-safe") { + assembly { // Get the last free memory pointer ( i might use 0 aswell) - let freeMemPointer := mload(0x40) + // let freeMemPointer := mload(0x40) // no need to reserve memory since the rest of the funcion will happen on assembly let compressedClaimCallsOffset := compressedClaimCalls.offset diff --git a/hardhat.config.ts b/hardhat.config.ts index 43062876b..42b6c9a73 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -134,7 +134,7 @@ const config: HardhatUserConfig = { runs: 999999, }, evmVersion: "shanghai", - viaIR: true, + //viaIR: true, }, }, }, diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts index 04624eb57..394e386b1 100644 --- a/test/contractsv2/ClaimCompressor.test.ts +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -137,19 +137,25 @@ describe("PolygonZkEVMBridge Contract", () => { const metadatas = [metadata]; const isMessage = [false]; + const sequenceForced = { + smtProofLocalExitRoot: proofLocal, + globalIndex: index, + originNetwork: originNetwork, + originAddress: tokenAddress, + destinationAddress: destinationAddress, + amount: amount, + metadata: metadata, + isMessage: false, + } as any; + + console.log(proofs[0], mainnetExitRoot, ethers.ZeroHash, [sequenceForced]); const compressedMultipleBytes = await claimCompressor.compressClaimCall( proofs[0], mainnetExitRoot, ethers.ZeroHash, - proofs, - indexes, - originNetworks, - tokenAddresses, - destinationAddresses, - amounts, - metadatas, - isMessage + [sequenceForced] ); + console.log({compressedMultipleBytes}); const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); From c25913acaa5ebc5dae8d27974b46aa6722785925 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Sat, 3 Feb 2024 03:19:08 +0100 Subject: [PATCH 06/10] first try :D --- contracts/v2/utils/ClaimCompressor.sol | 53 ++++++--- test/contractsv2/ClaimCompressor.test.ts | 133 ++++++++++++----------- 2 files changed, 102 insertions(+), 84 deletions(-) diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 3b51111bd..4e95a0395 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -30,12 +30,13 @@ contract ClaimCompressor { 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 = 32*32*2 + 32*8 + 32*2 + length metadata = totalLen + // 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 = - 32 * 32 * 2 + 8 * 32 + 32 * 2; + 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; @@ -259,6 +260,13 @@ contract ClaimCompressor { // Write the constant parameters for all claims in this call + // Copy smtProofLocalExitRoot + calldatacopy( + 4, // Memory offset, signature = 4 bytes + compressedClaimCallsOffset, // calldata offset + _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 @@ -298,11 +306,17 @@ contract ClaimCompressor { // smtProofRollupExitRoots, // mainnetExitRoot, // rollupExitRoot - let currentCalldataPointer := _CONSTANT_VARIABLES_LENGTH + let currentCalldataPointer := add( + compressedClaimCallsOffset, + _CONSTANT_VARIABLES_LENGTH + ) for { // initialization block, empty - } lt(currentCalldataPointer, compressedClaimCallsLen) { + } lt( + currentCalldataPointer, + add(compressedClaimCallsOffset, compressedClaimCallsLen) + ) { // after iteration block, empty } { // loop block, non empty ;) @@ -311,16 +325,16 @@ contract ClaimCompressor { switch shr(248, calldataload(currentCalldataPointer)) case 0 { // Write asset signature - mstore8(0, claimAssetSignature) - mstore8(1, shr(1, claimAssetSignature)) - mstore8(2, shr(2, claimAssetSignature)) - mstore8(3, shr(3, claimAssetSignature)) + mstore8(3, claimAssetSignature) + mstore8(2, shr(8, claimAssetSignature)) + mstore8(1, shr(16, claimAssetSignature)) + mstore8(0, shr(24, claimAssetSignature)) } case 1 { - mstore8(0, claimMessageSignature) - mstore8(1, shr(1, claimMessageSignature)) - mstore8(2, shr(2, claimMessageSignature)) - mstore8(3, shr(3, claimMessageSignature)) + mstore8(3, claimMessageSignature) + mstore8(2, shr(8, claimMessageSignature)) + mstore8(1, shr(16, claimMessageSignature)) + mstore8(0, shr(24, claimMessageSignature)) } // Add 1 byte of isMessage TODO @@ -374,7 +388,7 @@ contract ClaimCompressor { // Copy the next 64 bytes for the uint64(globalIndex[i]), calldatacopy( - add(memPointer, 23), // 24 bytes globalIndex Offset + add(memPointer, 24), // 24 bytes globalIndex Offset currentCalldataPointer, // calldata offset 8 // Copy uint64(globalIndex[i]) ) @@ -435,7 +449,7 @@ contract ClaimCompressor { // load metadataLen let metadataLen := shr( - 248, // 256 - 32(uint32(metadata[i].length)) = 224 + 224, // 256 - 32(uint32(metadata[i].length)) = 224 calldataload(currentCalldataPointer) ) @@ -443,7 +457,7 @@ contract ClaimCompressor { currentCalldataPointer := add(currentCalldataPointer, 4) - // mem pointer, add originNetwork(current) + // mem pointer, add metadata len memPointer := add(memPointer, 32) // Write metadata @@ -462,11 +476,14 @@ contract ClaimCompressor { memPointer := add(memPointer, metadataLen) - // clean mem just in case + // clean mem mstore(memPointer, 0) // len args should be a multiple of 32 bytes - metadataLen := add(metadataLen, mod(metadataLen, 32)) + let totalLenCall := add( + _CONSTANT_BYTES_PER_CLAIM, + add(metadataLen, mod(metadataLen, 32)) + ) // SHould i limit the gas TODO or the call let success := call( @@ -474,7 +491,7 @@ contract ClaimCompressor { bridgeAddress, // address 0, // value 0, // args offset - add(_CONSTANT_BYTES_PER_CLAIM, metadataLen), // argsSize + totalLenCall, // argsSize 0, // retOffset 0 // retSize ) diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts index 394e386b1..8558c6880 100644 --- a/test/contractsv2/ClaimCompressor.test.ts +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -67,7 +67,7 @@ describe("PolygonZkEVMBridge Contract", () => { const originNetwork = networkIDMainnet; const tokenAddress = ethers.hexlify(ethers.randomBytes(20)); const amount = ethers.parseEther("10"); - const destinationNetwork = networkIDMainnet; + const destinationNetwork = networkID; const destinationAddress = acc1.address; const metadata = metadataToken; const metadataHash = ethers.solidityPackedKeccak256(["bytes"], [metadata]); @@ -95,39 +95,6 @@ describe("PolygonZkEVMBridge Contract", () => { const indexRandom = 3; - const encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ - proofLocal, - proofLocal, - 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); - const proofs = [proofLocal]; const indexes = [index]; const originNetworks = [originNetwork]; @@ -139,7 +106,7 @@ describe("PolygonZkEVMBridge Contract", () => { const sequenceForced = { smtProofLocalExitRoot: proofLocal, - globalIndex: index, + globalIndex: indexRandom, originNetwork: originNetwork, originAddress: tokenAddress, destinationAddress: destinationAddress, @@ -148,24 +115,40 @@ describe("PolygonZkEVMBridge Contract", () => { isMessage: false, } as any; - console.log(proofs[0], mainnetExitRoot, ethers.ZeroHash, [sequenceForced]); + const encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ + proofLocal, + proofLocal, + indexRandom, + mainnetExitRoot, + ethers.ZeroHash, + originNetwork, + tokenAddress, + destinationNetwork, + destinationAddress, + amount, + metadata, + ]); + + //console.log(proofs[0], mainnetExitRoot, ethers.ZeroHash, [sequenceForced]); const compressedMultipleBytes = await claimCompressor.compressClaimCall( proofs[0], mainnetExitRoot, ethers.ZeroHash, [sequenceForced] ); - console.log({compressedMultipleBytes}); + //console.log({compressedMultipleBytes}); + //console.log(await claimCompressor.decompressClaimCall(compressedMultipleBytes)); const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); for (const log of receipt?.logs) { const parsedLog = bridgeReceiverMock.interface.parseLog(log); - console.log({parsedLog}); + //console.log({parsedLog}); + expect(parsedLog?.args[0]).to.be.equal(encodedCall); } await expect(claimCompressor.decompressClaimCall(compressedMultipleBytes)) .to.emit(bridgeReceiverMock, "FallbackEvent") - .withArgs("0x"); + .withArgs(encodedCall); // .to.emit(bridgeReceiverMock, "ClaimAsset") // .withArgs( // proofs[0], @@ -181,13 +164,14 @@ describe("PolygonZkEVMBridge Contract", () => { // isMessage[0] // ); }); + 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 = networkIDMainnet; + const destinationNetwork = networkID; const destinationAddress = acc1.address; const metadata = metadataToken; const metadataHash = ethers.solidityPackedKeccak256(["bytes"], [metadata]); @@ -249,44 +233,60 @@ describe("PolygonZkEVMBridge Contract", () => { const customSignedTx = processorUtils.rawTxToCustomRawTx(txSigned); // Compute calldatas - for (let i = 1; i < 0; i++) { - const proofs = [] as any; - const indexes = [] as any; - const originNetworks = []; - const tokenAddresses = []; - const destinationAddresses = []; - const amounts = []; - const metadatas = []; - const isMessage = []; + 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); - proofs.push(proofLocal); - indexes.push(index); - originNetworks.push(originNetwork); - tokenAddresses.push(tokenAddress); - destinationAddresses.push(destinationAddress); - amounts.push(amount); - metadatas.push(metadata); - isMessage.push(false); + const sequenceForced = { + smtProofLocalExitRoot: proofLocal, + globalIndex: index, + originNetwork: originNetwork, + originAddress: tokenAddress, + destinationAddress: destinationAddress, + amount: amount, + metadata: metadata, + isMessage: false, + } as any; + + sequenceForcedStructs.push(sequenceForced); } const compressedMultipleBytes = await claimCompressor.compressClaimCall( - proofs[0], + proofLocal, mainnetExitRoot, ethers.ZeroHash, - proofs, - indexes, - originNetworks, - tokenAddresses, - destinationAddresses, - amounts, - metadatas, - isMessage + sequenceForcedStructs ); + // ASsert correctness + const receipt = await (await claimCompressor.decompressClaimCall(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); + } + const txCompressedMultiple = { data: compressedMultipleBytes, to: bridge.address, @@ -304,6 +304,7 @@ describe("PolygonZkEVMBridge Contract", () => { console.log({ numClaims: i, + gasUsed: receipt?.gasUsed, dataClaimCall: encodedCall.length * i, dataCompressedCall: compressedMultipleBytes.length, ratioData: compressedMultipleBytes.length / (encodedCall.length * i), From 11a02d499eacb9a14943e6ddfed366db4293fca6 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Sat, 3 Feb 2024 22:58:00 +0100 Subject: [PATCH 07/10] it wooorks --- contracts/v2/mocks/BridgeReceiverMock.sol | 4 +- contracts/v2/utils/ClaimCompressor.sol | 16 +- package-lock.json | 4 +- test/contractsv2/ClaimCompressor.test.ts | 208 +++++++++++++--------- 4 files changed, 138 insertions(+), 94 deletions(-) diff --git a/contracts/v2/mocks/BridgeReceiverMock.sol b/contracts/v2/mocks/BridgeReceiverMock.sol index cf1954fa3..11364419b 100644 --- a/contracts/v2/mocks/BridgeReceiverMock.sol +++ b/contracts/v2/mocks/BridgeReceiverMock.sol @@ -65,7 +65,7 @@ contract BridgeReceiverMock { * @param amount Amount of tokens * @param metadata Abi encoded metadata if any, empty otherwise */ - function claimAssetAA( + function claimAsset( bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, uint256 globalIndex, @@ -116,7 +116,7 @@ contract BridgeReceiverMock { * @param amount message value * @param metadata Abi encoded metadata if any, empty otherwise */ - function claimMessageBB( + function claimMessage( bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, uint256 globalIndex, diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 4e95a0395..9d01a5a5f 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -154,7 +154,7 @@ contract ClaimCompressor { currentCompressClaimCalldata.smtProofLocalExitRoot[j] != compressClaimCalldata[0].smtProofLocalExitRoot[j] ) { - lastDifferentLevel = j; + lastDifferentLevel = j + 1; } } @@ -163,7 +163,7 @@ contract ClaimCompressor { for (uint256 j = 0; j < lastDifferentLevel; j++) { smtProofCompressed = abi.encodePacked( smtProofCompressed, - currentCompressClaimCalldata.smtProofLocalExitRoot[i] + currentCompressClaimCalldata.smtProofLocalExitRoot[j] ); } @@ -324,6 +324,7 @@ contract ClaimCompressor { // Read uint8 isMessageBool switch shr(248, calldataload(currentCalldataPointer)) case 0 { + // TODO optimization // Write asset signature mstore8(3, claimAssetSignature) mstore8(2, shr(8, claimAssetSignature)) @@ -482,10 +483,10 @@ contract ClaimCompressor { // len args should be a multiple of 32 bytes let totalLenCall := add( _CONSTANT_BYTES_PER_CLAIM, - add(metadataLen, mod(metadataLen, 32)) + add(metadataLen, mod(sub(32, mod(metadataLen, 32)), 32)) ) - // SHould i limit the gas TODO or the call + // SHould i limit the gas TODO of the call let success := call( gas(), // gas bridgeAddress, // address @@ -495,6 +496,13 @@ contract ClaimCompressor { 0, // retOffset 0 // retSize ) + + // Reset smtProofLocalExitRoot + calldatacopy( + 4, // Memory offset = 4 bytes + compressedClaimCallsOffset, // calldata offset + smtProofBytesToCopy // Copy smtProofBytesToCopy len + ) } } } 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 index 8558c6880..3d5cfebe2 100644 --- a/test/contractsv2/ClaimCompressor.test.ts +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -61,108 +61,126 @@ describe("PolygonZkEVMBridge Contract", () => { await claimCompressor.waitForDeployment(); }); - it("should check it works", async () => { + it("should check random values", 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++) { + + 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)); - const index = 0; - const proofLocal = merkleTreeLocal.getProofTreeByIndex(index); + // Mock rollup root, not necessary now + const randomIndex = 10; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(randomIndex); - const indexRandom = 3; + // Compute calldatas + for (let i = 1; i < totalLeafsMerkleTree; i++) { + const sequenceForcedStructs = [] as any; - const proofs = [proofLocal]; - const indexes = [index]; - const originNetworks = [originNetwork]; - const tokenAddresses = [tokenAddress]; - const destinationAddresses = [destinationAddress]; - const amounts = [amount]; - const metadatas = [metadata]; - const isMessage = [false]; - - const sequenceForced = { - smtProofLocalExitRoot: proofLocal, - globalIndex: indexRandom, - originNetwork: originNetwork, - originAddress: tokenAddress, - destinationAddress: destinationAddress, - amount: amount, - metadata: metadata, - isMessage: false, - } as any; + for (let j = 0; j < i; j++) { + const index = j; + const currentLeaf = leafs[j]; + const proofLocal = merkleTreeLocal.getProofTreeByIndex(j); - const encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ - proofLocal, - proofLocal, - indexRandom, - mainnetExitRoot, - ethers.ZeroHash, - originNetwork, - tokenAddress, - destinationNetwork, - destinationAddress, - amount, - metadata, - ]); + const sequenceForced = { + smtProofLocalExitRoot: proofLocal, + globalIndex: index, + originNetwork: currentLeaf.originNetwork, + originAddress: currentLeaf.tokenAddress, + destinationAddress: currentLeaf.destinationAddress, + amount: currentLeaf.amount, + metadata: currentLeaf.metadata, + isMessage: currentLeaf.leafType, + } as any; - //console.log(proofs[0], mainnetExitRoot, ethers.ZeroHash, [sequenceForced]); - const compressedMultipleBytes = await claimCompressor.compressClaimCall( - proofs[0], - mainnetExitRoot, - ethers.ZeroHash, - [sequenceForced] - ); - //console.log({compressedMultipleBytes}); + sequenceForcedStructs.push(sequenceForced); + } + + const compressedMultipleBytes = await claimCompressor.compressClaimCall( + proofLocal, + mainnetExitRoot, + rollupExitRoot, + sequenceForcedStructs + ); + + // ASsert correctness + const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); + for (let k = 0; k < receipt?.logs.length; k++) { + const currentLog = receipt?.logs[k]; + const currenSequenceForcedStructs = sequenceForcedStructs[k]; - //console.log(await claimCompressor.decompressClaimCall(compressedMultipleBytes)); - const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); + const decodeFunctionName = currenSequenceForcedStructs.isMessage ? "claimMessage" : "claimAsset"; - for (const log of receipt?.logs) { - const parsedLog = bridgeReceiverMock.interface.parseLog(log); - //console.log({parsedLog}); - expect(parsedLog?.args[0]).to.be.equal(encodedCall); + 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(proofLocal); + 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); + } } - await expect(claimCompressor.decompressClaimCall(compressedMultipleBytes)) - .to.emit(bridgeReceiverMock, "FallbackEvent") - .withArgs(encodedCall); - // .to.emit(bridgeReceiverMock, "ClaimAsset") - // .withArgs( - // proofs[0], - // mainnetExitRoot, - // ethers.ZeroHash, - // proofs[0], - // indexes[0], - // originNetworks[0], - // tokenAddresses[0], - // destinationAddresses[0], - // amounts[0], - // metadatas[0], - // isMessage[0] - // ); }); it("should check Compression", async () => { @@ -284,7 +302,25 @@ describe("PolygonZkEVMBridge Contract", () => { ]); const parsedLog = bridgeReceiverMock.interface.parseLog(currentLog); - expect(parsedLog?.args[0]).to.be.equal(encodedCall); + //expect(parsedLog?.args[0]).to.be.equal(encodedCall); + + expect(parsedLog?.args.smtProofLocalExitRoot).to.be.deep.equal( + currenSequenceForcedStructs.smtProofLocalExitRoot + ); + expect(parsedLog?.args.smtProofRollupExitRoot).to.be.deep.equal(proofLocal); + 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 = { From 5ceb05b8147ea27bc87c4cb17394ddd7f82b4db5 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Mon, 5 Feb 2024 01:59:47 +0100 Subject: [PATCH 08/10] it wworks --- contracts/v2/utils/ClaimCompressor.sol | 137 +++++--------- test/contractsv2/ClaimCompressor.test.ts | 227 ++++++++++++++++++++++- 2 files changed, 269 insertions(+), 95 deletions(-) diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 9d01a5a5f..86c639669 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -11,15 +11,6 @@ pragma solidity 0.8.20; contract ClaimCompressor { uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; - // Leaf type asset - uint8 private constant _LEAF_TYPE_ASSET = 0; - - // Leaf type message - uint8 private constant _LEAF_TYPE_MESSAGE = 1; - - // Mainnet identifier - uint32 private constant _MAINNET_NETWORK_ID = 0; - // Indicate where's the mainnet flag bit in the global index uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; @@ -54,34 +45,6 @@ contract ClaimCompressor { // 32 bytes position uint256 internal constant _METADATA_OFSSET = 32 * 32 * 2 + 8 * 32 + 32; - // 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 - // ) - - // 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 - // ) - // PolygonZkEVMBridge address address private immutable _bridgeAddress; @@ -129,7 +92,7 @@ contract ClaimCompressor { * @param compressClaimCalldata compress claim calldata **/ function compressClaimCall( - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, // TODO remove, is not unique bytes32 mainnetExitRoot, bytes32 rollupExitRoot, CompressClaimCallData[] calldata compressClaimCalldata @@ -172,13 +135,9 @@ contract ClaimCompressor { uint8(lastDifferentLevel), smtProofCompressed, uint8(currentCompressClaimCalldata.globalIndex >> 64), // define byte with all small values TODO - //bool(globalIndex & _GLOBAL_INDEX_MAINNET_FLAG != 0), // get isMainnet bool uint64(currentCompressClaimCalldata.globalIndex), - // mainnetExitRoot, - // rollupExitRoot, currentCompressClaimCalldata.originNetwork, currentCompressClaimCalldata.originAddress, - // destinationNetwork currentCompressClaimCalldata.destinationAddress, currentCompressClaimCalldata.amount, // could compress to 128 bits uint32(currentCompressClaimCalldata.metadata.length), @@ -194,14 +153,11 @@ contract ClaimCompressor { return totalCompressedClaim; } - function decompressClaimCall(bytes calldata compressedClaimCalls) external { - // Starts with the common parameters for all the claims: - - // smtProofLocalExitRoots bytes32[32] - // smtProofRollupExitRoot bytes32[32] - // mainnetExitRoot bytes32 - // rollupExitRoot bytes32 - + function sendCompressedClaims( + bytes calldata compressedClaimCalls + ) external { + // 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; @@ -209,31 +165,15 @@ contract ClaimCompressor { 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 ¿? + // no need to be memory-safe, since the rest of the function will happen on assembly assembly { - // Get the last free memory pointer ( i might use 0 aswell) + // 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 - // THe final calldata should be something like: - // Calldata claimMessage /claimAsset - // function claimMessage( - // 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 - // ) - // Encoded compressed Data: // Constant parameters: @@ -243,19 +183,19 @@ contract ClaimCompressor { // mainnetExitRoot, // rollupExitRoot - // Parameters per claim tx + // Parameters per claim tx: // [ - // uint8(isMessage[i] ? 1 : 0), // define byte with all small values TODO - // uint8(lastDifferentLevel), - // smtProofCompressed, - // uint8(globalIndex[i] >> 64), // define byte with all small values TODO - // uint64(globalIndex[i]), - // originNetwork[i], - // originAddress[i], - // destinationAddress[i], - // amount[i], // could compress to 128 bits - // uint32(metadata[i].length), - // metadata[i] + // uint8(currentCompressClaimCalldata.isMessage ? 1 : 0), + // uint8(lastDifferentLevel), + // smtProofCompressed, + // 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 @@ -288,24 +228,20 @@ contract ClaimCompressor { 32 // Copy rollupExitRoot len ) - // Copy destinationAddress, since is constant, just use mstore + // 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) - // Skip constant parameters - - // smtProofLocalExitRoots[0], - // smtProofRollupExitRoots, - // mainnetExitRoot, - // rollupExitRoot + // Start the calldata pointer after the constant parameters let currentCalldataPointer := add( compressedClaimCallsOffset, _CONSTANT_VARIABLES_LENGTH @@ -321,6 +257,21 @@ contract ClaimCompressor { } { // 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 { @@ -378,16 +329,16 @@ contract ClaimCompressor { // global exit root --> first 23 bytes to 0 // | 191 bits | 1 bit | 32 bits | 32 bits | - // | mainnetFlag | rollupIndex | localRootIndex | + // | 0 | mainnetFlag | rollupIndex | localRootIndex | mstore8( add(memPointer, 23), // 23 bytes globalIndex Offset - shr(255, calldataload(currentCalldataPointer)) + shr(248, calldataload(currentCalldataPointer)) // 256 - 8(lastDifferentLevel) = 248 ) - // Add 1 bytes of uint8(globalIndex[i] >> 64 + // Add 1 bytes of uint8(globalIndex[i] >> 64) currentCalldataPointer := add(currentCalldataPointer, 1) - // Copy the next 64 bytes for the uint64(globalIndex[i]), + // Copy the next 64 bits for the uint64(globalIndex[i]), calldatacopy( add(memPointer, 24), // 24 bytes globalIndex Offset currentCalldataPointer, // calldata offset @@ -480,7 +431,7 @@ contract ClaimCompressor { // clean mem mstore(memPointer, 0) - // len args should be a multiple of 32 bytes + // 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)) diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts index 3d5cfebe2..0772e5177 100644 --- a/test/contractsv2/ClaimCompressor.test.ts +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -18,12 +18,17 @@ const {calculateSnarkInput, calculateAccInputHash, calculateBatchHashData} = con 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; @@ -59,6 +64,39 @@ describe("PolygonZkEVMBridge Contract", () => { 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 () => { @@ -140,7 +178,7 @@ describe("PolygonZkEVMBridge Contract", () => { ); // ASsert correctness - const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); + 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]; @@ -183,6 +221,178 @@ describe("PolygonZkEVMBridge Contract", () => { } }); + 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 = 20; + + 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 = { + 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( + proofLocalFirst, + 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"); @@ -280,7 +490,7 @@ describe("PolygonZkEVMBridge Contract", () => { ); // ASsert correctness - const receipt = await (await claimCompressor.decompressClaimCall(compressedMultipleBytes)).wait(); + 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]; @@ -368,3 +578,16 @@ function calculateCallDataCost(calldataBytes: string): number { 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; + } +} From d2b9972f0bc99cdabf23dbc2638c4fef091a2313 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Mon, 5 Feb 2024 03:38:48 +0100 Subject: [PATCH 09/10] finish --- contracts/v2/utils/ClaimCompressor.sol | 181 ++++++++++++++++------- test/contractsv2/ClaimCompressor.test.ts | 40 +++-- 2 files changed, 157 insertions(+), 64 deletions(-) diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 86c639669..1d63c462b 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -33,11 +33,9 @@ contract ClaimCompressor { uint256 internal constant _BYTE_LEN_CONSTANT_ARRAYS = 32 * 32; // The following parameters are constant in the encoded compressed claim call - // smtProofLocalExitRoots[0], - // smtProofRollupExitRoots, // mainnetExitRoot, // rollupExitRoot - uint256 internal constant _CONSTANT_VARIABLES_LENGTH = 32 * 32 * 2 + 32 * 2; + 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 @@ -52,7 +50,8 @@ contract ClaimCompressor { uint32 private immutable _networkID; /** - * @param smtProofRollupExitRoots Smt proof + * @param smtProofLocalExitRoot Smt proof + * @param smtProofRollupExitRoot Smt proof * @param globalIndex Index of the leaf * @param mainnetExitRoot Mainnet exit root * @param rollupExitRoot Rollup exit root @@ -66,6 +65,7 @@ contract ClaimCompressor { */ struct CompressClaimCallData { bytes32[32] smtProofLocalExitRoot; + bytes32[32] smtProofRollupExitRoot; uint256 globalIndex; uint32 originNetwork; address originAddress; @@ -86,54 +86,101 @@ contract ClaimCompressor { /** * @notice Foward all the claim parameters to compress them inside the contrat - * @param smtProofRollupExitRoot Smt proof * @param mainnetExitRoot Mainnet exit root * @param rollupExitRoot Rollup exit root * @param compressClaimCalldata compress claim calldata **/ function compressClaimCall( - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, // TODO remove, is not unique bytes32 mainnetExitRoot, bytes32 rollupExitRoot, CompressClaimCallData[] calldata compressClaimCalldata ) external pure returns (bytes memory) { // common parameters for all the claims bytes memory totalCompressedClaim = abi.encodePacked( - compressClaimCalldata[0].smtProofLocalExitRoot, - smtProofRollupExitRoot, // coudl be encoded like (uint8 len, first len []) 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++) { - // Byte array that will be returned CompressClaimCallData memory currentCompressClaimCalldata = compressClaimCalldata[i]; - // compare smt proof against the first one + + // Compute Local Root compressed + + // compare smt proof against the last one uint256 lastDifferentLevel = 0; - for (uint256 j = 0; j < _DEPOSIT_CONTRACT_TREE_DEPTH; j++) { - if ( - currentCompressClaimCalldata.smtProofLocalExitRoot[j] != - compressClaimCalldata[0].smtProofLocalExitRoot[j] - ) { - lastDifferentLevel = j + 1; + 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; } - bytes memory smtProofCompressed; + currentSmtCompressed = abi.encodePacked( + currentSmtCompressed, + uint8(lastDifferentLevel) + ); for (uint256 j = 0; j < lastDifferentLevel; j++) { - smtProofCompressed = abi.encodePacked( - smtProofCompressed, - currentCompressClaimCalldata.smtProofLocalExitRoot[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 - uint8(lastDifferentLevel), - smtProofCompressed, + currentSmtCompressed, uint8(currentCompressClaimCalldata.globalIndex >> 64), // define byte with all small values TODO uint64(currentCompressClaimCalldata.globalIndex), currentCompressClaimCalldata.originNetwork, @@ -156,8 +203,8 @@ contract ClaimCompressor { function sendCompressedClaims( bytes calldata compressedClaimCalls ) external { - // TODO first rollupExitRoot, instead of zeroes, could be zero hashes - // Codecopy?¿ + // 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; @@ -178,8 +225,6 @@ contract ClaimCompressor { // Constant parameters: - // smtProofLocalExitRoots[0], - // smtProofRollupExitRoots, // mainnetExitRoot, // rollupExitRoot @@ -187,7 +232,11 @@ contract ClaimCompressor { // [ // uint8(currentCompressClaimCalldata.isMessage ? 1 : 0), // uint8(lastDifferentLevel), - // smtProofCompressed, + // smtProofCompressed: + // uint8(lastDifferentLevelLocal) + // smtProofCompressedLocal + // uint8(lastDifferentLevelRollup) + // smtProofCompressedRollup // uint8(currentCompressClaimCalldata.globalIndex >> 64), // uint64(currentCompressClaimCalldata.globalIndex), // currentCompressClaimCalldata.originNetwork, @@ -200,31 +249,33 @@ contract ClaimCompressor { // Write the constant parameters for all claims in this call - // Copy smtProofLocalExitRoot - calldatacopy( - 4, // Memory offset, signature = 4 bytes - compressedClaimCallsOffset, // calldata offset - _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 - ) + // 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 - add(compressedClaimCallsOffset, mul(64, 32)), // calldata offset, smtProofLocalExitRoots[0] + smtProofRollupExitRoots = 64*32 + 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, mul(65, 32)), // calldata offset, smtProofLocalExitRoots[0] + smtProofRollupExitRoots + mainnetExitRoot = 65*32 + add(compressedClaimCallsOffset, 32), // calldata offset, mainnetExitRoot = 32 32 // Copy rollupExitRoot len ) @@ -295,20 +346,20 @@ contract ClaimCompressor { // Mem pointer where the current data must be written let memPointer := 4 - // load lastDifferentLevel + // load lastDifferentLevelLocal let smtProofBytesToCopy := mul( shr( - 248, // 256 - 8(lastDifferentLevel) = 248 + 248, // 256 - 8(lastDifferentLevelLocal) = 248 calldataload(currentCalldataPointer) ), 32 ) - // Add 1 byte of lastDifferentLevel + // Add 1 byte of lastDifferentLevelLocal currentCalldataPointer := add(currentCalldataPointer, 1) calldatacopy( - 4, // Memory offset = 4 bytes + memPointer, // Memory offset currentCalldataPointer, // calldata offset smtProofBytesToCopy // Copy smtProofBytesToCopy len ) @@ -318,8 +369,35 @@ contract ClaimCompressor { currentCalldataPointer, smtProofBytesToCopy ) - // mem pointer, add smtProofLocalExitRoot(current) + smtProofRollupExitRoot(constant) - memPointer := add(memPointer, mul(32, 64)) + // 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 @@ -447,13 +525,6 @@ contract ClaimCompressor { 0, // retOffset 0 // retSize ) - - // Reset smtProofLocalExitRoot - calldatacopy( - 4, // Memory offset = 4 bytes - compressedClaimCallsOffset, // calldata offset - smtProofBytesToCopy // Copy smtProofBytesToCopy len - ) } } } diff --git a/test/contractsv2/ClaimCompressor.test.ts b/test/contractsv2/ClaimCompressor.test.ts index 0772e5177..29c79a0b3 100644 --- a/test/contractsv2/ClaimCompressor.test.ts +++ b/test/contractsv2/ClaimCompressor.test.ts @@ -157,6 +157,7 @@ describe("PolygonZkEVMBridge Contract", () => { const proofLocal = merkleTreeLocal.getProofTreeByIndex(j); const sequenceForced = { + smtProofRollupExitRoot: proofLocal, smtProofLocalExitRoot: proofLocal, globalIndex: index, originNetwork: currentLeaf.originNetwork, @@ -171,7 +172,6 @@ describe("PolygonZkEVMBridge Contract", () => { } const compressedMultipleBytes = await claimCompressor.compressClaimCall( - proofLocal, mainnetExitRoot, rollupExitRoot, sequenceForcedStructs @@ -203,7 +203,9 @@ describe("PolygonZkEVMBridge Contract", () => { expect(parsedLog?.args.smtProofLocalExitRoot).to.be.deep.equal( currenSequenceForcedStructs.smtProofLocalExitRoot ); - expect(parsedLog?.args.smtProofRollupExitRoot).to.be.deep.equal(proofLocal); + 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); @@ -232,7 +234,7 @@ describe("PolygonZkEVMBridge Contract", () => { const height = 32; const merkleTreeLocal = new MerkleTreeBridge(height); - const totalLeafsMerkleTree = 20; + const totalLeafsMerkleTree = 10; const leafs = []; for (let i = 0; i < totalLeafsMerkleTree; i++) { @@ -305,6 +307,7 @@ describe("PolygonZkEVMBridge Contract", () => { const globalIndex = computeGlobalIndex(index, 0, true); const sequenceForced = { + smtProofRollupExitRoot: proofLocal, smtProofLocalExitRoot: proofLocal, globalIndex: globalIndex, originNetwork: currentLeaf.originNetwork, @@ -349,7 +352,6 @@ describe("PolygonZkEVMBridge Contract", () => { } const compressedMultipleBytes = await realClaimCompressor.compressClaimCall( - proofLocalFirst, mainnetExitRoot, rollupExitRootSC, sequenceForcedStructs @@ -427,9 +429,10 @@ describe("PolygonZkEVMBridge Contract", () => { const indexRandom = 3; + const proofZeroes = new Array(32).fill(ethers.ZeroHash); const encodedCall = BridgeFactory.interface.encodeFunctionData("claimAsset", [ proofLocal, - proofLocal, + proofZeroes, indexRandom, mainnetExitRoot, ethers.ZeroHash, @@ -470,6 +473,7 @@ describe("PolygonZkEVMBridge Contract", () => { const sequenceForced = { smtProofLocalExitRoot: proofLocal, + smtProofRollupExitRoot: proofZeroes, globalIndex: index, originNetwork: originNetwork, originAddress: tokenAddress, @@ -483,13 +487,14 @@ describe("PolygonZkEVMBridge Contract", () => { } const compressedMultipleBytes = await claimCompressor.compressClaimCall( - proofLocal, 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]; @@ -517,7 +522,24 @@ describe("PolygonZkEVMBridge Contract", () => { expect(parsedLog?.args.smtProofLocalExitRoot).to.be.deep.equal( currenSequenceForcedStructs.smtProofLocalExitRoot ); - expect(parsedLog?.args.smtProofRollupExitRoot).to.be.deep.equal(proofLocal); + + 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); @@ -554,9 +576,9 @@ describe("PolygonZkEVMBridge Contract", () => { dataClaimCall: encodedCall.length * i, dataCompressedCall: compressedMultipleBytes.length, ratioData: compressedMultipleBytes.length / (encodedCall.length * i), - dataTotalTxClaimCall: customSignedTx.length * i, + dataTotalTxClaimCall: (customSignedTx.length / 2) * i, costCalldataTxClaimCall: customSignedCost * i, - dataTotalTxCompressedCall: customtxCompressedMultipleSigned.length, + dataTotalTxCompressedCall: customtxCompressedMultipleSigned.length / 2, calldataCostTxCompressed: customCompressedMultipleCost, ratioTxData: customtxCompressedMultipleSigned.length / (customSignedTx.length * i), ratioTxDataCost: customCompressedMultipleCost / (customSignedCost * i), From 5a141524839f8068a7d2b759b36adc870e6c2758 Mon Sep 17 00:00:00 2001 From: invocamanman Date: Mon, 5 Feb 2024 12:20:53 +0100 Subject: [PATCH 10/10] TODO --- contracts/v2/utils/ClaimCompressor.sol | 1 + deployment/v2/verifyContracts.js | 39 ++++++++++++-------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/v2/utils/ClaimCompressor.sol index 1d63c462b..f6dfc47f8 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/v2/utils/ClaimCompressor.sol @@ -203,6 +203,7 @@ contract ClaimCompressor { 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 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()