From 84798689afaa7a9ffbc2d81d8bb86447b4b2ea94 Mon Sep 17 00:00:00 2001 From: kelemeno <34402761+kelemeno@users.noreply.github.com> Date: Tue, 10 Sep 2024 13:21:02 +0100 Subject: [PATCH] chore: merge stable (#785) --- .../contracts/bridgehub/Bridgehub.sol | 150 +++++++++++++----- .../contracts/bridgehub/IBridgehub.sol | 4 + .../contracts/common/L1ContractErrors.sol | 22 ++- l1-contracts/deploy-scripts/Utils.sol | 5 +- .../foundry/l1/integration/GatewayTests.t.sol | 66 ++++++++ .../integration/_SharedZKChainDeployer.t.sol | 43 +++++ .../Bridgehub/experimental_bridge.t.sol | 6 +- 7 files changed, 248 insertions(+), 48 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index f3d318fa3..c87940d81 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -23,7 +23,7 @@ import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../comm import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {IMessageRoot} from "./IMessageRoot.sol"; import {ICTMDeploymentTracker} from "./ICTMDeploymentTracker.sol"; -import {AssetHandlerNotRegistered, ZKChainLimitReached, Unauthorized, CTMAlreadyRegistered, CTMNotRegistered, ZeroChainId, ChainIdTooBig, SharedBridgeNotSet, BridgeHubAlreadyRegistered, AddressTooLow, MsgValueMismatch, WrongMagicValue, ZeroAddress} from "../common/L1ContractErrors.sol"; +import {MigrationPaused, AssetIdAlreadyRegistered, ChainAlreadyLive, ChainNotLegacy, CTMNotRegistered, ChainIdNotRegistered, AssetHandlerNotRegistered, ZKChainLimitReached, CTMAlreadyRegistered, CTMNotRegistered, ZeroChainId, ChainIdTooBig, BridgeHubAlreadyRegistered, AddressTooLow, MsgValueMismatch, ZeroAddress, Unauthorized, SharedBridgeNotSet, WrongMagicValue, ChainIdAlreadyExists, ChainIdMismatch, ChainIdCantBeCurrentChain, EmptyAssetId, AssetIdNotSupported, IncorrectBridgeHubAddress} from "../common/L1ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -108,28 +108,38 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus } modifier onlyChainCTM(uint256 _chainId) { - require(msg.sender == chainTypeManager[_chainId], "BH: not chain CTM"); + if (msg.sender != chainTypeManager[_chainId]) { + revert Unauthorized(msg.sender); + } _; } modifier onlyL1() { - require(L1_CHAIN_ID == block.chainid, "BH: not L1"); + if (L1_CHAIN_ID != block.chainid) { + revert Unauthorized(msg.sender); + } _; } modifier onlySettlementLayerRelayedSender() { /// There is no sender for the wrapping, we use a virtual address. - require(msg.sender == SETTLEMENT_LAYER_RELAY_SENDER, "BH: not relayed senser"); + if (msg.sender != SETTLEMENT_LAYER_RELAY_SENDER) { + revert Unauthorized(msg.sender); + } _; } modifier onlyAssetRouter() { - require(msg.sender == assetRouter, "BH: not asset router"); + if (msg.sender != assetRouter) { + revert Unauthorized(msg.sender); + } _; } modifier whenMigrationsNotPaused() { - require(!migrationPaused, "BH: migrations paused"); + if (migrationPaused) { + revert MigrationPaused(); + } _; } @@ -218,11 +228,16 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @param _chainId The chainId of the legacy chain we are migrating. function setLegacyChainAddress(uint256 _chainId) external { address ctm = chainTypeManager[_chainId]; - require(ctm != address(0), "BH: chain not legacy"); - require(!zkChainMap.contains(_chainId), "BH: chain already migrated"); - /// Note we have to do this before CTM is upgraded. + if (ctm == address(0)) { + revert ChainNotLegacy(); + } + if (zkChainMap.contains(_chainId)) { + revert ChainAlreadyLive(); + } address chainAddress = IChainTypeManager(ctm).getZKChainLegacy(_chainId); - require(chainAddress != address(0), "BH: chain not legacy 2"); + if (chainAddress == address(0)) { + revert ChainNotLegacy(); + } _registerNewZKChain(_chainId, chainAddress); } @@ -260,7 +275,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice asset id can represent any token contract with the appropriate interface/functionality /// @param _baseTokenAssetId asset id of base token to be registered function addTokenAssetId(bytes32 _baseTokenAssetId) external onlyOwner { - require(!assetIdIsRegistered[_baseTokenAssetId], "BH: asset id already registered"); + if (assetIdIsRegistered[_baseTokenAssetId]) { + revert AssetIdAlreadyRegistered(); + } assetIdIsRegistered[_baseTokenAssetId] = true; emit BaseTokenAssetIdRegistered(_baseTokenAssetId); @@ -294,8 +311,12 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); // This method can be accessed by l1CtmDeployer only - require(sender == address(l1CtmDeployer), "BH: not ctm deployer"); - require(chainTypeManagerIsRegistered[_assetAddress], "CTM not registered"); + if (sender != address(l1CtmDeployer)) { + revert Unauthorized(sender); + } + if (!chainTypeManagerIsRegistered[_assetAddress]) { + revert CTMNotRegistered(); + } bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); ctmAssetIdToAddress[assetInfo] = _assetAddress; @@ -325,32 +346,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes calldata _initData, bytes[] calldata _factoryDeps ) external onlyOwnerOrAdmin nonReentrant whenNotPaused onlyL1 returns (uint256) { - if (_chainId == 0) { - revert ZeroChainId(); - } - if (_chainId > type(uint48).max) { - revert ChainIdTooBig(); - } - require(_chainId != block.chainid, "BH: chain id must not match current chainid"); - if (_chainTypeManager == address(0)) { - revert ZeroAddress(); - } - if (_baseTokenAssetId == bytes32(0)) { - revert ZeroAddress(); - } - - if (!chainTypeManagerIsRegistered[_chainTypeManager]) { - revert CTMNotRegistered(); - } - - require(assetIdIsRegistered[_baseTokenAssetId], "BH: asset id not registered"); - - if (assetRouter == address(0)) { - revert SharedBridgeNotSet(); - } - if (chainTypeManager[_chainId] != address(0)) { - revert BridgeHubAlreadyRegistered(); - } + _validateChainParams({_chainId: _chainId, _assetId: _baseTokenAssetId, _chainTypeManager: _chainTypeManager}); chainTypeManager[_chainId] = _chainTypeManager; @@ -423,7 +419,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function ctmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { address ctmAddress = chainTypeManager[_chainId]; - require(ctmAddress != address(0), "chain id not registered"); + if (ctmAddress == address(0)) { + revert ChainIdNotRegistered(_chainId); + } return ctmAssetId(chainTypeManager[_chainId]); } @@ -786,6 +784,78 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus }); } + /// @dev Registers an already deployed chain with the bridgehub + /// @param _chainId The chain Id of the chain + /// @param _zkChain Address of the zkChain + function registerAlreadyDeployedZKChain(uint256 _chainId, address _zkChain) external onlyOwner onlyL1 { + if (_zkChain == address(0)) { + revert ZeroAddress(); + } + if (zkChainMap.contains(_chainId)) { + revert ChainIdAlreadyExists(); + } + if (IZKChain(_zkChain).getChainId() != _chainId) { + revert ChainIdMismatch(); + } + + address ctm = IZKChain(_zkChain).getChainTypeManager(); + address chainAdmin = IZKChain(_zkChain).getAdmin(); + bytes32 chainBaseTokenAssetId = IZKChain(_zkChain).getBaseTokenAssetId(); + address bridgeHub = IZKChain(_zkChain).getBridgehub(); + + if (bridgeHub != address(this)) { + revert IncorrectBridgeHubAddress(bridgeHub); + } + + _validateChainParams({_chainId: _chainId, _assetId: chainBaseTokenAssetId, _chainTypeManager: ctm}); + + chainTypeManager[_chainId] = ctm; + + baseTokenAssetId[_chainId] = chainBaseTokenAssetId; + settlementLayer[_chainId] = block.chainid; + + _registerNewZKChain(_chainId, _zkChain); + messageRoot.addNewChain(_chainId); + + emit NewChain(_chainId, ctm, chainAdmin); + } + + function _validateChainParams(uint256 _chainId, bytes32 _assetId, address _chainTypeManager) internal view { + if (_chainId == 0) { + revert ZeroChainId(); + } + + if (_chainId > type(uint48).max) { + revert ChainIdTooBig(); + } + + if (_chainId == block.chainid) { + revert ChainIdCantBeCurrentChain(); + } + + if (_chainTypeManager == address(0)) { + revert ZeroAddress(); + } + if (_assetId == bytes32(0)) { + revert EmptyAssetId(); + } + + if (!chainTypeManagerIsRegistered[_chainTypeManager]) { + revert CTMNotRegistered(); + } + + if (!assetIdIsRegistered[_assetId]) { + revert AssetIdNotSupported(_assetId); + } + + if (assetRouter == address(0)) { + revert SharedBridgeNotSet(); + } + if (chainTypeManager[_chainId] != address(0)) { + revert BridgeHubAlreadyRegistered(); + } + } + /*////////////////////////////////////////////////////////////// PAUSE //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 73f02527f..440d6236a 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -116,6 +116,8 @@ interface IBridgehub is IAssetHandler, IL1AssetHandler { function migrationPaused() external view returns (bool); + function admin() external view returns (address); + /// Mailbox forwarder function proveL2MessageInclusion( @@ -224,4 +226,6 @@ interface IBridgehub is IAssetHandler, IL1AssetHandler { function L1_CHAIN_ID() external view returns (uint256); function setLegacyBaseTokenAssetId(uint256 _chainId) external; + + function registerAlreadyDeployedZKChain(uint256 _chainId, address _hyperchain) external; } diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 99a632c53..7f2bc781d 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -39,8 +39,12 @@ error AmountMustBeGreaterThanZero(); error AssetHandlerDoesNotExist(bytes32 assetId); // error AssetIdMismatch(bytes32 expected, bytes32 supplied); +// +error AssetIdAlreadyRegistered(); // 0x0bfcef28 error AlreadyWhitelisted(address); +// 0x04a0b7e9 +error AssetIdNotSupported(bytes32 assetId); // 0x6afd6c20 error BadReturnData(); // 0x6ef9a972 @@ -65,6 +69,16 @@ error CanOnlyProcessOneBatch(); error CantExecuteUnprovenBatches(); // 0xe18cb383 error CantRevertExecutedBatch(); +// 0x24591d89 +error ChainIdAlreadyExists(); +// 0x717a1656 +error ChainIdCantBeCurrentChain(); +// 0xa179f8c9 +error ChainIdMismatch(); +// +error ChainIdNotRegistered(uint256 chainId); +// +error ChainNotLegacy(); // 0x78d2ed02 error ChainAlreadyLive(); // 0x8f620a06 @@ -91,6 +105,8 @@ error DiamondFreezeIncorrectState(); error DiamondNotFrozen(); // error EmptyAddress(); +// 0x2d4d012f +error EmptyAssetId(); // 0xfc7ab1d3 error EmptyBlobVersionHash(uint256 index); // @@ -124,6 +140,8 @@ error HashMismatch(bytes32 expected, bytes32 actual); error ZKChainLimitReached(); // error InsufficientAllowance(uint256 providedAllowance, uint256 requiredAmount); +// 0xdd381a4c +error IncorrectBridgeHubAddress(address bridgehub); // 0x826fb11e error InsufficientChainBalance(); // 0x356680b7 @@ -194,6 +212,8 @@ error MerkleIndexOutOfBounds(); error MerklePathEmpty(); // 0x1c500385 error MerklePathOutOfBounds(); +// +error MigrationPaused(); // 0xfa44b527 error MissingSystemLogs(uint256 expected, uint256 actual); // 0x4a094431 @@ -385,8 +405,6 @@ error IncorrectBatchBounds( uint256 processFromProvided, uint256 processToProvided ); -// 0x04a0b7e9 -error AssetIdNotSupported(bytes32 assetId); // 0x64107968 error AssetHandlerNotRegistered(bytes32 assetId); diff --git a/l1-contracts/deploy-scripts/Utils.sol b/l1-contracts/deploy-scripts/Utils.sol index 08c838ca8..7c387ac5f 100644 --- a/l1-contracts/deploy-scripts/Utils.sol +++ b/l1-contracts/deploy-scripts/Utils.sol @@ -5,8 +5,7 @@ pragma solidity 0.8.24; import {Vm} from "forge-std/Vm.sol"; -import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; -import {L2TransactionRequestDirect} from "contracts/bridgehub/IBridgehub.sol"; +import {L2TransactionRequestDirect, IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {IGovernance} from "contracts/governance/IGovernance.sol"; import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; @@ -232,7 +231,7 @@ library Utils { address bridgehubAddress, address l1SharedBridgeProxy ) internal { - Bridgehub bridgehub = Bridgehub(bridgehubAddress); + IBridgehub bridgehub = IBridgehub(bridgehubAddress); uint256 gasPrice = bytesToUint256(vm.rpc("eth_gasPrice", "[]")); uint256 requiredValueToDeploy = bridgehub.l2TransactionBaseCost( diff --git a/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol b/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol index 9224e3341..331f0d2f4 100644 --- a/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol @@ -31,6 +31,8 @@ import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.so import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; import {TxStatus} from "contracts/common/Messaging.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {IncorrectBridgeHubAddress} from "contracts/common/L1ContractErrors.sol"; contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker, GatewayDeployer { uint256 constant TEST_USERS_COUNT = 10; @@ -215,6 +217,70 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T vm.stopBroadcast(); } + function test_registerAlreadyDeployedZKChain() public { + gatewayScript.registerGateway(); + IChainTypeManager stm = IChainTypeManager(l1Script.getCTM()); + IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); + address owner = Ownable(address(bridgeHub)).owner(); + + { + uint256 chainId = currentZKChainId++; + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); + + address chain = _deployZkChain( + chainId, + baseTokenAssetId, + address(bridgehub.sharedBridge()), + owner, + stm.protocolVersion(), + stm.storedBatchZero(), + address(bridgehub) + ); + + address stmAddr = IZKChain(chain).getChainTypeManager(); + + vm.startBroadcast(owner); + bridgeHub.addChainTypeManager(stmAddr); + bridgeHub.addTokenAssetId(baseTokenAssetId); + bridgeHub.registerAlreadyDeployedZKChain(chainId, chain); + vm.stopBroadcast(); + + address bridgeHubStmForChain = bridgeHub.chainTypeManager(chainId); + bytes32 bridgeHubBaseAssetIdForChain = bridgeHub.baseTokenAssetId(chainId); + address bridgeHubChainAddressdForChain = bridgeHub.getZKChain(chainId); + address bhAddr = IZKChain(chain).getBridgehub(); + + assertEq(bridgeHubStmForChain, stmAddr); + assertEq(bridgeHubBaseAssetIdForChain, baseTokenAssetId); + assertEq(bridgeHubChainAddressdForChain, chain); + assertEq(bhAddr, address(bridgeHub)); + } + + { + uint256 chainId = currentZKChainId++; + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); + address chain = _deployZkChain( + chainId, + baseTokenAssetId, + address(bridgehub.sharedBridge()), + owner, + stm.protocolVersion(), + stm.storedBatchZero(), + address(bridgehub.sharedBridge()) + ); + + address stmAddr = IZKChain(chain).getChainTypeManager(); + + vm.startBroadcast(owner); + bridgeHub.addTokenAssetId(baseTokenAssetId); + vm.expectRevert( + abi.encodeWithSelector(IncorrectBridgeHubAddress.selector, address(bridgehub.sharedBridge())) + ); + bridgeHub.registerAlreadyDeployedZKChain(chainId, chain); + vm.stopBroadcast(); + } + } + function finishMoveChain() public { IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); IChainTypeManager ctm = IChainTypeManager(l1Script.getCTM()); diff --git a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol index 27bebd98e..747a6c311 100644 --- a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol +++ b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol @@ -1,13 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; +import {StdStorage, stdStorage} from "forge-std/Test.sol"; + import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; import {RegisterZKChainScript} from "deploy-scripts/RegisterZKChain.s.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import "@openzeppelin/contracts-v4/utils/Strings.sol"; import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {IDiamondInit} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; contract ZKChainDeployer is L1ContractDeployer { + using stdStorage for StdStorage; + RegisterZKChainScript deployScript; struct ZKChainDescription { @@ -133,4 +141,39 @@ contract ZKChainDeployer is L1ContractDeployer { // add this to be excluded from coverage report function testZKChainDeployer() internal {} + + function _deployZkChain( + uint256 _chainId, + bytes32 _baseTokenAssetId, + address _sharedBridge, + address _admin, + uint256 _protocolVersion, + bytes32 _storedBatchZero, + address _bridgeHub + ) internal returns (address) { + Diamond.DiamondCutData memory diamondCut = abi.decode( + l1Script.getInitialDiamondCutData(), + (Diamond.DiamondCutData) + ); + bytes memory initData; + + { + initData = bytes.concat( + IDiamondInit.initialize.selector, + bytes32(_chainId), + bytes32(uint256(uint160(address(_bridgeHub)))), + bytes32(uint256(uint160(address(this)))), + bytes32(_protocolVersion), + bytes32(uint256(uint160(_admin))), + bytes32(uint256(uint160(address(0x1337)))), + _baseTokenAssetId, + bytes32(uint256(uint160(_sharedBridge))), + _storedBatchZero, + diamondCut.initCalldata + ); + } + diamondCut.initCalldata = initData; + DiamondProxy hyperchainContract = new DiamondProxy{salt: bytes32(0)}(block.chainid, diamondCut); + return address(hyperchainContract); + } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol index f122b8739..ce8d21311 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -30,7 +30,7 @@ import {L2TransactionRequestTwoBridgesInner} from "contracts/bridgehub/IBridgehu import {ETH_TOKEN_ADDRESS, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS, TWO_BRIDGES_MAGIC_VALUE} from "contracts/common/Config.sol"; import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; -import {ZeroChainId, AddressTooLow, ChainIdTooBig, WrongMagicValue, SharedBridgeNotSet, TokenNotRegistered, BridgeHubAlreadyRegistered, MsgValueMismatch, SlotOccupied, CTMAlreadyRegistered, TokenAlreadyRegistered, Unauthorized, NonEmptyMsgValue, CTMNotRegistered, InvalidChainId} from "contracts/common/L1ContractErrors.sol"; +import {ZeroChainId, ChainAlreadyLive, AssetIdAlreadyRegistered, AddressTooLow, ChainIdTooBig, WrongMagicValue, SharedBridgeNotSet, TokenNotRegistered, BridgeHubAlreadyRegistered, MsgValueMismatch, SlotOccupied, CTMAlreadyRegistered, TokenAlreadyRegistered, Unauthorized, NonEmptyMsgValue, CTMNotRegistered, InvalidChainId} from "contracts/common/L1ContractErrors.sol"; contract ExperimentalBridgeTest is Test { using stdStorage for StdStorage; @@ -379,7 +379,7 @@ contract ExperimentalBridgeTest is Test { // An already registered token cannot be registered again vm.prank(bridgeOwner); - vm.expectRevert("BH: asset id already registered"); + vm.expectRevert(AssetIdAlreadyRegistered.selector); bridgeHub.addTokenAssetId(assetId); } @@ -412,7 +412,7 @@ contract ExperimentalBridgeTest is Test { // An already registered token cannot be registered again by randomCaller if (randomCaller != bridgeOwner) { vm.prank(bridgeOwner); - vm.expectRevert("BH: asset id already registered"); + vm.expectRevert(AssetIdAlreadyRegistered.selector); bridgeHub.addTokenAssetId(assetId); } }