diff --git a/contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol b/contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol index d8e149fd..8a2920ad 100644 --- a/contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol +++ b/contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol @@ -13,6 +13,11 @@ interface IBasePolygonZkEVMGlobalExitRoot { */ error OnlyGlobalExitRootUpdater(); + /** + * @dev Thrown when the caller is not the globalExitRootRemover + */ + error OnlyGlobalExitRootRemover(); + /** * @dev Thrown when trying to insert a global exit root that is already set */ diff --git a/contracts/v2/PolygonZkEVMBridgeV2.sol b/contracts/v2/PolygonZkEVMBridgeV2.sol index 1d4c5ef4..8c65fcf3 100644 --- a/contracts/v2/PolygonZkEVMBridgeV2.sol +++ b/contracts/v2/PolygonZkEVMBridgeV2.sol @@ -36,10 +36,10 @@ contract PolygonZkEVMBridgeV2 is bytes4 private constant _PERMIT_SIGNATURE_DAI = 0x8fcbaf0c; // Mainnet identifier - uint32 private constant _MAINNET_NETWORK_ID = 0; + uint32 internal constant _MAINNET_NETWORK_ID = 0; // ZkEVM identifier - uint32 private constant _ZKEVM_NETWORK_ID = 1; + uint32 internal constant _ZKEVM_NETWORK_ID = 1; // Leaf type asset uint8 private constant _LEAF_TYPE_ASSET = 0; @@ -48,7 +48,7 @@ contract PolygonZkEVMBridgeV2 is uint8 private constant _LEAF_TYPE_MESSAGE = 1; // Nullifier offset - uint256 private constant _MAX_LEAFS_PER_NETWORK = 2 ** 32; + uint256 internal constant _MAX_LEAFS_PER_NETWORK = 2 ** 32; // Indicate where's the mainnet flag bit in the global index uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; @@ -840,21 +840,10 @@ contract PolygonZkEVMBridgeV2 is uint32 leafIndex, uint32 sourceBridgeNetwork ) external view returns (bool) { - uint256 globalIndex; - - // For consistency with the previous setted nullifiers - if ( - networkID == _MAINNET_NETWORK_ID && - sourceBridgeNetwork == _ZKEVM_NETWORK_ID - ) { - globalIndex = uint256(leafIndex); - } else { - globalIndex = - uint256(leafIndex) + - uint256(sourceBridgeNetwork) * - _MAX_LEAFS_PER_NETWORK; - } - (uint256 wordPos, uint256 bitPos) = _bitmapPositions(globalIndex); + (uint256 wordPos, uint256 bitPos) = _bitmapPositions( + leafIndex, + sourceBridgeNetwork + ); uint256 mask = (1 << bitPos); return (claimedBitMap[wordPos] & mask) == mask; } @@ -868,21 +857,10 @@ contract PolygonZkEVMBridgeV2 is uint32 leafIndex, uint32 sourceBridgeNetwork ) private { - uint256 globalIndex; - - // For consistency with the previous setted nullifiers - if ( - networkID == _MAINNET_NETWORK_ID && - sourceBridgeNetwork == _ZKEVM_NETWORK_ID - ) { - globalIndex = uint256(leafIndex); - } else { - globalIndex = - uint256(leafIndex) + - uint256(sourceBridgeNetwork) * - _MAX_LEAFS_PER_NETWORK; - } - (uint256 wordPos, uint256 bitPos) = _bitmapPositions(globalIndex); + (uint256 wordPos, uint256 bitPos) = _bitmapPositions( + leafIndex, + sourceBridgeNetwork + ); uint256 mask = 1 << bitPos; uint256 flipped = claimedBitMap[wordPos] ^= mask; if (flipped & mask == 0) { @@ -938,14 +916,29 @@ contract PolygonZkEVMBridgeV2 is } /** - * @notice Function decode an index into a wordPos and bitPos - * @param index Index + * @notice Computes globalIndex and decodes it into a wordPos and bitPos + * @param _leafIndex Index + * @param _sourceBridgeNetwork Origin network */ function _bitmapPositions( - uint256 index - ) private pure returns (uint256 wordPos, uint256 bitPos) { - wordPos = uint248(index >> 8); - bitPos = uint8(index); + uint32 _leafIndex, + uint32 _sourceBridgeNetwork + ) internal view returns (uint256 wordPos, uint256 bitPos) { + uint256 globalIndex; + // For consistency with the previous setted nullifiers + if ( + networkID == _MAINNET_NETWORK_ID && + _sourceBridgeNetwork == _ZKEVM_NETWORK_ID + ) { + globalIndex = uint256(_leafIndex); + } else { + globalIndex = + uint256(_leafIndex) + + uint256(_sourceBridgeNetwork) * + _MAX_LEAFS_PER_NETWORK; + } + wordPos = uint248(globalIndex >> 8); + bitPos = uint8(globalIndex); } /** diff --git a/contracts/v2/interfaces/IBridgeL2SovereignChains.sol b/contracts/v2/interfaces/IBridgeL2SovereignChains.sol index f86176af..ba364390 100644 --- a/contracts/v2/interfaces/IBridgeL2SovereignChains.sol +++ b/contracts/v2/interfaces/IBridgeL2SovereignChains.sol @@ -21,6 +21,11 @@ interface IBridgeL2SovereignChains is IPolygonZkEVMBridgeV2 { */ error OnlyBridgeManager(); + /** + * @dev Thrown when sender is not the claims updater + */ + error OnlyClaimsUpdater(); + /** * @dev Thrown when bridge manager address is invalid */ @@ -71,6 +76,10 @@ interface IBridgeL2SovereignChains is IPolygonZkEVMBridgeV2 { */ error EmergencyStateNotAllowed(); + /** + * @dev Thrown when trying to unset a not setted claim + */ + error ClaimNotSet(); function initialize( uint32 _networkID, @@ -81,6 +90,7 @@ interface IBridgeL2SovereignChains is IPolygonZkEVMBridgeV2 { bytes memory _gasTokenMetadata, address _bridgeManager, address sovereignWETHAddress, - bool _sovereignWETHAddressIsNotMintable + bool _sovereignWETHAddressIsNotMintable, + address _claimsUpdater ) external; } diff --git a/contracts/v2/sovereignChains/BridgeL2SovereignChain.sol b/contracts/v2/sovereignChains/BridgeL2SovereignChain.sol index b302253c..23d3dc30 100644 --- a/contracts/v2/sovereignChains/BridgeL2SovereignChain.sol +++ b/contracts/v2/sovereignChains/BridgeL2SovereignChain.sol @@ -5,8 +5,6 @@ pragma solidity 0.8.20; import "../interfaces/IBridgeL2SovereignChains.sol"; import "../PolygonZkEVMBridgeV2.sol"; -// WARNING: not audited - /** * Sovereign chains bridge that will be deployed on all Sovereign chains * Contract responsible to manage the token interactions with other networks @@ -24,11 +22,25 @@ contract BridgeL2SovereignChain is // Bridge manager address; can set custom mapping for any token address public bridgeManager; + // Claims updater address; can unset claims from claimedBitmap. + // In case of initializing a chain with Full execution proofs, this address should be set to zero, otherwise, some malicious sequencer could insert invalid global exit roots, claim, go back and the execution would be correctly proved. + address public claimsUpdater; + /** * @dev Emitted when a bridge manager is updated */ event SetBridgeManager(address bridgeManager); + /** + * @dev Emitted when a bridge manager is updated + */ + event SetClaimsUpdater(address claimsUpdater); + + /** + * @dev Emitted when a claim is unset + */ + event UnsetClaim(uint32 leafIndex, uint32 sourceBridgeNetwork); + /** * @dev Emitted when a token address is remapped by a sovereign token address */ @@ -81,6 +93,7 @@ contract BridgeL2SovereignChain is * @param _bridgeManager bridge manager address * @param _sovereignWETHAddress sovereign WETH address * @param _sovereignWETHAddressIsNotMintable Flag to indicate if the wrapped ETH is not mintable + * @param _claimsUpdater Address that can unset claims from claimedBitmap. In case of initializing a chain with Full execution proofs, this address should be set to zero, otherwise, some malicious sequencer could insert invalid global exit roots, claim, go back and the execution would be correctly proved. */ function initialize( uint32 _networkID, @@ -91,12 +104,14 @@ contract BridgeL2SovereignChain is bytes memory _gasTokenMetadata, address _bridgeManager, address _sovereignWETHAddress, - bool _sovereignWETHAddressIsNotMintable + bool _sovereignWETHAddressIsNotMintable, + address _claimsUpdater ) public virtual initializer { networkID = _networkID; globalExitRootManager = _globalExitRootManager; polygonRollupManager = _polygonRollupManager; bridgeManager = _bridgeManager; + claimsUpdater = _claimsUpdater; // Set gas token if (_gasTokenAddress == address(0)) { @@ -167,10 +182,20 @@ contract BridgeL2SovereignChain is _; } + modifier onlyClaimsUpdater() { + if (claimsUpdater != msg.sender) { + revert OnlyClaimsUpdater(); + } + _; + } + /** * @notice Remap multiple wrapped tokens to a new sovereign token address * @dev This function is a "multi/batch call" to `setSovereignTokenAddress` - * @param sovereignTokenAddresses Array of SovereignTokenAddress to remap + * @param originNetworks Array of Origin networks + * @param originTokenAddresses Array od Origin token addresses, 0 address is reserved for ether + * @param sovereignTokenAddresses Array of Addresses of the sovereign wrapped token + * @param isNotMintable Array of Flags to indicate if the wrapped token is not mintable */ function setMultipleSovereignTokenAddress( uint32[] memory originNetworks, @@ -197,32 +222,6 @@ contract BridgeL2SovereignChain is } } - /** - * @notice Remap a wrapped token to a new sovereign token address - * @dev This function is used to allow any existing token to be mapped with - * origin token. - * @notice If this function is called multiple times for the same existingTokenAddress, - * this will override the previous calls and only keep the last sovereignTokenAddress. - * @notice The tokenInfoToWrappedToken mapping value is replaced by the new sovereign address but it's not the case for the wrappedTokenToTokenInfo map where the value is added, this way user will always be able to withdraw their tokens - * @param originNetwork Origin network - * @param originTokenAddress Origin token address, 0 address is reserved for ether - * @param sovereignTokenAddress Address of the sovereign wrapped token - * @param isNotMintable Flag to indicate if the wrapped token is not mintable - */ - function setSovereignTokenAddress( - uint32 originNetwork, - address originTokenAddress, - address sovereignTokenAddress, - bool isNotMintable - ) external onlyBridgeManager { - _setSovereignTokenAddress( - originNetwork, - originTokenAddress, - sovereignTokenAddress, - isNotMintable - ); - } - /** * @notice Remap a wrapped token to a new sovereign token address * @dev This function is used to allow any existing token to be mapped with @@ -304,7 +303,8 @@ contract BridgeL2SovereignChain is if ( tokenInfoToWrappedToken[tokenInfoHash] == address(0) || - tokenInfoToWrappedToken[tokenInfoHash] == legacySovereignTokenAddress + tokenInfoToWrappedToken[tokenInfoHash] == + legacySovereignTokenAddress ) { revert TokenNotRemapped(); } @@ -379,6 +379,25 @@ contract BridgeL2SovereignChain is ); } + /** + * @notice unset multiple claims from the claimedBitmap + * @dev This function is a "multi/batch call" to `unsetClaimedBitmap` + * @param leafIndexes Array of Index + * @param sourceBridgeNetworks Array of Origin networks + */ + function unsetMultipleClaimedBitmap( + uint32[] memory leafIndexes, + uint32[] memory sourceBridgeNetworks + ) external onlyClaimsUpdater { + if (leafIndexes.length != sourceBridgeNetworks.length) { + revert InputArraysLengthMismatch(); + } + + for (uint256 i = 0; i < leafIndexes.length; i++) { + _unsetClaimedBitmap(leafIndexes[i], sourceBridgeNetworks[i]); + } + } + /** * @notice Updated bridge manager address * @param _bridgeManager Bridge manager address @@ -391,6 +410,17 @@ contract BridgeL2SovereignChain is emit SetBridgeManager(bridgeManager); } + /** + * @notice Updated bridge manager address + * @param _claimsUpdater Bridge manager address + */ + function setClaimsUpdater( + address _claimsUpdater + ) external onlyClaimsUpdater { + claimsUpdater = _claimsUpdater; + emit SetClaimsUpdater(claimsUpdater); + } + /** * @notice Burn tokens from wrapped token to execute the bridge, if the token is not mintable it will be transferred * note This function has been extracted to be able to override it by other contracts like Bridge2SovereignChain @@ -441,6 +471,26 @@ contract BridgeL2SovereignChain is } } + /* + * @notice unset a claim from the claimedBitmap + * @param leafIndex Index + * @param sourceBridgeNetwork Origin network + */ + function _unsetClaimedBitmap( + uint32 leafIndex, + uint32 sourceBridgeNetwork + ) private { + (uint256 wordPos, uint256 bitPos) = _bitmapPositions(leafIndex, sourceBridgeNetwork); + uint256 mask = ~(1 << bitPos); + // Check if the bit is already unset + if ((claimedBitMap[wordPos] & (1 << bitPos)) == 0) { + revert ClaimNotSet(); + } + // Use bitwise AND with the negated mask to unset the bit + claimedBitMap[wordPos] &= mask; + emit UnsetClaim(leafIndex, sourceBridgeNetwork); + } + // @note This function is not used in the current implementation. We overwrite it to improve deployed bytecode size function activateEmergencyState() external diff --git a/contracts/v2/sovereignChains/GlobalExitRootManagerL2SovereignChain.sol b/contracts/v2/sovereignChains/GlobalExitRootManagerL2SovereignChain.sol index 4ec2b5e3..f1b0e7cd 100644 --- a/contracts/v2/sovereignChains/GlobalExitRootManagerL2SovereignChain.sol +++ b/contracts/v2/sovereignChains/GlobalExitRootManagerL2SovereignChain.sol @@ -4,8 +4,6 @@ pragma solidity 0.8.20; import "../../PolygonZkEVMGlobalExitRootL2.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -// WARNING: not audited - /** * Contract responsible for managing the exit roots for the Sovereign chains and global exit roots */ @@ -16,6 +14,10 @@ contract GlobalExitRootManagerL2SovereignChain is // globalExitRootUpdater address address public globalExitRootUpdater; + // globalExitRootRemover address + // In case of initializing a chain with Full execution proofs, this address should be set to zero, otherwise, some malicious sequencer could insert invalid global exit roots, claim, go back and the execution would be correctly proved. + address public globalExitRootRemover; + // Inserted GER counter uint256 public insertedGERCount; @@ -34,6 +36,11 @@ contract GlobalExitRootManagerL2SovereignChain is */ event SetGlobalExitRootUpdater(address indexed newGlobalExitRootUpdater); + /** + * @dev Emitted when the globalExitRootRemover is set + */ + event SetGlobalExitRootRemover(address indexed newGlobalExitRootRemover); + /** * @param _bridgeAddress PolygonZkEVMBridge contract address */ @@ -44,13 +51,18 @@ contract GlobalExitRootManagerL2SovereignChain is } /** - * @notice Initialize contract setting the globalExitRootUpdater + * @notice Initialize contract + * @param _globalExitRootUpdater setting the globalExitRootUpdater. + * @param _globalExitRootRemover In case of initializing a chain with Full execution proofs, this address should be set to zero, otherwise, some malicious sequencer could insert invalid global exit roots, claim and go back and the execution would be correctly proved. */ function initialize( - address _globalExitRootUpdater + address _globalExitRootUpdater, + address _globalExitRootRemover ) external virtual initializer { // set globalExitRootUpdater globalExitRootUpdater = _globalExitRootUpdater; + // set globalExitRootRemover + globalExitRootRemover = _globalExitRootRemover; } modifier onlyGlobalExitRootUpdater() { @@ -67,6 +79,13 @@ contract GlobalExitRootManagerL2SovereignChain is _; } + modifier onlyGlobalExitRootRemover() { + // Only allowed to be called by GlobalExitRootRemover + if (globalExitRootRemover != msg.sender) { + revert OnlyGlobalExitRootRemover(); + } + _; + } /** * @notice Insert a new global exit root * @param _newRoot new global exit root to insert @@ -89,7 +108,7 @@ contract GlobalExitRootManagerL2SovereignChain is */ function removeLastGlobalExitRoots( bytes32[] calldata gersToRemove - ) external onlyGlobalExitRootUpdater { + ) external onlyGlobalExitRootRemover { uint256 insertedGERCountCache = insertedGERCount; // Can't remove if not enough roots have been inserted if (gersToRemove.length > insertedGERCountCache) { @@ -127,4 +146,15 @@ contract GlobalExitRootManagerL2SovereignChain is globalExitRootUpdater = _globalExitRootUpdater; emit SetGlobalExitRootUpdater(_globalExitRootUpdater); } + + /** + * @notice Set the globalExitRootRemover + * @param _globalExitRootRemover new globalExitRootRemover address + */ + function setGlobalExitRootRemover( + address _globalExitRootRemover + ) external onlyGlobalExitRootRemover { + globalExitRootRemover = _globalExitRootRemover; + emit SetGlobalExitRootRemover(_globalExitRootRemover); + } } diff --git a/deployment/v2/create_rollup_parameters.json.example b/deployment/v2/create_rollup_parameters.json.example index f6926eba..8184f46e 100644 --- a/deployment/v2/create_rollup_parameters.json.example +++ b/deployment/v2/create_rollup_parameters.json.example @@ -19,6 +19,8 @@ "bridgeManager": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "sovereignWETHAddress": "0x0000000000000000000000000000000000000000", "sovereignWETHAddressIsNotMintable": false, - "globalExitRootUpdater": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + "globalExitRootUpdater": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "globalExitRootRemover": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "claimsUpdater": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" } } diff --git a/docker/scripts/v2/create_rollup_parameters_docker.json b/docker/scripts/v2/create_rollup_parameters_docker.json index 83f5bf27..34a70f81 100644 --- a/docker/scripts/v2/create_rollup_parameters_docker.json +++ b/docker/scripts/v2/create_rollup_parameters_docker.json @@ -14,11 +14,13 @@ "maxPriorityFeePerGas": "", "multiplierGas": "", "programVKey": "0xac51a6a2e513d02e4f39ea51d4d133cec200b940805f1054eabbb6d6412c959f", - "isVanillaClient": false, + "isVanillaClient": true, "sovereignParams": { "bridgeManager": "0xC7899Ff6A3aC2FF59261bD960A8C880DF06E1041", "sovereignWETHAddress": "0x0000000000000000000000000000000000000000", "sovereignWETHAddressIsNotMintable": false, - "globalExitRootUpdater": "0xB55B27Cca633A73108893985350bc26B8A00C43a" + "globalExitRootUpdater": "0xB55B27Cca633A73108893985350bc26B8A00C43a", + "globalExitRootRemover": "0xB55B27Cca633A73108893985350bc26B8A00C43a", + "claimsUpdater": "0xB55B27Cca633A73108893985350bc26B8A00C43a" } } diff --git a/test/contractsv2/BridgeL2GasTokenMappedSovereignChains.test.ts b/test/contractsv2/BridgeL2GasTokenMappedSovereignChains.test.ts index 8f4bc6ab..33bd205c 100644 --- a/test/contractsv2/BridgeL2GasTokenMappedSovereignChains.test.ts +++ b/test/contractsv2/BridgeL2GasTokenMappedSovereignChains.test.ts @@ -88,14 +88,11 @@ describe("SovereignChainBridge Gas tokens tests", () => { tokenInitialBalance ); const tokenWrappedFactory = await ethers.getContractFactory("TokenWrapped"); - await ethers.provider.send("hardhat_impersonateAccount", [sovereignChainBridgeContract.target]); + await ethers.provider.send("hardhat_impersonateAccount", [sovereignChainBridgeContract.target]); const bridgeMock = await ethers.getSigner(sovereignChainBridgeContract.target as any); - WETHToken = await tokenWrappedFactory.connect(bridgeMock).deploy( - tokenName, - tokenSymbol, - decimals, - {gasPrice: 0} - ); + WETHToken = await tokenWrappedFactory + .connect(bridgeMock) + .deploy(tokenName, tokenSymbol, decimals, {gasPrice: 0}); gasTokenAddress = polTokenContract.target; gasTokenNetwork = 0; @@ -110,7 +107,8 @@ describe("SovereignChainBridge Gas tokens tests", () => { metadataToken, ethers.Typed.address(bridgeManager.address), WETHToken.target, - false + false, + deployer.address // claims updater ); expect(await sovereignChainBridgeContract.WETHToken()).to.be.equal(WETHToken.target); }); @@ -185,7 +183,9 @@ describe("SovereignChainBridge Gas tokens tests", () => { await sovereignChainBridgeContract.verifyMerkleProof(leafValue, proofLocal, index, rootJSRollup) ).to.be.equal(true); // Remap weth token - await expect(sovereignChainBridgeContract.connect(bridgeManager).setSovereignWETHAddress(sovereignToken.target, true)) + await expect( + sovereignChainBridgeContract.connect(bridgeManager).setSovereignWETHAddress(sovereignToken.target, true) + ) .to.emit(sovereignChainBridgeContract, "SetSovereignWETHAddress") .withArgs(sovereignToken.target, true); // try claim without balance to transfer (from bridge) @@ -203,8 +203,7 @@ describe("SovereignChainBridge Gas tokens tests", () => { amount, metadata ) - ) - .to.revertedWith("ERC20: transfer amount exceeds balance"); + ).to.revertedWith("ERC20: transfer amount exceeds balance"); // Transfer tokens to bridge await sovereignToken.transfer(sovereignChainBridgeContract.target, amount); const balanceBridge = await sovereignToken.balanceOf(sovereignChainBridgeContract.target); diff --git a/test/contractsv2/BridgeL2GasTokensSovereignChains.test.ts b/test/contractsv2/BridgeL2GasTokensSovereignChains.test.ts index 96b83cec..1b4f9bbe 100644 --- a/test/contractsv2/BridgeL2GasTokensSovereignChains.test.ts +++ b/test/contractsv2/BridgeL2GasTokensSovereignChains.test.ts @@ -107,7 +107,8 @@ describe("SovereignChainBridge Gas tokens tests", () => { metadataToken, ethers.Typed.address(bridgeManager.address), ethers.ZeroAddress, - false + false, + deployer.address, // claims updater ); // calculate the weth address: diff --git a/test/contractsv2/BridgeL2SovereignChain.test.ts b/test/contractsv2/BridgeL2SovereignChain.test.ts index d3001b1d..4a8c70bf 100644 --- a/test/contractsv2/BridgeL2SovereignChain.test.ts +++ b/test/contractsv2/BridgeL2SovereignChain.test.ts @@ -97,7 +97,8 @@ describe("BridgeL2SovereignChain Contract", () => { "0x", ethers.Typed.address(bridgeManager), ethers.ZeroAddress, - false + false, + deployer.address // claims updater ); // deploy token @@ -129,7 +130,8 @@ describe("BridgeL2SovereignChain Contract", () => { metadataToken, ethers.Typed.address(bridgeManager.address), ethers.ZeroAddress, - false + false, + deployer.address // claims updater ) ).to.be.revertedWithCustomError(sovereignChainBridgeContract, "GasTokenNetworkMustBeZeroOnEther"); @@ -144,7 +146,8 @@ describe("BridgeL2SovereignChain Contract", () => { metadataToken, ethers.Typed.address(bridgeManager.address), bridge.target, // Not zero, revert - false + false, + deployer.address // claims updater ) ).to.be.revertedWithCustomError(sovereignChainBridgeContract, "InvalidSovereignWETHAddressParams"); @@ -158,7 +161,8 @@ describe("BridgeL2SovereignChain Contract", () => { metadataToken, ethers.Typed.address(bridgeManager.address), ethers.ZeroAddress, - true // Not false, revert + true, // Not false, revert, + deployer.address // claims updater ) ).to.be.revertedWithCustomError(sovereignChainBridgeContract, "InvalidSovereignWETHAddressParams"); }); @@ -376,15 +380,20 @@ describe("BridgeL2SovereignChain Contract", () => { // Test to remove more than one global exit root expect(await sovereignChainGlobalExitRootContract.insertGlobalExitRoot(computedGlobalExitRoot)) - .to.emit(sovereignChainGlobalExitRootContract, "InsertGlobalExitRoot") - .withArgs(computedGlobalExitRoot); - const computedGlobalExitRoot2 = "0x5946741ff5ff7732e1c7614ae327543a1d9f5870fcb8afbf146bd5ea75d6d519" // Random 32 bytes + .to.emit(sovereignChainGlobalExitRootContract, "InsertGlobalExitRoot") + .withArgs(computedGlobalExitRoot); + const computedGlobalExitRoot2 = "0x5946741ff5ff7732e1c7614ae327543a1d9f5870fcb8afbf146bd5ea75d6d519"; // Random 32 bytes expect(await sovereignChainGlobalExitRootContract.insertGlobalExitRoot(computedGlobalExitRoot2)) - .to.emit(sovereignChainGlobalExitRootContract, "InsertGlobalExitRoot") - .withArgs(computedGlobalExitRoot2); + .to.emit(sovereignChainGlobalExitRootContract, "InsertGlobalExitRoot") + .withArgs(computedGlobalExitRoot2); expect(await sovereignChainGlobalExitRootContract.globalExitRootMap(computedGlobalExitRoot2)).to.be.eq(2); - expect(await sovereignChainGlobalExitRootContract.removeLastGlobalExitRoots([computedGlobalExitRoot2, computedGlobalExitRoot])) + expect( + await sovereignChainGlobalExitRootContract.removeLastGlobalExitRoots([ + computedGlobalExitRoot2, + computedGlobalExitRoot, + ]) + ) .to.emit(sovereignChainGlobalExitRootContract, "RemoveGlobalExitRoot") .withArgs(computedGlobalExitRoot); @@ -601,7 +610,8 @@ describe("BridgeL2SovereignChain Contract", () => { "0x", ethers.Typed.address(bridgeManager), ethers.ZeroAddress, - false + false, + deployer.address // claims updater ) ).to.be.revertedWith("Initializable: contract is already initialized"); @@ -1017,6 +1027,10 @@ describe("BridgeL2SovereignChain Contract", () => { .withArgs(deployer.address, sovereignChainBridgeContract.target, amount); expect(false).to.be.equal(await sovereignChainBridgeContract.isClaimed(indexLocal, indexRollup + 1)); + // try un set the claim, should revert because is not claimed + await expect( + sovereignChainBridgeContract.unsetClaimedBitmap(indexLocal, indexRollup + 1) + ).to.be.revertedWithCustomError(sovereignChainBridgeContract, "ClaimNotSet"); await expect( sovereignChainBridgeContract.claimAsset( @@ -1055,6 +1069,15 @@ describe("BridgeL2SovereignChain Contract", () => { ) ).to.be.revertedWithCustomError(sovereignChainBridgeContract, "AlreadyClaimed"); expect(true).to.be.equal(await sovereignChainBridgeContract.isClaimed(indexLocal, indexRollup + 1)); + // Unclaim the claim + // Revert only claim updater allowed + await expect( + sovereignChainBridgeContract.connect(rollupManager).unsetClaimedBitmap(indexLocal, indexRollup + 1) + ).to.be.revertedWithCustomError(sovereignChainBridgeContract, "OnlyClaimsUpdater"); + + await sovereignChainBridgeContract.unsetClaimedBitmap(indexLocal, indexRollup + 1); + + expect(false).to.be.equal(await sovereignChainBridgeContract.isClaimed(indexLocal, indexRollup + 1)); }); it("should claim tokens from Rollup to Mainnet", async () => { @@ -1365,8 +1388,28 @@ describe("BridgeL2SovereignChain Contract", () => { // Insert an already inserted GER await expect( sovereignChainGlobalExitRootContract.insertGlobalExitRoot(computedGlobalExitRoot2) - ).to.revertedWithCustomError(sovereignChainGlobalExitRootContract, "GlobalExitRootAlreadySet"); + ).to.be.revertedWithCustomError(sovereignChainGlobalExitRootContract, "GlobalExitRootAlreadySet"); + + // Set new claims updater + await sovereignChainBridgeContract.setClaimsUpdater(deployer.address); + // Unset claims in bulk + expect(true).to.be.equal(await sovereignChainBridgeContract.isClaimed(indexLocal, indexRollup + 1)); + expect(true).to.be.equal(await sovereignChainBridgeContract.isClaimed(index2, indexRollup + 1)); + + await expect(sovereignChainBridgeContract.unsetMultipleClaimedBitmap( + [indexLocal, index2], + [indexRollup + 1] + )).to.be.revertedWithCustomError(sovereignChainBridgeContract, "InputArraysLengthMismatch"); + + await sovereignChainBridgeContract.unsetMultipleClaimedBitmap( + [indexLocal, index2], + [indexRollup + 1, indexRollup + 1] + ); + + expect(false).to.be.equal(await sovereignChainBridgeContract.isClaimed(indexLocal, indexRollup + 1)); + expect(false).to.be.equal(await sovereignChainBridgeContract.isClaimed(index2, indexRollup + 1)); }); + it("should claim tokens from Rollup to Mainnet, failing deploy wrapped", async () => { const originNetwork = networkIDRollup; const tokenAddress = polTokenContract.target;